vue 甘特图 vxe-gantt 实现 table 表格与甘特图拖拽双向联动、拖拽添加,拖拽移除
在复杂的项目管理场景中,任务数据往往分散在不同的视图中——一部分任务在表格中展示,另一部分在甘特图中呈现。如果能让用户在表格和甘特图之间自由拖拽任务,实现数据的跨视图联动,将极大提升任务编排的效率。
vxe-gantt 通过 row-drag-config.isCrossTableDrag 配置,支持表格组件与甘特图组件之间的跨表拖拽,让任务可以在不同组件间自由移动。同时,内置的 CRUD 管理器会自动记录每次拖拽产生的数据变更(新增、删除、修改),方便开发者进行后续处理。
本文介绍跨表拖拽的配置方法、核心机制与完整示例。
核心配置
跨表拖拽需要同时配置源组件和目标组件,两者缺一不可:
| 配置项 | 类型 | 说明 |
|---|---|---|
| rowConfig.drag | Boolean | 必须设为 true,启用行拖拽功能。 |
| rowConfig.keyField | String | 必须指定数据唯一主键字段名(如 'id'),确保跨表拖拽时数据不混乱。 |
| rowDragConfig.isCrossTableDrag | Boolean | 必须设为 true,允许跨表格/跨组件拖拽。 |
| rowDragConfig.isCrossDrag | Boolean | 建议设为 true,允许跨层级拖拽(如果数据有父子结构)。 |
| treeConfig | Object | 如果数据为树形结构,需配置 transform: true、rowField 和 parentField。 |
重要提醒:跨表拖拽要求所有参与拖拽的表格/甘特图使用相同的主键字段名(由 keyField 指定),且数据主键不能重复,否则拖拽将导致数据错乱或报错。
核心机制:CRUD 管理器自动追踪变更
跨表拖拽的本质是数据的跨组件移动——当用户将一个任务从表格拖到甘特图时,源组件会删除该行数据,目标组件会新增该行数据。
vxe-gantt 内置的 CRUD 管理器会自动记录这些操作,开发者可以通过 getRecordset() 方法获取完整的变更记录集:
const { insertRecords, removeRecords, updateRecords } = $table.getRecordset()
| 返回值 | 说明 |
|---|---|
| insertRecords | 新增的数据行(拖拽进入的目标组件) |
| removeRecords | 删除的数据行(拖拽离开的源组件) |
| updateRecords | 修改的数据行 |
这一机制让开发者无需手动追踪数据变化,直接调用 API 即可获得完整的操作清单,便于提交后端进行持久化。
代码
表格组件与一个甘特图组件之间的双向拖拽联动。

<template>
<div>
<vxe-button status="success" @click="resultEvent1">获取数据1</vxe-button>
<vxe-grid ref="gridRef1" v-bind="gridOptions1"></vxe-grid>
<div style="background-color: antiquewhite; line-height: 60px; margin: 32px 0">支持上方表格与下方甘特图互相拖拽任务</div>
<vxe-button status="success" @click="resultEvent2">获取数据2</vxe-button>
<vxe-gantt ref="ganttRef2" v-bind="ganttOptions2"></vxe-gantt>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { VxeUI } from 'vxe-pc-ui'
const gridRef1 = ref()
const gridOptions1 = reactive({
border: true,
height: 400,
rowConfig: {
drag: true,
keyField: 'id'
},
rowDragConfig: {
isCrossDrag: true, // 允许跨级
isCrossTableDrag: true // 允许跨表
},
treeConfig: {
transform: true,
rowField: 'id',
parentField: 'parentId'
},
columns: [
{ type: 'seq', width: 70 },
{ field: 'id', title: 'ID', width: 70 },
{ field: 'title', title: '任务名称', minWidth: 160, treeNode: true, dragSort: true },
{ field: 'start', title: '开始时间', width: 100 },
{ field: 'end', title: '结束时间', width: 100 },
{ field: 'progress', title: '进度', width: 100 }
],
data: [
{ id: 10001, parentId: null, title: 'A项目', start: '2024-03-01', end: '2024-03-04', progress: 3 },
{ id: 10002, parentId: 10001, title: '城市道路修理进度', start: '2024-03-03', end: '2024-03-08', progress: 10 },
{ id: 10003, parentId: null, title: 'B大工程', start: '2024-03-03', end: '2024-03-11', progress: 90 },
{ id: 10004, parentId: 10003, title: '超级大工程', start: '2024-03-05', end: '2024-03-11', progress: 15 },
{ id: 10005, parentId: 10003, title: '地球净化项目', start: '2024-03-08', end: '2024-03-15', progress: 100 },
{ id: 10006, parentId: 10003, title: '一个小目标项目', start: '2024-03-10', end: '2024-03-21', progress: 0 },
{ id: 10007, parentId: 10005, title: '某某计划', start: '2024-03-15', end: '2024-03-24', progress: 70 }
]
})
const resultEvent1 = () => {
const $grid = gridRef1.value
if ($grid) {
const { insertRecords, removeRecords } = $grid.getRecordset()
const tableData = $grid.getFullData()
VxeUI.modal.message({
content: `新增:${insertRecords.length} 删除:${removeRecords.length} 现有:${tableData.length}`,
status: 'success'
})
}
}
const ganttRef2 = ref()
const ganttOptions2 = reactive({
border: true,
height: 400,
rowConfig: {
drag: true,
keyField: 'id'
},
rowDragConfig: {
isCrossDrag: true, // 允许跨级
isCrossTableDrag: true // 允许跨表
},
treeConfig: {
transform: true,
rowField: 'id',
parentField: 'parentId'
},
taskBarConfig: {
showProgress: true, // 是否显示进度条
showContent: true, // 是否在任务条显示内容
moveable: true, // 是否允许拖拽任务移动日期
resizable: true, // 是否允许拖拽任务调整日期
barStyle: {
round: true, // 圆角
bgColor: '#fca60b', // 任务条的背景颜色
completedBgColor: '#65c16f' // 已完成部分任务条的背景颜色
}
},
columns: [
{ type: 'seq', width: 70 },
{ field: 'title', title: '任务名称', minWidth: 160, treeNode: true, dragSort: true },
{ field: 'start', title: '开始时间', width: 100 },
{ field: 'end', title: '结束时间', width: 100 }
],
data: [
{ id: 10008, parentId: null, title: '某某科技项目', start: '2024-03-20', end: '2024-03-29', progress: 50 },
{ id: 10009, parentId: 10008, title: '地铁建设工程', start: '2024-03-19', end: '2024-03-20', progress: 5 },
{ id: 10010, parentId: 10008, title: '公寓装修计划2', start: '2024-03-12', end: '2024-03-20', progress: 30 },
{ id: 10011, parentId: 10008, title: '两个小目标工程', start: '2024-03-01', end: '2024-03-04', progress: 20 },
{ id: 10012, parentId: null, title: '蓝天计划', start: '2024-03-02', end: '2024-03-08', progress: 50 },
{ id: 10013, parentId: 10010, title: 'C大项目', start: '2024-03-08', end: '2024-03-11', progress: 10 },
{ id: 10014, parentId: 10010, title: 'H计划', start: '2024-03-12', end: '2024-03-16', progress: 100 },
{ id: 10015, parentId: 10011, title: '铁路修建计划', start: '2024-03-05', end: '2024-03-06', progress: 0 },
{ id: 10016, parentId: 10011, title: 'D项目', start: '2024-03-06', end: '2024-03-11', progress: 10 },
{ id: 10017, parentId: 10011, title: '海外改造工程', start: '2024-03-08', end: '2024-03-09', progress: 0 }
]
})
const resultEvent2 = () => {
const $gantt = ganttRef2.value
if ($gantt) {
const { insertRecords, removeRecords } = $gantt.getRecordset()
const tableData = $gantt.getFullData()
VxeUI.modal.message({
content: `新增:${insertRecords.length} 删除:${removeRecords.length} 现有:${tableData.length}`,
status: 'success'
})
}
}
</script>
说明
1. 主键唯一性(最重要)
跨表拖拽依赖主键来识别数据行。所有参与拖拽的组件必须使用相同的主键字段名(通过 keyField 指定),且所有数据的主键值全局唯一——不能出现两个组件中有相同 id 的任务。
如果主键重复,拖拽时组件无法区分数据归属,会导致数据错乱或报错
2. 树形结构的支持
如果数据存在父子层级(如示例中的 parentId 字段),需要配置 treeConfig:
treeConfig: {
transform: true, // 启用树形数据转换
rowField: 'id', // 行主键字段
parentField: 'parentId' // 父级关联字段
}
配合 isCrossDrag: true,可以支持跨层级的拖拽(如将子任务拖拽为另一个父任务的子任务)。
3. 拖拽把手的设置
在 columns 中,需要至少有一列设置 dragSort: true 作为拖拽把手:
{ field: 'title', title: '任务名称', dragSort: true }
需要拖拽该列所在的行或单元格才能触发拖拽行为。
CRUD 管理器 API 速查
| 方法 | 返回值 | 说明 |
|---|---|---|
| getRecordset() | 获取完整的变更记录集。 | |
| getFullData() | Array | 获取当前所有数据(包含拖拽后的最终状态)。 |
| getInsertRecords() | Array | 仅获取新增的数据行。 |
| getRemoveRecords() | Array | 仅获取被删除的数据行。 |
| getUpdateRecords() | Array | 仅获取被修改的数据行。 |
vxe-gantt 的跨表拖拽功能通过 row-drag-config.isCrossTableDrag 配置,实现了表格与甘特图之间的双向数据联动。其核心优势在于:
✅ 配置简单:只需同时开启源和目标组件的 isCrossTableDrag。
✅ 自动追踪:内置 CRUD 管理器自动记录每一次拖拽产生的数据变更。
✅ 树形支持:配合 treeConfig 可支持带父子层级的数据拖拽。
✅ 灵活联动:不仅支持表格 ↔ 甘特图,也支持多个表格或甘特图之间的互相拖拽。
只需确保数据主键全局唯一,并正确配置两端组件,即可快速实现跨视图的任务编排能力。

浙公网安备 33010602011771号