news 2026/4/22 1:33:21

第 34 课:任务看板拖拽改状态

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
第 34 课:任务看板拖拽改状态

第 34 课:任务看板拖拽改状态

这一课我们继续沿着“任务管理页主线能力增强”往下推进。

上一课我们已经让任务页支持:

  • 看板视图
  • 按状态分列展示
  • 本地持久化当前主视图模式

这一课继续把看板做得更像真实后台:

让用户可以直接把任务卡片从一个状态列拖到另一个状态列,并把这次状态流转立即写回页面状态和模拟服务。


这节课一句话在做什么?

这一课我们完成了 6 件事:

  1. 给看板卡片加上了原生拖拽能力。
  2. 给看板列加上了可放置高亮反馈。
  3. 拖拽落列后,会把目标任务状态直接更新成该列状态。
  4. 如果被拖拽的是当前正在编辑的任务,会主动关闭编辑弹窗。
  5. 刷新页面后,拖拽改过的状态仍然会保留。
  6. 补上了单元测试、E2E 测试和课程文档。

这一课最重要的设计结论

这一课最重要的结论是:

看板拖拽本质上不是“动画效果”,而是“单条任务状态流转”。


为什么看板拖拽应该走“页面级局部更新”?

很多初学者第一次做拖拽时,最容易把重点放错地方:

  • 先想动画
  • 先想第三方拖拽库
  • 先想怎么把卡片挪来挪去

但真实后台里更重要的问题其实是:

  • 这次拖拽改的到底是什么业务字段?

这节课里答案很明确:

  • 改的是任务的status

所以拖拽不是“纯视觉动作”,它对应的是:

  • 一次单条任务更新

1. 拖拽改的是任务状态,不是列顺序

这一课我们没有做:

  • 列内排序拖拽
  • 自由拖拽排位
  • 跨列自定义插入顺序

我们做的是更稳定的一类能力:

  • 把任务拖到目标状态列
  • 然后把任务的status改成目标状态

所以这节课的核心建模其实是:

  • 卡片拖拽只是输入方式
  • 状态字段更新才是业务结果

2. 这类更新最适合做“局部状态回写”

当前任务页早就已经练过:

  • 创建任务
  • 编辑任务
  • 删除任务
  • 批量改状态

这一课的拖拽改状态,本质上和它们是一类事情:

  • 只改一条记录
  • 不需要整表重载
  • 可以直接在本地状态里替换目标项

所以我们没有重新发起整页加载,而是:

  1. 找到目标任务
  2. 只更新这一条任务的status
  3. 同步写回模拟服务
  4. 让计算属性自动重新生成新的看板列

这就是非常典型的:

  • 页面级局部更新

3. 拖拽只是组件输入,状态流转仍应交给页面层

这一课在TaskKanbanBoard.vue里做了拖拽交互,但它并没有自己直接改任务数组。

它做的只是:

  1. 记录当前拖拽中的任务
  2. 记录当前悬停的目标列
  3. 在 drop 时发出:
    • move-task-status

然后由页面层和useTasksPage去处理真正的数据更新。

这说明你要继续建立一个重要意识:

复杂输入组件负责收集用户动作,页面级状态负责决定真实数据怎么变。


这一课在TaskKanbanBoard.vue里做了什么?

文件:

  • src/components/tasks/TaskKanbanBoard.vue

1. 用原生拖拽事件实现看板卡片流转

这次我们没有引入额外拖拽库,而是直接使用浏览器原生事件:

  • dragstart
  • dragend
  • dragover
  • dragleave
  • drop

这样做的好处是:

  • 学习成本更低
  • 代码路径更直接
  • 更适合你现在边做边理解事件流

2. 新增了拖拽中的任务状态

组件里新增了两个局部状态:

  • draggingTask
  • dragOverColumnKey

它们分别表示:

  • 当前正在被拖拽的是哪条任务
  • 当前用户正悬停在哪个目标列上

这两个状态都只属于:

  • 当前看板组件内部交互反馈

所以放在组件内部是合适的。


3. 给卡片和列都加了拖拽反馈

这节课不仅让拖拽“能工作”,还补了两种很重要的反馈:

  1. 被拖拽的卡片会降低透明度并轻微缩放
  2. 当前可放置的目标列会高亮并轻微上浮

为什么这一步很重要?

因为拖拽交互最怕的问题就是:

  • 用户不知道自己抓住了谁
  • 用户不知道会落到哪里

所以你应该逐步形成一个习惯:

能拖,不代表交互完整;拖拽落点反馈和目标反馈同样属于功能本体。


这一课在useTasksPage里做了什么?

文件:

  • src/composables/useTasksPage.ts

1. 新增了moveTaskToStatus(taskId, nextStatus)

这是这一课真正的核心页面级能力。

它做的事情很清楚:

  1. 判断当前是否正在加载
  2. 找到目标任务
  3. 判断目标状态是否真的发生变化
  4. 必要时关闭受影响的编辑弹窗
  5. 在本地任务数组中替换这一条记录
  6. 把更新后的任务写回模拟服务

也就是说,这个函数不是“拖拽函数”,而是:

  • 单条任务状态流转函数

拖拽只是调用它的一种方式。


2. 为什么不直接复用updateTask(taskDraft)

因为updateTask(taskDraft)的设计目标是:

  • 配合编辑弹窗表单
  • 依赖editingTaskId

而拖拽改状态的输入是:

  • taskId
  • nextStatus

两者上下文不同,所以我们新加了一个更贴近拖拽语义的函数,而不是硬把拖拽逻辑塞进表单更新流程。

这也是一个很重要的工程判断:

相似能力可以共享底层思路,但不一定要强行共用同一个入口函数。


3. 为什么这里要用“生成新数组再赋值”而不是直接splice

这次看板拖拽还有一个很值得你记住的细节:

  • 单条状态流转里,我们没有继续用原地splice
  • 而是改成了“生成一份新数组,再整体赋值回tasks.value

这样做的原因是:

  • 看板列是由一串计算属性推导出来的
  • 单条任务状态变化后,我们希望依赖tasks的结果一定重新计算
  • 整体替换数组会让这条依赖链更稳定、更直观

你可以把它理解成一个非常实用的前端经验:

当页面上有很多由列表派生出来的计算结果时,不可变更新通常比原地修改更稳。


这一课在taskService里做了什么?

文件:

  • src/services/taskService.ts

1. 为什么前面“写回模拟服务”还不够?

一开始我们已经把拖拽后的任务状态写回了模拟服务层。

但 E2E 测试继续往前走时,发现还有一个真实问题:

  • 拖拽后页面里立刻是对的
  • 一整页刷新后,又回到了初始假数据

这说明什么?

  • 说明之前的模拟服务只是模块级内存数据库
  • 页面一旦整刷新,模块重新执行,内存数据就被重置了

这其实正好帮你区分了两个层次:

  1. 页面内局部状态更新
  2. 跨刷新持久化数据源

只有第 1 层,没有第 2 层,就会出现:

  • “当前页里看起来改成功了”
  • “刷新后又丢了”

2. 这次怎么让任务数据真正跨刷新保留?

这一课在taskService.ts里新增了一个更真实的模拟策略:

  • 模块启动时优先从localStorage恢复任务数据库
  • 如果本地没有可用数据,就回退到默认假数据
  • 每次新增、更新、删除后,都立刻把最新任务数据库写回localStorage

所以现在任务服务层不再只是:

  • 纯内存数据库

而是变成了:

  • 内存数据库 + localStorage 持久化

这样整页刷新后,任务状态分布仍然能恢复。


3. 这一层为什么值得你单独学?

因为它非常接近真实项目里的一个核心认知:

  • 页面状态不等于数据源

页面里能马上看到变化,只能说明:

  • 视图层更新成功了

刷新后还能保留,才说明:

  • 这次修改真的写进了“更稳定的数据层”

真实项目里这个“稳定数据层”通常是:

  • 后端数据库
  • 接口服务
  • 本地离线存储

这一课我们用localStorage去模拟它。


4. 为什么拖拽会关闭编辑弹窗?

如果当前用户正在编辑某条任务,同时又通过看板把它拖到了另一列,那么编辑弹窗里的旧表单状态就可能变脏。

所以这一课延续前面批量操作的策略:

  • 如果当前受影响任务正好是编辑目标
  • 就主动关闭编辑弹窗

这不是“粗暴”,而是为了保证:

  • 页面上下文一致
  • 不让旧表单继续误导用户

这一课在页面层做了什么?

文件:

  • src/views/TasksView.vue

这一课页面层新增了:

  • handleMoveTaskToStatus(task, nextStatus)

它负责:

  1. 调用moveTaskToStatus
  2. 根据结果给出成功/失败提示
  3. 把事件继续留在页面层协调

这样做的好处是:

  • 组件只管抛事件
  • composable 只管改数据
  • 页面层只管交互反馈

这个职责分层非常清楚。


这一课最值得你学会的前端思想

1. 拖拽只是输入方式,不是业务本体

以后你做任何拖拽功能,都可以先问自己:

  • 这次拖拽到底在改什么业务数据?

只要你先把这个问题答清楚,后面实现就会顺很多。

这节课的答案是:

  • 在改任务状态

2. 局部更新比整表重载更贴近真实后台

这节课再次强化一个你已经反复练过的能力:

  • 局部更新

因为拖拽改状态只影响一条任务,所以最自然的做法就是:

  • 只改这一条
  • 不刷新全表
  • 让看板列自动重算

这比“拖一下就整页重载”更快,也更符合真实项目体验。


3. 交互反馈也属于功能设计的一部分

这一课如果只做数据更新,不做:

  • 卡片拖拽态
  • 目标列高亮
  • 页面级成功提示

那功能虽然“能用”,但体验还是不完整。

所以你应该开始建立一个更高标准:

输入反馈、状态反馈、结果反馈,三者合起来才算一个完整交互。


这一课补了哪些测试?

1. 单元测试

文件:

  • src/composables/__tests__/useTasksPage.spec.ts
  • src/services/__tests__/taskService.spec.ts

新增覆盖:

  • moveTaskToStatus()是否真的更新了目标任务状态
  • 是否把更新后的任务写回数据源
  • 是否在影响编辑目标时关闭编辑弹窗
  • 看板列数量是否随着状态流转同步变化
  • 同状态重复流转是否会被忽略

这一层主要验证:

  • 页面级状态更新是否正确
  • 单条状态流转边界是否正确
  • 模拟服务是否真的把最新任务数据库持久化到了localStorage
  • 模块重新初始化后是否能恢复刚才写回的数据

2. E2E 测试

文件:

  • e2e/pages/TasksPage.ts
  • e2e/app.spec.ts

新增覆盖:

  • 切到看板视图
  • 拖拽一条任务到目标状态列
  • 断言列数量更新
  • 断言目标任务出现在新列里
  • 刷新页面
  • 再次断言最新状态分布仍然保留

这一层主要验证:

  • 不只是函数能跑
  • 真实浏览器中的拖拽事件也能打通
  • 状态更新确实写回了数据源

这一课改了哪些文件?

  • src/components/tasks/TaskKanbanBoard.vue
  • src/composables/useTasksPage.ts
  • src/services/taskService.ts
  • src/views/TasksView.vue
  • src/composables/__tests__/useTasksPage.spec.ts
  • src/services/__tests__/taskService.spec.ts
  • e2e/pages/TasksPage.ts
  • e2e/app.spec.ts
  • docs/34-task-kanban-drag-and-drop-status-flow.md
  • docs/README.md

这一课最值得你真正记住什么?

如果你只记住“看板现在可以拖了”,那还不够。

你更应该记住下面这 6 点:

  1. 拖拽改状态的本质是单条任务状态流转。
  2. 拖拽输入和状态更新应该分层处理。
  3. 组件负责收集拖拽动作,页面级 composable 负责更新真实数据。
  4. 局部更新比整表重载更适合这种场景。
  5. 被拖拽卡片和目标列都需要明确反馈,交互才完整。
  6. 受影响的旧上下文必须清理,比如当前编辑弹窗。

这一课的验证命令

完成后至少应该验证:

npmrun test:unit ----runnpmrun type-checknpmrun lintnpmrun test:e2e ----project=chromium
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/22 1:31:15

收藏!2026年85%企业必做AI大模型应用,程序员/小白入门必看

刷着招聘软件,你是不是也有一瞬间的迟疑? 满屏的**“大模型工程师”、“AIGC应用开发”、“AI Agent开发”**,职位描述的技能要求越来越具体,从Prompt工程到向量数据库应用,从模型微调到底层部署,而自己简历…

作者头像 李华
网站建设 2026/4/22 1:27:17

SLC、MLC、TLC怎么选?深入NAND物理结构,揭秘寿命与性能的真实代价

SLC、MLC、TLC存储技术全解析:从物理结构到选型实战 在数据中心运维工程师的日常工作中,最令人头疼的故障往往来自存储设备。我曾经历过一次数据库集群瘫痪事故,溯源发现是某批TLC固态硬盘在密集写入场景下提前耗尽寿命。这次教训让我深刻意识…

作者头像 李华
网站建设 2026/4/22 1:27:08

城通网盘高速下载终极方案:免费破解限速的完整指南

城通网盘高速下载终极方案:免费破解限速的完整指南 【免费下载链接】ctfileGet 获取城通网盘一次性直连地址 项目地址: https://gitcode.com/gh_mirrors/ct/ctfileGet ctfileGet是一款专注于获取城通网盘一次性直连地址的开源工具,通过本地解析技…

作者头像 李华
网站建设 2026/4/22 1:26:21

深入SAP采购流程:ABAP BAPI_PR_CHANGE如何优雅修改已审批的采购申请?

SAP采购流程深度解析:如何安全修改已审批的采购申请? 在SAP MM模块的实际业务场景中,采购申请(Purchase Requisition)的修改操作往往比创建更具挑战性。特别是当采购申请已经进入审批流程甚至部分执行后,业…

作者头像 李华
网站建设 2026/4/22 1:26:03

uni-app怎么实现图片手势缩放 uni-app仿微信图片查看器【技巧】

uni-app中多点触控需用touches而非changedTouches判断双指,缩放锚点应为两指中心并用复合transform实现,小程序需catchtouchmoveenhanced模式优化频率,双击放大应基于tap事件并动态计算最大缩放倍数。uni-app 里 touchstart 拿不到多点触控的…

作者头像 李华