feat: 个人轻量看板工具 MVP
从零实现一个 local-first 的个人任务看板,数据只存在浏览器本地,不依赖后端。 功能 - 固定三列看板(待办 / 进行中 / 已完成),支持拖拽改状态与列内排序 - 快速新增、详情编辑、删除撤销、已完成归档 - 搜索与筛选(关键词 / 优先级 / 标签 / 今日到期 / 逾期) - 今日视图:按紧急度分组(逾期 > 今天到期 > 进行中)+ 统计卡片 - 导出导入 JSON;启动时自动留快照,保留最近 3 份 - 键盘快捷键(N / 「/」/ ? / Esc) - PWA:可安装、离线可用、更新提示 技术要点 - 持久化采用 TaskRepository 端口 + 整份快照读写,schemaVersion 迁移表 显式登记升级规则(当前 v2,v1 -> v2 为归档功能补 archivedAt) - 同列排序用相邻中点插入,间距不足时按列 normalize,避免浮点精度耗尽 - 拖拽只消费 Sortable 的事件,store 保持唯一数据源,不接管数据 - 数据损坏时先备份到 *.corrupt.<时间戳> 再报错,不覆盖用户数据 - 引入 vue-router 拆分看板与今日两个视图(见 docs/adr.md ADR-005) 验证 - 单元测试 437 个(33 个文件),行覆盖率 96.8% - 端到端测试 23 个(Playwright),含真实拖拽与断网离线场景 - npm run build 通过,产物可静态托管 注:部署时需配置 SPA 回落,否则直接访问 /today 会 404(详见 README)。
This commit is contained in:
+112
@@ -0,0 +1,112 @@
|
||||
# 架构决策记录(ADR)
|
||||
|
||||
本文件记录个人看板工具 MVP 的关键技术决策。每条包含:背景、结论、被否决的方案及原因。
|
||||
|
||||
---
|
||||
|
||||
## ADR-001 · 看板列固定为三列,不支持配置
|
||||
|
||||
**背景**
|
||||
初版设计在设置页提供「列配置」,但数据模型中 `status` 为固定枚举 `todo | doing | done`,两者互相矛盾。
|
||||
|
||||
**结论**
|
||||
MVP 固定三列:`todo`(待办)/ `doing`(进行中)/ `done`(已完成)。`status` 使用 TypeScript 字面量联合类型,**不使用 `enum`**(同时满足 `erasableSyntaxOnly` 编译约束)。
|
||||
|
||||
**被否决的方案**
|
||||
- *引入 `columns` 表 + `task.columnId`*:需要额外的列 CRUD、列删除时的任务迁移、列顺序持久化。对单人任务管理而言,收益为零,成本约为原方案的两倍。
|
||||
- *用 `enum` 定义列*:与 `erasableSyntaxOnly: true` 冲突,且 `enum` 在纯类型场景下没有优势。
|
||||
|
||||
**影响**
|
||||
若未来确需自定义列,需一次性迁移 `status` → `columnId`,并在 `schemaVersion` 中登记。
|
||||
|
||||
---
|
||||
|
||||
## ADR-002 · 存储采用 `TaskRepository` 接口 + localStorage 实现
|
||||
|
||||
**背景**
|
||||
初版方案直接选用 Dexie(IndexedDB)。但个人看板在 200 个任务规模下,数据量约 60KB,远低于 localStorage 5MB 上限。
|
||||
|
||||
**结论**
|
||||
定义 `TaskRepository` 接口(`list / get / create / update / remove / replaceAll`),MVP 只提供 localStorage 实现。UI 与 Store 仅依赖接口,不感知具体实现。
|
||||
|
||||
**被否决的方案**
|
||||
- *直接用 Dexie*:收益是索引查询与大容量,在该规模下用不上;代价是 Pinia 与 DB 双份状态、异步竞态、多标签页同步、测试需引入 `fake-indexeddb`,约多花 1.5 人天。
|
||||
- *直接在组件里读写 localStorage*:数据访问散落各处,无法测试,也无法替换实现。
|
||||
|
||||
**影响**
|
||||
数据量增长到万级或需要复杂查询时,新增一个 Dexie 实现并切换注入即可,接口不变。
|
||||
|
||||
---
|
||||
|
||||
## ADR-003 · `dueDate` 只表示日期,不表示时刻
|
||||
|
||||
**背景**
|
||||
`dueDate` 若同时承载日期与时刻,需要处理时区转换(本机为 Asia/Hong_Kong),并会在「今天到期」的判定上产生歧义。
|
||||
|
||||
**结论**
|
||||
`dueDate` 类型为 `string | null`,格式固定 `YYYY-MM-DD`,不含时刻与时区信息。「是否逾期」以本地日历日比较得出。
|
||||
|
||||
**被否决的方案**
|
||||
- *存储 ISO 8601 完整时间戳*:需要引入时区处理,且个人看板不需要精确到分钟的截止时间。
|
||||
- *存储 `Date` 对象*:JSON 序列化后仍会退化为字符串,且容易在反序列化时引入时区偏移。
|
||||
|
||||
**影响**
|
||||
若未来需要提醒功能,应新增独立字段(如 `remindAt`),而不是改变 `dueDate` 的语义。
|
||||
|
||||
---
|
||||
|
||||
## ADR-004 · 不引入 vue-router
|
||||
|
||||
> **状态:已被 ADR-005 取代。** 本决策在 MVP 阶段成立;当「今日视图」真的落地后,
|
||||
> 它预判的触发条件("若未来增加今日视图、统计等真正的独立页面")已经满足。
|
||||
|
||||
**背景**
|
||||
初版信息架构包含「主看板页」「任务详情」「设置页」三个页面。
|
||||
|
||||
**结论**
|
||||
MVP 为单视图应用:主看板常驻,任务详情使用 Drawer,导入导出与快捷键说明使用 Modal。不引入 `vue-router`。
|
||||
|
||||
**被否决的方案**
|
||||
- *引入 vue-router*:三个「页面」中有两个本质是覆盖层(overlay),用路由表达会让 URL 与状态同步复杂化,且多一个运行时依赖。
|
||||
- *用 URL query 承载筛选状态*:MVP 不做分享与深链需求,暂不需要。
|
||||
|
||||
**影响**
|
||||
若未来增加「今日视图」「统计」等真正的独立页面,再引入路由;届时筛选状态可一并迁移到 query。
|
||||
|
||||
---
|
||||
|
||||
## ADR-005 · 引入 vue-router,拆分看板与今日两个视图
|
||||
|
||||
**背景**
|
||||
ADR-004 决定 MVP 不引入路由,并注明「若未来增加今日视图、统计等真正的独立页面,再引入路由」。
|
||||
现在补做了「今日视图 + 简单统计」,触发条件成立。
|
||||
|
||||
「今日视图」与看板的关系值得说清楚:它不是看板的另一种筛选,而是**换了一个组织维度** ——
|
||||
看板按「状态」组织(待办/进行中/已完成),今日视图按「紧急度」组织(逾期 → 今天到期 → 进行中)。
|
||||
同一批数据,两种视角,用户需要在它们之间来回切换,而不是叠加在同一屏里。
|
||||
|
||||
**结论**
|
||||
引入 `vue-router`,两条路由:
|
||||
|
||||
- `/` — 看板
|
||||
- `/today` — 今日视图(含统计卡片与按紧急度排序的待办列表)
|
||||
|
||||
使用 `createWebHistory`(而非 hash),并导出 `createAppRouter(history)` 工厂函数而非单例,
|
||||
以便测试注入 memory history。
|
||||
|
||||
**被否决的方案**
|
||||
- *把今日视图做成一个 Drawer / Modal*:Drawer 适合「聚焦单条记录」(任务详情、备份面板),
|
||||
不适合承载一个完整的列表视图;而且它无法被收藏或分享,PWA 的 manifest shortcuts 也无处可指。
|
||||
- *在工具栏加一个「今日」筛选按钮*:那只是筛选,不是视图。用户仍然面对三列结构,
|
||||
还得自己在心里把「逾期」和「进行中」拼起来。
|
||||
- *用 hash 路由避免服务器配置*:URL 会变成 `/#/today`,不美观,且 PWA 的 `start_url`
|
||||
与 shortcuts 指向带 hash 的地址会让某些平台的安装体验变差。代价是部署时必须配置 SPA 回落 ——
|
||||
这一点已在 README 的部署章节明确写出。
|
||||
|
||||
**影响**
|
||||
- **部署要求变化**:服务器必须把未知路径回落到 `index.html`,否则直接访问 `/today` 会 404。
|
||||
README 已给出 Nginx 与 Netlify 的配置。
|
||||
- 今日视图按路由懒加载,不进入首屏包(独立 chunk 约 4KB)。
|
||||
- Service Worker 的 `navigateFallback` 设为 `index.html`,离线时深链 `/today` 同样可用。
|
||||
- 看板原有的筛选状态仍留在 Pinia 中,未迁移到 query —— 目前没有分享带筛选链接的需求,
|
||||
迁移只会增加复杂度。
|
||||
+402
@@ -0,0 +1,402 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"exportedAt": "2026-10-18T11:45:00+08:00",
|
||||
"tasks": [
|
||||
{
|
||||
"id": "t-01",
|
||||
"title": "T01 工程初始化与规范",
|
||||
"description": "Vite + Vue3 + TS 初始化;接入 Tailwind v4(@tailwindcss/vite)、Pinia、Vitest;配置 ESLint + Prettier;统一目录结构。\n\n依赖:无\n验收:npm run dev / lint / format / test 均可用;Tailwind 原子类生效;目录为 src/{components,composables,stores,types,utils,views}。",
|
||||
"status": "done",
|
||||
"priority": 3,
|
||||
"tags": [
|
||||
"M0-基础"
|
||||
],
|
||||
"dueDate": null,
|
||||
"sortOrder": 1024,
|
||||
"createdAt": "2026-10-18T10:08:00+08:00",
|
||||
"updatedAt": "2026-10-18T10:20:00+08:00",
|
||||
"completedAt": "2026-10-18T10:20:00+08:00"
|
||||
},
|
||||
{
|
||||
"id": "t-02",
|
||||
"title": "T02 技术决策记录(ADR)",
|
||||
"description": "将四项决策写入 docs/adr.md:固定三列、TaskRepository + localStorage、dueDate 仅存日期、不引入 vue-router。\n\n依赖:无\n验收:覆盖 4 项决策,每项写明否决了什么及原因。",
|
||||
"status": "done",
|
||||
"priority": 3,
|
||||
"tags": [
|
||||
"M0-基础"
|
||||
],
|
||||
"dueDate": null,
|
||||
"sortOrder": 2048,
|
||||
"createdAt": "2026-10-18T10:08:00+08:00",
|
||||
"updatedAt": "2026-10-18T10:20:00+08:00",
|
||||
"completedAt": "2026-10-18T10:20:00+08:00"
|
||||
},
|
||||
{
|
||||
"id": "t-03",
|
||||
"title": "T03 领域类型与常量",
|
||||
"description": "定义 Task / ColumnId / Priority 类型与列定义常量。\n\n依赖:T02\n验收:status 为字面量联合类型而非 enum;dueDate 为 YYYY-MM-DD 字符串或 null;列常量为只读;无 any 且 vue-tsc 通过。",
|
||||
"status": "done",
|
||||
"priority": 3,
|
||||
"tags": [
|
||||
"M1-数据层"
|
||||
],
|
||||
"dueDate": null,
|
||||
"sortOrder": 3072,
|
||||
"createdAt": "2026-10-18T10:08:00+08:00",
|
||||
"updatedAt": "2026-10-18T10:20:00+08:00",
|
||||
"completedAt": "2026-10-18T10:20:00+08:00"
|
||||
},
|
||||
{
|
||||
"id": "t-04",
|
||||
"title": "T04 TaskRepository 接口 + localStorage 实现",
|
||||
"description": "定义 TaskRepository 接口并提供 localStorage 实现,含 schemaVersion 与迁移入口。\n\n实现修订:接口最终定为 load()/save(tasks) 的整份快照读写,而非细粒度 CRUD(数据量小,整份写入无不一致中间态)。\n\n依赖:T03\n验收:接口与实现分离;含 schemaVersion;过高版本明确报错;隐私模式不白屏;写入超限抛 StorageWriteError;数据损坏先备份到 *.corrupt.<时间戳> 再报错。",
|
||||
"status": "done",
|
||||
"priority": 3,
|
||||
"tags": [
|
||||
"M1-数据层"
|
||||
],
|
||||
"dueDate": null,
|
||||
"sortOrder": 4096,
|
||||
"createdAt": "2026-10-18T10:08:00+08:00",
|
||||
"updatedAt": "2026-10-18T10:15:00+08:00",
|
||||
"completedAt": "2026-10-18T10:15:00+08:00"
|
||||
},
|
||||
{
|
||||
"id": "t-05",
|
||||
"title": "T05 排序算法 sortOrder + normalize",
|
||||
"description": "相邻中点插入;间距小于 1e-6 时对该列 normalize 重排(间隔 1024)。\n\n依赖:T03\n验收:任意两卡之间插入顺序正确;连续 100 次中间插入不崩坏;normalize 后严格递增且间距均匀;纯函数可独立单测。",
|
||||
"status": "done",
|
||||
"priority": 3,
|
||||
"tags": [
|
||||
"M1-数据层"
|
||||
],
|
||||
"dueDate": null,
|
||||
"sortOrder": 5120,
|
||||
"createdAt": "2026-10-18T10:08:00+08:00",
|
||||
"updatedAt": "2026-10-18T10:15:00+08:00",
|
||||
"completedAt": "2026-10-18T10:15:00+08:00"
|
||||
},
|
||||
{
|
||||
"id": "t-06",
|
||||
"title": "T06 Pinia store + 多标签页同步",
|
||||
"description": "Store 作为内存态唯一来源,读写走 repository;BroadcastChannel 广播变更保持多标签页一致。\n\n依赖:T04、T05\n验收:标签页 A 修改后 B 在 1 秒内反映;跨列拖拽在一次事务内同时更新 status 与 sortOrder;无「已换列但顺序仍旧」中间态。",
|
||||
"status": "done",
|
||||
"priority": 3,
|
||||
"tags": [
|
||||
"M1-数据层"
|
||||
],
|
||||
"dueDate": null,
|
||||
"sortOrder": 6144,
|
||||
"createdAt": "2026-10-18T10:08:00+08:00",
|
||||
"updatedAt": "2026-10-18T10:15:00+08:00",
|
||||
"completedAt": "2026-10-18T10:15:00+08:00"
|
||||
},
|
||||
{
|
||||
"id": "t-07",
|
||||
"title": "T07 看板三列骨架与空状态",
|
||||
"description": "三列布局(待办/进行中/已完成),响应式,每列含标题、计数、空状态。\n\n依赖:T06\n验收:≥1024px 三列并排、<768px 纵向堆叠;列内容区独立滚动不抖动。",
|
||||
"status": "done",
|
||||
"priority": 3,
|
||||
"tags": [
|
||||
"M2-UI"
|
||||
],
|
||||
"dueDate": null,
|
||||
"sortOrder": 7168,
|
||||
"createdAt": "2026-10-18T10:08:00+08:00",
|
||||
"updatedAt": "2026-10-18T10:22:00+08:00",
|
||||
"completedAt": "2026-10-18T10:22:00+08:00"
|
||||
},
|
||||
{
|
||||
"id": "t-08",
|
||||
"title": "T08 TaskCard 任务卡片组件",
|
||||
"description": "展示标题、优先级、标签、截止日期;逾期与已完成有区分样式。\n\n依赖:T07\n验收:三种优先级可区分;逾期显示警示色;已完成置灰;标签超 3 个折叠为 +N;长标题截断。",
|
||||
"status": "done",
|
||||
"priority": 3,
|
||||
"tags": [
|
||||
"M2-UI"
|
||||
],
|
||||
"dueDate": null,
|
||||
"sortOrder": 8192,
|
||||
"createdAt": "2026-10-18T10:08:00+08:00",
|
||||
"updatedAt": "2026-10-18T10:22:00+08:00",
|
||||
"completedAt": "2026-10-18T10:22:00+08:00"
|
||||
},
|
||||
{
|
||||
"id": "t-09",
|
||||
"title": "T09 UI 基础组件(Drawer/Toast/DatePicker/TagInput)",
|
||||
"description": "自建或基于 headless 库实现。注意 Tailwind 只提供样式不提供行为,本任务是最易被低估的一项。\n\n依赖:T01\n验收:Drawer 支持 ESC、遮罩关闭、焦点陷阱;Toast 自动消失且可手关;DatePicker 输出 YYYY-MM-DD 且可清空;TagInput 支持回车添加/退格删除/去重;均有 aria 属性。",
|
||||
"status": "done",
|
||||
"priority": 3,
|
||||
"tags": [
|
||||
"M2-UI"
|
||||
],
|
||||
"dueDate": null,
|
||||
"sortOrder": 9216,
|
||||
"createdAt": "2026-10-18T10:08:00+08:00",
|
||||
"updatedAt": "2026-10-18T10:22:00+08:00",
|
||||
"completedAt": "2026-10-18T10:22:00+08:00"
|
||||
},
|
||||
{
|
||||
"id": "t-10",
|
||||
"title": "T10 快速新增任务",
|
||||
"description": "列顶部或全局输入框,回车即建卡,默认进入「待办」。\n\n依赖:T07\n验收:空标题不可提交;提交后清空且保持聚焦可连续录入;全流程 ≤3 秒。",
|
||||
"status": "done",
|
||||
"priority": 3,
|
||||
"tags": [
|
||||
"M3-业务"
|
||||
],
|
||||
"dueDate": null,
|
||||
"sortOrder": 10240,
|
||||
"createdAt": "2026-10-18T10:08:00+08:00",
|
||||
"updatedAt": "2026-10-18T10:22:00+08:00",
|
||||
"completedAt": "2026-10-18T10:22:00+08:00"
|
||||
},
|
||||
{
|
||||
"id": "t-11",
|
||||
"title": "T11 任务详情表单(创建/编辑/校验)",
|
||||
"description": "创建与编辑复用同一表单,置于 Drawer 内。\n\n依赖:T09、T10\n验收:可编辑标题/描述/优先级/截止日期/标签;标题必填且有内联错误;取消不产生脏数据;保存后看板同步;updatedAt 正确刷新。",
|
||||
"status": "done",
|
||||
"priority": 3,
|
||||
"tags": [
|
||||
"M3-业务"
|
||||
],
|
||||
"dueDate": null,
|
||||
"sortOrder": 11264,
|
||||
"createdAt": "2026-10-18T10:08:00+08:00",
|
||||
"updatedAt": "2026-10-18T10:28:00+08:00",
|
||||
"completedAt": "2026-10-18T10:28:00+08:00"
|
||||
},
|
||||
{
|
||||
"id": "t-12",
|
||||
"title": "T12 删除与撤销",
|
||||
"description": "删除任务后提供 Toast 撤销入口,保留最近一次删除内容。\n\n依赖:T11\n验收:删除后卡片立即消失;撤销后连同原顺序恢复;撤销窗口结束后真正移除。",
|
||||
"status": "done",
|
||||
"priority": 3,
|
||||
"tags": [
|
||||
"M3-业务"
|
||||
],
|
||||
"dueDate": null,
|
||||
"sortOrder": 12288,
|
||||
"createdAt": "2026-10-18T10:08:00+08:00",
|
||||
"updatedAt": "2026-10-18T10:28:00+08:00",
|
||||
"completedAt": "2026-10-18T10:28:00+08:00"
|
||||
},
|
||||
{
|
||||
"id": "t-13",
|
||||
"title": "T13 拖拽改状态 + 列内排序",
|
||||
"description": "基于 vue-draggable-plus 实现跨列拖动与列内排序。\n\n依赖:T08、T11\n验收:跨列与列内变更刷新后保持;筛选/搜索激活时禁用列内排序(仅允许跨列改状态);拖拽不触发点击;触摸设备可拖动。",
|
||||
"status": "done",
|
||||
"priority": 3,
|
||||
"tags": [
|
||||
"M3-业务"
|
||||
],
|
||||
"dueDate": null,
|
||||
"sortOrder": 13312,
|
||||
"createdAt": "2026-10-18T10:08:00+08:00",
|
||||
"updatedAt": "2026-10-18T10:28:00+08:00",
|
||||
"completedAt": "2026-10-18T10:28:00+08:00"
|
||||
},
|
||||
{
|
||||
"id": "t-14",
|
||||
"title": "T14 导出/导入 JSON + 启动自动快照",
|
||||
"description": "一键导出全部任务为 JSON;覆盖式导入;启动时自动快照到独立 key。local-first 下这是唯一的数据安全网,优先级为 P0 不得后置。\n\n依赖:T04\n验收:导出文件名含日期且内容含 schemaVersion;导入前校验结构,非法文件不破坏现有数据;覆盖式导入需二次确认;启动写入快照并保留最近 3 份;导出→清空→导入完全还原。",
|
||||
"status": "done",
|
||||
"priority": 3,
|
||||
"tags": [
|
||||
"M4-数据安全"
|
||||
],
|
||||
"dueDate": null,
|
||||
"sortOrder": 14336,
|
||||
"createdAt": "2026-10-18T10:08:00+08:00",
|
||||
"updatedAt": "2026-10-18T10:37:00+08:00",
|
||||
"completedAt": "2026-10-18T10:37:00+08:00"
|
||||
},
|
||||
{
|
||||
"id": "t-15",
|
||||
"title": "T15 搜索与筛选",
|
||||
"description": "关键词搜索(标题 + 描述)+ 按优先级/标签筛选。\n\n依赖:T07\n验收:中文搜索用 includes,不引入分词库;输入防抖 ≤300ms;多条件为「与」;列计数反映筛选结果;可一键清空条件。",
|
||||
"status": "done",
|
||||
"priority": 2,
|
||||
"tags": [
|
||||
"M5-效率"
|
||||
],
|
||||
"dueDate": null,
|
||||
"sortOrder": 15360,
|
||||
"createdAt": "2026-10-18T10:08:00+08:00",
|
||||
"updatedAt": "2026-10-18T10:37:00+08:00",
|
||||
"completedAt": "2026-10-18T10:37:00+08:00"
|
||||
},
|
||||
{
|
||||
"id": "t-16",
|
||||
"title": "T16 逾期提示 + 键盘快捷键",
|
||||
"description": "顶部统计条显示今日到期与逾期数量;提供常用快捷键。\n\n依赖:T10\n验收:顶部显示「今日到期 N / 逾期 N」且可点击筛选;N 聚焦新增、/ 聚焦搜索、Esc 关闭抽屉;输入框内不劫持快捷键;有快捷键帮助提示。",
|
||||
"status": "done",
|
||||
"priority": 2,
|
||||
"tags": [
|
||||
"M5-效率"
|
||||
],
|
||||
"dueDate": null,
|
||||
"sortOrder": 16384,
|
||||
"createdAt": "2026-10-18T10:08:00+08:00",
|
||||
"updatedAt": "2026-10-18T10:37:00+08:00",
|
||||
"completedAt": "2026-10-18T10:37:00+08:00"
|
||||
},
|
||||
{
|
||||
"id": "t-17",
|
||||
"title": "T17 已完成归档 + 长列表性能",
|
||||
"description": "已完成任务归档 + 长列表性能。\n\n实现修订:归档通过新增 Task.archivedAt 字段实现,因此 SCHEMA_VERSION 从 1 升到 2,并在 MIGRATIONS 表登记 1->2 的迁移(为旧数据补 archivedAt: null)。这是迁移通道第一次被真实使用。\n\n依赖:T13\n验收:可一键归档超过 N 天的已完成任务;归档默认不显示可切换;列内渲染子集时禁用列内排序;归档可撤销;单列 200 张卡片正确渲染。",
|
||||
"status": "done",
|
||||
"priority": 2,
|
||||
"tags": [
|
||||
"M5-效率"
|
||||
],
|
||||
"dueDate": null,
|
||||
"sortOrder": 17408,
|
||||
"createdAt": "2026-10-18T10:08:00+08:00",
|
||||
"updatedAt": "2026-10-18T10:50:00+08:00",
|
||||
"completedAt": "2026-10-18T10:50:00+08:00"
|
||||
},
|
||||
{
|
||||
"id": "t-18",
|
||||
"title": "T18 核心逻辑单元测试",
|
||||
"description": "核心逻辑单元测试。\n\n实际结果:381 个测试、28 个文件,行覆盖率 96.54%、语句 94.64%,远超原定 80% 目标。已接入 @vitest/coverage-v8(npm run test:coverage)。\n\n依赖:T05、T14",
|
||||
"status": "done",
|
||||
"priority": 3,
|
||||
"tags": [
|
||||
"M6-质量"
|
||||
],
|
||||
"dueDate": null,
|
||||
"sortOrder": 18432,
|
||||
"createdAt": "2026-10-18T10:08:00+08:00",
|
||||
"updatedAt": "2026-10-18T10:50:00+08:00",
|
||||
"completedAt": "2026-10-18T10:50:00+08:00"
|
||||
},
|
||||
{
|
||||
"id": "t-19",
|
||||
"title": "T19 E2E 关键路径测试",
|
||||
"description": "Playwright E2E 关键路径测试。\n\n实现修订:默认驱动系统已安装的 Chrome(channel: chrome),不下载自带 Chromium —— 后者约 197MB,本机对 Playwright CDN 的下载会超时(拉到 10% 断连)。可用 PLAYWRIGHT_CHANNEL 切换为 msedge / chromium。\n\n结果:11 个用例全部通过,覆盖真实拖拽、刷新持久化、导出导入还原、删除撤销、搜索筛选、键盘快捷键。\n\n依赖:T13、T14",
|
||||
"status": "done",
|
||||
"priority": 2,
|
||||
"tags": [
|
||||
"M6-质量"
|
||||
],
|
||||
"dueDate": null,
|
||||
"sortOrder": 19456,
|
||||
"createdAt": "2026-10-18T10:08:00+08:00",
|
||||
"updatedAt": "2026-10-18T11:20:00+08:00",
|
||||
"completedAt": "2026-10-18T11:20:00+08:00"
|
||||
},
|
||||
{
|
||||
"id": "t-20",
|
||||
"title": "T20 生产构建与部署",
|
||||
"description": "生产构建与部署。\n\n结果:npm run build 无错误(62 modules,CSS 19.87 kB / JS 162.09 kB gzip 后 4.78 / 60.11);preview 静态托管验证通过,含深链刷新返回 200;README 写明启动、构建、部署、备份与已知限制。\n\n依赖:T18",
|
||||
"status": "done",
|
||||
"priority": 3,
|
||||
"tags": [
|
||||
"M6-质量"
|
||||
],
|
||||
"dueDate": null,
|
||||
"sortOrder": 20480,
|
||||
"createdAt": "2026-10-18T10:08:00+08:00",
|
||||
"updatedAt": "2026-10-18T11:20:00+08:00",
|
||||
"completedAt": "2026-10-18T11:20:00+08:00"
|
||||
},
|
||||
{
|
||||
"id": "t-21",
|
||||
"title": "T21 PWA 图标生成",
|
||||
"description": "用 Pillow 脚本生成 192/512/maskable/apple-touch 四套图标。\n\n关键点:Pillow 的 ImageDraw 不做 alpha 混合,半透明元素必须画在独立图层再 alpha_composite,否则会直接覆盖背景。\n\n验收:四套图标生成;maskable 内容位于中心 80% 安全区内。",
|
||||
"status": "done",
|
||||
"priority": 2,
|
||||
"tags": [
|
||||
"M7-增强"
|
||||
],
|
||||
"dueDate": null,
|
||||
"sortOrder": 21504,
|
||||
"createdAt": "2026-10-18T11:45:00+08:00",
|
||||
"updatedAt": "2026-10-18T11:45:00+08:00",
|
||||
"completedAt": "2026-10-18T11:45:00+08:00",
|
||||
"archivedAt": null
|
||||
},
|
||||
{
|
||||
"id": "t-22",
|
||||
"title": "T22 接入 vite-plugin-pwa",
|
||||
"description": "manifest(名称/图标/主题色/shortcuts)+ Workbox 预缓存 + navigateFallback。\n\n验收:构建产出 sw.js 与 manifest.webmanifest;Service Worker 激活并接管页面;断网后看板与深链 /today 均可打开。",
|
||||
"status": "done",
|
||||
"priority": 2,
|
||||
"tags": [
|
||||
"M7-增强"
|
||||
],
|
||||
"dueDate": null,
|
||||
"sortOrder": 22528,
|
||||
"createdAt": "2026-10-18T11:45:00+08:00",
|
||||
"updatedAt": "2026-10-18T11:45:00+08:00",
|
||||
"completedAt": "2026-10-18T11:45:00+08:00",
|
||||
"archivedAt": null
|
||||
},
|
||||
{
|
||||
"id": "t-23",
|
||||
"title": "T23 PWA 更新提示",
|
||||
"description": "registerType 为 prompt,新版本由用户确认后再生效,不打断正在进行的编辑。\n\n验收:有新版本时底部提示并可立即更新;已可离线使用时提示但不显示更新按钮;可关闭。",
|
||||
"status": "done",
|
||||
"priority": 2,
|
||||
"tags": [
|
||||
"M7-增强"
|
||||
],
|
||||
"dueDate": null,
|
||||
"sortOrder": 23552,
|
||||
"createdAt": "2026-10-18T11:45:00+08:00",
|
||||
"updatedAt": "2026-10-18T11:45:00+08:00",
|
||||
"completedAt": "2026-10-18T11:45:00+08:00",
|
||||
"archivedAt": null
|
||||
},
|
||||
{
|
||||
"id": "t-24",
|
||||
"title": "T24 引入 vue-router 拆分视图",
|
||||
"description": "按 ADR-005 引入路由,拆出 /(看板) 与 /today(今日),今日视图懒加载。\n\n验收:两条路由可切换并共享同一份数据;未知路径回落到看板;导航项带 aria-current。",
|
||||
"status": "done",
|
||||
"priority": 2,
|
||||
"tags": [
|
||||
"M7-增强"
|
||||
],
|
||||
"dueDate": null,
|
||||
"sortOrder": 24576,
|
||||
"createdAt": "2026-10-18T11:45:00+08:00",
|
||||
"updatedAt": "2026-10-18T11:45:00+08:00",
|
||||
"completedAt": "2026-10-18T11:45:00+08:00",
|
||||
"archivedAt": null
|
||||
},
|
||||
{
|
||||
"id": "t-25",
|
||||
"title": "T25 今日视图",
|
||||
"description": "按紧急度组织:逾期 > 今天到期 > 进行中,三组互斥,组内按优先级与截止日期排序。\n\n验收:分组互斥且已完成/已归档不出现;可直接勾选完成;空状态提示。",
|
||||
"status": "done",
|
||||
"priority": 2,
|
||||
"tags": [
|
||||
"M7-增强"
|
||||
],
|
||||
"dueDate": null,
|
||||
"sortOrder": 25600,
|
||||
"createdAt": "2026-10-18T11:45:00+08:00",
|
||||
"updatedAt": "2026-10-18T11:45:00+08:00",
|
||||
"completedAt": "2026-10-18T11:45:00+08:00",
|
||||
"archivedAt": null
|
||||
},
|
||||
{
|
||||
"id": "t-26",
|
||||
"title": "T26 简单统计",
|
||||
"description": "今日视图顶部统计卡片:今日待办 / 逾期 / 今天到期 / 本周完成(周一为一周之始)。\n\n验收:跨周边界正确;已完成任务不计入逾期与今天到期;归档任务仍计入完成数。",
|
||||
"status": "done",
|
||||
"priority": 2,
|
||||
"tags": [
|
||||
"M7-增强"
|
||||
],
|
||||
"dueDate": null,
|
||||
"sortOrder": 26624,
|
||||
"createdAt": "2026-10-18T11:45:00+08:00",
|
||||
"updatedAt": "2026-10-18T11:45:00+08:00",
|
||||
"completedAt": "2026-10-18T11:45:00+08:00",
|
||||
"archivedAt": null
|
||||
}
|
||||
]
|
||||
}
|
||||
+348
@@ -0,0 +1,348 @@
|
||||
# 个人看板工具 · MVP 开发任务卡
|
||||
|
||||
> 版本:v2(按评审修订)
|
||||
> 决策前提:**固定三列** · **TaskRepository 接口 + localStorage 存储** · **dueDate 仅存日期** · **不引入 vue-router**
|
||||
|
||||
## 已定死的四项决策
|
||||
|
||||
| 决策 | 结论 | 理由 |
|
||||
|---|---|---|
|
||||
| 列是否可配置 | **否**,固定 `todo` / `doing` / `done` | 可配置列需引入 `columns` 表,成本翻倍,个人场景无收益 |
|
||||
| 存储 | **`TaskRepository` 接口 + localStorage 起步** | 200 任务约 60KB,localStorage 足够;保留接口便于换 Dexie |
|
||||
| `dueDate` 语义 | **仅日期 `YYYY-MM-DD`** | 个人看板不需要时刻,直接避开时区坑 |
|
||||
| 路由 | **不引入 vue-router** | 单视图 + 抽屉/弹窗即可,减少依赖 |
|
||||
|
||||
---
|
||||
|
||||
## 总览
|
||||
|
||||
| ID | 任务 | 优先级 | 估时 | 依赖 |
|
||||
|---|---|---|---|---|
|
||||
| T01 | 工程初始化与规范 | P0 | 0.5d | — |
|
||||
| T02 | 技术决策记录(ADR) | P0 | 0.25d | — |
|
||||
| T03 | 领域类型与常量 | P0 | 0.25d | T02 |
|
||||
| T04 | TaskRepository 接口 + localStorage 实现(含 schema 迁移) | P0 | 0.5d | T03 |
|
||||
| T05 | 排序算法:sortOrder 中点插入 + normalize | P0 | 0.5d | T03 |
|
||||
| T06 | Pinia store + 多标签页同步 | P0 | 0.5d | T04, T05 |
|
||||
| T07 | 看板三列骨架与空状态 | P0 | 0.5d | T06 |
|
||||
| T08 | TaskCard 任务卡片组件 | P0 | 0.5d | T07 |
|
||||
| T09 | UI 基础组件(Drawer / Toast / DatePicker / TagInput) | P0 | 1d | T01 |
|
||||
| T10 | 快速新增任务 | P0 | 0.5d | T07 |
|
||||
| T11 | 任务详情表单(创建 / 编辑 / 校验) | P0 | 1d | T09, T10 |
|
||||
| T12 | 删除与撤销 | P0 | 0.5d | T11 |
|
||||
| T13 | 拖拽改状态 + 列内排序(含筛选态保护) | P0 | 1d | T08, T11 |
|
||||
| T14 | 导出 / 导入 JSON + 启动自动快照 | **P0** | 0.5d | T04 |
|
||||
| T15 | 搜索与筛选 | P0.5 | 0.5d | T07 |
|
||||
| T16 | 逾期提示 + 键盘快捷键 | P1 | 0.5d | T10 |
|
||||
| T17 | 已完成归档 + 长列表性能 | P1 | 0.5d | T13 |
|
||||
| T18 | 核心逻辑单元测试 | P0 | 0.5d | T05, T14 |
|
||||
| T19 | E2E 关键路径测试 | P1 | 0.5d | T13, T14 |
|
||||
| T20 | 生产构建与部署 | P0 | 0.5d | T18 |
|
||||
|
||||
**合计:11 人天**(纯开发,不含缓冲)→ 实际排期按 ×1.4 计,约 **15 天**。
|
||||
|
||||
**关键里程碑**:T01–T13 完成后即「可用 MVP」;T14 是数据安全底线,不得后置。
|
||||
|
||||
---
|
||||
|
||||
## 任务卡明细
|
||||
|
||||
### T01 · 工程初始化与规范
|
||||
**优先级** P0 **估时** 0.5d **依赖** —
|
||||
|
||||
**描述**
|
||||
Vite + Vue3 + TS 工程初始化;接入 Tailwind(**锁定 v4 + `@tailwindcss/vite`**)、Pinia、Vitest;配置 ESLint + Prettier;统一目录结构。
|
||||
|
||||
**验收标准**
|
||||
- [x] `npm run dev` 可启动,页面渲染正常
|
||||
- [x] Tailwind 原子类生效(页面出现预期样式)
|
||||
- [x] `npm run lint` 与 `npm run format` 均可执行且无报错
|
||||
- [x] `npm run test` 可运行(哪怕只有 1 个示例用例)
|
||||
- [x] 目录结构为 `src/{components,composables,data,stores,types,utils,views}`
|
||||
|
||||
---
|
||||
|
||||
### T02 · 技术决策记录(ADR)
|
||||
**优先级** P0 **估时** 0.25d **依赖** —
|
||||
|
||||
**描述**
|
||||
将上述四项决策写入 `docs/adr.md`,含背景、结论、被否决的方案及原因。
|
||||
|
||||
**验收标准**
|
||||
- [x] `docs/adr.md` 覆盖 4 项决策
|
||||
- [x] 每项决策写明「否决了什么、为什么」
|
||||
|
||||
---
|
||||
|
||||
### T03 · 领域类型与常量
|
||||
**优先级** P0 **估时** 0.25d **依赖** T02
|
||||
|
||||
**描述**
|
||||
定义 `Task`、`ColumnId`、`Priority` 类型与列定义常量。
|
||||
|
||||
**验收标准**
|
||||
- [x] `Task` 含 `id/title/description/status/priority/tags/dueDate/sortOrder/createdAt/updatedAt/completedAt`
|
||||
- [x] `status` 为字面量联合类型 `'todo' | 'doing' | 'done'`,**不使用 `enum`**
|
||||
- [x] `dueDate` 类型为 `string`(`YYYY-MM-DD`)或 `null`
|
||||
- [x] 列定义常量为只读,含 id / 标题 / 顺序
|
||||
- [x] 无 `any`,`vue-tsc` 通过
|
||||
|
||||
---
|
||||
|
||||
### T04 · TaskRepository 接口 + localStorage 实现
|
||||
**优先级** P0 **估时** 0.5d **依赖** T03
|
||||
|
||||
**描述**
|
||||
定义 `TaskRepository` 接口,提供 localStorage 实现,含 `schemaVersion` 与迁移入口。
|
||||
|
||||
> 实现修订:接口最终定为 `load()` / `save(tasks)` 的**整份快照读写**,而非细粒度 CRUD。
|
||||
> 原因:数据量小(约 60KB),整份写入不会产生「部分写入」的不一致状态;且排序、导入、
|
||||
> 跨标签页同步本质上都是快照操作。接口与实现分离这一目标不变。
|
||||
|
||||
**验收标准**
|
||||
- [x] 接口与实现分离,UI/Store 只依赖接口
|
||||
- [x] 持久化结构含 `schemaVersion` 字段
|
||||
- [x] 提供迁移入口(`MIGRATIONS` 表),未知/过高版本有明确报错而非静默降级
|
||||
- [x] localStorage 不可用(隐私模式)时给出可读错误,不白屏
|
||||
- [x] 写入失败(配额超限)抛出 `StorageWriteError` 并在界面提示
|
||||
- [x] 数据损坏时**先备份到 `*.corrupt.<时间戳>` 再报错**,不覆盖用户原数据
|
||||
|
||||
---
|
||||
|
||||
### T05 · 排序算法
|
||||
**优先级** P0 **估时** 0.5d **依赖** T03
|
||||
|
||||
**描述**
|
||||
实现 `sortOrder` 相邻中点插入;间距小于 `1e-6` 时对该列执行 normalize 重排(间隔 1024)。
|
||||
|
||||
**验收标准**
|
||||
- [x] 在任意两卡之间插入,顺序正确
|
||||
- [x] 连续 100 次中间插入后,顺序仍正确(不出现精度崩坏)
|
||||
- [x] normalize 后同列 `sortOrder` 严格递增且间距均匀
|
||||
- [x] 纯函数实现,无副作用,可独立单测
|
||||
|
||||
---
|
||||
|
||||
### T06 · Pinia store + 多标签页同步
|
||||
**优先级** P0 **估时** 0.5d **依赖** T04, T05
|
||||
|
||||
**描述**
|
||||
Store 作为内存态唯一来源,读写走 repository;通过 `BroadcastChannel` 广播变更,使多标签页一致。
|
||||
|
||||
**验收标准**
|
||||
- [x] 所有变更先写 repository 再更新内存态(或反之,但必须单一顺序)
|
||||
- [x] 标签页 A 的修改,标签页 B 在 1 秒内自动反映
|
||||
- [x] 跨列拖拽在一次事务内同时更新 `status` 与 `sortOrder`
|
||||
- [x] 无「卡片已换列但顺序仍旧」的中间态
|
||||
|
||||
---
|
||||
|
||||
### T07 · 看板三列骨架与空状态
|
||||
**优先级** P0 **估时** 0.5d **依赖** T06
|
||||
|
||||
**描述**
|
||||
三列布局(待办 / 进行中 / 已完成),响应式;每列含标题、计数、空状态。
|
||||
|
||||
**验收标准**
|
||||
- [x] ≥1024px 三列并排;<768px 纵向堆叠
|
||||
- [x] 每列显示任务计数
|
||||
- [x] 空列显示引导文案
|
||||
- [x] 列内容区可独立滚动,页面不整体抖动
|
||||
|
||||
---
|
||||
|
||||
### T08 · TaskCard 任务卡片组件
|
||||
**优先级** P0 **估时** 0.5d **依赖** T07
|
||||
|
||||
**描述**
|
||||
卡片展示标题、优先级、标签、截止日期;逾期与已完成有区分样式。
|
||||
|
||||
**验收标准**
|
||||
- [x] 三种优先级视觉可区分
|
||||
- [x] 逾期(`dueDate` < 今天且未完成)显示警示色
|
||||
- [x] 已完成卡片置灰 / 降饱和
|
||||
- [x] 标签超过 3 个折叠为 `+N`
|
||||
- [x] 长标题截断,不撑破卡片
|
||||
|
||||
---
|
||||
|
||||
### T09 · UI 基础组件
|
||||
**优先级** P0 **估时** 1d **依赖** T01
|
||||
|
||||
**描述**
|
||||
自建(或基于 headless 库)实现 Drawer/Modal、Toast、DatePicker、TagInput。**注意:Tailwind 只提供样式,不提供行为。**
|
||||
|
||||
**验收标准**
|
||||
- [x] Drawer 支持 ESC 关闭、遮罩点击关闭、**焦点陷阱(focus trap)**
|
||||
- [x] Toast 支持自动消失与手动关闭
|
||||
- [x] DatePicker 输出 `YYYY-MM-DD`,可清空
|
||||
- [x] TagInput 支持回车添加、退格删除、去重
|
||||
- [x] 上述组件均有可访问性属性(`role` / `aria-*`)
|
||||
|
||||
---
|
||||
|
||||
### T10 · 快速新增任务
|
||||
**优先级** P0 **估时** 0.5d **依赖** T07
|
||||
|
||||
**描述**
|
||||
列顶部或全局输入框,回车即建卡,默认进入「待办」。
|
||||
|
||||
**验收标准**
|
||||
- [x] 聚焦输入 → 输入标题 → 回车,卡片出现在列首
|
||||
- [x] 空标题不可提交
|
||||
- [x] 提交后输入框清空且保持聚焦(可连续录入)
|
||||
- [x] 全流程 ≤3 秒可完成
|
||||
|
||||
---
|
||||
|
||||
### T11 · 任务详情表单
|
||||
**优先级** P0 **估时** 1d **依赖** T09, T10
|
||||
|
||||
**描述**
|
||||
创建与编辑复用同一表单,置于 Drawer 内。
|
||||
|
||||
**验收标准**
|
||||
- [x] 可编辑标题、描述、优先级、截止日期、标签
|
||||
- [x] 标题必填,空值给出内联错误提示
|
||||
- [x] 取消编辑不产生脏数据
|
||||
- [x] 保存后看板与详情同步更新
|
||||
- [x] `updatedAt` 正确刷新
|
||||
|
||||
---
|
||||
|
||||
### T12 · 删除与撤销
|
||||
**优先级** P0 **估时** 0.5d **依赖** T11
|
||||
|
||||
**描述**
|
||||
删除任务后提供 Toast 撤销入口(保留最近一次删除内容)。
|
||||
|
||||
**验收标准**
|
||||
- [x] 删除后卡片立即消失
|
||||
- [x] Toast 提供「撤销」,点击后卡片**连同原顺序**恢复
|
||||
- [x] 撤销窗口结束后数据真正移除
|
||||
- [x] 删除需二次确认(或依赖撤销兜底)
|
||||
|
||||
---
|
||||
|
||||
### T13 · 拖拽改状态 + 列内排序
|
||||
**优先级** P0 **估时** 1d **依赖** T08, T11
|
||||
|
||||
**描述**
|
||||
基于 `vue-draggable-plus` 实现跨列拖动与列内排序。
|
||||
|
||||
**验收标准**
|
||||
- [x] 跨列拖动更新 `status`,刷新后保持
|
||||
- [x] 列内拖动更新顺序,刷新后保持
|
||||
- [x] **筛选/搜索激活时禁用列内排序**(仅允许跨列改状态),避免顺序写坏
|
||||
- [x] 拖拽中不触发卡片点击(区分 click 与 drag)
|
||||
- [x] 触摸设备可拖动
|
||||
|
||||
---
|
||||
|
||||
### T14 · 导出 / 导入 JSON + 自动快照
|
||||
**优先级** **P0** **估时** 0.5d **依赖** T04
|
||||
|
||||
**描述**
|
||||
一键导出全部任务为 JSON;支持覆盖式导入;启动时自动快照到独立 key。**local-first 下这是唯一的数据安全网。**
|
||||
|
||||
**验收标准**
|
||||
- [x] 导出文件名含日期,内容含 `schemaVersion`
|
||||
- [x] 导入前校验结构,非法文件给出明确错误且**不破坏现有数据**
|
||||
- [x] 导入为覆盖式,执行前二次确认
|
||||
- [x] 每次启动写入快照 key,保留最近 3 份
|
||||
- [x] 导出 → 清空 → 导入,数据完全还原
|
||||
|
||||
---
|
||||
|
||||
### T15 · 搜索与筛选
|
||||
**优先级** P0.5 **估时** 0.5d **依赖** T07
|
||||
|
||||
**描述**
|
||||
关键词搜索(标题 + 描述)+ 按优先级/标签筛选。
|
||||
|
||||
**验收标准**
|
||||
- [x] 中文搜索用 `includes` 即可,**不引入分词库**
|
||||
- [x] 输入有防抖(≤300ms)
|
||||
- [x] 多条件为「与」关系
|
||||
- [x] 筛选后列计数反映筛选结果
|
||||
- [x] 一键清空全部筛选条件
|
||||
|
||||
---
|
||||
|
||||
### T16 · 逾期提示 + 键盘快捷键
|
||||
**优先级** P1 **估时** 0.5d **依赖** T10
|
||||
|
||||
**描述**
|
||||
顶部统计条显示今日到期与逾期数量;提供常用快捷键。
|
||||
|
||||
**验收标准**
|
||||
- [x] 顶部显示「今日到期 N / 逾期 N」,可点击直接筛选
|
||||
- [x] `N` 聚焦快速新增;`/` 聚焦搜索;`Esc` 关闭抽屉
|
||||
- [x] 输入框内不劫持快捷键
|
||||
- [x] 快捷键有可查看的帮助提示
|
||||
|
||||
---
|
||||
|
||||
### T17 · 已完成归档 + 长列表性能
|
||||
**优先级** P1 **估时** 0.5d **依赖** T13
|
||||
|
||||
**描述**
|
||||
已完成列支持归档;长列表不卡顿。
|
||||
|
||||
**验收标准**
|
||||
- [x] 可一键归档「已完成」中超过 N 天的任务
|
||||
- [x] 归档任务默认不显示,可切换查看
|
||||
- [x] 单列 200 张卡片滚动仍流畅(必要时虚拟滚动)
|
||||
- [x] 归档操作可撤销
|
||||
|
||||
---
|
||||
|
||||
### T18 · 核心逻辑单元测试
|
||||
**优先级** P0 **估时** 0.5d **依赖** T05, T14
|
||||
|
||||
**描述**
|
||||
只覆盖纯逻辑,UI 不强制测试。
|
||||
|
||||
**验收标准**
|
||||
- [x] 排序:中间插入、normalize、边界值
|
||||
- [x] 筛选:单条件、多条件组合、空结果
|
||||
- [x] 状态流转:`completedAt` 的写入与清除
|
||||
- [x] 导入校验:合法 / 缺字段 / 版本不匹配
|
||||
- [x] 核心模块行覆盖率 ≥80%
|
||||
|
||||
---
|
||||
|
||||
### T19 · E2E 关键路径测试
|
||||
**优先级** P1 **估时** 0.5d **依赖** T13, T14
|
||||
|
||||
**描述**
|
||||
Playwright 覆盖一条主链路。
|
||||
|
||||
**验收标准**
|
||||
- [x] 新增任务 → 拖到进行中 → 标记完成 → **刷新后状态仍正确**
|
||||
- [x] 导出 → 清空 → 导入 → 数据一致
|
||||
- [x] 每个用例使用独立浏览器上下文,互不污染
|
||||
|
||||
---
|
||||
|
||||
### T20 · 生产构建与部署
|
||||
**优先级** P0 **估时** 0.5d **依赖** T18
|
||||
|
||||
**描述**
|
||||
生产构建、静态托管、基础可用性验证。
|
||||
|
||||
**验收标准**
|
||||
- [x] `npm run build` 无 TS 错误、无警告阻断
|
||||
- [x] 产物可静态托管,刷新不 404
|
||||
- [x] 首屏加载 ≤2s(本地网络)
|
||||
- [x] `README.md` 写明启动、构建、部署、数据备份方式
|
||||
|
||||
---
|
||||
|
||||
## 完成定义(DoD)
|
||||
|
||||
- 功能可演示,有明确交互结果
|
||||
- **核心纯逻辑必须有单测;UI 组件不强制**
|
||||
- 数据刷新后不丢失
|
||||
- 无阻断级 lint / type 错误
|
||||
- 涉及数据结构的变更,必须同步更新 `schemaVersion` 与迁移逻辑
|
||||
Reference in New Issue
Block a user