Files
iboard/docs/V0.2/设计文档.md
T
cheney 4a04720a5e feat(V0.2): OTA 版本与判新口径 + Tab 结构调整
1) 版本对齐分支
- package.json / tauri.conf.json / Cargo.toml / Cargo.lock: 0.1.0 -> 0.2.0, OTA 包版本变为 0.2.0-ota
- 新增 scripts/ci/check-version.mjs: 分支 dev-x.y 与 package.json 版本不一致时 CI 直接失败
- docker-build.yml / ota-build.yml: min_binary_version 与前端版本解耦, 固定 0.1.0
  (现有 APK 原生层仍是 0.1.0, 抬高会导致旧 APK 收不到更新)

2) 我的页 OTA「开发版本」开关 (默认关, 持久化 localStorage.iboard_ota_dev_mode)
- 关: 按版本号逐字段比较 (src/utils/version.js), 服务端 version 更高才算新
- 开: 按 build sequence 判新 (hotswap checkUpdate), 每次构建都能推到设备
- Web 端: 开发口径退化为版本不同即算新; 生产口径严格比较大小

3) 打卡 Tab 移到最左并默认选中, 移除特效开关
- BottomNav: 打卡/宏观/监控/我; 删除 home 图标
- App: / 与 404 均重定向到 /checkin
- RingProgress/Celebrate: 删除 effectsEnabled/setEffectsEnabled, 特效恒开
- HabitView: 去掉头部「特效开/关」按钮

4) 取消首页 Tab, 报警/新闻并入宏观
- 新增 pages/macro/AlertsView.jsx、NewsView.jsx; 删除 HomePage.jsx
- MacroPage: 6 个子 Tab (油价/经济数据/策略研究/经济文章/报警/新闻), 报警角标保留
- 新闻详情 /news/:id -> /macro/news/:id (宏观 Tab 保持高亮)

5) 文档: 需求文档补 F-OT 需求点与已决策项; 设计文档新增 §10 变更记录与判新设计;
   界面设计.html 11 屏 (Tab 4 项/去掉特效开关/宏观合并/我的 OTA 开关 关+开 两态)
2026-10-22 17:17:29 +08:00

27 KiB
Raw Blame History

iBoard V0.2 设计文档 — 打卡 / 日历 / 清单

项 内容
版本 V0.2(对应分支 dev-0.2,应用版本 0.2.0)
文档状态 已实现(与代码一致;含 2026-10-22 设计变更,见 §10)
上游文档 需求文档.md(需求编号 F-CK / F-CL / F-LS / F-SY / U / D / N / E 沿用本文)
配套文档 数据库设计文档.md、界面设计.html
技术栈 Tauri 2 + React 18 + antd-mobile + React Router v6 + Express 4 + Prisma/MariaDB
设计基线 移动端优先,视口 420×820,375~430px 自适应;时区统一 Asia/Shanghai

1. 总体设计

1.1 目标与范围

在既有 iBoard 之上新增「打卡」模块并升为默认首页(底部 Tab 由 5 个精简为 4 个:打卡 / 宏观 / 监控 / 我),页面内以三个子视图承载三类需求:

子视图 解决问题 核心隐喻
打卡 周期性习惯的量化坚持 环形进度,点环即打卡
日历 回看昨天、执行今天、预知明天 月历网格 + 日状态点
清单 出差/回老家等一次性场景的漏项检查 勾选清单 + 进度条

Tab 结构(V0.2 终态)

顺序 Tab 路由 说明
1 打卡(默认) /checkin 根路径 / 重定向到此;/checkin 内含 打卡/日历/清单 三视图
2 宏观 /macro/* 油价 / 经济数据 / 策略研究 / 经济文章 / 报警 / 新闻(后两者由原「首页」并入)
3 监控 /monitor 服务器 + 米家环境
4 我 /me 用户信息 + 前端 OTA 升级

原「首页」Tab 已移除:其中的报警、新闻 两个模块并入宏观,作为其第 5、6 个子 Tab(/macro/alerts、/macro/news),新闻详情改为 /macro/news/:id 以保持宏观 Tab 高亮。

不纳入 V0.2:多用户共享清单、提醒推送、数据导出、打卡项分组、年视图热力图(见 §9 演进)。

1.2 架构总览

┌──────────────────────── 前端 (React + antd-mobile, 打包进 Tauri / OTA) ────────────────────────┐
│  App.jsx 路由 (默认 / → /checkin)                                                               │
│    └ /checkin  ──► CheckinPage ── SwipeTabs ──┬── HabitView    (打卡)                           │
│                                              ├── CalendarView (日历)                           │
│                                              └── ListView     (清单)                           │
│    └ /macro/*  ──► MacroPage ── 油价/经济数据/策略研究/经济文章/报警/新闻                        │
│  组件: RingProgress(环形) / Celebrate(特效, 恒开) / BottomNav / TabIcon                          │
│  工具: src/pages/checkin/date.js (本地日期, 与后端同口径)  apiClient: src/api/checkin.js         │
└──────────────────────────────────┬─────────────────────────────────────────────────────────────┘
                                   │ HTTPS + JWT (Authorization: Bearer)
┌──────────────────────────────────▼─────────────────────────────────────────────────────────────┐
│ Express (server/index.js)                                                                      │
│   /api/checkin   ─► routes/checkin.js    ─┐                                                    │
│   /api/checklists─► routes/checklists.js ─┤─► middleware.authRequired ─► Prisma Client          │
│                                           └─► checkin/period.js (周期口径唯一来源)               │
└──────────────────────────────────┬─────────────────────────────────────────────────────────────┘
                                   ▼
                      MariaDB: Habit / CheckIn / CheckList / CheckListItem / CheckListTemplate

1.3 分层与职责

层 位置 职责
页面 src/pages/checkin/*.jsx 视图编排、本地状态、乐观更新与回滚
组件 src/components/RingProgress.jsx 纯展示:SVG 环形 + 完成特效(恒开,无开关)
日期工具 src/pages/checkin/date.js 与后端同口径的本地日期运算(改一处需同步另一处)
API 封装 src/api/checkin.js 统一走 api()(自带 token / 错误解析)
路由 server/routes/checkin.js、checklists.js 参数校验、鉴权、组装展示态
领域逻辑 server/checkin/period.js 周期窗口、日均期望、streak 判定
持久化 Prisma Client(generated/prisma) 表操作

1.4 目录结构(本次新增/修改)

prisma/
  schema.prisma                                     # +5 个 model,User 加反向关系
  migrations/20261022120000_add_checkin_checklist/  # 新建表 + 外键
server/
  checkin/period.js                                 # 周期计算(唯一口径)
  routes/checkin.js                                 # /api/checkin/*
  routes/checklists.js                              # /api/checklists/*
  index.js                                          # 挂载两个路由
src/
  api/checkin.js                                    # checkinApi / checklistApi
  api/ota.js                                        # OTA 封装 + 判新模式(version/sequence)
  utils/version.js                                  # 版本号字段比较(纯函数, 可单测)
  utils/format.js                                   # 日期格式化
  components/RingProgress.jsx                       # 环形进度 + Celebrate 特效
  components/BottomNav.jsx                          # Tab: 打卡(默认)/宏观/监控/我
  components/TabIcon.jsx                            # 图标集(移除 home)
  pages/checkin/CheckinPage.jsx                     # 容器:三视图切换
  pages/checkin/HabitView.jsx                       # 打卡视图
  pages/checkin/CalendarView.jsx                    # 日历视图
  pages/checkin/ListView.jsx                        # 清单视图
  pages/checkin/date.js                             # 前端日期工具
  pages/MacroPage.jsx                               # 宏观: 6 个子 Tab(含报警/新闻)
  pages/macro/AlertsView.jsx                        # 报警(原首页)
  pages/macro/NewsView.jsx                          # 新闻(原首页)
  pages/MePage.jsx                                  # 我: OTA 组内「开发版本」开关
  App.jsx                                           # / → /checkin; /macro/*; 移除 /news/:id
  styles/globals.css                                # 打卡相关样式段
scripts/ci/check-version.mjs                        # CI: 校验分支名 ↔ package.json 版本
docs/V0.2/                                          # 需求 / 设计 / 数据库设计 / 界面设计

2. 领域模型与核心口径

2.1 实体

Habit(打卡项定义)· CheckIn(一次打卡)· CheckList(场景清单)· CheckListItem(检查项)· CheckListTemplate(模板)。字段与索引见《数据库设计文档》。

2.2 周期口径(唯一来源:server/checkin/period.js)

概念 定义
本地日历日 Intl.DateTimeFormat('en-CA', { timeZone: 'Asia/Shanghai' }) → "YYYY-MM-DD"
周起始 周一(startOfWeek 把周日映射到第 7 天)
周期窗口 day = [d, d];week = [周一, 周日];month = [1 号, 月末];year = [1/1, 12/31]
环形进度 percent = 周期内累计次数 / n,上限 1 —— 回答「这周期做了多少」
日均期望次数 day→n;week→ceil(n/7);month→ceil(n/当月天数);year→ceil(n/365) —— 回答「今天该做多少」
完成 done >= target(超额允许,over = done - target 继续累加)
streak 从当前周期向前回溯:达标 +1;当前周期未结束且未达标 → 跳过而非中断;已结束周期未达标 → 终止

两种口径并存是有意为之:环形看「周期整体」,日历日标记看「当天份额」。例如「每周 3 次」,周三打 1 次 → 环形 33.3%,当天已达标(1 ≥ ceil(3/7)=1)。

2.3 边界规则

场景 规则 编号
提前打卡(未来日期) 拒绝,返回 400「不能为未来日期打卡」 F-CK-11
补打 仅限最近 7 天,记录 source='backfill' F-CK-10
跨零点 以服务端 Asia/Shanghai 日历日为准,前端不自行判断 E-02
周期中途新建 按完整 n 计算,不按剩余天数折算 E-01
编辑 target 立即生效,历史记录不改动 F-CK-13
删除打卡项 默认归档(archived=true),?hard=1 物理删并清记录 E-05
清单删除 物理删(一次性场景不留历史) F-LS-10

3. 前端设计

3.1 路由与容器

底部 Tab (BottomNav)  打卡(默认) ─ 宏观 ─ 监控 ─ 我
  /                → <Navigate to="/checkin" replace />
/checkin  →  CheckinPage
               ├─ NavBar「打卡」
               └─ SwipeTabs(左右滑动切换,与既有模块交互一致)
                    ├─ habit    → HabitView    (受 date 影响)
                    ├─ calendar → CalendarView (持有选中 date,可回写容器)
                    └─ list     → ListView     (独立)
/macro/*  →  MacroPage (油价 / 经济数据 / 策略研究 / 经济文章 / 报警 / 新闻)

activeKeyFromPath() 对未匹配路径兜底返回第一个 Tab(/checkin),与默认路由保持一致。

date 状态提升到 CheckinPage:日历里点某天 → 切回打卡视图时即为该日,实现「日历与打卡联动」。

3.2 组件树与关键状态

CheckinPage
├── useState: active('habit'|'calendar'|'list'), date('YYYY-MM-DD')
HabitView(date)
├── data: { today, date, items[], summary:{total,done} }
├── bursts: { habitId: n }        // 特效触发计数(+1 即播放一次)
└── form / showForm               // 新建/编辑弹窗
CalendarView(date, onDateChange)
├── month, cal(月历), detail(当日), bursts
└── quickValue: -1|0|1|null       // 昨天/今天/明天 分段控件
ListView()
├── archived, lists, openId(展开的清单), newItem, templates, form/showForm
└── bursts

统一约定:

  • 加载中 → DotLoading;错误 → error-banner + 重试;空 → antd-mobile Empty + 引导按钮(三态齐全,U-06)。
  • 页面重新可见时静默刷新(useRefreshOnVisible),与既有模块一致。

3.3 环形进度组件 RingProgress

项 设计
实现 双 <circle>:灰底环 + stroke-dasharray/dashoffset 进度环,rotate(-90) 从 12 点顺时针
尺寸 size=58,stroke=6,r=26,周长 2πr≈163.4
动画 transition: stroke-dashoffset .45s cubic-bezier(.4,0,.2,1)(400ms 级,U-01)
中心内容 未达标 → done / target 数字;达标 → 对勾 SVG(ring-check-in 缩放入场);超额 → 右上角 +n 徽标
交互 整个环可点 = 打卡(clickable),同时卡片右侧保留显式「打卡」按钮,降低发现成本
无障碍/降级 disabled 态半透明;完成特效恒开(V0.2 设计变更:取消页面内特效开关,无任何关闭入口)

完成特效 Celebrate(F-CK-07 / U-02):fire 计数器 +1 即触发一次 → 14 个粒子沿圆周角度扩散(--a/--d CSS 变量)+ 主色光环脉冲 0.7s,总时长 ≤900ms,pointer-events:none 不阻塞后续操作;同时 navigator.vibrate(18) 轻震动(U-03)。特效永远播放,不再读 localStorage.iboard_celebrate,也不再响应 prefers-reduced-motion 关闭(用户明确要求「特效永远打开」)。

3.4 交互清单

视图 主要交互
打卡 点环/点按钮打卡(乐观更新,失败回滚 + Toast)、撤销、编辑、归档、新建弹窗(名称/周期/目标次数/颜色/备注)
日历 月份左右切换、回到今天、「昨天/今天/明天」分段切换、点日期选中、当日详情内打卡(未来日期只读显示「计划」)、本月完成率
清单 进行中/已归档切换、展开/收起、逐条勾选、添加检查项、全选/全不选、复制、存为模板、归档/恢复、删除、从模板新建

排序:打卡项列表未完成在前(F-CK-16);清单按 updatedAt 倒序。

3.5 视觉规范

类别 取值
主色 #1677ff;成功 #00b578;警告/部分 #ffc53d;超额 #a855f7;危险 #ff3141
背景 / 卡片 #f5f5f5 / #ffffff,卡片 radius 10px + 0 1px 2px rgba(0,0,0,.04)
文字 主 #333、次 #666、弱 #999
日历日状态点 full 绿 / partial 黄 / over 紫 / empty 无
打卡项预设色 #1677ff #00b578 #ff8f1f #ff3141 #a855f7 #14b8a6 #f43f9e

完整高保真稿见 界面设计.html。


4. 后端设计

4.1 通用约定

  • 两个路由文件首行 router.use(authRequired),全部接口需登录;用户维度一律取 req.user.sub,不信任 body 中的 userId。
  • 响应统一 { item } / { items } / { ok };错误统一 { error: '中文提示' } + 语义化状态码。
  • 展示态(percent / completed / streak / dayDone …)由服务端计算后下发,前端不做二次口径推导,避免两端不一致。

4.2 接口清单

打卡 /api/checkin

方法 路径 说明 关键响应字段
GET /habits?date= 指定日待办 + 周期进度 items[](含 done/target/percent/completed/over/streak/expectedPerDay/periodStart,End)、summary
POST /habits 新建打卡项 item
PUT /habits/:id 编辑(名称/周期/目标/颜色/备注/归档/排序) item
DELETE /habits/:id?hard=1 默认归档;hard=1 物理删 { ok, removed }
POST /habits/:id/check 打一次卡({ date?, note? }) item(最新展示态)
DELETE /habits/:id/check?date= 撤销本周期最近一次 item
GET /day?date= 某日每个打卡项明细 items[](含 canCheck)、isToday、isFuture
GET /calendar?month=YYYY-MM 月历 + 完成率 days[](status/expected/actual/isToday/isFuture/habits[])、rate、habits[]

清单 /api/checklists

方法 路径 说明
GET `/?archived=0 1`
POST / 新建({ title, scene, note?, items?, templateId? })
GET /:id 单条详情
PUT /:id 改名称/场景/备注/归档
DELETE /:id 物理删除(连带检查项)
POST /:id/duplicate 复制为新清单(勾选状态重置)
POST /:id/items 新增检查项
PUT /items/:id 改标题/备注/勾选(done 变更时写 doneAt)
DELETE /items/:id 删除检查项
POST /:id/check-all 一键全选/全不选({ done })
GET /templates 模板列表(含系统预置,首次访问幂等写入)
POST /templates 另存为模板
DELETE /templates/:id 删除自建模板(预置模板拒绝)

4.3 关键流程:打卡

用户点环
  │
  ├─ 前端: 本地 +1,重算 percent/completed/over(乐观更新)
  │
  └─ POST /api/checkin/habits/:id/check
        ├─ 校验 habit 归属 → 404
        ├─ date > today → 400 不能为未来日期打卡
        ├─ date < today-7 → 400 最多只能补打最近 7 天
        ├─ seq = max(seq)+1 → insert
        │    └─ P2002 → 409 该次打卡已记录(幂等)
        └─ 返回最新 item
              │
              ├─ 成功: 用服务端 item 覆盖本地;若 completed → fire 特效
              └─ 失败: setData(before) 回滚 + Toast

4.4 关键流程:月历聚合

  1. 取全部 Habit(含归档)→ 过滤 archived=false 得 active。
  2. 一次查询当月 [01, 月末] 全部 CheckIn,按 habitId → date → count 分组。
  3. 逐日遍历:仅统计 Habit.createdAt <= 该日 的项;累加 expected += expectedPerDay(...)、actual += min(count, expected);任一 count > expected 记 over。
  4. status:全完成且无超额 → full;全完成且有超额 → over;有完成未全完成 → partial;否则 empty。
  5. 完成率 rate = Σactual / Σexpected(只统计非未来日期)。

复杂度:O(天数 × 打卡项数),30×20=600 次内存运算,无 N+1 查询。

4.5 校验与错误码

状态码 场景
400 名称为空/超长、周期非法、target < 1、未来日期打卡、超出补打窗口、本周期无记录可撤销、模板缺条目
404 资源不存在或不属于当前用户
409 并发重复打卡(P2002)
401 未登录 / token 失效(authRequired)

所有写操作均用 prisma.xxx.findFirst({ where: { id, userId } }) 或 where: { id, list: { userId } } 做归属校验,杜绝越权改他人数据。


5. 非功能设计

维度 设计
首屏 三视图懒切换,只有激活视图发请求;单视图首屏 ≤ 1 次列表请求(N-01)
弱网/失败 乐观更新 + 失败回滚 + Toast;错误横幅带重试;useRefreshOnVisible 静默刷新
幂等 唯一约束 (habitId, date, seq) + 前端按钮 loading,防连点重复计次
时区 全链路 Asia/Shanghai;库内 DATETIME 存 UTC,业务日期用 VARCHAR(10) 本地日历日
兼容 375~430px 自适应;Tauri Android WebView 支持 navigator.vibrate,不支持时静默跳过
性能 月历/周期统计均为「一次范围查询 + 内存聚合」,避免逐项查询
安全 全接口 authRequired;输入长度截断(名称 20、检查项 40、备注 200);无 SQL 拼接(Prisma 参数化)
可维护 周期口径单点(server/checkin/period.js),前端 date.js 与之对齐,改动需同步两处(已在文档与注释中标注)

6. 测试与验证

层级 内容 结果
单元 周期工具 23 条断言(窗口边界、周一起始、月末、闰年、日均期望、streak 跳过未结束周期) 全通过
单元 版本比较 10 条断言(-ota 后缀、v 前缀、缺位补 0、0.10 > 0.9、beta.3 < beta.10、空串) 全通过
接口冒烟 自签 JWT 端到端:每天 1 次闭合、每周 3 次 33.3%、超额 +1、撤销归零、未来日期 400、月历 31 天、模板生成 11 项、全选完成、复制重置 全通过,测试数据已清理
构建 npm run build 通过
迁移 prisma migrate deploy 到目标库 成功

7. 里程碑(对应需求文档 M1~M5)

里程碑 内容 状态
M1 数据模型 + 迁移 + 周期模块 + 全部后端接口 完成
M2 打卡视图:环形进度、特效、撤销/补打、新建编辑 完成
M3 日历视图:月历 + 昨天/今天/明天 + 完成率 完成
M4 清单视图:CRUD、勾选、进度、模板复用 完成
M5 文档(需求/设计/数据库/界面)+ 构建校验 + 提交 完成

8. 风险与权衡

项 权衡 缓解
周期口径前后端各实现一份 前端需离线即时反馈,不能等接口 两端算法同源同注释;口径变更必须同步 period.js 与 date.js(已写入项目记忆)
进度实时聚合而非冗余计数 换取一致性与配置即时生效,牺牲少量 CPU 单用户数据量极小(月历 30 天 × 20 项),实测无感;V0.3 可加汇总表
未来日期禁止打卡 防止「提前打完一周」刷进度,符合「日历看明天=计划」语义 明天视图只读展示「计划」,明确文案
特效恒开可能打扰 用户明确要求「特效永远打开」,不再提供开关 单次特效 ≤900ms 且 pointer-events:none,不影响连续打卡;若后续需要再评估恢复开关
Tab 调整为 4 个(打卡置左并默认) 首页被取消,报警/新闻并入宏观后宏观有 6 个子 Tab 子 Tab 可横向滚动;报警角标保留在子 Tab 上;底部 4 项在 375px 下热区充足(≥44px)

9. 后续演进(V0.3+)

  1. 提醒与通知:Habit.remindAt + Tauri 本地通知,解决「忘打卡」。
  2. 补打放宽 / 历史编辑:当前 7 天限制可配置化。
  3. 统计视图:连续天数榜单、月/年热力图(届时引入 HabitDailyStat 汇总表)。
  4. 打卡项分组与排序拖拽:现有 sortOrder 已预留,仅需交互。
  5. 清单协作/分享:引入 CheckListShare。
  6. 数据导出:清单完成后导出为图片或文本(便于发群里确认)。

10. 设计变更记录(2026-10-22)

10.1 变更清单

# 类型 变更 影响文件
1 Bug OTA 包版本与分支不对应:dev-0.2 上仍是 0.1.0 → OTA 报成 0.1.0-ota。统一升到 0.2.0,OTA 包变为 0.2.0-ota,并加 CI 强校验 package.json、src-tauri/tauri.conf.json、src-tauri/Cargo.toml、Cargo.lock、scripts/ci/check-version.mjs、docker-build.yml
2 功能 我的页 OTA 组新增**「开发版本」Switch**(默认关),切换判新口径:开=build sequence,关=版本号字段比较;开关值持久化在设备端 src/api/ota.js、src/utils/version.js、src/pages/MePage.jsx
3 设计 打卡 Tab 移到最左边并默认选中;打卡页移除特效开关,特效永远打开 BottomNav.jsx、App.jsx、TabIcon.jsx、HabitView.jsx、RingProgress.jsx、globals.css
4 设计 取消「首页」Tab,其报警 / 新闻并入宏观,成为宏观的第 5、6 个子 Tab MacroPage.jsx、新增 macro/AlertsView.jsx、macro/NewsView.jsx、删除 HomePage.jsx

10.2 OTA 版本号为什么必须跟分支走

scripts/build-ota.mjs 里 manifest.version = package.json.version + '-ota',也就是版本号的唯一来源仍是 package.json。 分支按版本命名(dev-0.2),但版本号是手工改的,一旦忘改就会出现「分支 0.2、包版本 0.1」的错位,客户端判新逻辑全部失准。

环节 取值 现状
package.json version 0.2.0 唯一手动管理点
OTA latest.json.version 0.2.0-ota 打包脚本自动拼 -ota 后缀
src/version.js APP_VERSION 0.2.0 前端展示与版本比较的基准
OTA latest.json.min_binary_version 0.1.0 与前端版本解耦:前端 bundle 要求的最低原生二进制版本。现有 APK 原生层仍是 0.1.0,跟着抬会导致旧 APK 收不到更新,CI 固定传 --min-binary 0.1.0
CI 守卫 scripts/ci/check-version.mjs 从 GITHUB_REF_NAME 解析 dev-<major>.<minor>,要求 version 以该前缀开头,否则构建直接失败;main/master 跳过

10.3 判新口径:版本号 vs build sequence

「我的 → 前端升级 (OTA)」组内新增 开发版本 开关(antd-mobile Switch),默认关闭:

开关 判新依据 实现路径 适用场景
关(默认) 版本号字段大小 GET /api/ota/check/0 取 manifest.version → compareVersion(manifest.version, 当前运行版本) > 0 才算新 生产口径。旧分支被重新构建(sequence 变大但版本更小)时不会把设备拉回旧版
开 build sequence tauri-plugin-hotswap 的 checkUpdate()(内部拿本地 sequence 请求 /api/ota/check/:seq,服务端只回比它大的) 开发联调。每次构建 sequence 都变,保证任何一次提交都能推到设备上

补充约定:

  • 持久化:localStorage['iboard_ota_dev_mode'] = '1' | '0'(默认 '0')。值只存在设备端 WebView 存储里,不进数据库、不上报,换设备/清数据即回默认。
  • 当前运行版本:已应用过 OTA 覆盖包时取覆盖包 version(插件 getVersionInfo()),否则取内置包 APP_VERSION;避免「内置包 0.1.0 + 覆盖包 0.2.0」时永远判定有新版本。
  • 切换开关会重置检查结果(otaState='idle'、清空 otaInfo),避免上一种口径的结论误导用户。
  • Web 端:浏览器里拿不到本地 sequence。默认口径按版本号比较;开发口径退化为「服务端版本 ≠ 本地版本即算新」(近似 sequence 语义)。Web 端只提示刷新,不下载不替换,与原设计一致。

版本比较算法(src/utils/version.js,纯函数可单测):

parseVersionFields('0.2.0-ota')  // → [0, 2, 0]
compareVersion('0.2.0-ota','0.1.0')  // → 1  (0.2.0 更新)
compareVersion('0.2','0.2.0')        // → 0  (缺位补 0)

规则:按非数字字符切段 → 过滤空段 → 转数字 → 逐段比较,位数不足补 0。单测 10 条覆盖 -ota 后缀、v 前缀、多位段(0.10 > 0.9)、空串、beta.3 < beta.10,全部通过。

10.4 交互与信息架构变更

项 变更前 变更后
底部 Tab 首页 / 宏观 / 监控 / 打卡 / 我(5 个) 打卡 / 宏观 / 监控 / 我(4 个),打卡在最左
默认落地 / → 首页(默认选中新闻) / → /checkin;RedirectIfAuthed 与 404 兜底同样指向 /checkin
报警 / 新闻 首页内的 2 个 Tab 宏观的第 5、6 个子 Tab(/macro/alerts、/macro/news),报警角标保留
新闻详情 /news/:id(底部无 Tab 高亮) /macro/news/:id(宏观 Tab 保持高亮)
打卡页头部 [特效开/关] + [+ 新建] 只保留 [+ 新建];特效恒开
特效实现 effectsEnabled() 读 localStorage.iboard_celebrate + prefers-reduced-motion 降级 删除开关与降级判断,Celebrate 只要 fire 变化就播放

10.5 界面设计同步

界面设计.html 已同步:底部 Tab 改为 4 项且「打卡」高亮在首位、打卡页去掉特效开关按钮、新增「我的 · OTA 开发版本开关」屏、宏观新增「报警 / 新闻」子 Tab 与合并后的 6 Tab 导航。