Files
Kanban/README.md
T
chenshoulin 61fa5515b8
CI / verify (push) Successful in 1m44s
CI / 部署到服务器 (push) Successful in 26s
feat: 添加标签颜色功能并更新相关组件和测试用例
2026-10-24 22:03:04 +08:00

367 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
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.
# 看板 · 个人任务管理
一个轻量的个人看板工具:打开即用、数据存在本地、不依赖后端。
- 固定三列:**待办 / 进行中 / 已完成**
- **已完成列按完成时间倒序**:最近完成的永远在最上面(列内不支持手动调序)
- 快速新增、拖拽排序、详情编辑、删除撤销;详情里可直接切换状态
- **全局撤销 / 重做**:`Ctrl+Z` 回退最近 50 步操作,`Ctrl+Shift+Z` 重做;批量改属性、批量归档、标签重命名都能回退
- **批量添加**:一次录入多行清单,整批共用目标列 / 优先级 / 标签 / 截止日期
- **批量编辑**:多选后可统一移动列、改优先级、加/减标签、设截止日期、归档
- **回收站**:删除是软删除,30 天内可随时恢复;也可永久删除或清空
- 搜索与筛选(关键词 / 优先级 / 标签 / 今日到期 / 逾期)
- **标签管理**:全局重命名、合并、删除标签,不必逐个任务改
- **标签颜色**:8 色色板,设过颜色的标签在卡片与筛选下拉里带一个圆点;重命名 / 合并时颜色自动跟随
- **今日视图**:按紧急度分组汇总(逾期 / 今天到期 / 进行中 / 待办),含本周完成等统计
- 键盘快捷键;任务卡片可用方向键换列与调序
- **深色模式**:跟随系统 / 浅色 / 深色三态
- 导出导入 JSON(**覆盖 / 合并**两种模式)+ 启动自动备份;覆盖或合并前都会自动留档
- 已完成任务可批量归档,归档任务可在详情里随时恢复
- **任务时间**:详情抽屉显示创建 / 完成 / 更新 / 归档时间;已完成卡片标注完成日期,悬停卡片可看精确时间
- **可安装为 PWA,支持离线使用**
---
## 快速开始
```bash
npm install
npm run dev
```
打开终端提示的地址即可。首次进入是空看板,在任意列顶部的输入框里输入标题、回车即可创建任务。
## 批量添加、批量编辑与回收站
工具栏的「批量添加」可以一次录入多条任务:清单里**一行一条**,整段粘贴即可;行首的 `-`、`*`、`1.` 这类列表符号会自动去掉,空行会被忽略。
这一批共用同一个目标列、优先级、标签与截止日期 —— 先统一建出来,再单独调整个别任务。
不打开面板也行:在任意列的快速新增输入框里**粘贴多行文本**,会按行直接拆成多条任务(插入该列列首);单行粘贴仍是常规输入,不会被拦截。
要批量处理,点工具栏的「多选」:卡片变成可勾选的方框,点哪张选哪张,操作条上可以「全选 N 个」(只覆盖**当前筛选后可见**的任务)或「清空选择」。第二行是批量编辑器:
- **移动到**:统一换到目标列,追加到该列末尾
- **优先级**:低 / 中 / 高
- **标签**:输入后「添加」或「移除」—— 是增删,不会覆盖每条任务原有的其它标签
- **截止日**:可设置,也可清空
- **归档**:批量归档,保留撤销入口
批量删除与批量归档仍提供 Toast 撤销;批量改属性不做撤销(与详情编辑一致),只提示「已更新 N 个任务」。
删除是**软删除**:任务进入顶部导航的「回收站」,保留 30 天后在下次打开时自动清理。回收站里可以逐条「恢复」或「永久删除」,也可以「清空回收站」(二次确认)。恢复会回到原列、原顺序,已归档的任务恢复后仍保持归档态。
多选期间拖拽会停用,避免顺手拖乱了顺序;筛选条件一变也会清空选择,保证「改掉的都是眼前看得见的」。
## 标签管理
标签下拉面板底部有「管理标签…」,打开后可以:
- 查看每个标签被多少任务使用(只统计未归档、未删除的任务)
- **重命名**:所有任务上的该标签一次性替换,其它标签不受影响
- **合并**:把两个标签并成一个,任务上的标签自动去重
- **删除**:只从任务上移除标签,不删除任务
操作后会提示影响了多少个任务。回收站里的任务不参与这些改写。
### 标签颜色
标签默认**不带颜色** —— 没设过就不画圆点,避免每个标签都顶着一个灰点。
在标签管理面板里给标签点一个色块即可(8 色,另有虚线圆点表示「清除颜色」)。颜色会同步出现在看板卡片和标签筛选下拉里。重命名或合并标签时颜色自动跟着走,不会出现「改名之后突然掉色」。
颜色是**本机显示偏好**,存在 `kanban.tagColors`,不进入任务数据:导出 JSON 与快照恢复都不会带上它,换设备后需要重新设置。这样「怎么显示」不会污染「有哪些任务」,导入别人的数据时也不会把颜色映射一起覆盖掉。
## 脚本
| 命令 | 说明 |
| --------------------------------- | --------------------------------------------------------- |
| `npm run dev` | 启动开发服务器 |
| `npm run build` | 类型检查 + 生产构建,产物在 `dist/` |
| `npm run preview` | 本地预览生产构建 |
| `npm run typecheck` | 只做类型检查 |
| `npm run test` | 单元测试(Vitest) |
| `npm run test:coverage` | 单元测试 + 覆盖率报告 |
| `npm run test:e2e` | 端到端测试(Playwright,默认驱动系统已安装的 Chrome) |
| `node scripts/screenshot.mjs` | 生成界面截图(需先起 dev server),产物在 `.screenshots/` |
| `npm run lint` / `npm run format` | 代码检查与格式化 |
## 快捷键
| 按键 | 作用 |
| -------------- | ------------------------------------- |
| `N` | 聚焦「待办」列的快速新增输入框 |
| `/` | 聚焦搜索框 |
| `?` | 打开快捷键帮助 |
| `Esc` | 关闭当前面板 |
| `Ctrl+Z` | 撤销上一步操作(macOS 用 `⌘Z`) |
| `Ctrl+Shift+Z` | 重做被撤销的操作(`Ctrl+Y` 同样可用) |
撤销历史只保留在内存里:刷新页面或换一个标签页即清空。在输入框内按 `Ctrl+Z` 撤销的是**文字输入**,不是任务操作。
卡片获得焦点(`Tab` 或点击)后:
| 按键 | 作用 |
| ------- | ------------------------- |
| `←` `→` | 把卡片移到上一列 / 下一列 |
| `↑` `↓` | 在列内上移 / 下移 |
| `Enter` | 打开任务详情 |
在输入框内输入时不会触发全局快捷键。方向键移动在筛选态下只允许跨列(列内排序需要看到完整列表才有意义);
「已完成」列的列内顺序由完成时间决定,`↑` `↓` 在该列不生效(拖拽同理只能跨列)。
---
## 两个视图
顶部导航可在两个视图之间切换:
| 视图 | 路径 | 组织方式 |
| ---- | -------- | ----------------------------------------------- |
| 看板 | `/` | 按**状态**:待办 / 进行中 / 已完成 |
| 今日 | `/today` | 按**紧急度**:已逾期 → 今天到期 → 进行中 → 待办 |
今日视图顶部有四张统计卡片(逾期 / 今天到期 / 进行中 / 本周完成),下方是可直接勾选完成的待办列表。
分组是**互斥**的:一张卡片只会出现在一组里,归属优先级为「逾期 > 今天到期 > 进行中 > 待办」,所以你能一眼数清今天到底有几件事。已完成和已归档的任务不会出现在今日视图。
只要任务未完成且未归档,就一定会出现在某个分组里 —— 包括没设截止日期的待办(归入「待办」组)。
---
## 安装与离线(PWA)
应用可以像原生应用一样安装到桌面或手机主屏,安装后支持**完全离线**使用 —— Service Worker 会预缓存页面与静态资源,断网后照常打开,数据本来就在本地。
- 桌面 Chrome / Edge:地址栏右侧会出现安装图标
- 手机:浏览器菜单 →「添加到主屏幕」
- 安装后长按图标有「今日」快捷方式,直达 `/today`
更新策略是 **prompt**:检测到新版本时会在底部弹出提示,由你决定何时刷新,不会打断正在进行的编辑。
图标由 `scripts/generate-icons.py`(Pillow)生成,需要改图标时运行:
```bash
python scripts/generate-icons.py
```
---
## 数据与备份(重要)
**数据只保存在你这台设备的浏览器里**(localStorage,键名 `kanban.tasks`)。没有服务端,也不会自动同步到其它设备。
这意味着以下操作会导致数据丢失:
- 清理浏览器数据 / 缓存
- 使用隐私模式
- 换浏览器或换设备
- Safari 的 7 天未访问存储清理策略
因此工具内置了三道保护:
1. **启动自动备份**:每次打开看板时留一份快照,最多保留最近 3 份,可在「备份」面板中一键恢复。
2. **覆盖前自动留档**:导入 JSON 或从快照恢复会整份替换数据,执行前会先把当前数据写一份快照,选错文件或选错备份也能退回去。
3. **手动导出**:「备份」面板 → 导出为 JSON,文件名形如 `kanban-2026-10-18.json`。
导入支持两种模式,选中文件后会先校验并显示预览,需要二次确认才会执行:
- **覆盖导入**:替换当前全部数据,并显示将被覆盖的任务数量
- **合并导入**:按 `id` 合并,同一条任务以 `updatedAt` 较新的一方为准;文件里新增的任务追加到对应列末尾,本地独有的任务保留(合并不是同步,不会删本地数据)。预览会显示「新增 X 个 / 更新 Y 个 / 保持 Z 个」
两种模式在确认前都会自动把当前数据写一份快照;文件不合法时不会改动任何现有数据。导出默认包含回收站里的任务,这样换设备后回收站也能一并带走。
任务详情里的「状态」可以直接切换待办 / 进行中 / 已完成,不必回到看板拖拽。编辑后如果没保存就关闭,抽屉会先提醒,不会静默丢弃草稿。
抽屉底部的「时间」区块是只读的:**创建** 与 **更新** 一律显示;切到「已完成」并保存后写入 **完成** 时间(未完成时标注「未完成」);归档任务额外显示归档时间。看板上已完成的卡片会在右下角标出完成日期,鼠标悬停任意卡片可看到精确到分钟的时间;回收站条目则同时给出创建时间与删除时间。
> 如果需要在多设备间同步,需要自行接入后端;`TaskRepository` 接口就是为此预留的扩展点(见下)。
---
## 部署
产物是纯静态文件,把 `dist/` 目录交给任意静态托管即可(Nginx、Vercel、Netlify、GitHub Pages 等)。
```bash
npm run build
# 将 dist/ 上传或指向静态服务器
```
### 部署到云服务器(当前实际架构)
服务器由 1Panel 管理,80/443 已被 **Nginx Proxy Manager** 占用,所以站点不装宿主机 Nginx,
而是跑一个 `nginx:alpine` 容器,由 NPM 反代:
```
浏览器 → NPM(80/443, 证书) → kanban 容器(:80) → /opt/kanban/dist
↑
CI runner 构建后 rsync 到这里(原地同步)
```
| 文件 | 用途 |
| -------------------------------------- | ---------------------------------------------------------------- |
| `deploy/nginx.docker.conf` | 容器内站点配置:SPA 回落、`sw.js` 不缓存、`/assets` 长缓存、gzip |
| `deploy/gitea-runner.config.yaml` | runner 配置:host 模式、`ubuntu-latest` 标签 |
| `deploy/gitea-runner.service` | runner 的 systemd 单元 |
| `deploy/deploy.sh` | 不走 CI 时的手动发布脚本 |
| `deploy/nginx.conf` + `server-init.sh` | 备选路径:80 端口空闲、直接用宿主机 Nginx 的机器 |
**容器启动命令**(已在服务器上运行,重建时照抄):
```bash
docker run -d --name kanban --restart unless-stopped \
--network 1panel-network \
-p 127.0.0.1:8081:80 \
-v /opt/kanban/dist:/usr/share/nginx/html:ro \
-v /opt/kanban/nginx.conf:/etc/nginx/conf.d/default.conf:ro \
nginx:alpine
```
**反向代理**:NPM 后台 → Hosts → Proxy Hosts → Add
| 字段 | 值 |
| ---------------- | -------------------- |
| Domain Names | `kanban.shoulin.net` |
| Scheme | `http` |
| Forward Hostname | `172.17.0.1` |
| Forward Port | `8081` |
其余站点都走 `172.17.0.1:端口`,这里保持一致(用容器名 `kanban:80` 实测也可通,但容器重建后 IP 可能变)。
SSL 页签挂证书并开启 Force SSL,域名解析到服务器 IP 即可。
**手动发布**(CI 不可用时):
```bash
SERVER=root@132.232.203.88 WEB_ROOT=/opt/kanban/dist ./deploy/deploy.sh
```
> 注意:`/opt/kanban/dist` 是容器挂载目录,**不能整目录 `mv`**——bind mount 跟的是 inode,
> 换目录后容器仍指向旧内容。`deploy.sh` 与 CI 都用 rsync 原地同步,就是为了避开这个坑。
> **HTTPS 不是可选项**:Service Worker 只在 HTTPS 或 localhost 下注册成功。用 IP 走 HTTP 访问时,
> 应用能打开,但「安装到桌面 / 离线可用 / 版本更新提示」全部失效。证书在 NPM 里配置。
### 其它静态托管
Netlify / Vercel 等平台,在 `dist/` 同级放一个 `_redirects` 或配置即可:
```
/* /index.html 200
```
> **注意**:应用使用前端路由(`/today`),因此**必须**配置 SPA 回落,否则直接访问或刷新 `/today` 会 404。
> 这不是可选项。若你的托管环境无法配置回落,可以改用 hash 路由(把 `src/router/index.ts` 里的
> `createWebHistory()` 换成 `createWebHashHistory()`),代价是 URL 变成 `/#/today`。
Service Worker 的 `navigateFallback` 已设为 `index.html`,因此**离线状态下**深链 `/today` 也能正常打开,
无需服务器参与。
---
## 技术栈
Vue 3 · TypeScript · Vite · Pinia · Vue Router · Tailwind CSS v4 · vue-draggable-plus (SortableJS) · dayjs · vite-plugin-pwa (Workbox) · Vitest · Playwright
## 目录结构
```
src/
├── components/ # 看板列、卡片、抽屉、工具栏、导航、PWA 提示等 UI 组件
├── composables/ # 可复用的组合式逻辑(键盘快捷键)
├── data/ # 持久化:schema 与迁移、仓库实现、快照
├── router/ # 路由定义(看板 / 今日)
├── stores/ # Pinia:任务、筛选、提示
├── types/ # 领域类型与常量
├── utils/ # 纯函数:排序、筛选、日期、归档、拖拽解析、今日分组、统计、任务补丁、合并、标签
└── views/ # 页面级组件(BoardView / TodayView)
docs/
├── adr.md # 架构决策记录
├── tasks.md # 开发任务卡(含验收标准)
└── tasks.json # 可导入看板的种子数据
e2e/ # Playwright 端到端测试
scripts/ # 图标生成等辅助脚本
.gitea/workflows/ # CI(lint / typecheck / test / build)
```
## 持续集成与自动部署
仓库内置 `.gitea/workflows/ci.yml`,分两个 job:
| Job | 触发条件 | 做什么 |
| -------- | ------------------------------------------ | --------------------------------------------------- |
| `verify` | push 与合并请求 | `npm ci` → `lint` → `typecheck` → `test` → `build` |
| `deploy` | push 到 `master` / `main` 且 `verify` 通过 | 重新构建 → `rsync` 到 `/opt/kanban/dist` → 线上自检 |
合并请求只跑校验、不部署。部署用 `rsync -a --delete` 原地同步,失败时旧文件仍在,不会把站点弄空;
同步完会自检根路径、`/today` 回落与 `sw.js`,任一异常就让 job 变红。
### Runner(自托管,已部署)
服务器 `132.232.203.88` 上的 `tencent-sh-1`,host 模式,systemd 常驻:
```bash
systemctl status gitea-runner
journalctl -u gitea-runner -n 50 --no-pager
```
配置见 `deploy/gitea-runner.config.yaml` 与 `deploy/gitea-runner.service`,几个要点:
- 标签 `ubuntu-latest:host`,任务直接在宿主机执行,不再套一层容器
- `DEFAULT_ACTIONS_URL=https://gitea.com` —— 这台机器访问 github.com 不通,action 必须换源
- 宿主机 Node 22 在 `/opt/node22`,软链到 `/usr/local/bin`;npm 源指向 `registry.npmmirror.com`
- runner 与站点同机,部署直接写本地目录,**不需要配置任何 Secrets**
### 为什么不用 SSH 部署
runner 与站点同一台机器,绕一圈 SSH 只是多一个故障点、多一份私钥要管。
真要跨机部署,用 `deploy/deploy.sh` 的 `SERVER=... WEB_ROOT=...` 形式即可。
E2E 需要真实浏览器,默认不放进 CI(要求 runner 预装 Chrome);请在本地或用带浏览器的 runner 执行 `npm run test:e2e`。
## 架构要点
- **单一数据源**:Pinia store 是内存态的唯一来源,所有变更经由 `TaskRepository` 落盘;拖拽也只消费事件,不接管数据。看板与今日视图共享同一份数据。
- **持久化端口**:`TaskRepository` 采用「整份快照读写」(`load` / `save`),而非细粒度 CRUD。要换成 IndexedDB 或后端 API,只需实现同一接口。
- **显式迁移**:落盘结构带 `schemaVersion`,升级规则登记在 `MIGRATIONS` 表中。当前版本为 3(v1 → v2 补 `archivedAt`,v2 → v3 补 `deletedAt` 以支持回收站)。
- **软删除是数据状态,不是筛选维度**:`deletedAt` 在 `activeTasks` 层统一剔除,列、计数、统计、今日视图、标签下拉都消费它;回收站单独由 `trashTasks` 提供。
- **损坏数据先备份再报错**:读取失败时把原始内容另存到 `kanban.tasks.corrupt.<时间戳>`,不覆盖用户数据。
- **视图与数据解耦**:今日视图只做「分组 + 排序」,全部逻辑在 `src/utils/today.ts` 与 `stats.ts` 的纯函数里,组件不持有业务规则。
更多取舍与理由见 [docs/adr.md](docs/adr.md)。
## 测试
- 单元测试:738 个用例,覆盖 `src/` 下的纯逻辑与组件行为。
- 端到端测试:28 个用例,分两组运行 —— 交互类跑 dev server,PWA 类跑生产构建(Service Worker 只在构建产物中生成)。覆盖「新增 → 拖拽 → 完成 → 刷新保持」「批量添加」「多选批量删除 + 撤销」「导出 → 清空 → 导入还原」「删除撤销」「搜索筛选」「键盘快捷键与方向键移动」「深色模式切换」「今日视图」「离线打开」等主链路,每个用例使用独立的浏览器上下文。
```bash
npm run test:coverage
npm run test:e2e
```
### 关于 E2E 使用的浏览器
默认驱动**系统已安装的 Chrome**(`channel: 'chrome'`),不下载 Playwright 自带的 Chromium —— 后者约 197MB,在网络受限的环境下容易超时失败。
如需改用其它通道:
```bash
PLAYWRIGHT_CHANNEL=msedge npm run test:e2e # 用系统 Edge
PLAYWRIGHT_CHANNEL=chromium npm run test:e2e # 用自带 Chromium(需先 npx playwright install chromium)
```
## 已知限制
- 单用户、单设备,无协作与同步能力(离线可用,但多设备之间不会同步)。
- 看板列固定为三列,不支持自定义。
- 「已完成」列固定按完成时间倒序,不支持手动调序(拖拽与方向键只能跨列)。
- 归档阈值固定为「已完成超过 7 天」,暂不可配置。
- 今日视图的分组规则固定(逾期 > 今天到期 > 进行中 > 待办),不可自定义。
- 统计只覆盖本周完成数等基础指标,没有趋势图或历史留存。
- 深色模式只跟随系统或手动三态切换,没有按时间段自动切换。
- 回收站固定保留 30 天,没有搜索 / 筛选,只有「按删除时间」一种顺序。
- 撤销栈只保留最近 50 步,且仅存在于当前会话与当前标签页:刷新或换标签页即清空,不跨设备同步;另一标签页改动数据后,本页的历史会作废。
- 导入冲突策略固定为「`updatedAt` 新者胜」,不可配置。
- 标签颜色是本机显示偏好,不随导出 JSON 或快照恢复携带,换设备后需要重新设置。