From 53b57e8bd42ca0bce7ec8732a4c0ed9fa0a7a727 Mon Sep 17 00:00:00 2001 From: cnliucheng Date: Wed, 8 Jul 2026 11:26:44 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E6=B7=BB=E5=8A=A0=E8=A7=86=E8=A7=89?= =?UTF-8?q?=E4=B8=8E=E5=8A=A8=E6=95=88=E5=85=A8=E9=87=8F=E5=8D=87=E7=BA=A7?= =?UTF-8?q?=E8=AE=BE=E8=AE=A1=20spec?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 完整方案:深色模式 + 骨架屏 + 浮动 TabBar + 数字滚动 + 全屏彩带 + 页面级转场 + 排名位移动画 + 空状态插画 零预算,不引第三方包,2 个工作日完成 --- config.js | 2 +- .../specs/2026-07-08-visual-upgrade-design.md | 373 ++++++++++++++++++ 2 files changed, 374 insertions(+), 1 deletion(-) create mode 100644 docs/superpowers/specs/2026-07-08-visual-upgrade-design.md diff --git a/config.js b/config.js index 2dc516b..c3739d5 100644 --- a/config.js +++ b/config.js @@ -7,7 +7,7 @@ module.exports = { version: 'v2.0', /** 最后更新日期,外显在设置页「关于」 */ - updatedAt: '2026-06-24', + updatedAt: '2026-07-08', /** 开发者名称 / 微信号,设置页点击可复制 */ developer: '刘承', diff --git a/docs/superpowers/specs/2026-07-08-visual-upgrade-design.md b/docs/superpowers/specs/2026-07-08-visual-upgrade-design.md new file mode 100644 index 0000000..f868a67 --- /dev/null +++ b/docs/superpowers/specs/2026-07-08-visual-upgrade-design.md @@ -0,0 +1,373 @@ +# 视觉与动效全量升级 — 设计文档 + +**日期**: 2026-07-08 +**项目**: 平板支撑训练小程序 (pbzc) +**类型**: 视觉/动效/UX 升级(不引第三方包,零预算) +**预计工时**: 2 个工作日 + +--- + +## 1. 背景与目标 + +### 1.1 现状 +项目已完成核心功能闭环:训练 / 记录 / 排行 / 设置 4 大页面 + 自定义 TabBar + 云函数同步 + Canvas 进度环 + 5 套主题色 + 7 套 CSS 动画 + 5 个自研组件。 + +### 1.2 问题 +与同类精品小程序(美团/京东/小红书)相比,仍存在 6 个"显低级"的视觉缺口: +1. 背景纯灰(`#F5F5F5`)有强 Web 页面感 +2. 加载中只有"加载中..."文字,无骨架屏 +3. 页面跳转是默认微信闪切,无过渡 +4. 排行榜头像全是占位图标 +5. 数字无更新动画 +6. TabBar 贴底矩形,无悬浮 Dock 感 + +### 1.3 目标 +不增加第三方依赖(无 Lottie / 无 npm 包),通过纯 CSS / 动画 / 少量 JS 工具,将小程序从"主流小程序"提升至"独立精品 App"视觉水平。 + +### 1.4 非目标 +- ❌ 不做头像上传(需要腾讯云 COS,零预算约束下跳过) +- ❌ 不做商业化后端 +- ❌ 不改训练 / 计时 / 同步 / 排行核心业务逻辑 +- ❌ 不重写已有组件的实现方式 + +--- + +## 2. 架构 + +### 2.1 总思路 +- 复用现有 `utils/theme.js` 的 CSS 变量体系 +- 深色模式 = 新增 `--*-dark` 变量 + 第二个可选参数 +- 新增 1 个组件 + 2 个工具函数 + 改 ~10 个现有文件 +- 业务 JS 0 改动,回滚 5 分钟 + +### 2.2 文件改动清单 + +| 类型 | 路径 | 说明 | +|------|------|------| +| 🆕 新增 | `components/ui-skeleton/ui-skeleton.{js,json,wxml,wxss}` | 骨架屏组件(3 variant) | +| 🆕 新增 | `utils/countUp.js` | 数字滚动工具 | +| 🆕 新增 | `utils/darkMode.js` | 系统主题监听 | +| ✏️ 修改 | `app.json` | 加 `darkmode: true` + theme JSON 配置 | +| ✏️ 修改 | `app.wxss` | 渐变背景 + 深色变量 | +| ✏️ 修改 | `utils/theme.js` | `getThemeStyle(theme, isDark)` 支持第二参数 | +| ✏️ 修改 | `custom-tab-bar/index.wxml` + `.wxss` | 悬浮 Dock 样式 | +| ✏️ 修改 | `pages/leaderboard/leaderboard.wxml` | 加载状态改骨架 + 排名位移动画 | +| ✏️ 修改 | `pages/records/records.wxml` | 加载状态改骨架 | +| ✏️ 修改 | `pages/timer/timer.wxml` + `.wxss` | 完成全屏彩带 + 数字滚动 | +| ✏️ 修改 | `pages/index/index.wxss` | 适配渐变背景 + 页面级入场动画 | +| ✏️ 修改 | `pages/settings/settings.wxml` | 加深色模式开关 | +| ✏️ 修改 | `app.wxss` 中追加 `.container.page-enter` 动画类 | 页面级转场动画(fade + translateY) | +| ✏️ 修改 | 2 个 empty 状态 WXML(leaderboard/records) | 用 CSS 简笔 SVG 替代空状态文字 | + +### 2.3 依赖关系 +``` +app.json (darkmode: true) + → app.onLaunch → 读 wx.getSystemInfoSync().theme + → utils/darkMode.js + → utils/theme.js → getThemeStyle(theme, isDark) + → 各页面 onLoad → applyThemeToPage() 注入 themeStyle + → 组件通过 CSS 变量级联自动适配 +``` + +--- + +## 3. 组件详细规格 + +### 3.1 新组件 `ui-skeleton` + +**Props 接口**: +```js +properties: { + variant: { type: String, value: 'card' }, // 'card' | 'list-row' | 'circle' + count: { type: Number, value: 1 }, // 仅 list-row 生效 + active: { type: Boolean, value: true } // 是否播放闪烁动画 +} +``` + +**3 种 variant 渲染规则**: +- `card`:模拟 `ui-card` —— 24rpx 圆角矩形 + 3 行灰条(标题宽度 60%,正文 100%,小字 40%) +- `list-row`:模拟排行榜行 —— 64rpx 圆形 + 2 行文字条 +- `circle`:模拟计时器进度环 —— 360rpx 大圆 + 中心 200×32rpx 灰条 + +**动画**:`@keyframes shimmer`,opacity 0.4 ↔ 0.8,1.4s ease-in-out infinite。 + +**最小显示时长**:组件挂载时起 200ms 定时器,避免骨架闪一下又消失。 + +### 3.2 `utils/countUp.js` + +**API**: +```js +function countUp({ from, to, duration, decimals = 0, onUpdate, onComplete }) + // 返回 { cancel() } + // 内部: setInterval(fn, 16) + easeOut (1 - (1-t)^3) +``` + +**使用场景**: +- 计时器归零瞬间:30 → 0 +- 排行榜加载完:"我的时长" 0 → 真实值 +- 完成庆祝:"本次 +30s" 飘字 + +**不在以下场景使用**(避免干扰): +- 计时器运行中(每秒变一次,肉眼够用) +- 用户已停留的页面(防打扰) + +**取消机制**: +- `onUnload` 必调 `cancel()` +- 页面实例持 `_countUpTask`,新一次 countUp 前 cancel 上一个 +- 防止 setData 到已销毁页面 + +### 3.3 `utils/darkMode.js` + +**API**: +```js +function getInitialDarkMode() // boolean +function watchDarkMode(onChange) // 注册系统主题变化回调 +``` + +**读取优先级**(在 `getInitialDarkMode` 内): +```js +const pref = wx.getStorageSync('dark_mode_pref') || 'system' +if (pref === 'system') return sysTheme === 'dark' +return pref === 'dark' +``` + +**系统变化监听**:`wx.onThemeChange(cb)`,兼容失败时退化为只读一次。 + +**主题切换**:用户改档位后,遍历 `getCurrentPages()`,每页调 `applyThemeToPage()` 重渲染。 + +### 3.4 theme.js 扩展 + +**改动最小化**:仅扩展 `getThemeStyle` 签名。 + +```js +function getThemeStyle(theme, isDark = false) { + const t = theme || getCurrentTheme() + if (isDark) { + return `${BASE_VARS_DARK}--primary:${t.primary};...` + } + return `${BASE_VARS}--primary:${t.primary};...` // 现有逻辑 +} +``` + +**深色变量**(`BASE_VARS_DARK`,在 app.wxss 中体现): +```css +--bg-dark: #0F0F12; +--card-bg-dark: #1C1C1E; +--text-dark: #E5E5E7; +--text-secondary-dark: #98989F; +--border-dark: #2C2C2E; +--success-dark: #30D158; +``` + +### 3.5 现有组件最小改动 + +| 组件 | 改动 | +|------|------| +| `ui-card` | 仅 CSS 变量跟随,0 JS 改动 | +| `ui-btn` | 加 `::before` 伪元素涟漪 + 已有的 `transform: scale(0.96)` | +| `progress-ring` | 完成瞬间数字走 `countUp` 滚动到目标值 | + +### 3.6 页面级转场动画 + +**实现方式**:不使用 `wx.pageContainer`(增加兼容性负担),改用 CSS 入场动画 + 极短的 `animation-delay` 错开元素。 + +在 `app.wxss` 追加: +```css +.container.page-enter { + animation: pageIn 0.35s cubic-bezier(0.2, 0, 0.2, 1) both; +} +@keyframes pageIn { + from { opacity: 0; transform: translateY(20rpx); } + to { opacity: 1; transform: translateY(0); } +} +``` + +**触发**:每个页面的根 `view` 加 `class="container page-enter"`。微信原生 page transition 的 ~100ms 渐隐 + 这个 350ms 滑入组合,肉眼感受是"自然过渡"。 + +**为什么不用 `wx.pageContainer`**:需要路由配置侵入式改动,调试成本高。350ms CSS 动画覆盖了 80% 体验。 + +### 3.7 排名位移动画 + +**场景**:排行榜 `rankedList` 渲染时,行依次滑入。 + +**实现**:在 `leaderboard.wxml` 给 `.rank-item` 加 `style="animation-delay: {{index * 60}}ms"`,配合 CSS: +```css +.rank-item { + animation: rankIn 0.4s cubic-bezier(0.2, 0, 0.2, 1) both; +} +@keyframes rankIn { + from { opacity: 0; transform: translateX(-20rpx); } + to { opacity: 1; transform: translateX(0); } +} +``` + +**约束**:超过 10 行不再延迟(避免最后一行等 600ms)。 + +### 3.8 空状态插画 + +**当前**:`暂无排行数据` + 小人图标。 + +**升级**:用 CSS 画一个 200×200rpx 的简笔平板支撑小人(body + 两条手臂 + 文字气泡),关键样式: +```css +.empty-illus { + width: 200rpx; height: 200rpx; + position: relative; +} +.empty-illus::before { /* 横躺的身体 */ } +.empty-illus::after { /* 头顶气泡 "加油" */ } +``` + +**优先级**:低。如果工时不够,可降级为"用 emoji 大字 + 主色圆形背景"占位。 + +--- + +## 4. 数据流 + +### 4.1 主题状态流 +``` +系统: wx.getSystemInfoSync().theme + → getInitialDarkMode() → boolean + → getThemeStyle(theme, isDark) → CSS 变量字符串 + → setData({ themeStyle }) + → 页面 生效 +``` + +### 4.2 用户手动覆盖 +- 设置页 3 档开关:`system` / `light` / `dark` +- 存储:`wx.setStorageSync('dark_mode_pref', value)` +- 切换时:调 `applyThemeToPage` 重渲染所有已打开页面 + +### 4.3 数字滚动流 +``` +setData({ displayTime: 30 }) + → countUp({ from: 0, to: 30, duration: 800 }) + → 每 16ms onUpdate(mid) + → setData({ displayTime: mid }) +``` + +### 4.4 骨架屏流 +``` +Page.onLoad + → setData({ loading: true }) + → 渲染 + → wx.cloud.callFunction 异步返回 + → setData({ loading: false, realData: ... }) + → 消失 + → <实际数据 wx:else> 出现 +``` + +--- + +## 5. 错误处理与边界 + +### 5.1 深色模式 +| 情况 | 处理 | +|------|------| +| 系统切换时停留在某页 | `wx.onThemeChange` → 遍历 `getCurrentPages()` 重渲染 | +| 老用户无 `dark_mode_pref` | 默认 `'system'`,行为同升级前 | +| 旧设备不支持 `onThemeChange` | try-catch,退化为只读一次 | +| 主题同步云端失败 | 不影响本地,不抛错 | + +### 5.2 countUp +| 情况 | 处理 | +|------|------| +| 页面销毁时还在滚动 | `onUnload` 必调 `cancel()` | +| 连续触发(开始/暂停反复) | 每次新 countUp 前 cancel 上一个 | +| from === to | 跳过动画,直接落到目标 | +| duration ≤ 0 | 跳过动画 | + +### 5.3 骨架屏 +| 情况 | 处理 | +|------|------| +| 加载 < 200ms | 最小显示定时器,避免闪一下 | +| 加载失败 | 骨架消失,进 empty 态 | +| 重复 setData loading | `if (this.data.loading) return` 短路 | + +### 5.4 浮动 TabBar +| 情况 | 处理 | +|------|------| +| iPhone X+ 安全区 | `env(safe-area-inset-bottom)` + `margin-bottom: 16rpx` | +| 点击当前 tab | 已有 `isSelected` 判断,避免重复 | + +### 5.5 回滚 +所有改动只新增文件 + 增量改 CSS,不改核心业务 JS: +```bash +git checkout HEAD -- app.wxss app.json custom-tab-bar/* \ + pages/*/*.wxss pages/*/*.wxml +rm -rf components/ui-skeleton/ utils/countUp.js utils/darkMode.js +``` + +--- + +## 6. 验收清单 + +### 6.1 视觉 +- [ ] 浅色模式背景是带主题色极淡径向渐变(非纯灰) +- [ ] 深色模式所有文字/卡片/边框自动转深色(不发白) +- [ ] 5 套主题 × 2 套明暗 = 10 种组合正常 +- [ ] TabBar 悬浮、圆角、阴影明显 +- [ ] 计时器数字完成时滚动归零 +- [ ] 完成时全屏彩带飘落 +- [ ] 页面级入场动画(fade + translateY)所有页面正常 +- [ ] 排行榜行依次滑入(前 10 行) + +### 6.2 性能 +- [ ] 首页首屏 < 500ms +- [ ] 切深色 < 100ms +- [ ] countUp 不掉帧 +- [ ] 包体积增量 < 5KB + +### 6.3 功能 +- [ ] 排行榜/记录页:先骨架再真实数据,无闪 +- [ ] 排行榜行依次滑入 +- [ ] 2 个空状态有插画 +- [ ] 设置页深色开关:切到 dark → 已打开页面立即响应 +- [ ] 系统切深色:当前页立即响应 +- [ ] countUp 取消 0 报错(开始/停止循环 10 次 console 无错) + +### 6.4 设备兼容 +- [ ] iOS 真机 +- [ ] Android 微信 8.0.3+ +- [ ] 开发者工具基础库 2.32.3+ + +--- + +## 7. 测试方法 + +无单测框架,端到端肉眼 + Console 验证: + +1. **本地开发者工具**:每个页面截图前后对比 +2. **真机预览**:预览码 / 体验码 +3. **Console 监听**:`countUp` / `darkMode` 关键路径加 log +4. **Storage 检查**:开发者工具 Storage 面板看 `dark_mode_pref` + +--- + +## 8. 风险评估 + +| 风险点 | 等级 | 原因 | +|--------|------|------| +| 深色模式不生效 | 🟢 低 | 复用 theme.js 已有机制 | +| 浮动 TabBar 高度不对 | 🟢 低 | 已有自研 TabBar | +| countUp 性能差 | 🟢 低 | 16ms + 线性插值 | +| 包体积超标 | 🟢 极低 | 0 第三方包 | +| 回归业务逻辑 | 🟢 极低 | 不改 timer/cloud/storage JS | + +--- + +## 9. 交付物 + +- 1 个新组件 `components/ui-skeleton/` +- 2 个新工具 `utils/countUp.js`、`utils/darkMode.js` +- 改动 ~10 个现有文件 +- 1 份升级前/后对比截图 + +--- + +## 10. 不在本次范围 + +- 头像上传(需 COS,零预算约束跳过) +- Lottie 动画(需引入 50KB 包) +- 视频示范(需 VOD) +- 社区/打卡广场(需后端) +- 商业化(支付 / 备案 / 会员)