From 3e813eb0c5b3314a2799014db813814d9e5fd0dc Mon Sep 17 00:00:00 2001
From: zjk <852185829@qq.com>
Date: 星期二, 13 十月 2026 10:42:55 +0800
Subject: [PATCH] feat: 搭建用户端小程序核心功能与页面
---
doc/design/用户端_首页与导航_20261012.md | 359 +++++++
hugray-user-miniapp/static/tabbar/tab-plan-active.png | 0
hugray-user-miniapp/pages/contact/index.vue | 194 +++
hugray-user-miniapp/utils/request.js | 11
hugray-user-miniapp/static/tabbar/tab-home.png | 0
hugray-user-miniapp/utils/stock-order.js | 27
hugray-user-miniapp/pages/address/list.vue | 245 ++++
hugray-user-miniapp/static/tabbar/tab-profile.png | 0
hugray-user-miniapp/components/stock-order-toast/stock-order-toast.vue | 190 +++
hugray-user-miniapp/pages/profile/index.vue | 485 +++++++++
hugray-user-miniapp/static/tabbar/tab-home-active.png | 0
hugray-user-miniapp/pages/health-plan/index.vue | 260 +++++
hugray-user-miniapp/pages/index/index.vue | 246 ++++
hugray-user-miniapp/pages/login/index.vue | 3
hugray-user-miniapp/static/tabbar/tab-plan.png | 0
hugray-user-miniapp/static/tabbar/tab-profile-active.png | 0
hugray-user-miniapp/static/tabbar/tab-order-active.png | 0
hugray-user-miniapp/pages/stock-order/list.vue | 44
hugray-user-miniapp/static/tabbar/tab-order.png | 0
hugray-user-miniapp/pages.json | 89 +
hugray-user-miniapp/pages/address/add.vue | 296 +++++
hugray-user-miniapp/config/index.js | 2
hugray-user-miniapp/App.vue | 40
hugray-user-miniapp/pages/order/index.vue | 568 +++++++++++
24 files changed, 3,033 insertions(+), 26 deletions(-)
diff --git "a/doc/design/\347\224\250\346\210\267\347\253\257_\351\246\226\351\241\265\344\270\216\345\257\274\350\210\252_20261012.md" "b/doc/design/\347\224\250\346\210\267\347\253\257_\351\246\226\351\241\265\344\270\216\345\257\274\350\210\252_20261012.md"
new file mode 100644
index 0000000..5e095d0
--- /dev/null
+++ "b/doc/design/\347\224\250\346\210\267\347\253\257_\351\246\226\351\241\265\344\270\216\345\257\274\350\210\252_20261012.md"
@@ -0,0 +1,359 @@
+# 用户端小程序 首页 / 底部导航 / 缺货订单全局提示 开发设计文档
+
+- 模块:`hugray-user-miniapp`(前端 UI 实现,本轮无后端改动)
+- 日期:2026-10-12
+- 需求来源:「参考图片以及 pen 源文件,实现首页以及底部 tab 的 ui 布局和样式,底部的缺货订单是一个全局的 toast,规则如图二,注意 icon 需要按照设计稿一比一还原」
+- 设计稿权威依据:`doc/琥歌瑞0928.pen`(首页主稿 line 4485 起;底部导航示例 line 14259;待支付/缺货提示体系 line 12839、横向通栏实例 line 17545)
+- 换算基准:设计稿 375pt 宽 → 750rpx(**pt × 2 = rpx**)
+
+已确认决策(用户逐项确认):
+
+| 序号 | 待澄清项 | 采用方案 |
+| --- | --- | --- |
+| 1 | 底部 tab 栏如何落地 | **新建 3 个占位页 + 注册路由**(健康方案 / 我的订单 / 个人中心),tab 可真实切换 |
+| 2 | 首页顶部沉浸式导航 | **只自绘品牌 Logo**;状态栏、灵动岛、微信原生胶囊由系统与微信渲染,避免重叠 |
+| 3 | 首页两张产品大图素材不在仓库 | **用渐变纯色块占位**,素材就绪后替换为 `image` |
+| 4 | 缺货订单数量数据来源 | **先本地 mock 数据**(`utils/stock-order.js` 收口) |
+
+---
+
+## 一、功能概述
+
+| 序号 | 功能 | 说明 |
+| --- | --- | --- |
+| 1 | 首页(`pages/index/index.vue`) | 沉浸式导航 + 明星产品展位(大图 / 轮播指示器 / 产品信息与行动区)+ 次席展位 |
+| 2 | 底部导航栏(`pages.json` → `tabBar`) | **原生 tabBar**,4 个 Tab(代谢300 / 健康方案 / 我的订单 / 个人中心),选中态、个人中心角标 |
+| 3 | 缺货订单全局提示(`components/stock-order-toast`) | **所有页面**均弹出;点击跳缺货订单列表页;单笔展示倒计时,多笔仅展示数量 |
+
+缺货提示落地方式:小程序 `App.vue` 不支持渲染模板,故采用**全局组件 + 各页面引入**方案(不使用 tabBar 层能力)。
+
+> 底部导航本轮明确采用**原生 tabBar**(在 `pages.json` 配置),不自定义组件;因此跳转统一用 `uni.switchTab`(仅 `reLaunch` 亦可,`navigateTo`/`redirectTo` 不可用)。
+
+---
+
+## 二、现状与关键约束
+
+### 2.1 底部导航采用原生 tabBar
+
+`pages.json` 原本**无 `tabBar` 配置**,本轮新增顶层 `tabBar` 字段:
+
+- 首页 `navigationStyle: "custom"`(沉浸式,自绘品牌区);
+- 底部导航为**原生 tabBar**(系统渲染于 webview 之上),页面内容区自动排除 tabBar 高度,页面内**无需为 tab 预留底部留白**;
+- 原生 tabBar **仅接受本地图片文件**作为图标(不支持 base64 / SVG / 网络图)→ `static/tabbar/*.png`;
+- 原生 tabBar **无 CSS 角标能力** → 用 `uni.setTabBarBadge` 设置。
+
+### 2.2 项目无 UI 库 / 无 lucide 图标库
+
+设计稿使用 lucide 图标库(`house` / `clipboard-check` / `package` / `circle-user-round` / `clipboard-list` / `chevron-right`),项目未引入该库。
+
+- 方案:**内联 base64 SVG**(`background-image: url('data:image/svg+xml;base64,...')`)。
+- 例外:**原生 tabBar 图标仅支持本地图片文件**,故 4 个 tab 图标(双态共 8 个)由 lucide SVG 光栅化为 81×81 PNG,落在 `static/tabbar/`。
+- 先例:`pages/login/register.vue` 已使用同款 base64 data-URI 图标背景(class `.field-icon`),风格一致。
+- 图标源:lucide 官方 SVG(`stroke-width=2`、`stroke-linecap/linejoin=round`),仅替换 `stroke` 颜色以适配双态。
+
+### 2.3 产品大图素材缺失
+
+设计稿引用的产品图(`微信图片_20260916202238_115_8451.png` 等)不在仓库 → 按决策以**同色系渐变块占位**,DOM 结构保留(便于后续替换为 `image`)。
+
+### 2.4 缺货订单数量无后端接口
+
+→ 本轮以本地 mock 收口在 `utils/stock-order.js`,接口就绪后仅替换 `getStockOrderCount()` 实现,调用方零改动。
+
+### 2.5 登录态校验复用
+
+各页面 `onShow` 统一调用 `utils/request.js` 的公共守卫 `ensureLogin()`(无 token 时 `uni.reLaunch` 至登录页)。
+
+---
+
+## 三、页面 / 路由变更(`pages.json`)
+
+| 路径 | 页面 | 导航样式 | 说明 |
+| --- | --- | --- | --- |
+| `pages/index/index` | 首页 | `navigationStyle: "custom"` | 沉浸式,自绘品牌区 |
+| `pages/health-plan/index` | 健康方案 | 原生标题栏「健康方案」 | tab 占位页(含 toast) |
+| `pages/order/index` | 我的订单 | 原生标题栏「我的订单」 | 占位页 |
+| `pages/profile/index` | 个人中心 | 原生标题栏「个人中心」 | 占位页 |
+| `pages/contact/index` | 联系我们 | `navigationStyle: "custom"` | 顶部深蓝品牌区自绘,个人中心「联系我们」菜单跳转的目标页;**不在 tabBar 列表** |
+| `pages/address/list` | 选择地址 | 原生标题栏「选择地址」(白底黑字) | 个人中心「收货地址」菜单跳转的目标页;**不在 tabBar 列表** |
+| `pages/stock-order/list` | 缺货订单 | 原生标题栏「缺货订单」 | 缺货提示点击跳转的目标页;**不在 tabBar 列表** |
+
+### 3.1 原生 tabBar 配置(`pages.json` → `tabBar`)
+
+```json
+{
+ "color": "#64748B",
+ "selectedColor": "#003E7E",
+ "backgroundColor": "#FFFFFF",
+ "borderStyle": "white",
+ "list": [
+ { "pagePath": "pages/index/index", "text": "代谢300", "iconPath": "static/tabbar/tab-home.png", "selectedIconPath": "static/tabbar/tab-home-active.png" },
+ { "pagePath": "pages/health-plan/index", "text": "健康方案", "iconPath": "static/tabbar/tab-plan.png", "selectedIconPath": "static/tabbar/tab-plan-active.png" },
+ { "pagePath": "pages/order/index", "text": "我的订单", "iconPath": "static/tabbar/tab-order.png", "selectedIconPath": "static/tabbar/tab-order-active.png" },
+ { "pagePath": "pages/profile/index", "text": "个人中心", "iconPath": "static/tabbar/tab-profile.png", "selectedIconPath": "static/tabbar/tab-profile-active.png" }
+ ]
+}
+```
+
+> `borderStyle` 仅支持 `black` / `white`,取 `white` 对应设计稿 `#F1F5F9` 淡描边。`iconPath` 路径不带前导斜杠。
+
+---
+
+## 四、视觉还原规格
+
+### 4.1 首页(375×812pt,fill `#F8FAFC`)
+
+| 元素 | 设计稿规格 | 实现值 |
+| --- | --- | --- |
+| 页面底色 | `#F8FAFC` | 同左;`.page` 100%×100vh,`overflow: hidden` |
+| 品牌区(沉浸导航) | 位于产品大图视口内左上,状态栏下方 | absolute 叠加于大图上方 z-index 2;`padding-top = 状态栏高度`;左右 24rpx |
+| 品牌 Logo | `metabolism-300-logo.png`(蓝「代谢」+ 绿「300」+ 绿波浪) | 168×88rpx(`aspectFit`) |
+| 明星展位-大图视口 | 375×406pt,fill `#FFFFFF`,mode cover | H812rpx,渐变占位 `180° #D9E7F7 → #E9F1FB 26% → #FFFFFF 54% → #F4F7FB` |
+| 轮播指示器(毛玻璃微胶囊) | x165 y376,H16 R8,fill `#FFFFFFB3`,stroke `#FFFFFF` 0.8,shadow `#0F172A12` y2 blur6,gap5 padding 0/7 | H32rpx R16rpx,`rgba(255,255,255,.7)` + 1rpx `#FFF` 描边 + `0 4rpx 12rpx` 阴影;居中、top 752rpx;gap10rpx、padding 0/14rpx |
+| 指示条-非激活 | 4×4 R2 `#003E7E30` | 8×8rpx R4rpx `rgba(0,62,126,.19)` |
+| 指示条-激活 | 14×4 R2 渐变 90° `#003E7E → #1E5EBC` | W28rpx H8rpx 渐变 90° `#003E7E → #1E5EBC` |
+| 产品信息与行动区 | 375×58pt,fill `#FFFFFF`,上下 1 描边 `#F1F5F9`,shadow `#0F172A06` y2 blur8,padding 0/18,space_between | H116rpx,同左(描边 1rpx,padding 0/36rpx) |
+| 英文型号编码 | `G&METS-II` 11pt 700 `#003E7E` 字距 1.2 | 22rpx 700 字距 2.4rpx `#003E7E` |
+| 产品中文主名 | `超分子基团复合肽` 17.5pt 800 `#0F172A` 字距 -0.2 | 35rpx 800 字距 -0.4rpx `#0F172A` |
+| 探索更多(胶囊) | H34 R17,渐变 90° `#82C236 → #639C1F`,shadow `#6AA02735` y3 blur8,gap6 padding 0/8/0/14 | H68rpx R34rpx,同渐变 + `0 6rpx 16rpx rgba(106,160,39,.21)`;文案 12.5pt 600 → 25rpx |
+| 探索更多-箭头底托 | 20×20 R10 `#FFFFFF28`,内嵌 lucide `chevron-right` 12×12 `#FFFFFF` | 40×40rpx R20rpx `rgba(255,255,255,.16)`;箭头 24×24rpx base64 |
+| 次席展位 | x0 y464,375×265pt,clip cover | H530rpx,渐变占位 `160° #4E3D94 → #6A5AC1 45% → #A99BE0` |
+
+### 4.2 底部导航栏(原生 tabBar,375×83pt)
+
+> 原生 tabBar 由系统渲染,**仅可通过 `pages.json` 配置有限属性**,以下为设计稿规格与原生能力的映射。
+
+| 元素 | 设计稿规格 | 原生实现 | 差异说明 |
+| --- | --- | --- | --- |
+| 容器 | fill `#FFFFFF` | `backgroundColor: "#FFFFFF"` | 一致 |
+| 顶部描边 | 1 描边 `#F1F5F9` | `borderStyle: "white"` | 原生仅 `black`/`white` 两档,取 `white`(系统绘制极淡描边) |
+| 容器阴影 | shadow `#0F172A08` y-4 blur16 | **不支持** | 原生 tabBar 无阴影配置项 |
+| Tab 组 | H48pt,4 等分 | 由系统按 `list` 数量等分 | 高度由系统控制(约 50px + 安全区) |
+| 图标底托 | 26×26pt,选中态 fill `#003E7E14` | **不支持** | 原生 tabBar 无选中底托能力 |
+| 图标 | 22×22pt,4 态:`house` / `clipboard-check` / `package` / `circle-user-round` | `static/tabbar/*.png`(81×81 PNG,系统缩放至 22pt 左右) | 未选中 `#94A3B8` / 选中 `#003E7E` |
+| Tab 文案 | 11pt,选中 `#003E7E` 700 / 未选中 `#64748B` 500 | `color: "#64748B"`、`selectedColor: "#003E7E"` | 原生**不支持字重区分**,颜色一致 |
+| 个人中心角标 | 14×14 R7 `#E53935`,白描边,白字 9/700 | `uni.setTabBarBadge({ index: 3, text })`(`App.vue` `onShow` 统一设置) | 样式由系统绘制,仅可传文本 |
+
+> 冲突取舍:pen「首页 frame」与「组件示例」对选中底托不一致 → 因原生 tabBar 无底托能力,**统一以图标 + 文案变色表达选中态**。个人中心角标数量本轮 mock(默认 1)。
+
+### 4.3 缺货订单全局提示(横向通栏标准态)
+
+| 元素 | 设计稿规格 | 实现值 |
+| --- | --- | --- |
+| 容器 | 351×40pt,R20,渐变 135° `#FFFFFFF6 → #FFFFFFE0`,stroke `#FFFFFF` 1.2,shadow `#0F294222` y4 blur14 | 左右各 24rpx,H80rpx R40rpx;渐变 135° `rgba(255,255,255,.96) → rgba(255,255,255,.88)`;2rpx `#FFF` 描边;`0 8rpx 28rpx rgba(15,41,66,.13)` |
+| 容器定位 | 悬浮于内容之上 | `position: fixed`;`bottom: calc(24rpx + 安全区)`;z-index 85 |
+| 前置图标 | lucide `clipboard-list` 15×15 | 30×30rpx base64 `#0F172A` |
+| 主文案 | 11pt 700 | 22rpx 700 `#0F172A`,单行省略 |
+| 倒计时数字块 | 16×14 R3,红底白字 | 32×28rpx R6rpx `#E53935`,白字 19rpx 700;仅**单笔**缺货时展示 |
+| 倒计时冒号 | 红 `#E53935` | 20rpx 700 `#E53935` |
+| 前往支付按钮 | H25 R12.5,渐变 90° `#EF4444 → #DC2626` | H50rpx R25rpx,同渐变 + `0 3rpx 10rpx rgba(220,38,38,.21)`;文案 21rpx 700 |
+| 按钮内箭头 | lucide `chevron-right` 9×9 `#FFFFFF` | 18×18rpx base64 |
+
+---
+
+## 五、缺货订单提示规则(按图二 / 需求原文)
+
+> 原文:「如果有缺货订单,需要所有页面都能弹出提示。点击跳转到缺货订单列表页。有多个情况,展示具体数量,不显示倒计时」
+
+| 场景 | 展示规则 | 文案 |
+| --- | --- | --- |
+| 无缺货订单 | 不展示 | — |
+| 单笔缺货(count = 1) | 展示提示 + 支付倒计时(`MM:SS`) | 您有一个缺货订单待支付 |
+| 多笔缺货(count > 1) | 展示提示 + **具体数量**,**不展示倒计时** | 您有 N 个缺货订单待支付 |
+| 点击提示条 | 跳转缺货订单列表页 | `uni.navigateTo('/pages/stock-order/list')` |
+
+实现要点(`components/stock-order-toast`):
+
+- `visible = count > 0`;`titleText` 按 count 分支;
+- 倒计时仅在 `count === 1` 时启动(`setInterval` 1s,初始 30 分钟),`onUnmounted` 清理定时器;
+- 组件在**每个业务页面**模板引入。
+
+---
+
+## 六、图标清单(lucide 1:1 还原)
+
+### 6.1 原生 tabBar 图标(PNG,`static/tabbar/`)
+
+> 原生 tabBar 仅支持本地图片文件 → 由 lucide SVG 光栅化为 81×81 PNG(`stroke-width=2`、`round` 端点),仅替换 `stroke` 颜色。
+
+| 图标 | 用途 | 文件(未选中 / 选中) | 颜色 |
+| --- | --- | --- | --- |
+| `house` | 代谢300 | `tab-home.png` / `tab-home-active.png` | `#94A3B8` / `#003E7E` |
+| `clipboard-check` | 健康方案 | `tab-plan.png` / `tab-plan-active.png` | `#94A3B8` / `#003E7E` |
+| `package` | 我的订单 | `tab-order.png` / `tab-order-active.png` | `#94A3B8` / `#003E7E` |
+| `circle-user-round` | 个人中心 | `tab-profile.png` / `tab-profile-active.png` | `#94A3B8` / `#003E7E` |
+
+### 6.2 内联 base64 SVG 图标
+
+| 图标 | 用途 | 颜色 | 尺寸(rpx) |
+| --- | --- | --- | --- |
+| `clipboard-list` | 缺货提示-前置图标 | `#0F172A` | 30×30 |
+| `chevron-right` | 探索更多 / 前往支付 | `#FFFFFF` | 24×24 / 18×18 |
+
+实现:class 背景图 + `background-repeat: no-repeat; background-position: center; background-size: 100% 100%`。
+
+
+---
+
+## 七、文件清单与影响范围
+
+新增:
+
+- `static/tabbar/*.png`(原生 tabBar 图标,4 图标 × 双态 = 8 个 PNG)
+- `components/stock-order-toast/stock-order-toast.vue`(全局缺货提示组件)
+- `utils/stock-order.js`(缺货数量 mock 数据源 + 列表页路径常量)
+- `pages/health-plan/index.vue`、`pages/order/index.vue`、`pages/profile/index.vue`(三个 tab 页,已按 pen 稿完整实现,详见第九章)
+- `pages/contact/index.vue`(联系我们页,个人中心菜单跳转目标,详见 9.6)
+- `pages/address/list.vue`(选择地址页,个人中心菜单跳转目标,详见 9.7)
+- `pages/stock-order/list.vue`(缺货订单列表页占位)
+
+修改:
+
+- `pages/index/index.vue`(全量重写为沉浸式首页)
+- `pages.json`(首页与个人中心 `navigationStyle: "custom"` + 4 条新路由 + 原生 `tabBar` 配置)
+- `App.vue`(`onShow` 用 `uni.setTabBarBadge` 设置个人中心角标)
+- `utils/request.js`(新增公共守卫 `ensureLogin()`)
+
+移除:
+
+- `components/app-tab-bar/`(自定义 tab 组件,已由原生 tabBar 取代);页面内 `<app-tab-bar>` 引用与底部留白同步删除
+
+影响:登录 / 注册 / 注册成功页不受影响(仍走各自既有布局)。健康方案 / 我的订单页复用 `ensureLogin()`;个人中心需展示未登录态,不强制跳转。跳转 tab 页须用 `uni.switchTab`。
+
+
+---
+
+## 八、待办 / 遗留
+
+| 序号 | 事项 | 状态 |
+| --- | --- | --- |
+| 1 | 首页两张产品大图素材入仓后替换渐变占位块 | 待素材 |
+| 2 | 缺货订单数量接口 + 支付倒计时截止时间由后端下发 | 待接口(当前 mock) |
+| 3 | 个人中心角标数量(未读)由后端下发 | 待接口(当前默认 1) |
+| 4 | 缺货订单列表页真实内容 | 占位 |
+| 5 | 「探索更多」产品详情页 | 占位(toast 提示) |
+| 6 | 三页真实数据接口(订单列表 / 收益数值 / 会员等级与昵称) | 待接口(当前 mock) |
+| 7 | 三页图片素材(方案卡、订单缩略图、用户头像)入仓后替换渐变占位 | 待素材 |
+
+
+---
+
+## 九、三个 Tab 页实现(健康方案 / 我的订单 / 个人中心)
+
+- 需求来源:「后面的健康方案、我的订单以及个人中心都结合图片以及根据 pen 源文件中的内容实现」
+- pen 依据:健康方案 `19179–19939`;我的订单 `19946–22330`;个人中心已登录 `22331–23709`、未登录 `23710–24963`
+- 图片素材(方案卡 3 张 `微信图片_*.png`、订单缩略图、用户头像)**均不在仓库** → 统一以同色系渐变块占位,DOM 结构保留便于后续替换为 `image`
+
+### 9.1 健康方案页(`pages/health-plan/index.vue`)
+
+| 区块 | 设计规格(pt → rpx) | 实现要点 |
+| --- | --- | --- |
+| 分类药丸栏 | 375×44,padding 0/16,药丸间隙 8 | `.filter-bar`;3 档 `G&METS-II` / `HBBLF-2` / `NEARCLC-II` |
+| 药丸选中态 | h28 R14,fill `#003E7E`,文字 11.5pt 700 `#FFFFFF` | `.pill--active`;未选中 fill `#EDF2F7`,文字 `#475569` |
+| 滑动指示条 | 轨道 28×3 `#E2E8F0`,滑块 10×3 `#C5A566` | `.slider` / `.slider-thumb`,按档位步进 18rpx |
+| 产品标题行 | 375×48,padding 0/16 | 副标 11pt 700 `#003E7E` letterSpacing 1.2 + 主名 17.5pt 800 `#0F172A` |
+| 探索更多按钮 | h32 渐变 90° `#82C236→#639C1F` R16 | `.explore-btn` + 白 chevron 底托(同首页范式) |
+| 方案卡片 | 343×160,R14,间隙 12 | 3 张渐变占位卡(`planList`),240rpx 高 |
+
+### 9.2 我的订单页(`pages/order/index.vue`)
+
+| 区块 | 设计规格 | 实现要点 |
+| --- | --- | --- |
+| 状态 Tab 栏 | 375×44 白底 + `#F1F5F9` 底描边 | 5 项:全部 / 待支付 / 待发货 / 待收货 / 售后;选中为 90° `#1E5EBC→#003E7E` 胶囊 |
+| 二级筛选栏 | 375×44,两个 166×32 下拉 | 商品类型「全部」+ 时间区间「选择时间」(calendar 图标),未开发 → toast 占位 |
+| 订单卡片 | 343 R12 白底,gap 12 padding 14 | 6 条 mock 覆盖多状态,`filteredOrderList` 按 Tab 过滤 |
+| 状态徽章配色 | padding [2,7] R4,11.5pt 700 | 待支付 `#FEF2F2`/`#E05338`;履约中 `#ECFDF5`/`#10B981`;待发货·待收货 `#FFF7ED`/`#EA580C`;已完成·已取消 `#F1F5F9`/`#94A3B8` |
+| 缩略图与操作 | 缩略图 40×40 R6,`•••` 更多指示 | 待支付:倒计时(16×14 `#E53935` 白字)+ 去支付按钮;其余:查看订单按钮 |
+
+### 9.3 个人中心页(`pages/profile/index.vue`)
+
+> **导航栏变更**:`pages.json` 中本页由系统导航栏改为 `navigationStyle: "custom"` —— 设计稿顶部无标题且页面底色为渐变,白底原生导航会造成视觉割裂;页面以 `statusBarHeight` + `.nav-holder`(92rpx)留出状态栏与胶囊区。
+
+| 区块 | 设计规格 | 实现要点 |
+| --- | --- | --- |
+| 页面底色 | 175° 渐变 `#D6E6F8` 0 → `#C4DCF6` .18 → `#E8F1FC` .38 → `#F8FAFC` .58 → `#F8FAFC` 1 | `.page` |
+| 头部信息区 | x20 335×72,居中,头像与文本间隙 14 | `.header` |
+| 头像 | 70×70 R35 fill `#F1F5F9` + 2.5 白描边 | 已登录内嵌 `camera` 32×32 `#94A3B8`;未登录内嵌 `user` 34×34 `#CBD5E1` |
+| 文本信息 | 昵称 18pt 700 `#0F172A` | 已登录「辉煌G891」+ 等级胶囊(`userLevel` 动态展示 L0~L4);未登录「点击登录 / 注册」+ 副标 12pt `#64748B` |
+| 会员等级胶囊 | h22 渐变 90° `#FDE68A→#FCD34D→#F59E0B`,V 勋章 18×18 渐变 145° | `.level-pill` / `.level-medal`;等级文字 12.5pt 800 `#78350F` |
+| 主按钮 | h32 渐变 90° `#1E5EBC→#003E7E` R16 | 已登录「编辑资料」/ 未登录「立即登录」 |
+| 收益玻璃卡 | x16 343×74,180° 半透明白渐变 + blur20,R16 | 三栏等分:**今日 10.00 / 本月 400.00 / 累计 128000.00**;未登录统一 `0.00`;**未登录或 L0 普通用户不展示该卡** |
+| 功能菜单卡 | x16 343×408,同款玻璃态,6 行 × 68(未登录 / L0 仅 2 行) | 行图标 20×20 渐变描边 `#3B82F6→#1D4ED8`,右箭头 16×16 `#94A3B8` |
+| 菜单项与等级 | — | L1~L4 且已登录时展示 6 项:`share-2` 我的邀请码 / `crown` 会员中心 / `file-text` 仓库订单 / `badge-cent` 收入明细 / `map-pin` 收货地址 / `contact` 联系我们;**未登录或 L0 普通用户仅展示「收货地址」「联系我们」** |
+| 仓库订单角标 | 18×18 `#EF4444` R9 文字 1 | **仅已登录态**展示 |
+| ICP 区 | x16 343×40 居中 | `京ICP备2026092800号-1X` + 版权行;**内容不足一屏时贴页面底部**(`.page` 为纵向 flex,`.icp` 用 `margin-top: auto` 吸收剩余空间) |
+
+> **用户等级与展示规则(L0~L4)**:由 `userLevel` 控制,`isRestrictedView = !isLoggedIn || userLevel === 'L0'`。**未登录或 L0 普通用户**在个人中心仅保留「收货地址」「联系我们」两行菜单,并隐藏收益数据卡;**已登录且 L1~L4** 展示全部内容。等级接口未联调(当前 mock `L4`),联调后由 `getUserInfo` 下发等级替换。
+
+### 9.4 新增内联 base64 图标
+
+| icon key | 用途 | 颜色 | 尺寸(rpx) |
+| --- | --- | --- | --- |
+| `chevron-down` | 订单二级筛选下拉 | `#94A3B8` | 26×26 |
+| `calendar` | 订单时间区间筛选 | `#94A3B8` | 24×24 |
+| `camera` | 个人中心已登录头像 | `#94A3B8` | 64×64 |
+| `user` | 个人中心未登录头像 | `#CBD5E1` | 68×68 |
+| `share-2` / `crown` / `file-text` / `badge-cent` / `map-pin` / `contact` | 个人中心 6 行菜单 | 渐变 `#3B82F6→#1D4ED8` | 40×40 |
+| `chevron-right` | 菜单右箭头 | `#94A3B8` | 32×32 |
+
+### 9.5 三页公共约定
+
+- 三页模板均引入 `<stock-order-toast />`,保证缺货提示全局可见。
+- 首页 / 健康方案 / 我的订单 / 个人中心:**均不校验登录态**,未登录可正常浏览(原 `ensureLogin()` 守卫已移除,仅缺货订单列表页保留)。
+- 个人中心:`onShow` 读 `getToken()` 仅用于切换「已登录 / 未登录」展示态,不做跳转;未登录时点击头像 / 名称 / 主按钮直接 `uni.reLaunch` 至登录页,**点击菜单项先 `uni.showModal` 提示登录、确认后跳转**。
+- 订单列表、收益数值、用户昵称、用户等级均为本地 mock,待 `getOrderList` / `getMemberInfo` / `getUserInfo` 就绪后替换。
+- 「探索更多 / 二级筛选 / 去支付 / 查看订单 / 编辑资料」本轮统一为 toast 占位(菜单项仅在已登录态为占位)。
+
+### 9.6 联系我们页(`pages/contact/index.vue`)
+
+- 需求来源:「联系我们的原型如图,参考 ui 设计风格实现」
+- 入口:个人中心菜单项「联系我们」(`menu.key === 'contact'`)→ `uni.navigateTo('/pages/contact/index')`;**未登录仍先 `uni.showModal` 提示登录**(沿用个人中心菜单统一拦截规则)
+- 导航:`pages.json` 注册 `navigationStyle: "custom"` —— 顶部为深蓝品牌色块,白底原生导航会造成视觉割裂
+
+| 区块 | 设计规格 | 实现要点 |
+| --- | --- | --- |
+| 顶部深蓝面板 | 纯色 `#0B3F6F`(取自 logo.png 底色) | `.top-panel`(`paddingTop` = `statusBarHeight`);导航栏与 Hero 区共用底色形成连续色块,且与 Logo 图圆角边缘无缝融合 |
+| 导航栏 | h44,返回箭头左置 x8,标题居中 | `.nav`;返回箭头为**纯 CSS**(左/下边框旋转 45°)避免额外 base64,白色 11pt 700 |
+| 品牌 Hero 区 | h200,居中展示品牌 Logo | `<image class="brand-logo" src="/static/logo.png" mode="widthFix" />`,宽 170pt,高度按原图比例自适应 |
+| 联系方式列表 | x20,列表项纵向间距 44 | `.info-list`(`padding: 140rpx 40rpx 0`) |
+| 单行信息 | 标签列宽 110pt,右侧数值 | `.info-label` 16pt 600 `#1F2937`;`.info-value` 15pt `#6B7280`;固定标签列宽保证各行数值纵向对齐 |
+| 平台客服按钮 | h30 R15,**pen 标准「微信按钮 WeChat」** 渐变(90°)`#07C160→#05A352` + 投影 `#07C16040` | `<button open-type="contact">`(微信客服会话)+ 内嵌 `/static/wechat-icon.png` + 白字 13pt 600;`::after { border: none }` 重置小程序默认边框 |
+| 热线文案 | `城市招商热线` / `合伙人招募` 均 `400-451-666` | 客服接口未就绪,先以文件内常量 `CITY_INVEST_HOTLINE` / `PARTNER_RECRUIT_HOTLINE` 维护,接口就绪后替换 |
+| 返回行为 | `uni.navigateBack()` | 无上级页面(如直接进入)时兜底 `uni.switchTab('/pages/profile/index')` |
+
+### 9.7 选择地址页(`pages/address/list.vue`)
+
+- 需求来源:「这是收货地址的原型,参考 ui 规范和设计风格实现收货地址的页面」
+- 入口:个人中心菜单项「收货地址」(`menu.key === 'address'`)→ `uni.navigateTo('/pages/address/list')`;未登录仍先 `uni.showModal` 提示登录
+- 导航:`pages.json` 注册原生标题栏「选择地址」(白底黑字)
+
+| 区块 | 设计规格 | 实现要点 |
+| --- | --- | --- |
+| 地址条目 | 纵向 `padding: 36rpx 40rpx`,无分割线 | `.addr-item` 横向 flex,文本区 `flex: 1; min-width: 0` 保证长地址可换行 |
+| 姓名 + 手机号 | 姓名 16pt 600 `#1F2937`;手机号 15pt `#374151` | `.addr-head` 横向 flex,`gap: 56rpx`;手机号按规范脱敏展示(`185****0110`) |
+| 默认标记 | 蓝框小标签(`#1E5EBC`,描边 1pt,圆角 3pt) | `.addr-tag`,仅 `isDefault` 为 true 时渲染 |
+| 地址详情 | 14pt `#6B7280`,行高 1.45,最多两行 | `.addr-detail` |
+| 编辑入口 | 右上角图标 22×22,`#1E5EBC` | 内联 base64 SVG `square-pen`,点击为 toast 占位(编辑页未实现) |
+| 底部操作栏 | 白色底 + 绿色胶囊按钮(H48 → 96rpx,PILL R24 → 48rpx),白字 + 白色加号 | 尺寸/圆角/文字与登录页 `.btn` 完全一致;`.add-bar` 固定底部,左右 `56rpx` 内边距 + `padding-bottom: calc(32rpx + env(safe-area-inset-bottom))`(胶囊不覆盖 Home 指示条);按钮采用 **pen 标准「草绿动能行动」** 渐变(90°)`#82C236→#639C1F` + 投影 `#6AA02735`,与首页 / 健康方案「探索更多」按钮同源 |
+| 空态占位 | 无地址时居中展示:灰底圆形(d100 `#F1F5F9`)+ 灰色 `map-pin` 图标(48×48 `#CBD5E1`)+ 主文案「暂无收货地址」15pt 600 `#475569` + 副文案「点击下方按钮添加收货地址」13pt `#94A3B8` | `v-if="addressList.length"` 渲染列表,`v-else` 渲染空态;底部「新增地址」按钮两种状态均保留 |
+| 数据来源 | 三条 mock 地址(张三为默认 / 李四 / 王五) | 地址列表接口就绪后替换 `MOCK_ADDRESS_LIST` |
+| 新增入口 | 底部「新增地址」按钮 → `uni.navigateTo('/pages/address/add')` | `handleAdd` 由 toast 占位改为跳转新增地址页(见 9.8) |
+
+### 9.8 新增地址页(`pages/address/add.vue`)
+
+- 需求来源:「点击新增地址时跳转地址新增页面,注意我给你的图片只是原型不是真正的ui设计,需要你参考 `琥歌瑞0928.pen` 的设计风格去设计然后实现」
+- 入口:选择地址页底部「新增地址」按钮 → `uni.navigateTo('/pages/address/add')`
+- 导航:`pages.json` 注册原生标题栏「新增地址」(白底黑字),与选择地址页同规格
+- **设计原则**:原型图仅提供字段结构(收件人 / 手机号 / 收件地区 / 详细地址 / 设为默认 / 提交);视觉按 pen「FORM」组件规范落地,并按要求做简化——**保留字段前缀图标**、输入框**只保留下边框**
+
+| 区块 | 设计规格 | 实现要点 |
+| --- | --- | --- |
+| 表单区 | 左右 `56rpx` 内边距(与底部提交栏对齐),首项距导航 `40rpx`,项间距 `40rpx` | `.form-area` / `.field-block`,复用 `pages/login/register.vue` 表单结构 |
+| 字段标签 | 12.5pt → `26rpx`,600 `#334155`,与输入框间距 `20rpx` | `.field-label`(pen「标签」规范) |
+| 输入框 | 高度 H44 → `88rpx`;**只保留下边框**:`border-bottom: 2rpx solid #CBD5E1`,去掉左/右/上三边与圆角,白底、左右内边距 `4rpx`,视觉更轻 | `.input-wrap` |
+| 前缀图标 | 每个字段左侧 `16pt` → `40rpx` 图标(`#64748B`,内联 base64 SVG,与输入框间距 `20rpx`):收件人 `user` / 手机号 `smartphone` / 收件地区 `map` / 详细地址 `map-pin` | `.field-icon` + `.icon-user` / `.icon-phone` / `.icon-map` / `.icon-pin` |
+| 收件地区 | 点击行唤起原生省市区选择器,右侧 `chevron-right`(`#94A3B8`)暗示可展开;未选择时展示占位文案 `#94A3B8` | `<picker mode="region">` 包裹 `.input-wrap`;`.field-input--placeholder` 控制占位色 |
+| 设为默认 | pen 无开关组件规范,取品牌蓝 `#003E7E`(与 tabBar 选中色同源) | `<switch :checked color="#003E7E" @change>`,默认关闭 |
+| 底部提交栏 | 白色底 + 绿色胶囊按钮(H48 → `96rpx`,PILL R24 → `48rpx`),白字;采用 pen「草绿动能行动」渐变(90°)`#82C236→#639C1F`,**去掉投影**保持扁平简洁 | `.submit-bar` 固定底部(左右 `56rpx` + `padding-bottom: calc(32rpx + env(safe-area-inset-bottom))`),与该页「新增地址」入口按钮、首页/健康方案「探索更多」同源 |
+| 表单校验 | 收件人非空 → 手机号非空且匹配 `^1[3-9]\d{9}$` → 地区非空 → 详细地址非空,逐项 toast 提示 | `_validateForm()` 返回失败原因,通过后 `_handleSubmit` 保存并 `uni.navigateBack()` |
+| 数据来源 | 地址新增接口未就绪,提交成功后本地 toast「保存成功」并返回列表页 | 接口就绪后替换 `_handleSubmit` 内的占位反馈 |
+
diff --git a/hugray-user-miniapp/App.vue b/hugray-user-miniapp/App.vue
index 8c2b732..5cd0850 100644
--- a/hugray-user-miniapp/App.vue
+++ b/hugray-user-miniapp/App.vue
@@ -1,15 +1,37 @@
<script>
- export default {
- onLaunch: function() {
- console.log('App Launch')
- },
- onShow: function() {
- console.log('App Show')
- },
- onHide: function() {
- console.log('App Hide')
+// 个人中心 tab 在 pages.json → tabBar.list 中的索引(0 起)
+const PROFILE_TAB_INDEX = 3
+// TODO: 后端「个人中心待处理/未读数量」接口就绪后替换为真实请求
+const MOCK_PROFILE_BADGE_COUNT = 1
+
+export default {
+ onLaunch: function() {
+ console.log('App Launch')
+ },
+ onShow: function() {
+ console.log('App Show')
+ this._syncTabBarBadge()
+ },
+ onHide: function() {
+ console.log('App Hide')
+ },
+ methods: {
+ // 同步个人中心 tab 原生角标(原生 tabBar 无法用 CSS 实现角标)
+ _syncTabBarBadge() {
+ if (MOCK_PROFILE_BADGE_COUNT > 0) {
+ uni.setTabBarBadge({
+ index: PROFILE_TAB_INDEX,
+ text: String(MOCK_PROFILE_BADGE_COUNT),
+ fail: (err) => {
+ console.error('[TABBAR_BADGE] set failed', err)
+ }
+ })
+ } else {
+ uni.removeTabBarBadge({ index: PROFILE_TAB_INDEX })
+ }
}
}
+}
</script>
<style>
diff --git a/hugray-user-miniapp/components/stock-order-toast/stock-order-toast.vue b/hugray-user-miniapp/components/stock-order-toast/stock-order-toast.vue
new file mode 100644
index 0000000..1296a03
--- /dev/null
+++ b/hugray-user-miniapp/components/stock-order-toast/stock-order-toast.vue
@@ -0,0 +1,190 @@
+<template>
+ <view v-if="visible" class="stock-toast" @tap="_handleGoList">
+ <!-- 左侧:图标 + 文案(单笔缺货额外展示支付倒计时) -->
+ <view class="stock-toast-left">
+ <view class="stock-toast-icon"></view>
+ <text class="stock-toast-title">{{ titleText }}</text>
+ <!-- 单笔缺货:展示支付倒计时;多笔缺货:仅展示具体数量,不展示倒计时 -->
+ <view v-if="count === 1" class="stock-toast-countdown">
+ <view class="countdown-block">{{ minuteText }}</view>
+ <text class="countdown-colon">:</text>
+ <view class="countdown-block">{{ secondText }}</view>
+ </view>
+ </view>
+ <!-- 右侧:前往支付按钮 -->
+ <view class="stock-toast-btn">
+ <text class="stock-toast-btn-text">前往支付</text>
+ <view class="stock-toast-btn-arrow"></view>
+ </view>
+ </view>
+</template>
+
+<script setup>
+/**
+ * 全局缺货订单提示(Toast)
+ * 规则:存在缺货订单的所有页面均弹出;点击跳转缺货订单列表页;
+ * 单笔展示支付倒计时,多笔仅展示具体数量、不展示倒计时。
+ * 用法:在各业务页面模板中直接引入 <stock-order-toast /> 即可。
+ */
+import { computed, onMounted, onUnmounted, ref } from 'vue'
+import { getStockOrderCount, STOCK_ORDER_LIST_PAGE } from '@/utils/stock-order.js'
+
+/** 缺货订单数量(Mock 数据源,接口就绪后由数据层替换) */
+const count = ref(getStockOrderCount())
+
+/** 是否存在缺货订单 */
+const visible = computed(() => count.value > 0)
+
+/** 提示文案:单笔用「一个」,多笔展示具体数量 */
+const titleText = computed(() =>
+ count.value > 1 ? `您有${count.value}个缺货订单待支付` : '您有一个缺货订单待支付'
+)
+
+// ======================== 支付倒计时(仅单笔缺货时展示) ========================
+
+/** 倒计时初始剩余秒数(Mock 30 分钟) */
+const COUNTDOWN_SECONDS = 30 * 60
+
+const remainSeconds = ref(COUNTDOWN_SECONDS)
+let countdownTimer = null
+
+/** 剩余分钟(补零两位) */
+const minuteText = computed(() => String(Math.floor(remainSeconds.value / 60)).padStart(2, '0'))
+/** 剩余秒数(补零两位) */
+const secondText = computed(() => String(remainSeconds.value % 60).padStart(2, '0'))
+
+/** 启动倒计时(多笔缺货时按规则不展示倒计时,无需启动) */
+const _startCountdown = () => {
+ if (count.value !== 1) return
+ countdownTimer = setInterval(() => {
+ if (remainSeconds.value <= 0) {
+ clearInterval(countdownTimer)
+ countdownTimer = null
+ return
+ }
+ remainSeconds.value -= 1
+ }, 1000)
+}
+
+onMounted(_startCountdown)
+onUnmounted(() => {
+ if (countdownTimer) clearInterval(countdownTimer)
+})
+
+/** 跳转缺货订单列表页 */
+const _handleGoList = () => {
+ uni.navigateTo({ url: STOCK_ORDER_LIST_PAGE })
+}
+</script>
+
+<style scoped lang="scss">
+/* 通栏胶囊:距原生 tabBar 24rpx,左右各 24rpx,毛玻璃白底 + 柔和投影 */
+.stock-toast {
+ position: fixed;
+ left: 24rpx;
+ right: 24rpx;
+ /* 原生 tabBar 由系统渲染在 webview 之外,内容区 bottom 即 tabBar 上沿 */
+ bottom: calc(24rpx + constant(safe-area-inset-bottom));
+ bottom: calc(24rpx + env(safe-area-inset-bottom));
+ z-index: 85;
+ height: 80rpx;
+ box-sizing: border-box;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 0 16rpx 0 24rpx;
+ border-radius: 40rpx;
+ background: linear-gradient(135deg, rgba(255, 255, 255, 0.96) 0%, rgba(255, 255, 255, 0.88) 100%);
+ border: 2rpx solid #ffffff;
+ box-shadow: 0 8rpx 28rpx rgba(15, 41, 66, 0.13);
+}
+
+.stock-toast-left {
+ flex: 1;
+ min-width: 0;
+ display: flex;
+ align-items: center;
+}
+
+/* 前置图标:lucide clipboard-list 15×15pt */
+.stock-toast-icon {
+ width: 30rpx;
+ height: 30rpx;
+ margin-right: 13rpx;
+ flex-shrink: 0;
+ background-repeat: no-repeat;
+ background-position: center center;
+ background-size: 100% 100%;
+ background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMEYxNzJBIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCI+PHJlY3Qgd2lkdGg9IjgiIGhlaWdodD0iNCIgeD0iOCIgeT0iMiIgcng9IjEiIHJ5PSIxIi8+PHBhdGggZD0iTTE2IDRoMmEyIDIgMCAwIDEgMiAydjE0YTIgMiAwIDAgMS0yIDJINmEyIDIgMCAwIDEtMi0yVjZhMiAyIDAgMCAxIDItMmgyIi8+PHBhdGggZD0iTTEyIDExaDQiLz48cGF0aCBkPSJNMTIgMTZoNCIvPjxwYXRoIGQ9Ik04IDExaC4wMSIvPjxwYXRoIGQ9Ik04IDE2aC4wMSIvPjwvc3ZnPg==');
+}
+
+.stock-toast-title {
+ font-size: 22rpx;
+ font-weight: 700;
+ line-height: 1;
+ color: #0f172a;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+/* 倒计时组合:红底数字块 + 冒号 */
+.stock-toast-countdown {
+ display: flex;
+ align-items: center;
+ margin-left: 13rpx;
+ flex-shrink: 0;
+}
+
+.countdown-block {
+ width: 32rpx;
+ height: 28rpx;
+ border-radius: 6rpx;
+ background: #e53935;
+ color: #ffffff;
+ font-size: 19rpx;
+ font-weight: 700;
+ line-height: 1;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.countdown-colon {
+ margin: 0 4rpx;
+ font-size: 20rpx;
+ font-weight: 700;
+ line-height: 1;
+ color: #e53935;
+}
+
+/* 前往支付按钮:红色渐变胶囊 */
+.stock-toast-btn {
+ flex-shrink: 0;
+ height: 50rpx;
+ padding: 0 18rpx;
+ border-radius: 25rpx;
+ display: flex;
+ align-items: center;
+ background: linear-gradient(90deg, #ef4444 0%, #dc2626 100%);
+ box-shadow: 0 3rpx 10rpx rgba(220, 38, 38, 0.21);
+}
+
+.stock-toast-btn-text {
+ font-size: 21rpx;
+ font-weight: 700;
+ line-height: 1;
+ color: #ffffff;
+}
+
+/* 按钮内箭头:lucide chevron-right 9×9pt */
+.stock-toast-btn-arrow {
+ width: 18rpx;
+ height: 18rpx;
+ margin-left: 4rpx;
+ background-repeat: no-repeat;
+ background-position: center center;
+ background-size: 100% 100%;
+ background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjRkZGRkZGIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCI+PHBhdGggZD0ibTkgMTggNi02LTYtNiIvPjwvc3ZnPg==');
+}
+</style>
diff --git a/hugray-user-miniapp/config/index.js b/hugray-user-miniapp/config/index.js
index 4377513..8ccd6da 100644
--- a/hugray-user-miniapp/config/index.js
+++ b/hugray-user-miniapp/config/index.js
@@ -9,7 +9,7 @@
// 各环境请求根地址(统一指向 RuoYi 网关,由网关按前缀转发至各微服务)
const BASE_URL_MAP = {
// 本地联调:网关默认端口 8085
- development: 'http://localhost:8085',
+ development: 'http://192.168.110.150:8085',
// 生产环境地址,上线前替换为正式域名
production: 'https://api.hugray.com'
}
diff --git a/hugray-user-miniapp/pages.json b/hugray-user-miniapp/pages.json
index 6129992..2cb82bc 100644
--- a/hugray-user-miniapp/pages.json
+++ b/hugray-user-miniapp/pages.json
@@ -3,7 +3,61 @@
{
"path": "pages/index/index",
"style": {
- "navigationBarTitleText": ""
+ "navigationStyle": "custom"
+ }
+ },
+ {
+ "path": "pages/health-plan/index",
+ "style": {
+ "navigationBarTitleText": "健康方案",
+ "navigationBarBackgroundColor": "#ffffff",
+ "navigationBarTextStyle": "black"
+ }
+ },
+ {
+ "path": "pages/order/index",
+ "style": {
+ "navigationBarTitleText": "我的订单",
+ "navigationBarBackgroundColor": "#ffffff",
+ "navigationBarTextStyle": "black"
+ }
+ },
+ {
+ "path": "pages/profile/index",
+ // 个人中心顶部无标题且底色为渐变,与白底原生导航冲突,故使用自定义导航栏
+ "style": {
+ "navigationStyle": "custom"
+ }
+ },
+ {
+ "path": "pages/contact/index",
+ // 顶部为深蓝底品牌区,与白底原生导航冲突,故使用自定义导航栏
+ "style": {
+ "navigationStyle": "custom"
+ }
+ },
+ {
+ "path": "pages/address/list",
+ "style": {
+ "navigationBarTitleText": "选择地址",
+ "navigationBarBackgroundColor": "#ffffff",
+ "navigationBarTextStyle": "black"
+ }
+ },
+ {
+ "path": "pages/address/add",
+ "style": {
+ "navigationBarTitleText": "新增地址",
+ "navigationBarBackgroundColor": "#ffffff",
+ "navigationBarTextStyle": "black"
+ }
+ },
+ {
+ "path": "pages/stock-order/list",
+ "style": {
+ "navigationBarTitleText": "缺货订单",
+ "navigationBarBackgroundColor": "#ffffff",
+ "navigationBarTextStyle": "black"
}
},
{
@@ -33,5 +87,38 @@
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8"
},
+ // 原生底部导航栏:图标仅支持本地图片文件(不支持 base64 / SVG / 网络图)
+ "tabBar": {
+ "color": "#64748B", // 未选中文案色
+ "selectedColor": "#003E7E", // 选中文案色
+ "backgroundColor": "#FFFFFF", // 背景色
+ "borderStyle": "white", // 仅支持 black / white,对应设计稿 #F1F5F9 顶描边
+ "list": [
+ {
+ "pagePath": "pages/index/index",
+ "text": "代谢300",
+ "iconPath": "static/tabbar/tab-home.png",
+ "selectedIconPath": "static/tabbar/tab-home-active.png"
+ },
+ {
+ "pagePath": "pages/health-plan/index",
+ "text": "健康方案",
+ "iconPath": "static/tabbar/tab-plan.png",
+ "selectedIconPath": "static/tabbar/tab-plan-active.png"
+ },
+ {
+ "pagePath": "pages/order/index",
+ "text": "我的订单",
+ "iconPath": "static/tabbar/tab-order.png",
+ "selectedIconPath": "static/tabbar/tab-order-active.png"
+ },
+ {
+ "pagePath": "pages/profile/index",
+ "text": "个人中心",
+ "iconPath": "static/tabbar/tab-profile.png",
+ "selectedIconPath": "static/tabbar/tab-profile-active.png"
+ }
+ ]
+ },
"uniIdRouter": {}
}
diff --git a/hugray-user-miniapp/pages/address/add.vue b/hugray-user-miniapp/pages/address/add.vue
new file mode 100644
index 0000000..1c2a00b
--- /dev/null
+++ b/hugray-user-miniapp/pages/address/add.vue
@@ -0,0 +1,296 @@
+<template>
+ <view class="page">
+ <!-- 新增地址表单:字段结构对齐原型(收件人 / 手机号 / 收件地区 / 详细地址 / 设为默认 / 提交),
+ 视觉按 pen「FORM」规范:标签 #334155 600、输入行 H88、前缀图标 #64748B、仅保留下边框 -->
+ <view class="form-area">
+ <!-- 收件人 -->
+ <view class="field-block">
+ <text class="field-label">收件人</text>
+ <view class="input-wrap">
+ <view class="field-icon icon-user"></view>
+ <input
+ v-model="form.name"
+ class="field-input"
+ maxlength="20"
+ placeholder="请输入收件人姓名"
+ placeholder-style="color:#94A3B8;font-size:28rpx"
+ />
+ </view>
+ </view>
+
+ <!-- 手机号 -->
+ <view class="field-block">
+ <text class="field-label">手机号</text>
+ <view class="input-wrap">
+ <view class="field-icon icon-phone"></view>
+ <input
+ v-model="form.phone"
+ class="field-input"
+ type="number"
+ maxlength="11"
+ placeholder="请输入收件人手机号"
+ placeholder-style="color:#94A3B8;font-size:28rpx"
+ />
+ </view>
+ </view>
+
+ <!-- 收件地区:点击唤起原生省市区选择器,右侧箭头暗示可展开 -->
+ <view class="field-block">
+ <text class="field-label">收件地区</text>
+ <picker mode="region" :value="regionValue" @change="_handleRegionChange">
+ <view class="input-wrap">
+ <view class="field-icon icon-map"></view>
+ <text class="field-input field-input--value" :class="{ 'field-input--placeholder': !regionText }">
+ {{ regionText || '请选择收件地区' }}
+ </text>
+ <view class="field-arrow"></view>
+ </view>
+ </picker>
+ </view>
+
+ <!-- 详细地址 -->
+ <view class="field-block">
+ <text class="field-label">详细地址</text>
+ <view class="input-wrap">
+ <view class="field-icon icon-pin"></view>
+ <input
+ v-model="form.detail"
+ class="field-input"
+ maxlength="60"
+ placeholder="请输入详细收件地址"
+ placeholder-style="color:#94A3B8;font-size:28rpx"
+ />
+ </view>
+ </view>
+
+ <!-- 设为默认:pen 中无开关组件规范,取品牌蓝 #003E7E(与 tabBar 选中色同源) -->
+ <view class="switch-row">
+ <text class="switch-label">设为默认</text>
+ <switch :checked="form.isDefault" color="#003E7E" @change="_handleDefaultChange" />
+ </view>
+ </view>
+
+ <!-- 底部固定提交栏:尺寸/圆角与地址列表页「新增地址」按钮一致 -->
+ <view class="submit-bar">
+ <view class="submit-btn" @tap="_handleSubmit">提交</view>
+ </view>
+ </view>
+</template>
+
+<script setup>
+import { ref, reactive, computed } from 'vue'
+
+// ======================== 数据定义区域 ========================
+
+// 新增地址表单
+const form = reactive({
+ name: '',
+ phone: '',
+ region: [], // 省 / 市 / 区三级,来源为 region picker
+ detail: '',
+ isDefault: false
+})
+
+// 地区选择器回显值(picker 的 value 需与当前选中的三级数组保持一致)
+const regionValue = ref([])
+
+// 地区文本:拼接为「省 市 区」,为空时由模板展示占位文案
+const regionText = computed(() => form.region.join(' '))
+
+// ======================== 方法定义区域 ========================
+
+/** 切换「设为默认」 */
+const _handleDefaultChange = (e) => {
+ form.isDefault = e.detail.value
+}
+
+/** 选择收件地区:回写三级数组,用于文本展示与 picker 回显 */
+const _handleRegionChange = (e) => {
+ const region = e.detail.value || []
+ form.region = region
+ regionValue.value = region
+}
+
+/** 表单校验,返回校验失败原因(通过则返回空串) */
+const _validateForm = () => {
+ if (!form.name.trim()) return '请输入收件人姓名'
+ if (!form.phone.trim()) return '请输入收件人手机号'
+ if (!/^1[3-9]\d{9}$/.test(form.phone.trim())) return '请输入正确手机号'
+ if (!form.region.length) return '请选择收件地区'
+ if (!form.detail.trim()) return '请输入详细收件地址'
+ return ''
+}
+
+/** 提交:校验通过后保存并返回地址列表页(地址新增接口未就绪,暂以本地成功反馈占位) */
+const _handleSubmit = () => {
+ const errorMsg = _validateForm()
+ if (errorMsg) {
+ uni.showToast({ title: errorMsg, icon: 'none' })
+ return
+ }
+
+ uni.showToast({ title: '保存成功', icon: 'none' })
+ setTimeout(() => uni.navigateBack(), 600)
+}
+</script>
+
+<style scoped lang="scss">
+.page {
+ min-height: 100vh;
+ box-sizing: border-box;
+ background-color: #ffffff;
+ /* 预留底部固定提交栏高度(16+96+32rpx),避免表单被遮挡 */
+ padding-bottom: calc(144rpx + env(safe-area-inset-bottom));
+}
+
+/* ==================== 表单区 ==================== */
+
+/* 左右 56rpx 与底部提交栏对齐,保证标签左边缘与按钮左边缘在同一条竖线上 */
+.form-area {
+ padding: 40rpx 56rpx 0;
+ box-sizing: border-box;
+}
+
+/* 单个输入项:统一上间距 40rpx,首项与导航栏保持 40rpx */
+.field-block {
+ margin-top: 40rpx;
+
+ &:first-child {
+ margin-top: 0;
+ }
+}
+
+.field-label {
+ display: block;
+ font-size: 26rpx;
+ font-weight: 600;
+ color: #334155;
+ margin-bottom: 20rpx;
+}
+
+/* 输入行:白底,仅保留下边框,视觉更轻 */
+.input-wrap {
+ display: flex;
+ align-items: center;
+ height: 88rpx;
+ padding: 0 4rpx;
+ border-bottom: 2rpx solid #cbd5e1;
+ box-sizing: border-box;
+}
+
+.field-icon {
+ width: 40rpx;
+ height: 40rpx;
+ margin-right: 20rpx;
+ flex-shrink: 0;
+ background-repeat: no-repeat;
+ background-position: center center;
+ background-size: 100% 100%;
+}
+
+.field-input {
+ flex: 1;
+ height: 100%;
+ font-size: 28rpx;
+ color: #1e293b;
+ min-width: 0;
+}
+
+/* 未选择地区时,文本行按占位色展示 */
+.field-input--placeholder {
+ color: #94a3b8;
+}
+
+/* 地区值为 text 节点(非 input),用 flex 垂直居中,避免文字偏下 */
+.field-input--value {
+ display: flex;
+ align-items: center;
+}
+
+/* 地区选择右侧箭头(chevron-right) */
+.field-arrow {
+ width: 32rpx;
+ height: 32rpx;
+ flex-shrink: 0;
+ margin-left: 16rpx;
+ background-repeat: no-repeat;
+ background-position: center center;
+ background-size: 100% 100%;
+ background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjOTRBM0I4IiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCI+PHBhdGggZD0ibTkgMTggNi02LTYtNiIvPjwvc3ZnPg==');
+}
+
+/* ==================== 设为默认 ==================== */
+
+.switch-row {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-top: 48rpx;
+}
+
+.switch-label {
+ font-size: 26rpx;
+ font-weight: 600;
+ color: #334155;
+}
+
+/* ==================== 底部固定提交栏 ==================== */
+
+.submit-bar {
+ position: fixed;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ /* 与地址列表页一致:左右 56rpx 内边距 + 安全区留白 */
+ padding: 16rpx 56rpx calc(32rpx + env(safe-area-inset-bottom));
+ background-color: #ffffff;
+}
+
+/* 尺寸/圆角与地址列表页「新增地址」按钮一致:H48 → 96rpx,PILL R24 → 48rpx */
+.submit-btn {
+ width: 100%;
+ height: 96rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 48rpx;
+ box-sizing: border-box;
+ /* pen 标准「草绿动能行动」渐变(90°) */
+ background: linear-gradient(90deg, #82c236 0%, #639c1f 100%);
+ font-size: 30rpx;
+ font-weight: 600;
+ line-height: 1;
+ color: #ffffff;
+}
+
+/* ==================== 图标(内联 base64 SVG) ==================== */
+
+.icon-user,
+.icon-phone,
+.icon-map,
+.icon-pin {
+ background-repeat: no-repeat;
+ background-position: center center;
+ background-size: 100% 100%;
+}
+
+/* 收件人:单人轮廓 */
+.icon-user {
+ background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjNjQ3NDhCIiBzdHJva2Utd2lkdGg9IjEuOSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cGF0aCBkPSJNMTkgMjF2LTJhNCA0IDAgMCAwLTQtNEg5YTQgNCAwIDAgMC00IDR2MiIvPjxjaXJjbGUgY3g9IjEyIiBjeT0iNyIgcj0iNCIvPjwvc3ZnPg==');
+}
+
+/* 手机号:手机外框 + 底部听筒点 */
+.icon-phone {
+ background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjNjQ3NDhCIiBzdHJva2Utd2lkdGg9IjEuOSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cmVjdCB3aWR0aD0iMTMiIGhlaWdodD0iMjAiIHg9IjUuNSIgeT0iMiIgcng9IjIuNSIvPjxwYXRoIGQ9Ik0xMSAxOGgyIi8+PC9zdmc+');
+}
+
+/* 收件地区:地图折页 */
+.icon-map {
+ background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjNjQ3NDhCIiBzdHJva2Utd2lkdGg9IjEuOSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cG9seWdvbiBwb2ludHM9IjMgNiA5IDMuNSAxNSA2IDIxIDMuNSAyMSAxOCAxNSAyMC41IDkgMTggMyAyMC41Ii8+PGxpbmUgeDE9IjkiIHgyPSI5IiB5MT0iMy41IiB5Mj0iMTgiLz48bGluZSB4MT0iMTUiIHgyPSIxNSIgeTE9IjYiIHkyPSIyMC41Ii8+PC9zdmc+');
+}
+
+/* 详细地址:定位图钉 */
+.icon-pin {
+ background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjNjQ3NDhCIiBzdHJva2Utd2lkdGg9IjEuOSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cGF0aCBkPSJNMjAgMTBjMCA2LTggMTItOCAxMnMtOC02LTgtMTJhOCA4IDAgMCAxIDE2IDBaIi8+PGNpcmNsZSBjeD0iMTIiIGN5PSIxMCIgcj0iMyIvPjwvc3ZnPg==');
+}
+</style>
diff --git a/hugray-user-miniapp/pages/address/list.vue b/hugray-user-miniapp/pages/address/list.vue
new file mode 100644
index 0000000..d8332a1
--- /dev/null
+++ b/hugray-user-miniapp/pages/address/list.vue
@@ -0,0 +1,245 @@
+<template>
+ <view class="page">
+ <!-- 有数据:渲染地址列表,右侧为编辑入口 -->
+ <view v-if="addressList.length">
+ <view v-for="item in addressList" :key="item.id" class="addr-item">
+ <view class="addr-main">
+ <!-- 姓名 + 手机号 -->
+ <view class="addr-head">
+ <text class="addr-name">{{ item.name }}</text>
+ <text class="addr-phone">{{ item.phone }}</text>
+ </view>
+ <!-- 默认地址标记 -->
+ <view v-if="item.isDefault" class="addr-tag">默认</view>
+ <text class="addr-detail">{{ item.detail }}</text>
+ </view>
+ <view class="icon icon-edit" @tap="handleEdit(item)"></view>
+ </view>
+ </view>
+
+ <!-- 空态占位:无收货地址时展示,引导用户新增 -->
+ <view v-else class="addr-empty">
+ <view class="empty-icon-wrap">
+ <view class="icon icon-empty"></view>
+ </view>
+ <text class="empty-title">暂无收货地址</text>
+ <text class="empty-desc">点击下方按钮添加收货地址</text>
+ </view>
+
+ <!-- 底部固定操作栏:新增地址 -->
+ <view class="add-bar">
+ <view class="add-btn" @tap="handleAdd">
+ <view class="icon icon-plus"></view>
+ <text class="add-text">新增地址</text>
+ </view>
+ </view>
+ </view>
+</template>
+
+<script setup>
+import { ref } from 'vue'
+
+/** 收货地址列表:地址接口未就绪,先本地 mock(联调后由收货地址列表接口下发);手机号按规范脱敏展示 */
+const MOCK_ADDRESS_LIST = [
+ {
+ id: '1',
+ name: '张三',
+ phone: '185****0110',
+ detail: '四川省成都市武侯区府城大道399号天府新谷9号楼2单元1005a',
+ isDefault: true
+ },
+ {
+ id: '2',
+ name: '李四',
+ phone: '185****0110',
+ detail: '四川省成都市武侯区府城大道399号天府新谷9号楼2单元1005a'
+ },
+ {
+ id: '3',
+ name: '王五',
+ phone: '185****0110',
+ detail: '四川省成都市武侯区府城大道399号天府新谷9号楼2单元1005a'
+ }
+]
+
+const addressList = ref(MOCK_ADDRESS_LIST)
+
+/** 编辑地址:编辑页未实现,暂为占位反馈 */
+const handleEdit = () => {
+ uni.showToast({ title: '敬请期待', icon: 'none' })
+}
+
+/** 新增地址:跳转到新增地址页 */
+const handleAdd = () => {
+ uni.navigateTo({ url: '/pages/address/add' })
+}
+</script>
+
+<style scoped lang="scss">
+.page {
+ min-height: 100vh;
+ box-sizing: border-box;
+ background-color: #ffffff;
+ /* 预留底部固定操作栏高度(16+96+32rpx),避免最后一条地址被遮挡 */
+ padding-bottom: calc(144rpx + env(safe-area-inset-bottom));
+}
+
+/* ==================== 地址列表 ==================== */
+
+.addr-item {
+ display: flex;
+ align-items: flex-start;
+ padding: 36rpx 40rpx;
+}
+
+/* 文本区:占满剩余宽度,地址超长自动换行 */
+.addr-main {
+ flex: 1;
+ min-width: 0;
+}
+
+.addr-head {
+ display: flex;
+ align-items: center;
+ gap: 56rpx;
+}
+
+.addr-name {
+ font-size: 32rpx;
+ font-weight: 600;
+ line-height: 1;
+ color: #1f2937;
+}
+
+.addr-phone {
+ font-size: 30rpx;
+ line-height: 1;
+ color: #374151;
+}
+
+/* 默认标记:蓝色描边小标签 */
+.addr-tag {
+ display: inline-flex;
+ align-items: center;
+ height: 32rpx;
+ margin: 10rpx 0 12rpx;
+ padding: 0 10rpx;
+ border: 2rpx solid #1e5ebc;
+ border-radius: 6rpx;
+ box-sizing: border-box;
+ font-size: 20rpx;
+ line-height: 1;
+ color: #1e5ebc;
+}
+
+.addr-detail {
+ display: block;
+ font-size: 28rpx;
+ line-height: 1.45;
+ color: #6b7280;
+}
+
+/* ==================== 空态占位 ==================== */
+
+/* 无收货地址时居中展示,避免出现空白页 */
+.addr-empty {
+ padding: 200rpx 0 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+/* 圆形底衬 + 地址图标 */
+.empty-icon-wrap {
+ width: 200rpx;
+ height: 200rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 50%;
+ background-color: #f1f5f9;
+}
+
+.empty-title {
+ margin-top: 36rpx;
+ font-size: 30rpx;
+ font-weight: 600;
+ line-height: 1;
+ color: #475569;
+}
+
+.empty-desc {
+ margin-top: 16rpx;
+ font-size: 26rpx;
+ line-height: 1;
+ color: #94a3b8;
+}
+
+/* ==================== 底部固定操作栏 ==================== */
+
+.add-bar {
+ position: fixed;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ /* 与登录页底部操作区一致:左右 56rpx 内边距 + 安全区留白,绿色胶囊不覆盖 Home 指示条 */
+ padding: 16rpx 56rpx calc(32rpx + env(safe-area-inset-bottom));
+ background-color: #ffffff;
+}
+
+/* 尺寸/圆角与登录页 .btn 完全一致:H48 → 96rpx,PILL R24 → 48rpx */
+.add-btn {
+ width: 100%;
+ height: 96rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 16rpx;
+ border-radius: 48rpx;
+ box-sizing: border-box;
+ /* pen 标准「草绿动能行动」渐变(90°)+ 绿色光晕投影 #6AA02735 */
+ background: linear-gradient(90deg, #82c236 0%, #639c1f 100%);
+ box-shadow: 0 6rpx 16rpx rgba(106, 160, 39, 0.21);
+}
+
+.add-text {
+ font-size: 30rpx;
+ font-weight: 600;
+ line-height: 1;
+ color: #ffffff;
+}
+
+/* ==================== 图标(内联 base64 SVG) ==================== */
+
+.icon-edit,
+.icon-plus,
+.icon-empty {
+ background-repeat: no-repeat;
+ background-position: center center;
+ background-size: 100% 100%;
+}
+
+/* 编辑:方形 + 铅笔(#1E5EBC) */
+.icon-edit {
+ width: 44rpx;
+ height: 44rpx;
+ flex-shrink: 0;
+ margin-left: 24rpx;
+ background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMUU1RUJDIiBzdHJva2Utd2lkdGg9IjEuOCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cGF0aCBkPSJNMTIgM0g1YTIgMiAwIDAgMC0yIDJ2MTRhMiAyIDAgMCAwIDIgMmgxNGEyIDIgMCAwIDAgMi0ydi03Ii8+PHBhdGggZD0iTTE4LjM3NSAyLjYyNWExIDEgMCAwIDEgMyAzbC05LjAxMyA5LjAxNGEyIDIgMCAwIDEtLjg1My41MDVsLTIuODczLjg0YS41LjUgMCAwIDEtLjYyLS42MmwuODQtMi44NzNhMiAyIDAgMCAxIC41MDYtLjg1MnoiLz48L3N2Zz4=');
+}
+
+/* 新增:白色加号 */
+.icon-plus {
+ width: 34rpx;
+ height: 34rpx;
+ flex-shrink: 0;
+ background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZmZmZmZmIiBzdHJva2Utd2lkdGg9IjIuNCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cGF0aCBkPSJNMTIgNXYxNE01IDEyaDE0Ii8+PC9zdmc+');
+}
+
+/* 空态:灰色定位图标(#CBD5E1) */
+.icon-empty {
+ width: 96rpx;
+ height: 96rpx;
+ background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjY2JkNWUxIiBzdHJva2Utd2lkdGg9IjEuNiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cGF0aCBkPSJNMjAgMTBjMCA2LTggMTItOCAxMnMtOC02LTgtMTJhOCA4IDAgMCAxIDE2IDBaIi8+PGNpcmNsZSBjeD0iMTIiIGN5PSIxMCIgcj0iMyIvPjwvc3ZnPg==');
+}
+</style>
diff --git a/hugray-user-miniapp/pages/contact/index.vue b/hugray-user-miniapp/pages/contact/index.vue
new file mode 100644
index 0000000..65f0bfa
--- /dev/null
+++ b/hugray-user-miniapp/pages/contact/index.vue
@@ -0,0 +1,194 @@
+<template>
+ <view class="page">
+ <!-- 深蓝顶部面板:自定义导航栏 + 品牌 Hero 区(同一底色,视觉连续) -->
+ <view class="top-panel" :style="{ paddingTop: statusBarHeight + 'px' }">
+ <!-- 导航栏:左侧返回箭头,居中标题 -->
+ <view class="nav">
+ <view class="nav-back" @tap="handleBack">
+ <view class="icon-back"></view>
+ </view>
+ <text class="nav-title">联系我们</text>
+ </view>
+
+ <!-- 品牌区:Logo 图(白色 YiOuS 标识) -->
+ <view class="hero">
+ <image class="brand-logo" src="/static/logo.png" mode="widthFix" />
+ </view>
+ </view>
+
+ <!-- 联系方式列表:热线为纯展示文案,平台客服为绿色胶囊按钮 -->
+ <view class="info-list">
+ <view v-for="item in CONTACT_LIST" :key="item.key" class="info-row">
+ <text class="info-label">{{ item.label }}</text>
+ <!-- 有 value 渲染文案,否则渲染「联系客服」按钮 -->
+ <text v-if="item.value" class="info-value">{{ item.value }}</text>
+ <button v-else class="service-btn" open-type="contact">
+ <image class="service-icon" src="/static/wechat-icon.png" mode="aspectFit" />
+ <text class="service-text">联系客服</text>
+ </button>
+ </view>
+ </view>
+ </view>
+</template>
+
+<script setup>
+/** 状态栏高度:自定义导航栏下用于顶部留白 */
+const statusBarHeight = uni.getSystemInfoSync().statusBarHeight || 0
+
+/** 招商 / 合伙人热线:客服接口未就绪,先以常量维护(接口就绪后替换) */
+const CITY_INVEST_HOTLINE = '400-451-666'
+const PARTNER_RECRUIT_HOTLINE = '400-451-666'
+
+/** 联系方式列表:value 为空表示该项渲染为客服按钮 */
+const CONTACT_LIST = [
+ { key: 'invest', label: '城市招商热线', value: CITY_INVEST_HOTLINE },
+ { key: 'partner', label: '合伙人招募', value: PARTNER_RECRUIT_HOTLINE },
+ { key: 'service', label: '平台客服' }
+]
+
+/** 返回上一页:无上级页面(如直接进入)时回到个人中心 */
+const handleBack = () => {
+ const pages = getCurrentPages()
+ if (pages.length > 1) {
+ uni.navigateBack()
+ return
+ }
+ uni.switchTab({ url: '/pages/profile/index' })
+}
+</script>
+
+<style scoped lang="scss">
+.page {
+ min-height: 100vh;
+ display: flex;
+ flex-direction: column;
+ box-sizing: border-box;
+ background-color: #ffffff;
+}
+
+/* ==================== 深蓝顶部面板 ==================== */
+
+.top-panel {
+ /* 与 logo.png 底色(#0B3F6F)保持一致,使 Logo 方块的圆角边缘无缝融合 */
+ background-color: #0b3f6f;
+}
+
+/* 导航栏(44pt 高) */
+.nav {
+ position: relative;
+ height: 88rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.nav-back {
+ position: absolute;
+ left: 16rpx;
+ top: 50%;
+ transform: translateY(-50%);
+ width: 64rpx;
+ height: 64rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+/* 纯 CSS 左向箭头:左 + 下边框旋转 45° */
+.icon-back {
+ width: 22rpx;
+ height: 22rpx;
+ border-left: 4rpx solid #ffffff;
+ border-bottom: 4rpx solid #ffffff;
+ transform: rotate(45deg);
+}
+
+.nav-title {
+ font-size: 34rpx;
+ font-weight: 700;
+ line-height: 1;
+ letter-spacing: 2rpx;
+ color: #ffffff;
+}
+
+/* 品牌 Hero 区(约 200pt 高) */
+.hero {
+ height: 400rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+/* 品牌 Logo:固定宽度,高度按原图比例自适应 */
+.brand-logo {
+ width: 340rpx;
+}
+
+/* ==================== 联系方式列表 ==================== */
+
+.info-list {
+ padding: 140rpx 40rpx 0;
+ display: flex;
+ flex-direction: column;
+ gap: 88rpx;
+}
+
+.info-row {
+ display: flex;
+ align-items: center;
+}
+
+/* 固定标签列宽,保证右侧数值纵向对齐 */
+.info-label {
+ width: 220rpx;
+ flex-shrink: 0;
+ font-size: 32rpx;
+ font-weight: 600;
+ line-height: 1;
+ letter-spacing: 1rpx;
+ color: #1f2937;
+}
+
+.info-value {
+ font-size: 30rpx;
+ line-height: 1;
+ letter-spacing: 1rpx;
+ color: #6b7280;
+}
+
+/* 平台客服:绿色胶囊按钮 */
+.service-btn {
+ height: 60rpx;
+ margin: 0;
+ padding: 0 26rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 10rpx;
+ border: none;
+ border-radius: 30rpx;
+ box-sizing: border-box;
+ line-height: 1;
+ /* pen 标准「微信按钮 WeChat」渐变(90°)+ 绿色光晕投影 #07C16040 */
+ background: linear-gradient(90deg, #07c160 0%, #05a352 100%);
+ box-shadow: 0 12rpx 32rpx rgba(7, 193, 96, 0.25);
+}
+
+/* 重置小程序 button 默认边框 */
+.service-btn::after {
+ border: none;
+}
+
+.service-icon {
+ width: 30rpx;
+ height: 30rpx;
+ flex-shrink: 0;
+}
+
+.service-text {
+ font-size: 26rpx;
+ font-weight: 600;
+ line-height: 1;
+ color: #ffffff;
+}
+</style>
diff --git a/hugray-user-miniapp/pages/health-plan/index.vue b/hugray-user-miniapp/pages/health-plan/index.vue
new file mode 100644
index 0000000..f922983
--- /dev/null
+++ b/hugray-user-miniapp/pages/health-plan/index.vue
@@ -0,0 +1,260 @@
+<template>
+ <view class="page">
+ <!-- 分类药丸筛选栏 -->
+ <view class="filter-bar">
+ <view class="pill-group">
+ <view
+ v-for="(code, index) in categoryList"
+ :key="code"
+ class="pill"
+ :class="{ 'pill--active': index === activeIndex }"
+ @tap="handleCategoryTap(index)"
+ >
+ <text class="pill-text">{{ code }}</text>
+ </view>
+ </view>
+ <!-- 药丸组滚动位置指示条(滑块随选中项移动) -->
+ <view class="slider">
+ <view class="slider-thumb" :style="{ marginLeft: sliderOffset }"></view>
+ </view>
+ </view>
+
+ <!-- 产品名称 + 探索更多 -->
+ <view class="product-bar">
+ <view class="product-text">
+ <text class="product-code">{{ currentCategory }}</text>
+ <text class="product-name">{{ productName }}</text>
+ </view>
+ <view class="explore-btn" @tap="handleExploreMore">
+ <text class="explore-btn-text">探索更多</text>
+ <view class="explore-btn-arrow-wrap">
+ <view class="explore-btn-arrow"></view>
+ </view>
+ </view>
+ </view>
+
+ <!-- 健康方案卡片列表 -->
+ <view class="plan-list">
+ <view
+ v-for="plan in planList"
+ :key="plan.id"
+ class="plan-card"
+ @tap="handlePlanTap(plan)"
+ >
+ <!-- 方案图素材未入仓,暂以同色系渐变块占位,素材就绪后替换为 image -->
+ <view class="plan-card-visual" :style="{ background: plan.background }"></view>
+ </view>
+ </view>
+
+ <!-- 全局缺货订单提示(存在缺货订单时所有页面均弹出) -->
+ <stock-order-toast />
+ </view>
+</template>
+
+<script setup>
+import { computed, ref } from 'vue'
+import StockOrderToast from '@/components/stock-order-toast/stock-order-toast.vue'
+
+/** 分类药丸:与设计稿一致的三档产品系列代号 */
+const categoryList = ['G&METS-II', 'HBBLF-2', 'NEARCLC-II']
+
+/** 当前选中的分类下标 */
+const activeIndex = ref(0)
+
+/** 产品主名(三档分类当前共用一个产品名,接口就绪后按分类拉取) */
+const productName = '超分子基团复合肽'
+
+/** 方案卡片:素材缺失,先以设计稿同色系渐变占位 */
+const planList = [
+ { id: 1, background: 'linear-gradient(135deg, #16336e 0%, #2b52b8 48%, #4a6fd0 100%)' },
+ { id: 2, background: 'linear-gradient(135deg, #3a2d86 0%, #5b4bc0 50%, #7d6bd8 100%)' },
+ { id: 3, background: 'linear-gradient(135deg, #c9dcf3 0%, #e6f0fb 52%, #f7fbff 100%)' }
+]
+
+/** 当前选中的分类代号 */
+const currentCategory = computed(() => categoryList[activeIndex.value])
+
+/** 指示条滑块偏移:轨道 28pt、滑块 10pt,共 3 档 → 每档步进 18rpx */
+const sliderOffset = computed(() => `${activeIndex.value * 18}rpx`)
+
+/** 切换分类药丸 */
+const handleCategoryTap = (index) => {
+ activeIndex.value = index
+}
+
+/** 探索更多:产品详情页尚未开发,先给出占位反馈 */
+const handleExploreMore = () => {
+ uni.showToast({ title: '敬请期待', icon: 'none' })
+}
+
+/** 方案卡片:详情页尚未开发,先给出占位反馈 */
+const handlePlanTap = () => {
+ uni.showToast({ title: '敬请期待', icon: 'none' })
+}
+</script>
+
+<style scoped lang="scss">
+.page {
+ min-height: 100vh;
+ background: #f8fafc;
+}
+
+/* ==================== 分类药丸筛选栏(375×44pt) ==================== */
+
+.filter-bar {
+ height: 88rpx;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 0 32rpx;
+ box-sizing: border-box;
+}
+
+.pill-group {
+ display: flex;
+ align-items: center;
+ gap: 16rpx;
+}
+
+.pill {
+ height: 56rpx;
+ display: flex;
+ align-items: center;
+ padding: 0 28rpx;
+ border-radius: 28rpx;
+ background: #edf2f7;
+}
+
+.pill-text {
+ font-size: 23rpx;
+ font-weight: 600;
+ line-height: 1;
+ color: #475569;
+}
+
+.pill--active {
+ background: #003e7e;
+}
+
+.pill--active .pill-text {
+ font-weight: 700;
+ color: #ffffff;
+}
+
+/* 滚动指示条:轨道 28×3pt,滑块 10×3pt */
+.slider {
+ flex-shrink: 0;
+ width: 56rpx;
+ height: 6rpx;
+ margin-left: 16rpx;
+ display: flex;
+ align-items: center;
+ border-radius: 3rpx;
+ background: #e2e8f0;
+}
+
+.slider-thumb {
+ width: 20rpx;
+ height: 6rpx;
+ border-radius: 3rpx;
+ background: #c5a566;
+}
+
+/* ==================== 产品名称 + 探索更多(375×48pt) ==================== */
+
+.product-bar {
+ height: 96rpx;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 0 32rpx;
+ box-sizing: border-box;
+}
+
+.product-text {
+ display: flex;
+ flex-direction: column;
+ gap: 4rpx;
+}
+
+.product-code {
+ font-size: 22rpx;
+ font-weight: 700;
+ letter-spacing: 2.4rpx;
+ line-height: 1;
+ color: #003e7e;
+}
+
+.product-name {
+ font-size: 35rpx;
+ font-weight: 800;
+ letter-spacing: -0.4rpx;
+ line-height: 1.1;
+ color: #0f172a;
+}
+
+/* 探索更多:绿色渐变胶囊按钮 */
+.explore-btn {
+ flex-shrink: 0;
+ height: 64rpx;
+ display: flex;
+ align-items: center;
+ gap: 12rpx;
+ padding: 0 16rpx 0 28rpx;
+ border-radius: 32rpx;
+ background: linear-gradient(90deg, #82c236 0%, #639c1f 100%);
+ box-shadow: 0 6rpx 16rpx rgba(106, 160, 39, 0.21);
+}
+
+.explore-btn-text {
+ font-size: 24rpx;
+ font-weight: 600;
+ letter-spacing: 0.4rpx;
+ line-height: 1;
+ color: #ffffff;
+}
+
+.explore-btn-arrow-wrap {
+ width: 36rpx;
+ height: 36rpx;
+ border-radius: 18rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: rgba(255, 255, 255, 0.16);
+}
+
+/* 箭头:lucide chevron-right 白色 */
+.explore-btn-arrow {
+ width: 22rpx;
+ height: 22rpx;
+ background-repeat: no-repeat;
+ background-position: center center;
+ background-size: 100% 100%;
+ background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjRkZGRkZGIiBzdHJva2Utd2lkdGg9IjIuNSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cGF0aCBkPSJtOSAxOCA2LTYtNi02Ii8+PC9zdmc+');
+}
+
+/* ==================== 方案卡片列表(343×160pt / 张) ==================== */
+
+.plan-list {
+ display: flex;
+ flex-direction: column;
+ gap: 24rpx;
+ padding: 8rpx 32rpx 32rpx;
+ box-sizing: border-box;
+}
+
+.plan-card {
+ width: 100%;
+ height: 320rpx;
+ overflow: hidden;
+ border-radius: 28rpx;
+ border: 1rpx solid rgba(226, 232, 240, 0.5);
+ box-shadow: 0 8rpx 24rpx rgba(15, 23, 42, 0.04);
+}
+
+.plan-card-visual {
+ width: 100%;
+ height: 100%;
+}
+</style>
diff --git a/hugray-user-miniapp/pages/index/index.vue b/hugray-user-miniapp/pages/index/index.vue
index 8d44327..e8502c5 100644
--- a/hugray-user-miniapp/pages/index/index.vue
+++ b/hugray-user-miniapp/pages/index/index.vue
@@ -1,24 +1,242 @@
<template>
+ <view class="page">
+ <!-- 内容区:设计稿内容高度与视口一致,矮屏可滚动 -->
+ <scroll-view class="scroll" scroll-y>
+ <!-- ==================== 明星产品展位:G&METS-II ==================== -->
+ <view class="hero">
+ <!-- 产品大图视口(沉浸加高,顶部延伸到状态栏下方) -->
+ <view class="hero-visual">
+ <!-- 顶部品牌区(沉浸式导航:状态栏占位 + 品牌 Logo) -->
+ <view class="brand-bar" :style="{ paddingTop: statusBarHeight + 'px' }">
+ <image class="brand-logo" src="/static/metabolism-300-logo.png" mode="aspectFit" />
+ </view>
+
+ <!-- 产品大图占位:设计稿素材未入仓,暂以同色系渐变块占位,素材就绪后替换为 image -->
+ <view class="hero-visual-placeholder"></view>
+
+ <!-- 轮播指示器:毛玻璃微胶囊 -->
+ <view class="hero-indicator">
+ <view class="indicator-dot"></view>
+ <view class="indicator-dot indicator-dot--active"></view>
+ <view class="indicator-dot"></view>
+ </view>
+ </view>
+
+ <!-- 产品信息与行动区 -->
+ <view class="hero-info">
+ <view class="hero-info-text">
+ <text class="hero-code">G&METS-II</text>
+ <text class="hero-name">超分子基团复合肽</text>
+ </view>
+ <view class="hero-explore" @tap="_handleExploreMore">
+ <text class="hero-explore-text">探索更多</text>
+ <view class="hero-explore-arrow-wrap">
+ <view class="hero-explore-arrow"></view>
+ </view>
+ </view>
+ </view>
+ </view>
+
+ <!-- ==================== 次席展位:HBBLF-2 与 代谢300 ==================== -->
+ <view class="sub-visual">
+ <!-- 展位图占位:素材未入仓,暂以渐变块占位 -->
+ <view class="sub-visual-placeholder"></view>
+ </view>
+ </scroll-view>
+
+ <!-- 全局缺货订单提示(存在缺货订单时所有页面均弹出) -->
+ <stock-order-toast />
+ </view>
</template>
<script setup>
-import { onShow } from '@dcloudio/uni-app'
-import { getToken } from '@/utils/request.js'
-import config from '@/config/index.js'
+import StockOrderToast from '@/components/stock-order-toast/stock-order-toast.vue'
-/**
- * 校验登录态:本地无 token 时重定向至登录页
- */
-const _checkLoginStatus = () => {
- if (getToken()) return
- uni.reLaunch({ url: config.loginPage })
+/** 状态栏高度(px):沉浸式布局下品牌区需下移避让系统状态栏 */
+const statusBarHeight = uni.getSystemInfoSync().statusBarHeight || 0
+
+/** 探索更多:产品详情页尚未开发,先给出占位反馈 */
+const _handleExploreMore = () => {
+ uni.showToast({ title: '敬请期待', icon: 'none' })
}
-
-// 每次页面显示均校验登录态,避免退出登录后仍停留在首页
-onShow(() => {
- _checkLoginStatus()
-})
</script>
<style scoped lang="scss">
+.page {
+ width: 100%;
+ height: 100vh;
+ overflow: hidden;
+ background: #f8fafc;
+}
+
+.scroll {
+ width: 100%;
+ height: 100vh;
+}
+
+/* ==================== 明星产品展位 ==================== */
+
+.hero {
+ width: 100%;
+}
+
+/* 产品大图视口:375×406pt */
+.hero-visual {
+ position: relative;
+ width: 100%;
+ height: 812rpx;
+ overflow: hidden;
+ /* 素材占位渐变:上部浅蓝(品牌区底色)向下过渡到白色 */
+ background: linear-gradient(180deg, #d9e7f7 0%, #e9f1fb 26%, #ffffff 54%, #f4f7fb 100%);
+}
+
+/* 品牌区:状态栏占位 + 导航行,叠加在大图之上 */
+.brand-bar {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ z-index: 2;
+ display: flex;
+ align-items: center;
+ padding-left: 24rpx;
+ padding-right: 24rpx;
+ box-sizing: border-box;
+}
+
+.brand-logo {
+ width: 168rpx;
+ height: 88rpx;
+}
+
+/* 产品大图占位块(保留文案提示,便于后续替换真实素材) */
+.hero-visual-placeholder {
+ width: 100%;
+ height: 100%;
+}
+
+/* 轮播指示器:毛玻璃微胶囊,水平居中,底部 376pt 处 */
+.hero-indicator {
+ position: absolute;
+ left: 50%;
+ top: 752rpx;
+ transform: translateX(-50%);
+ height: 32rpx;
+ box-sizing: border-box;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 10rpx;
+ padding: 0 14rpx;
+ border-radius: 16rpx;
+ background: rgba(255, 255, 255, 0.7);
+ border: 1rpx solid #ffffff;
+ box-shadow: 0 4rpx 12rpx rgba(15, 23, 42, 0.07);
+}
+
+.indicator-dot {
+ width: 8rpx;
+ height: 8rpx;
+ border-radius: 4rpx;
+ background: rgba(0, 62, 126, 0.19);
+}
+
+.indicator-dot--active {
+ width: 28rpx;
+ background: linear-gradient(90deg, #003e7e 0%, #1e5ebc 100%);
+}
+
+/* 产品信息与行动区:375×58pt */
+.hero-info {
+ height: 116rpx;
+ box-sizing: border-box;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 0 36rpx;
+ background: #ffffff;
+ border-top: 1rpx solid #f1f5f9;
+ border-bottom: 1rpx solid #f1f5f9;
+ box-shadow: 0 4rpx 16rpx rgba(15, 23, 42, 0.024);
+}
+
+.hero-info-text {
+ display: flex;
+ flex-direction: column;
+ gap: 4rpx;
+}
+
+.hero-code {
+ font-size: 22rpx;
+ font-weight: 700;
+ letter-spacing: 2.4rpx;
+ line-height: 1;
+ color: #003e7e;
+}
+
+.hero-name {
+ font-size: 35rpx;
+ font-weight: 800;
+ letter-spacing: -0.4rpx;
+ line-height: 1.15;
+ color: #0f172a;
+}
+
+/* 探索更多:绿色渐变胶囊按钮 */
+.hero-explore {
+ flex-shrink: 0;
+ height: 68rpx;
+ box-sizing: border-box;
+ display: flex;
+ align-items: center;
+ gap: 12rpx;
+ padding: 0 16rpx 0 28rpx;
+ border-radius: 34rpx;
+ background: linear-gradient(90deg, #82c236 0%, #639c1f 100%);
+ box-shadow: 0 6rpx 16rpx rgba(106, 160, 39, 0.21);
+}
+
+.hero-explore-text {
+ font-size: 25rpx;
+ font-weight: 600;
+ letter-spacing: 0.4rpx;
+ line-height: 1;
+ color: #ffffff;
+}
+
+.hero-explore-arrow-wrap {
+ width: 40rpx;
+ height: 40rpx;
+ border-radius: 20rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: rgba(255, 255, 255, 0.16);
+}
+
+/* 箭头:lucide chevron-right 12×12pt */
+.hero-explore-arrow {
+ width: 24rpx;
+ height: 24rpx;
+ background-repeat: no-repeat;
+ background-position: center center;
+ background-size: 100% 100%;
+ background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjRkZGRkZGIiBzdHJva2Utd2lkdGg9IjIuNSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cGF0aCBkPSJtOSAxOCA2LTYtNi02Ii8+PC9zdmc+');
+}
+
+/* ==================== 次席展位 ==================== */
+
+.sub-visual {
+ position: relative;
+ width: 100%;
+ height: 530rpx;
+ overflow: hidden;
+ /* 素材占位渐变:紫调过渡,素材就绪后替换为 image */
+ background: linear-gradient(160deg, #4e3d94 0%, #6a5ac1 45%, #a99be0 100%);
+}
+
+.sub-visual-placeholder {
+ width: 100%;
+ height: 100%;
+}
</style>
diff --git a/hugray-user-miniapp/pages/login/index.vue b/hugray-user-miniapp/pages/login/index.vue
index 87f0e13..8686106 100644
--- a/hugray-user-miniapp/pages/login/index.vue
+++ b/hugray-user-miniapp/pages/login/index.vue
@@ -313,7 +313,8 @@
if (!token) throw new Error('登录失败:未获取到登录凭证')
setToken(token)
- uni.reLaunch({ url: '/pages/index/index' })
+ // 首页为原生 tabBar 页面,须用 switchTab 切换
+ uni.switchTab({ url: '/pages/index/index' })
}
/** 跳转注册页 */
diff --git a/hugray-user-miniapp/pages/order/index.vue b/hugray-user-miniapp/pages/order/index.vue
new file mode 100644
index 0000000..6b309aa
--- /dev/null
+++ b/hugray-user-miniapp/pages/order/index.vue
@@ -0,0 +1,568 @@
+<template>
+ <view class="page">
+ <!-- 订单状态过滤 Tab(375×44pt) -->
+ <view class="status-tabs">
+ <view
+ v-for="tab in statusTabs"
+ :key="tab.key"
+ class="status-tab"
+ :class="{ 'status-tab--active': tab.key === activeStatus }"
+ @tap="handleStatusTap(tab.key)"
+ >
+ <text class="status-tab-text">{{ tab.label }}</text>
+ </view>
+ </view>
+
+ <!-- 二级复合筛选:商品类型 + 时间区间 -->
+ <view class="sub-filter">
+ <view class="sub-filter-item" @tap="handleFilterTap">
+ <text class="sub-filter-text">全部</text>
+ <view class="icon icon-chevron-down"></view>
+ </view>
+ <view class="sub-filter-item" @tap="handleFilterTap">
+ <view class="sub-filter-left">
+ <view class="icon icon-calendar"></view>
+ <text class="sub-filter-text">选择时间</text>
+ </view>
+ <view class="icon icon-chevron-down"></view>
+ </view>
+ </view>
+
+ <!-- 订单卡片列表 -->
+ <view class="order-list">
+ <view v-for="order in filteredOrderList" :key="order.orderId" class="order-card">
+ <!-- 状态行:下单时间 + 状态徽章 -->
+ <view class="order-card-head">
+ <text class="order-time">{{ order.createTime }}</text>
+ <text class="order-badge" :class="`order-badge--${order.statusKey}`">
+ {{ order.statusText }}
+ </text>
+ </view>
+
+ <!-- 摘要行:套餐名 + 件数 + 实付金额 -->
+ <view class="order-card-summary">
+ <text class="order-title">{{ order.title }}</text>
+ <view class="order-summary-right">
+ <text class="order-count">共{{ order.quantity }}件</text>
+ <view class="order-amount">
+ <text class="order-amount-label">实付</text>
+ <text class="order-amount-symbol">¥</text>
+ <text class="order-amount-value">{{ order.amount }}</text>
+ </view>
+ </view>
+ </view>
+
+ <!-- 缩略图与操作行 -->
+ <view class="order-card-foot">
+ <view class="order-thumbs">
+ <view
+ v-for="(thumb, thumbIndex) in order.thumbnails"
+ :key="thumbIndex"
+ class="order-thumb"
+ :style="{ background: thumb }"
+ ></view>
+ <view v-if="order.moreCount > 0" class="order-thumb order-thumb--more">
+ <text class="order-thumb-more-text">•••</text>
+ </view>
+ </view>
+ <view class="order-actions">
+ <!-- 待支付:倒计时 + 去支付 -->
+ <template v-if="order.statusKey === 'pending'">
+ <view class="order-countdown">
+ <view class="order-countdown-num">
+ <text class="order-countdown-num-text">{{ order.countdownMinute }}</text>
+ </view>
+ <text class="order-countdown-colon">:</text>
+ <view class="order-countdown-num">
+ <text class="order-countdown-num-text">{{ order.countdownSecond }}</text>
+ </view>
+ </view>
+ <view class="order-pay-btn" @tap="handlePayTap(order)">
+ <text class="order-pay-btn-text">去支付</text>
+ </view>
+ </template>
+ <!-- 其余状态:查看订单 -->
+ <view v-else class="order-view-btn" @tap="handleViewTap(order)">
+ <text class="order-view-btn-text">查看订单</text>
+ </view>
+ </view>
+ </view>
+ </view>
+
+ <!-- 空态:无该状态订单 -->
+ <view v-if="filteredOrderList.length === 0" class="order-empty">
+ <text class="order-empty-text">暂无相关订单</text>
+ </view>
+ </view>
+
+ <!-- 全局缺货订单提示(存在缺货订单时所有页面均弹出) -->
+ <stock-order-toast />
+ </view>
+</template>
+
+<script setup>
+import { computed, ref } from 'vue'
+import StockOrderToast from '@/components/stock-order-toast/stock-order-toast.vue'
+
+/** 缩略图占位渐变(商品图素材未入仓) */
+const THUMB_BLUE = 'linear-gradient(135deg, #173a8f 0%, #2f57bd 100%)'
+const THUMB_LIGHT = 'linear-gradient(135deg, #cfe0f5 0%, #eef5fd 100%)'
+
+/** 订单状态过滤 Tab:全部 / 待支付 / 待发货 / 待收货 / 售后 */
+const statusTabs = [
+ { key: 'all', label: '全部' },
+ { key: 'pending', label: '待支付' },
+ { key: 'unshipped', label: '待发货' },
+ { key: 'unreceived', label: '待收货' },
+ { key: 'aftersale', label: '售后' }
+]
+
+/** 当前选中的状态(默认「全部」) */
+const activeStatus = ref('all')
+
+/** 订单列表:接口未就绪,先本地 mock(联调后替换为 getOrderList) */
+const orderList = [
+ {
+ orderId: '20260902001',
+ createTime: '2026-09-02 16:33:52',
+ statusKey: 'pending',
+ statusText: '待支付',
+ title: '超分子基团复合肽套餐',
+ quantity: 3,
+ amount: '386',
+ thumbnails: [THUMB_BLUE, THUMB_BLUE, THUMB_LIGHT],
+ moreCount: 1,
+ countdownMinute: '29',
+ countdownSecond: '59'
+ },
+ {
+ orderId: '20260902002',
+ createTime: '2026-09-02 16:33:52',
+ statusKey: 'fulfilling',
+ statusText: '履约中',
+ title: '超分子基团复合肽套餐',
+ quantity: 1,
+ amount: '386',
+ thumbnails: [THUMB_BLUE],
+ moreCount: 0
+ },
+ {
+ orderId: '20260902003',
+ createTime: '2026-09-02 16:33:52',
+ statusKey: 'unshipped',
+ statusText: '待发货',
+ title: '超分子基团复合肽套餐',
+ quantity: 3,
+ amount: '386',
+ thumbnails: [THUMB_BLUE, THUMB_BLUE, THUMB_LIGHT],
+ moreCount: 0
+ },
+ {
+ orderId: '20260902004',
+ createTime: '2026-09-02 16:33:52',
+ statusKey: 'unreceived',
+ statusText: '待收货',
+ title: '超分子基团复合肽套餐',
+ quantity: 3,
+ amount: '386',
+ thumbnails: [THUMB_BLUE, THUMB_BLUE, THUMB_LIGHT],
+ moreCount: 0
+ },
+ {
+ orderId: '20260902005',
+ createTime: '2026-09-02 16:33:52',
+ statusKey: 'done',
+ statusText: '已完成',
+ title: '超分子基团复合肽套餐',
+ quantity: 3,
+ amount: '386',
+ thumbnails: [THUMB_BLUE, THUMB_BLUE, THUMB_LIGHT],
+ moreCount: 0
+ },
+ {
+ orderId: '20260902006',
+ createTime: '2026-09-02 16:33:52',
+ statusKey: 'cancelled',
+ statusText: '已取消',
+ title: '超分子基团复合肽套餐',
+ quantity: 3,
+ amount: '386',
+ thumbnails: [THUMB_BLUE, THUMB_BLUE, THUMB_LIGHT],
+ moreCount: 0
+ }
+]
+
+/** 按选中状态过滤订单(「全部」不过滤) */
+const filteredOrderList = computed(() => {
+ if (activeStatus.value === 'all') {
+ return orderList
+ }
+ return orderList.filter((order) => order.statusKey === activeStatus.value)
+})
+
+/** 切换订单状态 Tab */
+const handleStatusTap = (key) => {
+ activeStatus.value = key
+}
+
+/** 二级筛选(商品类型 / 时间区间):筛选面板尚未开发,先给出占位反馈 */
+const handleFilterTap = () => {
+ uni.showToast({ title: '敬请期待', icon: 'none' })
+}
+
+/** 去支付:支付流程尚未开发,先给出占位反馈 */
+const handlePayTap = () => {
+ uni.showToast({ title: '敬请期待', icon: 'none' })
+}
+
+/** 查看订单:订单详情页尚未开发,先给出占位反馈 */
+const handleViewTap = () => {
+ uni.showToast({ title: '敬请期待', icon: 'none' })
+}
+</script>
+
+<style scoped lang="scss">
+.page {
+ min-height: 100vh;
+ background: #f8fafc;
+}
+
+/* ==================== 订单状态过滤 Tab(375×44pt) ==================== */
+
+.status-tabs {
+ height: 88rpx;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 0 32rpx;
+ box-sizing: border-box;
+ background: #ffffff;
+ border-bottom: 1rpx solid #f1f5f9;
+}
+
+.status-tab {
+ height: 56rpx;
+ display: flex;
+ align-items: center;
+ padding: 0 24rpx;
+ border-radius: 28rpx;
+ background: #edf2f7;
+}
+
+.status-tab-text {
+ font-size: 23rpx;
+ font-weight: 600;
+ line-height: 1;
+ color: #475569;
+}
+
+/* 选中态:蓝色渐变胶囊 */
+.status-tab--active {
+ background: linear-gradient(90deg, #1e5ebc 0%, #003e7e 100%);
+ box-shadow: 0 4rpx 12rpx rgba(0, 62, 126, 0.15);
+}
+
+.status-tab--active .status-tab-text {
+ font-weight: 700;
+ color: #ffffff;
+}
+
+/* ==================== 二级复合筛选栏(375×44pt) ==================== */
+
+.sub-filter {
+ height: 88rpx;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 0 32rpx;
+ box-sizing: border-box;
+}
+
+.sub-filter-item {
+ width: 332rpx;
+ height: 64rpx;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 0 24rpx;
+ box-sizing: border-box;
+ border-radius: 12rpx;
+ background: #ffffff;
+ border: 1rpx solid #e2e8f0;
+}
+
+.sub-filter-left {
+ display: flex;
+ align-items: center;
+ gap: 10rpx;
+}
+
+.sub-filter-text {
+ font-size: 24rpx;
+ font-weight: 500;
+ line-height: 1;
+ color: #334155;
+}
+
+/* ==================== 图标 ==================== */
+
+.icon {
+ flex-shrink: 0;
+ background-repeat: no-repeat;
+ background-position: center center;
+ background-size: 100% 100%;
+}
+
+/* lucide chevron-down 13×13pt */
+.icon-chevron-down {
+ width: 26rpx;
+ height: 26rpx;
+ background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjOTRBM0I4IiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCI+PHBhdGggZD0ibTYgOSA2IDYgNi02Ii8+PC9zdmc+');
+}
+
+/* lucide calendar 12×12pt */
+.icon-calendar {
+ width: 24rpx;
+ height: 24rpx;
+ background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjOTRBM0I4IiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCI+PHJlY3Qgd2lkdGg9IjE4IiBoZWlnaHQ9IjE4IiB4PSIzIiB5PSI0IiByeD0iMiIvPjxwYXRoIGQ9Ik0xNiAydjQiLz48cGF0aCBkPSJNOCAydjQiLz48cGF0aCBkPSJNMyAxMGgxOCIvPjwvc3ZnPg==');
+}
+
+/* ==================== 订单卡片列表 ==================== */
+
+.order-list {
+ display: flex;
+ flex-direction: column;
+ gap: 24rpx;
+ padding: 8rpx 32rpx 32rpx;
+ box-sizing: border-box;
+}
+
+.order-card {
+ display: flex;
+ flex-direction: column;
+ gap: 24rpx;
+ padding: 28rpx;
+ box-sizing: border-box;
+ border-radius: 24rpx;
+ background: #ffffff;
+ border: 1rpx solid rgba(226, 232, 240, 0.5);
+ box-shadow: 0 6rpx 16rpx rgba(15, 23, 42, 0.03);
+}
+
+.order-card-head {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.order-time {
+ font-size: 22rpx;
+ font-weight: 500;
+ color: #94a3b8;
+}
+
+.order-badge {
+ padding: 4rpx 14rpx;
+ border-radius: 8rpx;
+ font-size: 23rpx;
+ font-weight: 700;
+ line-height: 1.35;
+}
+
+.order-badge--pending {
+ background: #fef2f2;
+ color: #e05338;
+}
+
+.order-badge--fulfilling {
+ background: #ecfdf5;
+ color: #10b981;
+}
+
+.order-badge--unshipped,
+.order-badge--unreceived {
+ background: #fff7ed;
+ color: #ea580c;
+}
+
+.order-badge--done,
+.order-badge--cancelled {
+ background: #f1f5f9;
+ color: #94a3b8;
+}
+
+/* 摘要行 */
+.order-card-summary {
+ display: flex;
+ align-items: flex-end;
+ justify-content: space-between;
+}
+
+.order-title {
+ font-size: 29rpx;
+ font-weight: 700;
+ color: #0f172a;
+}
+
+.order-summary-right {
+ display: flex;
+ align-items: flex-end;
+ gap: 16rpx;
+}
+
+.order-count {
+ padding: 4rpx 12rpx;
+ border-radius: 8rpx;
+ background: #f1f5f9;
+ font-size: 21rpx;
+ font-weight: 600;
+ line-height: 1.35;
+ color: #475569;
+}
+
+.order-amount {
+ display: flex;
+ align-items: baseline;
+ gap: 4rpx;
+}
+
+.order-amount-label {
+ font-size: 22rpx;
+ font-weight: 500;
+ color: #64748b;
+}
+
+.order-amount-symbol {
+ font-size: 23rpx;
+ font-weight: 700;
+ color: #0f172a;
+}
+
+.order-amount-value {
+ font-size: 34rpx;
+ font-weight: 800;
+ line-height: 1;
+ color: #0f172a;
+}
+
+/* 缩略图与操作行 */
+.order-card-foot {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.order-thumbs {
+ display: flex;
+ align-items: center;
+ gap: 12rpx;
+}
+
+.order-thumb {
+ width: 80rpx;
+ height: 80rpx;
+ box-sizing: border-box;
+ border-radius: 12rpx;
+ border: 1rpx solid #f1f5f9;
+}
+
+.order-thumb--more {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: #f8fafc;
+ border-color: #e2e8f0;
+}
+
+.order-thumb-more-text {
+ font-size: 22rpx;
+ font-weight: 700;
+ line-height: 1;
+ color: #94a3b8;
+}
+
+.order-actions {
+ display: flex;
+ align-items: center;
+ gap: 16rpx;
+}
+
+/* 倒计时(仅待支付) */
+.order-countdown {
+ display: flex;
+ align-items: center;
+ gap: 4rpx;
+}
+
+.order-countdown-num {
+ width: 32rpx;
+ height: 28rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 6rpx;
+ background: #e53935;
+}
+
+.order-countdown-num-text {
+ font-size: 19rpx;
+ font-weight: 700;
+ line-height: 1;
+ color: #ffffff;
+}
+
+.order-countdown-colon {
+ font-size: 20rpx;
+ font-weight: 700;
+ color: #e53935;
+}
+
+/* 去支付按钮 */
+.order-pay-btn {
+ height: 56rpx;
+ display: flex;
+ align-items: center;
+ padding: 0 28rpx;
+ border-radius: 28rpx;
+ background: linear-gradient(90deg, #ef4444 0%, #dc2626 100%);
+ box-shadow: 0 4rpx 12rpx rgba(220, 38, 38, 0.21);
+}
+
+.order-pay-btn-text {
+ font-size: 24rpx;
+ font-weight: 700;
+ line-height: 1;
+ color: #ffffff;
+}
+
+/* 查看订单按钮 */
+.order-view-btn {
+ height: 56rpx;
+ display: flex;
+ align-items: center;
+ padding: 0 28rpx;
+ box-sizing: border-box;
+ border-radius: 28rpx;
+ background: #f8fafc;
+ border: 1rpx solid #cbd5e1;
+}
+
+.order-view-btn-text {
+ font-size: 24rpx;
+ font-weight: 500;
+ line-height: 1;
+ color: #475569;
+}
+
+/* 空态 */
+.order-empty {
+ padding: 120rpx 0;
+ display: flex;
+ justify-content: center;
+}
+
+.order-empty-text {
+ font-size: 26rpx;
+ color: #94a3b8;
+}
+</style>
diff --git a/hugray-user-miniapp/pages/profile/index.vue b/hugray-user-miniapp/pages/profile/index.vue
new file mode 100644
index 0000000..1da43be
--- /dev/null
+++ b/hugray-user-miniapp/pages/profile/index.vue
@@ -0,0 +1,485 @@
+<template>
+ <view class="page" :style="{ paddingTop: statusBarHeight + 'px' }">
+ <!-- 自定义导航栏占位:系统胶囊按钮所在区域 -->
+ <view class="nav-holder"></view>
+
+ <!-- 用户头部信息区(335×72pt) -->
+ <view class="header">
+ <view class="user-main" @tap="handleAccountTap">
+ <view class="avatar">
+ <view class="icon" :class="isLoggedIn ? 'icon-camera' : 'icon-user'"></view>
+ </view>
+ <view class="user-text">
+ <!-- 已登录:昵称 + 会员等级;未登录:登录入口 + 引导文案 -->
+ <text class="user-name">{{ isLoggedIn ? nickname : '点击登录 / 注册' }}</text>
+ <view v-if="isLoggedIn" class="level-pill">
+ <view class="level-medal">
+ <text class="level-medal-text">V</text>
+ </view>
+ <text class="level-text">{{ userLevel }}</text>
+ </view>
+ <text v-else class="user-sub">登录开启代谢300专属健康方案</text>
+ </view>
+ </view>
+ <view class="edit-btn" @tap="handleAccountTap">
+ <text class="edit-btn-text">{{ isLoggedIn ? '编辑资料' : '立即登录' }}</text>
+ </view>
+ </view>
+
+ <!-- 收益数据展示区(343×74pt):未登录或 L0 普通用户不展示 -->
+ <view v-if="!isRestrictedView" class="earnings-card">
+ <view v-for="item in earnings" :key="item.key" class="earning-item">
+ <text class="earning-label">{{ item.label }}</text>
+ <text class="earning-value">{{ item.value }}</text>
+ </view>
+ </view>
+
+ <!-- 功能菜单卡片组(343×408pt,6 行 × 68pt) -->
+ <view class="menu-card">
+ <view
+ v-for="menu in menuList"
+ :key="menu.key"
+ class="menu-row"
+ @tap="handleMenuTap(menu)"
+ >
+ <view class="menu-left">
+ <view class="icon menu-icon" :class="`icon-${menu.icon}`"></view>
+ <text class="menu-title">{{ menu.title }}</text>
+ </view>
+ <view class="menu-right">
+ <!-- 仓库订单角标:仅已登录态展示 -->
+ <view v-if="menu.withBadge && isLoggedIn" class="menu-badge">
+ <text class="menu-badge-text">1</text>
+ </view>
+ <view class="icon icon-chevron-right"></view>
+ </view>
+ </view>
+ </view>
+
+ <!-- ICP 备案区 -->
+ <view class="icp">
+ <text class="icp-no">京ICP备2026092800号-1X</text>
+ <text class="icp-copy">代谢300 · 数据基于系统权限版权所有</text>
+ </view>
+
+ <!-- 全局缺货订单提示(存在缺货订单时所有页面均弹出) -->
+ <!-- <stock-order-toast /> -->
+ </view>
+</template>
+
+<script setup>
+import { computed, ref } from 'vue'
+import { onShow } from '@dcloudio/uni-app'
+import { getToken } from '@/utils/request.js'
+import config from '@/config/index.js'
+import StockOrderToast from '@/components/stock-order-toast/stock-order-toast.vue'
+
+/** 状态栏高度:自定义导航栏下用于顶部留白 */
+const statusBarHeight = uni.getSystemInfoSync().statusBarHeight || 0
+
+/** 登录态:本页需同时兼容「已登录 / 未登录」两种展示,故以本地 token 判定 */
+const isLoggedIn = ref(false)
+
+/** 用户昵称:用户信息接口未就绪,先本地 mock(联调后替换为 getUserInfo) */
+const nickname = ref('辉煌G891')
+
+/** 用户等级:取值 L0~L4,其中 L0 为普通用户;等级接口未联调,先本地 mock(联调后取 getUserInfo 下发等级) */
+const userLevel = ref('L4')
+
+/** 是否受限展示:未登录或 L0 普通用户,个人中心仅展示「收货地址」「联系我们」 */
+const isRestrictedView = computed(() => !isLoggedIn.value || userLevel.value === 'L0')
+
+/** 收益三栏:未登录态统一展示 0.00 */
+const earnings = computed(() => {
+ const zeroValue = '0.00'
+ return [
+ { key: 'today', label: '今日收益', value: isLoggedIn.value ? '10.00' : zeroValue },
+ { key: 'month', label: '本月收益', value: isLoggedIn.value ? '400.00' : zeroValue },
+ { key: 'total', label: '累计收益', value: isLoggedIn.value ? '128000.00' : zeroValue }
+ ]
+})
+
+/** 功能菜单全集:图标 key 对应 SCSS 中的 .icon-xxx 类;restricted 为 true 表示仅 L1 及以上等级可见 */
+const MENU_LIST = [
+ { key: 'invite', icon: 'share2', title: '我的邀请码', restricted: true },
+ { key: 'member', icon: 'crown', title: '会员中心', restricted: true },
+ { key: 'warehouse', icon: 'file-text', title: '仓库订单', withBadge: true, restricted: true },
+ { key: 'income', icon: 'badge-cent', title: '收入明细', restricted: true },
+ { key: 'address', icon: 'map-pin', title: '收货地址' },
+ { key: 'contact', icon: 'contact', title: '联系我们' }
+]
+
+/** 当前可见菜单:受限展示(未登录 / L0)时仅保留「收货地址」「联系我们」 */
+const menuList = computed(() =>
+ isRestrictedView.value ? MENU_LIST.filter((menu) => !menu.restricted) : MENU_LIST
+)
+
+/** 头像 / 名称 / 按钮点击:未登录跳登录页,已登录进入资料编辑 */
+const handleAccountTap = () => {
+ if (!isLoggedIn.value) {
+ uni.reLaunch({ url: config.loginPage })
+ return
+ }
+ uni.showToast({ title: '敬请期待', icon: 'none' })
+}
+
+/** 菜单项点击:未登录先提示登录;已登录进入二级功能页(未接入的暂为占位反馈) */
+const handleMenuTap = (menu) => {
+ if (!isLoggedIn.value) {
+ uni.showModal({
+ title: '提示',
+ content: '请先登录后再操作',
+ confirmText: '去登录',
+ cancelText: '取消',
+ success: (res) => {
+ // 用户确认后跳转登录页
+ if (res.confirm) uni.reLaunch({ url: config.loginPage })
+ }
+ })
+ return
+ }
+ // 联系我们:跳转独立页面
+ if (menu.key === 'contact') {
+ uni.navigateTo({ url: '/pages/contact/index' })
+ return
+ }
+ // 收货地址:跳转地址列表页
+ if (menu.key === 'address') {
+ uni.navigateTo({ url: '/pages/address/list' })
+ return
+ }
+ uni.showToast({ title: '敬请期待', icon: 'none' })
+}
+
+// 每次页面显示均刷新登录态,保证登录 / 退出后展示态同步
+onShow(() => {
+ isLoggedIn.value = Boolean(getToken())
+})
+</script>
+
+<style scoped lang="scss">
+.page {
+ min-height: 100vh;
+ display: flex;
+ flex-direction: column;
+ box-sizing: border-box;
+ /* 页面底色:顶部淡蓝渐变过渡至浅灰 */
+ background: linear-gradient(
+ 175deg,
+ #d6e6f8 0%,
+ #c4dcf6 18%,
+ #e8f1fc 38%,
+ #f8fafc 58%,
+ #f8fafc 100%
+ );
+}
+
+/* 自定义导航栏占位(46pt 胶囊栏,状态栏由行内 paddingTop 撑开) */
+.nav-holder {
+ height: 92rpx;
+}
+
+/* ==================== 用户头部信息区 ==================== */
+
+.header {
+ height: 144rpx;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 0 40rpx;
+ box-sizing: border-box;
+}
+
+.user-main {
+ display: flex;
+ align-items: center;
+ gap: 28rpx;
+}
+
+.avatar {
+ width: 140rpx;
+ height: 140rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ box-sizing: border-box;
+ border-radius: 70rpx;
+ background: #f1f5f9;
+ border: 5rpx solid #ffffff;
+}
+
+/* 已登录:相机图标;未登录:用户图标 */
+.icon-camera {
+ width: 64rpx;
+ height: 64rpx;
+ background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjOTRBM0I4IiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCI+PHBhdGggZD0iTTE0LjUgNGgtNUw3IDdINGEyIDIgMCAwIDAtMiAydjlhMiAyIDAgMCAwIDIgMmgxNmEyIDIgMCAwIDAgMi0yVjlhMiAyIDAgMCAwLTItMmgtM2wtMi41LTN6Ii8+PGNpcmNsZSBjeD0iMTIiIGN5PSIxMyIgcj0iMyIvPjwvc3ZnPg==');
+}
+
+.icon-user {
+ width: 68rpx;
+ height: 68rpx;
+ background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjQ0JENUUxIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCI+PHBhdGggZD0iTTE5IDIxdi0yYTQgNCAwIDAgMC00LTRIOWE0IDQgMCAwIDAtNCA0djIiLz48Y2lyY2xlIGN4PSIxMiIgY3k9IjciIHI9IjQiLz48L3N2Zz4=');
+}
+
+.user-text {
+ display: flex;
+ flex-direction: column;
+ /* 左对齐:避免等级胶囊被拉伸到与昵称同宽(pen 中胶囊按内容自适应宽度) */
+ align-items: flex-start;
+ gap: 10rpx;
+}
+
+.user-name {
+ font-size: 36rpx;
+ font-weight: 700;
+ line-height: 1.15;
+ color: #0f172a;
+}
+
+.user-sub {
+ font-size: 24rpx;
+ line-height: 1.2;
+ color: #64748b;
+}
+
+/* 会员等级胶囊(L4) */
+.level-pill {
+ height: 44rpx;
+ display: flex;
+ align-items: center;
+ gap: 10rpx;
+ padding: 0 20rpx 0 4rpx;
+ box-sizing: border-box;
+ border-radius: 22rpx;
+ background: linear-gradient(90deg, #fde68a 0%, #fcd34d 50%, #f59e0b 100%);
+ box-shadow: 0 4rpx 12rpx rgba(245, 158, 11, 0.19);
+}
+
+.level-medal {
+ width: 36rpx;
+ height: 36rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ box-sizing: border-box;
+ border-radius: 50%;
+ background: linear-gradient(145deg, #f59e0b 0%, #d97706 100%);
+ border: 3rpx solid #ffffff;
+}
+
+.level-medal-text {
+ font-size: 23rpx;
+ font-weight: 900;
+ line-height: 1;
+ color: #ffffff;
+}
+
+.level-text {
+ font-size: 25rpx;
+ font-weight: 800;
+ line-height: 1;
+ letter-spacing: 1rpx;
+ color: #78350f;
+}
+
+/* 编辑资料 / 立即登录按钮 */
+.edit-btn {
+ flex-shrink: 0;
+ height: 64rpx;
+ display: flex;
+ align-items: center;
+ padding: 0 32rpx;
+ border-radius: 32rpx;
+ background: linear-gradient(90deg, #1e5ebc 0%, #003e7e 100%);
+ box-shadow: 0 6rpx 16rpx rgba(0, 62, 126, 0.21);
+}
+
+.edit-btn-text {
+ font-size: 25rpx;
+ font-weight: 600;
+ line-height: 1;
+ color: #ffffff;
+}
+
+/* ==================== 收益 / 菜单:玻璃态卡片 ==================== */
+
+.earnings-card {
+ height: 148rpx;
+ margin: 24rpx 32rpx 0;
+ display: flex;
+ align-items: center;
+ padding: 0 24rpx;
+ box-sizing: border-box;
+ border-radius: 32rpx;
+ border: 2rpx solid rgba(255, 255, 255, 0.82);
+ background: linear-gradient(
+ 180deg,
+ rgba(255, 255, 255, 0.94) 0%,
+ rgba(240, 245, 253, 0.86) 50%,
+ rgba(228, 238, 251, 0.82) 100%
+ );
+ backdrop-filter: blur(20px);
+ box-shadow: 0 12rpx 40rpx rgba(15, 23, 42, 0.04);
+}
+
+.earning-item {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 10rpx;
+}
+
+.earning-label {
+ font-size: 25rpx;
+ font-weight: 500;
+ line-height: 1;
+ color: #64748b;
+}
+
+.earning-value {
+ font-size: 38rpx;
+ font-weight: 700;
+ line-height: 1;
+ color: #0f172a;
+}
+
+.menu-card {
+ margin: 24rpx 32rpx 0;
+ border-radius: 32rpx;
+ overflow: hidden;
+ border: 2rpx solid rgba(255, 255, 255, 0.82);
+ background: linear-gradient(
+ 180deg,
+ rgba(255, 255, 255, 0.94) 0%,
+ rgba(240, 245, 253, 0.86) 50%,
+ rgba(228, 238, 251, 0.82) 100%
+ );
+ backdrop-filter: blur(20px);
+ box-shadow: 0 12rpx 40rpx rgba(15, 23, 42, 0.04);
+}
+
+.menu-row {
+ height: 136rpx;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 0 40rpx;
+ box-sizing: border-box;
+ border-bottom: 2rpx solid rgba(226, 232, 240, 0.5);
+}
+
+/* 末行无分隔线 */
+.menu-row:last-child {
+ border-bottom: none;
+}
+
+.menu-left {
+ display: flex;
+ align-items: center;
+ gap: 28rpx;
+}
+
+.menu-title {
+ font-size: 30rpx;
+ font-weight: 500;
+ line-height: 1;
+ color: #1e293b;
+}
+
+.menu-right {
+ display: flex;
+ align-items: center;
+ gap: 16rpx;
+}
+
+/* 仓库订单红色角标 */
+.menu-badge {
+ width: 36rpx;
+ height: 36rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 50%;
+ background: #ef4444;
+}
+
+.menu-badge-text {
+ font-size: 22rpx;
+ font-weight: 700;
+ line-height: 1;
+ color: #ffffff;
+}
+
+/* ==================== 图标(内联 base64 SVG) ==================== */
+
+.icon {
+ flex-shrink: 0;
+ background-repeat: no-repeat;
+ background-position: center center;
+ background-size: 100% 100%;
+}
+
+/* 统一渐变描边:135° #3B82F6 → #1D4ED8 */
+.menu-icon {
+ width: 40rpx;
+ height: 40rpx;
+}
+
+.icon-share2 {
+ background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSJ1cmwoI2cpIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJnIiB4MT0iMCIgeTE9IjAiIHgyPSIxIiB5Mj0iMSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjM0I4MkY2Ii8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjMUQ0RUQ4Ii8+PC9saW5lYXJHcmFkaWVudD48L2RlZnM+PGNpcmNsZSBjeD0iMTgiIGN5PSI1IiByPSIzIi8+PGNpcmNsZSBjeD0iNiIgY3k9IjEyIiByPSIzIi8+PGNpcmNsZSBjeD0iMTgiIGN5PSIxOSIgcj0iMyIvPjxwYXRoIGQ9Im04LjU5IDEzLjUxIDYuODMgMy45OCIvPjxwYXRoIGQ9Im0xNS40MSA2LjUxLTYuODIgMy45OCIvPjwvc3ZnPg==');
+}
+
+.icon-crown {
+ background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSJ1cmwoI2cpIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJnIiB4MT0iMCIgeTE9IjAiIHgyPSIxIiB5Mj0iMSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjM0I4MkY2Ii8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjMUQ0RUQ4Ii8+PC9saW5lYXJHcmFkaWVudD48L2RlZnM+PHBhdGggZD0iTTExLjU2MiAzLjI2NmEuNS41IDAgMCAxIC44NzYgMEwxNS4zOSA4Ljg3YTEgMSAwIDAgMCAxLjUxNi4yOTRMMjEuMTgzIDUuNWEuNS41IDAgMCAxIC43OTguNTE5bC0yLjgzNCAxMC4yNDZhMSAxIDAgMCAxLS45NTYuNzM1SDUuODFhMSAxIDAgMCAxLS45NTctLjczNUwyLjAyIDYuMDJhLjUuNSAwIDAgMSAuNzk4LS41MTlsNC4yNzYgMy42NjRhMSAxIDAgMCAwIDEuNTE2LS4yOTR6Ii8+PHBhdGggZD0iTTUgMjFoMTQiLz48L3N2Zz4=');
+}
+
+.icon-file-text {
+ background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSJ1cmwoI2cpIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJnIiB4MT0iMCIgeTE9IjAiIHgyPSIxIiB5Mj0iMSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjM0I4MkY2Ii8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjMUQ0RUQ4Ii8+PC9saW5lYXJHcmFkaWVudD48L2RlZnM+PHBhdGggZD0iTTE1IDJINmEyIDIgMCAwIDAtMiAydjE2YTIgMiAwIDAgMCAyIDJoMTJhMiAyIDAgMCAwIDItMlY3WiIvPjxwYXRoIGQ9Ik0xNCAydjRhMiAyIDAgMCAwIDIgMmg0Ii8+PHBhdGggZD0iTTEwIDlIOCIvPjxwYXRoIGQ9Ik0xNiAxM0g4Ii8+PHBhdGggZD0iTTE2IDE3SDgiLz48L3N2Zz4=');
+}
+
+.icon-badge-cent {
+ background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSJ1cmwoI2cpIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJnIiB4MT0iMCIgeTE9IjAiIHgyPSIxIiB5Mj0iMSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjM0I4MkY2Ii8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjMUQ0RUQ4Ii8+PC9saW5lYXJHcmFkaWVudD48L2RlZnM+PHBhdGggZD0iTTMuODUgOC42MmE0IDQgMCAwIDEgNC43OC00Ljc3IDQgNCAwIDAgMSA2Ljc0IDAgNCA0IDAgMCAxIDQuNzggNC43OCA0IDQgMCAwIDEgMCA2Ljc0IDQgNCAwIDAgMS00Ljc3IDQuNzggNCA0IDAgMCAxLTYuNzUgMCA0IDQgMCAwIDEtNC43OC00Ljc3IDQgNCAwIDAgMSAwLTYuNzZaIi8+PHBhdGggZD0iTTEyIDd2MTAiLz48cGF0aCBkPSJNMTAgMTBoNCIvPjwvc3ZnPg==');
+}
+
+.icon-map-pin {
+ background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSJ1cmwoI2cpIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJnIiB4MT0iMCIgeTE9IjAiIHgyPSIxIiB5Mj0iMSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjM0I4MkY2Ii8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjMUQ0RUQ4Ii8+PC9saW5lYXJHcmFkaWVudD48L2RlZnM+PHBhdGggZD0iTTIwIDEwYzAgNC45OTMtNS41MzkgMTAuMTkzLTcuMzk5IDExLjc5OWExIDEgMCAwIDEtMS4yMDIgMEM5LjUzOSAyMC4xOTMgNCAxNC45OTMgNCAxMGE4IDggMCAwIDEgMTYgMCIvPjxjaXJjbGUgY3g9IjEyIiBjeT0iMTAiIHI9IjMiLz48L3N2Zz4=');
+}
+
+.icon-contact {
+ background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSJ1cmwoI2cpIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJnIiB4MT0iMCIgeTE9IjAiIHgyPSIxIiB5Mj0iMSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjM0I4MkY2Ii8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjMUQ0RUQ4Ii8+PC9saW5lYXJHcmFkaWVudD48L2RlZnM+PHBhdGggZD0iTTE2IDJ2MiIvPjxwYXRoIGQ9Ik04IDJ2MiIvPjxjaXJjbGUgY3g9IjEyIiBjeT0iMTEiIHI9IjMiLz48cmVjdCB3aWR0aD0iMTgiIGhlaWdodD0iMTgiIHg9IjMiIHk9IjQiIHJ4PSIyIi8+PHBhdGggZD0iTTcgMjJ2LTJhMiAyIDAgMCAxIDItMmg2YTIgMiAwIDAgMSAyIDJ2MiIvPjwvc3ZnPg==');
+}
+
+/* lucide chevron-right 16×16pt #94A3B8 */
+.icon-chevron-right {
+ width: 32rpx;
+ height: 32rpx;
+ background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjOTRBM0I4IiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCI+PHBhdGggZD0ibTkgMTggNi02LTYtNiIvPjwvc3ZnPg==');
+}
+
+/* ==================== ICP 备案区 ==================== */
+
+.icp {
+ /* 内容不足一屏时,备案区自动贴住页面底部(剩余空间由 margin-top:auto 吸收) */
+ margin: auto 32rpx 0;
+ padding: 36rpx 0 48rpx;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 6rpx;
+}
+
+.icp-no {
+ font-size: 22rpx;
+ font-weight: 500;
+ line-height: 1;
+ letter-spacing: 0.6rpx;
+ color: #94a3b8;
+}
+
+.icp-copy {
+ font-size: 20rpx;
+ line-height: 1;
+ color: #a0aec0;
+}
+</style>
diff --git a/hugray-user-miniapp/pages/stock-order/list.vue b/hugray-user-miniapp/pages/stock-order/list.vue
new file mode 100644
index 0000000..5cf829d
--- /dev/null
+++ b/hugray-user-miniapp/pages/stock-order/list.vue
@@ -0,0 +1,44 @@
+<template>
+ <view class="page">
+ <view class="empty">
+ <text class="empty-title">暂无缺货订单</text>
+ <text class="empty-desc">缺货订单列表页建设中</text>
+ </view>
+ </view>
+</template>
+
+<script setup>
+import { onShow } from '@dcloudio/uni-app'
+import { ensureLogin } from '@/utils/request.js'
+
+// 每次页面显示均校验登录态
+onShow(() => {
+ ensureLogin()
+})
+</script>
+
+<style scoped lang="scss">
+.page {
+ min-height: 100vh;
+ background: #f8fafc;
+}
+
+.empty {
+ padding-top: 320rpx;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+.empty-title {
+ font-size: 34rpx;
+ font-weight: 700;
+ color: #0f172a;
+}
+
+.empty-desc {
+ margin-top: 16rpx;
+ font-size: 26rpx;
+ color: #94a3b8;
+}
+</style>
diff --git a/hugray-user-miniapp/static/tabbar/tab-home-active.png b/hugray-user-miniapp/static/tabbar/tab-home-active.png
new file mode 100644
index 0000000..6738182
--- /dev/null
+++ b/hugray-user-miniapp/static/tabbar/tab-home-active.png
Binary files differ
diff --git a/hugray-user-miniapp/static/tabbar/tab-home.png b/hugray-user-miniapp/static/tabbar/tab-home.png
new file mode 100644
index 0000000..a0791a3
--- /dev/null
+++ b/hugray-user-miniapp/static/tabbar/tab-home.png
Binary files differ
diff --git a/hugray-user-miniapp/static/tabbar/tab-order-active.png b/hugray-user-miniapp/static/tabbar/tab-order-active.png
new file mode 100644
index 0000000..e5e5a8a
--- /dev/null
+++ b/hugray-user-miniapp/static/tabbar/tab-order-active.png
Binary files differ
diff --git a/hugray-user-miniapp/static/tabbar/tab-order.png b/hugray-user-miniapp/static/tabbar/tab-order.png
new file mode 100644
index 0000000..934dcbf
--- /dev/null
+++ b/hugray-user-miniapp/static/tabbar/tab-order.png
Binary files differ
diff --git a/hugray-user-miniapp/static/tabbar/tab-plan-active.png b/hugray-user-miniapp/static/tabbar/tab-plan-active.png
new file mode 100644
index 0000000..e3f5e68
--- /dev/null
+++ b/hugray-user-miniapp/static/tabbar/tab-plan-active.png
Binary files differ
diff --git a/hugray-user-miniapp/static/tabbar/tab-plan.png b/hugray-user-miniapp/static/tabbar/tab-plan.png
new file mode 100644
index 0000000..5047549
--- /dev/null
+++ b/hugray-user-miniapp/static/tabbar/tab-plan.png
Binary files differ
diff --git a/hugray-user-miniapp/static/tabbar/tab-profile-active.png b/hugray-user-miniapp/static/tabbar/tab-profile-active.png
new file mode 100644
index 0000000..70a42cb
--- /dev/null
+++ b/hugray-user-miniapp/static/tabbar/tab-profile-active.png
Binary files differ
diff --git a/hugray-user-miniapp/static/tabbar/tab-profile.png b/hugray-user-miniapp/static/tabbar/tab-profile.png
new file mode 100644
index 0000000..49d0c95
--- /dev/null
+++ b/hugray-user-miniapp/static/tabbar/tab-profile.png
Binary files differ
diff --git a/hugray-user-miniapp/utils/request.js b/hugray-user-miniapp/utils/request.js
index 4d48c8c..6e51d28 100644
--- a/hugray-user-miniapp/utils/request.js
+++ b/hugray-user-miniapp/utils/request.js
@@ -24,6 +24,17 @@
uni.removeStorageSync(config.tokenKey)
}
+/**
+ * 页面登录态守卫:无 token 时重定向至登录页
+ * 供各页面 onShow 调用,避免登录态校验逻辑重复散落
+ * @returns {Boolean} 已登录返回 true,未登录(已触发跳转)返回 false
+ */
+export function ensureLogin() {
+ if (getToken()) return true
+ uni.reLaunch({ url: config.loginPage })
+ return false
+}
+
// ======================== 核心请求方法 ========================
/**
diff --git a/hugray-user-miniapp/utils/stock-order.js b/hugray-user-miniapp/utils/stock-order.js
new file mode 100644
index 0000000..afd91e8
--- /dev/null
+++ b/hugray-user-miniapp/utils/stock-order.js
@@ -0,0 +1,27 @@
+/**
+ * 缺货订单数据源(用户端)
+ * 说明:后端暂未提供「缺货订单数量」接口,当前使用本地 mock 数据。
+ * 接口就绪后仅需替换 getStockOrderCount 内部实现,调用方无需改动。
+ */
+
+/** 缺货订单列表页路径 */
+export const STOCK_ORDER_LIST_PAGE = '/pages/stock-order/list'
+
+/** mock:缺货订单数量(联调多笔场景时可写入本地缓存覆盖) */
+const MOCK_STOCK_ORDER_COUNT = 1
+
+/** 本地缓存键名(仅用于覆盖 mock 值) */
+const STOCK_ORDER_COUNT_KEY = 'HUGRAY_STOCK_ORDER_COUNT'
+
+/**
+ * 获取缺货订单数量
+ * @returns {Number} 缺货订单数量,无缺货时返回 0
+ */
+export function getStockOrderCount() {
+ // TODO: 后端「缺货订单数量」接口就绪后替换为真实请求
+ const cached = uni.getStorageSync(STOCK_ORDER_COUNT_KEY)
+ if (cached === '' || cached === null || cached === undefined) {
+ return MOCK_STOCK_ORDER_COUNT
+ }
+ return Number(cached) || 0
+}
--
Gitblit v1.7.1