vue甘特图实现一行显示多个子任务

在项目管理中,我们经常需要将一组子任务聚合到父级任务行中展示,以便直观地看到整个任务组的完整时间跨度。vxe-gantt 通过 子任务视图(Subview) 机制,可以将任意层级的子任务渲染到父级任务行内,实现“一行显示多个子任务”的效果。本文介绍两种方案:单进度条(聚合展示)和双进度条(计划 vs 实际对比)。

核心配置

子任务视图的核心配置由两部分组成:

配置项 说明
父任务标记 在数据中为父任务设置 type: VxeGanttTaskType.Subview(或字符串 ‘subview’),表示该行是一个子任务容器。
taskBarSubviewConfig 全局配置子任务视图的样式和行为。

方案一:单进度条(聚合展示)

适用场景:只需展示子任务的进度和分布,不区分计划和实际。

  • 配置要点
    • 父任务设置 type: VxeGanttTaskType.Subview
    • 子任务正常配置 start、end、progress
    • 可选开启 showOverview: true 显示总览条

行内展示子任务由 task-bar-subview-config 和父级任务设置type= ‘subview’ | VxeGanttTaskType.Subview 来启用。启用后会将所有子任务渲染到父级任务中,可以直观的看到任务全程进度。 还可以设置 task-bar-subview-config.showOverview 是否显示任务总览,当子任务被展开后自动显示任务总览

代码

<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: 'id', // 自定义树节点关联的主键
  parentField: 'parentId', // 自定义树节点关联的父节点的字段名
  showIcon: false // 是否显示展开按钮
})

const ganttOptions = reactive({
  border: true,
  height: 500,
  treeConfig,
  taskConfig: {
    titleField: 'title', // 自定义标题字段名
    startField: 'start', // 自定义开始日期字段名
    endField: 'end', // 自定义结束期字段名
    progressField: 'progress', // 自定义进度值字段名
    typeField: 'type' // 自定义渲染类型字段名
  },
  taskBarSubviewConfig: {
    showOverview: true // 是否显示任务总览,当子任务被展开后自动显示任务总览
  },
  taskBarConfig: {
    showContent: true, // 是否在任务条显示内容
    moveable: true, // 是否允许拖拽任务移动日期
    resizable: true, // 是否允许拖拽任务调整日期
    barStyle: {
      round: true, // 圆角
      bgColor: '#fca60b', // 任务条的背景颜色
      completedBgColor: '#65c16f', // 已完成部分任务条的背景颜色
      overviewBgColor: '#617b63' // 总览任务条的背景颜色
    }
  },
  columns: [{ field: 'title', title: '任务名称', treeNode: true }],
  data: [
    { id: 10001, parentId: null, title: '任务1', start: '', end: '', progress: 0, type: VxeGanttTaskType.Subview },
    { id: 10002, parentId: 10001, title: '任务2', start: '2024-03-01', end: '2024-03-03', progress: 60 },
    { id: 10003, parentId: null, title: '任务3', start: '', end: '', progress: 0, type: VxeGanttTaskType.Subview },
    { id: 10004, parentId: 10003, title: '任务4', start: '2024-03-01', end: '2024-03-02', progress: 100 },
    { id: 10005, parentId: 10003, title: '任务5', start: '', end: '', progress: 0, type: VxeGanttTaskType.Subview },
    { id: 10006, parentId: 10003, title: '任务6', start: '2024-03-19', end: '2024-03-22', progress: 90 },
    { id: 10007, parentId: 10005, title: '任务7', start: '2024-03-03', end: '2024-03-05', progress: 50 },
    { id: 10008, parentId: null, title: '任务8', start: '', end: '', progress: 0, type: VxeGanttTaskType.Subview },
    { id: 10009, parentId: 10008, title: '任务9', start: '2024-03-02', end: '2024-03-03', progress: 60 },
    { id: 10010, parentId: 10008, title: '任务10', start: '2024-03-05', end: '2024-03-07', progress: 80 },
    { id: 10011, parentId: 10008, title: '任务11', start: '2024-03-08', end: '2024-03-11', progress: 80 },
    { id: 10012, parentId: null, title: '任务12', start: '', end: '', progress: 0, type: VxeGanttTaskType.Subview },
    { id: 10013, parentId: 10012, title: '任务13', start: '2024-03-04', end: '2024-03-05', progress: 80 },
    { id: 10014, parentId: 10012, title: '任务14', start: '2024-03-06', end: '2024-03-07', progress: 80 },
    { id: 10015, parentId: 10012, title: '任务15', start: '2024-03-09', end: '2024-03-12', progress: 100 },
    { id: 10016, parentId: 10012, title: '任务16', start: '2024-03-13', end: '2024-03-15', progress: 90 },
    { id: 10017, parentId: 10012, title: '任务17', start: '2024-03-17', end: '2024-03-21', progress: 80 },
    { id: 10018, parentId: null, title: '任务18', start: '2024-03-02', end: '2024-03-06', progress: 80 },
    { id: 10019, parentId: null, title: '任务19', start: '2024-03-06', end: '2024-03-11', progress: 80 },
    { id: 10020, parentId: null, title: '任务20', start: '', end: '', progress: 0, type: VxeGanttTaskType.Subview },
    { id: 10021, parentId: 10020, title: '任务21', start: '2024-03-18', end: '2024-03-21', progress: 80 },
    { id: 10022, parentId: 10020, title: '任务22', start: '2024-03-23', end: '2024-03-25', progress: 80 },
    { id: 10023, parentId: 10020, title: '任务23', start: '2024-03-27', end: '2024-04-02', progress: 100 },
    { id: 10024, parentId: 10020, title: '任务24', start: '2024-04-04', end: '2024-04-06', progress: 100 },
    { id: 10025, parentId: 10020, title: '任务25', start: '2024-04-07', end: '2024-04-11', progress: 100 },
    { id: 10026, parentId: null, title: '任务26', start: '2024-03-20', end: '2024-04-01', progress: 80 },
    { id: 10027, parentId: null, title: '任务27', start: '', end: '', progress: 0, type: VxeGanttTaskType.Subview },
    { id: 10028, parentId: 10027, title: '任务28', start: '2024-03-10', end: '2024-03-13', progress: 80 },
    { id: 10029, parentId: 10027, title: '任务29', start: '2024-03-14', end: '2024-03-18', progress: 80 },
    { id: 10030, parentId: 10027, title: '任务30', start: '2024-03-19', end: '2024-03-25', progress: 100 },
    { id: 10031, parentId: null, title: '任务31', start: '2024-03-17', end: '2024-03-22', progress: 80 },
    { id: 10032, parentId: null, title: '任务32', start: '', end: '', progress: 0, type: VxeGanttTaskType.Subview },
    { id: 10033, parentId: 10032, title: '任务33', start: '2024-03-09', end: '2024-03-14', progress: 80 },
    { id: 10034, parentId: 10032, title: '任务34', start: '2024-03-15', end: '2024-03-21', progress: 80 },
    { id: 10035, parentId: 10005, title: '任务35', start: '2024-03-06', end: '2024-03-11', progress: 80 },
    { id: 10036, parentId: 10005, title: '任务36', start: '2024-03-12', end: '2024-03-17', progress: 80 }
  ]
})
</script>

方案二:双进度条(计划 vs 实际)

适用场景:需要同时对比计划进度和实际进度,且支持多级子任务。

  • 实现思路
  • 将每个叶子任务拆分为两条记录:
    • 计划记录(flag: 1):使用 planStartDate / planEndDate
    • 实际记录(flag: 2):使用 actualStartDate / actualEndDate

父任务转换为组节点(type: Subview),其子节点为拆分后的计划和实际记录。通过 barStyle 的 top 偏移让两条错开显示。

代码

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

两种方案对比

特性 单进度条方案 双进度条方案
数据复杂度 低(原始数据结构简单) 较高(需要拆分为计划和实际)
视觉呈现 每个子任务显示为一条进度条 每个子任务显示计划和实际两条,错开排列
父级汇总 显示总览条(showOverview: true) 显示总览条,同时汇总双进度
适用场景 简单进度跟踪 计划 vs 实际偏差分析
配置难度 简单(仅需标记 Subview) 中等(需要数据转换和样式偏移)
  • 主键唯一性:双进度条方案中,拆分后的 newId 必须全局唯一(通过 g、p、a 前缀区分),否则树结构会混乱。
  • 父级关联:子任务的 newParentId 需指向对应父级组节点的 ID(如 g10001),而非原始 parentId。
  • 日期字段:双进度条方案需要原始数据包含 planStartDate、planEndDate、actualStartDate、actualEndDate 等字段。
  • 空日期处理:如果计划或实际日期为空,对应的任务条将不显示。
  • 偏移量调整:top 的绝对值(如 -12px / 12px)可根据行高适当调整,以保证双条不重叠且视觉平衡。
  • 交互支持:两种方案都支持 moveable(拖拽移动)和 resizable(拖拽调整日期),但双进度条方案中,计划和实际条需要分别拖拽,如果希望联动修改,需要自行实现事件处理。

总结

  • vxe-gantt 的子任务视图(Subview)功能,为“一行显示多个子任务”提供了两种灵活的方案:
    • 单进度条:轻量级方案,只需为父任务标记 type: Subview,即可将子任务聚合到父行中展示,适合简单进度跟踪。
    • 双进度条:通过数据拆分和样式偏移,实现计划和实际的并行对比,适合需要偏差分析的项目管理场景。

https://gantt.vxeui.com

原文链接:vue甘特图实现一行显示多个子任务

© 版权声明
THE END
喜欢就支持一下吧
点赞14 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容