Files
chenshoulin 4e064e82ea 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)。
2026-10-18 11:59:49 +08:00

349 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 个人看板工具 · 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` 与迁移逻辑