Files
iboard/docs/V0.2/任务模块/任务模块功能设计.md
T
jif2.zhang b71ceca3f2
Docker Build and Push / build-image (push) Successful in 4m19s
fix(V0.2): BUILD_ID 时区 / 新建弹框残留 / 项目信息块
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 覆盖
2026-10-24 21:37:05 +08:00

21 KiB
Raw Blame History

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 为评审后新增,编号不连续,非笔误。)