如何用 vue 甘特图来实现计划和实际双任务条进度展示

在项目管理中,同时展示任务的计划进度与实际进度,能够直观对比执行偏差,是项目监控的核心需求。vxe-gantt 提供了灵活的子任务视图(Subview)机制,可以轻松将单个任务拆分为"计划"和"实际"两条任务条,在同一行内并行展示,实现清晰的双进度对比。

本文将通过一个完整的示例,演示如何利用子任务视图和树形结构,将原始数据拆分为计划与实际两条记录,并分别以不同颜色和位置呈现在甘特图中。

核心实现思路

  • 数据拆分:将每个原始任务拆分为三条记录:
    • 父节点:作为容器,类型标记为 VxeGanttTaskType.Subview。
    • 计划子节点:使用计划日期(planStartDate / planEndDate),flag: 1。
    • 实际子节点:使用实际日期(actualStartDate / actualEndDate),flag: 2。
  • 树形结构:通过 treeConfig 将拆分后的数据组装为树形关系。
  • 样式区分:通过 barStyle 根据 flag 值设置不同的颜色和垂直偏移量,使两条任务条错开显示。

关键配置详解

配置项 作用
treeConfig 启用树形结构,将拆分后的记录按父子关系组织。
taskConfig.typeField 指定类型字段,用于识别 Subview 节点。
taskBarSubviewConfig.barStyle 控制子任务条样式,通过 top 偏移实现上下错位。
taskBarConfig.barStyle 控制主任务条样式,根据 flag 区分计划/实际颜色。
VxeGanttTaskType.Subview 标记父节点为子任务视图容器。

代码

html 复制代码
<template>
  <div>
    <vxe-gantt v-bind="ganttOptions"></vxe-gantt>
  </div>
</template>

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

const ganttOptions = reactive({
  border: true,
  height: 500,
  loading: false,
  cellConfig: {
    height: 60
  },
  treeConfig: {
    transform: true,
    rowField: 'id',
    parentField: 'parentId'
  },
  taskConfig: {
    startField: 'start',
    endField: 'end',
    typeField: 'type'
  },
  taskBarSubviewConfig: {
    barStyle({ row }) {
      if (row.flag === 1) {
        return {
          style: {
            top: '-12px' // 偏移位置,计划于实际不重叠
          }
        }
      }
      if (row.flag === 2) {
        return {
          style: {
            top: '12px' // 偏移位置,计划于实际不重叠
          }
        }
      }
    }
  },
  taskBarConfig: {
    showContent: true,
    barStyle({ row }) {
      return {
        round: true, // 圆角
        bgColor: row.flag === 1 ? '#40d9eff' : '#31d231', // 任务条的背景颜色
        completedBgColor: row.flag === 1 ? '#409eff' : '#14ae14' // 已完成部分任务条的背景颜色
      }
    }
  },
  taskViewConfig: {
    tableStyle: {
      width: 480
    }
  },
  columns: [
    { field: 'title', title: '任务名称', minWidth: 100 },
    { field: 'planStartDate', title: '计划开始时间', width: 100 },
    { field: 'planEndDate', title: '计划结束时间', width: 100 },
    { field: 'actualStartDate', title: '实际开始时间', width: 100 },
    { field: 'actualEndDate', title: '实际结束时间', width: 100 }
  ],
  data: []
})

// 模拟后端接口
const loadList = () => {
  ganttOptions.loading = true
  setTimeout(() => {
    const list = [
      { id: 10001, parentId: null, title: '任务R790', planStartDate: '2024-03-03', planEndDate: '2024-03-15', actualStartDate: '2024-03-03', actualEndDate: '2024-03-12' },
      { id: 10002, parentId: null, title: 'B项目', planStartDate: '2024-03-10', planEndDate: '2024-03-25', actualStartDate: '2024-03-08', actualEndDate: '2024-03-16' },
      { id: 10003, parentId: null, title: 'C项目', planStartDate: '2024-03-20', planEndDate: '2024-04-10', actualStartDate: '2024-03-22', actualEndDate: '2024-04-01' },
      { id: 10004, parentId: null, title: '任务D554', planStartDate: '2024-03-28', planEndDate: '2024-04-19', actualStartDate: '2024-03-28', actualEndDate: '2024-04-12' },
      { id: 10005, parentId: null, title: 'E项目', planStartDate: '2024-04-05', planEndDate: '2024-04-28', actualStartDate: '2024-04-01', actualEndDate: '2024-04-24' }
    ]
    // 转成子任务视图
    const ganttData = []
    list.forEach((item) => {
      const currRow = XEUtils.assign({}, item, { type: VxeGanttTaskType.Subview })
      const planRow = XEUtils.assign({}, item, {
        id: 10000000 + item.id,
        title: '计划',
        parentId: item.id,
        start: item.planStartDate,
        end: item.planEndDate,
        flag: 1
      })
      const actualRow = XEUtils.assign({}, item, {
        id: 20000000 + item.id,
        parentId: item.id,
        title: '实际',
        start: item.actualStartDate,
        end: item.actualEndDate,
        flag: 2
      })
      ganttData.push(currRow)
      ganttData.push(planRow)
      ganttData.push(actualRow)
    })
    ganttOptions.data = ganttData
    ganttOptions.loading = false
  }, 200)
}

loadList()
</script>

实现要点解析

1. 数据拆分逻辑

  • 个原始任务生成三条记录:
    • 父节点:保留原始信息,type 设为 Subview,作为容器。
    • 计划子节点:使用计划日期,id 重新生成(避免与父节点冲突),parentId 指向父节点。
    • 实际子节点:使用实际日期,id 同样重新生成,parentId 指向父节点。

2. 样式错位(top 偏移)

在 taskBarSubviewConfig.barStyle 中,通过 top 属性让计划条和实际条在垂直方向上错开:

类型 top 值 效果
计划(flag: 1) -12px 任务条向上偏移,显示在上方
实际(flag: 2) 12px 任务条向下偏移,显示在下方

这样两条任务条在同一行内并行显示,互不遮挡。

3. 颜色区分

通过 taskBarConfig.barStyle 返回动态样式:

类型 背景色 已完成颜色
计划(flag: 1) #40d9eff(蓝色系) #409eff
实际(flag: 2) #31d231(绿色系) #14ae14

通过 子任务视图(Subview) + 树形结构,vxe-gantt 提供了一种简洁而强大的方式来实现计划与实际双进度

gantt.vxeui.com

相关推荐
京东云开发者4 小时前
【全栈实践】第一个 AI Agent 项目:从零搭建 AI 音频创作助手【最终版】
前端·vue.js·音视频开发
一颗烂土豆5 小时前
ECharts 太平面?试试这款 Vue 3D 图表库
前端·vue.js·echarts
宿6745 小时前
vscode文件夹
前端·vue.js·vscode
雪芽蓝域zzs6 小时前
新建前端pnpm(vue js ) 仿若依项目(一)
前端·javascript·vue.js
vipbic6 小时前
网站升级了,我却有点舍不得
前端·vue.js·后端
YWL6 小时前
OpenLayers测距测面:完整测量工具
前端·javascript·vue.js·信息可视化·openlayers
独泪了无痕16 小时前
viewer.js 安装与配置指南:实现图片预览功能
前端·vue.js
lemon_yyds1 天前
uVIew2 弹框滚动穿透问题-[uu-popup ]组件
vue.js
超爱吃香菜的菜鸟1 天前
关于pnpm 部分使用(一)
前端·vue.js