diff --git a/docs/V0.2/数据库设计文档.md b/docs/V0.2/数据库设计文档.md
new file mode 100644
index 0000000..003a9d4
--- /dev/null
+++ b/docs/V0.2/数据库设计文档.md
@@ -0,0 +1,325 @@
+# iBoard V0.2 数据库设计文档
+
+| 项 | 内容 |
+| --- | --- |
+| 版本 | V0.2 |
+| 文档状态 | 已实现(与代码一致) |
+| 数据库 | MariaDB / MySQL 8(Prisma `provider = "mysql"`) |
+| 字符集 | `utf8mb4 / utf8mb4_unicode_ci` |
+| 时区约定 | 应用层统一 `Asia/Shanghai`,库内 `DATETIME` 存 UTC |
+| 相关文件 | `prisma/schema.prisma`、`prisma/migrations/20261022120000_add_checkin_checklist/migration.sql` |
+| 关联文档 | `需求文档.md`、`设计文档.md`、`界面设计.html` |
+
+---
+
+## 1. 概述
+
+V0.2 新增「打卡 / 日历 / 清单」三个功能域,共引入 **5 张表**:
+
+| 表 | 作用 | 归属域 |
+| --- | --- | --- |
+| `Habit` | 打卡项(每天/每周/每月/每年 n 次的目标定义) | 打卡 |
+| `CheckIn` | 打卡记录(一次打卡 = 一条) | 打卡 |
+| `CheckList` | 检查清单(出差/回老家等场景的一次性清单) | 清单 |
+| `CheckListItem` | 检查项(勾选即完成) | 清单 |
+| `CheckListTemplate` | 清单模板(系统预置 + 用户自定义) | 清单 |
+
+日历视图**不新增表**:完全由 `Habit` + `CheckIn` 按月聚合得出(见 §7.3)。
+
+### 1.1 设计原则
+
+1. **记录为真,进度不落库。** 环形进度、完成率、streak 一律由 `CheckIn` 现场聚合,不在 `Habit` 上冗余 `doneCount` 字段——避免并发计数不一致、以及用户中途把「每周 3 次」改成「每周 5 次」后需要回填历史的问题。
+2. **日期以本地日历日字符串存储。** `CheckIn.date` 用 `VARCHAR(10)` 存 `"YYYY-MM-DD"`(Asia/Shanghai 下的日历日),而不是 `DATETIME`:范围查询走索引、字典序等于时间序、天然幂等边界,且彻底规避「UTC 存 + 本地读」导致的跨零点归属错误。
+3. **软删除优先。** 打卡项删除默认走 `archived` 归档(保留历史统计),只有显式 `?hard=1` 才物理删除。
+4. **一切按 `userId` 隔离。** 所有业务表都带 `userId`,`Habit`/`CheckIn`/`CheckList`/`CheckListTemplate` 外键 `ON DELETE CASCADE` 到 `User`。
+5. **迁移只增不改。** 新增需求一律新迁移文件,不修改已执行的迁移(部署走 `prisma migrate deploy`)。
+
+---
+
+## 2. 概念模型(ER)
+
+```
+ ┌──────────┐
+ │ User │ (既有表, 本次仅新增反向关系)
+ └────┬─────┘
+ ┌───────────────┼────────────────────┬─────────────────┐
+ │ │ │ │
+ ┌────▼────┐ ┌────▼────┐ ┌─────▼─────┐ ┌──────▼──────┐
+ │ Habit │1───*│ CheckIn │ │ CheckList │1─* │CheckListItem│
+ └─────────┘ └─────────┘ └───────────┘ └─────────────┘
+ 打卡项 打卡记录 场景清单 检查项
+ (周期+目标n) (habitId,date,seq) (done, doneAt)
+ ▲
+ │ 生成(复制条目, 不建外键)
+ ┌─────┴──────────┐
+ │CheckListTemplate│
+ │ (builtin/自定义) │
+ └────────────────┘
+```
+
+基数说明:
+
+| 关系 | 基数 | 级联 |
+| --- | --- | --- |
+| User → Habit | 1 : N | Cascade |
+| User → CheckIn | 1 : N | Cascade |
+| Habit → CheckIn | 1 : N | Cascade |
+| User → CheckList | 1 : N | Cascade |
+| CheckList → CheckListItem | 1 : N | Cascade |
+| User → CheckListTemplate | 1 : N(可空,系统预置为空) | Cascade |
+| CheckListTemplate → CheckList | 无外键,创建时**复制**条目内容 | — |
+
+---
+
+## 3. 表结构
+
+### 3.1 `Habit` — 打卡项
+
+周期目标的**定义**表,不含任何计数。
+
+| 字段 | 类型 | 空 | 默认 | 说明 |
+| --- | --- | --- | --- | --- |
+| `id` | INT PK | 否 | AUTO_INCREMENT | 主键 |
+| `userId` | INT FK | 否 | — | 所属用户 |
+| `name` | VARCHAR(191) | 否 | — | 名称,接口校验 1~20 字 |
+| `period` | VARCHAR(191) | 否 | `'day'` | `day` \| `week` \| `month` \| `year` |
+| `target` | INT | 否 | `1` | 周期内目标次数 n(校验 ≥1) |
+| `color` | VARCHAR(191) | 否 | `'#1677ff'` | 环形与标记主色(7 色预设之一) |
+| `icon` | VARCHAR(191) | 否 | `''` | emoji / 图标名,**V0.2 预留不展示** |
+| `note` | VARCHAR(191) | 否 | `''` | 备注 |
+| `archived` | BOOLEAN | 否 | `false` | 归档后不出现在待办,历史保留 |
+| `sortOrder` | INT | 否 | `0` | 排序权重(升序) |
+| `createdAt` | DATETIME(3) | 否 | now() | 用于「创建之前的日期不计入月历期望」 |
+| `updatedAt` | DATETIME(3) | 否 | 自动 | — |
+
+索引:`INDEX(userId, archived, sortOrder)` — 覆盖「取当前用户未归档打卡项」主查询。
+
+### 3.2 `CheckIn` — 打卡记录
+
+**一次打卡 = 一条记录**,同日多次用 `seq` 递增。
+
+| 字段 | 类型 | 空 | 默认 | 说明 |
+| --- | --- | --- | --- | --- |
+| `id` | INT PK | 否 | AUTO_INCREMENT | 主键 |
+| `habitId` | INT FK | 否 | — | 所属打卡项 |
+| `userId` | INT FK | 否 | — | 冗余用户(用于单用户全量区间查询,避免 join) |
+| `date` | VARCHAR(10) | 否 | — | 本地日历日 `"YYYY-MM-DD"` |
+| `seq` | INT | 否 | `1` | 当日第几次(1..n,超额继续累加) |
+| `note` | VARCHAR(191) | 否 | `''` | 单次备注(接口截断 200 字) |
+| `source` | VARCHAR(191) | 否 | `'user'` | `user` \| `backfill`(补打) |
+| `createdAt` | DATETIME(3) | 否 | now() | 打卡时刻(UTC) |
+
+约束与索引:
+
+| 名称 | 类型 | 字段 | 作用 |
+| --- | --- | --- | --- |
+| `CheckIn_habitId_date_seq_key` | **UNIQUE** | `(habitId, date, seq)` | 并发重复提交的幂等防线(冲突 → P2002 → 409) |
+| `CheckIn_userId_date_idx` | INDEX | `(userId, date)` | 月历按月区间扫全量记录 |
+| `CheckIn_habitId_date_idx` | INDEX | `(habitId, date)` | 单打卡项周期区间聚合 |
+
+### 3.3 `CheckList` — 检查清单
+
+| 字段 | 类型 | 空 | 默认 | 说明 |
+| --- | --- | --- | --- | --- |
+| `id` | INT PK | 否 | AUTO_INCREMENT | 主键 |
+| `userId` | INT FK | 否 | — | 所属用户 |
+| `title` | VARCHAR(191) | 否 | — | 清单名,如「出差 · 杭州」 |
+| `scene` | VARCHAR(191) | 否 | `''` | 场景:`出差` \| `回老家` \| `其他` |
+| `note` | VARCHAR(191) | 否 | `''` | 备注 |
+| `archived` | BOOLEAN | 否 | `false` | 归档 |
+| `createdAt` / `updatedAt` | DATETIME(3) | 否 | — | 列表默认按 `updatedAt` 倒序 |
+
+索引:`INDEX(userId, archived)` — 对应「进行中 / 已归档」两个分段列表。
+
+### 3.4 `CheckListItem` — 检查项
+
+| 字段 | 类型 | 空 | 默认 | 说明 |
+| --- | --- | --- | --- | --- |
+| `id` | INT PK | 否 | AUTO_INCREMENT | 主键 |
+| `listId` | INT FK | 否 | — | 所属清单 |
+| `title` | VARCHAR(191) | 否 | — | 条目名(校验 ≤40 字) |
+| `note` | VARCHAR(191) | 否 | `''` | 条目备注 |
+| `done` | BOOLEAN | 否 | `false` | 是否已完成 |
+| `doneAt` | DATETIME(3) | **是** | NULL | 勾选时刻;取消勾选置回 NULL |
+| `sortOrder` | INT | 否 | `0` | 排序(升序,同值按 id) |
+| `createdAt` / `updatedAt` | DATETIME(3) | 否 | — | — |
+
+索引:`INDEX(listId, sortOrder)`。
+
+### 3.5 `CheckListTemplate` — 清单模板
+
+| 字段 | 类型 | 空 | 默认 | 说明 |
+| --- | --- | --- | --- | --- |
+| `id` | INT PK | 否 | AUTO_INCREMENT | 主键 |
+| `userId` | INT FK | **是** | NULL | NULL = 系统预置(`builtin=true`) |
+| `name` | VARCHAR(191) | 否 | — | 模板名 |
+| `scene` | VARCHAR(191) | 否 | `''` | 场景标签 |
+| `items` | **JSON** | 否 | — | `[{ title, note }]`,创建清单时展开为 `CheckListItem` |
+| `builtin` | BOOLEAN | 否 | `false` | 系统预置:只读、不可删(接口层限制) |
+| `createdAt` / `updatedAt` | DATETIME(3) | 否 | — | — |
+
+索引:`INDEX(userId, builtin)` — 查询条件为 `builtin=true OR userId=me`。
+
+系统预置数据(首次访问 `GET /api/checklists/templates` 时**幂等写入**):
+
+| 模板 | 条目数 | 条目 |
+| --- | --- | --- |
+| 出差 | 11 | 身份证/证件、手机充电器+充电宝、笔记本电脑+电源、耳机、换洗衣物、洗漱包、常用药、雨伞、车票/机票/酒店确认、工牌/名片、报销单据 |
+| 回老家 | 12 | 身份证/证件、钥匙、手机充电器+充电宝、换洗衣物、洗漱包、常用药、给家人的礼物/伴手礼、长辈的药品/营养品、口罩、现金零钱、车票/加油/行程确认、家里门窗水电检查 |
+
+---
+
+## 4. 外键与级联
+
+| 子表.字段 | 父表.字段 | ON DELETE | ON UPDATE |
+| --- | --- | --- | --- |
+| `Habit.userId` | `User.id` | CASCADE | CASCADE |
+| `CheckIn.habitId` | `Habit.id` | CASCADE | CASCADE |
+| `CheckIn.userId` | `User.id` | CASCADE | CASCADE |
+| `CheckList.userId` | `User.id` | CASCADE | CASCADE |
+| `CheckListItem.listId` | `CheckList.id` | CASCADE | CASCADE |
+| `CheckListTemplate.userId` | `User.id` | CASCADE | CASCADE |
+
+删除链路:删用户 → 级联清 Habit/CheckIn/CheckList/Template;删 Habit → 清其全部 CheckIn;删 CheckList → 清其全部 Item。业务层删打卡项默认只置 `archived=true`,物理删需 `?hard=1`(此时先 `deleteMany` 记录再删主表)。
+
+---
+
+## 5. 关键设计决策
+
+### 5.1 为什么 `date` 用 `VARCHAR(10)` 而不是 `DATETIME`
+
+| 维度 | VARCHAR(10) 本地日历日 | DATETIME(UTC) |
+| --- | --- | --- |
+| 跨零点归属 | 明确:由应用层按 Asia/Shanghai 计算一次,落库即定 | 读时需要再转时区,易错 |
+| 范围查询 | `date >= '2026-10-01' AND date <= '2026-10-31'` 直接走索引 | 需函数/边界换算,易失效 |
+| 幂等边界 | `UNIQUE(habitId, date, seq)` 天然按「日」收敛 | 需按日截取 |
+| 字典序 | 等于时间序,可安全字符串比较 | — |
+| 存储 | 10 字节 | 5 字节(略优,但不足以抵消上述成本) |
+
+代价:跨时区展示需自行换算——本项目全链路 Asia/Shanghai,不成为问题。
+
+### 5.2 为什么不冗余 `doneCount` / `streak`
+
+- **并发**:计数需事务 + 行锁,聚合查询天然一致。
+- **改配置**:用户把「每周 3 次」改成「每周 5 次」,冗余计数立刻失真,需要全量重算;现方案改 `target` 立即生效。
+- **撤销**:撤销即删一条记录,无需维护计数器。
+- **成本**:单个用户打卡项数量在 10~30 量级,月历查询一次拉取整月记录(≤ 数百行)在内存聚合,开销可接受。
+
+### 5.3 `seq` 与幂等
+
+同日多次打卡用 `seq = max(seq)+1`。唯一约束 `(habitId, date, seq)` 使并发重复提交必然撞 P2002,服务端转 409「该次打卡已记录」,前端幂等收敛,不会出现「点两下加两次」的体验事故。
+
+### 5.4 归档 vs 物理删除
+
+| 操作 | 语义 | 数据影响 |
+| --- | --- | --- |
+| `DELETE /api/checkin/habits/:id` | 归档(默认) | `archived=true`,记录全保留 |
+| `DELETE /api/checkin/habits/:id?hard=1` | 物理删除 | 先删 CheckIn,再删 Habit |
+| `DELETE /api/checklists/:id` | 物理删除(一次性清单,不留历史) | 先删 Item,再删 List |
+
+理由:打卡是长期行为数据,历史构成 streak 与完成率;清单是短期事务,用完即归档/删除。
+
+### 5.5 模板用 JSON 而非明细表
+
+模板条目结构固定(`title` + `note`)、只在「创建清单」时整体读取并展开、不需要按条目检索或统计 —— 用 `JSON` 列避免额外的 `TemplateItem` 表与 join。展开后落到 `CheckListItem`,之后模板改动不影响已生成清单。
+
+---
+
+## 6. 数据量估算
+
+单用户规模假设:打卡项 20 个,平均每天打卡 25 次;清单每月 4 份,每份 12 条。
+
+| 表 | 年增量(单用户) | 行大小(约) | 年占用(约) |
+| --- | --- | --- | --- |
+| `CheckIn` | 25 × 365 ≈ 9,100 | ~80 B | ~0.7 MB |
+| `CheckListItem` | 4 × 12 × 12 = 576 | ~90 B | ~0.05 MB |
+| `Habit` / `CheckList` / `CheckListTemplate` | < 100 | ~200 B | 忽略 |
+
+结论:**无需分区、无需归档策略**。10 用户 × 5 年 ≈ 45 万行 CheckIn,仍远低于单表瓶颈。若未来接入多租户或做年视图长周期统计,再考虑按月汇总表。
+
+---
+
+## 7. 典型查询
+
+### 7.1 今日待办 + 周期进度
+
+```sql
+-- 步骤 1: 取未归档打卡项 (走 Habit_userId_archived_sortOrder_idx)
+SELECT id, name, period, target, color FROM Habit
+WHERE userId = ? AND archived = 0 ORDER BY sortOrder, id;
+
+-- 步骤 2: 取覆盖所有周期窗口的最小区间内的记录 (走 CheckIn_userId_date_idx)
+SELECT habitId, date FROM CheckIn
+WHERE userId = ? AND date >= ? AND date <= ?; -- 如 week: '2026-10-19' ~ '2026-10-25'
+-- 步骤 3: 内存按 habitId/date 分组 -> 计算 done/percent/streak
+```
+
+### 7.2 打一次卡
+
+```sql
+SELECT MAX(seq) FROM CheckIn WHERE habitId = ? AND date = ?; -- -> seq
+INSERT INTO CheckIn (habitId, userId, date, seq, note, source) VALUES (?, ?, ?, ?, ?, 'user');
+-- 冲突 -> P2002 -> 409
+```
+
+撤销(撤销本周期最近一次):
+
+```sql
+SELECT id FROM CheckIn WHERE habitId = ? AND date BETWEEN ? AND ?
+ORDER BY date DESC, seq DESC LIMIT 1;
+DELETE FROM CheckIn WHERE id = ?;
+```
+
+### 7.3 月历(日历视图数据来源)
+
+```sql
+SELECT habitId, date FROM CheckIn
+WHERE userId = ? AND date >= '2026-10-01' AND date <= '2026-10-31';
+```
+
+按天聚合后与 `Habit.expectedPerDay` 比较得出日状态:
+
+| 状态 | 条件 | 展示 |
+| --- | --- | --- |
+| `full` | 全部打卡项当日次数 ≥ 日均期望 | 绿点 |
+| `over` | 有任一打卡项当日次数 > 日均期望 | 紫点 |
+| `partial` | 有完成但未全完成 | 黄点 |
+| `empty` | 无记录或当天无打卡项 | 无点 |
+
+`Habit.createdAt` 早于该日的才计入期望,避免新建打卡项把历史日期标成「未完成」。
+
+### 7.4 清单列表(带进度)
+
+```sql
+SELECT l.*, i.* FROM CheckList l
+LEFT JOIN CheckListItem i ON i.listId = l.id
+WHERE l.userId = ? AND l.archived = ?
+ORDER BY l.updatedAt DESC, i.sortOrder, i.id;
+```
+
+---
+
+## 8. 迁移
+
+| 项 | 内容 |
+| --- | --- |
+| 迁移目录 | `prisma/migrations/20261022120000_add_checkin_checklist/` |
+| 内容 | 5 张 `CREATE TABLE` + 6 条 `ADD CONSTRAINT`(见 `migration.sql`) |
+| 生成方式 | 手写 SQL(与 `prisma migrate diff` 产出格式一致),已通过 `prisma migrate deploy` 验证 |
+| 部署命令 | `npx prisma migrate deploy && npx prisma generate` |
+| 回滚 | 无自动回滚。应急:`DROP TABLE CheckIn, CheckListItem, CheckList, CheckListTemplate, Habit;`(注意外键顺序)并把 `_prisma_migrations` 中该记录删除 |
+
+>V0.2 全为新增表与新增字段(`User` 侧仅新增 Prisma 反向关系,不产生 DDL),对既有数据零影响。
+
+---
+
+## 9. V0.3 预留
+
+| 方向 | 可能的表变更 |
+| --- | --- |
+| 打卡提醒 | `Habit` 增加 `remindAt TIME`、`remindDays` |
+| 打卡统计快照 | 新增 `HabitDailyStat(userId, date, done, expected)` 汇总表,用于年视图/热力图加速 |
+| 清单共享协作 | 新增 `CheckListShare(listId, userId, role)` |
+| 模板市场 | `CheckListTemplate` 增加 `visibility`、`useCount` |
+
+以上均可通过「新增表 / 新增可空字段」完成,不影响 V0.2 数据结构。
diff --git a/docs/V0.2/界面设计.html b/docs/V0.2/界面设计.html
new file mode 100644
index 0000000..0ad34c2
--- /dev/null
+++ b/docs/V0.2/界面设计.html
@@ -0,0 +1,889 @@
+
+
+
+
+
+iBoard V0.2 界面设计 · 打卡 / 日历 / 清单
+
+
+
+
+
+
+
iBoard V0.2 · 界面设计
+
打卡 / 日历 / 清单 —— 移动端静态高保真原型(设计基线 420 × 820,适配 375~430px)
+
+ 版本 V0.2分支 dev-0.2antd-mobile 风格
+ 底部 5 Tab(新增「打卡」)时区 Asia/Shanghai文档日期 2026-10-22
+
+
+
+
+
1 设计规范Design Tokens
+
沿用项目既有 :root 变量(src/styles/globals.css),本模块不引入新主题。
+
+
+
语义色
+
+
#1677ff主色 / 今日 / 链接
+
#00b578完成 / 进度条
+
#ffc53d部分完成 / 粒子
+
#a855f7超额完成
+
#ff8f1f连续天数 / 超额徽标
+
#ff3141危险操作
+
#f5f5f5页面底色
+
#f7fbf8已完成卡片底
+
+
+ 日历·全部完成
+ 日历·部分完成
+ 日历·超额
+
+
+
+
布局与圆角
+
+ 视口420 × 820(375~430 自适应)
+ NavBar 高45px
+ TabBar 高72px(含安全区)
+ 页面内边距左右 12px
+ 卡片圆角10px
+ 卡片间距10px 纵向
+ 卡片阴影0 1px 2px rgba(0,0,0,.04)
+ 环形尺寸58 × 58,环宽 6px
+ 最小点按区≥ 40 × 40
+
+
+
+
字号层级
+
18 / 700日期标题「10月22日」
+
17 / 600NavBar 标题「打卡」
+
16 / 700环形中心数字 / 弹窗标题
+
15 / 600打卡项名 / 清单标题
+
13 / 400正文 / 日历日期数字
+
12 / 400次要信息 / 操作链接
+
11 / 400备注 / 周期范围 / 徽标
+
+
+
+
+
2 主界面三个子视图(SwipeTabs 左右滑动切换)
+
+
+
+
+
+
+
打卡
+
+
+
+
10月22日 周四
+
已完成 2 / 4
+
+
+
+
+
+
+
+
+
+
喝水
+
每天 2 次连续 12
+
今日
+
+
+
+
+
+
+
+
+
+
健身
+
每周 3 次连续 5
+
10-19 ~ 10-25
+
+
+
+
+
+
+
+
+
+
读书
+
每月 12 次连续 3
+
10-01 ~ 10-31
+
+
+
+
+
+
+
+
+
+
吃药
+
每天 1 次连续 30
+
今日超额 1
+
+
+
+
+
+
+
+
+
+
2.1 打卡视图 · 今天默认进入
+
头部日期 + 完成数;未完成卡片排在前面。环形中心未达标显示 done/target,达标显示对勾(每天 1 次打完即刻闭合);超额在右上角显示 +n。点环或「打卡」按钮均可打卡,卡片底部提供撤销 / 编辑 / 归档。
+
+
+
+
+
+
+
打卡
+
+
+
+ ‹2026年10月›
+ 回到今天
+
+
+
+
+
一二三四五六日
+
+
28
+
29
+
30
+
1
+
2
+
3
+
4
+
+
5
+
6
+
7
+
8
+
9
+
10
+
11
+
+
12
+
13
+
14
+
15
+
16
+
17
+
18
+
+
19
+
20
+
21
+
22
+
23
+
24
+
25
+
+
26
+
27
+
28
+
29
+
30
+
31
+
1
+
+
+
+
本月完成率 82%
+
今天 · 2026-10-22
+
+
+ 喝水
+ 每天 2 次 · 当日 2/2
+ 已达标
+
+
+ 健身
+ 每周 3 次 · 当日 0/1
+ 打卡
+
+
+ 读书
+ 每月 12 次 · 当日 1/1
+ 已达标
+
+
+ 吃药
+ 每天 1 次 · 当日 2/1
+ 已达标
+
+
+
+
+
+
2.2 日历视图 · 今天选中
+
周一起始月历 + 「昨天 / 今天 / 明天」快捷分段;日期下方状态点:绿=全部完成、黄=部分完成、紫=超额、无点=未完成或未来。今天用主色数字并选中高亮,未来日期数字弱化。下方为该日明细,可直接打卡;底部展示本月完成率。
+
+
+
+
+
+
+
打卡
+
+
+
+
+
+
出差 · 杭州出差
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 全选全不选复制
+ 存为模板归档删除
+
+
+
+
+
+
+
+
+
+
2.3 清单视图 · 展开态
+
卡片头部常显标题、场景标签、进度条与 8/12;点击展开逐条勾选(勾选后绿色圆底、文字加删除线并弱化)。底部可继续添加检查项,并提供全选 / 全不选 / 复制 / 存为模板 / 归档 / 删除。收起态只保留头部一行。
+
+
+
+
+
+
+
打卡
+
+
+
+
10月22日 周四
+
已完成 2 / 4
+
+
+
+
+
+
+
+
新建打卡项
+
+
+
+
+
+
保存
+
取消
+
+
+
+
+
2.4 新建 / 编辑打卡项(底部弹层)
+
名称 ≤20 字;周期四选一(每天 / 每周 / 每月 / 每年);目标次数用 Stepper,最小 1;颜色 7 色预设;备注可选。编辑态标题变为「编辑打卡项」并回填当前值。
+
+
+
+
+
+
+
打卡
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
2.5 达标瞬间 · 完成特效
+
环形闭合 + 对勾缩放入场(0.32s);同时 14 颗粒子沿圆周扩散(26~58px,交替主色与金色)+ 主色光环脉冲(0.7s),总时长 ≤900ms,pointer-events:none 不阻塞后续操作;并触发 18ms 轻震动。可在头部一键关闭(持久化开关),系统「减弱动效」时自动降级。
+
+
+
+
+
+
+
打卡
+
+
+
‹2026年10月›回到今天
+
+
+
一二三四五六日
+
+
28
29
30
+
1
+
2
+
3
+
4
+
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
13
+
14
+
15
+
16
+
17
+
18
+
19
+
20
+
21
+
22
+
23
+
24
+
25
+
26
27
+
28
29
+
30
31
+
1
+
+
+
本月完成率 82%
+
明天 · 2026-10-23计划(不可打卡)
+
喝水每天 2 次 · 当日 0/2待办
+
健身每周 3 次 · 当日 0/1待办
+
读书每月 12 次 · 当日 0/1待办
+
吃药每天 1 次 · 当日 0/1待办
+
+
+
+
+
2.6 日历 · 明天(计划,只读)
+
未来日期不允许打卡:标题右侧显示「计划(不可打卡)」徽标,条目右侧只显示「待办」文案而不渲染按钮,月历上未来日期数字弱化且不打点。昨天视图则相反——可补打最近 7 天内的漏打卡。
+
+
+
+
+
+
+
+
2.7 清单全部完成(12/12)
+
进度条 100%、计数变为 12/12、卡片底色转 #f7fbf8,并播放与打卡一致的完成特效。清单完成后用户通常选择「归档」或「复制」复用。
+
+
+
+
+
+
+
打卡
+
+
+
+
○
+
还没有打卡项
+
创建第一个打卡项
+
+
+
+
+
+
2.8 空态
+
三个视图均具备加载中 / 空态 / 错误(带重试)三态。空态给出明确引导按钮;头部「特效开 / 特效关」为全局动效开关,关闭后仅保留环形与对勾,不播放粒子与震动。
+
+
+
+
+
+
+
3 组件状态定义Component States
+
+
3.1 环形进度(尺寸 58,环宽 6,r=26,周长 163.4)
+
+ | 状态 | 条件 | 环形 | 中心 | 卡片底 | 按钮 |
+ | 未开始 | done = 0 | 仅灰底环 | 0/2 | 白 | 打卡(主色) |
+ | 进行中 | 0 < done < target | 按 done/target 填充(45°…330°) | 1/3 | 白 | 打卡(主色) |
+ | 已达标 | done = target | 闭合整圈 | 对勾(主色) | #f7fbf8 | 再打一次(灰) |
+ | 超额 | done > target | 闭合 + 右上角 +n 橙标 | 对勾 | #f7fbf8 | 再打一次(灰) |
+ | 每日 1 次达标 | target = 1 且已打卡 | 直接整圈闭合,不做渐增生长期 | 对勾 | #f7fbf8 | 再打一次 |
+
+
环形进度 = 周期完成度(done / n);日历日标记 = 当日份额(当日次数 / 日均期望),两者口径不同,互不影响。
+
+
3.2 日历日期
+
+ | 状态 | 判定 | 视觉 |
+ 全部完成 full | 所有打卡项当日次数 ≥ 日均期望,且无超额 | 绿色圆点 |
+ 超额 over | 全部达标且存在任一打卡项当日次数 > 期望 | 紫色圆点 |
+ 部分完成 partial | 有完成但未全部完成 | 黄色圆点 |
+ 无 empty | 无记录 / 当天无打卡项 / 未来日期 | 不显示圆点 |
+ | 今天 | date = today | 数字主色加粗 |
+ | 选中 | 用户点击 | 主色实心底,数字与圆点转白 |
+ | 非本月 / 未来 | 月份不符 / date > today | 数字弱化(#c8c8c8 / #b0b0b0) |
+
+
+
3.3 清单检查项
+
+ | 状态 | 勾选框 | 文字 | 触发 |
+ | 未完成 | 1.5px 灰边圆圈,对勾透明 | #333 | 点击圆圈或文字切换 |
+ | 已完成 | 绿底绿边,白色对勾显现(0.2s) | #a0a0a0 + 删除线 | 同上,写 doneAt |
+ | 整单完成 | — | 进度条 100%、卡片转绿 | 最后一项勾选时播放特效 |
+
+
+
+
4 交互与动效Motion
+
+ | 动效 | 时长 | 曲线 / 实现 | 说明 |
+ | 环形增长 | 450ms | cubic-bezier(.4,0,.2,1),stroke-dashoffset 过渡 | 12 点方向顺时针增长 |
+ | 对勾入场 | 320ms | scale(.4) → 1 + 透明度 | 达标瞬间出现 |
+ | 完成粒子 | ≤900ms | 14 颗粒子按角度 --a、距离 --d 扩散,交错 40ms 延迟 | 不阻塞点击 |
+ | 光环脉冲 | 700ms | 主色圆环放大淡出 | 与粒子同时开始 |
+ | 触感反馈 | 18ms | navigator.vibrate(18) | WebView 不支持则静默跳过 |
+ | 进度条填充 | 400ms | width 过渡 | 清单勾选后更新 |
+ | 视图切换 | — | SwipeTabs 跟手横滑 | 与既有模块一致 |
+
+
+
4.1 打卡时序
+
用户点环 / 点「打卡」
+ └─ 前端乐观更新:done+1,重算 percent / completed / over,环形立即增长
+ └─ POST /api/checkin/habits/:id/check
+ ├─ 200 → 用服务端 item 覆盖本地;若 completed → 触发特效 + 震动
+ ├─ 400 → 回滚 + Toast(未来日期 / 超出 7 天补打窗口)
+ ├─ 409 → 视为已打卡,刷新一次(幂等)
+ └─ 网络失败 → 回滚 + Toast「打卡失败」
+
+
+
5 适配与可达性
+
+ | 项 | 约定 |
+ | 视口 | 设计稿 420×820;375~430px 内元素不换行、不裁切;底部 TabBar 5 项每项 ≥ 40px 可点宽度 |
+ | 安全区 | TabBar 使用 antd-mobile safeArea,页面底部留出 tabbar-h + safe-bottom |
+ | 减弱动效 | 系统开启「减弱动效」或用户关闭特效开关 → 不播放粒子 / 光环 / 震动,只保留环形与对勾 |
+ | 大字号 | 卡片采用 grid 自适应列宽,文字超长换行不截断;名称限制 20 字、检查项 40 字 |
+ | 深色模式 | V0.2 沿用现有浅色主题,暂不适配 |
+
+
+
本文档为静态设计稿,不含脚本逻辑;实现对应 src/pages/checkin/*.jsx、src/components/RingProgress.jsx 与样式段(src/styles/globals.css)。接口与数据口径见《设计文档.md》与《数据库设计文档.md》。
+
+
+
+
diff --git a/docs/V0.2/设计文档.md b/docs/V0.2/设计文档.md
new file mode 100644
index 0000000..3ea15dd
--- /dev/null
+++ b/docs/V0.2/设计文档.md
@@ -0,0 +1,338 @@
+# iBoard V0.2 设计文档 — 打卡 / 日历 / 清单
+
+| 项 | 内容 |
+| --- | --- |
+| 版本 | V0.2(对应分支 `dev-0.2`) |
+| 文档状态 | 已实现(与代码一致) |
+| 上游文档 | `需求文档.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(首页 / 宏观 / 监控 / 我)之上新增**第 5 个底部 Tab「打卡」**,页面内以三个子视图承载三类需求:
+
+| 子视图 | 解决问题 | 核心隐喻 |
+| --- | --- | --- |
+| **打卡** | 周期性习惯的量化坚持 | 环形进度,点环即打卡 |
+| **日历** | 回看昨天、执行今天、预知明天 | 月历网格 + 日状态点 |
+| **清单** | 出差/回老家等一次性场景的漏项检查 | 勾选清单 + 进度条 |
+
+**不纳入 V0.2**:多用户共享清单、提醒推送、数据导出、打卡项分组、年视图热力图(见 §9 演进)。
+
+### 1.2 架构总览
+
+```
+┌──────────────────────── 前端 (React + antd-mobile, 打包进 Tauri / OTA) ────────────────────────┐
+│ App.jsx 路由 │
+│ └ /checkin ──► CheckinPage ── SwipeTabs ──┬── HabitView (打卡) │
+│ ├── CalendarView (日历) │
+│ └── ListView (清单) │
+│ 组件: 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
+ components/RingProgress.jsx # 环形进度 + Celebrate 特效
+ components/BottomNav.jsx # 新增「打卡」Tab
+ components/TabIcon.jsx # 新增 checkin 图标
+ pages/checkin/CheckinPage.jsx # 容器:三视图切换
+ pages/checkin/HabitView.jsx # 打卡视图
+ pages/checkin/CalendarView.jsx # 日历视图
+ pages/checkin/ListView.jsx # 清单视图
+ pages/checkin/date.js # 前端日期工具
+ App.jsx # 新增 /checkin 路由
+ styles/globals.css # 打卡相关样式段
+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 路由与容器
+
+```
+/checkin → CheckinPage
+ ├─ NavBar「打卡」
+ └─ SwipeTabs(左右滑动切换,与既有模块交互一致)
+ ├─ habit → HabitView (受 date 影响)
+ ├─ calendar → CalendarView (持有选中 date,可回写容器)
+ └─ list → ListView (独立)
+```
+
+`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 // 新建/编辑弹窗
+└── effects: boolean // 特效开关(持久化 localStorage)
+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`
+
+| 项 | 设计 |
+| --- | --- |
+| 实现 | 双 ``:灰底环 + `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` 态半透明;系统「减弱动效」时特效自动关闭 |
+
+**完成特效 `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` 自动降级(U-07)。
+
+### 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` | 清单列表(带 `done/total/percent/completed`) |
+| 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 跳过未结束周期) | 全通过 |
+| 接口冒烟 | 自签 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 可加汇总表 |
+| 未来日期禁止打卡 | 防止「提前打完一周」刷进度,符合「日历看明天=计划」语义 | 明天视图只读展示「计划」,明确文案 |
+| 特效可能打扰 | 部分用户不喜动效 | 一键开关 + `prefers-reduced-motion` 自动降级 |
+| 新增第 5 个 Tab | 底部空间变拥挤 | 图标线性简洁;`TabBar` 原生 safe-area,5 项在 375px 下仍在可点区(≥44px) |
+
+---
+
+## 9. 后续演进(V0.3+)
+
+1. **提醒与通知**:`Habit.remindAt` + Tauri 本地通知,解决「忘打卡」。
+2. **补打放宽 / 历史编辑**:当前 7 天限制可配置化。
+3. **统计视图**:连续天数榜单、月/年热力图(届时引入 `HabitDailyStat` 汇总表)。
+4. **打卡项分组与排序拖拽**:现有 `sortOrder` 已预留,仅需交互。
+5. **清单协作/分享**:引入 `CheckListShare`。
+6. **数据导出**:清单完成后导出为图片或文本(便于发群里确认)。