Docker Build and Push / build-image (push) Successful in 4m19s
1. BUILD_ID 改用 UTC+8 生成: CI runner 是 UTC 时区, 原来取本地时间会错位;
统一偏移 +8h 后按 UTC 读取 (vite.config.js makeBuildId)
2. 修复「我的项目」页反复弹新建框: createSignal 一次性消费,
ProjectList 处理后回调清零 (此前 TaskPage 状态在 /task 与 /task/list
间保留, 信号残留导致每次进入都触发)
3. 新增项目信息块 (T-PJ-13):
- Project.notes Json ([{title,content}], <=50 块 / 标题<=50 字 / 内容<=20000 字)
- 迁移 20261024130000: 加列(NULL)->回填'[]'->收紧 NOT NULL, 存量行安全
- GET 详情返回 notes; PUT 接受 notes 整体替换
- 项目管理新增「信息块」分区: 列表预览 + 底部弹层独立编辑(标题+多行
textarea+删除), 键盘弹出复用 useKeyboardInset 抬升
- 文档: 需求 T-PJ-13 (149 条) + 功能设计对应行
已验证: 109 文件语法 0 错误、文档 149/149 覆盖
340 lines
21 KiB
Markdown
340 lines
21 KiB
Markdown
# iBoard V0.2 任务模块 功能设计
|
||
|
||
| 项 | 内容 |
|
||
| --- | --- |
|
||
| 版本 | V0.2(对应分支 `dev-0.2`,应用版本 `0.2.0`) |
|
||
| 文档状态 | **设计基线**(待评审) |
|
||
| 上游文档 | `任务模块需求文档.md`(**149 条需求点**,编号 `T-*`) |
|
||
| 配套文档 | `任务模块设计文档-框架设计.md`、`任务模块数据库设计文档.md`、`任务模块界面设计.html` |
|
||
| 对应关系 | **本文与需求点一一对应**:每条需求点均给出「设计落点(功能/组件/接口/表字段)」与「实现要点」 |
|
||
|
||
> **阅读方式**
|
||
> 1. 本文按需求文档的模块码组织(`VW` / `COL` / `BD` / `FL` / `TK` / `AS` / `ST` / `PJ` / `PM` / `CP` / `UI` / `DT` / `NF` / `EX`)。
|
||
> 2. 每条需求点一行:**编号 → 设计落点 → 实现要点**。设计落点中的组件名、接口路径、表字段均与《任务模块数据库设计文档》《任务模块界面设计》一致。
|
||
> 3. 文末 §15 给出**覆盖自检表**,确保 149 条无遗漏。
|
||
|
||
---
|
||
|
||
## 0. 通用约定
|
||
|
||
### 0.1 契约命名
|
||
|
||
| 类别 | 约定 | 示例 |
|
||
| --- | --- | --- |
|
||
| 状态枚举 | `todo` / `doing` / `done` | 未开始 / 执行中 / 完成 |
|
||
| 列 | `BoardColumn` 实体,按 `sortOrder` 升序 | 默认 `Plan/Doing/Action/Done` |
|
||
| 保留选项 | `never` / `today` / `3d` / `week` / `month` / `forever` | 永不显示 / 当天 / 3 天 / 本周 / 本月 / 永久显示 |
|
||
| 视图 | `list` / `board` | 设备端记忆 |
|
||
| 设备端键 | `iboard_task_prefs` | `{ lastProjectId, viewMode: { [projectId]: 'list'\|'board' } }` |
|
||
|
||
### 0.2 接口总览
|
||
|
||
| 方法 | 路径 | 用途 |
|
||
| --- | --- | --- |
|
||
| GET | `/api/projects` | 我参与的项目(含个人默认项目、归档标记) |
|
||
| POST | `/api/projects` | 新建普通项目(同时初始化 4 个默认列) |
|
||
| GET | `/api/projects/:id` | 项目详情(含成员、列配置) |
|
||
| PUT | `/api/projects/:id` | 改名 / 归档 |
|
||
| DELETE | `/api/projects/:id` | 删除项目(个人项目 400) |
|
||
| POST | `/api/projects/:id/members` | 加成员(个人项目 400) |
|
||
| DELETE | `/api/projects/:id/members/:uid` | 移成员 / 自己退出 |
|
||
| GET/POST | `/api/projects/:id/columns` | 列列表 / 新增列 |
|
||
| PUT | `/api/columns/:id` | 改名 / 改绑定 / 改保留选项 |
|
||
| DELETE | `/api/columns/:id` | 删列(需 `moveToColumnId`,最后列 400) |
|
||
| PUT | `/api/projects/:id/columns/order` | 列排序 |
|
||
| GET | `/api/projects/:id/tasks` | 任务列表(含子任务与列信息) |
|
||
| POST | `/api/projects/:id/tasks` | 新建任务 / 子任务 |
|
||
| GET | `/api/tasks/:id` | 任务详情 |
|
||
| PUT | `/api/tasks/:id` | 改状态 / 换列 / 改字段(触发裁决) |
|
||
| DELETE | `/api/tasks/:id` | 删除任务(级联子任务) |
|
||
| POST | `/api/tasks/:id/copy` | 跨项目拷贝 |
|
||
|
||
---
|
||
|
||
## 1. 视图体系(T-VW)
|
||
|
||
| 编号 | 设计落点 | 实现要点 |
|
||
| --- | --- | --- |
|
||
| T-VW-01 | `ProjectView.jsx` 内 `viewMode` 分支渲染 `ListView` / `BoardView` | 两视图共用 `TaskStore` 的同一份任务数据,仅渲染方式不同 |
|
||
| T-VW-02 | `SwipeTabs`/分段控件切换 `viewMode` | 纯本地状态切换,不触发路由跳转、不重新拉取数据(满足"无整页刷新") |
|
||
| T-VW-03 | `prefs.js` → `viewMode[projectId]` | 按项目 id 分键存储,A/B 项目互不影响 |
|
||
| T-VW-04 | `ProjectView` 挂载时读 `prefs.getViewMode(projectId)` | 有值用之;无值走 T-VW-05 默认 |
|
||
| T-VW-05 | 默认值 `'list'` | 新项目首次进入为列表 |
|
||
| T-VW-06 | `localStorage.iboard_task_prefs` | **不落库、不同步**;换设备不继承(与 T-FL-15 的项目级配置区分) |
|
||
| T-VW-07 | `prefs.getLastProjectId()` / `setLastProjectId()` | 进入 `/task` 时读取;无记录或记录的项目已不可见 ⇒ 回落个人默认项目 |
|
||
| T-VW-08 | `TaskPage` 首屏上部 `RecentProjects` 条 | 取最近访问的 3 个项目(按设备端访问时间倒序),末尾「更多项目」入口 → `/task/list`(完整项目列表 `ProjectList`,从「更多」进入) |
|
||
| T-VW-09 | `TaskPage` 首屏下部直接内嵌 `ProjectView` | 当前项目 = 上次访问项目(无记录 ⇒ 个人默认项目);点首屏项目卡片切换当前项目,下部整体切换,不跳转页面 |
|
||
| T-VW-10 | `prefs.js` 增 `recentOrder[]`(设备端) | 长按卡片拖动排序并持久化;无手动记录时按最近访问时间自动排列;项目不可见时从记录剔除 |
|
||
|
||
> **首屏结构调整(T-VW-08/09)**:`TaskPage`(`/task`)不再以项目列表为首屏,改为「最近项目条 + 当前项目 `ProjectView`」;完整项目列表迁移至 `/task/list`。旧路由 `/task/:pid` 保留(打开指定项目),供深链使用。
|
||
|
||
> **实现细节**:`lastProjectId` 指向的项目若已被退出/删除,视为"无记录",静默回落到个人默认项目,不弹错。
|
||
|
||
---
|
||
|
||
## 2. 看板列管理(T-COL)
|
||
|
||
| 编号 | 设计落点 | 实现要点 |
|
||
| --- | --- | --- |
|
||
| T-COL-01 | `POST /api/projects` 内部初始化 4 列 | 事务内创建 `Plan(todo, sort 0)`、`Doing(doing,1)`、`Action(null,2)`、`Done(done,3)` |
|
||
| T-COL-02 | `ColumnSettings` → `POST /api/projects/:id/columns` | `sortOrder = max+1` 追加末尾;`boundStatus=null`、`retention='forever'`(B-18) |
|
||
| T-COL-03 | `PUT /api/columns/:id` | 改 `name`;前端 `TaskStore` 整体替换列列表后重渲染 |
|
||
| T-COL-04 | `DELETE /api/columns/:id?moveToColumnId=` | 二次确认弹窗;见 T-EX-16 |
|
||
| T-COL-05 | `PUT /api/projects/:id/columns/order` | 传完整 `[columnId…]` 数组,服务端按索引重写 `sortOrder` |
|
||
| T-COL-06 | 服务端校验总量 | `1 <= count <= 10`;达上限拒绝新增(T-EX-19)、仅剩 1 列拒绝删除(T-EX-17) |
|
||
| T-COL-07 | `BoardColumn.name` 校验 | trim 后非空、长度 ≤ 12;`@@unique([projectId,name])` 兜底重名 409 |
|
||
| T-COL-08 | 列删除即绑定消失 | 绑定存在列记录上,列删则解除,无需额外清理 |
|
||
| T-COL-09 | `requireProjectMember` | 项目级配置,任一成员可改;不影响其他项目 |
|
||
| T-COL-10 | `ListView` 不分组但仍显示列信息 | 任务行内以小标签显示所属列名(可关闭),归属始终正确 |
|
||
|
||
---
|
||
|
||
## 3. 看板与列-状态绑定(T-BD)
|
||
|
||
> 联动的**唯一实现**在 `server/task-domain/resolveStatus.js`,前端只做乐观更新。判定矩阵见《框架设计》§2.3。
|
||
|
||
| 编号 | 设计落点 | 实现要点 |
|
||
| --- | --- | --- |
|
||
| T-BD-01 | `BoardView` 按 `columns` 数组渲染列 | 列来自项目配置,可增删改排序 |
|
||
| T-BD-02 | `ColumnSettings` 每列一组「绑定开关 + 状态下拉」 | 写入 `BoardColumn.boundStatus`(`null`/`todo`/`doing`/`done`) |
|
||
| T-BD-03 | 无唯一约束限制多列绑定同状态 | 允许 `done` 与 `action` 同为 `done` |
|
||
| T-BD-04 | `onColumnChange()` | 目标列 `boundStatus != null` ⇒ `status = boundStatus` |
|
||
| T-BD-05 | `onStatusChange()` | 状态变更后若当前列已匹配则不动作,否则走归位 |
|
||
| T-BD-06 | 归位取列序首个匹配 | `columns` 按 `sortOrder` 排序后 `find()` 首个 |
|
||
| T-BD-07 | **只看目标列** | 目标列不绑定 ⇒ 保留原状态;**无"拖出回退"规则** |
|
||
| T-BD-08 | 不绑定列完全正交 | 拖入/拖出、改状态均不改该列以外语义 |
|
||
| T-BD-14 | `resolveColumn(task, newStatus)` | 当前列 `boundStatus !== newStatus` ⇒ 触发归位 |
|
||
| T-BD-15 | 按当前 `sortOrder` 取首个 | 用户调序后归位目标随之改变 |
|
||
| T-BD-16 | 无匹配返回 `null` | `null` ⇒ 不改 `columnId`,状态照常写库 |
|
||
| T-BD-09 | 初始化默认值 | `Plan=todo`、`Doing=doing`、`Done=done`、`Action=null` |
|
||
| T-BD-10 | 项目级配置 | 任一成员可改;不同项目独立 |
|
||
| T-BD-11 | 配置变更不回溯 | 保存配置时**不触发**任何任务更新语句 |
|
||
| T-BD-12 | 保存后返回最新列列表 | 前端整体替换 + 重算可见集合,无需刷新 |
|
||
| T-BD-13 | `resolveStatus()` 首行判 `parentId` | 子任务直接跳过全部联动 |
|
||
|
||
---
|
||
|
||
## 4. 已完成任务显示控制(T-FL)
|
||
|
||
| 编号 | 设计落点 | 实现要点 |
|
||
| --- | --- | --- |
|
||
| T-FL-01 | `BoardColumn.retention` | 每列独立字段 |
|
||
| T-FL-02 | `ColumnSettings` 下拉 6 项 | 固定枚举,无自由输入 |
|
||
| T-FL-03 | `retention='forever'` | 判定恒为可见 |
|
||
| T-FL-04 | `retention='never'` | 已完成任务恒不可见 |
|
||
| T-FL-05 | `retention='today'` | `doneAt >= 今天 00:00` |
|
||
| T-FL-06 | `retention='3d'` | `doneAt >= 今天-2天 00:00` |
|
||
| T-FL-07 | `retention='week'` | `doneAt >= 本周一 00:00` |
|
||
| T-FL-08 | `retention='month'` | `doneAt >= 本月1日 00:00` |
|
||
| T-FL-09 | `task-domain/retention.js` + 前端定时重算 | 超出窗口自动隐藏,无需操作 |
|
||
| T-FL-10 | 判定字段 `Task.doneAt` | 与当前列无关;取消完成时 `doneAt=null` |
|
||
| T-FL-11 | 前端可见性统一走同一函数 | 列表与看板共用 `isTaskVisible(task, column)`;列表不分组但归属不变 |
|
||
| T-FL-12 | 判定前置 `status !== 'done'` | 未完成任务恒可见 |
|
||
| T-FL-13 | 纯展示层过滤 | 不修改任何字段;接口返回全量,前端过滤(或服务端按需过滤但不改数据) |
|
||
| T-FL-14 | 列计数取过滤后集合长度 | 列头计数与卡片数同源,杜绝不一致 |
|
||
| T-FL-15 | 存在 `BoardColumn` 上 | 项目级共享,非个人偏好(对比 T-VW-06 设备端) |
|
||
|
||
> **实现细节**:保留判定在前后端各有一份实现(服务端用于分页/统计,前端用于即时过滤),但**必须共用同一口径常量**,跨度边界由 `date.js` 单测覆盖。
|
||
|
||
---
|
||
|
||
## 5. 任务字段体系(T-TK)
|
||
|
||
| 编号 | 设计落点 | 实现要点 |
|
||
| --- | --- | --- |
|
||
| T-TK-01 | `Task` 表 | 字段:`title`/`description`/`projectId`/`columnId`/`status`/`assigneeId`/`creatorId`/`doneAt`/`sortOrder` + 关联成员/标签/扩展 |
|
||
| T-TK-02 | `title` 非空校验 | 其余字段允许空;trim 后为空 ⇒ 400 |
|
||
| T-TK-03 | `status` 枚举三值 | DB 层用 `VARCHAR(8)` + 应用层校验(避免 ENUM 改值困难) |
|
||
| T-TK-04 | `TaskDetail` → `ExtrasCard` 组件 | 任意条目增删改,无字段上限(建议 ≤ 20 条) |
|
||
| T-TK-05 | `Task.extras` JSON | 结构 `[{label, value, url?}]`;`value` 支持纯文本或 URL |
|
||
| T-TK-06 | `ExtrasCard` 默认 `collapsed=true` | 首次打开详情为收起态 |
|
||
| T-TK-07 | 无必填校验 | 全空可保存 |
|
||
| T-TK-08 | `Task.assigneeId` 单值 | 重复设置以最后一次为准 |
|
||
| T-TK-09 | 新建时服务端默认赋值 | 未显式传 `assigneeId` ⇒ 取 `req.user.id`(B-10) |
|
||
| T-TK-10 | `assigneeId` 允许 `null` | 显式传 `null` 即清空,UI 显示「未指派」 |
|
||
| T-TK-11 | `TaskMember` 关联表 | 多对多,`@@unique([taskId,userId])` 去重 |
|
||
| T-TK-12 | `TaskTag` 表 + 卡片标签渲染 | 多标签,展示于卡片/条目 |
|
||
| T-TK-13 | 服务端维护 | `creatorId`/`createdAt` 不可改;`updatedAt` 自动刷新 |
|
||
|
||
---
|
||
|
||
## 6. 负责人 & 成员(T-AS)
|
||
|
||
| 编号 | 设计落点 | 实现要点 |
|
||
| --- | --- | --- |
|
||
| T-AS-01 | `TaskCard` 负责人区 | 字号 13px/600 + 头像,位于标题下方主信息位 |
|
||
| T-AS-02 | `TaskCard` 成员区 | 字号 11px/400、色 `#999`,位于卡片底部 |
|
||
| T-AS-03 | `UserPicker` 数据源 = 项目成员 | 只列 `ProjectMember` 中的用户 |
|
||
| T-AS-04 | 同上(多选模式) | 不可选项目外用户 |
|
||
| T-AS-05 | `assigneeId === null` 时渲染「未指派」 | 占位样式弱化,可点击再次指派 |
|
||
| T-AS-06 | 渲染时 `LEFT JOIN ProjectMember` 判定 | 非成员 ⇒ 显示「已退出」灰标;见 T-EX-06 |
|
||
| T-AS-07 | 权限模型 | 无"负责人可编辑"概念,所有成员全读写(T-PM-02) |
|
||
|
||
---
|
||
|
||
## 7. 子任务体系(T-ST)
|
||
|
||
| 编号 | 设计落点 | 实现要点 |
|
||
| --- | --- | --- |
|
||
| T-ST-01 | `Task.parentId` 自关联 | 创建子任务时校验:父任务 `parentId IS NULL`,否则 400 |
|
||
| T-ST-02 | 一致性校验 | 子任务 `projectId` 必须等于父任务 `projectId`,否则 400(T-EX-12) |
|
||
| T-ST-03 | 状态独立 | 父/子状态字段各自独立,无级联更新 |
|
||
| T-ST-04 | `SubtaskRow` 缩进 | 左侧 28px 缩进 + 层级线 |
|
||
| T-ST-05 | 折叠/展开按钮 | 组件内存状态,折叠时不渲染子项 |
|
||
| T-ST-06 | `TaskCard` 内嵌 `SubtaskRow` 列表 | 简洁单行:复选框 + 标题,最多显示 3 条 + 「查看全部」 |
|
||
| T-ST-07 | 卡片内直接勾选 | 勾选调 `PUT /api/tasks/:id {status}`;完整编辑走 `TaskDetail` 弹窗 |
|
||
| T-ST-08 | 子任务跳过绑定 | 见 T-BD-13;父任务完成时子任务状态不变 |
|
||
| T-ST-09 | `SubtaskRow` 带 `StatusDot` | 三色小圆点:灰=未开始 / 蓝=执行中 / 绿=完成 |
|
||
| T-ST-10 | 看板只渲染顶层任务 | 看板查询 `WHERE parentId IS NULL` |
|
||
| T-ST-11 | `Task.parentId` 级联删除 | `onDelete: Cascade`(T-EX-07) |
|
||
| T-ST-12 | `useLongPressDrag` 落点为目标顶层任务 | 设置 `parentId` |
|
||
| T-ST-13 | 目标为子任务 ⇒ 拒绝 | Toast「仅支持一级子任务」 |
|
||
| T-ST-14 | 被拖任务有子任务 ⇒ 拒绝 | 避免二级嵌套 |
|
||
| T-ST-15 | 保持原列与状态 | 仅更新 `parentId`,不触发状态裁决 |
|
||
|
||
---
|
||
|
||
## 8. 项目体系(T-PJ)
|
||
|
||
| 编号 | 设计落点 | 实现要点 |
|
||
| --- | --- | --- |
|
||
| T-PJ-01 | 用户首次访问自动创建 | 按 `isPersonal=true` 且 `ownerId=userId` 幂等创建 |
|
||
| T-PJ-02 | `DELETE /api/projects/:id` 校验 `isPersonal` | 为真 ⇒ 400(T-EX-09) |
|
||
| T-PJ-03 | `POST .../members` 校验 `isPersonal` | 为真 ⇒ 400 |
|
||
| T-PJ-04 | `name` 初始 `${user.name}的任务` | 允许 `PUT` 改名,`isPersonal` 不变 |
|
||
| T-PJ-05 | `POST /api/projects` | 创建者自动写入 `ProjectMember` |
|
||
| T-PJ-06 | 成员接口 | 增/删成员,即时影响可见性 |
|
||
| T-PJ-07 | `DELETE .../members/:uid` 允许 `uid == ownerId` | 创建人退出后不再是成员 ⇒ 不可见 |
|
||
| T-PJ-08 | 删除最后成员不删项目 | 仅删 `ProjectMember`(B-11);见 T-EX-11 |
|
||
| T-PJ-09 | `Project.archived` | 列表分「进行中 / 已归档」 |
|
||
| T-PJ-10 | 列表查询按成员关系过滤 | `WHERE EXISTS ProjectMember(userId)` |
|
||
| T-PJ-11 | 新组件 `ProjectManage`(项目管理页) | 分区承载:成员(复用 `ProjectMembers` 逻辑)、列设置(复用 `ColumnSettings`)、项目信息;`ProjectView` 头部仅保留「项目管理」一个入口 |
|
||
| T-PJ-12 | `ProjectManage` 的「项目信息」分区 | 改名(`PUT /api/projects/:id {name}`)、归档/取消归档(`{archived}`)、删除(个人项目隐藏入口)、退出(仅创建人可见);删除与退出均有二次确认 |
|
||
| T-PJ-13 | `Project.notes` Json + `ProjectManage` 的「信息块」分区 | 存 `[{title, content}]`(≤50 块,标题 ≤50 字、内容 ≤20000 字);`PUT {notes}` 整体替换;底部弹层独立编辑(标题输入 + 多行 textarea + 删除),键盘弹出时复用 `useKeyboardInset` 抬升 |
|
||
|
||
---
|
||
|
||
## 9. 项目权限与入口(T-PM)
|
||
|
||
| 编号 | 设计落点 | 实现要点 |
|
||
| --- | --- | --- |
|
||
| T-PM-01 | `requireProjectMember` 中间件 | 非成员 403;列表查询天然过滤 |
|
||
| T-PM-02 | 无角色区分 | 所有成员对任务/列/成员均有全读写 |
|
||
| T-PM-03 | `ProjectMember.permission` 字段 | 本期恒为 `member`,预留分级扩展位 |
|
||
| T-PM-04 | 归档接口无权限限制 | 任意成员可归档(`PUT /api/projects/:id {archived}`) |
|
||
| T-PM-05 | 复用 `authRequired` | 未登录 401 |
|
||
| T-PM-06 | `BottomNav.TABS` 插入 `{ key:'/task', label:'任务' }` | 位于 `打卡` 之后;`HomePath` 仍为 `/checkin` |
|
||
| T-PM-07 | `prefs.lastProjectId` | 设备端;无记录 ⇒ 个人默认项目 |
|
||
|
||
---
|
||
|
||
## 10. 跨项目拷贝(T-CP)
|
||
|
||
| 编号 | 设计落点 | 实现要点 |
|
||
| --- | --- | --- |
|
||
| T-CP-01 | `POST /api/tasks/:id/copy` | 复制而非移动,源任务零改动 |
|
||
| T-CP-02 | `copyTask()` 字段映射 | 标题/描述/状态/列/扩展/标签/成员 全量复制 |
|
||
| T-CP-03 | 递归复制子任务 | 只复制一层(子任务无更深层级) |
|
||
| T-CP-04 | `creatorId = req.user.id` | 覆盖源创建人 |
|
||
| T-CP-05 | 无任何关联字段 | 拷贝后两条独立记录 |
|
||
| T-CP-06 | 校验目标项目成员身份 | 非成员 403 |
|
||
| T-CP-07 | 人员交集过滤 | 保留属于目标项目成员者,返回 `dropped` 名单(T-EX-08) |
|
||
| T-CP-08 | 子任务 `projectId` 同步改写 | 与父任务一致 |
|
||
| T-CP-09 | 状态与列沿用不纠正 | 见 T-EX-14 |
|
||
|
||
---
|
||
|
||
## 11. UI 交互规范(T-UI)
|
||
|
||
| 编号 | 设计落点 | 实现要点 |
|
||
| --- | --- | --- |
|
||
| T-UI-01 | 列表条目结构 / 快速新增 / 子任务折叠 | 参考 Todoist:底部输入框回车即建 |
|
||
| T-UI-02 | `TaskCard` 精简版 | 仅标题、状态点、负责人、子任务摘要;无边框装饰 |
|
||
| T-UI-03 | 单 `TaskStore` | 任一视图修改写入 store,另一视图读取同一数据 |
|
||
| T-UI-04 | 视图切换纯本地 | 不重新请求,不重载页面 |
|
||
| T-UI-05 | 复用 `.card` 与安全区变量 | 375~430px 无横向滚动;底部预留 TabBar 高度 |
|
||
| T-UI-06 | 勾选即时变色 | 乐观更新,先改 UI 再发请求 |
|
||
| T-UI-07 | `useLongPressDrag` | 长按 300ms 拖拽 + 「⋯」菜单「移动到」兜底 |
|
||
| T-UI-08 | `EmptyState` 组件 | 无项目 / 无任务 / 无子任务三档空态 + CTA |
|
||
| T-UI-09 | `LoadingState` 骨架屏 | 首屏与切换视图 |
|
||
| T-UI-10 | `ErrorState` 组件 | 展示错误 + 重试按钮 |
|
||
| T-UI-11 | 弹层容器统一 `padding-bottom: calc(env(safe-area-inset-bottom) + 20px)` | 底部按钮(删除等)与屏幕底边保持安全距离;小屏下弹层 `max-height` + 内部滚动,底部按钮始终完整可见 |
|
||
|
||
---
|
||
|
||
## 12. 数据需求(T-DT)
|
||
|
||
| 编号 | 设计落点 | 实现要点 |
|
||
| --- | --- | --- |
|
||
| T-DT-01 | `Project` / `ProjectMember` | 见《数据库设计文档》 |
|
||
| T-DT-02 | `BoardColumn.boundStatus` | 每列独立字段,允许多列同值 |
|
||
| T-DT-03 | `BoardColumn.retention` | 六档枚举字符串 |
|
||
| T-DT-04 | `Task` | 含 `parentId` 自关联、`assigneeId`、`status`、`columnId`、`extras` |
|
||
| T-DT-05 | `TaskMember` / `TaskTag` | 关联表;`extras` 为 JSON |
|
||
| T-DT-06 | 设备端 | 不落库 |
|
||
| T-DT-07 | 索引 | `(projectId, columnId)`、`(projectId, status)`、`(parentId)` |
|
||
| T-DT-08 | 迁移文件 | `prisma migrate deploy` 流程 |
|
||
| T-DT-09 | `Project.archived` / 任务无物理删 | 归档保留历史 |
|
||
|
||
---
|
||
|
||
## 13. 非功能需求(T-NF)
|
||
|
||
| 编号 | 设计落点 | 实现要点 |
|
||
| --- | --- | --- |
|
||
| T-NF-01 | 单请求聚合 + 乐观更新 | 见《框架设计》§4.2 |
|
||
| T-NF-02 | 不依赖桌面专有 API | 触控事件统一 `touch` / `pointer` 兼容 |
|
||
| T-NF-03 | `src/pages/task/*` + `server/routes/*.js` | 与既有模块结构一致 |
|
||
| T-NF-04 | 路由挂载于 `server/index.js` | 沿用既有组织方式 |
|
||
| T-NF-05 | 千级任务可用 | 索引 + 分页/虚拟滚动 |
|
||
|
||
---
|
||
|
||
## 14. 异常与边界(T-EX)
|
||
|
||
| 编号 | 设计落点 | 实现要点 |
|
||
| --- | --- | --- |
|
||
| T-EX-01 | 前端 500ms 防抖 + 服务端幂等判定 | 相同终态请求直接返回当前任务 |
|
||
| T-EX-02 | 乐观更新快照回滚 | 失败恢复快照 + Toast + 重试按钮 |
|
||
| T-EX-03 | 列配置改为不绑定 | 已有任务保持不动 |
|
||
| T-EX-04 | 状态与列不匹配 | 走 T-BD-14~16 归位 |
|
||
| T-EX-05 | 拖到不绑定列 | 状态保留,不回退 |
|
||
| T-EX-06 | 非成员渲染「已退出」 | 见 T-AS-06 |
|
||
| T-EX-07 | 级联删除子任务 | 确认弹窗提示条数 |
|
||
| T-EX-08 | 拷贝丢弃人员 | 返回 `dropped` 并提示 |
|
||
| T-EX-09 | 个人项目两个保护接口 | 400 + 明确文案 |
|
||
| T-EX-10 | 列空态 | 展示该列空态,不报错 |
|
||
| T-EX-11 | 无成员项目 | 静默不可见,无特判报错 |
|
||
| T-EX-12 | 子任务跨项目 | 服务端 400 |
|
||
| T-EX-13 | 拖拽降级 | 保持原列与状态 |
|
||
| T-EX-14 | 拷贝状态/列无法对应 | 沿用源值,不纠正 |
|
||
| T-EX-15 | 拖到自身或后代 | 拒绝 + Toast |
|
||
| T-EX-16 | 删除有任务的列 | 二次确认 + 迁移到指定列(默认相邻)+ 重新裁决 |
|
||
| T-EX-17 | 删除最后一列 | 400「至少保留一列」 |
|
||
| T-EX-18 | 跨周期当场消失 | 定时重算 + 退场动画 + 重排 |
|
||
| T-EX-19 | 达到列数上限 | 拒绝新增 + 提示 |
|
||
|
||
---
|
||
|
||
## 15. 覆盖自检表
|
||
|
||
| 模块 | 编号范围 | 条数 | 本文对应章节 |
|
||
| --- | --- | --- | --- |
|
||
| 视图体系 | T-VW-01 ~ 10 | 10 | §1 |
|
||
| 看板列管理 | T-COL-01 ~ 10 | 10 | §2 |
|
||
| 列-状态绑定 | T-BD-01 ~ 16 | 16 | §3 |
|
||
| 已完成显示控制 | T-FL-01 ~ 15 | 15 | §4 |
|
||
| 任务字段体系 | T-TK-01 ~ 13 | 13 | §5 |
|
||
| 负责人 & 成员 | T-AS-01 ~ 07 | 7 | §6 |
|
||
| 子任务体系 | T-ST-01 ~ 15 | 15 | §7 |
|
||
| 项目体系 | T-PJ-01 ~ 13 | 13 | §8 |
|
||
| 项目权限与入口 | T-PM-01 ~ 07 | 7 | §9 |
|
||
| 跨项目拷贝 | T-CP-01 ~ 09 | 9 | §10 |
|
||
| UI 交互规范 | T-UI-01 ~ 11 | 11 | §11 |
|
||
| 数据需求 | T-DT-01 ~ 09 | 9 | §12 |
|
||
| 非功能需求 | T-NF-01 ~ 05 | 5 | §13 |
|
||
| 异常与边界 | T-EX-01 ~ 19 | 19 | §14 |
|
||
| **合计** | — | **149** | — |
|
||
|
||
> 需求文档共 **149 条**需求点,本文逐条给出设计落点,覆盖率 **149/149**。(其中 `T-BD-14~16`、`T-COL-*`、`T-EX-18~19`、`T-VW-08~10`、`T-PJ-11/12`、`T-UI-11` 为评审后新增,编号不连续,非笔误。)
|