Skip to content

KanbanBoard 看板 ​

轻量级、无外部拖拽库依赖的看板组件,粗边框列 + 硬朗阴影卡片尽显新粗野主义张力。

预览 ​

Preview

待办

2

设计首页 Banner

确定配色与排版方案

设计

编写组件文档

文档

进行中

1

开发 Kanban 组件

支持拖拽排序

开发P0

已完成

2

Accordion 组件

完成

Timeline 组件

完成

列拖拽排序与自定义添加按钮

拖拽列标题可重排顺序;待办列使用自定义「新建任务」按钮。

Backlog

1

调研需求

Progress

1

搭建脚手架

Review

1

代码评审

安装 ​

pnpm dlx brutx-vue@latest add kanban

用法 ​

vue
<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 事件。

vue
<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} 插槽可替换为自定义入口。

vue
<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>

数据类型 ​

ts
interface KanbanCard {
    id: string
    title: string
    description?: string
    tags?: string[]
    color?: string
}

interface KanbanColumn {
    id: string
    title: string
    color?: string
    cards: KanbanCard[]
}

程序化控制 ​

通过 ref 访问组件实例后可调用以下方法:

vue
<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 参考 ​

KanbanBoard — 属性
名称类型默认值说明
modelValue
必填
KanbanColumn[]
—
看板的列及卡片数据,通过 v-model 接收移动后的新数据;列 id 和卡片 id 应保持唯一。
详情

声明类型

KanbanColumn[]

关联类型

KanbanColumn
类型定义 — KanbanColumn
export interface KanbanColumn {
    id: string
    title: string
    color?: string
    cards: KanbanCard[]
}

默认值详情

未声明默认值
—

来源

KanbanBoard.vue:16

packages/ui/src/components/kanban/KanbanBoard.vue

class
string | undefined
—
合并到横向排列各列的看板容器上的自定义 CSS 类。
详情

声明类型

string | undefined

默认值详情

未声明默认值
—

来源

KanbanBoard.vue:17

packages/ui/src/components/kanban/KanbanBoard.vue

KanbanBoard — 事件
名称签名或类型说明
@update:modelValue
[columns: KanbanColumn[]]
有效卡片移动或列排序后发出新的列数组,先于对应的移动事件发出。
详情

声明类型

[columns: KanbanColumn[]]

关联类型

KanbanColumn
类型定义 — KanbanColumn
export interface KanbanColumn {
    id: string
    title: string
    color?: string
    cards: KanbanCard[]
}

来源

KanbanBoard.vue

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]

来源

KanbanBoard.vue

packages/ui/src/components/kanban/KanbanBoard.vue

@card-click
[card: KanbanCard, columnId: string]
点击卡片或聚焦后按 Enter 时发出卡片与列 id;卡片拖拽期间不会发出。Space 用于抓取与放下卡片。
详情

声明类型

[card: KanbanCard, columnId: string]

关联类型

KanbanCard
类型定义 — KanbanCard
export interface KanbanCard {
    id: string
    title: string
    description?: string
    tags?: string[]
    color?: string
}

来源

KanbanBoard.vue

packages/ui/src/components/kanban/KanbanBoard.vue

@column-move
[columnId: string, fromIndex: number, toIndex: number]
拖拽列标题或调用 moveColumn 成功排序后发出列 id、原索引与新索引,索引从 0 开始。
详情

声明类型

[columnId: string, fromIndex: number, toIndex: number]

来源

KanbanBoard.vue

packages/ui/src/components/kanban/KanbanBoard.vue

@add-card
[columnId: string]
点击内置添加按钮或调用实例 addCard 时发出列 id;由使用方创建卡片并更新数据。
详情

声明类型

[columnId: string]

来源

KanbanBoard.vue

packages/ui/src/components/kanban/KanbanBoard.vue

KanbanBoard — 插槽
名称签名或类型说明
#add-{columnId}
{ columnId: string }
替换指定列底部的添加卡片入口,接收该列 id。未提供时渲染 outline、sm 的 Button,点击后发出 add-card。
详情

声明类型

{ columnId: string }

来源

KanbanBoard.vue:360

packages/ui/src/components/kanban/KanbanBoard.vue

KanbanBoard — 暴露成员
名称签名或类型说明
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

来源

KanbanBoard.vue:267

packages/ui/src/components/kanban/KanbanBoard.vue

moveColumn
(fromId: string, toId: string) => void
把 fromId 对应的列移到 toId 当前所在索引,其间各列顺移;成功后发出数据更新与列移动事件。
详情

声明类型

(fromId: string, toId: string) => void

来源

KanbanBoard.vue:270

packages/ui/src/components/kanban/KanbanBoard.vue

addCard
(columnId: string) => void
为指定列发出 add-card 请求;此方法本身不创建卡片。
详情

声明类型

(columnId: string) => void

来源

KanbanBoard.vue:273

packages/ui/src/components/kanban/KanbanBoard.vue

getColumn
(columnId: string) => KanbanColumn | undefined
按 id 返回当前 modelValue 中的列对象;找不到时返回 undefined,返回值保留原对象引用。
详情

声明类型

(columnId: string) => KanbanColumn | undefined

关联类型

KanbanColumn
类型定义 — KanbanColumn
export interface KanbanColumn {
    id: string
    title: string
    color?: string
    cards: KanbanCard[]
}

来源

KanbanBoard.vue:274

packages/ui/src/components/kanban/KanbanBoard.vue

getAllColumns
() => KanbanColumn[]
返回当前 modelValue 列数组引用;需要编辑时由使用方构建新数据并更新绑定值。
详情

声明类型

() => KanbanColumn[]

关联类型

KanbanColumn
类型定义 — KanbanColumn
export interface KanbanColumn {
    id: string
    title: string
    color?: string
    cards: KanbanCard[]
}

来源

KanbanBoard.vue:275

packages/ui/src/components/kanban/KanbanBoard.vue

可访问性 ​

  • 卡片支持键盘导航,可通过 Tab 键聚焦
  • 聚焦后按 Enter 键可触发 card-click 事件,Space 用于抓取和放下卡片
  • 空列会显示本地化的提示文本,引导用户拖放卡片
  • 键盘拖拽:聚焦卡片后按 Space 键抓取卡片,使用方向键移动,再次按 Space 放下,Escape 取消
    • ↑/↓:在当前列内上下移动卡片
    • ←/→:将卡片移动到相邻列
  • 抓取状态下卡片会添加 aria-grabbed="true" 属性
  • 移动结果会通过 aria-live="polite" 区域自动播报

键盘拖拽示例 ​

vue
<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 事件,在事件处理函数中判断目标列的卡片数量,如果超出限制则通过修改数据将卡片移回原列来实现。

蛮力铸就。