feat: 搭建用户端小程序核心功能与页面
新增底部原生tabbar及全套图标资源,配置pages.json路由与页面样式;新增首页、健康方案、我的订单等核心页面,完善地址管理、联系我们、缺货订单等业务页面;新增登录态校验工具、缺货订单业务工具与全局提示组件;修复登录跳转逻辑适配原生tabbar页面,调整本地开发API地址;新增App级tabbar角标同步逻辑。
| New file |
| | |
| | | # 用户端小程序 首页 / 底部导航 / 缺货订单全局提示 开发设计文档 |
| | | |
| | | - 模块:`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` 内的占位反馈 | |
| | | |
| | |
| | | <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> |
| New file |
| | |
| | | <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> |
| | |
| | | // 各环境请求根地址(统一指向 RuoYi 网关,由网关按前缀转发至各微服务) |
| | | const BASE_URL_MAP = { |
| | | // 本地联调:网关默认端口 8085 |
| | | development: 'http://localhost:8085', |
| | | development: 'http://192.168.110.150:8085', |
| | | // 生产环境地址,上线前替换为正式域名 |
| | | production: 'https://api.hugray.com' |
| | | } |
| | |
| | | { |
| | | "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" |
| | | } |
| | | }, |
| | | { |
| | |
| | | "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": {} |
| | | } |
| New file |
| | |
| | | <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> |
| New file |
| | |
| | | <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> |
| New file |
| | |
| | | <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> |
| New file |
| | |
| | | <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> |
| | |
| | | <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> |
| | |
| | | if (!token) throw new Error('登录失败:未获取到登录凭证') |
| | | |
| | | setToken(token) |
| | | uni.reLaunch({ url: '/pages/index/index' }) |
| | | // 首页为原生 tabBar 页面,须用 switchTab 切换 |
| | | uni.switchTab({ url: '/pages/index/index' }) |
| | | } |
| | | |
| | | /** 跳转注册页 */ |
| New file |
| | |
| | | <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> |
| New file |
| | |
| | | <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> |
| New file |
| | |
| | | <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> |
| | |
| | | uni.removeStorageSync(config.tokenKey) |
| | | } |
| | | |
| | | /** |
| | | * 页面登录态守卫:无 token 时重定向至登录页 |
| | | * 供各页面 onShow 调用,避免登录态校验逻辑重复散落 |
| | | * @returns {Boolean} 已登录返回 true,未登录(已触发跳转)返回 false |
| | | */ |
| | | export function ensureLogin() { |
| | | if (getToken()) return true |
| | | uni.reLaunch({ url: config.loginPage }) |
| | | return false |
| | | } |
| | | |
| | | // ======================== 核心请求方法 ======================== |
| | | |
| | | /** |
| New file |
| | |
| | | /** |
| | | * 缺货订单数据源(用户端) |
| | | * 说明:后端暂未提供「缺货订单数量」接口,当前使用本地 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 |
| | | } |