【码道案例】Vue3 + Tailwind 打造高颜值拖拽看板:从代码生成到环境踩坑的完整实战
项目背景
在日常开发中,看板(Kanban)任务管理是团队协作的标配工具。市面上虽然有很多成熟产品,但我想尝试用 AI 辅助编程快速搭建一个轻量级、高颜值、可定制的看板组件,既能满足个人项目管理需求,也能作为 Vue 3 拖拽交互的最佳实践案例。
技术选型:
- Vue 3 Composition API(<script setup>)
- Tailwind CSS(快速构建现代 UI)
- HTML5 原生 Drag & Drop API(零依赖实现拖拽)
- Vite 5(构建工具)
核心目标:
- 支持任务卡片跨列拖拽 + 列内排序
- 暗黑模式一键切换
- 数据本地持久化(localStorage)
- 零第三方拖拽库依赖
实现过程
1. 核心功能:拖拽逻辑设计
拖拽是看板的核心交互。我采用了 Vue 3 的 provide/inject 模式,在父组件 KanbanBoard.vue 中统一管理拖拽状态,子组件 TaskCard.vue 通过 @dragover.stop 实现精确插入位置判断。
关键代码片段:
// 拖拽运行时状态
const drag = reactive({
taskId: null, // 正在被拖拽的任务 id
fromColId: null, // 拖拽来源列 id
overColId: null, // 当前悬停目标列 id
overIndex: null, // 当前悬停插入位置索引
})
// 卡片开始拖拽:写入 dataTransfer(Firefox 必需)
function startDrag(e, taskId, colId) {
e.dataTransfer.setData('text/plain', taskId)
e.dataTransfer.effectAllowed = 'move'
drag.taskId = taskId
drag.fromColId = colId
}
// 悬停在卡片上:按鼠标相对卡片中点的位置,决定插到该卡片前还是后
function dragOverCard(colId, index, e) {
e.preventDefault() // 必须 preventDefault 才允许 drop
drag.overColId = colId
const rect = e.currentTarget.getBoundingClientRect()
const midY = rect.top + rect.height / 2
drag.overIndex = e.clientY < midY ? index : index + 1
}
// 核心移动:从源列移除,插入目标列指定位置
function moveTask(fromColId, taskId, toColId, toIndex) {
const fromCol = columns.value.find(c => c.id === fromColId)
const toCol = columns.value.find(c => c.id === toColId)
const i = fromCol.tasks.findIndex(t => t.id === taskId)
const [task] = fromCol.tasks.splice(i, 1) // 先移除
// 同列向后移动时,splice 已删一个元素,目标索引要 -1 修正
let at = toIndex
if (fromColId === toColId && i < toIndex) at = toIndex - 1
at = Math.max(0, Math.min(at, toCol.tasks.length))
toCol.tasks.splice(at, 0, task)
}
设计亮点:
- 使用 @dragover.stop 阻止事件冒泡,避免列容器的 dragover 覆盖精确索引
- 通过 getBoundingClientRect() 计算鼠标相对卡片中点位置,实现智能插入(上半区插前,下半区插后)
- 同列拖拽时的索引修正逻辑(i < toIndex 时 toIndex - 1)
2. UI 设计:Tailwind CSS 快速构建
卡片组件采用 group hover 模式,删除按钮默认隐藏,鼠标悬停时渐现:
<div
draggable="true"
@dragstart="kanban.startDrag($event, task.id, colId)"
@dragend="kanban.endDrag()"
@dragover.stop="kanban.dragOverCard(colId, index, $event)"
class="group p-3 rounded-lg bg-white dark:bg-gray-700 shadow-sm
hover:shadow-md hover:-translate-y-1 transition-all
cursor-grab active:cursor-grabbing"
:class="isDragging ? 'opacity-50' : 'opacity-100'"
>
<div class="flex justify-between items-start">
<span class="text-sm text-gray-800 dark:text-gray-100">{{ task.title }}</span>
<button
@click="kanban.deleteTask(colId, task.id)"
class="opacity-0 group-hover:opacity-100 transition-opacity text-gray-400 hover:text-red-500"
>
×
</button>
</div>
</div>
暗黑模式实现:
- 使用 localStorage 持久化主题状态
- Tailwind 的 dark: 前缀自动切换颜色
- 按钮文案动态切换亮色模式/暗色模式
踩坑全记录(代码5分钟,环境调20分钟)
先说结论: 代码层面一次写对——我用 Vue 官方 @vue/compiler-sfc 校验四个组件,<script setup>、模板编译全部通过,拖拽逻辑、Tailwind 暗黑模式都没返工。真正的坑全在构建工具链,而且是一个套一个,排查了 12 轮才最终跑通。
坑 1:npm run build 静默失败,dist 无产物
现象: 执行 npm run build 后,终端只显示命令回显,没有报错,但 dist/ 目录为空。
排查: npm 提示 install-scripts not yet covered,怀疑 esbuild 的 postinstall 脚本没执行。
尝试:
npm install-scripts approve esbuild
npm rebuild esbuild
坑 2:rebuild esbuild 报 status: 3221226505(Windows 0xC0000409)
现象: npm rebuild esbuild 失败,错误码 3221226505 对应 Windows 的 STATUS_STACK_BUFFER_OVERRUN。
根因确认:
# 直接运行 esbuild 二进制测试
& "node_modules\esbuild\win32-x64\esbuild.exe" --version
# 输出:exit=-1073740791(0xC0000409)
结论: esbuild 的 Windows 二进制文件本身执行就崩溃,与 Node/npm 无关。Node 版本是 v26.7.0(非 LTS 预览版),搭配的 esbuild 0.21.5 二进制不兼容。
坑 3:升级 esbuild@latest 被 postinstall 回滚
尝试:
npm install esbuild@latest -D
问题: 新包的 install.js 脚本验证二进制失败,导致回滚到 0.21.5。
解法:
npm install esbuild@0.25.5 -D --ignore-scripts
# 手动验证
esbuild.exe --version # 输出 0.25.5, exit=0
坑 4:顶层 esbuild 升了,build 仍报 "The service was stopped"
现象: 升级顶层 esbuild 后,npm run build 仍然失败。
排查: 报错栈指向 node_modules/vite/node_modules/esbuild/lib/main.js
根因: Vite 自带了一个嵌套的旧版 esbuild(0.21),我升的是顶层,Vite 用的还是嵌套版本。这是最隐蔽的一坑。
坑 5:overrides 与直接依赖冲突 EOVERRIDE
尝试: 在 package.json 添加:
{
"overrides": {
"esbuild": "0.25.5"
}
}
问题: npm 报 Override for esbuild conflicts with direct dependency。
解法: 从 devDependencies 删除 esbuild 直接依赖(它本就是 vite 的间接依赖,不该手动列),只保留 overrides。
坑 6:清装时 rollup 的 .node 文件 EPERM
现象: Remove-Item node_modules 清理时,rollup 的原生 .node 文件因 OneDrive 同步占用无法删除。
影响: 只是清理警告,安装仍成功。
坑 7:npm run dev 端口 5173 被占
现象: Vite 自动切换到 5174。
解决: 非问题,但预览 URL 要对应修改。
坑 8:验证嵌套 esbuild 已被替换
验证脚本:
node -e "const fs=require('fs');
const p='node_modules/vite/node_modules/esbuild/package.json';
console.log('vite 嵌套 esbuild:', fs.existsSync(p) ? require(p).version : '已提升(无嵌套)');
console.log('顶层 esbuild:', require('esbuild/package.json').version)"
输出:
vite 嵌套 esbuild: 已提升(无嵌套)
顶层 esbuild: 0.25.5
结论: 嵌套旧版已消除,全局统一为 0.25.5。
坑 9:最终构建验证
npm run build 2>&1 | Out-String
结果:
- 构建成功
- 产物已生成(dist/ 下 html + css 13KB + js 72KB)
- 3.05s 完成
- 问题已彻底修复
好用的 Prompt 分享
为了让码道生成高质量代码,我使用了以下结构化 Prompt:
# 角色设定
你是一位拥有10年经验的资深前端架构师,精通 Vue 3 和现代 CSS 框架,擅长编写高可维护、高颜值的组件。
# 任务目标
请使用 Vue 3 (Composition API, <script setup>) 和 Tailwind CSS 编写一个看板(Kanban)任务管理组件。要求代码结构清晰,UI 现代且美观。
# 功能需求
1. 看板包含三个默认列:待办 (To Do)、进行中 (In Progress)、已完成 (Done)。
2. 支持在每列中添加、删除任务卡片。
3. 支持任务卡片在列与列之间、以及列内部进行拖拽排序(请使用原生 HTML5 Drag and Drop API 实现)。
4. 数据持久化:使用 localStorage 保存看板数据,刷新页面不丢失。
5. 暗黑模式:提供一个切换按钮,支持亮色/暗色主题切换。
# UI/UX 设计要求
1. 整体布局:全屏高度,三列等宽,列之间留有适当间距。
2. 卡片设计:带有轻微的阴影(shadow-sm),圆角(rounded-lg),hover 时有上浮效果(hover:-translate-y-1 transition-transform)。
3. 颜色搭配:
- 亮色模式:背景为浅灰(bg-gray-100),卡片为纯白。
- 暗色模式:背景为深灰(bg-gray-900),卡片为稍浅的深灰(bg-gray-800)。
4. 交互反馈:拖拽时,被拖拽的卡片透明度降低(opacity-50),放置目标列有高亮提示。
# 代码规范要求
1. 请将代码整合在一个 Vue 单文件组件 (SFC) 中。
2. 使用 Tailwind CSS 进行样式编写,不要使用自定义 CSS。
3. 关键逻辑(如拖拽处理、本地存储同步)必须添加详细的中文注释。
Prompt 设计技巧:
- 明确角色: 让 AI 以"资深架构师"身份思考,代码质量更高
- 分点列举: 功能需求、UI 设计、代码规范分开写,避免遗漏
- 技术约束: 明确"不要自定义 CSS"、"使用原生 API"等边界
- 注释要求: 强制要求中文注释,便于后续维护
效果展示
截图 1:初始空状态(亮色模式)
- 顶部工具栏:标题看板任务管理 + 亮色模式按钮
- 三列等宽并排,列头带蓝/黄/绿色点
- 空列显示"暂无任务"灰色提示
截图 2:添加任务交互
- 列头"+"按钮点击后,下方展开输入框 + 添加按钮
- 支持回车快速添加
截图 3:三列各有任务卡片
- 卡片白底、圆角、阴影
- hover 时上浮效果 + 右侧"×"删除按钮渐现
- 列头任务计数实时更新
截图 4:拖拽中状态(最关键)
- 被拖卡片半透明(opacity-50)
- 目标列蓝色 ring-2 边框高亮
- 鼠标悬停处蓝色细横线指示插入位置
- 鼠标变 grab/grabbing 手型
截图 5:暗黑模式
- 整页背景深灰(bg-gray-900)
- 列容器 bg-gray-800,卡片 bg-gray-700
- 按钮文案变为"暗色模式"
截图 6:跨列拖拽完成
- 卡片成功移动到目标列
- 列计数自动更新
总结与技巧提炼
1. 技术层面的收获
拖拽交互最佳实践:
- 使用 HTML5 原生 Drag & Drop API,零依赖
- e.dataTransfer.setData() 在 Firefox 中必需,否则不触发拖拽
- @dragover.stop 阻止冒泡是实现精确插入位置的关键
- 同列拖拽时的索引修正逻辑(i < toIndex 时 toIndex - 1)
Vue 3 Composition API 优势:
- provide/inject 模式让父子组件通信更清晰
- reactive 统一管理拖拽状态,避免 prop drilling
Tailwind CSS 开发效率:
- group + group-hover 实现卡片删除按钮渐现
- dark: 前缀一键切换暗黑模式
- hover:-translate-y-1 等工具类快速实现微交互
2. 环境踩坑的经验
核心教训:
- Node 版本选择: 别用非 LTS 预览版跑构建链(v26.7.0 + esbuild 0.21 二进制不兼容)
- overrides 是利器: 统一嵌套重复依赖(Vite 自带的 esbuild)
- --ignore-scripts 慎用: 升级 esbuild 时需跳过 postinstall,但装完后要手动验证二进制
- 嵌套依赖陷阱: 顶层升了 esbuild,Vite 用的还是嵌套版本,必须用 overrides 强制统一
排查思路:
- 从报错栈定位具体文件(vite/node_modules/esbuild/lib/main.js)
- 直接运行二进制文件验证(esbuild.exe --version)
- 检查 node -p "process.arch" 确认架构匹配
3. 最终修复清单(三步走)
步骤 1: 修改 package.json
{
"devDependencies": {
"@vitejs/plugin-vue": "^5.0.4",
"vite": "^5.2.0"
},
"overrides": {
"esbuild": "0.25.5"
}
}
步骤 2: 彻底清理
Remove-Item -Recurse -Force node_modules, package-lock.json
步骤 3: 干净重装
npm install --ignore-scripts
npm run build
4. AI 辅助编程的心得
代码生成:
- 结构化 Prompt 比自然语言更有效
- 明确技术约束("不要自定义 CSS")避免 AI 自由发挥
- 要求中文注释,便于后续维护
代码验证:
- 使用 Vue 官方编译器 @vue/compiler-sfc 校验语法
- 拖拽逻辑、Tailwind 类名一次写对,节省大量调试时间
时间分配:
- 代码生成:5 分钟
- 环境调试:20 分钟
- 效果验证:5 分钟
结论: AI 能帮你快速写出正确的代码,但环境配置、依赖冲突等"脏活累活"还得自己扛。不过,踩坑的过程本身就是宝贵的经验积累!
5. 额外提示
关于 rollup 的 .node 文件 EPERM 警告:
- 这是 OneDrive 同步占用导致的清理警告,不影响运行
- 如果介意,可以把项目移出 OneDrive 目录
关于 Node 版本建议:
- Node v26 是非 LTS 预览版,存在兼容性问题
- 长期建议:换用 Node 20/22 LTS 版本更稳妥
- 生产环境务必使用 LTS 版本
项目源码
完整项目包含 11 个文件,核心组件:
- KanbanBoard.vue(拖拽逻辑 + 状态管理)
- TaskCard.vue(卡片组件 + DnD 绑定)
- Column.vue(列容器 + 添加任务)
- package.json(overrides 配置是关键)
最后说一句: 如果你也在用码道做项目,遇到构建问题别慌,先查嵌套依赖,再用 overrides 强制统一版本,大概率能解决!
更多推荐


所有评论(0)