uni-app页面使用u-view组件简化版列表页+详情页实现

本文介绍了一个简洁的项目列表页和详情页Vue实现方案。列表页包含顶部导航、状态筛选Tab、项目列表展示、搜索功能以及分页加载逻辑,采用模拟数据进行演示。详情页展示项目基本信息、描述和快捷操作入口,包含加载状态处理。代码特点包括:功能完整但简洁,添加了清晰注释,采用语义化命名和组件化设计,便于扩展和复用。该实现不依赖后端API,适合学习分享,展示了常见的列表-详情交互模式。

列表页 (SimpleList.vue)

复制代码
<template>
  <view class="container">
    <!-- 顶部导航 -->
    <cu-custom bgColor="bg-blue" :isBack="true">
      <block slot="content">项目列表</block>
      <block slot="right">
        <span @click="showSearch = true">
          <u-icon name="search" color="#fff" size="28"></u-icon>
        </span>
      </block>
    </cu-custom>

    <!-- 状态筛选Tab -->
    <view class="tabs">
      <view 
        v-for="(tab, index) in tabs" 
        :key="index"
        :class="['tab', activeTab === index ? 'active' : '']"
        @click="switchTab(index)"
      >
        {{ tab }}
      </view>
    </view>

    <!-- 项目列表 -->
    <scroll-view 
      scroll-y 
      class="list"
      @scrolltolower="loadMore"
      refresher-enabled
      :refresher-triggered="refreshing"
      @refresherrefresh="refreshData"
    >
      <view 
        v-for="(item, i) in listData" 
        :key="i" 
        class="item"
        @click="goDetail(item.id)"
      >
        <view class="item-header">
          <text class="title">{{ item.name }}</text>
          <text class="time">{{ item.startDate }} 至 {{ item.endDate }}</text>
        </view>
        
        <view class="item-footer">
          <view class="manager">
            <u-avatar :src="item.avatar" size="24"></u-avatar>
            <text>{{ item.manager }}</text>
          </view>
          <view class="progress">
            <text>进度: {{ item.progress }}%</text>
            <view class="progress-bar">
              <view class="progress-inner" :style="{width: item.progress + '%'}"></view>
            </view>
          </view>
        </view>
      </view>

      <u-loadmore :status="loadStatus" />
    </scroll-view>

    <!-- 搜索面板 -->
    <u-popup v-model="showSearch" mode="bottom" border-radius="20">
      <view class="search-panel">
        <view class="search-title">项目搜索</view>
        
        <u-form :model="searchForm">
          <u-form-item label="项目名称">
            <u-input v-model="searchForm.name" placeholder="请输入项目名称" />
          </u-form-item>
          
          <u-form-item label="负责人">
            <u-input v-model="searchForm.manager" placeholder="请输入负责人" />
          </u-form-item>
          
          <u-form-item label="状态">
            <u-radio-group v-model="searchForm.status">
              <u-radio :name="0">全部</u-radio>
              <u-radio :name="1">进行中</u-radio>
              <u-radio :name="2">已完成</u-radio>
            </u-radio-group>
          </u-form-item>
        </u-form>

        <view class="search-buttons">
          <u-button type="default" @click="resetSearch">重置</u-button>
          <u-button type="primary" @click="doSearch">搜索</u-button>
        </view>
      </view>
    </u-popup>
  </view>
</template>

<script>
export default {
  data() {
    return {
      tabs: ['全部', '进行中', '已完成', '已延期'],
      activeTab: 0,
      listData: [],
      page: 1,
      loadStatus: 'loadmore',
      refreshing: false,
      showSearch: false,
      searchForm: {
        name: '',
        manager: '',
        status: 0
      }
    }
  },
  onLoad() {
    this.loadData()
  },
  methods: {
    // 加载数据
    loadData() {
      // 模拟API请求
      setTimeout(() => {
        // 模拟数据
        const mockData = Array.from({length: 10}).map((_, i) => ({
          id: 'proj-' + (this.page * 10 + i),
          name: `示例项目${this.page * 10 + i}`,
          startDate: '2023-01-01',
          endDate: '2023-12-31',
          manager: ['张经理', '李主管', '王总监'][i % 3],
          avatar: '',
          progress: Math.floor(Math.random() * 100),
          status: [0, 1, 2][Math.floor(Math.random() * 3)] // 0-待启动 1-进行中 2-已完成
        }))
        
        if (this.page === 1) {
          this.listData = mockData
        } else {
          this.listData = [...this.listData, ...mockData]
        }
        
        this.loadStatus = this.page >= 3 ? 'nomore' : 'loadmore'
        this.refreshing = false
      }, 800)
    },
    
    // 切换Tab
    switchTab(index) {
      this.activeTab = index
      this.page = 1
      this.loadData()
    },
    
    // 加载更多
    loadMore() {
      if (this.loadStatus === 'nomore') return
      this.page++
      this.loadData()
    },
    
    // 下拉刷新
    refreshData() {
      this.refreshing = true
      this.page = 1
      this.loadData()
    },
    
    // 重置搜索
    resetSearch() {
      this.searchForm = {
        name: '',
        manager: '',
        status: 0
      }
    },
    
    // 执行搜索
    doSearch() {
      this.showSearch = false
      this.page = 1
      this.loadData()
    },
    
    // 跳转详情
    goDetail(id) {
      uni.navigateTo({
        url: '/pages/project/SimpleDetail?id=' + id
      })
    }
  }
}
</script>

<style lang="scss">
.container {
  background-color: #f5f5f5;
  min-height: 100vh;
}

.tabs {
  display: flex;
  background: #fff;
  padding: 20rpx 0;
  
  .tab {
    flex: 1;
    text-align: center;
    font-size: 28rpx;
    color: #666;
    
    &.active {
      color: #409EFF;
      font-weight: bold;
    }
  }
}

.list {
  height: calc(100vh - 180rpx);
  padding: 20rpx;
}

.item {
  background: #fff;
  border-radius: 12rpx;
  padding: 24rpx;
  margin-bottom: 20rpx;
  
  .item-header {
    margin-bottom: 20rpx;
    
    .title {
      font-size: 32rpx;
      font-weight: bold;
      color: #333;
      display: block;
      margin-bottom: 10rpx;
    }
    
    .time {
      font-size: 24rpx;
      color: #999;
    }
  }
  
  .item-footer {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding-top: 15rpx;
    border-top: 1px solid #f0f0f0;
    
    .manager {
      display: flex;
      align-items: center;
      font-size: 26rpx;
      color: #666;
      
      text {
        margin-left: 10rpx;
      }
    }
    
    .progress {
      text-align: right;
      font-size: 24rpx;
      color: #999;
      
      .progress-bar {
        width: 150rpx;
        height: 8rpx;
        background: #eee;
        border-radius: 4rpx;
        margin-top: 5rpx;
        overflow: hidden;
        
        .progress-inner {
          height: 100%;
          background: #409EFF;
          border-radius: 4rpx;
        }
      }
    }
  }
}

.search-panel {
  padding: 30rpx;
  
  .search-title {
    font-size: 32rpx;
    font-weight: bold;
    text-align: center;
    margin-bottom: 30rpx;
  }
  
  .search-buttons {
    display: flex;
    margin-top: 40rpx;
    gap: 20rpx;
    
    button {
      flex: 1;
    }
  }
}
</style>

详情页 (SimpleDetail.vue)

复制代码
<template>
  <view class="container">
    <!-- 顶部导航 -->
    <cu-custom bgColor="bg-blue" :isBack="true">
      <block slot="content">项目详情</block>
    </cu-custom>

    <!-- 加载状态 -->
    <u-loading mode="circle" v-if="loading"></u-loading>

    <!-- 内容区域 -->
    <view v-else class="content">
      <!-- 基本信息 -->
      <view class="card">
        <view class="title">{{ detail.name }}</view>
        
        <view class="info-row">
          <text class="label">时间:</text>
          <text class="value">{{ detail.startDate }} 至 {{ detail.endDate }}</text>
        </view>
        
        <view class="info-row">
          <text class="label">负责人:</text>
          <view class="value">
            <u-avatar :src="detail.avatar" size="30"></u-avatar>
            <text style="margin-left:10rpx">{{ detail.manager }}</text>
          </view>
        </view>
        
        <view class="info-row">
          <text class="label">进度:</text>
          <view class="progress">
            <text>{{ detail.progress }}%</text>
            <view class="progress-bar">
              <view class="progress-inner" :style="{width: detail.progress + '%'}"></view>
            </view>
          </view>
        </view>
      </view>

      <!-- 项目描述 -->
      <view class="card">
        <view class="section-title">项目描述</view>
        <view class="desc">{{ detail.desc || '暂无描述' }}</view>
      </view>

      <!-- 快捷操作 -->
      <view class="card">
        <view class="section-title">快捷操作</view>
        <view class="actions">
          <view class="action-item" @click="goPage('task')">
            <u-icon name="list" size="40" color="#409EFF"></u-icon>
            <text>查看任务</text>
          </view>
          
          <view class="action-item" @click="goPage('file')">
            <u-icon name="file-text" size="40" color="#409EFF"></u-icon>
            <text>项目文件</text>
          </view>
        </view>
      </view>
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      loading: true,
      detail: {}
    }
  },
  onLoad(options) {
    if (options.id) {
      this.loadDetail(options.id)
    }
  },
  methods: {
    // 加载详情数据
    loadDetail(id) {
      this.loading = true
      
      // 模拟API请求
      setTimeout(() => {
        this.detail = {
          id: id,
          name: `示例项目 ${id.slice(-3)}`,
          startDate: '2023-01-01',
          endDate: '2023-12-31',
          manager: '张经理',
          avatar: '',
          progress: 65,
          desc: '这是一个示例项目,用于演示详情页的实现。实际项目中这里会显示项目的详细描述信息,包括项目背景、目标、关键节点等内容。'
        }
        
        this.loading = false
      }, 800)
    },
    
    // 跳转页面
    goPage(type) {
      const urlMap = {
        task: '/pages/project/task?id=' + this.detail.id,
        file: '/pages/project/file?id=' + this.detail.id
      }
      
      if (urlMap[type]) {
        uni.navigateTo({
          url: urlMap[type]
        })
      }
    }
  }
}
</script>

<style lang="scss">
.container {
  background-color: #f5f5f5;
  min-height: 100vh;
}

.content {
  padding: 20rpx;
}

.card {
  background: #fff;
  border-radius: 12rpx;
  padding: 24rpx;
  margin-bottom: 20rpx;
  
  .title {
    font-size: 36rpx;
    font-weight: bold;
    margin-bottom: 20rpx;
    color: #333;
  }
  
  .info-row {
    display: flex;
    margin-bottom: 15rpx;
    font-size: 28rpx;
    
    .label {
      color: #666;
      width: 120rpx;
    }
    
    .value {
      flex: 1;
      color: #333;
      display: flex;
      align-items: center;
    }
    
    .progress {
      flex: 1;
      
      .progress-bar {
        width: 100%;
        height: 8rpx;
        background: #eee;
        border-radius: 4rpx;
        margin-top: 5rpx;
        overflow: hidden;
        
        .progress-inner {
          height: 100%;
          background: #409EFF;
          border-radius: 4rpx;
        }
      }
    }
  }
  
  .section-title {
    font-size: 30rpx;
    font-weight: bold;
    margin-bottom: 20rpx;
    color: #333;
    padding-bottom: 10rpx;
    border-bottom: 1px solid #f0f0f0;
  }
  
  .desc {
    font-size: 28rpx;
    color: #333;
    line-height: 1.6;
  }
  
  .actions {
    display: flex;
    justify-content: space-around;
    padding: 20rpx 0;
    
    .action-item {
      display: flex;
      flex-direction: column;
      align-items: center;
      
      text {
        margin-top: 10rpx;
        font-size: 26rpx;
        color: #666;
      }
    }
  }
}
</style>

特点说明

  1. 简洁性

    • 列表页仅保留核心功能:Tab切换、搜索、下拉刷新、上拉加载

    • 详情页展示基本信息、描述和快捷操作入口

  2. 易读性

    • 添加了清晰的注释说明

    • 使用语义化的class命名

    • 保持一致的代码风格

  3. 功能性

    • 列表页包含完整的分页逻辑

    • 详情页有加载状态处理

    • 保留了必要的交互功能

  4. 可扩展性

    • 数据结构清晰,易于添加新字段

    • 组件化设计,方便复用

  5. 适合分享

    • 使用模拟数据,不依赖后端API

    • 无敏感业务信息

    • 完整的功能演示