vue 甘特图 vxe-gantt 实现双进度条:计划 vs 实际,支持多级子任务拆分

在项目管理中,计划进度与实际进度的对比是衡量执行偏差的关键指标。vxe-gantt 提供了灵活的树形结构和自定义任务条能力,通过巧妙的数据拆分策略,我们可以在一棵甘特树中同时展示计划和实际两条进度条,并且支持从一级到多级的完整继承。

本文将分两个方式来实现方案:

  • 一级双进度:仅对顶层任务拆分计划和实际,子任务不拆分。
  • 多级双进度:所有层级(包括孙任务)都展示计划和实际,父级自动汇总子级进度

实现一级双进度

实现计划进度和实际进度双进度任务条;以此为例,不管是三层进度、四层进度都能轻松实现们,该方式只能对一级任务进行拆分,无法再拆分子级。

思路

vxe-gantt 的树形甘特图支持通过 treeConfig 将扁平数据转换为树结构。要实现"计划/实际"双进度,最直接的方法是将每个原始任务拆分为两条虚拟记录:

  • 计划记录:使用计划日期字段(planStartDate / planEndDate)
  • 实际记录:使用实际日期字段(actualStartDate / actualEndDate)

这两条记录作为原始任务的子级(或同级),通过自定义 type 字段标记为 Subview(子任务视图),并分别赋予不同的颜色和样式,从而实现视觉上的双条对比。

关键配置项

配置 作用
treeConfig 启用树形结构,自定义主键和父键字段。
taskConfig 映射日期、进度、标题等字段到数据属性。
taskBarSubviewConfig 子任务视图的样式(偏移位置、颜色等)。
VxeGanttTaskType.Subview 标记该行作为子任务视图,在父行内展开。

样式区分

在 taskBarConfig.barStyle 中根据 flag 返回不同颜色:

javascript 复制代码
taskBarConfig: {
  barStyle({ row }) {
    return {
      bgColor: row.flag === 1 ? '#40d9eff' : '#31d231' // 计划蓝色,实际绿色
    }
  }
}

代码

适用场景:只需要对比顶层任务的计划与实际,子任务不需要单独展示双进度。

数据转换逻辑

对每个原始任务,生成两条新记录:

  • 计划记录:flag: 1,使用计划日期
  • 实际记录:flag: 2,使用实际日期

如果原始任务有子任务(hasChild: true),则将其类型设为 Subview,以便在父行内展开显示。

html 复制代码
<template>
  <div>
    <vxe-switch v-model="treeConfig.showIcon"></vxe-switch>

    <vxe-gantt v-bind="ganttOptions"></vxe-gantt>
  </div>
</template>

<script setup>
import { reactive } from 'vue'
import { VxeGanttTaskType } from 'vxe-gantt'

const treeConfig = reactive({
  transform: true, // 自动将列表转为树结构
  rowField: 'newId', // 自定义树节点关联的主键
  parentField: 'newParentId', // 自定义树节点关联的父节点的字段名
  showIcon: false // 是否显示展开按钮
})

const ganttOptions = reactive({
  border: true,
  height: 600,
  treeConfig,
  columnConfig: {
    resizable: true
  },
  taskConfig: {
    titleField: 'title', // 自定义标题字段名
    startField: 'start', // 自定义开始日期字段名
    endField: 'end', // 自定义结束期字段名
    progressField: 'progress', // 自定义进度值字段名
    typeField: 'type' // 自定义渲染类型字段名
  },
  taskBarSubviewConfig: {
    showOverview: true, // 是否显示任务总览,当子任务被展开后自动显示任务总览
    // 自定义子任务视图任务条的样式
    barStyle({ row }) {
      if (row.flag === 1) {
        return {
          backgroundColor: '#40d9eff'
        }
      }
      if (row.flag === 2) {
        return {
          backgroundColor: '#31d231'
        }
      }
    }
  },
  taskBarConfig: {
    showContent: true, // 是否在任务条显示内容
    moveable: true, // 是否允许拖拽任务移动日期
    resizable: true, // 是否允许拖拽任务调整日期
    barStyle({ row }) {
      return {
        round: true, // 圆角
        bgColor: row.flag === 1 ? '#40d9eff' : '#31d231' // 已完成部分任务条的背景颜色
      }
    }
  },
  taskViewConfig: {
    tableStyle: {
      width: 200
    },
    viewStyle: {
      border: 'inner'
    }
  },
  columns: [
    { field: 'newTitle', title: '任务名称', minWidth: 160, treeNode: true },
    { field: 'planStartDate', title: '计划开始日期', width: 140 },
    { field: 'planEndDate', title: '实际 结束日期', width: 140 },
    { field: 'actualStartDate', title: '计划开始日期', width: 140 },
    { field: 'actualEndDate', title: '实际结束日期', width: 140 }
  ],
  data: []
})

// 将实际和计划一分二
const handleMultiTask = (list) => {
  const multiRest = []
  list.forEach((item) => {
    // 计划
    const planRow = {
      ...item,
      newTitle: `计划:${item.title}`,
      flag: 1,
      newId: `p${item.id}`,
      newParentId: item.parentId ? `p${item.parentId}` : null,
      start: item.planStartDate,
      end: item.planEndDate,
      progress: 0
    }
    // 实际
    const actualRow = {
      ...item,
      newTitle: `实际:${item.title}`,
      flag: 2,
      newId: `a${item.id}`,
      newParentId: item.parentId ? `a${item.parentId}` : null,
      start: item.actualStartDate,
      end: item.actualEndDate,
      progress: 0
    }
    if (item.hasChild) {
      planRow.type = VxeGanttTaskType.Subview
      actualRow.type = VxeGanttTaskType.Subview
    }
    multiRest.push(planRow, actualRow)
  })
  return multiRest
}

const loadList = () => {
  const list = [
    { id: 10001, parentId: null, title: '任务1', planStartDate: '', planEndDate: '', actualStartDate: '', actualEndDate: '', planProgress: 90, actualProgress: 80, hasChild: true },
    { id: 10002, parentId: 10001, title: '任务2', planStartDate: '2024-02-27', planEndDate: '2024-03-03', actualStartDate: '2024-03-01', actualEndDate: '2024-03-02', planProgress: 90, actualProgress: 80 },
    { id: 10003, parentId: null, title: '任务3', planStartDate: '', planEndDate: '', actualStartDate: '', actualEndDate: '', planProgress: 90, actualProgress: 80, hasChild: true },
    { id: 10004, parentId: 10003, title: '任务4', planStartDate: '2024-03-01', planEndDate: '2024-03-02', actualStartDate: '2024-03-03', actualEndDate: '2024-03-04', planProgress: 90, actualProgress: 80 },
    { id: 10005, parentId: 10003, title: '任务5', planStartDate: '2024-03-03', planEndDate: '2024-03-05', actualStartDate: '2024-03-04', actualEndDate: '2024-03-07', planProgress: 90, actualProgress: 80 },
    { id: 10006, parentId: 10003, title: '任务6', planStartDate: '2024-03-19', planEndDate: '2024-03-22', actualStartDate: '2024-03-17', actualEndDate: '2024-03-20', planProgress: 90, actualProgress: 80 },
    { id: 10008, parentId: null, title: '任务8', planStartDate: '', planEndDate: '', actualStartDate: '', actualEndDate: '', planProgress: 90, actualProgress: 80, hasChild: true },
    { id: 10009, parentId: 10008, title: '任务9', planStartDate: '2024-03-02', planEndDate: '2024-03-03', actualStartDate: '2024-03-02', actualEndDate: '2024-03-03', planProgress: 90, actualProgress: 80 },
    { id: 10010, parentId: 10008, title: '任务10', planStartDate: '2024-03-05', planEndDate: '2024-03-07', actualStartDate: '2024-03-05', actualEndDate: '2024-03-09', planProgress: 90, actualProgress: 80 },
    { id: 10011, parentId: 10008, title: '任务11', planStartDate: '2024-03-08', planEndDate: '2024-03-11', actualStartDate: '2024-03-11', actualEndDate: '2024-03-15', planProgress: 90, actualProgress: 80 },
    { id: 10012, parentId: null, title: '任务12', planStartDate: '', planEndDate: '', actualStartDate: '', actualEndDate: '', planProgress: 90, actualProgress: 80, hasChild: true },
    { id: 10013, parentId: 10012, title: '任务13', planStartDate: '2024-03-04', planEndDate: '2024-03-05', actualStartDate: '2024-03-06', actualEndDate: '2024-03-08', planProgress: 90, actualProgress: 80 },
    { id: 10014, parentId: 10012, title: '任务14', planStartDate: '2024-03-06', planEndDate: '2024-03-07', actualStartDate: '2024-03-05', actualEndDate: '2024-03-05', planProgress: 90, actualProgress: 80 },
    { id: 10015, parentId: 10012, title: '任务15', planStartDate: '2024-03-09', planEndDate: '2024-03-12', actualStartDate: '2024-03-10', actualEndDate: '2024-03-11', planProgress: 90, actualProgress: 80 },
    { id: 10016, parentId: 10012, title: '任务16', planStartDate: '2024-03-13', planEndDate: '2024-03-15', actualStartDate: '2024-03-14', actualEndDate: '2024-03-16', planProgress: 90, actualProgress: 80 },
    { id: 10017, parentId: 10012, title: '任务17', planStartDate: '2024-03-17', planEndDate: '2024-03-21', actualStartDate: '2024-03-17', actualEndDate: '2024-03-18', planProgress: 90, actualProgress: 80 },
    { id: 10018, parentId: null, title: '任务18', planStartDate: '2024-03-11', planEndDate: '2024-03-12', actualStartDate: '2024-03-12', actualEndDate: '2024-03-15', planProgress: 90, actualProgress: 80 },
    { id: 10019, parentId: null, title: '任务19', planStartDate: '2024-03-15', planEndDate: '2024-03-17', actualStartDate: '2024-03-17', actualEndDate: '2024-03-20', planProgress: 90, actualProgress: 80 },
    { id: 10020, parentId: null, title: '任务20', planStartDate: '', planEndDate: '', actualStartDate: '', actualEndDate: '', planProgress: 90, actualProgress: 80, hasChild: true },
    { id: 10021, parentId: 10020, title: '任务21', planStartDate: '2024-03-18', planEndDate: '2024-03-21', actualStartDate: '2024-03-19', actualEndDate: '2024-03-21', planProgress: 90, actualProgress: 80 },
    { id: 10022, parentId: 10020, title: '任务22', planStartDate: '2024-03-23', planEndDate: '2024-03-25', actualStartDate: '2024-03-23', actualEndDate: '2024-03-25', planProgress: 90, actualProgress: 80 },
    { id: 10023, parentId: 10020, title: '任务23', planStartDate: '2024-03-27', planEndDate: '2024-04-02', actualStartDate: '2024-03-27', actualEndDate: '2024-03-28', planProgress: 90, actualProgress: 80 },
    { id: 10024, parentId: 10020, title: '任务24', planStartDate: '2024-04-04', planEndDate: '2024-04-06', actualStartDate: '2024-04-02', actualEndDate: '2024-04-05', planProgress: 90, actualProgress: 80 },
    { id: 10025, parentId: 10020, title: '任务25', planStartDate: '2024-04-07', planEndDate: '2024-04-11', actualStartDate: '2024-04-08', actualEndDate: '2024-04-13', planProgress: 90, actualProgress: 80 },
    { id: 10026, parentId: null, title: '任务26', planStartDate: '2024-04-05', planEndDate: '2024-04-08', actualStartDate: '2024-04-07', actualEndDate: '2024-04-10', planProgress: 90, actualProgress: 80 },
    { id: 10027, parentId: null, title: '任务27', planStartDate: '', planEndDate: '', actualStartDate: '', actualEndDate: '', planProgress: 90, actualProgress: 80, hasChild: true },
    { id: 10028, parentId: 10027, title: '任务28', planStartDate: '2024-03-10', planEndDate: '2024-03-13', actualStartDate: '2024-03-13', actualEndDate: '2024-03-14', planProgress: 90, actualProgress: 80 },
    { id: 10029, parentId: 10027, title: '任务29', planStartDate: '2024-03-14', planEndDate: '2024-03-18', actualStartDate: '2024-03-16', actualEndDate: '2024-03-18', planProgress: 90, actualProgress: 80 },
    { id: 10030, parentId: 10027, title: '任务30', planStartDate: '2024-03-19', planEndDate: '2024-03-25', actualStartDate: '2024-03-20', actualEndDate: '2024-03-26', planProgress: 90, actualProgress: 80 },
    { id: 10031, parentId: null, title: '任务31', planStartDate: '2024-03-22', planEndDate: '2024-03-24', actualStartDate: '2024-03-19', actualEndDate: '2024-03-20', planProgress: 90, actualProgress: 80 },
    { id: 10032, parentId: null, title: '任务32', planStartDate: '', planEndDate: '', actualStartDate: '', actualEndDate: '', planProgress: 90, actualProgress: 80, hasChild: true },
    { id: 10033, parentId: 10032, title: '任务33', planStartDate: '2024-03-09', planEndDate: '2024-03-14', actualStartDate: '2024-03-11', actualEndDate: '2024-03-15', planProgress: 90, actualProgress: 80 },
    { id: 10034, parentId: 10032, title: '任务34', planStartDate: '2024-03-15', planEndDate: '2024-03-21', actualStartDate: '2024-03-16', actualEndDate: '2024-03-19', planProgress: 90, actualProgress: 80 }
  ]
  const multiRest = handleMultiTask(list)
  ganttOptions.data = multiRest
}
loadList()
</script>

实现多级双进度

适用场景:需要每一层级的子任务都展示计划与实际,且父级自动汇总子级进度。

数据转换策略

与一级方案不同,这里将每个叶子任务拆分为计划和实际两条记录,而每个父级任务(hasChild: true)则作为一个独立的 Subview 组节点,其子节点包含该父级下所有叶子任务的计划和实际记录。这样,无论嵌套多少层,每一层都能独立展示双进度。

子任务视图偏移

为了让计划和实际条在视觉上不重叠,在 taskBarSubviewConfig.barStyle 中设置 top 偏移:

javascript 复制代码
taskBarSubviewConfig: {
  barStyle({ row }) {
    if (row.flag === 1) {
      return { top: '-12px', backgroundColor: '#40d9eff' }
    }
    if (row.flag === 2) {
      return { top: '12px', backgroundColor: '#31d231' }
    }
  }
}
  • 计划条上移 12px,实际条下移 12px,形成双条并排效果。
  • 父级组节点自动汇总子级进度,并显示为"总览"条。

代码

html 复制代码
<template>
  <div>
    <vxe-switch v-model="treeConfig.showIcon"></vxe-switch>

    <vxe-gantt v-bind="ganttOptions"></vxe-gantt>
  </div>
</template>

<script setup>
import { reactive } from 'vue'
import { VxeGanttTaskType } from 'vxe-gantt'

const treeConfig = reactive({
  transform: true, // 自动将列表转为树结构
  rowField: 'newId', // 自定义树节点关联的主键
  parentField: 'newParentId', // 自定义树节点关联的父节点的字段名
  showIcon: true // 是否显示展开按钮
})
const ganttOptions = reactive({
  border: true,
  height: 600,
  columnConfig: {
    resizable: true
  },
  treeConfig,
  taskConfig: {
    titleField: 'newTitle', // 自定义标题字段名
    startField: 'start', // 自定义开始日期字段名
    endField: 'end', // 自定义结束期字段名
    progressField: 'progress', // 自定义进度值字段名
    typeField: 'type' // 自定义渲染类型字段名
  },
  taskBarSubviewConfig: {
    showOverview: true, // 是否显示任务总览,当子任务被展开后自动显示任务总览
    // 自定义子任务视图任务条的样式
    barStyle({ row }) {
      if (row.flag === 1) {
        return {
          top: '-12px', // 偏移位置,计划于实际不重叠
          backgroundColor: '#40d9eff'
        }
      }
      if (row.flag === 2) {
        return {
          top: '12px', // 偏移位置,计划于实际不重叠
          backgroundColor: '#31d231'
        }
      }
    }
  },
  taskBarConfig: {
    showContent: true, // 是否在任务条显示内容
    moveable: true, // 是否允许拖拽任务移动日期
    resizable: true, // 是否允许拖拽任务调整日期
    barStyle({ row }) {
      return {
        round: true, // 圆角
        bgColor: row.flag === 1 ? '#40d9eff' : '#31d231' // 已完成部分任务条的背景颜色
      }
    }
  },
  taskViewConfig: {
    tableStyle: {
      width: 200
    },
    viewStyle: {
      border: 'inner'
    }
  },
  columns: [
    { field: 'newTitle', title: '任务名称', minWidth: 160, treeNode: true },
    { field: 'planStartDate', title: '计划开始日期', width: 140 },
    { field: 'planEndDate', title: '实际 结束日期', width: 140 },
    { field: 'actualStartDate', title: '计划开始日期', width: 140 },
    { field: 'actualEndDate', title: '实际结束日期', width: 140 }
  ],
  data: []
})

// 将实际和计划一分二
const handleMultiTask = (list) => {
  const multiRest = []
  list.forEach((item) => {
    if (item.hasChild) {
      const groupRow = {
        ...item,
        newTitle: `${item.title}`,
        flag: 3,
        newId: `g${item.id}`,
        newParentId: item.parentId ? `g${item.parentId}` : null,
        start: '',
        end: '',
        progress: 0,
        type: VxeGanttTaskType.Subview
      }
      multiRest.push(groupRow)
    } else {
      // 计划
      const planRow = {
        ...item,
        newTitle: `计划:${item.title}`,
        flag: 1,
        newId: `p${item.id}`,
        newParentId: item.parentId ? `g${item.parentId}` : null,
        start: item.planStartDate,
        end: item.planEndDate,
        progress: 0
      }
      // 实际
      const actualRow = {
        ...item,
        newTitle: `实际:${item.title}`,
        flag: 2,
        newId: `a${item.id}`,
        newParentId: item.parentId ? `g${item.parentId}` : null,
        start: item.actualStartDate,
        end: item.actualEndDate,
        progress: 0
      }
      multiRest.push(planRow, actualRow)
    }
  })
  return multiRest
}

const loadList = () => {
  const list = [
    { id: 10001, parentId: null, title: '任务1', planStartDate: '', planEndDate: '', actualStartDate: '', actualEndDate: '', planProgress: 90, actualProgress: 80, hasChild: true },
    { id: 10002, parentId: 10001, title: '任务2', planStartDate: '2024-02-27', planEndDate: '2024-03-03', actualStartDate: '2024-03-01', actualEndDate: '2024-03-02', planProgress: 90, actualProgress: 80 },
    { id: 10003, parentId: null, title: '任务3', planStartDate: '', planEndDate: '', actualStartDate: '', actualEndDate: '', planProgress: 90, actualProgress: 80, hasChild: true },
    { id: 10004, parentId: 10003, title: '任务4', planStartDate: '2024-03-01', planEndDate: '2024-03-02', actualStartDate: '2024-03-03', actualEndDate: '2024-03-04', planProgress: 90, actualProgress: 80 },
    { id: 10005, parentId: 10003, title: '任务5', planStartDate: '2024-03-03', planEndDate: '2024-03-05', actualStartDate: '2024-03-04', actualEndDate: '2024-03-07', planProgress: 90, actualProgress: 80, hasChild: true },
    { id: 10006, parentId: 10003, title: '任务6', planStartDate: '2024-03-19', planEndDate: '2024-03-22', actualStartDate: '2024-03-17', actualEndDate: '2024-03-20', planProgress: 90, actualProgress: 80 },
    { id: 10007, parentId: 10005, title: '任务7', planStartDate: '2024-03-17', planEndDate: '2024-03-20', actualStartDate: '2024-03-19', actualEndDate: '2024-03-22', planProgress: 90, actualProgress: 80 },
    { id: 10008, parentId: null, title: '任务8', planStartDate: '', planEndDate: '', actualStartDate: '', actualEndDate: '', planProgress: 90, actualProgress: 80, hasChild: true },
    { id: 10009, parentId: 10008, title: '任务9', planStartDate: '2024-03-02', planEndDate: '2024-03-03', actualStartDate: '2024-03-02', actualEndDate: '2024-03-03', planProgress: 90, actualProgress: 80 },
    { id: 10010, parentId: 10008, title: '任务10', planStartDate: '2024-03-05', planEndDate: '2024-03-07', actualStartDate: '2024-03-05', actualEndDate: '2024-03-09', planProgress: 90, actualProgress: 80 },
    { id: 10011, parentId: 10008, title: '任务11', planStartDate: '2024-03-08', planEndDate: '2024-03-11', actualStartDate: '2024-03-11', actualEndDate: '2024-03-15', planProgress: 90, actualProgress: 80 },
    { id: 10012, parentId: null, title: '任务12', planStartDate: '', planEndDate: '', actualStartDate: '', actualEndDate: '', planProgress: 90, actualProgress: 80, hasChild: true },
    { id: 10013, parentId: 10012, title: '任务13', planStartDate: '2024-03-04', planEndDate: '2024-03-05', actualStartDate: '2024-03-06', actualEndDate: '2024-03-08', planProgress: 90, actualProgress: 80 },
    { id: 10014, parentId: 10012, title: '任务14', planStartDate: '2024-03-06', planEndDate: '2024-03-07', actualStartDate: '2024-03-03', actualEndDate: '2024-03-05', planProgress: 90, actualProgress: 80 },
    { id: 10015, parentId: 10012, title: '任务15', planStartDate: '2024-03-09', planEndDate: '2024-03-12', actualStartDate: '2024-03-10', actualEndDate: '2024-03-11', planProgress: 90, actualProgress: 80, hasChild: true },
    { id: 10016, parentId: 10012, title: '任务16', planStartDate: '2024-03-13', planEndDate: '2024-03-15', actualStartDate: '2024-03-17', actualEndDate: '2024-03-18', planProgress: 90, actualProgress: 80, hasChild: true },
    { id: 10017, parentId: 10012, title: '任务17', planStartDate: '2024-03-17', planEndDate: '2024-03-21', actualStartDate: '2024-03-18', actualEndDate: '2024-03-19', planProgress: 90, actualProgress: 80 },
    { id: 10018, parentId: null, title: '任务18', planStartDate: '2024-03-11', planEndDate: '2024-03-12', actualStartDate: '2024-03-12', actualEndDate: '2024-03-15', planProgress: 90, actualProgress: 80 },
    { id: 10019, parentId: null, title: '任务19', planStartDate: '2024-03-15', planEndDate: '2024-03-17', actualStartDate: '2024-03-17', actualEndDate: '2024-03-20', planProgress: 90, actualProgress: 80 },
    { id: 10020, parentId: null, title: '任务20', planStartDate: '', planEndDate: '', actualStartDate: '', actualEndDate: '', planProgress: 90, actualProgress: 80, hasChild: true },
    { id: 10021, parentId: 10020, title: '任务21', planStartDate: '2024-03-18', planEndDate: '2024-03-21', actualStartDate: '2024-03-19', actualEndDate: '2024-03-21', planProgress: 90, actualProgress: 80 },
    { id: 10022, parentId: 10020, title: '任务22', planStartDate: '2024-03-23', planEndDate: '2024-03-25', actualStartDate: '2024-03-23', actualEndDate: '2024-03-25', planProgress: 90, actualProgress: 80 },
    { id: 10023, parentId: 10020, title: '任务23', planStartDate: '2024-03-27', planEndDate: '2024-04-02', actualStartDate: '2024-03-27', actualEndDate: '2024-03-28', planProgress: 90, actualProgress: 80 },
    { id: 10024, parentId: 10020, title: '任务24', planStartDate: '2024-04-04', planEndDate: '2024-04-06', actualStartDate: '2024-04-02', actualEndDate: '2024-04-05', planProgress: 90, actualProgress: 80 },
    { id: 10025, parentId: 10020, title: '任务25', planStartDate: '2024-04-07', planEndDate: '2024-04-11', actualStartDate: '2024-04-08', actualEndDate: '2024-04-13', planProgress: 90, actualProgress: 80 },
    { id: 10026, parentId: null, title: '任务26', planStartDate: '2024-04-05', planEndDate: '2024-04-08', actualStartDate: '2024-04-07', actualEndDate: '2024-04-10', planProgress: 90, actualProgress: 80 },
    { id: 10027, parentId: null, title: '任务27', planStartDate: '', planEndDate: '', actualStartDate: '', actualEndDate: '', planProgress: 90, actualProgress: 80, hasChild: true },
    { id: 10028, parentId: 10027, title: '任务28', planStartDate: '2024-03-10', planEndDate: '2024-03-13', actualStartDate: '2024-03-13', actualEndDate: '2024-03-14', planProgress: 90, actualProgress: 80 },
    { id: 10029, parentId: 10027, title: '任务29', planStartDate: '2024-03-14', planEndDate: '2024-03-18', actualStartDate: '2024-03-16', actualEndDate: '2024-03-18', planProgress: 90, actualProgress: 80 },
    { id: 10030, parentId: 10027, title: '任务30', planStartDate: '2024-03-19', planEndDate: '2024-03-25', actualStartDate: '2024-03-20', actualEndDate: '2024-03-26', planProgress: 90, actualProgress: 80 },
    { id: 10031, parentId: null, title: '任务31', planStartDate: '2024-03-22', planEndDate: '2024-03-24', actualStartDate: '2024-03-19', actualEndDate: '2024-03-20', planProgress: 90, actualProgress: 80 },
    { id: 10032, parentId: null, title: '任务32', planStartDate: '', planEndDate: '', actualStartDate: '', actualEndDate: '', planProgress: 90, actualProgress: 80, hasChild: true },
    { id: 10033, parentId: 10032, title: '任务33', planStartDate: '2024-03-09', planEndDate: '2024-03-14', actualStartDate: '2024-03-11', actualEndDate: '2024-03-15', planProgress: 90, actualProgress: 80 },
    { id: 10034, parentId: 10032, title: '任务34', planStartDate: '2024-03-15', planEndDate: '2024-03-21', actualStartDate: '2024-03-16', actualEndDate: '2024-03-19', planProgress: 90, actualProgress: 80 },
    { id: 10035, parentId: 10015, title: '任务35', planStartDate: '2024-03-16', planEndDate: '2024-03-18', actualStartDate: '2024-03-17', actualEndDate: '2024-03-19', planProgress: 90, actualProgress: 80 },
    { id: 10036, parentId: 10015, title: '任务36', planStartDate: '2024-03-12', planEndDate: '2024-03-14', actualStartDate: '2024-03-13', actualEndDate: '2024-03-15', planProgress: 90, actualProgress: 80 },
    { id: 10037, parentId: 10016, title: '任务37', planStartDate: '2024-03-19', planEndDate: '2024-03-22', actualStartDate: '2024-03-21', actualEndDate: '2024-03-23', planProgress: 90, actualProgress: 80 },
    { id: 10038, parentId: 10016, title: '任务38', planStartDate: '2024-03-23', planEndDate: '2024-03-25', actualStartDate: '2024-03-24', actualEndDate: '2024-03-27', planProgress: 90, actualProgress: 80 }
  ]
  const multiRest = handleMultiTask(list)
  ganttOptions.data = multiRest
}

loadList()
</script>

两种实现方式对比

特性 一级双进度 多级双进度
拆分粒度 仅顶层任务 所有层级(包括叶子)
父级汇总 不汇总子级进度 自动汇总子级双进度
子任务条偏移 无(仅单条) 计划/实际偏移显示
适用场景 只需关注顶层对比 需要精细到每层级的对比
数据复杂度 较低 较高(需要递归处理)

注意事项

  1. 主键唯一性
    拆分后的记录必须使用全新主键(如 p10001、a10001),否则树结构会冲突。
  2. 父级关联
    计划和实际记录的 parentId 需要映射到对应的组节点 ID(如 g10001),而非原始父 ID。
  3. 日期字段
    确保计划日期和实际日期字段存在(示例中为 planStartDate 等),并在 start 和 end 中正确引用。
  4. 进度值
    示例中将进度固定为 0,实际使用时可从原始数据中读取 planProgress / actualProgress 并赋值给 progress 字段。
  5. 样式微调
    偏移量 top: '-12px' 和 top: '12px' 可根据行高调整,以保证双条不重叠且视觉平衡。
  6. 开启树形展开
    使用 treeConfig.showIcon: true 让用户可展开/折叠节点,查看子任务详情。

vxe-gantt 实现该功能的核心在于数据拆分和父级分组,配合 VxeGanttTaskType.Subview 和样式偏移,即可获得清晰、直观的双进度甘特图。

https://gantt.vxeui.com

相关推荐
踩着两条虫14 小时前
可视化 vs 终端 vs 云端:VTJ.PRO、Claude Code、Codex 三强横评
前端·vue.js·人工智能·低代码·架构
前端毕业班15 小时前
uni-app 小程序支持 teleport 了
前端·javascript·vue.js
妙码生花17 小时前
从 PHP 到 AI + Golang,程序员自救转型手记(二十一):网络请求封装优化
前端·javascript·vue.js
sowhat8821 小时前
Vue3+vite+Ts+pinia—第八章 生命周期
vue.js
这是个栗子21 小时前
【Vue代码分析】 import.meta.env.DEV:现代前端工程中的环境变量
前端·javascript·vue.js·环境变量
乌夷1 天前
Vue.js 3 的设计思路
前端·javascript·vue.js
前端之虎陈随易1 天前
编程语言级别的Skill市场,AI Agent 的未来形态
前端·vue.js·人工智能·typescript·node.js
格子软件2 天前
2026年GEO贴牌代理:分布式多级分账状态机源码深度解构
java·vue.js·分布式·vue·geo
之歆2 天前
Vue商品详情与放大镜组件
前端·javascript·vue.js