第 38 课:任务列表里高亮当前正在查看详情的任务
这一课我们继续沿着“任务管理页主线”往下推进,把上一课已经做好的“任务详情抽屉上下文导航”再向真实后台体验推进一步。
这次的目标很明确:
- 当你打开某条任务详情时,列表里对应任务要同步高亮
- 这个高亮不只在表格里生效,还要在卡片视图、看板视图里都成立
- 当你在详情抽屉里切到上一条 / 下一条时,高亮要跟着切换
- 当你关闭详情抽屉时,高亮要立刻消失
- 补上单元测试、E2E 测试和课程文档
这一课一句话在做什么?
这一课本质上是在做一件事:
让“当前正在查看哪条任务详情”这个页面上下文,反向影响列表的视觉反馈。
也就是说,详情抽屉不再只是“右边弹出来一块内容”。
它会开始和左边列表形成联动:
- 详情在看谁
- 列表就高亮谁
- 详情切到谁
- 列表就跟着切到谁
- 详情关闭
- 列表就清掉高亮
这是一种很典型的后台系统体验增强。
为什么真实后台里经常需要这种联动?
因为用户在后台里很少是“只看详情,不看列表”。
更常见的是:
- 先在列表里筛出一批任务
- 打开其中一条详情
- 处理一会儿后切到下一条
- 同时希望眼睛还能快速知道“当前处理的是列表里的哪一条”
如果没有高亮,用户很容易出现这几个问题:
- 看着详情,不知道自己正在处理列表中的哪条记录
- 在详情里切到下一条后,列表没有视觉跟进
- 列表、详情像两套互不认识的系统
所以这节课练的,不只是“加一个背景色”。
真正练的是:
- 页面上下文如何共享
- 同一份状态如何驱动多个视图
- 为什么后台里的“联动反馈”比“单点组件功能”更重要
这节课最关键的设计结论
1. 高亮状态不应该分散在三个视图里各自乱算
这次我们新增了一个共享工具文件:
src/components/tasks/taskDetailActiveHighlight.ts
里面只做两件事:
isTaskDetailActive(taskId, activeTaskId)getTaskTableRowClassName(taskId, activeTaskId)
这说明一个重要原则:
同一条业务规则,如果会被多个视图同时使用,就应该优先抽成共享函数。
这样做的好处很直接:
- 表格、卡片、看板用的是同一套“是否高亮”规则
- 后面修规则时,不会三个地方各改一遍
- 单元测试也可以直接先测纯函数,再测组件渲染
2. 页面状态继续留在页面层,而不是塞进子组件内部
这次三种列表视图都没有自己维护“我当前高亮谁”。
它们都只是接收:
activeTaskId
然后根据这个 id 去决定:
- 我该不该高亮这一行
- 我该不该高亮这张卡片
这说明:
子组件负责消费状态,页面层负责拥有状态。
这里的状态来源仍然是:
useTasksPage.tsTasksView.vue
这和前面几课一直强调的分层原则是完全一致的。
3. 三种视图的高亮表现可以不同,但判定标准必须相同
这一课里:
- 表格视图用行 class 高亮
- 卡片视图用卡片 class 和
data-detail-active - 看板视图也用卡片 class 和
data-detail-active
它们的视觉实现不同,是合理的。
因为三种视图的 DOM 结构本来就不同。
但是它们判定“当前是不是详情任务”的标准必须一样。
这也是为什么这次要抽共享判断函数。
这次主要改了哪些文件?
这一课主要改了这些地方:
src/components/tasks/taskDetailActiveHighlight.tssrc/views/TasksView.vuesrc/components/tasks/TaskTable.vuesrc/components/tasks/TaskCardList.vuesrc/components/tasks/TaskKanbanBoard.vuesrc/components/tasks/__tests__/taskDetailActiveHighlight.spec.tssrc/components/tasks/__tests__/taskDetailActiveViews.spec.tse2e/pages/TasksPage.tse2e/app.spec.tsdocs/README.md
另外新增了本节文档:
docs/38-task-list-highlight-active-detail-task.md
在TasksView.vue里学什么?
文件:
src/views/TasksView.vue
这次页面层做的事情不复杂,但非常关键:
- 继续从
useTasksPage()拿到activeTaskDetailId - 把它分别传给三个列表组件
- 让三种视图都共享同一个“当前详情任务 id”
也就是说,这次页面层做的不是“写新逻辑”,而是“把已有页面上下文继续分发给更多视图”。
这很像真实项目里的迭代方式:
- 先把状态设计对
- 再让更多组件接入这份状态
这比每次新功能都重新发明一套状态结构健康得多。
在TaskTable.vue里学什么?
文件:
src/components/tasks/TaskTable.vue
这一课里,表格最重要的变化是:
- 接收
activeTaskId - 行 class 改成通过共享函数统一生成
现在表格行 class 不再是只拼:
task-table-rowtask-table-row--task-id-xxx
而是会在命中当前详情任务时额外补上:
task-table-row--active-detail
这节课很适合你理解一个后台前端的常见技巧:
样式 class 不只是给 CSS 用的,它也经常是测试稳定定位和状态表达的一部分。
这里的task-table-row--task-id-xxx方便 E2E 精准定位。
而task-table-row--active-detail则负责表达“当前详情高亮中”。
在TaskCardList.vue和TaskKanbanBoard.vue里学什么?
文件:
src/components/tasks/TaskCardList.vuesrc/components/tasks/TaskKanbanBoard.vue
这两个组件这次都做了同一类升级:
- 接收
activeTaskId - 复用共享高亮判断函数
- 把当前详情任务映射成 class 和
data-detail-active
其中data-detail-active很值得你记住。
因为它代表一种很实用的工程习惯:
文件: 这次单元测试分成两层。 在 这类测试的价值是: 在 这类测试练的是: 文件: 这次我们给页面对象补了 6 个断言方法: 然后补了一条新的完整 E2E: 这条测试链路会验证: 这条 E2E 很有代表性。 这就是越来越像真实后台系统测试的地方。 学完这一课,你不应该只记住“加了高亮样式”。 更重要的是理解下面 4 件事: 这说明页面状态不是只能服务单个组件。 这次把“当前详情任务是否命中”抽成共享函数,目的是避免: 一旦规则分散,后面就很容易出现“一个视图亮,一个视图不亮”的问题。 这次的测试结构很典型: 这是非常值得你建立的工程习惯。 前几课我们做了: 这一课开始把这些能力真正连起来。 所以这节课的价值,不在“功能数量增加了多少”。 是的,而且更像了很多。 现在这个任务页已经不只是: 而是开始具备一种更真实的后台工作感: 这已经很接近真实后台里的“列表 <-> 详情联动体验”了。当视觉状态需要被测试稳定读取时,可以用>单元测试这次测了什么?src/components/tasks/__tests__/taskDetailActiveHighlight.spec.tssrc/components/tasks/__tests__/taskDetailActiveViews.spec.ts1. 先测纯函数规则
taskDetailActiveHighlight.spec.ts里,主要验证了:2. 再测组件是否把规则变成真实 DOM 状态
taskDetailActiveViews.spec.ts里,主要验证了:data-detail-activeactiveTaskId变化或清空时,DOM 状态会跟着更新E2E 这次测了什么?
e2e/pages/TasksPage.tse2e/app.spec.tsexpectTaskRowActiveByIdexpectTaskRowInactiveByIdexpectTaskCardActiveByIdexpectTaskCardInactiveByIdexpectTaskKanbanCardActiveByIdexpectTaskKanbanCardInactiveByIduser can highlight current detail task across task list views
因为它测的已经不只是“一个组件有没有渲染出来”,而是:你现在真正应该学会什么?
1. 详情状态本身就是一种页面上下文
activeTaskDetailId不只是“右边抽屉显示谁”。
它还可以驱动左边列表的反馈。
它经常会跨组件、跨区域复用。2. 同一规则最好只有一个来源
3. 测试最好分层
4. 真正的后台体验来自联动,而不是单点功能堆叠
而在“已有功能之间开始互相配合”。这一课完成后,任务页更像真实后台了吗?