vue3 甘特图(四):甘特图进度条拆分

vue3 甘特图(四):甘特图进度条拆分

  目前项目采取有两种拆分进度条的方式。单个进度条展示多维度数据,一个总任务条下有多个子进程展示。

  1.1 单个进度条展示多维度数据(不关联时间),如图

  

此方法是通过设置单个进度条上内容,以一个进度条为容器,修改内容(即task_text内容)展示多维度的数据,比如当前项目需要展示项目消耗的工时/预估工时、进度、告警事件、滞后事件,将其分割成固定的多少段(不关联时间)直接展示数据。
核心逻辑(直接替换进度条内容):
gantt.templates.task_text = function (start, end, task) {
    if (task.projectStatus == '暂无任务') {
      return `
                <div class="project-bar no-data">
                    <div class="no">暂无任务</div>
                </div>
               `
    } else {
      let progressState = ''
      let progressPerson = ''
      // 超前  滞后 完成 暂无任务 延期
      if (task.projectStatus == '滞后' || task.projectStatus == '延期') {
        progressState = 'lag'
        let warnDiv = '' //告警事件 部门
        let errorDic = task.functionName //滞后严重功能
        let _index = 1
        let warnIcon = ''
        let errorIcon = ''
        for (const key in task.projectMap) {
          if (_index <= 3) {
            warnDiv += `${_index}.${key}(${task.projectMap[key]})  `
            _index += 1
          }
        }
        if (Object.keys(task.projectMap).length > 0) {
          warnIcon = `<div class="icon-box">
                                        <div class="warn-icon"></div>
                                        <div class="icon-line warn"></div>
                                    </div>`
        }
        if (errorDic) {
          errorIcon = `<div class="icon-box">
                                        <div class="error-icon"></div>
                                        <div class="icon-line error"></div>
                                    </div>`
        }
        // 判断是否因为时间过短 导致图上文字显示不全 采用钓鱼线引出
        let inLine = data.topTime == 'year' ? 'no-in-line' : 'in-line'
        if (
          new Date(task.endTime).getTime() - new Date(task.startTime).getTime() <
          3600 * 1000 * 24 * 30
        ) {
          inLine = 'no-in-line'
        }
        progressPerson = `  <div class="project-warn ${inLine}">
                                    ${warnIcon}
                                    <div class="war-des">${warnDiv}</div>
                                </div>
                                <div class="project-error ${inLine}">
                                    ${errorIcon}
                                    <div class="error-des">${errorDic}</div>
                                </div>
                             `
      } else {
        progressState = 'normal'
      }
      return `
                <div class="project-bar">
                    <div class="project-all-time">${task.projectUsedTime}/${
        task.projectTotalTime
      }</div>
                    <div class="project-progress ${progressState}">${
        (task.projectProgress * 100).toFixed(1) + '%'
      }</div>
                    ${progressPerson}
                </div>
               `
    }
  }
完整代码:
<section class="my-gantt">
    <div class="time-box">
      <el-radio-group v-model="data.timeState" @change="changeTime">
        <el-radio-button
          v-for="(time, t_index) in data.timeList"
          :key="t_index"
          :label="time.code"
          size="default"
          border
          >{{ time.name }}</el-radio-button
        >
      </el-radio-group>
    </div>
    <div id="gantt_here" class="gantt-container"></div>
  </section>
</template>

<script setup>
import { reactive, toRefs, onBeforeMount, onMounted, watchEffect, defineExpose } from 'vue'

import { gantt } from 'dhtmlx-gantt'
import 'dhtmlx-gantt/codebase/dhtmlxgantt.css'
// 此方法是通过设置单个进度条上内容,以一个进度条为容器,修改内容(即task_text内容)展示多维度的数据,比如当前项目需要展示项目消耗的工时/预估工时、进度、告警事件、滞后事件,将其分割成固定的多少段(不关联时间)直接展示数据
const data = reactive({
  timeList: [
    // {
    //     name: "周",
    //     code: "week",
    // },
    {
      name: '',
      code: 'month'
    },
    {
      name: '',
      code: 'quarter'
    },
    {
      name: '',
      code: 'year'
    }
  ],
  timeState: 'month',
  demoData: {
    data: [
      {
        id: 520,
        projectName: '项目1',
        startTime: '2023-09-25',
        endTime: '2023-10-31',
        showEndTime: '2023-11-01',
        projectStatus: '暂无任务',
        projectProgress: 0,
        projectRatio: '',
        projectTotalTime: 0,
        projectUsedTime: 0,
        functionName: '',
        xmdj: '2',
        cityName: '成都',
        name: '1',
        projectMap: {},
        parent: 0,
        start_date: '2023-09-24 16:00:00.000',
        end_date: '2023-10-31 16:00:00.000',
        progress: 0.5,
        duration: 37
      },
      {
        id: 517,
        projectName: '项目2',
        startTime: '2023-09-18',
        endTime: '2023-10-23',
        showEndTime: '2023-10-24',
        projectStatus: '暂无任务',
        projectProgress: 0,
        projectRatio: '',
        projectTotalTime: 0,
        projectUsedTime: 0,
        functionName: '',
        xmdj: '0',
        cityName: '深圳',
        name: '2',
        projectMap: {},
        parent: 0,
        start_date: '2023-09-17 16:00:00.000',
        end_date: '2023-10-23 16:00:00.000',
        progress: 0.2
      },
      {
        id: 505,
        projectName: '项目3',
        startTime: '2023-09-04',
        endTime: '2023-09-30',
        showEndTime: '2023-10-01',
        projectStatus: '滞后',
        projectProgress: 0.76,
        projectRatio: 0.12,
        projectTotalTime: 3267.6,
        projectUsedTime: 2477.7,
        functionName: '现状还原',
        xmdj: '3',
        cityName: '成都',
        name: '3',
        projectMap: {},
        parent: 0,
        start_date: '2023-09-03 16:00:00.000',
        end_date: '2023-09-30 16:00:00.000',
        progress: 0.1
      },
      {
        id: 508,
        projectName: '项目4',
        startTime: '2023-09-04',
        endTime: '2023-10-20',
        showEndTime: '2023-10-21',
        projectStatus: '滞后',
        projectProgress: 0.57,
        projectRatio: 0.04,
        projectTotalTime: 3582.5,
        projectUsedTime: 2033.2,
        functionName: '测试功能',
        xmdj: '1',
        cityName: '成都',
        name: '4',
        projectMap: {},
        parent: 0,
        start_date: '2023-09-03 16:00:00.000',
        end_date: '2023-10-20 16:00:00.000',
        progress: 0.15
      },
      {
        id: 511,
        projectName: '项目5',
        startTime: '2023-09-01',
        endTime: '2023-10-31',
        showEndTime: '2023-11-01',
        projectStatus: '滞后',
        projectProgress: 0.07,
        projectRatio: 0.03,
        projectTotalTime: 2150.5,
        projectUsedTime: 140,
        functionName: '悬浮球',
        xmdj: '1',
        cityName: '成都',
        name: '5',
        projectMap: {},
        parent: 0,
        start_date: '2023-07-31 16:00:00.000',
        end_date: '2023-10-31 16:00:00.000',
        progress: 0.28
      },
      {
        id: 507,
        projectName: '项目6',
        startTime: '2023-08-28',
        endTime: '2023-10-01',
        showEndTime: '2023-10-02',
        projectStatus: '滞后',
        projectProgress: 0.48,
        projectRatio: 0.21,
        projectTotalTime: 4957,
        projectUsedTime: 2367,
        functionName: '产品原型图',
        xmdj: '1',
        cityName: '三亚',
        name: '6',
        projectMap: {
          部门1: 1
        },
        parent: 0,
        start_date: '2023-07-27 16:00:00.000',
        end_date: '2023-10-01 16:00:00.000',
        progress: 0.33
      },
      {
        id: 1,
        projectName: '项目7',
        startTime: '2023-08-28',
        endTime: '2023-10-25',
        showEndTime: '2023-10-26',
        projectStatus: '超前',
        projectProgress: 0.27,
        projectRatio: 0.15,
        projectTotalTime: 2027.5,
        projectUsedTime: 557,
        functionName: '测量工具',
        xmdj: '1',
        cityName: '佛山',
        name: '7',
        projectMap: {},
        parent: 0,
        start_date: '2023-06-27 16:00:00.000',
        end_date: '2023-10-25 16:00:00.000',
        progress: 0.67
      },
      {
        id: 2,
        projectName: '项目7',
        startTime: '2023-08-28',
        endTime: '2023-10-25',
        showEndTime: '2023-10-26',
        projectStatus: '滞后',
        projectProgress: 0.27,
        projectRatio: 0.15,
        projectTotalTime: 2027.5,
        projectUsedTime: 557,
        functionName: '测量工具',
        xmdj: '1',
        cityName: '佛山',
        name: '7',
        projectMap: {},
        parent: 0,
        start_date: '2023-06-27 16:00:00.000',
        end_date: '2023-10-25 16:00:00.000',
        progress: 0.67
      },
      {
        id: 3,
        projectName: '项目7',
        startTime: '2023-08-28',
        endTime: '2023-10-25',
        showEndTime: '2023-10-26',
        projectStatus: '滞后',
        projectProgress: 0.27,
        projectRatio: 0.15,
        projectTotalTime: 2027.5,
        projectUsedTime: 557,
        functionName: '测量工具',
        xmdj: '1',
        cityName: '佛山',
        name: '7',
        projectMap: {},
        parent: 0,
        start_date: '2023-06-27 16:00:00.000',
        end_date: '2023-10-25 16:00:00.000',
        progress: 0.67
      },
      {
        id: 4,
        projectName: '项目7',
        startTime: '2023-08-28',
        endTime: '2023-10-25',
        showEndTime: '2023-10-26',
        projectStatus: '滞后',
        projectProgress: 0.27,
        projectRatio: 0.15,
        projectTotalTime: 2027.5,
        projectUsedTime: 557,
        functionName: '测量工具',
        xmdj: '1',
        cityName: '佛山',
        name: '7',
        projectMap: {},
        parent: 0,
        start_date: '2023-06-27 16:00:00.000',
        end_date: '2023-10-25 16:00:00.000',
        progress: 0.67
      },
      {
        id: 5,
        projectName: '项目7',
        startTime: '2023-08-28',
        endTime: '2023-10-25',
        showEndTime: '2023-10-26',
        projectStatus: '滞后',
        projectProgress: 0.27,
        projectRatio: 0.15,
        projectTotalTime: 2027.5,
        projectUsedTime: 557,
        functionName: '测量工具',
        xmdj: '1',
        cityName: '佛山',
        name: '7',
        projectMap: {},
        parent: 0,
        start_date: '2023-06-27 16:00:00.000',
        end_date: '2023-10-25 16:00:00.000',
        progress: 0.67
      },
      {
        id: 6,
        projectName: '项目7',
        startTime: '2023-08-28',
        endTime: '2023-10-25',
        showEndTime: '2023-10-26',
        projectStatus: '滞后',
        projectProgress: 0.27,
        projectRatio: 0.15,
        projectTotalTime: 2027.5,
        projectUsedTime: 557,
        functionName: '测量工具',
        xmdj: '1',
        cityName: '佛山',
        name: '7',
        projectMap: {},
        parent: 0,
        start_date: '2023-06-27 16:00:00.000',
        end_date: '2023-10-25 16:00:00.000',
        progress: 0.67
      },
      {
        id: 7,
        projectName: '项目7',
        startTime: '2023-08-28',
        endTime: '2023-10-25',
        showEndTime: '2023-10-26',
        projectStatus: '滞后',
        projectProgress: 0.27,
        projectRatio: 0.15,
        projectTotalTime: 2027.5,
        projectUsedTime: 557,
        functionName: '测量工具',
        xmdj: '1',
        cityName: '佛山',
        name: '7',
        projectMap: {},
        parent: 0,
        start_date: '2023-06-27 16:00:00.000',
        end_date: '2023-10-25 16:00:00.000',
        progress: 0.67
      },
      {
        id: 8,
        projectName: '项目7',
        startTime: '2023-08-28',
        endTime: '2023-10-25',
        showEndTime: '2023-10-26',
        projectStatus: '滞后',
        projectProgress: 0.27,
        projectRatio: 0.15,
        projectTotalTime: 2027.5,
        projectUsedTime: 557,
        functionName: '测量工具',
        xmdj: '1',
        cityName: '佛山',
        name: '7',
        projectMap: {},
        parent: 0,
        start_date: '2023-06-27 16:00:00.000',
        end_date: '2023-10-25 16:00:00.000',
        progress: 0.67
      },
      {
        id: 9,
        projectName: '项目7',
        startTime: '2023-08-28',
        endTime: '2023-10-25',
        showEndTime: '2023-10-26',
        projectStatus: '滞后',
        projectProgress: 0.27,
        projectRatio: 0.15,
        projectTotalTime: 2027.5,
        projectUsedTime: 557,
        functionName: '测量工具',
        xmdj: '1',
        cityName: '佛山',
        name: '7',
        projectMap: {},
        parent: 0,
        start_date: '2023-06-27 16:00:00.000',
        end_date: '2023-10-25 16:00:00.000',
        progress: 0.67
      }
    ]
  }
})

const zoomConfig = {
  levels: [
    {
      name: 'day',
      scale_height: 60,
      scales: [{ unit: 'day', step: 1, format: '%d %M' }]
    },
    {
      name: 'week',
      scale_height: 60,
      scales: [
        {
          unit: 'week',
          step: 1,
          format: function (date) {
            let dateToStr = gantt.date.date_to_str('%m-%d')
            let endDate = gantt.date.add(date, -6, 'day')
            let weekNum = gantt.date.date_to_str('%W')(date) //第几周
            return dateToStr(endDate) + '' + dateToStr(date)
          }
        },
        {
          unit: 'day',
          step: 1,
          format: '%d', // + "周%D"
          css: function (date) {
            if (date.getDay() == 0 || date.getDay() == 6) {
              return 'day-item weekend weekend-border-bottom'
            } else {
              return 'day-item'
            }
          }
        }
      ]
    },
    {
      name: 'month',
      scale_height: 60,
      min_column_width: 18,
      scales: [
        { unit: 'month', format: '%Y-%m' },
        {
          unit: 'day',
          step: 1,
          format: '%d',
          css: function (date) {
            if (date.getDay() == 0 || date.getDay() == 6) {
              return 'day-item weekend weekend-border-bottom'
            } else {
              return 'day-item'
            }
          }
        }
      ]
    },
    {
      name: 'quarter',
      height: 60,
      min_column_width: 110,
      scales: [
        {
          unit: 'quarter',
          step: 1,
          format: function (date) {
            let yearStr = new Date(date).getFullYear() + ''
            let dateToStr = gantt.date.date_to_str('%M')
            let endDate = gantt.date.add(gantt.date.add(date, 3, 'month'), -1, 'day')
            return yearStr + dateToStr(date) + ' - ' + dateToStr(endDate)
          }
        },
        {
          unit: 'week',
          step: 1,
          format: function (date) {
            let dateToStr = gantt.date.date_to_str('%m-%d')
            let endDate = gantt.date.add(date, 6, 'day')
            let weekNum = gantt.date.date_to_str('%W')(date)
            return dateToStr(date) + '' + dateToStr(endDate)
          }
        }
      ]
    },
    {
      name: 'year',
      scale_height: 50,
      min_column_width: 150,
      scales: [
        { unit: 'year', step: 1, format: '%Y年' },
        { unit: 'month', format: '%Y-%m' }
      ]
    }
  ]
}

//初始化甘特图
const initGantt = () => {
  let dateToStr = gantt.date.date_to_str('%Y.%m.%d')
  gantt.config.grid_width = 350
  gantt.config.add_column = false //添加符号

  //时间轴图表中,如果不设置,只有行边框,区分上下的任务,设置之后带有列的边框,整个时间轴变成格子状。
  gantt.config.autofit = false
  gantt.config.row_height = 60
  gantt.config.bar_height = 34
  // gantt.config.fit_tasks = true //自动延长时间刻度,以适应所有显示的任务
  gantt.config.auto_types = true //将包含子任务的任务转换为项目,将没有子任务的项目转换回任务
  gantt.config.xml_date = '%Y-%m-%d' //甘特图时间数据格式
  gantt.config.readonly = true //是否只读

  gantt.templates.task_text = function (start, end, task) {
    if (task.projectStatus == '暂无任务') {
      return `
                <div class="project-bar no-data">
                    <div class="no">暂无任务</div>
                </div>
               `
    } else {
      let progressState = ''
      let progressPerson = ''
      // 超前  滞后 完成 暂无任务 延期
      if (task.projectStatus == '滞后' || task.projectStatus == '延期') {
        progressState = 'lag'
        let warnDiv = '' //告警事件 部门
        let errorDic = task.functionName //滞后严重功能
        let _index = 1
        let warnIcon = ''
        let errorIcon = ''
        for (const key in task.projectMap) {
          if (_index <= 3) {
            warnDiv += `${_index}.${key}(${task.projectMap[key]})  `
            _index += 1
          }
        }
        if (Object.keys(task.projectMap).length > 0) {
          warnIcon = `<div class="icon-box">
                                        <div class="warn-icon"></div>
                                        <div class="icon-line warn"></div>
                                    </div>`
        }
        if (errorDic) {
          errorIcon = `<div class="icon-box">
                                        <div class="error-icon"></div>
                                        <div class="icon-line error"></div>
                                    </div>`
        }
        // 判断是否因为时间过短 导致图上文字显示不全 采用钓鱼线引出
        let inLine = data.topTime == 'year' ? 'no-in-line' : 'in-line'
        if (
          new Date(task.endTime).getTime() - new Date(task.startTime).getTime() <
          3600 * 1000 * 24 * 30
        ) {
          inLine = 'no-in-line'
        }
        progressPerson = `  <div class="project-warn ${inLine}">
                                    ${warnIcon}
                                    <div class="war-des">${warnDiv}</div>
                                </div>
                                <div class="project-error ${inLine}">
                                    ${errorIcon}
                                    <div class="error-des">${errorDic}</div>
                                </div>
                             `
      } else {
        progressState = 'normal'
      }
      return `
                <div class="project-bar">
                    <div class="project-all-time">${task.projectUsedTime}/${
        task.projectTotalTime
      }</div>
                    <div class="project-progress ${progressState}">${
        (task.projectProgress * 100).toFixed(1) + '%'
      }</div>
                    ${progressPerson}
                </div>
               `
    }
  }

  gantt.config.columns = [
    {
      name: 'projectName',
      label: '项目名称',
      tree: true,
      width: '*'
    },
    {
      name: '',
      label: '时间',
      align: 'center',
      width: 150,
      template: function (item) {
        return `<div class="project-time">${
          dateToStr(item.start_date) + '-' + item.endTime.replace(/[-]/g, '.')
        }</div>`
      }
    }
  ]

  gantt.i18n.setLocale('cn') //设置语言
  gantt.init('gantt_here') //初始化
  gantt.parse(data.demoData) //填充数据
  scrollInit()
  gantt.ext.zoom.init(zoomConfig) //配置初始化扩展
  gantt.ext.zoom.setLevel('month') //切换到指定的缩放级别
}

//拖拽滚动
const scrollInit = () => {
  const nav = document.querySelectorAll('.gantt_task')[0]
  const parNav = document.querySelectorAll('.gantt_hor_scroll')[0]
  parNav.scrollLeft = 0
  let flag
  let downX
  let scrollLeft
  nav.addEventListener('mousedown', function (event) {
    flag = true
    downX = event.clientX // 获取到点击的x下标
    scrollLeft = this.scrollLeft // 获取当前元素滚动条的偏移量
  })
  nav.addEventListener('mousemove', function (event) {
    if (flag) {
      let moveX = event.clientX
      let scrollX = moveX - downX
      parNav.scrollLeft = scrollLeft - scrollX
    }
  })
  // 鼠标抬起停止拖动
  nav.addEventListener('mouseup', function () {
    flag = false
  })
  // 鼠标离开元素停止拖动
  nav.addEventListener('mouseleave', function (event) {
    flag = false
  })
}

const changeTime = () => {
  gantt.ext.zoom.setLevel(data.timeState)
}

onBeforeMount(() => {})
onMounted(() => {
  initGantt()
})
watchEffect(() => {})
defineExpose({
  ...toRefs(data)
})
</script>
<style scoped lang="scss">
.my-gantt {
  height: 800px;
  width: 100vw;
  .time-box {
    text-align: center;
    margin-bottom: 20px;
  }
  ::v-deep .gantt-container {
    border-radius: 8px 0px 0px 8px;
    overflow: hidden;
    height: 100%;
    .no-data {
      background: #d4d4d4;
      .no {
        padding: 0 10px;
        border-radius: 4px;
        box-shadow: 4px 0px 4px 0px rgba(0, 0, 0, 0.25);
      }
    }
    .gantt_grid_head_projectText {
      line-height: 2.8;
    }
    .gantt_task_line {
      background: #86afff;
    }
    .gantt_side_content,
    .gantt_task_content,
    .gantt_task_progress {
      overflow: visible !important;
    }
    .project-progress-marker {
      position: absolute;
      z-index: 10;
      display: flex;
      align-content: center;
      align-items: center;
      .icon {
        width: 20px;
        height: 20px;
        background-size: 100% 100%;
        background-repeat: no-repeat;
        margin-right: 2px;
      }
    }
    .marker-lag {
      color: #d81e06;
      .icon {
        background-image: url('./../../assets/img/manage/icon_lag.png');
      }
    }
    .marker-advance {
      color: #14cf20;
      .icon {
        background-image: url('./../../assets/img/manage/icon_advance.png');
      }
    }
    .project-bar {
      display: flex;

      border-radius: 4px;
      color: #333;
      font-size: 12px;
      font-weight: 400;
      text-align: center;
      .project-all-time {
        width: 20%;
        background: #ffd28f;
        border-radius: 4px;
        box-shadow: 4px 0px 4px 0px rgba(0, 0, 0, 0.25);
        position: relative;
        z-index: 12;
        overflow: hidden;
        min-width: min-content;
        padding: 0 2px;
      }
      .project-progress {
        width: 20%;
        border-radius: 0 4px 4px 0;
        box-shadow: 4px 0px 4px 0px rgba(0, 0, 0, 0.25);
        transform: translateX(-1px);
        position: relative;
        z-index: 11;
        overflow: hidden;
        min-width: min-content;
        padding: 0 2px;
      }
      .lag {
        background: #ff8f8f;
      }
      .normal {
        background: #b3ff8f;
      }
      .project-normal {
        width: -webkit-fill-available;
        text-align: center;
      }
      .icon-line {
        width: 21px;
        height: 10px;
        background-image: url('./../../assets/img/gante/icon_line.png');
        background-size: 100% 100%;
        background-repeat: no-repeat;
        position: absolute;
        left: 30px;
        bottom: -6px;
        z-index: 10;
      }
      .project-warn {
        width: 40%;
        background: #86afff;
        border-radius: 4px;
        box-shadow: 4px 0px 4px 0px rgba(0, 0, 0, 0.25);
        text-align: left;
        display: flex;
        // -webkit-box-orient: vertical;
        // -webkit-line-clamp: 1;
        // overflow: hidden;
        position: relative;
        z-index: 10;
        transform: translateX(-1px);
        padding: 0 2px;

        .warn-icon {
          min-width: 34px;
          height: 34px;
          background-image: url('./../../assets/img/gante/warn_icon.png');
          background-size: 100% 100%;
          background-repeat: no-repeat;
        }

        .war-des {
          line-height: 1;
          position: absolute;
          left: 50px;
          bottom: -20px;
          padding-top: 2px;
          border-top: 1px solid #000;
        }
      }
      .project-error {
        width: 20%;
        background: #86afff;
        border-radius: 4px;
        box-shadow: 4px 0px 4px 0px rgba(0, 0, 0, 0.25);
        text-align: left;
        display: flex;
        // -webkit-box-orient: vertical;
        // -webkit-line-clamp: 1;
        position: relative;
        padding: 0 2px;

        .error-icon {
          min-width: 34px;
          height: 34px;
          background-image: url('./../../assets/img/gante/error_icon.png');
          background-size: 100% 100%;
          background-repeat: no-repeat;
        }
        .error {
          bottom: 10px;
          left: 28px;
        }
        .error-des {
          line-height: 1;
          position: absolute;
          left: 48px;
          bottom: 10px;
          padding-bottom: 2px;
          border-bottom: 1px solid #000;
        }
      }
      .in-line {
        .icon-box {
          .icon-line {
            display: none;
          }
        }
        .war-des {
          line-height: 34px;
          position: absolute;
          left: 35px;
          bottom: auto;
          padding-top: 2px;
          border-top: none;
        }
        .error-des {
          line-height: 34px;
          position: absolute;
          left: 35px;
          bottom: auto;
          border-bottom: none;
        }
      }
    }

    .col-project-name {
      width: 100%;
      line-height: 1;
      text-align: center;
      height: 100%;
      display: flex;
      flex-wrap: wrap;
      align-items: center;
      align-content: center;
      &:hover {
        .project-name {
          color: #2c94ff;
        }
      }
      .project-index {
        width: 20%;
        height: 100%;
        font-size: 14px;
        font-weight: 400;
        text-align: center;
        color: rgba(0, 0, 0, 0.37);
        border-right: 1px solid #d9d9d9;
        display: inline-flex;
        flex-wrap: wrap;
        align-content: center;
        justify-content: center;
      }
      .project-name {
        width: 75%;
        height: 100%;
        padding: 2px 0;
        font-size: 12px;
        font-weight: 400;
        text-decoration: underline;
        text-align: center;
        color: #1e86ff;
        cursor: pointer;
        display: inline-flex;
        flex-wrap: wrap;
        align-content: center;
        div {
          width: 100%;
          margin: 2px 0;
        }
      }
    }
    .gantt_tree_icon {
      display: none;
    }
    .gantt_cell_tree {
      box-shadow: 1px 0px 4px 0px rgba(0, 0, 0, 0.15);
    }
    .gantt_grid_head_project {
      box-shadow: 1px 0px 4px 0px rgba(0, 0, 0, 0.15);
    }
    .gantt_cell {
      padding: 0;
    }
  }
}
</style>

 

  

  1.2 任务条下有多个子进程展示(关联时间),如图

  

此方法是通过父子节点的关联性设置并且与时间相关联,父节点 设置 render: 'split'拆分子任务过程,子任务过程关联上父节点id(用parent)。可以通过设置子进程的开始结束时间或持续时间(duration)来设置子进程的长度。
核心逻辑(数据层次):
{
        id: 13, //通过父子节点关系 父节点 设置 render: 'split'拆分子任务过程
        text: '任务 #2',
        start_date: '2023-03-04 00:00',
        type: 'project',
        render: 'split',
        parent: '11',
        progress: 0.5,
        open: false,
        duration: 11
      },
      {
        //子任务过程关联上父节点id
        id: 17,
        text: '过程 #1',
        start_date: '2023-03-04 00:00',
        end_date: '2023-03-15 00:00',
        // duration: 10,
        parent: '13',
        progress: 0,
        open: true
      },
      {
        id: 18,
        text: '过程 #2',
        start_date: '2023-05-04 00:00',
        duration: 20,
        parent: '13',
        progress: 0,
        open: true
      },
 完整代码:
<section class="my-gantt">
    <div class="time-box">
      <el-radio-group v-model="data.timeState" @change="changeTime">
        <el-radio-button
          v-for="(time, t_index) in data.timeList"
          :key="t_index"
          :label="time.code"
          size="default"
          border
          >{{ time.name }}</el-radio-button
        >
      </el-radio-group>
    </div>
    <div id="gantt_here" class="gantt-container"></div>
  </section>
</template>

<script setup>
import { reactive, toRefs, onBeforeMount, onMounted, watchEffect, defineExpose } from 'vue'
import { gantt } from 'dhtmlx-gantt'
import 'dhtmlx-gantt/codebase/dhtmlxgantt.css'
//此方法是通过父子节点的关联性设置并且与时间相关联,父节点 设置 render: 'split'拆分子任务过程,子任务过程关联上父节点id(用parent)。可以通过设置子进程的开始结束时间或持续时间(duration)来设置子进程的长度。
// 注意id 的唯一性 
const data = reactive({
  timeList: [
    {
      name: '',
      code: 'month'
    },
    {
      name: '',
      code: 'quarter'
    },
    {
      name: '',
      code: 'year'
    }
  ],
  timeState: 'year',
  demoData: {
    data: [
      {
        id: 11,
        text: '项目 #1',
        type: 'project',
        progress: 0,
        open: true,
        start_date: '2023-02-04 00:00',
        duration: 13,
        parent: 0
      },
      {
        id: 12,
        text: '任务 #1',
        start_date: '2023-03-04 00:00',
        duration: 5,
        parent: '11',
        progress: 0,
        open: true
      },
      {
        id: 13, //通过父子节点关系 父节点 设置 render: 'split'拆分子任务过程
        text: '任务 #2',
        start_date: '2023-03-04 00:00',
        type: 'project',
        render: 'split',
        parent: '11',
        progress: 0.5,
        open: false,
        duration: 11
      },
      {
        //子任务过程关联上父节点id
        id: 17,
        text: '过程 #1',
        start_date: '2023-03-04 00:00',
        end_date: '2023-03-15 00:00',
        // duration: 10,
        parent: '13',
        progress: 0,
        open: true
      },
      {
        id: 18,
        text: '过程 #2',
        start_date: '2023-05-04 00:00',
        duration: 20,
        parent: '13',
        progress: 0,
        open: true
      },
      {
        id: 19,
        text: '过程 #3',
        start_date: '2023-08-04 00:00',
        duration: 10,
        parent: '13',
        progress: 0,
        open: true
      },
      {
        id: 20,
        text: '过程 #4',
        start_date: '2023-10-04 00:00',
        duration: 45,
        parent: '13',
        progress: 0,
        open: true
      },
      {
        id: 14,
        text: '任务 #3',
        start_date: '2023-02-04 00:00',
        duration: 6,
        parent: '11',
        progress: 0,
        open: true
      },
      {
        id: 15,
        text: '任务 #4',
        type: 'project',
        render: 'split',
        parent: '11',
        progress: 0,
        open: true,
        start_date: '2023-03-04 00:00',
        duration: 11
      },
      {
        id: 21,
        text: '过程 #1',
        start_date: '2023-03-04 00:00',
        duration: 4,
        parent: '15',
        progress: 0,
        open: true
      },
      {
        id: 22,
        text: '过程 #2',
        start_date: '2023-08-04 00:00',
        duration: 3,
        parent: '15',
        progress: 0,
        open: true
      }
    ]
  }
})

const zoomConfig = {
  levels: [
    {
      name: 'day',
      scale_height: 60,
      scales: [{ unit: 'day', step: 1, format: '%d %M' }]
    },
    {
      name: 'week',
      scale_height: 60,
      scales: [
        {
          unit: 'week',
          step: 1,
          format: function (date) {
            let dateToStr = gantt.date.date_to_str('%m-%d')
            let endDate = gantt.date.add(date, -6, 'day')
            let weekNum = gantt.date.date_to_str('%W')(date) //第几周
            return dateToStr(endDate) + '' + dateToStr(date)
          }
        },
        {
          unit: 'day',
          step: 1,
          format: '%d', // + "周%D"
          css: function (date) {
            if (date.getDay() == 0 || date.getDay() == 6) {
              return 'day-item weekend weekend-border-bottom'
            } else {
              return 'day-item'
            }
          }
        }
      ]
    },
    {
      name: 'month',
      scale_height: 60,
      min_column_width: 18,
      scales: [
        { unit: 'month', format: '%Y-%m' },
        {
          unit: 'day',
          step: 1,
          format: '%d',
          css: function (date) {
            if (date.getDay() == 0 || date.getDay() == 6) {
              return 'day-item weekend weekend-border-bottom'
            } else {
              return 'day-item'
            }
          }
        }
      ]
    },
    {
      name: 'quarter',
      height: 60,
      min_column_width: 110,
      scales: [
        {
          unit: 'quarter',
          step: 1,
          format: function (date) {
            let yearStr = new Date(date).getFullYear() + ''
            let dateToStr = gantt.date.date_to_str('%M')
            let endDate = gantt.date.add(gantt.date.add(date, 3, 'month'), -1, 'day')
            return yearStr + dateToStr(date) + ' - ' + dateToStr(endDate)
          }
        },
        {
          unit: 'week',
          step: 1,
          format: function (date) {
            let dateToStr = gantt.date.date_to_str('%m-%d')
            let endDate = gantt.date.add(date, 6, 'day')
            let weekNum = gantt.date.date_to_str('%W')(date)
            return dateToStr(date) + '' + dateToStr(endDate)
          }
        }
      ]
    },
    {
      name: 'year',
      scale_height: 50,
      min_column_width: 150,
      scales: [
        { unit: 'year', step: 1, format: '%Y年' },
        { unit: 'month', format: '%Y-%m' }
      ]
    }
  ]
}

//初始化甘特图
const initGantt = () => {
  let dateToStr = gantt.date.date_to_str('%Y.%m.%d')
  gantt.config.grid_width = 350
  gantt.config.add_column = false //添加符号

  //时间轴图表中,如果不设置,只有行边框,区分上下的任务,设置之后带有列的边框,整个时间轴变成格子状。
  gantt.config.autofit = false
  gantt.config.row_height = 60
  gantt.config.bar_height = 34
  // gantt.config.fit_tasks = true //自动延长时间刻度,以适应所有显示的任务
  gantt.config.auto_types = true //将包含子任务的任务转换为项目,将没有子任务的项目转换回任务
  gantt.config.xml_date = '%Y-%m-%d' //甘特图时间数据格式
  gantt.config.readonly = true //是否只读

  gantt.config.columns = [
    {
      name: 'text',
      label: '项目名称',
      tree: true,
      width: '*'
    },
    {
      name: 'start_date',
      label: '开始时间',
      align: 'center',
      width: 150
    }
  ]

  gantt.i18n.setLocale('cn') //设置语言
  gantt.init('gantt_here') //初始化
  gantt.parse(data.demoData) //填充数据
  scrollInit()
  gantt.ext.zoom.init(zoomConfig) //配置初始化扩展
  gantt.ext.zoom.setLevel('year') //切换到指定的缩放级别
}

//拖拽滚动
const scrollInit = () => {
  const nav = document.querySelectorAll('.gantt_task')[0]
  const parNav = document.querySelectorAll('.gantt_hor_scroll')[0]
  parNav.scrollLeft = 0
  let flag
  let downX
  let scrollLeft
  nav.addEventListener('mousedown', function (event) {
    flag = true
    downX = event.clientX // 获取到点击的x下标
    scrollLeft = this.scrollLeft // 获取当前元素滚动条的偏移量
  })
  nav.addEventListener('mousemove', function (event) {
    if (flag) {
      let moveX = event.clientX
      let scrollX = moveX - downX
      parNav.scrollLeft = scrollLeft - scrollX
    }
  })
  // 鼠标抬起停止拖动
  nav.addEventListener('mouseup', function () {
    flag = false
  })
  // 鼠标离开元素停止拖动
  nav.addEventListener('mouseleave', function (event) {
    flag = false
  })
}

const changeTime = () => {
  gantt.ext.zoom.setLevel(data.timeState)
}
onBeforeMount(() => {})
onMounted(() => {
  initGantt()
})
watchEffect(() => {})
defineExpose({
  ...toRefs(data)
})
</script>
<style scoped lang="scss">
.my-gantt {
  height: 800px;
  width: 100vw;
  .time-box {
    text-align: center;
    margin-bottom: 20px;
  }
  ::v-deep .gantt-container {
    border-radius: 8px 0px 0px 8px;
    overflow: hidden;
    height: 100%;
  }
}
</style>

 

 

  总结:也可以将两者相结合,在例2的子进程里面也开业包含例1的内容。

  除此之外还有一些里程版或者重要节点的功能,也可以通过采用例1的方法去实现,比使用原生的自带方法更好。还有任务与任务直接的连接线,直接修改任务条数据等,看后续是否有相关需求。

  附件:例子中的甘特图样式或数据。vue 甘特图(附件):甘特图附件 - 根号九九 - 博客园 (cnblogs.com)

posted @ 2023-11-14 16:45  根号九九  阅读(1493)  评论(5编辑  收藏  举报