《微信小程序》第八章:“我的“设计

系列文章

《微信小程序》https://blog.csdn.net/sen_shan/category_13069009.html

第七章:TabBar设计https://blog.csdn.net/sen_shan/article/details/153969785

文章目录

目录

系列文章

文章目录

前言

我的

一、组件定位

二、关键实现细节

订单清单

调试与测试


前言

这篇文章详细介绍了微信小程序中「个人中心」页面的设计与实现,主要包含以下内容:

页面功能模块:

用户登录状态展示与切换

订单入口(带角标提示)

功能菜单(地址管理、优惠券等)

退出登录功能

技术实现要点:

登录态判断使用本地token缓存

订单角标动态更新策略

统一的路由跳转规范

订单列表页面实现(支持Tab切换)

测试流程:

登录状态验证 订单查看功能

退出登录流程

重新登录验证

我的

html 复制代码
<template>
  <view class="mine-page">
    <!-- 头部:登录状态 -->
    <view class="header" >
      <image class="avatar" src="/static/img/avatar.png" mode="aspectFill" />
      <view class="info">
        <text class="name">{{ userName }}</text>
        <text class="desc">{{ userDesc }}</text>
      </view>
      <text class="btn" @tap="goAuth">{{ isLogin ? '退出' : '点击登录' }}</text>
    </view>

    <!-- 订单入口 -->
    <view class="order-card">
      <view class="title">我的订单</view>
      <view class="order-grid">
			<view v-for="o in orderList" :key="o.type" class="item" @tap="goOrder(o.type)">
			  <view class="icon-box">
				<image class="icon-img" :src="o.icon" />
				<view v-if="o.badge" class="badge">{{ o.badge }}</view>
			  </view>
			  <text class="label">{{ o.label }}</text>
			</view>

      </view>
    </view>

    <!-- 功能菜单 -->
    <view class="menu-list">
      <view v-for="m in menuList" :key="m.type" class="menu-item" @tap="onMenu(m.type)">
        <image class="icon-img" :src="m.icon" />
        <text class="label">{{ m.label }}</text>
        <text class="arrow iconfont icon-arrow-right" />
      </view>
    </view>

    <!-- 退出登录(已登录才显示) -->
    <view v-if="isLogin" class="logout" @tap="logout">退出登录</view>
  </view>
</template>

<script setup>
import { ref, computed, reactive } from 'vue'
import { onShow } from '@dcloudio/uni-app'

/* ---------- 状态 ---------- */
const token = ref('')
const userInfo = reactive({ name: '', phone: '' })

onShow(() => {
  token.value = uni.getStorageSync('token') || ''
  const info = uni.getStorageSync('userInfo') || {}
  console.log(userInfo)
  userInfo.name = info.username || ''
  userInfo.phone = info.phone || ''
})

/* ---------- 计算属性 ---------- */
const isLogin = computed(() => !!token.value)
const userName = computed(() => (isLogin.value ? userInfo.name : '游客'))
const userDesc = computed(() => (isLogin.value ?  '' : '登录后享受更多功能'))

/* ---------- 数据 ---------- */
const orderList = [
  { type: 'unpay',  label: '待付款', icon: '/static/icon/mine/unpay.png',  badge: 0 },
  { type: 'send',   label: '待发货', icon: '/static/icon/mine/send.png',   badge: 1 },
  { type: 'receive',label: '待收货', icon: '/static/icon/mine/receive.png',badge: 0 },
  { type: 'comment',label: '待评价', icon: '/static/icon/mine/comment.png',badge: 3 }
]
const menuList = [
  { type: 'address', label: '收货地址', icon: '/static/icon/mine/address.png' },
  { type: 'coupon',  label: '优惠券',  icon: '/static/icon/mine/coupon.png'  },
  { type: 'collect', label: '我的收藏', icon: '/static/icon/mine/collect.png' },
  { type: 'setting', label: '设置',     icon: '/static/icon/mine/setting.png' }
]

/* ---------- 方法 ---------- */
function goAuth() {
  if (isLogin.value) return logout()          // 已登录→走退出
  uni.navigateTo({ url: '/pages/login/index' }) // 未登录→去登录
}
function logout() {
  uni.showModal({
    title: '提示',
    content: '确定退出登录?',
    success: res => {
      if (res.confirm) {
        uni.removeStorageSync('token')
        uni.removeStorageSync('userInfo')
        token.value = ''
      }
    }
  })
}
function goOrder(type) {
  uni.navigateTo({ url: `/pages/order/index?type=${type}` })
}
function onMenu(type) {
  uni.navigateTo({ url: `/pages/mine/${type}` })
}
</script>

<style scoped>
/* ===== 全局基础 ===== */
.mine-page {
  background: #f5f5f5;
  min-height: 100vh;
  padding-bottom: calc(50px + env(safe-area-inset-bottom));
}

/* ===== 头部 ===== */
.header {
  display: flex;
  align-items: center;
  background: #fff;
  padding: 30rpx;
  margin-bottom: 20rpx;
}

.avatar {
  width: 100rpx;
  height: 100rpx;
  border-radius: 50%;
}

.info {
  flex: 1;
  margin-left: 20rpx;
}

.name {
  font-size: 32rpx;
  color: #333;
}

.desc {
  font-size: 24rpx;
  color: #999;
  margin-top: 6rpx;
}

.btn {
  font-size: 28rpx;
  color: #07c160;
  border: 1rpx solid #07c160;
  padding: 6rpx 16rpx;
  border-radius: 8rpx;
}

/* ===== 订单卡片 ===== */
.order-card {
  background: #fff;
  margin-bottom: 20rpx;
  padding: 20rpx 0;
}

.title {
  padding: 0 30rpx;
  font-size: 28rpx;
  color: #333;
  margin-bottom: 20rpx;
}

.order-grid {
  display: flex;
}

.item {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  position: relative;
}

.icon-box {
  position: relative;
  margin-bottom: 6rpx; /* 图标与文字间距 */
}

.icon-img {
  width: 40rpx;
  height: 40rpx;
}

.badge {
  position: absolute;
  top: -8rpx;
  right: -12rpx;
  background: #ff4757;
  color: #fff;
  font-size: 20rpx;
  line-height: 1;
  padding: 2rpx 8rpx;
  border-radius: 12rpx;
}

.label {
  font-size: 24rpx;
  color: #666;
}

/* ===== 菜单列表 ===== */
.menu-list {
  background: #fff;
  margin-bottom: 20rpx;
}

.menu-item {
  display: flex;
  align-items: center;
  padding: 26rpx 30rpx;
  border-bottom: 1rpx solid #f1f1f1;
}

.menu-item:last-child {
  border: none;
}

.menu-item .icon-img {
  width: 32rpx;
  height: 32rpx;
  margin-right: 20rpx;
}

.menu-item .label {
  flex: 1;
  font-size: 28rpx;
  color: #333;
}

.menu-item .arrow {
  font-size: 24rpx;
  color: #ccc;
}

/* ===== 退出登录 ===== */
.logout {
  margin: 40rpx 30rpx;
  text-align: center;
  background: #fff;
  padding: 24rpx;
  border-radius: 10rpx;
  font-size: 30rpx;
  color: #e64340;
}
</style>

一、组件定位

商城小程序「个人中心」模块的核心页面,承担:

  1. 登录态展示与切换

  2. 订单入口分流(带角标)

  3. 功能菜单跳转

  4. 全局退出登录

二、关键实现细节

1 登录态判断

TypeScript 复制代码
const isLogin = computed(() => !!token.value)

仅依赖本地 token,不做接口校验,减少 onShow 耗时。

如业务需要强校验,可在 onShow 中调用 Api 刷新。

2 角标刷新策略

目前写死 badge: 0/1/3 ;

真实场景在 onShow 中调 /api/order/count 赋值,保持与「我的」TabBar 角标同步。

3 跳转规范

TypeScript 复制代码
function goOrder(type) {
  uni.navigateTo({ url: `/pages/order/index?type=${type}` })
}

路径统一为 /pages/order/index ,接收 type 参数后自动高亮对应 Tab。

避免使用 redirectTo 防止用户无法返回「我的」

订单清单

新建pages\order\index.vue文件

html 复制代码
<template>
  <view class="order-page">
    <!-- 顶部 Tab -->
    <view class="tab-bar">
      <view
        v-for="t in tabs"
        :key="t.type"
        class="tab-item"
        :class="{ active: curTab === t.type }"
        @tap="curTab = t.type"
      >
        <text>{{ t.name }}</text>
        <view v-if="curTab === t.type" class="line" />
      </view>
    </view>

    <!-- 订单列表 -->
    <scroll-view class="order-list" scroll-y>
      <view v-if="showList.length">
        <view v-for="o in showList" :key="o.id" class="order-card">
          <view class="card-header">
			<text class="orderNo">{{ o.orderNo }}</text>
            <text class="time">{{ o.createTime }}</text>
            <text class="status" :style="{ color: '#07c160' }">{{ o.statusTxt }}</text>
          </view>

          <view class="goods-list">
            <view v-for="g in o.goods" :key="g.id" class="goods-item">
              <image class="goods-img" :src="g.img" />
              <view class="goods-info">
                <text class="goods-title">{{ g.title }}</text>
                <text class="goods-spec">{{ g.spec }} ×{{ g.num || 1 }}</text>
                <text class="goods-price">¥{{ g.price }}</text>
              </view>
            </view>
          </view>

          <view class="card-footer">
            <text class="total">共{{ o.totalNum }}件 合计:¥{{ o.totalPrice }}</text>
            <text class="btn" @tap="goDetail(o.id)">查看详情</text>
            <text v-if="o.status === 'unpay'" class="btn primary" @tap="goPay(o.id)">立即付款</text>
          </view>
        </view>
      </view>

      <!-- 空态 -->
      <view v-else class="empty">
        <image class="empty-img" src="/static/img/empty/order.png" />
        <text class="empty-txt">暂无订单</text>
      </view>
    </scroll-view>
  </view>
</template>

<script setup>
import { ref, computed } from 'vue'
import { onLoad } from '@dcloudio/uni-app'

onLoad((query) => {
  // 如果地址栏带了 type 且是合法值,就高亮对应 Tab
  const { type = 'all' } = query
  const allow = ['all', 'unpay', 'send', 'receive', 'comment']
  curTab.value = allow.includes(type) ? type : 'all'
})


/* ----------------- 顶部 Tab ----------------- */
const tabs = [
  { type: 'all',   name: '全部' },
  { type: 'unpay', name: '待付款' },
  { type: 'send',  name: '待发货' },
  { type: 'receive', name: '待收货' }
]
const curTab = ref('all')

/* ----------------- 死数据 ----------------- */
const orderList = ref([
  {
    id: 1,
    orderNo: 'DD2025060112000001',   // ← 新增
    createTime: '2025-06-01 12:00',
    status: 'unpay',
    statusTxt: '待付款',
    totalNum: 2,
    totalPrice: 299,
    goods: [
      { id: 11, title: 'uni-app 实战教程', spec: '彩色版', price: 149, img: '/static/goods/01.png' },
      { id: 12, title: 'uni-app 组件库',  spec: '专业版', price: 150, img: '/static/goods/02.png' }
    ]
  },
  {
    id: 2,
    orderNo: 'DD2025060210300002',   // ← 新增
    createTime: '2025-06-02 10:30',
    status: 'send',
    statusTxt: '待发货',
    totalNum: 1,
    totalPrice: 199,
    goods: [
      { id: 21, title: 'Vue3 组件库', spec: '专业版', price: 199, img: '/static/goods/03.png' }
    ]
  },
  {
    id: 3,
    orderNo: 'DD2025060315200003',   // ← 新增
    createTime: '2025-06-03 15:20',
    status: 'receive',
    statusTxt: '待收货',
    totalNum: 3,
    totalPrice: 447,
    goods: [
      { id: 31, title: 'JavaScript 高级', spec: '精装版', price: 89,  img: '/static/goods/04.png' },
      { id: 32, title: 'CSS 魔法',         spec: '电子版', price: 59,  img: '/static/goods/05.png' },
      { id: 33, title: 'HTML5 指南',      spec: '电子版', price: 299, img: '/static/goods/06.png' }
    ]
  },
  {
    id: 4,
    orderNo: 'DD2025060409000004',   // ← 新增
    createTime: '2025-06-04 09:00',
    status: 'finish',
    statusTxt: '已完成',
    totalNum: 1,
    totalPrice: 89,
    goods: [
      { id: 41, title: 'Node.js 实战', spec: '平装版', price: 89, img: '/static/goods/07.png' }
    ]
  }
])

/* ----------------- 计算属性 ----------------- */
const showList = computed(() =>
  curTab.value === 'all'
    ? orderList.value
    : orderList.value.filter(o => o.status === curTab.value)
)

/* ----------------- 事件 ----------------- */
function goDetail(id) {
  uni.navigateTo({ url: `/pages/order/detail?id=${id}` })
}
function goPay(id) {
  uni.showToast({ title: `去支付 #${id}`, icon: 'none' })
}
</script>

<style scoped>
/* ========== 页面框架 ========== */
.order-page {
  height: 100vh;
  display: flex;
  flex-direction: column;
  background: #f5f5f5;
}

/* ========== Tab 栏 ========== */
.tab-bar {
  display: flex;
  height: 88rpx;
  background: #fff;
  border-bottom: 1rpx solid #e5e5e5;
}
.tab-item {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 28rpx;
  color: #666;
  position: relative;
  transition: color 0.2s;
}
.tab-item.active {
  color: #07c160;
}
.tab-item .line {
  position: absolute;
  left: 50%;
  bottom: 0;
  width: 60rpx;
  height: 4rpx;
  background: #07c160;
  transform: translateX(-50%);
}

/* ========== 订单列表 ========== */
.order-list {
  flex: 1;
  overflow-y: auto;
  padding: 20rpx;
}
.order-card {
  background: #fff;
  border-radius: 12rpx;
  margin-bottom: 20rpx;
  padding: 24rpx;
}
.card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 16rpx;
}
.time {
  font-size: 24rpx;
  color: #999;
}
.status {
  font-size: 26rpx;
  color: #07c160;
}
.goods-list {
  margin-bottom: 16rpx;
}
.goods-item {
  display: flex;
  margin-bottom: 12rpx;
}
.goods-img {
  width: 120rpx;
  height: 120rpx;
  border-radius: 8rpx;
  margin-right: 16rpx;
}
.goods-info {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
}
.goods-title {
  font-size: 28rpx;
  color: #333;
  line-height: 40rpx;
  overflow: hidden;
  text-overflow: ellipsis;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}
.goods-spec {
  font-size: 24rpx;
  color: #999;
}
.goods-price {
  font-size: 28rpx;
  color: #e64340;
}
.card-footer {
  display: flex;
  justify-content: flex-end;
  align-items: center;
}
.total {
  font-size: 26rpx;
  color: #333;
  margin-right: 16rpx;
}
.btn {
  font-size: 26rpx;
  padding: 8rpx 20rpx;
  border-radius: 8rpx;
  border: 1rpx solid #ccc;
  color: #333;
  margin-left: 12rpx;
}
.btn.primary {
  color: #fff;
  background: #07c160;
  border-color: #07c160;
}

/* ========== 空态 ========== */
.empty {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}
.empty-img {
  width: 240rpx;
  height: 240rpx;
}
.empty-txt {
  font-size: 28rpx;
  color: #999;
  margin-top: 24rpx;
}
</style>

只是为了验证,所以技术文档暂不提供。

调试与测试

1.登录后进入我的

2.点击待发货

3.返回后点击退出

4.退出确认

5.点击登录

相关推荐
前端 贾公子3 小时前
小程序蓝牙打印探索与实践(上)
小程序
拙慕JULY5 小时前
小程序返回 base64 文件报错
开发语言·javascript·小程序
dh131222505255 小时前
按月季度销售业绩核算小程序
小程序·销售小程序·绩效小程序·业绩统计小程序·业绩核算小程序
拙慕JULY6 小时前
微信小程序自定义标题背景色
微信小程序·小程序
前端 贾公子8 小时前
小程序蓝牙打印探索与实践(下)
小程序·apache
00后程序员张8 小时前
Jenkins 自动上传 IPA 到 App Store 把发布步骤融入 CI/CD
android·ios·小程序·https·uni-app·iphone·webview
万岳科技系统开发11 小时前
骑手配送系统如何支持外卖与跑腿一体化运营
大数据·前端·小程序
2501_9159090611 小时前
iOS IPA文件反编译与打包操作方法详解
android·ios·小程序·https·uni-app·iphone·webview
克里斯蒂亚诺更新1 天前
微信小程序使用vant4 weapp自定义菜单 但是弹出层却被菜单遮挡的解决办法
微信小程序·小程序·notepad++
静Yu1 天前
从一个九宫格素材小程序,看轻量工具产品该如何优化体验
前端·微信小程序