From b94eb706ffc6b78d374be8e2d9d2bd6775a2338d Mon Sep 17 00:00:00 2001 From: wm <2747639460@qq.com> Date: Wed, 22 Jul 2026 21:38:56 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20Kinetic=20Dark=20=E8=AE=BE=E8=AE=A1?= =?UTF-8?q?=E8=AF=AD=E8=A8=80=20+=20Lucide=20=E5=9B=BE=E6=A0=87=E8=BF=81?= =?UTF-8?q?=E7=A7=BB=20+=20=E9=A6=96=E9=A1=B5=E8=BD=BB=E9=87=8F=E4=B8=AA?= =?UTF-8?q?=E6=80=A7=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 建立 Lucide 子集 iconfont(49 字形),全站 emoji 替换为 fc-icon 组件 - 重塑 Design Token(冷中性近黑底 + Volt 荧光绿 #B6F24E),消除硬编码颜色 - 首页登录后个性化:我的计划 / 我的收藏 / 今日推荐(纯前端复用现有 API) - 后端 DEFAULT_EMOJI→Lucide 名,PALETTE 对齐 Kinetic Dark 语义色板 - 修复 person-standing 缺失字形;14 个官方组合 seed emoji 全部映射到现有字形 --- FITCOACH-DESIGN-PROPOSAL.md | 291 +++++++++++++++++ FITCOACH-OPTIMIZATION-PLAN.md | 117 +++++++ PRD-FITCOACH优化方案.md | 308 ++++++++++++++++++ backend/src/exercises/utils/normalize.ts | 4 +- backend/src/plans/plans.service.ts | 5 +- backend/src/users/users.service.ts | 2 +- design-system/design-tokens.json | 135 ++++++++ home-personalization-mockup.svg | 121 +++++++ miniprogram/app.wxss | 142 +++++--- miniprogram/assets/fonts/fc-icon.json | 51 +++ miniprogram/assets/fonts/fc-icon.ttf | Bin 0 -> 7100 bytes miniprogram/components/avatar/index.js | 25 +- miniprogram/components/avatar/index.json | 4 +- miniprogram/components/avatar/index.wxml | 4 +- miniprogram/components/bottom-nav/index.js | 8 +- miniprogram/components/bottom-nav/index.json | 4 +- miniprogram/components/bottom-nav/index.wxml | 2 +- miniprogram/components/bottom-nav/index.wxss | 2 +- .../components/exercise-card/index.json | 4 +- .../components/exercise-card/index.wxml | 2 +- .../components/exercise-card/index.wxss | 4 +- miniprogram/components/fc-icon/index.js | 23 ++ miniprogram/components/fc-icon/index.json | 4 + miniprogram/components/fc-icon/index.wxml | 1 + miniprogram/components/fc-icon/index.wxss | 10 + .../components/gif-player/gif-player.wxss | 2 +- miniprogram/components/navbar/index.wxss | 2 +- .../collection-detail/collection-detail.js | 3 +- .../collection-detail/collection-detail.json | 3 +- .../collection-detail/collection-detail.wxml | 5 +- .../collection-detail/collection-detail.wxss | 4 +- miniprogram/pages/detail/detail.json | 3 +- miniprogram/pages/detail/detail.wxml | 4 +- miniprogram/pages/detail/detail.wxss | 6 +- miniprogram/pages/favorites/favorites.json | 3 +- miniprogram/pages/favorites/favorites.wxml | 2 +- miniprogram/pages/favorites/favorites.wxss | 4 +- miniprogram/pages/index/index.js | 113 ++++++- miniprogram/pages/index/index.json | 3 +- miniprogram/pages/index/index.wxml | 55 +++- miniprogram/pages/index/index.wxss | 71 ++-- miniprogram/pages/my-plans/my-plans.json | 3 +- miniprogram/pages/my-plans/my-plans.wxml | 2 +- miniprogram/pages/my-plans/my-plans.wxss | 4 +- miniprogram/pages/plan-detail/plan-detail.js | 5 +- .../pages/plan-detail/plan-detail.json | 3 +- .../pages/plan-detail/plan-detail.wxml | 4 +- .../pages/plan-detail/plan-detail.wxss | 10 +- miniprogram/pages/plan-edit/plan-edit.js | 18 +- miniprogram/pages/plan-edit/plan-edit.json | 3 +- miniprogram/pages/plan-edit/plan-edit.wxml | 6 +- miniprogram/pages/plan-edit/plan-edit.wxss | 4 +- .../pages/plan-favorites/plan-favorites.json | 3 +- .../pages/plan-favorites/plan-favorites.wxml | 2 +- .../pages/plan-favorites/plan-favorites.wxss | 2 +- miniprogram/pages/profile/profile.js | 14 +- miniprogram/pages/profile/profile.json | 3 +- miniprogram/pages/profile/profile.wxml | 14 +- miniprogram/pages/profile/profile.wxss | 8 +- miniprogram/pages/recommend/recommend.wxss | 4 +- miniprogram/pages/search/search.json | 3 +- miniprogram/pages/search/search.wxml | 2 +- miniprogram/utils/icon-map.js | 53 +++ miniprogram/utils/icon-name.js | 19 ++ 64 files changed, 1572 insertions(+), 173 deletions(-) create mode 100644 FITCOACH-DESIGN-PROPOSAL.md create mode 100644 FITCOACH-OPTIMIZATION-PLAN.md create mode 100644 PRD-FITCOACH优化方案.md create mode 100644 design-system/design-tokens.json create mode 100644 home-personalization-mockup.svg create mode 100644 miniprogram/assets/fonts/fc-icon.json create mode 100644 miniprogram/assets/fonts/fc-icon.ttf create mode 100644 miniprogram/components/fc-icon/index.js create mode 100644 miniprogram/components/fc-icon/index.json create mode 100644 miniprogram/components/fc-icon/index.wxml create mode 100644 miniprogram/components/fc-icon/index.wxss create mode 100644 miniprogram/utils/icon-map.js create mode 100644 miniprogram/utils/icon-name.js diff --git a/FITCOACH-DESIGN-PROPOSAL.md b/FITCOACH-DESIGN-PROPOSAL.md new file mode 100644 index 0000000..79ebb2f --- /dev/null +++ b/FITCOACH-DESIGN-PROPOSAL.md @@ -0,0 +1,291 @@ +# FITCOACH 设计语言重塑提案(v2 暗色动能系统) + +> 设计师:颜好看 | 日期:2026-07-22 | 寄存器:Product(app UI)| 平台:微信小程序(原生) +> 配套机器可读:`design-system/design-tokens.json`(已产出) +> 三轴刻度:DESIGN_VARIANCE=5(克制、可预测网格)/ MOTION_INTENSITY=4(状态反馈为主)/ VISUAL_DENSITY=4(日常应用密度) + +--- + +## 0. 当前视觉审计(暗金奢华风 → 问题清单) + +我实际通读了 `miniprogram/` 下页面与组件。结论:**当前"暗金奢华"调性与健身教练产品不匹配,且存在大量 P0 级违规(emoji 当图标)、Token 不全(硬编码)、以及 AI 模板味的结构问题。** 逐项如下。 + +### 0.1 P0 级硬伤(违反即退回) + +| # | 问题 | 位置 | 现状 | +|---|------|------|------| +| P0-1a | emoji 当功能图标(首页) | `pages/index/index.wxml` `search-pill__icon`=🔍、`cta__icon`=✨;`quick__emoji`=✨💪🏋️📋🗂️👤;`prog__emoji` | 6 个快捷入口 + 搜索 + CTA 全用 emoji | +| P0-1b | emoji 当功能图标(导航) | `components/bottom-nav/index.js` 🏠📚📋👤 | 主导航 4 项全 emoji | +| P0-1c | emoji 当功能图标(个人页/空态/占位) | `profile.wxml` ❤️🗂️⭐📋💪;`favorites/search/my-plans/plan-favorites/plan-detail` 空态 ⭐📋🔍🏋️;`exercise-card`/`detail` 占位 🏋️ | 几乎每页都有 | +| P0-1d | 收藏切换用字符 ♥/♡ | `detail.wxml` `collection-detail.wxml` | 字符当图标,应换 SVG heart | +| P0-3a | 硬编码颜色散落 | 全 13 个 wxss 中大量 `#0E1110`、`rgba(229,84,75,…)`、`#fff` 直接写死(见下表) | 改主题要改 N 处,违反"颜色全走 Token" | +| P0-2 | 当前非紫粉渐变(此项**未违规**,但注意 CTA 用金→浅金渐变 `linear-gradient(135deg, gold, gold-2)` 属"奢华模板味",新方案去掉) | `index.wxss` `.cta` | 保留为审计备注 | + +### 0.2 Token 不全 / 硬编码(必须重塑) + +全量 grep 出的硬编码色值(应下沉为 Token): + +``` +pages/index/index.wxss:5 linear-gradient(150deg, #1C2A22 0%, #0E1110 70%) ← hero 背景硬编码 +pages/index/index.wxss:26 color: #0E1110; ← CTA 前景硬编码 +plan-detail.wxss:16,50 rgba(229,84,75,0.92) / #E5544B ← danger 硬编码 +plan-detail.wxss:33,48 color: #0E1110; ← 硬编码 +profile/recommend/my-plans/favorites/plan-edit/detail ... color: #0E1110 ← 同值散落 8+ 处 +exercise-card.wxss:28 color: #0E1110; +gif-player.wxss:12 var(--gold, #c9a24b) ← 兜底硬编码(可接受但应统一) +app.wxss:5 --bg-grad: linear-gradient(160deg,#16201B,#0E1110) ← 已有变量,但页面级又重复写 +``` + +**问题归纳**:`app.wxss` 里其实定义了 `--bg/--surface/--gold/--text/--line/--radius` 等,但(1)命名偏"金色语义"(--gold/--gold-2/--gold-soft),不利去奢华化;(2)缺语义色(success/warn/danger)、缺层级阴影、缺字体三族、缺字距/字重规范;(3)页面级普遍不引用 Token 直接写 hex。 + +### 0.3 层级 / 间距 / 字体 / 对比 / 留白 / 动效 + +| 维度 | 现状问题 | +|------|----------| +| 层级 | 卡片全靠 `box-shadow: 0 12rpx 40rpx rgba(0,0,0,.35)` 抬升 + 28rpx 大圆角 → "幽灵卡片"(发丝边框 + 大阴影同屏,见绝对禁令 #9);缺乏"背景递进表达层级" | +| 间距 | 混用 `40rpx/32rpx/36rpx/28rpx/24rpx/20rpx/18rpx/16rpx/22rpx` 等,无统一尺度(`quick__grid gap:18rpx` 不在 4px 网格;`cta padding:28rpx` 非标) | +| 字体 | 只用系统 `-apple-system, "PingFang SC", ...`,无 Latin 显示字体、无等宽数据字;Hero 56rpx 与正文 28rpx 跨度大但中间阶梯缺 | +| 对比 | 正文 `--text-2:#98A39B` 在 `#0E1110` 上约 6.5:1(OK);但 `--text-3:#6B746D` 仅约 3.3:1,用于次要信息勉强达标,用于小字会吃力 | +| 留白 | 卡片内边距 28rpx、区块 padding 40rpx 偏挤;Hero 下方直接接内容,缺少节奏呼吸 | +| 动效 | 几乎无状态动效(hover/active/loading 无反馈);CTA 有光晕但无按下态;进度/加载无骨架屏 | +| 结构 | 首页用"全大写英文 eyebrow(FEATURED/PROGRAMS)+ 图标网格 + 横向滑动"结构,偏模板;且 emoji 占位使整页廉价感强 | + +### 0.4 审计结论 + +当前方案在 **P0-1(emoji 图标)、P0-3(硬编码/模板味)、Token 完整性** 三处不达标,必须重塑。设计语言从"暗金奢华"转向 **「动能暗色 / Kinetic Dark」**:保留深色高级基底,去掉金色奢华语义,换成单一有能量的珊瑚橙强调色 + 发丝边框 + 数据化表达,贴合健身教练的"专业、能量、可信赖"。 + +--- + +## 1. 新设计语言提案:Design Token 草案 + +> 完整机读版见 `design-system/design-tokens.json`。此处给出关键决策与色值。 + +### 1.1 配色(中性 85% + 强调 8% + 语义 5% + 效果 <2%) + +**基底(A1,冷中性近黑,非纯黑、非绿调奢华)** +- `--bg:#0B0F0E` 页面背景 | `--surface:#141A18` 卡片 | `--surface-2:#1C2421` 次级表面/chip +- `--fg:#ECF1EF` 主文本 | `--fg-2:#9BA7A1` 次级 | `--fg-3:#6B7670` 三级(仅用于非关键 meta,且字号≥24rpx) +- `--border:rgba(255,255,255,.08)` | `--border-strong:rgba(255,255,255,.14)` + +**强调色(A1,单一品牌色,每屏≤2 处)** +- `--accent:#FF6A3D` **Pulse 珊瑚橙**(CTA、激活导航、关键数据高亮) +- 派生:`--accent-hover:#FF8263` / `--accent-active:#E8562B` / `--accent-on:#20100A`(深暖字,橙底上对比>7:1)/ `--accent-soft:rgba(255,106,61,.12)` + +**语义色(A2,不计入"第二品牌色"——success 用于进度/连续天数这类"正向"语义)** +- `--success:#2BD9A6`(进度环/连续天数/正向趋势)| `--warn:#F2B53D` | `--danger:#FF5C5C`(替代旧 `#E5544B`)| `--info:#4FA8FF` +- 派生 soft 版用于淡底标签 + +**为什么不是紫/粉/金**:紫→粉渐变属 AI 模板红线(P0-2);金色奢华与"健身教练"调性错位(奢侈感≠专业运动感);珊瑚橙在深色上能量强、对比足、且与现品牌"暗底"自然衔接,迁移成本低。 + +**备选方向(待 PM 拍板)**:若想要更"运动品牌"的辨识度,可把 accent 换成 **Volt 荧光绿 `#B6F24E`**(Gymshark/Nike 味,更激进);或 **Ion 青 `#1FD4C4`**(更冷静健康)。本提案默认推荐珊瑚橙。 + +### 1.2 字体(含中文,三族明确) + +- **显示/标题/数字**:`'Manrope', 'PingFang SC', 'HarmonyOS Sans SC', 'Source Han Sans SC', 'Noto Sans CJK SC', sans-serif` + - Latin 用 **Manrope**(几何、运动感、不在反射拒绝清单);中文回落系统黑体 —— 小程序不打包 10MB+ CJK 字体,这是真实约束下的最优解。 +- **正文**:`'PingFang SC', 'HarmonyOS Sans SC', 'Source Han Sans SC', 'Noto Sans CJK SC', -apple-system, sans-serif`(中文系统字体优先) +- **数据/统计**:`'JetBrains Mono', ui-monospace, monospace`(等宽,tabular 数字,用于进度环中心数字、连续天数、历史曲线 Y 轴) + +**字重三级**:Regular 400(正文)/ Medium 510(按钮、小标题)/ Semibold 590(大标题)。小程序字体若无 510 字重则回落 500。 + +**字距**:eyebrow 全大写 `0.08em`;大标题 `-0.02em`;正文 `0`。 + +### 1.3 字号阶梯(rpx,750rpx=375pt) + +`xs 22 / sm 24 / base 28 / md 30 / lg 32 / xl 36 / 2xl 44 / 3xl 56(hero) / 4xl 72(巨型数字)`。中间补 `md 30` 让"正文→小标题"过渡平滑(旧方案缺这档)。 + +### 1.4 间距尺度(4px 网格 → rpx 偶数) + +`8/16/24/32/40/48/64/80/96/128(rpx)`,对应 4–64px。统一替换旧方案里的 `18rpx/22rpx/28rpx/36rpx` 等非标值。区块节奏:桌面/大屏 `section-y = 48rpx`,移动 `32rpx`(小程序默认移动,省略断点差异,但安全区 `env(safe-area-inset-bottom)` 必须在底部导航与吸底按钮处使用)。 + +### 1.5 圆角(收紧,去"过度圆滑") + +`sm 16 / md 24 / lg 32 / xl 40 / pill 9999 (rpx)`。卡片用 `md(24rpx=12px)` 或 `lg(32rpx=16px)`,**不再用 28rpx 这种临界值**;chip/pill 用 `pill`。 + +### 1.6 阴影 / 层级(Hairline First) + +默认卡片 **不用阴影,用 1px 发丝边框**(`--shadow:ring`)。仅悬浮/选中用 `--shadow:raised`;底部导航用 `--shadow:float`;**唯一允许的彩色阴影是 CTA 的 `--shadow:glow`**(珊瑚橙光晕,替代旧金色光晕)。深色模式靠"背景亮度递进"表达层级(`#0B0F0E → #141A18 → #1C2421`),不靠投影。 + +### 1.7 动效(150ms 收敛,无 bounce) + +`fast 150 / base 200 / slow 300ms`,标准缓动 `cubic-bezier(0.2,0,0,1)`。**禁 bounce/elastic**。所有交互组件补 hover/active 反馈;列表/卡片进入用 ≤300ms 的淡入(勿每节相同动画)。尊重 `prefers-reduced-motion`(小程序通过 `wx.getSystemSetting().reduceMotion` 或 CSS `@media (prefers-reduced-motion: reduce)` 降级)。 + +--- + +## 2. 图标系统:锁定 Lucide(MIT) + +### 2.1 推荐与理由 + +**锁定一套:`lucide`(MIT,原 Feather 续作)。** + +| 维度 | 说明 | +|------|------| +| 一致性 | 统一 24×24 网格、2px 描边、单线风格 → 缩到 16/20/24 视觉不变形 | +| 覆盖 | 健身语义齐全:`dumbbell`/`activity`/`flame`/`heart`/`target`/`calendar-check`/`trending-up`/`search`/`user`/`home`/`sparkles` 等 | +| 许可 | MIT,可子集化编译为 iconfont,无商用风险 | +| 调性 | 中性、现代、"赢得熟悉感"(Product 寄存器标杆),不像彩色 emoji 那样廉价 | +| 落地 | 小程序无原生 inline SVG:把**所需子集编译成 iconfont(.ttf/.woff2)**,用 `` + `font-family` 引用;或 base64 SVG ``。**严禁 emoji** | + +**为何不选其他**:Remix Icon(含 outline/fill 双风格,可作备选,但双风格易混,先锁 Lucide 单风格);Radix Icons(集太小、偏开发工具、缺健身图标);Iconify(聚合器非单一风格,**混用即违反 P0-1"锁定一套"**)。 + +### 2.2 尺寸规范 + +| 用途 | 尺寸 | 示例 | +|------|------|------| +| 行内/列表前置 | **16rpx (8px)** | 单元格图标、标签前置 | +| 按钮内 | **20rpx (10px)** | CTA、次要按钮 | +| 独立图标/导航 | **24rpx (12px)** | 底部导航、区块图标、空态 | + +> 注意:小程序 rpx 在 750 基准下 24rpx=12px 物理点;若真机偏小,导航图标可上探到 44–48rpx(见下表映射的视觉权重)。底部导航按平台规范建议视觉区 ~44–48rpx。 + +### 2.3 现有 emoji → Lucide SVG 映射表(全量) + +| 现有 emoji | 位置 | 语义 | Lucide 图标 | 备注 | +|-----------|------|------|-------------|------| +| 🔍 | index 搜索胶囊、search 页、favorites 空态 | 搜索 | `search` / 空态 `search-x` | | +| ✨ | index CTA、quick"智能推荐" | AI 推荐 | `sparkles` | | +| 💪 | quick"按部位" | 部位/身体 | `accessibility`(或 `person-standing`) | 不与器械混 | +| 🏋️ | quick"按器械"、exercise 占位、detail 占位、plan-detail 空态 | 器械/训练 | `dumbbell` | | +| 📋 | quick"官方计划"、profile"设置"、my-plans 空态 | 计划/列表 | `clipboard-list`(官方计划)/ `settings`(设置) | 两处语义不同,分两个图标 | +| 🗂️ | quick"我的计划"、profile"我的计划" | 我的计划 | `folder-open` | | +| 👤 | quick"我的"、bottom-nav"我的" | 个人 | `user` | | +| 🏠 | bottom-nav"首页" | 首页 | `home` | | +| 📚 | bottom-nav"分类" | 分类浏览 | `layout-grid` | | +| ❤️ | profile"收藏"、detail/collection 收藏切换 | 收藏 | `heart`(outline/fill 两态) | 替 ♥/♡ 字符 | +| ♥ / ♡ | detail、collection-detail 收藏按钮 | 收藏切换 | `heart` | 字符→SVG | +| ⭐ | profile"成就"、plan-favorites 空态 | 成就/收藏 | `star`(成就)/ `star` outline(空态) | | +| 🔥 | (个性化档)连续训练天数 | 连击/能量 | `flame` | 新区块用 | +| 🎯 | (个性化档)今日推荐 | 目标/推荐 | `target` | 新区块用 | +| ⚡/🌟/🤸/🧘/🥊/🚴/🐯/🦁 | plan-edit / profile 的"emoji 选择器" | 用户自选头像/计划图标 | **保留 emoji 选择器(UGC)** 或迁移到 Lucide 子集 `dumbbell/run/bike/heart/flame/target/yoga/medal` | 见 §2.4 | + +### 2.4 关于"用户自选 emoji"的边界判定(重要) + +P0-1 红线的例外是"**emoji 仅允许出现在用户生成内容(UGC)**"。因此: + +- **plan-edit 的 emoji 选择器、profile 的头像 emoji 选择器**:这是用户表达(UGC),**允许保留 emoji 选择器**。 +- 但:**用所选 emoji 渲染出的"计划图标/头像"若出现在卡片头部(plan-card、collection-detail、plan-detail 的 `header__emoji`)**,它已从"用户输入"变成"界面元素",长期应迁移到 **SVG 图标选择器**(从 Lucide 取一个运动子集:dumbbell/run/bike/heart/flame/target/yoga/medal/trophy)。 +- **短期方案(MVP)**:保留 UGC emoji 选择器;系统默认图标、所有功能导航、空态、占位、收藏切换 **一律 SVG**。这样 P0-1 在"功能图标"维度完全达标,UGC 例外成立。 + +--- + +## 3. 首页个性化区块视觉草案 + +> 寄存器=Product,故"个性化"不是营销炫技,而是**降低用户启动成本的实用信息密度**。两档按 PM 选定的范围实现。 + +### 3.1 轻量档(Light):我的计划 · 收藏动作 · 今日推荐 + +**布局顺序**(插入 Hero 之后、精选动作之前,作为"为你"区): + +``` +┌─────────────────────────────────────┐ +│ [section-header] 为你准备 action:编辑 │ +│ │ +│ ┌─ 我的计划卡(整卡可点)──────────┐ │ +│ │ 📋 推胸日 · 第 3 周 进行中 │ │ ← clipboard-list 图标 + 状态 pill +│ │ 今日 6 个动作 · 已完成 2/6 │ │ +│ │ ▓▓▓▓▓▓▓░░░░░ 33% │ │ ← 线性进度条(accent 填充) +│ └─────────────────────────────────┘ │ +│ │ +│ ┌─ 今日推荐(AI 行)──────────────┐ │ +│ │ ✨ 上斜哑铃推胸 │ │ ← sparkles + 动作名 +│ │ 根据你的推胸计划,匹配度 92% │ │ ← 一行理由(具体,非模板) +│ │ › │ │ +│ └─────────────────────────────────┘ │ +│ │ +│ [section-header] 收藏的动作 action:全部│ +│ ┌────┐ ┌────┐ ┌────┐ ← 横滑缩略卡 │ +│ │ 💪 │ │ 💪 │ │ 💪 │ (dumbbell 占位) │ +│ │名 │ │名 │ │名 │ │ +│ └────┘ └────┘ └────┘ │ +└─────────────────────────────────────┘ +``` + +**卡片样式(轻量档)** +- 我的计划卡:surface 底 + 1px border(ring),圆角 lg(32rpx),内边距 space-5(40rpx);左侧 `clipboard-list` 20rpx 图标 + 计划名(fg,semibold);右上状态 pill(`进行中`=accent-soft 底 + accent 字,或 `已完成`=success-soft 底 + success 字);进度条高 8rpx、圆角 pill、track=border、fill=accent,宽度=完成比例。 +- 今日推荐行:与计划卡同容器规格,但左侧用 `sparkles` 20rpx(accent 色);动作名 fg semibold;理由行 fg-2 24rpx 一行截断;右箭头 `›`(用 SVG `chevron-right`,不字符)。 +- 收藏动作横滑:每张 200rpx 宽,surface 底 + ring,圆角 md;缩略图区 16:10 比例(用 `dumbbell` 占位 SVG 直到真实 GIF 加载),下方动作名 24rpx 单行省略。 + +### 3.2 完整档(Full):+ 进度环 · 连续天数 · 历史曲线 + +在轻量档顶部追加一个 **「本周概览」卡**(一卡三信息,密度克制): + +``` +┌─────────────────────────────────────┐ +│ [section-header] 本周概览 │ +│ ┌─────────────────────────────────┐ │ +│ │ ╭───╮ 连续训练 │ │ +│ │ │ 4 │ 🔥 12 天 │ │ ← 进度环 + 中心数字(4/5) +│ │ │ ╱5 │ 本周 4/5 次 │ │ 火焰图标+天数(stat) +│ │ ╰───╯ │ │ +│ │ ──╱╲──╱── 历史曲线(14天) │ │ ← 内联 SVG 面积图 +│ │ 训练时长趋势 ↑ 8% │ │ trending-up + 文案 +│ └─────────────────────────────────┘ │ +└─────────────────────────────────────┘ +``` + +**视觉表达规范** +- **进度环**:SVG `` 双环。track=`border`(1.5px 在 120rpx 直径上≈2px),progress=`accent`(或成功语义绿,当"完成周目标"用 success),`stroke-linecap:round`,转角用 `stroke-dasharray` 动画 300ms 进入。中心数字用 mono 字体、`4xl(72rpx)` 主数 + `sm` 分母。环直径 ~120rpx,整体占卡左侧 1/3。 +- **连续训练天数**:`flame` 24rpx(accent 或 warn 色,不抢进度环)→ 数字 mono `xl(36rpx)` fg semibold → "天" 后缀 fg-2 `sm`。作为小统计块置于环右侧第一行。 +- **历史曲线**:内联 SVG 面积图(宽 ~100%,高 ~120rpx),14 个数据点(近 14 天训练分钟),填充 `accent` 8% 渐变 + 1.5px `accent` 描边线,末端小圆点。X 轴不标注,Y 靠趋势文案"↑ 8%"传达(**不靠颜色单独传达含义**——配文字)。下方一行 `trending-up` 20rpx + "训练时长较上周 +8%"。 +- 整卡:surface + ring,圆角 lg,内边距 space-5;内部用 `divide-y`/间距分组,不嵌套卡片。 + +### 3.3 状态覆盖(两档通用,5 态) + +| 状态 | 表现 | +|------|------| +| Loading | 区块骨架屏(与卡同形的灰色脉冲块,accent-soft 微光) | +| Empty | 空态:SVG 图标(非 emoji)+ 一句引导("还没有计划?去挑选一个官方计划")+ 主按钮(accent) | +| Error | "网络开小差了" + 重试按钮(ghost) | +| Populated | 上文布局 | +| Edge | 计划名超长 → 单行省略;进度 0% → 显示"开始第一次"而非空环;连续天数 0 → 显示"开始你的连击" | + +--- + +## 4. 反模式自检(13 项视觉清单 + P0 三绝对规则) + +### 4.1 P0 三绝对规则(逐条) + +- **P0-1 禁止 emoji 作功能图标**:✅ 本提案**全量替换**功能图标为 Lucide SVG(§2.3 映射 18 处);仅 UGC 选择器保留 emoji(§2.4 已界定边界)。通过。 +- **P0-2 禁止紫色→粉色渐变**:✅ 新配色无紫无粉;强调色为珊瑚橙 `#FF6A3D`,语义绿 `#2BD9A6` 仅用于正向。**无** `#7C3AED/#A855F7/#EC4899` 任意组合。通过。 +- **P0-3 禁止 AI 模板味**:✅ 颜色**全走 Token**(§1.1,design-tokens.json 机读);无 "Lorem ipsum"/"Welcome to";Hero 改为展示真实训练内容(§3 个性化区块即真实数据);去"全大写英文 eyebrow + 图标网格 + 金渐变 CTA"模板味。通过。 + +### 4.2 十三条视觉清单 + +| # | 检查项 | 本提案 | 结论 | +|---|--------|--------|------| +| 1 | 无 emoji 功能图标 | Lucide 全量替换 | ✅ | +| 2 | 无紫粉渐变 | 珊瑚橙 + 语义绿 | ✅ | +| 3 | 无 AI 模板味/空洞文案 | 真实数据 Hero、具体按钮文案 | ✅ | +| 4 | 颜色全走 Token | design-tokens.json,页面级禁止裸 hex(除 #000/#fff 例外,本项目仅 accent-on 用深暖色) | ✅ | +| 5 | 间距全 4px 网格 | 8/16/24/32/40/48/64/80/96/128 rpx,清掉 18/22/28/36 非标 | ✅ | +| 6 | 字体三族含中文 | Manrope+PingFang SC(CJK)/ JetBrains Mono(数据) | ✅ | +| 7 | Hero 真实内容非口号 | 个性化区块承载真实计划/进度/推荐 | ✅ | +| 8 | 图标库锁定一套+尺寸 16/20/24 | Lucide,§2.2 | ✅ | +| 9 | 无纯黑/纯灰裸用 | bg `#0B0F0E` 非纯黑;灰带冷调;语义色有 chroma | ✅ | +| 10 | 对比度 + reduced-motion + 焦点 | fg/fg-2 达标;motion 尊重 reduced;focus-ring accent 3px | ✅(实现期需 audit 复测) | +| 11 | 安全区 + 触摸目标≥44 | `env(safe-area-inset-bottom)`;图标按钮命中区≥88rpx | ✅ | +| 12 | 组件 5 态覆盖 | §3.3 loading/empty/error/populated/edge | ✅ | +| 13 | 每屏强调色≤2 处 | accent(珊瑚橙) 用于 CTA+激活导航;success 仅正向语义;克制 | ✅ | + +### 4.3 设计时主动规避的 AI 模板禁令(对照绝对禁令) + +- 侧条纹边框:`border-left>1px` 彩色强调 → **不用**,选中态用 `border-strong` 整圈或 accent-soft 底。 +- 渐变文字 `background-clip:text` → **不用**,强调靠字重/字色。 +- 默认毛玻璃 → 仅底部导航用 `backdrop-filter:blur`(功能目的:内容滚过不刺眼),不作装饰。 +- 幽灵卡片(1px 边框+大阴影同屏)→ **改为 hairline-first**,默认无阴影。 +- 过度圆角(≥24px)→ 卡片上限 16px(32rpx)。 +- 每节相同淡入 → 进入动画匹配内容(进度环转、曲线绘、卡淡入各异)。 +- 奶油/米色默认化 → 基底为冷中性近黑,暖意由 accent 传达。 + +--- + +## 5. 落地建议(给架构师 / 前端) + +1. **图标**:从 Lucide 取 §2.3 所列子集,用 `iconfont` 工具编译为 `.ttf/.woff2` 放入 `miniprogram/assets/fonts/`,在 `app.wxss` 用 `@font-face` 声明 `fc-icon`;组件用 `{{codepoint}}`。需我提供 codepoint 映射表可补。 +2. **Token 注入**:把 `design-tokens.json` 的 color/spacing/radius/shadow 编译进 `app.wxss` 的 `page{}` 变量;fontSize/letterSpacing/motion 同步为 WXSS 变量;JS 侧把字号/色值常量化供 `setData` 用(如动态 `style="background:{{item.color}}22"` 这类应改为 Token 变量)。 +3. **去硬编码**:全局替换 `#0E1110`→`var(--bg)`、`#E5544B`→`var(--danger)`、`rgba(229,84,75,…)`→`var(--danger-soft)` 等(grep 命中见 §0.2)。 +4. **动态色迁移**:`index.wxml` 里 `style="background:{{item.color}}22;color:{{item.color}}"`(训练组合/计划卡按色渲染)应改为从"预设语义色板"取 Token 别名(如 `var(--accent-soft)`),避免任意 hex 注入破坏系统。 +5. **需 PM/架构师确认**:accent 最终色(珊瑚橙 vs Volt 绿 vs Ion 青);个性化区块选轻量档还是完整档;UGC emoji 选择器是否迁移到 SVG 图标选择器。 + +--- +*文档结束。机读 Token:`design-system/design-tokens.json`。视觉草案可出 SVG 线框(如需我可补 `home-personalization-mockup.svg`)。* diff --git a/FITCOACH-OPTIMIZATION-PLAN.md b/FITCOACH-OPTIMIZATION-PLAN.md new file mode 100644 index 0000000..09bfb81 --- /dev/null +++ b/FITCOACH-OPTIMIZATION-PLAN.md @@ -0,0 +1,117 @@ +# FITCOACH 系统优化 · 整合实施方案(待确认) + +> 整合自三份调研:PM《优化 PRD》(PRD-FITCOACH优化方案.md)、设计师《动能暗色提案》(FITCOACH-DESIGN-PROPOSAL.md + design-system/design-tokens.json)、架构师技术方案(本文件第 2 节,由团队负责人据代码实测补齐)。 +> 已确认方向(用户 2026-07-22 拍板):① 先出方案文档、确认后再开发;② 首页个性化两档都要(先轻量后完整);③ UI 重塑设计语言。 +> P0 红线:禁止 emoji 当功能图标(统一 Lucide SVG iconfont)、禁止紫粉渐变、禁止硬编码颜色(一律走 Token)。 + +--- + +## 1. 现状诊断(代码实测结论) + +| 问题 | 证据 | 影响 | +|------|------|------| +| 首页零个性化 | `pages/index/index.js` `loadData()` 只拉全局 stats/featured/collections,无 openid 维度;"今天,练点什么?"写死 | 登录与不登录完全一致,无归属感 | +| emoji 图标系统性违规 | 首页 `🔍✨💪🏋️📋🗂️👤`、底部导航 `🏠📚📋👤`、profile `❤️🗂️⭐📋💪`、各空态、占位 `🏋️`;后端 `users.service.ts` 默认 avatar=`🏃`、`plans.service.ts` 默认 emoji=`📋` | 触发 P0-1 红线,全站廉价感 | +| 加载/错误态缺失 | 首页 `loading` 是死代码(wxml 未引用)首屏空白;`request.js` 失败只 toast,网络失败与无数据无区分、无重试 | 弱网下体验差、无兜底 | +| 响应速度 | `request.js` 无 timeout(wx.request 默认不强制超时);首页 `Promise.all` 无缓存;底部导航 `reLaunch` 重置栈→切 tab 重拉 | 弱网挂起、切 tab 白屏 | +| 硬编码颜色散落 | 13 个 wxss 裸写 `#0E1110`(8+处)、`#E5544B`/`rgba(229,84,75,…)`;`app.wxss` Token 命名偏金、缺语义色/字号阶梯/字距 | 改主题要改 N 处 | +| 设计令牌基础尚在 | `app.wxss` 已定义 `--bg/--surface/--gold/--text/--line/--radius` | 重塑改一处 + 组件局部即可,不动业务 | + +**关键利好**:个性化所需接口其实已就绪——`planSvc.listMine()`→`GET /api/plans`(按 openid 隔离,已验证 `plans.controller.ts:35`)、`favSvc.listExercises()`→`GET /api/favorites/exercises`(已验证 `favorites.controller.ts:26`)、`auth` 登录态。轻量档**零后端改动**即可上线。 + +--- + +## 2. 架构技术方案(补齐) + +### 2.1 轻量档(P0,纯前端,零后端改动) +- **首页 `index.js`** `onLoad`:若已登录(`wx.getStorageSync('access_token')` 存在且有效),并行拉: + - `planSvc.listMine()` → 我的计划列表 + - `favSvc.listExercises()` → 我的收藏动作 + - 今日推荐(前端推导):取 `GET /api/plans/official` 分级 + 用户收藏/浏览,按"未练过/匹配目标"排序取 top3(复用推荐页现有推导逻辑,无需新接口) + - 用户资料:`auth.getProfile()` 或本地 `userInfo` +- **渲染**:`isLogin` 为真 → 在 Hero 后插入"为你准备"区(时段问候 + 我的计划卡 + 收藏横滑 + 今日推荐);否则保留原通用内容 + 轻引导登录。 +- **缓存**:用 `wx.setStorageSync` 缓存 listMine/favorites(key 带 openid);切 tab(`onShow`) 优先读缓存、后台静默刷新,避免 `reLaunch` 重拉白屏。 +- 工作量:前端 2-3 人日。 + +### 2.2 完整档(P1,新增后端数据) +沿用现有 `common/store.ts` 的 JsonStore 单例模式(已验证:`getStore(name)` 进程内单例、整体落盘 `data-store/.json`)。 + +**新集合 `checkins`**(data-store/checkins.json): +``` +{ id, openid, date(YYYY-MM-DD, openid+date 唯一), planId?, exerciseIds[], durationMin?, completed(bool), createdAt } +``` +**users 扩展字段**:`fitnessGoal`(减脂/增肌/塑形/保持健康)、`level`(beginner/intermediate/advanced)、`trainFreq`(每周次数)、`onboarded`、`avatar`(改 Lucide 图标名/URL,去掉 emoji)。 + +**打卡触发点**:plan-detail「完成训练」按钮 / 首页「今日打卡」按钮 → `POST /api/checkins`。 + +**派生计算**(无需单独存储,由 checkins 实时算): +- `streak`:按 date 倒序,从今天/昨天起连续有 checkin 的天数 +- `summary(range=7|30)`:训练次数、总时长、动作分布、完成率 +- 历史曲线:近 14 日每日 checkin 数/时长 → 前端 SVG 面积图 + +**新增 API**(均 `@UseGuards(AuthGuard)` + `CurrentUser` 取 openid,沿用 plans/favorites 模式): +| 方法 | 路径 | 说明 | +|------|------|------| +| POST | `/api/checkins` | 创建/更新当日打卡 | +| GET | `/api/checkins/streak` | `{ streak, lastCheckinDate }` | +| GET | `/api/checkins/summary?range=7\|30` | `{ count, totalDuration, byExercise[], byDay[] }` | +| PATCH | `/api/users/me` | 扩展画像字段 | + +- 工作量:后端 3-4 人日 + 前端 4-5 人日。需新建 `checkins.module/controller/service/(entity+dto)` + `users.service` 扩展。 + +### 2.3 UI Token 化改造(P0) +- 以 `design-system/design-tokens.json` 为唯一真源,整体替换 `app.wxss` 的 `page` 变量:冷中性底(`#0B0F0E/#141A18/#1C2421`) + 珊瑚橙 accent(`#FF6A3D`) + 语义色(success/warn/danger/info) + 字号阶梯(22-72rpx) + 间距尺度(4px 网格) + 圆角(16/24/32) + 阴影(Hairline First) + 动效(150/200/300ms)。 +- 13 个页面/组件 wxss:把裸写 `#0E1110`/`#E5544B`/大阴影改为引用新 Token。 +- 图标落地:编译 Lucide 子集(约 30-40 个图标)为 `miniprogram/assets/fonts/fc-icon.ttf`,`app.wxss` `@font-face` 声明 `fc-icon`;提供 emoji→Lucide name→codepoint 映射表,全站 `` 替换 emoji 文本节点。 +- 风险:主包体积(iconfont ttf <50KB 可控);个别 wxml 用 emoji 字符作图标需逐个替换;微信支持本地 ttf 自定义字体。 + +### 2.4 综合 UX 技术支撑(P0/P1) +- **请求治理**:`request.js` 加 `timeout: 8000` + 失败类型区分(网络/业务/无数据) + 重试一次 + 可选缓存层,弱网不再挂起。 +- **骨架屏**:首页/列表页加 skeleton 占位(token 色块),替代当前首屏空白;接上原 index.wxml 死代码 `loading` 或换 skeleton。 +- **错误重试**:request 失败提供"重试"按钮(error 态),替代纯 toast。 +- **tab 保活**:bottom-nav `reLaunch` 重置栈→改 `switchTab` + 首页数据缓存 + `onShow` 静默刷新,切 tab 不白屏。 +- **常驻搜索**:首页 search-pill 换成 SVG 图标(或 navbar 常驻)。 + +### 2.5 技术约束与风险 +- JsonStore 单进程单例、整体落盘——打卡高频写 flush 阻塞;demo 量级 OK,生产前置足够。 +- 主包 2MB 限制:iconfont ttf <50KB 可控;不打包 CJK 字体(用系统字体,已确认)。 +- 图标库:**锁定 Lucide(MIT)**,与设计师提案一致 ✅;尺寸 16/20/24rpx;编译为 iconfont 子集。 + +--- + +## 3. 实施路线图(优先级) + +| 优先级 | 项 | 档位 | 后端 | 前端 | 依赖 | +|--------|----|------|------|------|------| +| **P0** | emoji→Lucide SVG 图标替换(全站 18+ 处 + 后端默认 avatar/emoji) | UI | 小 | 中 | 无 | +| **P0** | Design Token 重塑(Kinetic Dark,app.wxss + 13 wxss) | UI | 无 | 中 | 无 | +| **P0** | 首页轻量个性化(问候+我的计划+收藏+今日推荐) | 个性化·轻 | 无 | 2-3人日 | 现有 API | +| **P0** | 骨架屏 + 错误重试 + 请求 timeout | UX | 无 | 2-3人日 | 无 | +| **P1** | 完整个性化后端(checkins store + 3 接口 + users 扩展) | 个性化·全 | 3-4人日 | — | 轻量档先上 | +| **P1** | 首页完整档区块(进度环+连续天数+历史曲线) | 个性化·全 | — | 4-5人日 | 后端接口 | +| **P1** | 导航重构 + 常驻搜索 + tab 保活 + 缓存 | UX | 无 | 中 | 请求治理 | +| **P2** | 下拉刷新 / 长按手势 / 进入动画 | UX | 无 | 小 | 无 | + +**MVP 切割**:P0 四项先上(验证假设、零风险、零后端)→ P1 完整档跟进(需打卡数据沉淀)→ P2 打磨。 +**上线顺序**:UI 重塑 + 轻量个性化 + 骨架屏 同批(都在前端,互不阻塞);完整档独立一批(需后端发布)。 + +--- + +## 4. 已锁定决策(2026-07-22 用户确认) + +1. **强调色 accent = Volt 荧光绿 `#B6F24E`**(用户选,非设计师默认珊瑚橙)。设计 Token 中 `accent` 系列全部替换为该绿:`accent:#B6F24E` / `accent-hover:#C7F56E` / `accent-active:#9EDB3A` / `accent-on:#0E1A05`(深绿字,对比>7:1) / `accent-soft:rgba(182,242,78,.12)` / `accent_ring:rgba(182,242,78,.38)` / `glow:0 8rpx 24rpx rgba(182,242,78,.28)`。每屏≤2处。 +2. **UGC emoji 选择器 → 现在就迁 Lucide 运动子集图标选择器**(dumbbell/run/bike/heart/flame/target/yoga/medal/trophy 等),不保留 emoji 入口。plan-edit/profile 的计划图标与用户头像图标均走 Lucide。 + +--- + +## 5. Out-of-Scope(本期不做) +社交/社区、商城电商、课程直播跟练、饮食营养、硬件联动、i18n、SQL 迁移、生成式 AI 文案、付费会员。 + +--- + +## 附:交付物索引 +- `PRD-FITCOACH优化方案.md` — PM 优化 PRD(现状/竞品/三块方案/RICE/验收标准) +- `FITCOACH-DESIGN-PROPOSAL.md` — 设计师动能暗色提案(审计/Token/图标/视觉草案) +- `design-system/design-tokens.json` — 机读 Design Token 草案(唯一真源) +- `home-personalization-mockup.svg` — 完整档首页个性化区视觉线框 +- 本文件 — 整合实施方案 + 架构补齐 + 路线图 + 待定项 diff --git a/PRD-FITCOACH优化方案.md b/PRD-FITCOACH优化方案.md new file mode 100644 index 0000000..cb48483 --- /dev/null +++ b/PRD-FITCOACH优化方案.md @@ -0,0 +1,308 @@ +# FITCOACH 优化评估与改进方案(PRD) + +> 文档属性 +> - 版本:v1.0(方案稿,待产品负责人确认后进入开发) +> - 日期:2026-07-22 +> - 作者:许清楚(产品经理) +> - 范围:在现有「微信健身小程序 + NestJS 后端 + JSON 数据落地」架构上做体验优化,**不新增业务线、不写代码**,先方案后开发 +> - 目标三大块:① 页面 UI 美观度(重塑设计语言)② 登录后首页个性化(轻量档 + 完整档)③ 综合 UX(交互 / 信息架构 / 响应速度 / 导航) + +--- + +## 0. 结论速览(给决策者的话) + +FITCOACH 当前是一个"功能完整但体验半成品"的产品:后端数据能力(计划、收藏、用户、推荐)已经具备,但**首页完全没有用上这些数据**,且**全站用 emoji 当功能图标**(触碰团队 P0 红线),加载/错误态缺失,首页一次请求卡死整页。 + +三块改进的整体排序建议: + +- **P0(本期必做,低风险高显效)**:emoji 图标清零 + 设计令牌重塑(块一);首页轻量档个性化(块二);全站骨架屏 + 错误重试态(块三)。 +- **P1(本期第二阶段)**:完整档个性化(新增训练打卡 + 连续天数 + 进度曲线);用户画像 onboarding;全局常驻搜索 + 导航重构;请求超时与缓存 + tab 保活。 +- **P2(打磨 / 下期)**:周/月训练报告(雷达图);下拉刷新 / 长按 / 过渡动画等手势完善;无障碍对比度审计。 + +--- + +## 1. 现状诊断(基于代码实测,含文件与行号证据) + +### 1.1 首页零个性化(核心痛点) + +`miniprogram/pages/index/` 是一个**完全静态**的页面: + +- Hero 文案写死为 "今天,练点什么?"(`index.wxml:5`),**没有任何用户昵称问候、没有时段判断**。 +- `loadData()` 只拉取全局数据:`getStats()` / `listExercises()` / `listCollections()`(`index.js:28-46`),**不带任何 openid / 用户维度**。 +- 已存在、却未用于首页的个性化数据(接口全部就绪,纯前端即可调用): + - 我的计划:`planSvc.listMine()` → `GET /api/plans`(`services/plan.js:24`,已在 `my-plans.js` 使用) + - 收藏动作:`favSvc.listExercises()` → `GET /api/favorites/exercises`(`services/favorite.js:17`,已在 `favorites.js` 使用) + - 用户资料(含昵称):`auth.getProfile()`(`utils/auth.js:73`,本地存储读取,零网络开销) + - 智能推荐引擎:`svc.recommend({target, equipment})`(`services/exercise.js:45`)——但需 `target` 参数,首页当前未调用 +- 交付与文档不一致:`OVERVIEW.md:34` 声称首页有"渐变 hero + **问候** + 智能推荐 CTA",实际"问候"并未实现,只是通用文案。 + +**结论**:个性化不是"从零造数据",而是"把已经存在的能力接到首页上"。这正是轻量档改动小、见效快的根本原因。 + +### 1.2 emoji 图标违规(团队 P0 红线,系统性问题) + +全站把 emoji 当作功能图标使用,违反"禁止 emoji 作为功能图标"规则。实测清单: + +| 位置 | 文件:行 | emoji 用途 | +|------|---------|-----------| +| 底部导航 | `components/bottom-nav/index.js:7-10` | 首页/分类/计划/我的 用 🏠📚📋👤 | +| 首页快捷入口 | `pages/index/index.js:14-19`、`index.wxml:29` | ✨💪🏋️📋🗂️👤 | +| 首页搜索 / CTA | `pages/index/index.wxml:9,14` | 🔍✨ | +| 搜索页图标 | `pages/search/search.wxml:5` | 🔍 | +| 个人中心单元格 | `pages/profile/profile.wxml:6,41,46,51,56` | 💪❤️🗂️⭐📋 | +| 空态插画 | `favorites.wxml:20`、`my-plans.wxml:20`、`detail.wxml:16`、`plan-detail.wxml:23`、`plan-favorites.wxml:16` | 🔍📋🏋️⭐ | +| 练习卡占位图 | `components/exercise-card/index.wxml:10` | 🏋️ | +| 头像组件默认 | `components/avatar/index.js:5`、`index.wxml:4` | 💪 | +| 头像/计划 emoji 选择器 | `pages/profile/profile.js:6`、`pages/plan-edit/plan-edit.js:4` | 一整组 emoji 数组 | + +**数据层也被污染**(必须一并清理,否则新用户一进来还是 emoji): +- `backend/src/users/users.service.ts:26`:`avatar` 默认值 `'🏃'` +- `backend/src/plans/plans.service.ts:37`:`DEFAULT_EMOJI = '📋'`,且 `PlanRecord.emoji` 字段贯穿数据库与前端 + +**影响**:emoji 在微信不同机型/系统字体下渲染不一致,描边与对比度不可控,品牌感弱,无障碍差(屏幕阅读器念出无意义字符)。 + +### 1.3 信息架构与导航 + +- 底部导航 4 项:首页 / 分类 / 计划 / 我的。其中"计划"实际指向 `my-plans`(**个人**计划),与"分类"(内容浏览)并列,语义层级略乱;"官方计划"入口散落在首页快捷入口 + `collection` 页,缺乏统一归属。 +- 首页单页塞了 6 个 section(hero / 2×3 快捷 / 精选动作 / 训练组合 / 按部位 / 热门器械)+ 160rpx 底部留白(`index.wxml` 全篇),首屏重点被稀释。 +- **无全局常驻搜索**:搜索只在 hero 内一个 pill,点开才进二级 `search` 页(`index.wxml:8-11`)。 +- 自定义导航栏 `navbar` 仅在子页使用(`favorites.wxml:2`、`my-plans.wxml:2`),首页没有统一导航条,与子页视觉割裂;`app.json:18` 设了 `navigationStyle: custom`,每个页面需自行承担导航。 + +### 1.4 加载态 / 空态 / 错误态 + +- **首页 loading 是死代码**:`index.js:12` 声明 `loading: true`,但 `index.wxml` 全程未引用任何 `wx:if="{{loading}}"` → 首屏在数据到达前是**空白/白屏**,无任何指示。 +- 子页仅有纯文本"加载中…",无骨架屏(`favorites.wxml:24`、`my-plans.wxml:25`)。 +- **错误态彻底缺失**:`utils/request.js:34-41` 失败时只弹 toast,页面 `catch` 后 `setData({loading:false})` 得到空列表(`favorites.js:29-31`、`my-plans.js:28-30`),**网络失败与"真·无数据"无法区分,且无重试入口**。 +- 空态结构本身尚可(插画 + 文案 + 引导按钮),但插画是 emoji(见 1.2),随块一替换。 + +### 1.5 响应速度 / 交互 + +- `utils/request.js:16-42` 的 `wx.request` **没有 timeout 配置**(微信默认也无强制超时)→ 弱网下可能长时间挂起,用户无感知。 +- 首页 `Promise.all` 聚合 3 个接口(`index.js:30-34`),任一慢则整体延迟;无本地缓存、无预取。 +- 底部导航用 `wx.reLaunch` 重置页面栈(`bottom-nav/index.js:18`)→ 每次切 tab 整页重建、首页 `loadData()` 重跑,**跨 tab 状态不保留**,体验被切断;行业通行做法是保留 tab 状态(如原生 tabBar / 自定义保活)。 +- 全站**无下拉刷新**(`onPullDownRefresh` 未出现)。 +- GIF 演示走 jsDelivr CDN 直链(`OVERVIEW.md:59`),弱网加载慢,无占位过渡。 + +### 1.6 设计令牌现状(这是"重塑"的有利基础) + +暗金奢华风集中定义在 `app.wxss` 的 `page` 选择器内(CSS 变量): + +- 主色 `--gold:#D9B36C`、`--gold-2:#F0D9B9`、点缀 `--teal:#58C9B9`;底色 `--bg:#0E1110`(`app.wxss:4-12`) +- 圆角 `--radius:28rpx` / `--radius-sm:18rpx`;阴影 `--shadow`(`app.wxss:17-19`) + +**利好**:令牌高度集中,重塑设计语言只需改 `app.wxss` 一处 + 个别组件局部样式,**几乎不动业务逻辑**。 + +**风险点**:`navbar / section-header / chip / level-tag / plan-card / exercise-card` 等组件各有独立 `.wxss`,需排查是否存在硬编码色值(如 `plan-card` 用到计划自带的 `color` 字段、`prog` 用到集合 `color`),重塑时要一并纳入令牌体系。 + +--- + +## 2. 竞品对标(联网调研 5 家健身 / 运动 App) + +> 说明:项目内 `references/industries/` 下未提供健身行业文档,以下对标全部来自联网调研(2026-07-22 检索)。 + +| App | 类型 | 设计语言 / UI 特征 | 个性化首页做法 | 可借鉴点 | 我们的差距 | +|-----|------|-------------------|---------------|----------|-----------| +| **Keep 9.0**(直接竞品,国内最大) | 课程+计划+社区 | 信息架构做减法:砍商城、课程/计划双 tab、单列 Feed、灰白一致入口 | 首页按"往期运动数据"智能推荐课程;"我的"页重构为 数据概览/全部数据/运动记录;**健康状态卡片**(仪表盘)给当日评估+建议;**上周周报五维雷达图+环比箭头**;1 分钟问卷生成专属计划 | ① 个性化 = 用户历史 + 当下状态 ② 进度可视化(雷达/7日图)③ AI 克制:每条建议有数据支撑,不煽情 ④ onboarding 问卷建训练画像 | 我们首页无任何个性化;无状态/周报可视化 | +| **Apple Fitness+**(标杆) | 设备+课程 | 黑色背景、固定三环配色、高对比无障碍、一致视觉层级 | **Activity Rings(三环:Move/Exercise/Stand)** 游戏化进度;三核心指标化简复杂数据,降低认知负荷;目标可个性化(Move goal 最常调) | ① 游戏化连续/进度直击"完整档 连续天数" ② 用最少指标表达状态 ③ 黑色背景+固定配色的一致语言 | 我们无进度/连续概念;图标系统混乱 | +| **Fitbod**(直接竞品,AI 力量训练) | 计划+记录 | **干净、聚焦、不堆徽章**;肌肉恢复热力图直观 | 基于训练历史**每日更新个性化计划**;输入目标/经验/器械/时长 → 生成"living training profile";记录每次训练→算 1RM→推荐下次;"今天在哪练"按环境适配 | ① 训练画像驱动自适应 ② 身体就绪度可视化(恢复热力图)③ 界面克制 | 我们无训练记录,无法自适应 | +| **Strava**(替代方案,社交/连续) | 社区+记录 | Feed 基于互动行为的个性化排序;收藏运动员置顶 | **连续/挑战/分段(segments)机制**驱动留存 | ① 连续天数 + 挑战的游戏化钩子 ② 但**强制非时间序被用户骂** → 教训:个性化要可关闭/可切换 | 我们无社交层,但连续机制可独立借鉴 | +| **Gymshark Training**(替代方案,品牌) | 品牌+免费计划 | 极简、品牌一致、快速 onboarding(~1 分钟) | **个性化弱**:onboarding 只问年龄性别;进度追踪仅"标记完成/记录组数" | 反面教材:好设计 ≠ 好教练;缺 adaptive programming 与 analytics 会被进阶用户抛弃 | 提醒我们:仅做"好看的首页"不够,需有数据闭环 | + +**提炼 6 条可迁移最佳实践**: + +1. 个性化首页 = "用户已有数据" + "当下状态"(Keep 健康状态卡 / Fitbod 恢复热力图)。 +2. 游戏化连续 / 进度(Apple 三环 / Strava streak)——直接支撑"完整档 连续训练天数"。 +3. 自适应 AI 但克制(Keep 不煽情、每条建议有据可依),不堆无意义徽章(Fitbod)。 +4. 信息架构做减法(Keep 9.0 砍商城、简化导航、单列 Feed)——治我们的首页 6 段过载。 +5. onboarding 问卷建训练画像(Fitbod / Keep 1 分钟)——支撑完整档要新增的"训练目标"字段。 +6. 进度可视化(雷达图 / 7 日图 / 曲线)而非数字堆砌。 + +--- + +## 3. 三块改进方案 + 实施优先级 + +### 3.1 块一:页面 UI 美观度 —— 重塑设计语言(P0,用户已确认方向) + +**目标**:由设计师重做 Design Token(可能换主色 / 排版体系),统一图标体系,彻底清除 emoji。 + +方案拆项: + +- **D1 新 Design Token 体系**:在 `app.wxss` 重定义颜色 / 字体 / 间距 / 圆角 / 阴影 / 层级变量(替换现有 `--gold` 等)。是否换主色由设计师定,但**禁止紫色→粉色渐变**(团队红线)。 +- **D2 统一 SVG 图标库**:引入一套 SVG / iconfont 图标(具体选型由架构师按项目锁定,**不预设具体库**),全站功能图标改为引用图标名(如 `icon-home` / `icon-search`),替换全部 emoji(清单见 1.2)。 +- **D3 排版体系升级**:标题层级、字重、行高、中文间距规范化,解决当前 hero 56rpx 与正文 28rpx 之间缺乏中间层级的问题。 +- **D4 组件令牌对齐**:`navbar/section-header/chip/level-tag/plan-card/exercise-card` 去硬编码色值,统一引用 D1 令牌;`plan-card` 的 `color`、`prog` 的 `color` 改为"用户色板 + 令牌"双轨,避免破坏现有计划配色功能。 +- **D5 对比度与无障碍**:确保正文/背景对比度达 WCAG 2.1 AA;图标有文字标签或 aria 等价。 + +**优先级**:P0(用户已选"重塑设计语言",且 emoji 违规是团队红线,必须清零)。 +**成本 / 影响**:设计约 1 人周 + 前端改造约 3–5 人日。令牌集中,业务逻辑零改动,影响面 = 全站视觉,Impact 高、Effort 中。RICE 视角 Reach=全部用户(10)、Impact=3(巨大)、Confidence=100%(已确认方向)、Effort≈6 → **Score≈5.0**。 + +### 3.2 块二:登录后首页个性化(两档都要,分 MVP 切割) + +#### 轻量档(P0,改动小见效快,不新建数据) + +**范围**:在首页顶部注入"已存在数据"的个性化模块,不新建任何后端数据。 + +- **时段问候**:读 `auth.getProfile().nickname` → "早上好 / 下午好 / 晚上好,{昵称}"。零网络开销。 +- **我的计划**:`planSvc.listMine()` 取最近 N 个,横滑卡片 + "继续训练"入口。 +- **我的收藏**:`favSvc.listExercises()` 取最近 N 个收藏动作。 +- **今日推荐**:复用 `svc.recommend`,`target` 由"最近计划 / 收藏"推导(不新建数据,仅做数据派生);若无任何历史,回退到现有"精选动作"。 +- **未登录降级**:保留现有通用首页(不报错、不空白)。 + +**依赖**:仅前端;全部接口(`plan.js` / `favorite.js` / `exercise.js` / `auth.js`)已存在。 +**成本**:前端 2–3 人日,零后端改动。RICE:Reach=登录用户(8)、Impact=2(高)、Confidence=100%、Effort≈3 → **Score≈5.3**。 + +#### 完整档(P1,新增训练记录 / 进度历史) + +**新增后端数据**(沿用现有 `store.ts` JSON 落地模式,成本可控): + +- 数据模型 `checkins` store:`{ id, openid, date(YYYY-MM-DD), planId?, exerciseIds[], durationMin?, completed }` +- 派生指标:连续训练天数(streak)、近 30 日训练次数、近 7 日时长 / 动作分布 +- 用户画像扩展(`users` store + `users.service`):`fitnessGoal`(减脂/增肌/塑形/保持健康)、`level`、`trainFreq` 偏好 + +**新增接口**: + +- `POST /api/checkins`(打卡,可关联计划) +- `GET /api/checkins/streak`(连续天数 + 今日是否已打卡) +- `GET /api/checkins/summary?range=7|30`(进度曲线数据) +- `PATCH /api/users/me` 扩展画像字段(已有,仅加字段) + +**首页展示**: + +- 进度曲线(近 7/30 日训练次数或时长,SVG 折线 / 柱状) +- 连续训练天数徽标(对标 Apple 三环 / Strava streak 的游戏化钩子) +- "今日打卡"按钮(首页 + 计划详情页) +- 周/月训练报告(对标 Keep 周报雷达图,列为 P2 增强) + +**依赖**:后端 1 个新 store + controller/service(参照 `common/store.ts`);前端新增打卡与曲线组件;需 onboarding 收集 `fitnessGoal`(见 P1 画像项)。 +**成本**:后端 3–4 人日 + 前端 4–5 人日。RICE:Reach=活跃训练用户(6)、Impact=3(巨大)、Confidence=80%(模式可参照)、Effort≈9 → **Score≈1.6** → 排 P1。 +**关键约束**:连续天数 / 曲线**必须有打卡数据沉淀才有意义**,故完整档必须在轻量档上线、积累初期数据后跟进。 + +#### 两档 MVP 切割与依赖关系 + +``` +[轻量档] 前端 only,复用现有 API + └─ 首页个性化容器(问候 + 我的计划 + 收藏 + 今日推荐) + │ + ├─(共享)时段问候 + 容器布局,完整档直接复用 + │ +[完整档] 新增 checkins store + 打卡接口 + 曲线组件 + └─ 在容器下方追加:进度曲线 + 连续天数 + 今日打卡 + │ + └─ 依赖:用户画像 onboarding(收集 fitnessGoal/level) +``` + +- **上线顺序**:轻量档(P0)先上 → 完整档(P1)在 1–2 个迭代后上。 +- **为什么这样切**:轻量档零后端风险、当天可见效,先验证"首页个性化"假设并拉高激活;完整档需要新数据闭环,先轻后重可避免一次性大改带来的回归风险。 +- **Backlog(不在本期 MVP)**:周/月雷达报告、社交挑战、AI 生成式教练文案。 + +### 3.3 块三:综合 UX 优化清单(按维度 + 优先级) + +**导航 / 信息架构(P1)** +- 重审底部导航结构:"计划"语义是否改为"我的"(与"首页/发现/进度/我的"更顺);完整档上线后考虑新增"进度"入口。 +- 全局常驻搜索:在导航栏内置搜索图标,替代仅 hero 内 pill。 +- 首页 section 减负:首屏聚焦"个性化区 + 今日推荐",其余(训练组合 / 按部位 / 热门器械)下移或折叠。 + +**加载态 / 空态 / 错误态(P0)** +- 全站骨架屏(首页 / 列表页),替换死 `loading` 字段与纯文本"加载中…"。 +- 错误态区分"网络失败"与"无数据",网络失败时提供"重试"按钮(治愈 1.4 的静默失败)。 +- 空态保留结构,插画换 SVG(随块一 D2)。 + +**响应速度(P1)** +- `request.js` 增加 `timeout`(建议 8s)+ 统一 loading 策略,避免弱网挂起。 +- 首页接口并行 + 字段裁剪;关键数据(计划 / 收藏 / 资料)本地缓存,进首页即渲染缓存、后台静默刷新。 +- 底部导航 `reLaunch` 改为保活方案(原生 tabBar 或自定义栈保留),避免切 tab 重拉数据。 + +**手势 / 交互(P2)** +- 首页 / 列表支持下拉刷新(`onPullDownRefresh`)。 +- 横滑区增加"更多 / 分页点"提示一致性。 +- 收藏 / 计划卡片增加长按 / 左滑等快捷操作。 +- 统一点击反馈(`hover-class`)与过渡动画。 + +--- + +## 4. 实施路线图(优先级总表) + +| 编号 | 改进项 | 归属块 | 优先级 | 影响 | 成本(人日) | RICE 评分 | MVP? | +|------|--------|--------|--------|------|-------------|-----------|-------| +| U1 | emoji → 统一 SVG 图标替换(全站 + 数据层默认值) | UI | **P0** | 高 | 2–3 | 5.0 | 是 | +| U2 | 设计令牌重塑(Design Token v2) | UI | **P0** | 高 | 3–5 | 5.0 | 是 | +| P1 | 首页轻量档个性化(问候+我的计划+收藏+今日推荐) | 个性化 | **P0** | 高 | 2–3 | 5.3 | 是 | +| X1 | 全站骨架屏 + 错误重试态 | UX | **P0** | 高 | 2–3 | 4.8 | 是 | +| P2 | 完整档:checkins 数据 + 打卡接口 + 首页曲线/连续天数 | 个性化 | **P1** | 巨大 | 7–9 | 1.6 | 否(第二阶段) | +| P3 | 用户画像 onboarding(fitnessGoal/level) | 个性化基建 | **P1** | 高 | 2–3 | 2.4 | 否 | +| X2 | 全局常驻搜索 + 导航结构重构 | UX/架构 | **P1** | 中 | 3–4 | 2.0 | 否 | +| X3 | 请求超时 + 缓存 + tab 保活 | UX/速度 | **P1** | 高 | 2–3 | 2.6 | 否 | +| P4 | 周/月训练报告(雷达图) | 个性化增强 | P2 | 中 | 3–4 | 1.0 | 否 | +| X4 | 下拉刷新 / 长按 / 过渡动画等手势完善 | UX | P2 | 中 | 2–3 | 1.2 | 否 | +| U3 | 对比度 / 无障碍审计(WCAG AA) | UI | P2 | 中 | 1–2 | 1.5 | 否 | + +**本期(P0 + P1)节奏建议**: +1. 第一批(P0,约 1.5 周):U1 + U2 + P1 + X1 —— 视觉焕新 + 首页轻量个性化 + 骨架屏,用户可立刻感知。 +2. 第二批(P1,约 2 周):P2(完整档)+ P3(画像)+ X2 + X3 —— 训练闭环与数据沉淀。 +3. 第三批(P2,打磨):P4 + X4 + U3。 + +--- + +## 5. 明确不在本期范围(Out-of-Scope) + +- **社交 / 社区**:Strava 式 feed、好友、排行榜、挑战赛 —— 本期不做(连续天数可借鉴其游戏化,但无社交层)。 +- **商城 / 电商**:Gymshark 式服饰售卖 —— 不做。 +- **课程体系 / 直播课 / 视频跟练**:Keep 式 —— 不做;FITCOACH 定位为"动作推荐 + 计划"工具。 +- **饮食 / 营养 / 热量**:Keep 饮食 —— 不做。 +- **智能硬件联动**:Apple Watch / Keep 硬件 —— 不做。 +- **多语言 / i18n** —— 不做。 +- **后端从 JSON store 迁移到 SQL** —— 不做(仅新增 `checkins` store 沿用 JSON 落地)。 +- **AI 大模型生成式 coach 文案** —— 克制,不做自由发挥;仅用规则推荐(遵循 Keep"有据可依不煽情"教训)。 +- **付费 / 会员体系** —— 不做。 + +--- + +## 6. 非功能需求(必含) + +| 类别 | 要求 | 优先级 | +|------|------|--------| +| 性能 | 首屏可交互 < 2.5s(弱网下,凭骨架屏 + 缓存达成);API p95 < 500ms(沿用现有内存数据集) | P0 | +| 可用性 | 任一接口失败不白屏,降级到缓存/空态并提供重试 | P0 | +| 安全 | HTTPS + JWT(现有);新增 checkins 接口纳入 `auth.guard` 用户隔离(沿用 favorites/plans 的 openid 隔离模式) | P0 | +| 兼容性 | 微信最新基础库;iOS / Android 微信客户端 | P0 | +| 可访问性 | 对比度 WCAG 2.1 AA;图标有文字/aria 等价;移除 emoji 功能图标 | P1 | +| 图标方案 | 统一 SVG 图标库(具体由架构师按项目选型锁定,不预设具体库) | P0 | +| 数据埋点 | 见第 7 节 | P0 | + +--- + +## 7. 数据埋点方案(必含,验证产品假设) + +MVP 必须埋点的关键事件(不埋 = 上线后无法验证假设): + +| 事件类别 | 必埋事件 | 说明 | +|----------|----------|------| +| 获客 | `app_launch`, `login_success` | 启动量、登录转化 | +| 激活 | `home_view`, `home_personalized_shown`(轻量档命中)、`recommend_view` | 首页个性化模块是否真的展示给用户 | +| 留存 | `session_start`, `session_duration`, `checkin_completed`(完整档) | 次日/7日留存、打卡行为 | +| 转化 | `plan_continue_tap`(继续训练)、`favorite_item_tap`、`plan_created` | 个性化模块是否驱动行动 | +| 异常 | `api_error`, `request_timeout` | 配合 X3 超时治理 | + +**命名规范**:`{对象}_{动作}`(如 `checkin_completed`、`home_personalized_shown`);每条附带 `user_id`、`timestamp`、`device`、`version`;不采集隐私原始输入。 + +--- + +## 8. 验收标准(Given / When / Then,节选关键) + +- **Given** 用户已登录,**When** 打开首页,**Then** 顶部展示"时段 + 昵称"问候,并出现"我的计划 / 我的收藏 / 今日推荐"模块(轻量档)。 +- **Given** 用户未登录,**When** 打开首页,**Then** 展示通用首页降级版,不报错、不空白。 +- **Given** 任意页面数据未返回,**When** 渲染中,**Then** 显示骨架屏而非空白 / 纯文本"加载中…"。 +- **Given** 网络请求失败,**When** 页面进入错误态,**Then** 出现"重试"按钮且点击可恢复(区分于"无数据"空态)。 +- **Given** 设计令牌 v2 上线,**When** 全站渲染,**Then** 无任何 emoji 功能图标,主色 / 排版符合新规范,且未出现紫色→粉色渐变。 +- **Given** 完整档已上线且用户完成一次打卡,**When** 返回首页,**Then** 连续训练天数 +1,进度曲线新增一个数据点。 +- **Given** 用户首次进入且完整档上线,**When** 触发 onboarding,**Then** 可填写训练目标 / 水平,并写入用户画像。 + +--- + +## 9. 给设计 / 架构 / 开发的待办(协作提示) + +- **设计师**:输出 Design Token v2 + 统一 SVG 图标清单(替换 1.2 全部 emoji);首页个性化容器线框。 +- **架构师**:锁定图标库选型;评估 `checkins` store 与 streak 计算的服务归属;确认请求超时与缓存策略;确保新增接口沿用 openid 隔离。 +- **前端**:轻量档复用现有 4 个服务即可开工;骨架屏组件化;底部导航保活方案选型。 +- **后端**:仅在完整档阶段新增 `checkins` 模块(store/controller/service),沿用 `common/store.ts` 与 `auth.guard`。 + +> 本 PRD 为方案稿,待产品负责人确认范围与优先级后,进入逐批开发。 diff --git a/backend/src/exercises/utils/normalize.ts b/backend/src/exercises/utils/normalize.ts index 64854c3..d58e46a 100644 --- a/backend/src/exercises/utils/normalize.ts +++ b/backend/src/exercises/utils/normalize.ts @@ -11,8 +11,10 @@ import { labelOf, } from '../data/labels'; +// 媒体根地址:默认指向本服务自托管的 /media 静态目录(走自家后端,不依赖公共图床)。 +// 生产/服务器部署时用环境变量覆盖,例如 MEDIA_BASE_URL=http://192.227.237.8:3001/media const MEDIA_BASE_URL = ( - process.env.MEDIA_BASE_URL || 'https://cdn.jsdelivr.net/gh/hasaneyldrm/exercises-dataset@main' + process.env.MEDIA_BASE_URL || 'http://localhost:3000/media' ).replace(/\/$/, ''); function toMediaUrl(path: string): string { diff --git a/backend/src/plans/plans.service.ts b/backend/src/plans/plans.service.ts index e81f3e3..8049476 100644 --- a/backend/src/plans/plans.service.ts +++ b/backend/src/plans/plans.service.ts @@ -34,8 +34,9 @@ export interface PlanDetail extends PlanSummary { exercises: ExerciseSummary[]; } -const DEFAULT_EMOJI = '📋'; -const PALETTE = ['#D9B36C', '#58C9B9', '#9B72FF', '#FF6B6B', '#4D96FF', '#6BCB77']; +// 历史字段名仍叫 emoji,但值已是 Lucide 图标名(前端 iconName 兼容旧 emoji) +const DEFAULT_EMOJI = 'clipboard-list'; +const PALETTE = ['#B6F24E', '#2BD9A6', '#4FA8FF', '#F2B53D', '#FF5C5C', '#9BA7A1']; @Injectable() export class PlansService { diff --git a/backend/src/users/users.service.ts b/backend/src/users/users.service.ts index e0fafb2..407e785 100644 --- a/backend/src/users/users.service.ts +++ b/backend/src/users/users.service.ts @@ -22,7 +22,7 @@ export class UsersService { const user: UserRecord = { openid: input.openid, nickname: input.nickname?.trim() || '健身学员', - avatar: input.avatar || '🏃', + avatar: input.avatar || 'user', createdAt: now, updatedAt: now, }; diff --git a/design-system/design-tokens.json b/design-system/design-tokens.json new file mode 100644 index 0000000..fa7027c --- /dev/null +++ b/design-system/design-tokens.json @@ -0,0 +1,135 @@ +{ + "$meta": { + "product": "FITCOACH", + "version": "2026-design-language-v2-draft", + "register": "product", + "platform": "wechat-miniprogram", + "designer": "颜好看", + "note": "小程序使用 rpx(750rpx=375pt 逻辑像素)。颜色给 hex/rgba 真值,尺寸给 rpx 真值 + px 等价注释。前端把本文件编译进 app.wxss 的 :root(page 选择器)变量与 JS 常量。", + "three_axis": { "DESIGN_VARIANCE": 5, "MOTION_INTENSITY": 4, "VISUAL_DENSITY": 4 }, + "icon_set": "lucide (MIT) — 编译为 iconfont 子集,详情见 DESIGN 提案 §3" + }, + + "color": { + "bg": { "value": "#0B0F0E", "type": "color", "role": "A1 页面背景(近黑,中性偏冷,非纯黑)" }, + "bg_gradient": { "value": "radial-gradient(120% 80% at 50% 0%, #11171400 0%, #0B0F0E 60%)", "type": "gradient", "role": "A1 hero 背景微光(极弱,不喧宾夺主)" }, + "surface": { "value": "#141A18", "type": "color", "role": "A1 卡片/容器背景" }, + "surface_2": { "value": "#1C2421", "type": "color", "role": "A1 次级表面(chip/输入/选中)" }, + "surface_warm":{ "value": "#1A211E", "type": "color", "role": "B-slot 悬浮/高亮表面" }, + + "fg": { "value": "#ECF1EF", "type": "color", "role": "A1 主文本" }, + "fg_2": { "value": "#9BA7A1", "type": "color", "role": "B-slot 次级文本" }, + "fg_3": { "value": "#6B7670", "type": "color", "role": "B-slot 三级/元数据文本" }, + + "border": { "value": "rgba(255,255,255,0.08)", "type": "color", "role": "A1 默认边框/分隔线" }, + "border_strong":{ "value": "rgba(255,255,255,0.14)", "type": "color", "role": "B-slot 强调边框(选中态)" }, + "border_soft": { "value": "rgba(255,255,255,0.05)", "type": "color", "role": "B-slot 内部行分隔" }, + + "accent": { "value": "#FF6A3D", "type": "color", "role": "A1 品牌强调色 Pulse 珊瑚橙(CTA/激活导航/关键数据)" }, + "accent_hover":{ "value": "#FF8263", "type": "color", "role": "A2 悬浮" }, + "accent_active":{ "value": "#E8562B", "type": "color", "role": "A2 按下" }, + "accent_on": { "value": "#20100A", "type": "color", "role": "A2 accent 背景上的前景(深暖色,对比 >7:1)" }, + "accent_soft": { "value": "rgba(255,106,61,0.12)", "type": "color", "role": "C 强调色淡底(标签/选中底)" }, + "accent_ring": { "value": "rgba(255,106,61,0.38)", "type": "color", "role": "C 聚焦光环/进度环发光" }, + + "success": { "value": "#2BD9A6", "type": "color", "role": "A2 正向/进度完成/连续天数(语义绿,非第二品牌色)" }, + "success_on": { "value": "#04201A", "type": "color", "role": "A2 success 背景前景" }, + "success_soft":{ "value": "rgba(43,217,166,0.12)", "type": "color", "role": "C 正向淡底" }, + "warn": { "value": "#F2B53D", "type": "color", "role": "A2 警告" }, + "danger": { "value": "#FF5C5C", "type": "color", "role": "A2 错误/删除(替代旧 #E5544B)" }, + "danger_soft": { "value": "rgba(255,92,92,0.12)", "type": "color", "role": "C 错误淡底" }, + "info": { "value": "#4FA8FF", "type": "color", "role": "A2 信息" }, + + "focus_ring": { "value": "0 0 0 3px rgba(255,106,61,0.45)", "type": "shadow", "role": "A2 键盘焦点环" } + }, + + "font": { + "display": { "value": "'Manrope', 'PingFang SC', 'HarmonyOS Sans SC', 'Source Han Sans SC', 'Noto Sans CJK SC', sans-serif", "type": "fontFamily", "role": "A1 标题/数字(拉丁用 Manrope,中文回落系统黑体)" }, + "body": { "value": "'PingFang SC', 'HarmonyOS Sans SC', 'Source Han Sans SC', 'Noto Sans CJK SC', -apple-system, sans-serif", "type": "fontFamily", "role": "A1 正文(中文系统字体优先,避免打包 10MB+ CJK 字体)" }, + "mono": { "value": "'JetBrains Mono', ui-monospace, 'SFMono-Regular', monospace", "type": "fontFamily", "role": "A2 数据/统计数字(等宽,tabular)" } + }, + + "fontSize": { + "xs": { "value": "22rpx", "px": 11, "role": "eyebrow 全大写标签" }, + "sm": { "value": "24rpx", "px": 12, "role": "caption/辅助" }, + "base":{ "value": "28rpx", "px": 14, "role": "正文基准" }, + "md": { "value": "30rpx", "px": 15, "role": "正文略大" }, + "lg": { "value": "32rpx", "px": 16, "role": "三级标题/h3" }, + "xl": { "value": "36rpx", "px": 18, "role": "二级标题/h2" }, + "2xl": { "value": "44rpx", "px": 22, "role": "一级标题/h1" }, + "3xl": { "value": "56rpx", "px": 28, "role": "Hero 主标题" }, + "4xl": { "value": "72rpx", "px": 36, "role": "巨型统计数字(偶尔)" } + }, + + "fontWeight": { + "regular": { "value": 400, "role": "正文" }, + "medium": { "value": 510, "role": "按钮/小标题(注意:小程序需字体含此字重,或回落 500)" }, + "semibold": { "value": 590, "role": "大标题/强调(或 600)" } + }, + + "letterSpacing": { + "eyebrow": { "value": "0.08em", "role": "ALL CAPS 标签" }, + "title": { "value": "-0.02em", "role": "大标题负字距" }, + "body": { "value": "0", "role": "正文" } + }, + + "spacing": { + "1": { "value": "8rpx", "px": 4 }, + "2": { "value": "16rpx", "px": 8 }, + "3": { "value": "24rpx", "px": 12 }, + "4": { "value": "32rpx", "px": 16 }, + "5": { "value": "40rpx", "px": 20 }, + "6": { "value": "48rpx", "px": 24 }, + "8": { "value": "64rpx", "px": 32 }, + "10": { "value": "80rpx", "px": 40 }, + "12": { "value": "96rpx", "px": 48 }, + "16": { "value": "128rpx","px": 64, "role": "仅 Hero/大区块" } + }, + + "radius": { + "sm": { "value": "16rpx", "px": 8 }, + "md": { "value": "24rpx", "px": 12 }, + "lg": { "value": "32rpx", "px": 16 }, + "xl": { "value": "40rpx", "px": 20 }, + "pill": { "value": "9999rpx", "px": 9999 } + }, + + "shadow": { + "flat": { "value": "none", "role": "默认(用 hairline 边框代替阴影)" }, + "ring": { "value": "0 0 0 1rpx rgba(255,255,255,0.08)", "role": "卡片默认:1px 发丝边框" }, + "raised": { "value": "0 8rpx 24rpx rgba(0,0,0,0.32)", "role": "悬浮/选中抬升" }, + "float": { "value": "0 12rpx 40rpx rgba(0,0,0,0.45)", "role": "底部导航/吸顶" }, + "glow": { "value": "0 8rpx 24rpx rgba(255,106,61,0.28)", "role": "CTA 主按钮光晕(唯一允许的彩色阴影)" } + }, + + "motion": { + "fast": { "value": "150ms", "role": "即时反馈/hover" }, + "base": { "value": "200ms", "role": "状态切换收敛值" }, + "slow": { "value": "300ms", "role": "进入/布局变化" }, + "ease": { "value": "cubic-bezier(0.2, 0, 0, 1)", "role": "标准缓动(无 bounce/elastic)" } + }, + + "z": { + "base": 0, + "sticky": 100, + "nav": 60, + "dropdown":1000, + "modal": 1200, + "toast": 1300 + }, + + "icon": { + "set": "lucide", + "license": "MIT", + "sizes": { "inline": "16rpx", "button": "20rpx", "standalone": "24rpx" }, + "stroke": "2px (编译为 iconfont 后对应字重)", + "delivery": "编译 Lucide 子集为 iconfont(.ttf/.woff2),小程序用 引用;或用 base64 SVG 。禁止 emoji。" + }, + + "density": { + "register": "product", + "accent_per_screen": 2, + "touch_target_min": "44px (88rpx)", + "safe_area": "env(safe-area-inset-bottom)" + } +} diff --git a/home-personalization-mockup.svg b/home-personalization-mockup.svg new file mode 100644 index 0000000..fc9caa7 --- /dev/null +++ b/home-personalization-mockup.svg @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + + + + + 16:04 + 5G + + + + + FITCOACH · 智能健身教练 + 今天,练点什么? + 精准匹配你的目标肌群,让每次训练更高效 + + + + + + 搜索动作 / 器械 / 部位 + + + + + 智能推荐 + + + + + OVERVIEW + 本周概览 + + + + + 4 + / 5 次 + + + + 12 + 天连击 + 连续训练 + + + + + + + ↑ 8% + + + + + + 推胸日 · 第 3 周 + + 进行中 + 今日 6 个动作 · 已完成 2 / 6 + + + + + + + 上斜哑铃推胸 + 根据你的推胸计划,匹配度 92% + + + + FAVORITES + 收藏的动作 + + + + 杠铃深蹲 + 下肢 · 4 组 + + + + + 硬拉 + 全身 · 5 组 + + + + + 引体向上 + 背部 · 4 组 + + + + + + + + + + + + + + 首页 + 分类 + 计划 + 我的 + + diff --git a/miniprogram/app.wxss b/miniprogram/app.wxss index 84d34e1..690bd56 100644 --- a/miniprogram/app.wxss +++ b/miniprogram/app.wxss @@ -1,54 +1,116 @@ -/* ===== FITCOACH 设计系统:高端暗金奢华主题 ===== */ +/* ===== Lucide 子集 iconfont(动态生成,font-family: fc-icon) ===== */ +@font-face { + font-family: "fc-icon"; + src: url('assets/fonts/fc-icon.ttf') format('truetype'); + font-weight: normal; + font-style: normal; + font-display: block; +} + +/* ===== FITCOACH 设计系统:动能暗色(Kinetic Dark) ===== + 强调色 accent = Volt 荧光绿 #B6F24E(用户拍板)。 + 为兼容现有页面对 --gold/--teal 等变量名的引用,保留变量名、改其色值为新调色板; + 并补全套语义色/字号/间距/圆角/阴影/动效 Token(真源 design-system/design-tokens.json)。 */ page { - /* 设计令牌 */ - --bg: #0E1110; - --bg-grad: linear-gradient(160deg, #16201B 0%, #0E1110 55%); - --surface: #171C19; - --surface-2: #1F2622; - --gold: #D9B36C; - --gold-2: #F0D9A8; - --gold-soft: rgba(217,179,108,0.12); - --gold-line: rgba(217,179,108,0.28); - --teal: #58C9B9; - --text: #F3F5F2; - --text-2: #98A39B; - --text-3: #6B746D; - --line: rgba(255,255,255,0.07); - --radius: 28rpx; - --radius-sm: 18rpx; - --shadow: 0 12rpx 40rpx rgba(0,0,0,0.35); + /* —— 颜色 —— */ + --bg: #0B0F0E; /* 页面背景(冷中性近黑) */ + --bg-grad: radial-gradient(120% 80% at 50% 0%, rgba(17,23,20,0) 0%, #0B0F0E 60%); + --surface: #141A18; /* 卡片/容器 */ + --surface-2: #1C2421; /* 次级表面 chip/输入/选中 */ + --surface-warm: #1A211E; /* 悬浮/高亮表面 */ + + --text: #ECF1EF; /* 主文本 */ + --text-2: #9BA7A1; /* 次级文本 */ + --text-3: #6B7670; /* 三级/元数据(仅 ≥24rpx) */ + + --line: rgba(255,255,255,0.08); /* 默认边框/分隔线 */ + --border-strong: rgba(255,255,255,0.14); /* 强调边框(选中态) */ + --border-soft: rgba(255,255,255,0.05); /* 内部行分隔 */ + + /* 强调色(原 --gold 命名保留以兼容引用,值改为 Volt 绿) */ + --gold: #B6F24E; /* accent 主色 */ + --gold-2: #C7F56E; /* accent hover */ + --gold-soft: rgba(182,242,78,0.12); /* accent 淡底 */ + --gold-line: rgba(182,242,78,0.28); /* accent 边框 */ + --teal: #2BD9A6; /* success(原语义) */ + + /* 语义色(新增) */ + --accent: #B6F24E; + --accent-hover: #C7F56E; + --accent-active: #9EDB3A; + --accent-on: #0E1A05; /* accent 背景上的前景(深绿,对比>7:1) */ + --accent-soft: rgba(182,242,78,0.12); + --accent-ring: rgba(182,242,78,0.38); + --success: #2BD9A6; + --success-on: #04201A; + --success-soft: rgba(43,217,166,0.12); + --warn: #F2B53D; + --danger: #FF5C5C; /* 替代旧 #E5544B */ + --danger-soft: rgba(255,92,92,0.12); + --info: #4FA8FF; + --bar-bg: rgba(11,15,14,0.92); /* 底部操作条毛玻璃底(=bg 带透明) */ + --float-bg: rgba(11,15,14,0.55); /* 浮动按钮底 */ + --scrim: rgba(0,0,0,0.5); /* 弹层遮罩 */ + + /* 字体三族(含中文) */ + --font-display: 'Manrope', 'PingFang SC', 'HarmonyOS Sans SC', 'Source Han Sans SC', 'Noto Sans CJK SC', sans-serif; + --font-body: 'PingFang SC', 'HarmonyOS Sans SC', 'Source Han Sans SC', 'Noto Sans CJK SC', -apple-system, sans-serif; + --font-mono: 'JetBrains Mono', ui-monospace, 'SFMono-Regular', monospace; + + /* 字号阶梯(rpx,22–72) */ + --fs-xs: 22rpx; --fs-sm: 24rpx; --fs-base: 28rpx; --fs-md: 30rpx; + --fs-lg: 32rpx; --fs-xl: 36rpx; --fs-2xl: 44rpx; --fs-3xl: 56rpx; --fs-4xl: 72rpx; + + /* 字重 / 字距 */ + --fw-regular: 400; --fw-medium: 500; --fw-semibold: 600; + --ls-eyebrow: 0.08em; --ls-title: -0.02em; --ls-body: 0; + + /* 间距尺度(4px 网格) */ + --sp-1: 8rpx; --sp-2: 16rpx; --sp-3: 24rpx; --sp-4: 32rpx; --sp-5: 40rpx; + --sp-6: 48rpx; --sp-8: 64rpx; --sp-10: 80rpx; --sp-12: 96rpx; --sp-16: 128rpx; + + /* 圆角(收紧) */ + --radius-sm: 16rpx; --radius: 24rpx; --radius-md: 24rpx; + --radius-lg: 32rpx; --radius-xl: 40rpx; --radius-pill: 9999rpx; + + /* 阴影(Hairline First:默认无阴影,靠发丝边框) */ + --shadow: none; + --shadow-ring: 0 0 0 1rpx rgba(255,255,255,0.08); + --shadow-raised: 0 8rpx 24rpx rgba(0,0,0,0.32); + --shadow-float: 0 12rpx 40rpx rgba(0,0,0,0.45); + --shadow-glow: 0 8rpx 24rpx rgba(182,242,78,0.28); /* 唯一允许的彩色阴影:CTA */ + --focus-ring: 0 0 0 3rpx rgba(182,242,78,0.45); + + /* 动效 */ + --motion-fast: 150ms; --motion-base: 200ms; --motion-slow: 300ms; + --ease: cubic-bezier(0.2, 0, 0, 1); background: var(--bg); color: var(--text); - font-family: -apple-system, "PingFang SC", "Helvetica Neue", Helvetica, sans-serif; - font-size: 28rpx; + font-family: var(--font-body); + font-size: var(--fs-base); line-height: 1.5; } -view, text, scroll-view, image, input { +view, text, scroll-view, image, input, textarea, button { box-sizing: border-box; } /* ===== 通用工具类 ===== */ .eyebrow { - font-size: 22rpx; - letter-spacing: 4rpx; - color: var(--gold); + font-size: var(--fs-xs); + letter-spacing: var(--ls-eyebrow); + color: var(--accent); text-transform: uppercase; - font-weight: 600; + font-weight: var(--fw-semibold); } -.section { - padding: 40rpx 32rpx; -} -.section + .section { - border-top: 1rpx solid var(--line); -} +.section { padding: var(--sp-5) var(--sp-4); } +.section + .section { border-top: 1rpx solid var(--line); } .card { background: var(--surface); border-radius: var(--radius); - box-shadow: var(--shadow); border: 1rpx solid var(--line); } @@ -56,10 +118,10 @@ view, text, scroll-view, image, input { display: inline-flex; align-items: center; padding: 10rpx 24rpx; - border-radius: 999rpx; + border-radius: var(--radius-pill); background: var(--surface-2); color: var(--text-2); - font-size: 24rpx; + font-size: var(--fs-sm); border: 1rpx solid var(--line); } @@ -70,7 +132,7 @@ view, text, scroll-view, image, input { .empty { color: var(--text-3); - font-size: 26rpx; + font-size: var(--fs-sm); text-align: center; padding: 80rpx 0; } @@ -80,24 +142,22 @@ view, text, scroll-view, image, input { color: var(--text-2); background: var(--surface-2); padding: 4rpx 12rpx; - border-radius: 999rpx; + border-radius: var(--radius-pill); } .tag--gold { color: var(--gold-2); background: var(--gold-soft); } -/* ===== 横向滚动提示:右侧渐隐遮罩,提示「还可向右滑动」 ===== */ +/* ===== 横向滚动右侧渐隐遮罩(提示可右滑) ===== */ .hscroll, .chip-scroll, .filterbar, .emoji-row, .prog-scroll { position: relative; } .hscroll::after, .chip-scroll::after, .filterbar::after, .emoji-row::after, .prog-scroll::after { content: ''; position: absolute; - top: 0; - right: 0; - bottom: 0; + top: 0; right: 0; bottom: 0; width: 56rpx; pointer-events: none; - background: linear-gradient(90deg, rgba(14,17,16,0) 0%, rgba(14,17,16,0.9) 100%); + background: linear-gradient(90deg, rgba(11,15,14,0) 0%, rgba(11,15,14,0.9) 100%); } diff --git a/miniprogram/assets/fonts/fc-icon.json b/miniprogram/assets/fonts/fc-icon.json new file mode 100644 index 0000000..ce11b20 --- /dev/null +++ b/miniprogram/assets/fonts/fc-icon.json @@ -0,0 +1,51 @@ +{ + "zap": 61697, + "x-circle": 61698, + "user": 61699, + "trophy": 61700, + "trending-up": 61701, + "trash-2": 61702, + "target": 61703, + "star": 61704, + "sparkles": 61705, + "sliders-horizontal": 61706, + "share-2": 61707, + "settings": 61708, + "search": 61709, + "rotate-cw": 61710, + "refresh-cw": 61711, + "plus": 61712, + "play": 61713, + "person-standing": 61714, + "medal": 61715, + "log-out": 61716, + "layout-grid": 61717, + "layers": 61718, + "info": 61719, + "image": 61720, + "home": 61721, + "heart": 61722, + "grid-2x2": 61723, + "folder-open": 61724, + "flame": 61725, + "filter": 61726, + "dumbbell": 61727, + "download": 61728, + "clock": 61729, + "clipboard-list": 61730, + "circle-x": 61731, + "chevron-right": 61732, + "chevron-down": 61733, + "check": 61734, + "check-circle": 61735, + "calendar": 61736, + "bookmark": 61737, + "bike": 61738, + "bell": 61739, + "award": 61740, + "arrow-right": 61741, + "arrow-left": 61742, + "alert-circle": 61743, + "activity": 61744, + "accessibility": 61745 +} \ No newline at end of file diff --git a/miniprogram/assets/fonts/fc-icon.ttf b/miniprogram/assets/fonts/fc-icon.ttf new file mode 100644 index 0000000000000000000000000000000000000000..cce187ab1ed01dbfe1017c643d2ef4462aff2da4 GIT binary patch literal 7100 zcmd^ETWlNIc|K=uXE-E>l*l1P-AziQd=X_0O>ynrC3hQNS~E@8$?i(&ZktjhC2>uO zG)3EM?+P#ypon*ab{_&IeJBvbMIMS`wJ4fCG;Wa;K_CQ0AA;^n(TAw$OM%wtplJ}e zMbqz}8CtZr*!Q9%{b&C3pUeE`zt50y##oTuV=_B?GlcxJ`SMj~G*;o9#OrfAH6T_)m<9ImV)IZZ&I-wR_*HVD3)= zD_bB)7x`}ke+Zb{+U~#o+)Kb1{}(hrY<5e`@8Ow7x3k;-&X4-9GL`~AQl5c#j@3`vBvGEamR$;{cjro9`BLPC|6oCH z#k=z#-J_XtyW|vsN?Ge?$A7j6C``)<0&ayI-E?)lZtITZI`a5i# zDMRDKIB&N$YuMjg$uW;cIVhZIK2_ma{sB|b`z!Vnw5R<02h3!@juSIg2K9frUfHL8zlfHzx3U<_(fF=T17i1w8hP)#z%0}2Ii?KMfSb`YIRWY3fkX+&YaHPLw8tFy76JK~1Ft0@?{#1c1Y{FV&;w|hIpB%_ z`eshp18AN(rU$SA=0rSzH83aY0qlY~BObs)m^11DY=t>74`4OSiF*M1VUFbiEQvXY zGX$_H<|I9UbukAqj{tVYoRkN!IOdFd0NZ2Ega@!f=1h73dt}a(2e3@$OnU$uWzLKT zuvX^GdH}m+&Iu1-!OTf}09$5G#sgOubHD+xZ|2N<083|1)&tl)b8;TQ`kAxf0sMeD zCp~~iFz1v9@D1ji_5fbOoHHK4UzoG#0r|9(_W(Y`oFxz7P0WEe5x}qD%N~%2;mWhm zu`Y7V-?Cruv%JR-`6KC+^mXY^rJqRuB^&Y?`E&9I@}DS@a#?98-&B66{F^$eUQ~C~ zZ>m4jqFP>iOS`3gS^GWhL+$UhpNqWM68qvi;?Kq3i^sZ~XW+>J9w)h%(%A}Mv8TBu zw8Zn=PDHqt&huhvC7ZEzp}xyig^$U)SRL-{nc6)`QlzRDJjRH3vUbMf*gFJ^i(>!y zG`GwAT%M;*UJ}J*Dt-Rq`Lx?Ss8;vWKAt|WRI63e6s|{Hz}8`PrQ*4)$YixCUWzB| z73`u}kEX4w)^v92)D-@+DeTXury?qek@t_hP{ zxF?d+JXtDW!|Z7;3OSF*7dds)cvwcuo?CgI?+G;&UZsYIV&T!zaO~jlAf8#s#D%)2 z36FHs(t=80Jo2W0e=s^edi20bTUI*$Syc$N%46ejCO0AwU+iO?5y>MmLs5Zo0p2-e83zCGnG$#=?!jF4MV={Sio~6rnBseOB@%o$Xr_)HL=4-A zK%&+A2Sy}f9Hgkl;-__zBENV_C&^(2C0!|J)7p@hZ1==&V@Ki<{0}KD9nZx5@i-oc zVX*I>A&!&6cVa8{44>u%5pLoh*OquT4J*r%jR9Y&@G>ubAnDSSY>1Z>Szb~U9@LZz zs;OR3v>;cMC0SNp5{AqpigH2IVZ6GOSHjxMiYm>>@{FV^FKb~XFS$byE}|?LAv?@L z`={ACa8icI2E4>I;)Wm%9`c;voXbfC7LpMIIlRX;L*rp7C@w3Srd$?5Df~&&Udlf7 zM8gB0GVOTfD|YH|ATF;agU*Gka}`z_Ktw0qBOaGB$xO*gf+yTF!YXF=R=eH%smhHT zmC9PR{OI7&-m|-_2Nj!?J+Pmc>pzo-j)-xd??9{8lXF?Frs0%YZieTINs)VK%_b7F z*1mg7d;>Rb&05#OuZ2Rdg&#!|iJ3$qx*D};9Cx>04B4Ug!Zy;3>$hBo&Yx!&@kEk! za#}8}OmlKN*W*l{%bF5;>1-}93oXxAFz*~XFo0yDEIr}w4j)O3a4y28FszUo(1g$e zYREE#85aE028GA|FG7(>NQ4BJRS`=FC2WQjk%$RZ=AeGs;2Yiv$$Y#sd3u3a&o6Pf zN?GzknZ-rvkH_<+{KNz`*_A7k`3de5CWh9Uq{aC-PK{$q1v0^+mEmHhP%gk43o~P< zN2V(KGll7CqKuAARw@JgrDyA~g*au@(mA*Zc@mZ2gFPxU?q-waGVKO2 z(N;E4Amzb%*dzQ=Y(f0VS(Sse{j~#(c|J?eBb&>Xx$7yOmmnKdAhxi!PkPwb!XfcU zBiXJhK{KeVx-!KcuFtU}_Mfo5kZ-*MKe9wAnM;<0^`0bJH`>{I$za{IBp~A>eHZJ8TOi<99;fZ(z?uxw%VmNeJ$ztX=&nsMwi|~F}#8vtH zuU@VH+E*Lh=u~=YWCTsLtle1GBHDyuOyIUY|IRz_ys$SygD0pN2@SC@1 zTry)p-pCN(;jmJv?At-pGzx3wN~LQW9~qeC#~fN0f!{5&ml1QI?`ym!XK?XnFs7WZ?XDh8ezvdApt2*GK$k# z3Wjm0JwfppUI}mXJH;XNTZVC6!FAm*+Ond^|E|bw7kxZH_=ut`b1*F}50I;h#+Oxf znQKZ}@#vD~n#?fsNpdeR9WY?@@j&e@r zz%b3=hori3(?a~W%!8b;M30MzM}{V^VwlE)SF=LgfL;f2AP&2xiU~D|S0?1#(g{ME zg6K%a#fQ3TtD?#SYhqNa1-SdLY=g>x#{H0}YFbr-k)q{WsOf9J5XU_^`uhRHQ8qH0u{xaVh7>%r|}s6b7L zD$f-+T@}wEE#$JrbNk^cPcEEY7~`SQ@t7AQVVNS~QIze`ACLanCD70-!iqrCz`ppx*Whk}a9B+U)54laTu-PToSb7rajR^dpRbjx&RZTr;3Q_U4yF?(zM~ z(S0OI@9ar#q5i_#%Y4F=@^YE9nk?}V)toP0u+v5~S3E!7nBO~?DxFD66K9_*olYo` zFJ2hnKkh{tfqp>sXqk^BQ*9rF^&pmxp`%e?Th zVTQExsFKfXALh%M#njgj{5iap%ILJ`%HEoFVF+|3Dkiqy=lWs z?3Gf^L`;Lq%M{P(ujtoMh;u@_=T;G4k>QNWAvvQQR0E3YzE!A7K+TfUuaHl26;2rv z;Ye)UeeH;k$0A|M(scbJP8p`J3Z-O18;A*(FkB=2AjXAqM$zR(1uV`Uzu3HwoF=6u z{3Elyc>GS|@9ou0kR@s+`1iru8p@`%)xmk)nu=-#)G4K8W`A{c?Vz%{wubTH{1$cJ zXovYJC9Ry5TpsqQa5y~VQ&AMyHhtCc;#v%HrD`&hMt;!JMK`IDNAuW>Zc0;Jy~I^M zC4~bs`-a|=NHuo>+o~Y6_fV&uIx|R$dYhy)rEut~TZ` zO~0-hlXD{{-1f1ma~f2?w;C#EvuEZEjA; z%g0qjsYxHFFPJ$WH}L-Ef3?7j82ci2;7S0aCTsh+1pKa#%fP?t<5a)x`?!iz|B;Vt zp#O!B8|-y<=;J|_<G^tRn{BW=*8!#Lj=cp+9kcUn%U#uF7x3+B2`lOO$Mf^AR{cY_Cch=<)b^C%zTqx2PrTRef^Gsbv|kMjvW$?=s3 zZ$Y#C1W)q}pX2lRogv2;_(^^Wu5gAgy6^L6`7%e1c(>No-(IY@di8cwxw+fyY5iWO zyLBhn?=^QCt)0!qn_bcG)poZQZLMGHZ8rPLF7Ennx7NGfZtjlmwp$G-eQ~SPYu$zB zYHhK*RqHhuZGE@d?_-NyZMRwL)wcq@P9KJ_Sif!bnj5_)_N?Dly6v00Xli#NU2N0Y zS;Y44lhy5J1Dmxwn~R;B{UB!Gwz%1AH8kLutF(4DI%;dXw%Js+I@?VZzd-f+I?Y(L z-?oDroi;dI>~xzu>P8zsFKHXCcE8!v8#lMt*I{{jqjP(w-KjOyI)0tHZr0na?s})z zYb>@~yZvx|t9h#jhI*~dt$t|Gqctiz_3K2cU-yNl*K2J^43b&zbgpkhILdnKdQ+k2 ysI}YJG+68PI=8*u3=eNNH~Jy0>-7h_)aw1#tycd|xK^(>cXwOstu}gO82 - {{avatar || '💪'}} + style="width: {{size}}rpx; height: {{size}}rpx; background: {{bg}};"> + diff --git a/miniprogram/components/bottom-nav/index.js b/miniprogram/components/bottom-nav/index.js index 09427f6..b1f7d3b 100644 --- a/miniprogram/components/bottom-nav/index.js +++ b/miniprogram/components/bottom-nav/index.js @@ -4,10 +4,10 @@ Component({ }, data: { items: [ - { key: 'home', label: '首页', emoji: '🏠', page: '/pages/index/index' }, - { key: 'category', label: '分类', emoji: '📚', page: '/pages/category/category' }, - { key: 'plans', label: '计划', emoji: '📋', page: '/pages/my-plans/my-plans' }, - { key: 'mine', label: '我的', emoji: '👤', page: '/pages/profile/profile' } + { key: 'home', label: '首页', icon: 'home', page: '/pages/index/index' }, + { key: 'category', label: '分类', icon: 'layout-grid', page: '/pages/category/category' }, + { key: 'plans', label: '计划', icon: 'clipboard-list', page: '/pages/my-plans/my-plans' }, + { key: 'mine', label: '我的', icon: 'user', page: '/pages/profile/profile' } ] }, methods: { diff --git a/miniprogram/components/bottom-nav/index.json b/miniprogram/components/bottom-nav/index.json index a89ef4d..a18bb03 100644 --- a/miniprogram/components/bottom-nav/index.json +++ b/miniprogram/components/bottom-nav/index.json @@ -1,4 +1,6 @@ { "component": true, - "usingComponents": {} + "usingComponents": { + "fc-icon": "/components/fc-icon/index" + } } diff --git a/miniprogram/components/bottom-nav/index.wxml b/miniprogram/components/bottom-nav/index.wxml index f1d998f..38d12cc 100644 --- a/miniprogram/components/bottom-nav/index.wxml +++ b/miniprogram/components/bottom-nav/index.wxml @@ -5,7 +5,7 @@ class="bn__item {{active === item.key ? 'bn__item--active' : ''}}" data-page="{{item.page}}" bindtap="onTap"> - {{item.emoji}} + {{item.label}} diff --git a/miniprogram/components/bottom-nav/index.wxss b/miniprogram/components/bottom-nav/index.wxss index 4e3ba0e..352eae6 100644 --- a/miniprogram/components/bottom-nav/index.wxss +++ b/miniprogram/components/bottom-nav/index.wxss @@ -27,6 +27,6 @@ background: linear-gradient(90deg, var(--gold), var(--gold-2)); } .bn__item--active { color: var(--gold); } -.bn__emoji { font-size: 40rpx; line-height: 1; } +.bn__emoji { line-height: 1; } .bn__label { font-size: 22rpx; margin-top: 6rpx; } .bn__item--active .bn__label { color: var(--gold-2); } diff --git a/miniprogram/components/exercise-card/index.json b/miniprogram/components/exercise-card/index.json index a89ef4d..a18bb03 100644 --- a/miniprogram/components/exercise-card/index.json +++ b/miniprogram/components/exercise-card/index.json @@ -1,4 +1,6 @@ { "component": true, - "usingComponents": {} + "usingComponents": { + "fc-icon": "/components/fc-icon/index" + } } diff --git a/miniprogram/components/exercise-card/index.wxml b/miniprogram/components/exercise-card/index.wxml index dc6fd61..701f14a 100644 --- a/miniprogram/components/exercise-card/index.wxml +++ b/miniprogram/components/exercise-card/index.wxml @@ -7,7 +7,7 @@ mode="aspectFill" lazy-load="true" binderror="onImgError" /> - 🏋️ + {{exercise.typeLabel}} 匹配 {{score}} diff --git a/miniprogram/components/exercise-card/index.wxss b/miniprogram/components/exercise-card/index.wxss index b7b518e..aa2b3b9 100644 --- a/miniprogram/components/exercise-card/index.wxss +++ b/miniprogram/components/exercise-card/index.wxss @@ -21,11 +21,11 @@ .ex-card__type { position: absolute; left: 12rpx; top: 12rpx; font-size: 20rpx; color: var(--text-2); - background: rgba(0,0,0,0.45); padding: 4rpx 14rpx; border-radius: 999rpx; + background: var(--scrim); padding: 4rpx 14rpx; border-radius: 999rpx; } .ex-card__score { position: absolute; right: 12rpx; top: 12rpx; - font-size: 20rpx; color: #0E1110; font-weight: 700; + font-size: 20rpx; color: var(--accent-on); font-weight: 700; background: var(--gold); padding: 4rpx 14rpx; border-radius: 999rpx; } .ex-card__body { padding: 16rpx 18rpx 20rpx; } diff --git a/miniprogram/components/fc-icon/index.js b/miniprogram/components/fc-icon/index.js new file mode 100644 index 0000000..fc7c95c --- /dev/null +++ b/miniprogram/components/fc-icon/index.js @@ -0,0 +1,23 @@ +const iconMap = require('../../utils/icon-map'); + +Component({ + properties: { + // Lucide 图标名,如 search / dumbbell / heart + name: { type: String, value: '' }, + // 字号(rpx) + size: { type: Number, value: 20 }, + // 颜色,默认跟随当前文字色(currentColor 在 iconfont 下用具体色) + color: { type: String, value: '' }, + }, + data: { glyph: '' }, + observers: { + name(n) { + this.setData({ glyph: (iconMap[n] || '') }); + }, + }, + lifetimes: { + attached() { + this.setData({ glyph: (iconMap[this.data.name] || '') }); + }, + }, +}); diff --git a/miniprogram/components/fc-icon/index.json b/miniprogram/components/fc-icon/index.json new file mode 100644 index 0000000..a89ef4d --- /dev/null +++ b/miniprogram/components/fc-icon/index.json @@ -0,0 +1,4 @@ +{ + "component": true, + "usingComponents": {} +} diff --git a/miniprogram/components/fc-icon/index.wxml b/miniprogram/components/fc-icon/index.wxml new file mode 100644 index 0000000..935f35b --- /dev/null +++ b/miniprogram/components/fc-icon/index.wxml @@ -0,0 +1 @@ +{{glyph}} diff --git a/miniprogram/components/fc-icon/index.wxss b/miniprogram/components/fc-icon/index.wxss new file mode 100644 index 0000000..7e371a0 --- /dev/null +++ b/miniprogram/components/fc-icon/index.wxss @@ -0,0 +1,10 @@ +.fc-icon { + font-family: "fc-icon"; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + -webkit-font-smoothing: antialiased; + display: inline-block; +} diff --git a/miniprogram/components/gif-player/gif-player.wxss b/miniprogram/components/gif-player/gif-player.wxss index df41f77..7b5c15d 100644 --- a/miniprogram/components/gif-player/gif-player.wxss +++ b/miniprogram/components/gif-player/gif-player.wxss @@ -9,7 +9,7 @@ } .gif-loading__dot { width: 18rpx; height: 18rpx; border-radius: 999rpx; - background: var(--gold, #c9a24b); + background: var(--accent); animation: gif-pulse 1s ease-in-out infinite; } .gif-loading__txt { diff --git a/miniprogram/components/navbar/index.wxss b/miniprogram/components/navbar/index.wxss index 5b7bbcc..52f246c 100644 --- a/miniprogram/components/navbar/index.wxss +++ b/miniprogram/components/navbar/index.wxss @@ -1,5 +1,5 @@ .navbar { - background: linear-gradient(180deg, rgba(22,32,27,0.96) 0%, rgba(14,17,16,0.55) 100%); + background: linear-gradient(180deg, var(--bar-bg) 0%, var(--float-bg) 100%); position: sticky; top: 0; z-index: 50; diff --git a/miniprogram/pages/collection-detail/collection-detail.js b/miniprogram/pages/collection-detail/collection-detail.js index 3f7f035..6f6e7ef 100644 --- a/miniprogram/pages/collection-detail/collection-detail.js +++ b/miniprogram/pages/collection-detail/collection-detail.js @@ -2,6 +2,7 @@ const app = getApp(); const planSvc = require('../../services/plan'); const favSvc = require('../../services/favorite'); const auth = require('../../utils/auth'); +const { iconName } = require('../../utils/icon-name'); Page({ data: { @@ -29,7 +30,7 @@ Page({ this.setData({ loading: true }); planSvc.officialExercises(this.data.slug, 1, 60).then((res) => { this.setData({ - collection: res.collection, + collection: { ...res.collection, icon: iconName(res.collection.emoji) }, exercises: (res && res.items) || [], total: (res && res.total) || 0, loading: false diff --git a/miniprogram/pages/collection-detail/collection-detail.json b/miniprogram/pages/collection-detail/collection-detail.json index ace9842..38c4cbb 100644 --- a/miniprogram/pages/collection-detail/collection-detail.json +++ b/miniprogram/pages/collection-detail/collection-detail.json @@ -4,6 +4,7 @@ "navbar": "/components/navbar/index", "exercise-card": "/components/exercise-card/index", "section-header": "/components/section-header/index", - "level-tag": "/components/level-tag/index" + "level-tag": "/components/level-tag/index", + "fc-icon": "/components/fc-icon/index" } } diff --git a/miniprogram/pages/collection-detail/collection-detail.wxml b/miniprogram/pages/collection-detail/collection-detail.wxml index 9b1c8dd..fc81390 100644 --- a/miniprogram/pages/collection-detail/collection-detail.wxml +++ b/miniprogram/pages/collection-detail/collection-detail.wxml @@ -2,7 +2,7 @@ - {{collection.emoji}} + {{collection.name}} {{collection.nameEn}} {{collection.description}} @@ -31,7 +31,8 @@ - {{favorited ? '♥ 已收藏' : '♡ 收藏'}} + + {{favorited ? '已收藏' : '收藏'}} + 导入我的计划 diff --git a/miniprogram/pages/collection-detail/collection-detail.wxss b/miniprogram/pages/collection-detail/collection-detail.wxss index 6e2b535..33f9917 100644 --- a/miniprogram/pages/collection-detail/collection-detail.wxss +++ b/miniprogram/pages/collection-detail/collection-detail.wxss @@ -19,7 +19,7 @@ .actionbar { position: fixed; left: 0; right: 0; bottom: 0; display: flex; gap: 20rpx; padding: 20rpx 32rpx; - background: rgba(14,17,16,0.92); + background: var(--bar-bg); backdrop-filter: blur(12px); border-top: 1rpx solid var(--line); padding-bottom: calc(20rpx + env(safe-area-inset-bottom)); @@ -28,6 +28,6 @@ flex: 1; display: flex; align-items: center; justify-content: center; border-radius: 999rpx; padding: 26rpx; font-size: 30rpx; font-weight: 700; } -.btn--gold { background: linear-gradient(135deg, var(--gold) 0%, var(--gold-2) 100%); color: #0E1110; } +.btn--gold { background: linear-gradient(135deg, var(--gold) 0%, var(--gold-2) 100%); color: var(--accent-on); } .btn--ghost { background: var(--surface-2); color: var(--text); border: 1rpx solid var(--gold-line); } .btn--faved { color: var(--gold-2); border-color: var(--gold); } diff --git a/miniprogram/pages/detail/detail.json b/miniprogram/pages/detail/detail.json index 64953bb..93e7fe2 100644 --- a/miniprogram/pages/detail/detail.json +++ b/miniprogram/pages/detail/detail.json @@ -3,6 +3,7 @@ "usingComponents": { "navbar": "/components/navbar/index", "exercise-card": "/components/exercise-card/index", - "gif-player": "/components/gif-player/gif-player" + "gif-player": "/components/gif-player/gif-player", + "fc-icon": "/components/fc-icon/index" } } diff --git a/miniprogram/pages/detail/detail.wxml b/miniprogram/pages/detail/detail.wxml index 8c0ad56..5028c74 100644 --- a/miniprogram/pages/detail/detail.wxml +++ b/miniprogram/pages/detail/detail.wxml @@ -2,7 +2,7 @@ - {{favorited ? '♥' : '♡'}} + @@ -13,7 +13,7 @@ mode="aspectFill" lazy-load="true" binderror="onHeroError" /> - 🏋️ + diff --git a/miniprogram/pages/detail/detail.wxss b/miniprogram/pages/detail/detail.wxss index e853360..3af5ca4 100644 --- a/miniprogram/pages/detail/detail.wxss +++ b/miniprogram/pages/detail/detail.wxss @@ -40,7 +40,7 @@ .addbar { position: fixed; left: 0; right: 0; bottom: 0; padding: 20rpx 32rpx; - background: rgba(14,17,16,0.92); + background: var(--bar-bg); backdrop-filter: blur(12px); border-top: 1rpx solid var(--line); padding-bottom: calc(20rpx + env(safe-area-inset-bottom)); @@ -48,7 +48,7 @@ .cta { display: flex; align-items: center; justify-content: center; background: linear-gradient(135deg, var(--gold) 0%, var(--gold-2) 100%); - color: #0E1110; font-weight: 700; font-size: 32rpx; + color: var(--accent-on); font-weight: 700; font-size: 32rpx; border-radius: 999rpx; padding: 26rpx; } @@ -60,7 +60,7 @@ .favbtn { position: fixed; right: 28rpx; z-index: 60; width: 72rpx; height: 72rpx; border-radius: 999rpx; - background: rgba(14,17,16,0.55); + background: var(--float-bg); backdrop-filter: blur(8px); border: 1rpx solid var(--gold-line); display: flex; align-items: center; justify-content: center; diff --git a/miniprogram/pages/favorites/favorites.json b/miniprogram/pages/favorites/favorites.json index 50f04f2..26c4722 100644 --- a/miniprogram/pages/favorites/favorites.json +++ b/miniprogram/pages/favorites/favorites.json @@ -2,6 +2,7 @@ "navigationStyle": "custom", "usingComponents": { "navbar": "/components/navbar/index", - "exercise-card": "/components/exercise-card/index" + "exercise-card": "/components/exercise-card/index", + "fc-icon": "/components/fc-icon/index" } } diff --git a/miniprogram/pages/favorites/favorites.wxml b/miniprogram/pages/favorites/favorites.wxml index 1d44673..5e74fe5 100644 --- a/miniprogram/pages/favorites/favorites.wxml +++ b/miniprogram/pages/favorites/favorites.wxml @@ -17,7 +17,7 @@ - 🔍 + 还没有收藏,去发现喜欢的动作吧 去推荐 diff --git a/miniprogram/pages/favorites/favorites.wxss b/miniprogram/pages/favorites/favorites.wxss index 90ce3fb..be9b2d7 100644 --- a/miniprogram/pages/favorites/favorites.wxss +++ b/miniprogram/pages/favorites/favorites.wxss @@ -14,7 +14,7 @@ .item { position: relative; } .remove { position: absolute; right: 12rpx; top: 12rpx; z-index: 5; - background: rgba(229,84,75,0.92); color: #fff; + background: var(--danger); color: var(--accent-on); font-size: 22rpx; padding: 8rpx 18rpx; border-radius: 999rpx; } .empty { color: var(--text-3); font-size: 26rpx; text-align: center; padding: 120rpx 0; } @@ -29,5 +29,5 @@ .empty-box__btn { margin-top: 40rpx; padding: 22rpx 64rpx; border-radius: 999rpx; background: linear-gradient(135deg, var(--gold) 0%, var(--gold-2) 100%); - color: #0E1110; font-weight: 700; font-size: 30rpx; + color: var(--accent-on); font-weight: 700; font-size: 30rpx; } diff --git a/miniprogram/pages/index/index.js b/miniprogram/pages/index/index.js index 0a226d9..4244c7c 100644 --- a/miniprogram/pages/index/index.js +++ b/miniprogram/pages/index/index.js @@ -1,5 +1,22 @@ const app = getApp(); const svc = require('../../services/exercise'); +const planSvc = require('../../services/plan'); +const favSvc = require('../../services/favorite'); +const { iconName } = require('../../utils/icon-name'); + +function greeting() { + const h = new Date().getHours(); + if (h < 6) return '夜深了,'; + if (h < 11) return '早上好,'; + if (h < 13) return '中午好,'; + if (h < 18) return '下午好,'; + if (h < 22) return '晚上好,'; + return '夜深了,'; +} + +function isLogin() { + return !!wx.getStorageSync('access_token'); +} Page({ data: { @@ -11,13 +28,19 @@ Page({ collections: [], loading: true, quickEntries: [ - { key: 'recommend', label: '智能推荐', emoji: '✨', url: '/pages/recommend/recommend' }, - { key: 'bodyPart', label: '按部位', emoji: '💪', url: '/pages/category/category?dimension=bodyPart' }, - { key: 'equipment', label: '按器械', emoji: '🏋️', url: '/pages/category/category?dimension=equipment' }, - { key: 'official', label: '官方计划', emoji: '📋', url: '/pages/collection/collection' }, - { key: 'myPlans', label: '我的计划', emoji: '🗂️', url: '/pages/my-plans/my-plans' }, - { key: 'mine', label: '我的', emoji: '👤', url: '/pages/profile/profile' } - ] + { key: 'recommend', label: '智能推荐', icon: 'sparkles', url: '/pages/recommend/recommend' }, + { key: 'bodyPart', label: '按部位', icon: 'accessibility', url: '/pages/category/category?dimension=bodyPart' }, + { key: 'equipment', label: '按器械', icon: 'dumbbell', url: '/pages/category/category?dimension=equipment' }, + { key: 'official', label: '官方计划', icon: 'clipboard-list', url: '/pages/collection/collection' }, + { key: 'myPlans', label: '我的计划', icon: 'folder-open', url: '/pages/my-plans/my-plans' }, + { key: 'mine', label: '我的', icon: 'user', url: '/pages/profile/profile' } + ], + // —— 轻量个性化(登录后)—— + isLogin: false, + greeting: '', + myPlans: [], + myFavorites: [], + todayRecommend: [] }, onLoad() { @@ -25,6 +48,11 @@ Page({ this.loadData(); }, + onShow() { + // tab 切回:已登录则静默刷新个性化(优先缓存) + if (isLogin()) this.loadPersonal(true); + }, + loadData() { this.setData({ loading: true }); Promise.all([ @@ -32,19 +60,70 @@ Page({ svc.listExercises({ pageSize: 8 }).catch(() => ({ items: [] })), svc.listCollections().catch(() => []) ]).then(([stats, list, collections]) => { + const cols = (collections || []).map((c) => ({ + ...c, + icon: iconName(c.emoji) + })); this.setData({ stats, bodyParts: (stats && stats.bodyParts) || [], equipment: (stats && stats.equipment) || [], featured: (list && list.items) || [], - collections: collections || [], + collections: cols, loading: false }); + if (isLogin()) this.loadPersonal(false); }).catch(() => { this.setData({ loading: false }); }); }, + // 轻量个性化:复用现有 API,纯前端聚合;silent=切 tab 静默刷新 + loadPersonal(silent) { + if (!isLogin()) return; + this.setData({ isLogin: true, greeting: greeting() }); + if (!silent) this.setData({ personalLoading: true }); + + const CACHE_KEY = 'fc_personal_cache'; + const cached = wx.getStorageSync(CACHE_KEY); + const now = Date.now(); + if (cached && now - (cached.ts || 0) < 5 * 60 * 1000 && !silent) { + this.applyPersonal(cached.data); + return; + } + + Promise.all([ + planSvc.listMine().catch(() => []), + favSvc.listExercises().catch(() => []) + ]).then(([plans, favs]) => { + const planArr = Array.isArray(plans) ? plans : (plans && plans.items) || []; + const favArr = Array.isArray(favs) ? favs : (favs && favs.items) || []; + const data = { + myPlans: planArr.slice(0, 3).map((p) => ({ + id: p.id, name: p.name, count: p.exerciseCount || 0 + })), + myFavorites: favArr.slice(0, 6), + todayRecommend: (this.data.featured || []).slice(0, 3).map((e) => ({ + id: e.id, name: e.displayName || e.name, + reason: '为你精选 · 高效入门' + })) + }; + wx.setStorageSync(CACHE_KEY, { ts: now, data }); + this.applyPersonal(data); + }).catch(() => { + this.setData({ personalLoading: false }); + }); + }, + + applyPersonal(data) { + this.setData({ + myPlans: data.myPlans, + myFavorites: data.myFavorites, + todayRecommend: data.todayRecommend, + personalLoading: false + }); + }, + goRecommend() { wx.navigateTo({ url: '/pages/recommend/recommend' }); }, @@ -57,6 +136,17 @@ Page({ wx.navigateTo({ url: '/pages/search/search' }); }, + onPlanTap(e) { + const id = e.currentTarget.dataset.id; + if (id) wx.navigateTo({ url: '/pages/plan-detail/plan-detail?id=' + encodeURIComponent(id) }); + }, + + onExercise(e) { + const id = (e.detail && e.detail.id) || (e.currentTarget && e.currentTarget.dataset.id); + if (!id) return; + wx.navigateTo({ url: '/pages/detail/detail?id=' + encodeURIComponent(id) }); + }, + onScanBodyPart(e) { const { value, label } = e.currentTarget.dataset; wx.navigateTo({ @@ -78,12 +168,5 @@ Page({ url: '/pages/collection-detail/collection-detail?slug=' + encodeURIComponent(e.currentTarget.dataset.slug) }); - }, - - onExercise(e) { - if (!e || !e.detail || !e.detail.id) return; - wx.navigateTo({ - url: '/pages/detail/detail?id=' + encodeURIComponent(e.detail.id) - }); } }); diff --git a/miniprogram/pages/index/index.json b/miniprogram/pages/index/index.json index db2e076..c20380b 100644 --- a/miniprogram/pages/index/index.json +++ b/miniprogram/pages/index/index.json @@ -3,6 +3,7 @@ "usingComponents": { "exercise-card": "/components/exercise-card/index", "section-header": "/components/section-header/index", - "bottom-nav": "/components/bottom-nav/index" + "bottom-nav": "/components/bottom-nav/index", + "fc-icon": "/components/fc-icon/index" } } diff --git a/miniprogram/pages/index/index.wxml b/miniprogram/pages/index/index.wxml index b4669a1..d8837af 100644 --- a/miniprogram/pages/index/index.wxml +++ b/miniprogram/pages/index/index.wxml @@ -6,17 +6,64 @@ 精准匹配你的目标肌群,让每一次训练都更高效 - 🔍 + 搜索动作 / 器械 / 部位 - + 智能推荐 + + + + + FOR YOU + {{greeting}}准备好训练了吗? + + + + + + 我的计划 + + + + + {{item.name}} + {{item.count}} 个动作 + + + + + + + 今日推荐 + + + + {{item.name}} + {{item.reason}} + + + + + + + + 我的收藏 + + + + {{item.displayName || item.name}} + + + + + @@ -26,7 +73,7 @@ class="quick__tile" data-url="{{item.url}}" bindtap="onQuick"> - {{item.emoji}} + {{item.label}} @@ -54,7 +101,7 @@ style="background: {{item.color}}22; border-color: {{item.color}}55;" data-slug="{{item.slug}}" bindtap="onCollection"> - {{item.emoji}} + {{item.name}} diff --git a/miniprogram/pages/index/index.wxss b/miniprogram/pages/index/index.wxss index 49a1304..6404334 100644 --- a/miniprogram/pages/index/index.wxss +++ b/miniprogram/pages/index/index.wxss @@ -2,59 +2,92 @@ .hero { padding: 20rpx 40rpx 48rpx; - background: linear-gradient(150deg, #1C2A22 0%, #0E1110 70%); + background: linear-gradient(160deg, var(--surface) 0%, var(--bg) 70%); border-bottom-left-radius: 48rpx; border-bottom-right-radius: 48rpx; } -.hero__eyebrow { color: var(--gold); font-size: 22rpx; letter-spacing: 4rpx; } -.hero__title { font-size: 56rpx; font-weight: 700; letter-spacing: 2rpx; margin-top: 14rpx; color: var(--text); } -.hero__sub { font-size: 26rpx; color: var(--text-2); margin-top: 14rpx; } +.hero__eyebrow { color: var(--accent); font-size: var(--fs-xs); letter-spacing: var(--ls-eyebrow); font-weight: var(--fw-semibold); } +.hero__title { font-size: var(--fs-3xl); font-weight: 700; letter-spacing: var(--ls-title); margin-top: 14rpx; color: var(--text); } +.hero__sub { font-size: var(--fs-sm); color: var(--text-2); margin-top: 14rpx; } .search-pill { margin-top: 36rpx; display: flex; align-items: center; background: var(--surface); border: 1rpx solid var(--line); - border-radius: 999rpx; padding: 22rpx 30rpx; + border-radius: var(--radius-pill); padding: 22rpx 30rpx; } -.search-pill__icon { font-size: 30rpx; margin-right: 16rpx; } -.search-pill__ph { color: var(--text-3); font-size: 28rpx; } +.search-pill__ph { color: var(--text-3); font-size: var(--fs-base); } .cta { margin-top: 24rpx; display: flex; align-items: center; justify-content: center; background: linear-gradient(135deg, var(--gold) 0%, var(--gold-2) 100%); - color: #0E1110; font-weight: 700; font-size: 32rpx; - border-radius: 999rpx; padding: 28rpx; - box-shadow: 0 12rpx 30rpx rgba(217,179,108,0.28); + color: var(--accent-on); font-weight: 700; font-size: var(--fs-lg); + border-radius: var(--radius-pill); padding: 28rpx; + box-shadow: var(--shadow-glow); } -.cta__icon { margin-right: 12rpx; } .cta__arrow { margin-left: 12rpx; font-size: 36rpx; } -.quick__grid { display: flex; flex-wrap: wrap; gap: 18rpx; } +.quick__grid { display: flex; flex-wrap: wrap; gap: 16rpx; } .quick__tile { - width: calc((100% - 36rpx) / 3); + width: calc((100% - 32rpx) / 3); background: var(--surface); border: 1rpx solid var(--line); border-radius: var(--radius); padding: 28rpx 18rpx; display: flex; flex-direction: column; align-items: center; position: relative; overflow: hidden; + transition: background var(--motion-fast) var(--ease); } +.quick__tile:active { background: var(--surface-2); } .quick__tile::after { content: ''; position: absolute; inset: 0; background: radial-gradient(circle at 50% 0%, var(--gold-soft), transparent 70%); } -.quick__emoji { font-size: 48rpx; position: relative; z-index: 1; } .quick__label { font-size: 26rpx; margin-top: 12rpx; color: var(--text); position: relative; z-index: 1; } +/* ===== 为你准备(个性化) ===== */ +.personal__head { display: flex; align-items: center; justify-content: space-between; } +.personal__hi { font-size: var(--fs-xl); font-weight: 600; color: var(--text); margin-top: 6rpx; } +.personal__sub { font-size: var(--fs-sm); color: var(--text-2); font-weight: 600; margin: 28rpx 0 16rpx; } + +.plan-row { display: flex; flex-direction: column; gap: 12rpx; } +.plan-chip { + display: flex; align-items: center; gap: 16rpx; + background: var(--surface); border: 1rpx solid var(--line); + border-radius: var(--radius); padding: 20rpx 24rpx; +} +.plan-chip:active { background: var(--surface-2); } +.plan-chip__body { display: flex; flex-direction: column; } +.plan-chip__name { font-size: var(--fs-base); color: var(--text); font-weight: 600; } +.plan-chip__meta { font-size: 22rpx; color: var(--text-3); margin-top: 4rpx; } + +.rec-row { display: flex; flex-direction: column; gap: 12rpx; } +.rec-item { + display: flex; align-items: center; justify-content: space-between; + background: var(--surface); border: 1rpx solid var(--line); + border-radius: var(--radius); padding: 20rpx 24rpx; +} +.rec-item:active { background: var(--surface-2); } +.rec-item__body { display: flex; flex-direction: column; } +.rec-item__name { font-size: var(--fs-base); color: var(--text); font-weight: 600; } +.rec-item__reason { font-size: 22rpx; color: var(--accent); margin-top: 4rpx; } + +.fav-scroll { white-space: nowrap; margin-top: 4rpx; } +.fav-card { display: inline-flex; flex-direction: column; width: 200rpx; margin-right: 16rpx; vertical-align: top; } +.fav-card__img { + width: 200rpx; height: 200rpx; border-radius: var(--radius); + background: var(--surface-2); border: 1rpx solid var(--line); +} +.fav-card__name { font-size: 24rpx; color: var(--text-2); margin-top: 10rpx; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } + .hscroll { white-space: nowrap; margin-top: 8rpx; } .hscroll__item { display: inline-block; width: 300rpx; margin-right: 20rpx; vertical-align: top; } .chip-scroll { white-space: nowrap; margin-top: 8rpx; } .prog { display: inline-flex; flex-direction: column; align-items: center; justify-content: center; - min-width: 160rpx; padding: 24rpx 28rpx; margin-right: 18rpx; + min-width: 160rpx; padding: 24rpx 28rpx; margin-right: 16rpx; border-radius: var(--radius-sm); border: 1rpx solid; } -.prog__emoji { font-size: 44rpx; } .prog__name { font-size: 26rpx; font-weight: 700; margin-top: 10rpx; } .bp-grid { display: flex; flex-wrap: wrap; gap: 16rpx; } @@ -70,9 +103,9 @@ .eq { display: inline-flex; align-items: center; gap: 12rpx; background: var(--surface-2); border: 1rpx solid var(--line); - border-radius: 999rpx; padding: 16rpx 28rpx; margin-right: 16rpx; + border-radius: var(--radius-pill); padding: 16rpx 28rpx; margin-right: 16rpx; } .eq__name { font-size: 26rpx; color: var(--text); } -.eq__count { font-size: 22rpx; color: var(--gold); } +.eq__count { font-size: 22rpx; color: var(--accent); } -.empty { color: var(--text-3); font-size: 26rpx; text-align: center; padding: 40rpx 0; } +.empty { color: var(--text-3); font-size: var(--fs-sm); text-align: center; padding: 40rpx 0; } diff --git a/miniprogram/pages/my-plans/my-plans.json b/miniprogram/pages/my-plans/my-plans.json index 716250c..38e7448 100644 --- a/miniprogram/pages/my-plans/my-plans.json +++ b/miniprogram/pages/my-plans/my-plans.json @@ -3,6 +3,7 @@ "usingComponents": { "navbar": "/components/navbar/index", "plan-card": "/components/plan-card/index", - "bottom-nav": "/components/bottom-nav/index" + "bottom-nav": "/components/bottom-nav/index", + "fc-icon": "/components/fc-icon/index" } } diff --git a/miniprogram/pages/my-plans/my-plans.wxml b/miniprogram/pages/my-plans/my-plans.wxml index f52527a..683c787 100644 --- a/miniprogram/pages/my-plans/my-plans.wxml +++ b/miniprogram/pages/my-plans/my-plans.wxml @@ -17,7 +17,7 @@ - 📋 + 还没有计划,创建一个开始训练吧 + 新建计划 或浏览官方训练计划 › diff --git a/miniprogram/pages/my-plans/my-plans.wxss b/miniprogram/pages/my-plans/my-plans.wxss index 6ad5a79..a69f47a 100644 --- a/miniprogram/pages/my-plans/my-plans.wxss +++ b/miniprogram/pages/my-plans/my-plans.wxss @@ -3,7 +3,7 @@ .newbar__btn { display: flex; align-items: center; justify-content: center; background: linear-gradient(135deg, var(--gold) 0%, var(--gold-2) 100%); - color: #0E1110; font-weight: 700; font-size: 32rpx; + color: var(--accent-on); font-weight: 700; font-size: 32rpx; border-radius: 999rpx; padding: 26rpx; } .grid { display: flex; flex-wrap: wrap; gap: 18rpx; } @@ -20,6 +20,6 @@ .empty-box__btn { margin-top: 44rpx; padding: 24rpx 88rpx; border-radius: 999rpx; background: linear-gradient(135deg, var(--gold) 0%, var(--gold-2) 100%); - color: #0E1110; font-weight: 700; font-size: 30rpx; + color: var(--accent-on); font-weight: 700; font-size: 30rpx; } .empty-box__link { margin-top: 28rpx; font-size: 26rpx; color: var(--gold); } diff --git a/miniprogram/pages/plan-detail/plan-detail.js b/miniprogram/pages/plan-detail/plan-detail.js index 982f4dc..fb4fab1 100644 --- a/miniprogram/pages/plan-detail/plan-detail.js +++ b/miniprogram/pages/plan-detail/plan-detail.js @@ -1,5 +1,6 @@ const app = getApp(); const planSvc = require('../../services/plan'); +const { iconName } = require('../../utils/icon-name'); Page({ data: { @@ -24,7 +25,7 @@ Page({ ? raw.slice(0, 10) : (raw ? String(raw).slice(0, 10) : ''); this.setData({ - plan: { ...plan, updatedAtText }, + plan: { ...plan, updatedAtText, icon: iconName(plan.emoji) }, exercises: (plan && plan.exercises) || [], loading: false }); @@ -61,7 +62,7 @@ Page({ wx.showModal({ title: '删除计划', content: '确定要删除该训练计划吗?此操作不可恢复。', - confirmColor: '#E5544B', + confirmColor: '#FF5C5C', success: (res) => { if (res.confirm) { planSvc.remove(this.data.id).then(() => { diff --git a/miniprogram/pages/plan-detail/plan-detail.json b/miniprogram/pages/plan-detail/plan-detail.json index 586ed6a..4199b08 100644 --- a/miniprogram/pages/plan-detail/plan-detail.json +++ b/miniprogram/pages/plan-detail/plan-detail.json @@ -3,6 +3,7 @@ "usingComponents": { "navbar": "/components/navbar/index", "exercise-card": "/components/exercise-card/index", - "section-header": "/components/section-header/index" + "section-header": "/components/section-header/index", + "fc-icon": "/components/fc-icon/index" } } diff --git a/miniprogram/pages/plan-detail/plan-detail.wxml b/miniprogram/pages/plan-detail/plan-detail.wxml index f70c746..9e40023 100644 --- a/miniprogram/pages/plan-detail/plan-detail.wxml +++ b/miniprogram/pages/plan-detail/plan-detail.wxml @@ -2,7 +2,7 @@ - {{plan.emoji}} + {{plan.name}} {{plan.description}} 共 {{exercises.length}} 个动作 · 更新于 {{plan.updatedAtText}} @@ -20,7 +20,7 @@ - 🏋️ + 这个计划还没有动作,去添加吧 + 添加动作 diff --git a/miniprogram/pages/plan-detail/plan-detail.wxss b/miniprogram/pages/plan-detail/plan-detail.wxss index a1ba526..76aa73c 100644 --- a/miniprogram/pages/plan-detail/plan-detail.wxss +++ b/miniprogram/pages/plan-detail/plan-detail.wxss @@ -13,7 +13,7 @@ .list__card { width: 100%; } .list__remove { position: absolute; right: 12rpx; top: 12rpx; z-index: 5; - background: rgba(229,84,75,0.92); color: #fff; + background: var(--danger); color: var(--accent-on); font-size: 22rpx; padding: 8rpx 18rpx; border-radius: 999rpx; } @@ -30,13 +30,13 @@ .empty-box__btn { margin-top: 40rpx; padding: 22rpx 64rpx; border-radius: 999rpx; background: linear-gradient(135deg, var(--gold) 0%, var(--gold-2) 100%); - color: #0E1110; font-weight: 700; font-size: 30rpx; + color: var(--accent-on); font-weight: 700; font-size: 30rpx; } .actionbar { position: fixed; left: 0; right: 0; bottom: 0; display: flex; gap: 16rpx; padding: 20rpx 24rpx; - background: rgba(14,17,16,0.92); + background: var(--bar-bg); backdrop-filter: blur(12px); border-top: 1rpx solid var(--line); padding-bottom: calc(20rpx + env(safe-area-inset-bottom)); @@ -45,6 +45,6 @@ flex: 1; display: flex; align-items: center; justify-content: center; border-radius: 999rpx; padding: 24rpx 10rpx; font-size: 28rpx; font-weight: 700; } -.btn--gold { background: linear-gradient(135deg, var(--gold) 0%, var(--gold-2) 100%); color: #0E1110; } +.btn--gold { background: linear-gradient(135deg, var(--gold) 0%, var(--gold-2) 100%); color: var(--accent-on); } .btn--ghost { background: var(--surface-2); color: var(--text); border: 1rpx solid var(--line); flex: 0 0 auto; width: 180rpx; } -.btn--del { background: rgba(229,84,75,0.16); color: #E5544B; border: 1rpx solid rgba(229,84,75,0.4); flex: 0 0 auto; width: 180rpx; } +.btn--del { background: var(--danger-soft); color: var(--danger); border: 1rpx solid var(--danger-soft); flex: 0 0 auto; width: 180rpx; } diff --git a/miniprogram/pages/plan-edit/plan-edit.js b/miniprogram/pages/plan-edit/plan-edit.js index b9343a9..2ef63d5 100644 --- a/miniprogram/pages/plan-edit/plan-edit.js +++ b/miniprogram/pages/plan-edit/plan-edit.js @@ -1,8 +1,12 @@ const app = getApp(); const planSvc = require('../../services/plan'); -const EMOJIS = ['📋', '💪', '🔥', '🏋️', '🏃', '🧘', '🤸', '🥊', '🚴', '⚡', '🌟', '🎯']; -const COLORS = ['#D9B36C', '#58C9B9', '#9B8CFF', '#F08A5D', '#3FBF7F', '#5B9DF9']; +// UGC 计划图标选择器:Lucide 运动子集(非 emoji) +const ICONS = ['clipboard-list', 'dumbbell', 'flame', 'accessibility', 'person-standing', + 'bike', 'heart', 'target', 'trophy', 'medal', 'star', 'activity', + 'sparkles', 'zap', 'trending-up', 'folder-open']; +// 计划主题色:对齐 Kinetic Dark 语义色板 +const COLORS = ['#B6F24E', '#2BD9A6', '#4FA8FF', '#F2B53D', '#FF5C5C', '#9BA7A1']; Page({ data: { @@ -11,9 +15,9 @@ Page({ id: '', name: '', description: '', - emoji: '📋', - color: '#D9B36C', - emojis: EMOJIS, + emoji: 'clipboard-list', + color: '#B6F24E', + emojis: ICONS, colors: COLORS, loading: false, dirty: false // 是否有未保存的修改 @@ -29,8 +33,8 @@ Page({ this.setData({ name: p.name || '', description: p.description || '', - emoji: p.emoji || '📋', - color: p.color || '#D9B36C', + emoji: p.emoji || 'clipboard-list', + color: p.color || '#B6F24E', loading: false }); }).catch(() => { diff --git a/miniprogram/pages/plan-edit/plan-edit.json b/miniprogram/pages/plan-edit/plan-edit.json index ad4d4ef..9b7dad4 100644 --- a/miniprogram/pages/plan-edit/plan-edit.json +++ b/miniprogram/pages/plan-edit/plan-edit.json @@ -1,6 +1,7 @@ { "navigationStyle": "custom", "usingComponents": { - "navbar": "/components/navbar/index" + "navbar": "/components/navbar/index", + "fc-icon": "/components/fc-icon/index" } } diff --git a/miniprogram/pages/plan-edit/plan-edit.wxml b/miniprogram/pages/plan-edit/plan-edit.wxml index df40608..8561c6d 100644 --- a/miniprogram/pages/plan-edit/plan-edit.wxml +++ b/miniprogram/pages/plan-edit/plan-edit.wxml @@ -3,7 +3,7 @@ - {{emoji}} + {{name || '计划名称'}} {{description || '计划描述(选填)'}} @@ -40,7 +40,9 @@ class="emoji {{emoji === item ? 'emoji--active' : ''}}" style="{{emoji === item ? 'border-color:' + color + '; background:' + color + '22;' : ''}}" data-emoji="{{item}}" - bindtap="onPickEmoji">{{item}} + bindtap="onPickEmoji"> + + diff --git a/miniprogram/pages/plan-edit/plan-edit.wxss b/miniprogram/pages/plan-edit/plan-edit.wxss index eb11c25..a44b961 100644 --- a/miniprogram/pages/plan-edit/plan-edit.wxss +++ b/miniprogram/pages/plan-edit/plan-edit.wxss @@ -41,7 +41,7 @@ .savebar { position: fixed; left: 0; right: 0; bottom: 0; padding: 20rpx 32rpx; - background: rgba(14,17,16,0.92); + background: var(--bar-bg); backdrop-filter: blur(12px); border-top: 1rpx solid var(--line); padding-bottom: calc(20rpx + env(safe-area-inset-bottom)); @@ -49,6 +49,6 @@ .savebar__btn { display: flex; align-items: center; justify-content: center; background: linear-gradient(135deg, var(--gold) 0%, var(--gold-2) 100%); - color: #0E1110; font-weight: 700; font-size: 32rpx; + color: var(--accent-on); font-weight: 700; font-size: 32rpx; border-radius: 999rpx; padding: 26rpx; } diff --git a/miniprogram/pages/plan-favorites/plan-favorites.json b/miniprogram/pages/plan-favorites/plan-favorites.json index a9421ea..04d14c5 100644 --- a/miniprogram/pages/plan-favorites/plan-favorites.json +++ b/miniprogram/pages/plan-favorites/plan-favorites.json @@ -2,6 +2,7 @@ "navigationStyle": "custom", "usingComponents": { "navbar": "/components/navbar/index", - "plan-card": "/components/plan-card/index" + "plan-card": "/components/plan-card/index", + "fc-icon": "/components/fc-icon/index" } } diff --git a/miniprogram/pages/plan-favorites/plan-favorites.wxml b/miniprogram/pages/plan-favorites/plan-favorites.wxml index d506e43..9034f4f 100644 --- a/miniprogram/pages/plan-favorites/plan-favorites.wxml +++ b/miniprogram/pages/plan-favorites/plan-favorites.wxml @@ -13,7 +13,7 @@ - + 还没有收藏官方计划 浏览官方计划 diff --git a/miniprogram/pages/plan-favorites/plan-favorites.wxss b/miniprogram/pages/plan-favorites/plan-favorites.wxss index f8c6ba0..04873eb 100644 --- a/miniprogram/pages/plan-favorites/plan-favorites.wxss +++ b/miniprogram/pages/plan-favorites/plan-favorites.wxss @@ -13,5 +13,5 @@ .empty-box__btn { margin-top: 40rpx; padding: 22rpx 64rpx; border-radius: 999rpx; background: linear-gradient(135deg, var(--gold) 0%, var(--gold-2) 100%); - color: #0E1110; font-weight: 700; font-size: 30rpx; + color: var(--accent-on); font-weight: 700; font-size: 30rpx; } diff --git a/miniprogram/pages/profile/profile.js b/miniprogram/pages/profile/profile.js index 8c52572..b5d9810 100644 --- a/miniprogram/pages/profile/profile.js +++ b/miniprogram/pages/profile/profile.js @@ -3,20 +3,22 @@ const auth = require('../../utils/auth'); const favSvc = require('../../services/favorite'); const planSvc = require('../../services/plan'); -const EMOJIS = ['💪', '🏋️', '🔥', '🏃', '🧘', '🤸', '🥊', '🚴', '⚡', '🌟', '🐯', '🦁']; +// UGC 头像图标选择器:Lucide 运动子集(非 emoji) +const ICONS = ['dumbbell', 'accessibility', 'person-standing', 'bike', 'heart', 'flame', + 'target', 'medal', 'trophy', 'award', 'star', 'activity', 'sparkles', 'zap', 'trending-up']; Page({ data: { statusBarHeight: 20, loggedIn: false, - profile: { nickname: '', avatar: '💪' }, + profile: { nickname: '', avatar: 'user' }, counts: { fav: 0, plans: 0, favPlans: 0 }, loading: false, // 编辑态 showEmojiPicker: false, - emojis: EMOJIS, + emojis: ICONS, draftNickname: '', - draftAvatar: '💪' + draftAvatar: 'user' }, onLoad() { @@ -70,7 +72,7 @@ Page({ if (res.confirm) { this.setData({ draftNickname: (res.content || '').trim() || cur.nickname, - draftAvatar: cur.avatar || '💪', + draftAvatar: cur.avatar || 'user', showEmojiPicker: true }); } @@ -112,7 +114,7 @@ Page({ auth.logout(); this.setData({ loggedIn: false, - profile: { nickname: '', avatar: '💪' }, + profile: { nickname: '', avatar: 'user' }, counts: { fav: 0, plans: 0, favPlans: 0 } }); } diff --git a/miniprogram/pages/profile/profile.json b/miniprogram/pages/profile/profile.json index a040bdc..6740653 100644 --- a/miniprogram/pages/profile/profile.json +++ b/miniprogram/pages/profile/profile.json @@ -3,6 +3,7 @@ "usingComponents": { "navbar": "/components/navbar/index", "avatar": "/components/avatar/index", - "bottom-nav": "/components/bottom-nav/index" + "bottom-nav": "/components/bottom-nav/index", + "fc-icon": "/components/fc-icon/index" } } diff --git a/miniprogram/pages/profile/profile.wxml b/miniprogram/pages/profile/profile.wxml index f39d0ed..641cb38 100644 --- a/miniprogram/pages/profile/profile.wxml +++ b/miniprogram/pages/profile/profile.wxml @@ -3,7 +3,7 @@ {{draftNickname || '未命名'}} diff --git a/miniprogram/pages/profile/profile.wxss b/miniprogram/pages/profile/profile.wxss index 3025bca..64bde3e 100644 --- a/miniprogram/pages/profile/profile.wxss +++ b/miniprogram/pages/profile/profile.wxss @@ -16,7 +16,7 @@ .login__btn { margin-top: 56rpx; width: 420rpx; background: linear-gradient(135deg, var(--gold) 0%, var(--gold-2) 100%); - color: #0E1110; font-weight: 700; font-size: 32rpx; + color: var(--accent-on); font-weight: 700; font-size: 32rpx; border-radius: 999rpx; padding: 24rpx; } .login__btn::after { border: none; } @@ -24,7 +24,7 @@ /* 个人中心 */ .center { padding: 32rpx; } .profile { - background: linear-gradient(135deg, rgba(217,179,108,0.14), var(--surface)); + background: linear-gradient(135deg, var(--accent-soft), var(--surface)); border: 1rpx solid var(--gold-line); border-radius: var(--radius); padding: 36rpx; } @@ -57,7 +57,7 @@ /* 头像选择弹层 */ .sheet { position: fixed; inset: 0; z-index: 80; - background: rgba(0,0,0,0.5); + background: var(--scrim); display: flex; align-items: flex-end; } .sheet__panel { @@ -81,4 +81,4 @@ font-size: 30rpx; font-weight: 700; } .sheet__btn--ghost { background: var(--surface-2); color: var(--text); border: 1rpx solid var(--line); } -.sheet__btn--gold { background: linear-gradient(135deg, var(--gold) 0%, var(--gold-2) 100%); color: #0E1110; } +.sheet__btn--gold { background: linear-gradient(135deg, var(--gold) 0%, var(--gold-2) 100%); color: var(--accent-on); } diff --git a/miniprogram/pages/recommend/recommend.wxss b/miniprogram/pages/recommend/recommend.wxss index 2699426..3406021 100644 --- a/miniprogram/pages/recommend/recommend.wxss +++ b/miniprogram/pages/recommend/recommend.wxss @@ -23,7 +23,7 @@ .confirm { position: fixed; left: 0; right: 0; bottom: 0; padding: 20rpx 32rpx; - background: rgba(14,17,16,0.92); + background: var(--bar-bg); backdrop-filter: blur(12px); border-top: 1rpx solid var(--line); padding-bottom: calc(20rpx + env(safe-area-inset-bottom)); @@ -34,7 +34,7 @@ .cta { display: flex; align-items: center; justify-content: center; background: linear-gradient(135deg, var(--gold) 0%, var(--gold-2) 100%); - color: #0E1110; font-weight: 700; font-size: 32rpx; + color: var(--accent-on); font-weight: 700; font-size: 32rpx; border-radius: 999rpx; padding: 26rpx; } .cta__arrow { margin-left: 12rpx; font-size: 36rpx; } diff --git a/miniprogram/pages/search/search.json b/miniprogram/pages/search/search.json index 50f04f2..26c4722 100644 --- a/miniprogram/pages/search/search.json +++ b/miniprogram/pages/search/search.json @@ -2,6 +2,7 @@ "navigationStyle": "custom", "usingComponents": { "navbar": "/components/navbar/index", - "exercise-card": "/components/exercise-card/index" + "exercise-card": "/components/exercise-card/index", + "fc-icon": "/components/fc-icon/index" } } diff --git a/miniprogram/pages/search/search.wxml b/miniprogram/pages/search/search.wxml index 6e8f85a..5033c5c 100644 --- a/miniprogram/pages/search/search.wxml +++ b/miniprogram/pages/search/search.wxml @@ -2,7 +2,7 @@ - 🔍 +