使用 Vue 3 + TypeScript 集成 DHTMLX 甘特图
前言
在现代前端开发中,Vue 3 结合 TypeScript 是一个强大的组合,能够帮助开发者更好地构建类型安全、可扩展的应用。而 DHTMLX Gantt 作为一款高效的项目管理组件,可以轻松实现任务调度、时间跟踪等功能。本文将详细介绍如何在 Vue 3 项目中结合 TypeScript 和 DHTMLX 甘特图,实现一个项目管理系统的关键功能。
官网:https://docs.dhtmlx.com/gantt/
一、使用步骤
1.1 安装dhtmlx-gantt依赖
代码如下(示例):
| npm install dhtmlx-gantt -save |
复制
1.2 在页面中引入插件
| import {gantt} from 'dhtmlx-gantt' |
| import 'dhtmlx-gantt/codebase/dhtmlxgantt.css' |
| import demoData from './ganttData.json' |
复制
二.页面代码
2.1 index.vue
代码如下(示例):
| <template> |
| <div style="height:100%;background-color: white"> |
| <section class="my-gantt"> |
| <div class="time-box" > |
| <el-form :model="search" inline style="margin-left: 15px"> |
| <el-form-item style="margin-left: 0px"> |
| <el-radio-group v-model="fullData.timeState" @change="changeTime"> |
| <el-radio-button |
| v-for="(time, t_index) in fullData.timeList" |
| :key="t_index" |
| :label="time.code" |
| size="small" |
| border |
| >{{ time.name }} |
| </el-radio-button |
| > |
| </el-radio-group> |
| </el-form-item> |
| <el-form-item> |
| <div style="height: 40px;position: relative;float: left;"> |
| <el-tooltip content="已完成" placement="top"> |
| <div |
| style="background-color:#BEE4BE;width: 14px;height: 14px;position: relative;float: left;top: 13px;border: 1px solid;"> |
| </div> |
| </el-tooltip> |
| <el-tooltip content="进行中" placement="top"> |
| <div |
| style="background-color:#ffd28f;width: 14px;height: 14px;position: relative;float: left;left: 15%;top: 13px;border: 1px solid;"> |
| </div> |
| </el-tooltip> |
| <el-tooltip content="未开始" placement="top"> |
| <div |
| style="background-color: #d4d4d4;width: 14px;height: 14px;position: relative;float: left;left: 30%;top: 13px;border: 1px solid;"> |
| </div> |
| </el-tooltip> |
| </div> |
| </el-form-item> |
| </el-form> |
| </div> |
| <div id="gantt_here" class="gantt-container"></div> |
| </section> |
| </div> |
| </template> |
复制
2.2 typeScript
| <script setup lang="ts"> |
| import {ref, reactive, toRefs, onBeforeMount, onMounted, watchEffect, defineExpose,nextTick} from 'vue' |
| import { gantt } from 'dhtmlx-gantt' |
| import 'dhtmlx-gantt/codebase/dhtmlxgantt.css' |
| import {Search, Plus,Edit,Delete,ArrowDownBold } from "@element-plus/icons-vue" |
| import demoData from "../progressManage/demoData.json" |
| |
| |
| |
| const startWidth = ref(0); |
| const fullData = reactive({ |
| timeList: [ |
| { |
| name: '日', |
| code: 'day' |
| }, |
| { |
| name: '周', |
| code: 'week' |
| }, |
| { |
| name: '月', |
| code: 'month' |
| } |
| ], |
| timeState: 'month', |
| demoData:{data:[]} |
| |
| }) |
| const zoomConfig = { |
| levels: [ |
| { |
| name: 'day', |
| 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: 'week', |
| 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: 'month', |
| 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.row_height = 34 |
| gantt.config.bar_height = 28 |
| |
| gantt.config.auto_types = true |
| gantt.config.xml_date = '%Y.%m.%d' |
| gantt.config.readonly = true |
| gantt.config.show_errors = false |
| gantt.config.show_empty_state = true |
| |
| gantt.config.open_split_tasks = true |
| |
| |
| gantt.locale.labels.deadline_enable_button = 'Set'; |
| gantt.locale.labels.deadline_disable_button = 'Remove'; |
| |
| gantt.attachEvent("onTaskLoading", function (task) { |
| if (task.end_date) |
| task.end_date = gantt.date.parseDate(task.end_date, "xml_date"); |
| return true; |
| }); |
| gantt.ext.draw = true |
| |
| gantt.templates.task_text = function (start, end, task) { |
| let progressState = '' |
| let progressPerson = '' |
| if (task.state == '1') { |
| progressState = 'lag' |
| }else if(task.state == '3'){ |
| progressState = 'ing' |
| }else if(task.state == '2'){ |
| progressState = 'wanch' |
| }else{ |
| progressState = 'normal' |
| } |
| return ` <div class="project-progress-${progressState}">${task.text} |
| </div>` |
| |
| } |
| |
| gantt.config.columns = [ |
| { |
| name: "end_date", label: "", width: 30, template: function (obj) { |
| if(obj.skip==0){ |
| if(obj.Long||obj.gap){ |
| return `<el-tooltip content="${obj.text}"placement="top" effect="dark"><div class="overdue-indicator" ></div></el-tooltip>`; |
| } |
| }else if(obj.skip==1){ |
| return `<el-tooltip content="跳过该节点"placement="top" effect="dark"><div class="overdue-indicator1" ></div></el-tooltip>`; |
| } |
| } |
| }, |
| { |
| name: 'text', |
| label: '项目名称', |
| resize:true, |
| tree: true, |
| align:'left', |
| width: 250, |
| template: function (obj) { |
| return ` <el-tooltip content="${obj.text}"placement="bottom"> <span class="box-item">${obj.text}</span></el-tooltip>` |
| } |
| }, |
| { |
| name: 'apply_unit', |
| label: '项目地', |
| resize:true, |
| align:'left', |
| width: 150, |
| template: function (obj) { |
| return `<el-tooltip content="${obj.apply_unit}"placement="bottom"> <span class="box-item">${obj.apply_unit}</span></el-tooltip>` |
| } |
| } |
| ] |
| |
| |
| |
| |
| |
| |
| |
| |
| gantt.templates.grid_folder = (item) => { |
| return "" |
| } |
| |
| gantt.templates.grid_file = (item) => { |
| return "" |
| } |
| gantt.i18n.setLocale('ZN') |
| gantt.ext.zoom.init(zoomConfig) |
| gantt.ext.zoom.setLevel('month') |
| |
| gantt.plugins({ marker: true, tooltip: true }); |
| |
| |
| gantt.templates.tooltip_text=function (start,end,task) { |
| var tooltip = "<span>项目名称:</span>"+ task.text+"<br><span>开始时间:</span>"+dateToStr(task.start_date)+ |
| "<br><span>结束时间:</span>"+dateToStr(task.end_date) |
| if(task.Long){ |
| tooltip+=`<br><font color="red">预警信息:${task.Long}</font>` |
| } |
| if(task.gap){ |
| tooltip+=`<br><font color="red">预警信息:${task.gap}</font>` |
| } |
| if(task.skip){ |
| tooltip+=`<br><font color="blue">预警信息:跳过该节点</font>` |
| } |
| return tooltip |
| } |
| const todayMarker = gantt.addMarker({ |
| start_date: new Date(), |
| css: "today", |
| text: "今日", |
| title: new Date() |
| }); |
| gantt.config.autofit = true |
| gantt.init('gantt_here') |
| gantt.parse(demoData) |
| scrollInit() |
| gantt.showDate(new Date()) |
| |
| |
| scrollInit() |
| gantt.showDate(new Date()) |
| gantt.attachEvent("onGridHeaderClick",function (name,e) { |
| console.log("name",e,name) |
| }) |
| } |
| |
| 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 |
| 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(fullData.timeState) |
| gantt.showDate(new Date()) |
| } |
| |
| gantt.attachEvent('onTaskClick',function (id, e,parent) { |
| |
| |
| |
| if(e.target.className=='project-progress-wanch'||e.target.className=='project-progress-ing'||e.target.className=='project-progress-lag'){ |
| console.log("Task clicked:", id,e,parent) |
| queryProgress.findNode({id:id}).then(res=>{ |
| nodeInfo.value = res[0] |
| nodeTitle.value = "节点信息【"+nodeInfo.value.node_name +"】" |
| reserveProject.getFileByID({id:id}).then(res=>{ |
| myfileList.value =res |
| }) |
| console.log("myfileList.value",myfileList.value) |
| nodeDetail.value = true |
| }) |
| }else if(e.target.className=='project-progress-normal'){ |
| console.log("aaa",id,e) |
| projectDetail.value = true |
| queryProgress.findProject({id:id}).then(res=>{ |
| clickInfo.value = res[0] |
| projectTitle.value = "项目信息【"+clickInfo.value.project_name +"】" |
| }) |
| } |
| else if(e.target.className=='gantt_open'||e.target.className=='gantt_close'){ |
| console.log('e', e.target.className) |
| |
| gantt.eachTask(function(task){ |
| gantt.close(task.id); |
| }); |
| |
| switch (e.target.className) { |
| case 'gantt_open': |
| gantt.open(id) |
| break; |
| case 'gantt_close': |
| gantt.close(id) |
| break; |
| default: |
| break; |
| } |
| } |
| else{ |
| var task = gantt.getTask(id) |
| gantt.showTask(task.id) |
| return true |
| } |
| }) |
| |
| gantt.attachEvent('onTaskRowClick',function (id,row) { |
| console.log("点击",id,row) |
| }) |
| |
| const toPdf =(id) => { |
| loading1.value = true |
| previewMmgzWj({id:id}).then(res=>{ |
| console.log("res",res) |
| dialogtitle.value = res[0].file_name |
| pdfUrl.value = res[0].url_yl |
| downloadUrl.value = res[0].url |
| pdfShow.value = true; |
| loading1.value = false |
| }) |
| pdfHeight.value = window.innerHeight - 200 + 'px' |
| }; |
| function getColumn() { |
| var col = 3 |
| return col |
| } |
| function save() { |
| window.open(downloadUrl.value) |
| } |
| |
| function print() { |
| window.open(pdfUrl.value) |
| } |
| onMounted(() => { |
| initGantt() |
| }) |
| |
| watchEffect(() => {}) |
| defineExpose({ |
| ...toRefs(fullData) |
| }) |
| </script> |
复制
2.3 css样式
代码如下(示例):
| <style lang="scss" scoped> |
| .my-gantt { |
| height: 800px; |
| width: 100vw; |
| ::v-deep .gantt-container { |
| border-radius: 8px 0px 0px 8px; |
| overflow: hidden; |
| height: 100%; |
| |
| } |
| } |
| .el-form--inline .el-form-item { |
| margin-right: 10px !important; |
| margin-top: 10px !important; |
| } |
| .el-form-item__label { |
| font-weight: 500 !important; |
| } |
| .today1{ |
| background: red; |
| color: red |
| } |
| |
| ::v-deep(.gantt_open) { |
| width: 12px !important; |
| height: 100%; |
| background-image: url('../../assets/left.png') !important; |
| background-repeat: no-repeat; |
| background-position: center center; |
| background-size: 100% auto; |
| margin-right: 5px; |
| transform: rotate(-90deg); |
| } |
| |
| ::v-deep(.gantt_close) { |
| width: 12px !important; |
| height: 100%; |
| background-image: url('../../assets/left.png') !important ; |
| background-repeat: no-repeat; |
| background-position: center center; |
| background-size: 100% auto; |
| margin-right: 10px; |
| } |
| |
| |
| ::v-deep(.box-item ){ |
| overflow: hidden; |
| text-overflow: ellipsis; |
| white-space: nowrap; |
| max-width: 100%; |
| display: inline-block; |
| } |
| |
| |
| ::v-deep(.project-progress-ing) { |
| background: #ffd28f !important; |
| color: black; |
| |
| } |
| ::v-deep(.project-progress-warn) { |
| background: #ff8f8f !important; |
| } |
| ::v-deep(.project-progress-wanch ){ |
| background: #BEE4BE !important; |
| |
| |
| color: black; |
| |
| } |
| ::v-deep(.project-progress-lag){ |
| background: #d4d4d4 !important; |
| color: black; |
| } |
| ::v-deep(.project-progress-normal){ |
| background: #7ea7f5 !important; |
| color: black; |
| } |
| |
| |
| .deadline { |
| position: absolute; |
| border-radius: 12px; |
| border: 2px solid #585858; |
| -moz-box-sizing: border-box; |
| box-sizing: border-box; |
| width: 22px; |
| height: 22px; |
| margin-left: -11px; |
| margin-top: 6px; |
| z-index: 1; |
| background: url("../../assets/warning.png") center no-repeat; |
| } |
| ::v-deep(.overdue-indicator) { |
| width: 20px !important; |
| height: 100%; |
| background: url("../../assets/warning.png"); |
| background-repeat: no-repeat; |
| background-position: center center; |
| background-size: 100% auto; |
| } |
| ::v-deep(.overdue-indicator1) { |
| width: 20px !important; |
| height: 100%; |
| background: url("../../assets/skip1.png"); |
| background-repeat: no-repeat; |
| background-position: center center; |
| background-size: 100% auto; |
| } |
| |
| |
| |
| |
| |
| ::v-deep(.gantt_task_line) { |
| background-color: rgba(0,0,0,0) !important; |
| border: 1px solid rgba(0,0,0,0) !important; |
| } |
| ::v-deep(.gantt_task_line.gantt_project) { |
| background-color: #fff !important; |
| border: 1px solid #fff !important; |
| } |
| |
| ::v-deep(.gantt_task_content) { |
| color: #fff; |
| top: 0px; |
| height: 16px; |
| border-radius: 50px; |
| } |
| |
| |
| ::v-deep( .gantt_data_area div, .gantt_grid div) { |
| -ms-touch-action: none; |
| -webkit-tap-highlight-color: rgba(0,0,0,0); |
| line-height: 18px; |
| top: 5px; |
| } |
| </style> |
复制
2.4 demoData.json数据
代码如下(示例):
| { |
| "data": [ |
| { |
| "apply_unit": "成都", |
| "end_date": "2024-12-31", |
| "parent": "0", |
| "id": "001", |
| "text": "项目1", |
| "type": "project", |
| "render": "split", |
| "open": false, |
| "start_date": "2024-01-01" |
| }, |
| { |
| "apply_unit": "深圳", |
| "end_date": "2024-12-31", |
| "parent": "0", |
| "id": "002", |
| "text": "项目1", |
| "type": "project", |
| "render": "split", |
| "open": false, |
| "start_date": "2024-01-01" |
| }, |
| { |
| "apply_unit": "广东", |
| "end_date": "2025-10-01", |
| "parent": "0", |
| "id": "003", |
| "text": "项目3", |
| "type": "project", |
| "render": "split", |
| "open": false, |
| "start_date": "2024-01-01" |
| }, |
| { |
| "apply_unit": "北京", |
| "end_date": "2024-12-30", |
| "parent": "0", |
| "id": "004", |
| "text": "项目4", |
| "type": "project", |
| "render": "split", |
| "open": false, |
| "start_date": "2024-01-01" |
| }, |
| { |
| "apply_unit": "天津", |
| "end_date": "2024-06-30", |
| "parent": "0", |
| "id": "005", |
| "text": "项目5", |
| "type": "project", |
| "render": "split", |
| "open": false, |
| "start_date": "2024-01-01" |
| }, |
| { |
| "apply_unit": "山东", |
| "end_date": "2024-06-30", |
| "parent": "0", |
| "id": "006", |
| "text": "项目6", |
| "type": "project", |
| "render": "split", |
| "open": false, |
| "start_date": "2024-01-01" |
| }, |
| { |
| "apply_unit": "广西", |
| "end_date": "2024-06-30", |
| "parent": "0", |
| "id": "007", |
| "text": "项目7", |
| "type": "project", |
| "render": "split", |
| "open": false, |
| "start_date": "2024-01-01" |
| }, |
| { |
| "id": "0011", |
| "text": "项目1节点1", |
| "parent": "001", |
| "start_date": "2024-01-01", |
| "end_date": "2024-02-21", |
| "plan_start_date": "20240101", |
| "plan_end_date": "20241231", |
| "state": 2, |
| "skip": 0, |
| "apply_unit": "成都", |
| "open": false |
| }, |
| { |
| "id": "0012", |
| "text": "项目1节点2", |
| "parent": "001", |
| "start_date": "2024-02-23", |
| "end_date": "2024-03-31", |
| "plan_start_date": "20240331", |
| "plan_end_date": "20240531", |
| "state": 2, |
| "skip": 0, |
| "apply_unit": "成都", |
| "open": false, |
| "gap": "该节点与前一节点时间间距过长" |
| }, |
| { |
| "id": "0013", |
| "text": "项目1节点3", |
| "parent": "001", |
| "start_date": "2024-04-01", |
| "end_date": "2024-06-30", |
| "plan_start_date": "20240314", |
| "plan_end_date": "20241231", |
| "state": 2, |
| "skip": 0, |
| "apply_unit": "成都", |
| "open": false, |
| "Long": "该节点耗时过长", |
| "gap": "该节点与前一节点时间间距过长" |
| }, |
| { |
| "id": "0014", |
| "text": "项目1节点4", |
| "parent": "001", |
| "start_date": "2024-07-01", |
| "end_date": "2024-10-24", |
| "plan_start_date": "20240101", |
| "plan_end_date": "20241231", |
| "state": 3, |
| "apply_unit": "成都", |
| "open": false, |
| "Long": "该节点耗时过长" |
| }, |
| { |
| "id": "0015", |
| "text": "项目1节点5", |
| "parent": "001", |
| "start_date": "2024-10-01", |
| "end_date": "2024-11-21", |
| "plan_start_date": "20240101", |
| "plan_end_date": "20241231", |
| "state": 1, |
| "apply_unit": "成都", |
| "open": false, |
| "Long": "该节点耗时过长" |
| }, |
| { |
| "id": "0016", |
| "text": "项目1节点6", |
| "parent": "001", |
| "start_date": "2024-11-01", |
| "end_date": "2024-12-31", |
| "plan_start_date": "20240101", |
| "plan_end_date": "20241231", |
| "state": 1, |
| "apply_unit": "成都", |
| "open": false, |
| "Long": "该节点耗时过长" |
| }, |
| { |
| "id": "0021", |
| "text": "项目2节点1", |
| "parent": "002", |
| "start_date": "2024-01-01", |
| "end_date": "2024-12-31", |
| "plan_start_date": "20240101", |
| "plan_end_date": "20241231", |
| "state": 1, |
| "apply_unit": "广东", |
| "open": false, |
| "Long": "该节点耗时过长" |
| }, |
| { |
| "id": "0022", |
| "text": "项目2节点2", |
| "parent": "002", |
| "start_date": "2024-01-01", |
| "end_date": "2024-12-31", |
| "plan_start_date": "20240101", |
| "plan_end_date": "20241231", |
| "state": 1, |
| "apply_unit": "广东", |
| "open": false, |
| "Long": "该节点耗时过长" |
| }, |
| { |
| "id": "0023", |
| "text": "项目2节点3", |
| "parent": "002", |
| "start_date": "2024-01-01", |
| "end_date": "2024-12-31", |
| "plan_start_date": "20240101", |
| "plan_end_date": "20241231", |
| "state": 1, |
| "apply_unit": "广东", |
| "open": false, |
| "Long": "该节点耗时过长" |
| }, |
| { |
| "id": "0024", |
| "text": "项目2节点4", |
| "parent": "002", |
| "start_date": "2024-01-01", |
| "end_date": "2024-12-31", |
| "plan_start_date": "20240101", |
| "plan_end_date": "20241231", |
| "state": 1, |
| "skip": 0, |
| "apply_unit": "广东", |
| "open": false, |
| "Long": "该节点耗时过长" |
| }, |
| { |
| "id": "0025", |
| "text": "项目2节点5", |
| "parent": "002", |
| "start_date": "2024-01-01", |
| "end_date": "2024-12-31", |
| "plan_start_date": "20240101", |
| "plan_end_date": "20241231", |
| "state": 1, |
| "skip": 0, |
| "apply_unit": "广东", |
| "open": false, |
| "Long": "该节点耗时过长" |
| }, |
| { |
| "id": "0026", |
| "text": "项目2节点6", |
| "parent": "002", |
| "start_date": "2024-01-01", |
| "end_date": "2024-12-31", |
| "plan_start_date": "20240101", |
| "plan_end_date": "20241231", |
| "state": 1, |
| "skip": 0, |
| "apply_unit": "广东", |
| "open": false, |
| "Long": "该节点耗时过长" |
| }, |
| { |
| "id": "0031", |
| "text": "项目3节点1", |
| "parent": "003", |
| "start_date": "2024-01-01", |
| "end_date": "2024-12-31", |
| "plan_start_date": "20240101", |
| "plan_end_date": "20241231", |
| "state": 1, |
| "apply_unit": "深圳", |
| "open": false, |
| "Long": "该节点耗时过长" |
| }, |
| { |
| "id": "0032", |
| "text": "项目3节点2", |
| "parent": "003", |
| "start_date": "2024-01-01", |
| "end_date": "2024-12-31", |
| "plan_start_date": "20240101", |
| "plan_end_date": "20241231", |
| "state": 1, |
| "apply_unit": "深圳", |
| "open": false, |
| "Long": "该节点耗时过长" |
| }, |
| { |
| "id": "0033", |
| "text": "项目3节点3", |
| "parent": "002", |
| "start_date": "2024-01-01", |
| "end_date": "2024-12-31", |
| "plan_start_date": "20240101", |
| "plan_end_date": "20241231", |
| "state": 1, |
| "apply_unit": "深圳", |
| "open": false, |
| "Long": "该节点耗时过长" |
| }, |
| { |
| "id": "0034", |
| "text": "项目3节点4", |
| "parent": "003", |
| "start_date": "2024-01-01", |
| "end_date": "2024-12-31", |
| "plan_start_date": "20240101", |
| "plan_end_date": "20241231", |
| "state": 1, |
| "skip": 0, |
| "apply_unit": "深圳", |
| "open": false, |
| "Long": "该节点耗时过长" |
| }, |
| { |
| "id": "0035", |
| "text": "项目3节点5", |
| "parent": "003", |
| "start_date": "2024-01-01", |
| "end_date": "2024-12-31", |
| "plan_start_date": "20240101", |
| "plan_end_date": "20241231", |
| "state": 1, |
| "skip": 0, |
| "apply_unit": "深圳", |
| "open": false, |
| "Long": "该节点耗时过长" |
| }, |
| { |
| "id": "0036", |
| "text": "项目3节点6", |
| "parent": "003", |
| "start_date": "2024-01-01", |
| "end_date": "2024-12-31", |
| "plan_start_date": "20240101", |
| "plan_end_date": "20241231", |
| "state": 1, |
| "skip": 0, |
| "apply_unit": "深圳", |
| "open": false, |
| "Long": "该节点耗时过长" |
| } |
| ] |
| } |
| |
复制
三、效果图
