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 即可获得完整的操作清单,便于提交后端进行持久化。

代码

表格组件与一个甘特图组件之间的双向拖拽联动。

Video_2026-06-25_181014-ezgif.com-video-to-gif-converter

<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 可支持带父子层级的数据拖拽。
✅ 灵活联动:不仅支持表格 ↔ 甘特图,也支持多个表格或甘特图之间的互相拖拽。
只需确保数据主键全局唯一,并正确配置两端组件,即可快速实现跨视图的任务编排能力。

https://gantt.vxeui.com

posted @ 2026-06-25 18:20  你个老六  阅读(7)  评论(0)    收藏  举报