KanbanBoard 看板
轻量级、无外部拖拽库依赖的看板组件,粗边框列 + 硬朗阴影卡片尽显新粗野主义张力。
预览
待办
设计首页 Banner
确定配色与排版方案
编写组件文档
进行中
开发 Kanban 组件
支持拖拽排序
已完成
Accordion 组件
Timeline 组件
列拖拽排序与自定义添加按钮
拖拽列标题可重排顺序;待办列使用自定义「新建任务」按钮。
Backlog
调研需求
Progress
搭建脚手架
Review
代码评审
安装
pnpm dlx brutx-vue@latest add kanban用法
<script setup>
import { KanbanBoard } from 'brutx-ui-vue'
import type { KanbanColumn } from 'brutx-ui-vue'
import { ref } from 'vue'
const columns = ref<KanbanColumn[]>([
{
id: 'todo',
title: '待办',
color: '#FFE66D',
cards: [
{ id: 'c1', title: '需求分析', tags: ['研发'] },
],
},
{
id: 'done',
title: '已完成',
cards: [],
},
])
</script>
<template>
<KanbanBoard
v-model="columns"
@card-move="(id, from, to) => console.log(id, from, to)"
/>
</template>列排序
拖拽列标题即可在列之间重新排序,排序结果通过 v-model 同步,并触发 column-move 事件。
<script setup>
import { ref } from 'vue'
import { KanbanBoard } from 'brutx-ui-vue'
const columns = ref([
{ id: 'todo', title: '待办', cards: [] },
{ id: 'done', title: '已完成', cards: [] },
])
function handleColumnMove(columnId, fromIndex, toIndex) {
console.log('列', columnId, '从', fromIndex, '移动到', toIndex)
}
</script>
<template>
<KanbanBoard v-model="columns" @column-move="handleColumnMove" />
</template>自定义添加卡片入口
每列底部默认渲染一个 outline 风格的「添加卡片」按钮,点击触发 add-card 事件。通过 #add-{columnId} 插槽可替换为自定义入口。
<script setup>
import { ref } from 'vue'
import { KanbanBoard, Button } from 'brutx-ui-vue'
const columns = ref([
{ id: 'todo', title: '待办', cards: [] },
])
function handleAddCard(columnId) {
columns.value[0].cards.push({ id: Date.now().toString(), title: '新卡片' })
}
</script>
<template>
<KanbanBoard v-model="columns" @add-card="handleAddCard">
<template #add-todo="{ columnId }">
<Button variant="primary" size="sm" class="w-full" @click="handleAddCard(columnId)">
新建任务
</Button>
</template>
</KanbanBoard>
</template>数据类型
interface KanbanCard {
id: string
title: string
description?: string
tags?: string[]
color?: string
}
interface KanbanColumn {
id: string
title: string
color?: string
cards: KanbanCard[]
}程序化控制
通过 ref 访问组件实例后可调用以下方法:
<script setup>
import { ref } from 'vue'
import { KanbanBoard } from 'brutx-ui-vue'
const kanbanRef = ref(null)
const columns = ref([
{ id: 'todo', title: 'Todo', cards: [{ id: 'card-1', title: 'Task 1' }] },
{ id: 'done', title: 'Done', cards: [] },
])
function moveCardRight() {
// 将卡片移动到右侧列
kanbanRef.value?.moveCard('card-1', 'todo', 1)
}
</script>
<template>
<KanbanBoard ref="kanbanRef" v-model="columns" />
<button @click="moveCardRight">Move Card Right</button>
</template>API 参考
| 名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
modelValue必填 | KanbanColumn[] | — | 看板的列及卡片数据,通过 v-model 接收移动后的新数据;列 id 和卡片 id 应保持唯一。 |
详情声明类型KanbanColumn[] 关联类型 KanbanColumn 类型定义 — KanbanColumnexport interface KanbanColumn {
id: string
title: string
color?: string
cards: KanbanCard[]
}默认值详情
来源packages/ui/src/components/kanban/KanbanBoard.vue | |||
class | string | undefined | — | 合并到横向排列各列的看板容器上的自定义 CSS 类。 |
详情声明类型string | undefined 默认值详情
来源packages/ui/src/components/kanban/KanbanBoard.vue | |||
| 名称 | 签名或类型 | 说明 |
|---|---|---|
@update:modelValue | [columns: KanbanColumn[]] | 有效卡片移动或列排序后发出新的列数组,先于对应的移动事件发出。 |
详情声明类型[columns: KanbanColumn[]] 关联类型 KanbanColumn 类型定义 — KanbanColumnexport interface KanbanColumn {
id: string
title: string
color?: string
cards: KanbanCard[]
}来源packages/ui/src/components/kanban/KanbanBoard.vue | ||
@card-move | [cardId: string, fromColumn: string, toColumn: string] | 卡片通过鼠标、键盘或实例方法成功移动后发出卡片 id、来源列 id 和目标列 id;同列排序时两列 id 相同。 |
详情声明类型[cardId: string, fromColumn: string, toColumn: string] 来源packages/ui/src/components/kanban/KanbanBoard.vue | ||
@card-click | [card: KanbanCard, columnId: string] | 点击卡片或聚焦后按 Enter 时发出卡片与列 id;卡片拖拽期间不会发出。Space 用于抓取与放下卡片。 |
详情声明类型[card: KanbanCard, columnId: string] 关联类型 KanbanCard 类型定义 — KanbanCardexport interface KanbanCard {
id: string
title: string
description?: string
tags?: string[]
color?: string
}来源packages/ui/src/components/kanban/KanbanBoard.vue | ||
@column-move | [columnId: string, fromIndex: number, toIndex: number] | 拖拽列标题或调用 moveColumn 成功排序后发出列 id、原索引与新索引,索引从 0 开始。 |
详情声明类型[columnId: string, fromIndex: number, toIndex: number] 来源packages/ui/src/components/kanban/KanbanBoard.vue | ||
@add-card | [columnId: string] | 点击内置添加按钮或调用实例 addCard 时发出列 id;由使用方创建卡片并更新数据。 |
详情声明类型[columnId: string] 来源packages/ui/src/components/kanban/KanbanBoard.vue | ||
| 名称 | 签名或类型 | 说明 |
|---|---|---|
#add-{columnId} | { columnId: string } | 替换指定列底部的添加卡片入口,接收该列 id。未提供时渲染 outline、sm 的 Button,点击后发出 add-card。 |
详情声明类型{ columnId: string }来源packages/ui/src/components/kanban/KanbanBoard.vue | ||
| 名称 | 签名或类型 | 说明 |
|---|---|---|
moveCard | (cardId: string, columnId: string, direction: number, mode?: "in-column" | "adjacent") => void | 按方向移动卡片并处理焦点与播报。mode 为 adjacent 时移到相邻列,为 in-column 时在当前列排序;direction 为 1 表示向右或向下,其他值按向左或向上处理。省略 mode 时使用 adjacent。 |
详情声明类型(cardId: string, columnId: string, direction: number, mode?: "in-column" | "adjacent") => void 来源packages/ui/src/components/kanban/KanbanBoard.vue | ||
moveColumn | (fromId: string, toId: string) => void | 把 fromId 对应的列移到 toId 当前所在索引,其间各列顺移;成功后发出数据更新与列移动事件。 |
详情声明类型(fromId: string, toId: string) => void 来源packages/ui/src/components/kanban/KanbanBoard.vue | ||
addCard | (columnId: string) => void | 为指定列发出 add-card 请求;此方法本身不创建卡片。 |
详情声明类型(columnId: string) => void 来源packages/ui/src/components/kanban/KanbanBoard.vue | ||
getColumn | (columnId: string) => KanbanColumn | undefined | 按 id 返回当前 modelValue 中的列对象;找不到时返回 undefined,返回值保留原对象引用。 |
详情声明类型(columnId: string) => KanbanColumn | undefined 关联类型 KanbanColumn 类型定义 — KanbanColumnexport interface KanbanColumn {
id: string
title: string
color?: string
cards: KanbanCard[]
}来源packages/ui/src/components/kanban/KanbanBoard.vue | ||
getAllColumns | () => KanbanColumn[] | 返回当前 modelValue 列数组引用;需要编辑时由使用方构建新数据并更新绑定值。 |
详情声明类型() => KanbanColumn[] 关联类型 KanbanColumn 类型定义 — KanbanColumnexport interface KanbanColumn {
id: string
title: string
color?: string
cards: KanbanCard[]
}来源packages/ui/src/components/kanban/KanbanBoard.vue | ||
可访问性
- 卡片支持键盘导航,可通过
Tab键聚焦 - 聚焦后按
Enter键可触发card-click事件,Space用于抓取和放下卡片 - 空列会显示本地化的提示文本,引导用户拖放卡片
- 键盘拖拽:聚焦卡片后按
Space键抓取卡片,使用方向键移动,再次按Space放下,Escape取消↑/↓:在当前列内上下移动卡片←/→:将卡片移动到相邻列
- 抓取状态下卡片会添加
aria-grabbed="true"属性 - 移动结果会通过
aria-live="polite"区域自动播报
键盘拖拽示例
<script setup>
import { ref } from 'vue'
import { KanbanBoard } from 'brutx-ui-vue'
const columns = ref([
{ id: 'todo', title: '待办', cards: [
{ id: 'c1', title: '任务 1' },
{ id: 'c2', title: '任务 2' },
]},
{ id: 'done', title: '已完成', cards: [] },
])
</script>
<template>
<!-- 用户可以:
1. Tab 聚焦到卡片
2. 按 Space 抓取卡片
3. 使用方向键移动卡片
4. 按 Space 放下卡片
5. 按 Escape 取消操作
-->
<KanbanBoard v-model="columns" />
</template>常见问题
Q: 为什么拖拽卡片后数据没有更新?
A: KanbanBoard 使用 v-model 双向绑定数据。请确保传入的 columns 是响应式数据(使用 ref 或 reactive),组件会在卡片移动或列排序后通过 update:modelValue 事件自动更新数据。如果使用普通变量,数据将无法同步。
Q: 移动端是否支持拖拽?
A: 组件基于原生 HTML5 拖拽 API 实现,在移动端浏览器上的支持可能有限。如果需要在移动端使用,建议测试目标设备的兼容性,或考虑为移动端提供替代的交互方式(如通过按钮操作)。
Q: 如何限制某个列中的卡片数量?
A: 组件本身不提供卡片数量限制功能。可以通过监听 card-move 事件,在事件处理函数中判断目标列的卡片数量,如果超出限制则通过修改数据将卡片移回原列来实现。