在项目管理中,我们经常需要将一组子任务聚合到父级任务行中展示,以便直观地看到整个任务组的完整时间跨度。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,即可将子任务聚合到父行中展示,适合简单进度跟踪。
- 双进度条:通过数据拆分和样式偏移,实现计划和实际的并行对比,适合需要偏差分析的项目管理场景。
原文链接:vue甘特图实现一行显示多个子任务
© 版权声明
THE END


![表情[baoquan]-拾光赋](https://blogs.ink/wp-content/themes/zibll/img/smilies/baoquan.gif)


暂无评论内容