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)