ToolJet Kanban 组件详解:看板数据结构、事件机制与组件专属操作(CSA)实战指南
2026/9/10 7:36:33 网站建设 项目流程

ToolJet Kanban 组件详解:看板数据结构、事件机制与组件专属操作(CSA)实战指南

【免费下载链接】ToolJetOpen-source foundation of ToolJet AI - the enterprise app generation platform for internal tools, dashboards, business applications, workflows and AI agents. Build visually, from a prompt, or from Claude Code, Codex and Cursor over MCP 🚀项目地址: https://gitcode.com/GitHub_Trending/to/ToolJet

本文基于 ToolJet 官方组件文档与前端源码,系统讲解 Kanban(看板)组件的完整用法:如何用columnData/cardData绑定动态数据、卡片内cardData模板变量、6 类事件与 7 个暴露变量、4 个可脚本调用的组件专属操作(CSA),以及卡片详情弹窗、删除区、样式与设备适配等全部配置项。读完后你可以把一个静态任务板改造成由查询驱动、事件可监听、卡片可增删移改的完整工作流看板,并理解其底层基于@dnd-kit的拖拽实现原理。

一、组件定位与默认结构

Kanban组件用于以可视化看板方式组织与排期任务,提供透明的任务流转工作流:可配置显示列数、启用/禁用“+Add Card”按钮,并把外部数据绑定到卡片上(参见 官方文档)。

在 组件配置文件 中可以看到,新添加的 Kanban 组件默认宽度 40、高度 490,并自带两个默认的Text子组件,直接绑定到当前卡片数据上:

// 默认子组件 1:卡片标题(top: 20, left: 4, 加粗 16px) text: '{{cardData.title}}' // 默认子组件 2:卡片描述(top: 50, left: 4, 14px) text: '{{cardData.description}}'

默认看板数据为 3 列(r1To Do /r2In Progress /r3Done)与 10 张卡片,卡片默认宽度302、高度100,删除区默认文案Drop here to delete——即开箱即可运行一个完整的“待办-进行中-完成”看板。

卡片与弹窗内的受限组件

:::info 受限组件 Kanban 的CardPopout(卡片详情弹窗)中不允许放置部分组件:

  • Card:Calendar、Kanban、Form、Tabs、Modal、ListView、Container
  • Popout:Calendar、Kanban :::

这一限制从源码结构看可以避免嵌套拖拽容器(DndContext)与嵌套弹窗造成状态冲突。

二、数据绑定:Column data 与 Card data

这是看板数据模型的骨架,两项均为code类型属性,支持直接写数组字面量或绑定查询结果:

属性说明期望值
Column data以对象数组(或返回对象数组的查询)提供列的idtitle{{[{ "id": "c1", "title": "to do" },{ "id": "c2", "title": "in progress" },{ "id": "c3", "title": "Completed" }]}}{{queries.xyz.data}}
Card data以对象数组(或查询)提供卡片的idtitlecolumnId(可含description等自定义字段){{[{ id: "r1", title: "Title 1", description: "Description 1", columnId: "c1" },{ id: "r2", title: "Title 2", description: "Description 2", columnId: "c2" },{ id: "r3", title: "Title 3", description: "Description 3", columnId: "c3" }]}}{{queries.abc.data}}

两条强制约束(缺失时看板无法正确渲染):

  1. column data每列必须提供idid可为stringnumber
  2. Card data每张卡片必须提供idcolumnId,两者类型同为stringnumber

从 helpers/utils.js 的实现可以印证这一点:convertArrayToObj直接以d.id作为键构建containers[d.id] = dgetColumnData只做columnData.map(container => container.id)getCardData则以card.columnId分组、card.id入列。因此id缺失或重复都会破坏“列→卡片 ID 列表”的映射结构。非数组输入会被normalizeCardData归一为[],表现为空看板而不是报错。

三、在卡片内使用cardData变量

卡片内部组件需要动态展示当前卡片的数据时,使用cardData键。例如把卡片上一个 Text 组件的Data属性设为:

{{cardData.title}} // 将 title 替换为你数据中的字段名,如 cardData.description

底层机制在 KanbanBoard.jsx 中实现:组件通过updateCardDataInCustomResolvables(id, flatCardData.map(d => ({ cardData: d })), 'cardData', moduleId)为每张卡片注册一条独立的cardData可解析值。也就是说,同一张卡片内的所有子组件共享“这张卡片”这一上下文的cardData,而不同卡片各自的cardData互不干扰。因此除了内置的title/description默认占位,你的查询数据中的任意字段(如assigneedueDate)都可以在卡片内以{{cardData.字段名}}直接引用。

四、板面配置:Card width / Card height / 添加与删除区

属性说明期望值
Card width设置卡片宽度数值,默认{{302}}
Card height设置卡片高度数值,默认{{100}}
Enable add card显示/隐藏看板上的+Add Cards按钮默认启用;点击属性旁的fx可绑定{{true}}/{{false}}动态控制
Show delete button显示/隐藏看板底部的Drop here to delete cards删除区默认启用;同样可通过fx动态控制

几个源码级细节:

  • 列容器宽度由cardWidth推导:KanbanBoard.jsx 中width: \${(Number(cardWidth) || 300) + 48}px``,即卡片宽度加 48px 内边距;
  • 删除区在代码中是id为常量'void'TRASH_ID)的Trash组件:拖拽卡片悬停其上并松开即触发删除;
  • + Add Card按钮点击时触发onAddCardClick事件,且受enableAddCard控制(禁用时按钮被设为invisible类);
  • 配置中还存在Delete zone label属性(默认文案Drop here to delete),可自定义删除区提示文字。

五、卡片详情弹窗(Popout)配置

文档的 Events 一节提到“点击卡片打开弹窗”,其对应配置在 kanban.js 的Card details modal小节:

属性说明默认值
Open modal on card click点击卡片是否打开详情弹窗{{true}}
Modal size弹窗尺寸,可选small/medium/large/fullscreenlg(medium)
Height弹窗高度400

弹窗内的 Popout 区域可像 Card 一样放置组件(受前文所列受限组件约束),组件内同样可读取cardData

六、Events:6 个事件的触发时机

在右侧属性面板的Events区点击Add handler即可为下列事件添加处理器,与 ToolJet 其他组件一样支持同一事件绑定多个 handler:

事件触发时机
On update卡片数据(id、title、description 或 columnID)通过组件专属操作(CSA)被更新时触发
On add card click点击看板上的Add card按钮时触发
Card removed卡片被删除时触发(拖入底部删除区,或通过 CSA 删除)
Card added通过 CSA 向看板添加卡片时触发
Card moved卡片在看板上的位置改变时触发(拖拽或通过 CSA 移动)
Card selected点击卡片打开弹窗(选中)时触发

与事件对应的内部实现:每个 CSA 或拖拽落点在执行完状态变更后调用fireEvent('onCardAdded' | 'onCardRemoved' | 'onCardMoved' | 'onUpdate')(见 KanbanBoard.jsx)。需要注意一个细节:删除卡片若通过拖入删除区(overId === TRASH_ID分支),只会设置lastRemovedCard并触发onCardRemoved;而通过 CSAdeleteCard删除时同理。事件更多说明可参考 ToolJet 文档站中的Action Reference分类。

七、Exposed Variables:7 个暴露变量全解

变量说明访问方式
updatedCardData看板中所有卡片的最新值集合。只有当对任意卡片执行过移动、添加、删除或更新操作后才有值直接读取{{components.kanban1.updatedCardData}}
lastAddedCard最后添加的卡片,含idtitledescriptioncolumnId{{components.kanban1.lastAddedCard.title}}
lastRemovedCard最近被删除的卡片,含idtitledescriptioncolumnId{{components.kanbanboard1.lastRemovedCard.title}}
lastCardMovement最近移动的卡片:originColumnIddestinationColumnIdoriginCardIndexdestinationCardIndex,以及cardDetails对象(含idtitledescriptioncolumnId{{components.kanbanboard1.lastCardMovement.cardDetails.title}}{{components.kanbanboard1.lastCardMovement.destinationCardIndex}}
lastSelectedCard最后选中(点击查看)卡片的idtitlecolumnIddescription{{components.kanban1.lastSelectedCard.columnId}}
lastUpdatedCard最后通过 CSA 更新的卡片,含idtitledescriptioncolumnId{{components.kanban1.lastUpdatedCard.columnId}}
lastCardUpdate记录本次 CSA 更新中“被修改属性”的旧值与新值(数组){{components.kanban1.lastCardUpdate[0].title.oldValue}}

这 7 个变量与 kanban.js 中exposedVariables的声明一一对应。lastCardUpdate的“新旧值对比”结构由deep-object-diff计算得出——updateCardData实现中先对更新前后的卡片做diff,再把变更键映射为{ [key]: { oldValue, newValue } }数组,这解释了为什么访问写法是lastCardUpdate[0].title.oldValue(数组下标 + 属性名 + oldValue/newValue)。lastCardMovement在拖拽与 CSA 两条路径下都会被写入:拖拽路径记录originCardIndex/destinationCardIndex(数组内索引),CSAmoveCard路径则把卡片插入目标列首位、destinationIndex0

八、Component Specific Actions(CSA):脚本化操作看板

以下 4 个操作可在任意事件 handler 的RunJS查询中调用,实现对看板的编程式控制(参数签名以 kanban.js 中actions定义为准):

操作说明调用示例
updateCardData更新卡片数据components.kanban1.updateCardData('c1', { title: 'New Title' })
moveCard把卡片移动到另一列await components.kanban1.moveCard('c1', 'r2')(第一个参数为卡片 id,第二个为目标列 id)
addCard向看板添加卡片await components.kanban1.addCard('c1', { title: 'New Title' })
deleteCard删除卡片await components.kanban1.deleteCard('c2')(参数为卡片 id)

CSA 的防御性校验在 KanbanBoard.jsx 中可见:

  • updateCardData/moveCard/deleteCard:目标卡片不存在时弹出Card not found提示并中止;
  • moveCard:若卡片已处于目标列(columnId相同)则直接返回,不触发事件;
  • addCard:卡片 id 已存在时提示Card already existscolumnId缺失或指向不存在的列时提示Column Id not found

每个 CSA 成功后都会同步刷新updatedCardData及对应的lastXxx变量,并触发对应事件,因此“CSA 改状态 → 暴露变量更新 → 事件 handler 执行后续查询/保存”构成了完整的可编程闭环。

一个典型的“点击按钮落库”场景:

// 事件 handler 中的 RunJS 示例 // 1. 把最近选中的卡片移动到其他列 await components.kanban1.moveCard(components.kanban1.lastSelectedCard.id, 'r3'); // 2. 更新卡片标题 components.kanban1.updateCardData(components.kanban1.lastSelectedCard.id, { title: 'Done!' }); // 3. 把最新全量卡片数据提交到后端查询 await queries.saveCards.run({ cards: components.kanban1.updatedCardData });

九、拖拽实现原理:基于 @dnd-kit 的多列排序

从源码结构看,看板拖拽由@dnd-kit驱动(KanbanBoard.jsx):

  • DndContext注册了MouseSensorTouchSensorKeyboardSensor(键盘坐标由 multipleContainersKeyboardCoordinates.js 的coordinateGetter提供,支持跨列键盘移动),碰撞检测使用rectIntersection
  • 每个列是一个tj-kanban-container-{columnId}可放置容器,列内是SortableContext+verticalListSortingStrategy的垂直排序上下文;
  • onDragOver负责跨列实时重排并记录lastCardMovement中间态;onDragEnd处理落点:落点在删除区('void')走删除分支,落点位置变化走arrayMove重排分支,并在此统一写入lastCardMovement、触发onCardMoved
  • 拖拽中的卡片由DragOverlay渲染,松手时有半透明落位动画(dropAnimationopacity: 0.5)。

这也解释了文档约束的由来:findContainer通过tj-kanban-container-前缀识别列容器,跨列拖拽判定、卡片归属都依赖这套 ID 约定,因此列/卡片的id必须是字符串或数字且保持稳定。

十、General、Devices 与 Styles 配置

Tooltip(General 折叠区)

General区设置字符串后,鼠标悬停组件即显示该文案作为提示。当前实现还支持Tooltip format切换(plainText/markdown/html,默认plainText),可让提示文本支持 Markdown 或 HTML 渲染。

Devices

属性说明
Show on desktop桌面视图下是否可见。默认{{true}};可用开关设置,或点击fx填入逻辑表达式动态控制
Show on mobile移动视图下是否可见。默认{{false}};同样支持 fx 动态绑定

Styles

样式说明
Disable禁用后组件被锁定且不可交互。默认值{{false}}(文档表格中“默认禁用”的表述与源码默认值{{false}}不一致,以源码definition.styles.disabledState为准,即默认未禁用
Visibility控制组件可见性,{{false}}时应用部署后组件不显示。默认{{true}}
Accent color列标题底色/强调色,可输入 Hex 值或用取色器选择;默认值为 CSS 变量var(--cc-primary-brand)+ Add Card按钮与列标题共用该色(源码中colAccentColor = { color: '#fff', backgroundColor: accentColor ?? '#4d72fa' }

从 Kanban.jsx 可见,disabledState会通过data-disabled属性与useDisableInert钩子将看板标记为inert,使其按钮与内嵌组件同时退出 Tab 键盘焦点顺序;visibilityfalse时外层容器直接display: none

十一、兼容旧版 KanbanBoard 组件

仓库中另有一份 kanbanBoard.js 配置,文件首行注释明确写道:KanbanBoard 组件已弃用(deprecated),该配置仅为兼容存量应用中的旧组件而保留。旧版属性名为columns,事件多一个onCardUpdated(新版并入onUpdate),且暴露变量中没有lastSelectedCard/lastCardUpdate。如果你在新建应用中看到 Kanban Board 选项,应优先使用新版Kanban组件;旧应用数据可沿用旧组件继续工作,但不再获得新特性。

十二、小结

  • 数据模型:列只需id+title,卡片必须带id+columnId,两者均支持字面量数组或{{queries.xxx.data}}查询绑定;卡片内组件统一通过{{cardData.字段}}读取当前卡片数据。
  • 交互能力:6 个事件覆盖更新、加卡点击、删除、添加、移动、选中;7 个暴露变量(updatedCardData及 6 个lastXxx)让 handler 能精确感知每次操作前后的状态。
  • 编程控制addCard/deleteCard/moveCard/updateCardData四个 CSA 可在 RunJS 中 await 调用,与事件、暴露变量组合即可实现“拖拽→事件→落库”的完整闭环。
  • 底层实现:基于@dnd-kitDndContext+ 多容器SortableContext,支持鼠标、触摸与键盘三套 sensor,删除区是一个 id 为'void'的特殊放置目标。

【免费下载链接】ToolJetOpen-source foundation of ToolJet AI - the enterprise app generation platform for internal tools, dashboards, business applications, workflows and AI agents. Build visually, from a prompt, or from Claude Code, Codex and Cursor over MCP 🚀项目地址: https://gitcode.com/GitHub_Trending/to/ToolJet

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询