这一课我们继续沿着"任务管理页主线"往前推进。
上一课你已经做到:
- 在任务详情抽屉里直接建立批量选择上下文
- 选中当前任务
- 选中同状态任务
- 选中同优先级任务
- 清空当前批量选择
但那时候还差最后半步。
因为用户虽然已经能在详情里"选一批任务",却还不能在详情里"直接把这批任务处理完"。
所以这节课真正要补上的就是:
让任务详情抽屉从"建立批量上下文的入口",继续升级成"完成批量处理的闭环入口"。
也就是说,用户现在可以直接在详情抽屉里:
- 批量改状态
- 批量删除
- 并且当当前详情任务也被批量删掉时,自动切到相邻仍然存在的任务
这就更像真实后台系统了。
这一课一句话在做什么?
这一课本质上是在做:
把"看一条任务 -> 选一批任务 -> 处理这一批任务"完整闭环起来。
重点不是多加几个按钮。
重点是把三件事真正接起来:
- 详情抽屉继续只负责发起意图
- 页面层继续复用已有批量处理逻辑
- 删除当前详情任务时,详情上下文还能自然跳到相邻任务
为什么这一步非常像真实后台?
因为真实后台里,用户很少只"看完一条就结束"。
更常见的路径其实是:
- 打开一条任务详情
- 发现它代表一类任务
- 直接把这一类任务一起推进
例如:
- 看一条
待开始任务后,想把所有待开始任务一起改成进行中 - 看一条中优先级任务后,想把同类任务一起删掉
如果这时候用户必须:
- 先回到列表顶部
- 再去找批量操作栏
- 再重复执行动作
体验就会断裂。
所以这一课的核心价值是:
让详情抽屉不只是"上下文查看器",还成为真正的工作流执行入口。
这节课最关键的设计结论
1. 详情抽屉仍然是展示层,不直接掌握业务状态
这一课里,TaskDetailDrawer.vue 新增了两个事件:
change-selected-statusdelete-selected
但它依然没有直接去改:
selectedTaskIds- 任务列表数据
- 当前详情 id
它只是把意图抛给页面层。
这说明一个非常重要的原则:
即使功能越来越强,展示组件也不应该顺手接管跨区域业务状态。
2. 新入口最好复用旧逻辑,而不是复制一套逻辑
这一课没有新写一套"详情专用批量改状态逻辑"。
也没有新写一套"详情专用批量删除逻辑"。
而是继续复用页面层已有的:
handleUpdateSelectedTasksStatushandleDeleteSelectedTasks
这很重要。
因为真正可靠的工程写法通常不是:
- 每加一个入口,就复制一套业务逻辑
而是:
- 新入口接到同一套页面级业务能力上
这样后面无论从哪里发起批量处理:
- 批量操作栏
- 详情抽屉
都会走同一套规则。
3. "当前详情任务也参与了批量处理"需要单独考虑
这一课里,页面层在批量改状态前会先记录:
- 当前详情任务是否也在已选集合里
这样处理成功后,提示文案就可以更准确地告诉用户:
- 当前详情上下文也已经同步更新
这是一种很典型的后台交互细节。
因为用户不只关心:
- 改了多少条
还关心:
- 我现在正在看的这条有没有一起更新
4. 批量删除时,"详情切到哪条"值得单独抽成纯函数
这一课新抽了一个纯函数:
getTaskDetailBatchDeleteFallbackId
它专门处理这个问题:
如果当前详情任务正好包含在这次批量删除里,删除后详情应该切到哪一条?
规则是:
- 先沿着当前详情位置往后找还活着的任务
- 后面没有,再往前找
- 如果前后都没有,就关闭详情
这类逻辑很适合抽成纯函数。
因为它:
- 决策清晰
- 不依赖组件实例
- 非常适合单元测试
5. E2E 测试必须验证"当前代码构建产物",不能误测旧 dist
这一课顺手修了一个很关键的测试工程问题:
playwright.config.ts
之前 CI=1 时只启动 vite preview,如果本地 dist 不是最新的,就可能出现:
- 你改了源码
- 但 E2E 跑的还是旧构建产物
现在已经改成:
npm run build && npm run preview
这说明一个很重要的工程意识:
端到端测试不只是"会点页面",还必须保证自己验证的是当前真实代码。
这次主要改了哪些文件?
这一课主要涉及这些文件:
src/components/tasks/TaskDetailDrawer.vuesrc/views/TasksView.vuesrc/utils/taskDetailLinkage.tssrc/utils/__tests__/taskDetailLinkage.spec.tssrc/components/tasks/__tests__/taskDetailDrawer.spec.tse2e/pages/TasksPage.tse2e/app.spec.tsplaywright.config.tsdocs/README.md
另外新增了本节文档:
docs/43-task-detail-drawer-batch-actions-and-delete-linkage.md
在 TaskDetailDrawer.vue 里学什么?
这一课给详情抽屉新增了一个真正执行动作的 批量处理 区块。
1. "批量上下文"和"批量处理"要分成两个区块
这节课非常值得你注意的一点是:
- 上一课的区块叫
批量上下文 - 这一课新增的区块叫
批量处理
它们不是一回事。
前者解决的是:
- 我要把哪些任务纳入处理范围
后者解决的是:
- 我准备对这批任务做什么
这就是很典型的后台页面分层:
- 先组织对象
- 再执行动作
2. 批量改状态按钮继续复用系统里的全部合法状态
这一区块没有手写四个死按钮值。
而是继续遍历:
props.statusOptions
这样详情抽屉里的批量改状态能力,会自动和整套任务状态定义保持一致。
这是很好的习惯。
因为展示入口最好不要偷偷复制业务枚举。
3. 按钮禁用条件要和页面真实能力保持一致
这一课里,这组按钮在下面几种情况下会禁用:
- 页面正在加载
- 当前视图不支持批量处理
- 当前根本没有已选任务
也就是说,详情抽屉不能制造一组"页面层实际上接不住"的假入口。
这再次说明:
展示层入口必须尊重页面真实业务能力边界。
在 TasksView.vue 里学什么?
这一课最核心的协调逻辑继续放在页面层。
1. 批量改状态现在要考虑"当前详情是否一起变了"
handleUpdateSelectedTasksStatus 这次新增了一步:
- 在真正批量更新前,先记录当前详情任务是否也在已选集合里
这样更新成功后就可以区分两种反馈:
- 普通批量更新成功
- 当前详情上下文也已经同步更新
这个细节会让页面提示更贴近用户正在做的事。
2. 批量删除要在真正删除前先快照上下文
handleDeleteSelectedTasks 里这次先保存了:
- 删除前的已选 id 列表
- 删除前的已选数量
- 当前详情任务是否包含在这次删除里
- 删除后应该跳到哪条任务
注意这里非常关键:
这些信息都必须在真正删除之前先算好。
因为一旦删除完成:
- 选择状态会被清空
- 原始任务可能已经不存在
如果这时候才开始判断,就拿不到完整上下文了。
3. 当前详情任务被批量删除后,要么切相邻任务,要么关闭详情
删除成功后,页面层现在会这样处理:
- 如果当前详情任务不在删除范围里,详情继续保留
- 如果在删除范围里,并且还能找到相邻存活任务,就自动切过去
- 如果找不到,就关闭详情
这就是很真实的后台体验。
因为详情抽屉不应该留在一条已经不存在的记录上。
在 taskDetailLinkage.ts 里学什么?
这一课新增的纯函数是:
getTaskDetailBatchDeleteFallbackId
它本质上是在回答一个非常具体的问题:
一批任务删掉之后,当前详情还能自然停留在什么地方?
这个函数的价值不只是"代码能跑"。
更重要的是它把这类复杂联动拆成了:
- 可独立理解
- 可独立测试
- 可被页面层复用
这就是你之后写复杂页面时要重点训练的能力:
把复杂联动里的"决策部分"抽成纯函数。
单元测试这次补了什么?
1. taskDetailDrawer.spec.ts
这一课在抽屉组件测试里补了:
批量处理区块是否渲染批量改为待评审是否会抛出change-selected-status批量删除已选是否会抛出delete-selected
这说明展示层组件测试仍然要抓住两件事:
- 渲染是否符合设计
- 事件是否把正确意图抛了出去
2. taskDetailLinkage.spec.ts
这一课给新纯函数补了几类关键场景:
- 删除当前详情后优先切到后一条存活任务
- 如果后面都删掉了,就回退到前一条存活任务
- 如果前后都没有,就返回
null - 如果当前详情任务根本不在上下文里,也返回
null
这类测试特别有价值。
因为它把"最容易写错的边界联动"锁得很死。
E2E 这次补了什么?
1. Page Object 补了详情抽屉里的批量处理入口
文件:
e2e/pages/TasksPage.ts
新增方法:
changeSelectedTasksStatusFromDetailDrawerdeleteSelectedTasksFromDetailDrawer
另外还顺手让删除确认框和确认按钮同时兼容:
- 单条删除
- 批量删除
2. 新增了两条真正闭环的端到端测试
文件:
e2e/app.spec.ts
新增测试:
user can batch update selected tasks from detail drawer and keep detail synceduser can batch delete selected tasks from detail drawer and switch detail to adjacent survivor
它们分别验证:
批量改状态链路
- 从详情抽屉选中当前任务
- 选中同状态任务
- 在详情抽屉里直接批量改成
进行中 - 当前详情状态立刻同步
- 通过详情抽屉继续导航到下一条 / 下一条
- 确认跨页任务也已经一起更新
批量删除链路
- 从详情抽屉选中当前任务
- 选中同状态任务
- 直接从详情抽屉里发起批量删除
- 确认批量删除确认框
- 删除成功后自动切到仍然存活的相邻任务
- 地址栏
taskId和详情位置摘要一起同步更新
这里你要特别注意:
这两条测试测的不是"按钮能不能点"。
它们测的是整条联动链:
- 详情抽屉
- 批量选择
- 页面级批量处理
- 详情同步
- 删除后的上下文恢复
- 地址栏联动
这已经是比较完整的真实后台场景了。
你现在真正应该学会什么?
学完这一课,你最应该记住的是下面 6 件事:
1. 一个入口可以既服务单条上下文,也服务批量工作流
详情抽屉不是只能"看详情"。
它也可以成为组织和执行批量工作的入口。
2. "建立批量上下文"和"执行批量处理"最好分成两步
不要把:
- 选哪些对象
- 做什么动作
混成一个区块。
3. 新入口最好的接法,是复用旧业务逻辑
不要因为入口换了,就重新复制一套业务实现。
4. 复杂删除联动最好先快照上下文,再执行真正删除
否则删除后很多判断条件都会消失。
5. 复杂页面里的"下一条切哪里"很适合抽成纯函数
这样更容易:
- 思考
- 测试
- 复用
6. E2E 测试不只要测功能,还要保证测的是最新构建结果
这是工程可靠性的一部分,不只是脚本技巧。