甘特图开发代码(测试版)
场景:要实现的功能就是单行数据能左右拖动。
流程五个:ABCDE。(对应:Charter开发、概念和计划、初样开发、正样开发、验证)
1、A有开始时间,结束时间。B的开始时间必须是A的结束时间(相等或者大于A的结束时间)。CDE以此类推。
2、一条数据有父子级的话,父级数据的进度条取A的开始时间,E的结束时间,如果当前流程没到E,就是取现有的流程的结束时间。流程阶段影响父级
3、单个流程进度条移动会影响前后进度条,如果没有前后节点就不影响
一、代码实现
https://juejin.cn/post/7451884906761486372
https://juejin.cn/post/7273096710826967092
问题版:
const tooltipConfig = {grid: true,bars: true,cells: false,scales: false,links: false,
}// window.addEventListener('click', function (e) {
// for (key in tooltipConfig) {
// if (e.target.closest("." + key)) {
// tooltipConfig[key] = !tooltipConfig[key];
// gantt.message(`Tooltip config changed. ${key}: ${tooltipConfig[key]}`)
// }
// }
// });const zoomLevels = [// {// name: 'hour',// label: '小时',// scale_height: 50,// min_column_width: 30,// scales: [// { unit: 'day', format: '%Y-%m-%d' },// { unit: 'hour', format: '%H' },// ],// },// {// name: 'day',// label: '日',// scale_height: 70,// min_column_width: 30,// scales: [// { unit: 'month', format: '%Y年 %F' },// // { unit: "day", step: 1, format: "%j %D" },// {// unit: 'day',// step: 1,// format: date => {// const weekDays = ['日', '一', '二', '三', '四', '五', '六'];// const day = new Date(date).getDate();// const weekDay = new Date(date).getDay();// return `<div class='scale-formate-date'>// <span class='formate-date'>${day}</span>// <span class='formate-weekDay'>${weekDays[weekDay]}</span>// </div>`;// // return "<strong>Day " + dayNumber(date) + "</strong><br/>" + dateFormat(date);// },// },// ],// },// {// name: 'week',// label: '周',// scale_height: 50,// min_column_width: 50,// scales: [// { unit: 'month', format: '%Y年 %F' },// { unit: 'week', step: 1, date: '%W周' },// ],// },{name: 'month',label: '月',scale_height: 50,min_column_width: 50,scales: [// { unit: "year", step: 1, format: "%Y年" },{unit: 'quarter',step: 1,format: date => {const year = new Date(date).getFullYear();const month = new Date(date).getMonth();const quarter = Math.floor(month / 3 + 1);return `${year}年-Q${quarter}`;// return `Q${quarter}`;},},{ unit: 'month', step: 1, format: '%F' },],},{name: 'quarter',label: '季',scale_height: 50,min_column_width: 50,scales: [{ unit: 'year', step: 1, format: '%Y年' },{unit: 'quarter',step: 1,format: date => {// const year = new Date(date).getFullYear();const month = new Date(date).getMonth();const quarter = Math.floor(month / 3 + 1);// return `${year}年-Q${quarter}`;return `Q${quarter}`;},},],},{name: 'year',label: '年',scale_height: 50,min_column_width: 50,scales: [{ unit: 'year', step: 1, format: '%Y年' }],},
];/*** 禁用系列*/
// 禁止双击创建任务
gantt.config.dblclick_create = false;
// 禁止创建任务时弹出编辑弹窗
gantt.config.edit_on_create = false;
// 禁止双击任务时弹出详情弹窗
gantt.config.details_on_dblclick = false;
// 禁止双击创建链接
gantt.config.dblclick_create_link = false;
// 禁止创建链接时弹出编辑弹窗
gantt.config.edit_on_create_link = false;
// 禁止任务进度调整
gantt.config.drag_progress = false;
// 禁止拖拽连线
gantt.config.drag_link = false;
// 关闭报错弹窗
gantt.config.show_errors = false;
/*** end*/// 语言设置-中文
gantt.i18n.setLocale('cn');
// 行高
gantt.config.row_height = 48;
// 日期格式
gantt.config.date_format = '%Y-%m-%d %H:%i';
// 初始化缩放插件
gantt.ext.zoom.init({levels: zoomLevels, // 刻度级别配置
});// 设置默认刻度级别为月视图
gantt.ext.zoom.setLevel('month');/** 定义:第二网格列的配置 */
const secondGridColumns = {columns: [{name: 'actions',label: '操作',width: 120,align: 'center',template: task => {// 自定义列内容的模板函数,接收任务对象作为参数return `<div class="gantt-actions"><!-- 编辑按钮,绑定任务的 id --><div class="edit-btn" data-task-id="${task.id}">编辑</div><!-- 详情按钮,绑定任务的 id --><div class="detail-btn" data-task-id="${task.id}">详情</div></div>`;},},],
};/*** 设置列(第一列)* name - 数据字段;label-表头显示的名称;width-列宽;align-对齐方式;templage-自定义列内容的模板函数;*/
gantt.config.columns = [{name: 'selected', // 勾选框列label: '', // 表头为空width: 50,align: 'center',template: task => {return `<div class="gantt-checkbox"><inputtype="checkbox"data-task-id="${task.id}"${task.selected ? 'checked' : ''}/></div>`;},},{name: 'name',label: '姓名',width: 150,align: 'left',tree: true,},{ name: 'start_date', label: '开始日期', width: 130, align: 'center' },{ name: 'end_date', label: '持续时间', width: 80, align: 'center' },
];// 布局
gantt.config.layout = {css: 'gantt_container', // 容器的 CSS 类名rows: [{cols: [// 左侧网格(任务列表){ view: 'grid', width: 320, scrollY: 'scrollVer' }, // // 网格视图,宽度为 320,垂直滚动条绑定到 'scrollVer'{ resizer: true, width: 1 }, // 可调整宽度的分隔条,宽度为// 时间线视图{ view: 'timeline', scrollX: 'scrollHor', scrollY: 'scrollVer' }, // 时间线视图,水平和垂直滚动条分别绑定到 'scrollHor' 和 'scrollVer'{ resizer: true, width: 1 },// 右侧网格(自定义操作列){view: 'grid', // 网格视图width: 120,bind: 'task', // 绑定到任务数据scrollY: 'scrollVer', // 垂直滚动条绑定到 'scrollVer'config: secondGridColumns, // 使用上面定义的 secondGridColumns 配置},// 垂直滚动条{ view: 'scrollbar', id: 'scrollVer' }, // 垂直滚动条视图,id 为 'scrollVer'],},// 水平滚动条{ view: 'scrollbar', id: 'scrollHor', height: 20 }, // 水平滚动条视图,id 为 'scrollHor',高度为 20],
};gantt.plugins({tooltip: true
});const tasks = {"data": [// 第一条数据{pCode: '0',type: 1,code: 'm1234',name: 'doudou',open: true, // 默认展开id: 'm1234',color: '#c7cfd8',text: '',},{id: 'm1234567-1',pCode: 'm1234567',startDate: '2025-01-02',endDate: '2025-02-02',text: 'Charter开发',milestonskey: 'CHARTER_DEVELOPMENT',parent: 'm1234567',start_date: '2025-01-02',end_date: '2025-02-02',color: '#adbbff',},{id: 'm1234567-2',pCode: 'm1234567',startDate: '2025-02-02',endDate: '2025-03-02',text: '概念和计划',milestonskey: 'CONCEPT_AND_PLAN',parent: 'm1234567',start_date: '2025-02-02',end_date: '2025-03-02',color: '#ffe179',},{id: 'm1234567-3',pCode: 'm1234567',startDate: '2025-03-02',endDate: '2025-04-02',text: '初样开发',milestonskey: 'INITIAL_SAMPLE_DEVELOPMENT',parent: 'm1234567',start_date: '2025-03-02',end_date: '2025-04-02',color: '#adbbff',},{id: 'm1234567-4',pCode: 'm1234567',startDate: '2025-04-02',endDate: '2025-05-02',text: '正样开发',milestonskey: 'PROTOTYPE_DEVELOPMENT',parent: 'm1234567',start_date: '2025-04-02',end_date: '2025-05-02',color: '#92d9fb',},{id: 'm1234567-5',pCode: 'm1234567',startDate: '2025-05-02',endDate: '2025-09-02',text: '验证',milestonskey: 'VERIFY',parent: 'm1234567',start_date: '2025-05-02',end_date: '2025-09-02',color: '#64e0b7',},// 第一条数据的:第一个子级{bid: 4,pCode: 'm1234',type: 2,code: 'm1234567',name: 'ububuu',milestones: [{id: 'm1234567-1',pCode: 'm1234567',startDate: '2025-01-02',endDate: '2025-02-02',text: 'Charter开发',milestonskey: 'CHARTER_DEVELOPMENT',parent: 'm1234567',start_date: '2025-01-02',end_date: '2025-02-02',},{id: 'm1234567-2',pCode: 'm1234567',startDate: '2025-02-02',endDate: '2025-03-02',text: '概念和计划',milestonskey: 'CONCEPT_AND_PLAN',parent: 'm1234567',start_date: '2025-02-02',end_date: '2025-03-02',},{id: 'm1234567-3',pCode: 'm1234567',startDate: '2025-03-02',endDate: '2025-04-02',text: '初样开发',milestonskey: 'INITIAL_SAMPLE_DEVELOPMENT',parent: 'm1234567',start_date: '2025-03-02',end_date: '2025-04-02',},{id: 'm1234567-4',pCode: 'm1234567',startDate: '2025-04-02',endDate: '2025-05-02',text: '正样开发',milestonskey: 'PROTOTYPE_DEVELOPMENT',parent: 'm1234567',start_date: '2025-04-02',end_date: '2025-05-02',},{id: 'm1234567-5',pCode: 'm1234567',startDate: '2025-05-02',endDate: '2025-09-02',text: '验证',milestonskey: 'VERIFY',parent: 'm1234567',start_date: '2025-05-02',end_date: '2025-09-02',},],children: null,parent: 'm1234',render: 'split',isChildrenNode: true,id: 'm1234567',},{id: 'm12345678-1',pCode: 'm12345678',startDate: '2025-01-02',endDate: '2025-02-02',text: 'Charter开发',milestonskey: 'CHARTER_DEVELOPMENT',parent: 'm12345678',start_date: '2025-01-02',end_date: '2025-02-02',color: '#adbbff',},{id: 'm12345678-2',pCode: 'm12345678',startDate: '2025-02-02',endDate: '2025-03-02',text: '概念和计划',milestonskey: 'CONCEPT_AND_PLAN',parent: 'm12345678',start_date: '2025-02-02',end_date: '2025-03-02',color: '#ffe179',},{id: 'm12345678-3',pCode: 'm12345678',startDate: '2025-03-02',endDate: '2025-04-02',text: '初样开发',milestonskey: 'INITIAL_SAMPLE_DEVELOPMENT',parent: 'm12345678',start_date: '2025-03-02',end_date: '2025-04-02',color: '#adbbff',},{id: 'm12345678-4',pCode: 'm12345678',startDate: '2025-04-02',endDate: '2025-05-02',text: '正样开发',milestonskey: 'PROTOTYPE_DEVELOPMENT',parent: 'm12345678',start_date: '2025-04-02',end_date: '2025-05-02',color: '#92d9fb',},{id: 'm12345678-5',pCode: 'm12345678',startDate: '2025-05-02',endDate: '2025-07-02',text: '验证',milestonskey: 'VERIFY',parent: 'm12345678',start_date: '2025-05-02',end_date: '2025-07-02',color: '#64e0b7',},// 第一第的第二子级{bid: 5,pCode: 'm1234',type: 2,code: 'm12345678',name: 'm1234',// 任务节点milestones: [{id: 'm12345678-1',pCode: 'm12345678',startDate: '2025-01-02',endDate: '2025-02-02',text: 'Charter开发',milestonskey: 'CHARTER_DEVELOPMENT',parent: 'm12345678',start_date: '2025-01-02',end_date: '2025-02-02',},{id: 'm12345678-2',pCode: 'm12345678',startDate: '2025-02-02',endDate: '2025-03-02',text: '概念和计划',milestonskey: 'CONCEPT_AND_PLAN',parent: 'm12345678',start_date: '2025-02-02',end_date: '2025-03-02',},{id: 'm12345678-3',pCode: 'm12345678',startDate: '2025-03-02',endDate: '2025-04-02',text: '初样开发',milestonskey: 'INITIAL_SAMPLE_DEVELOPMENT',parent: 'm12345678',start_date: '2025-03-02',end_date: '2025-04-02',},{id: 'm12345678-4',pCode: 'm12345678',startDate: '2025-04-02',endDate: '2025-05-02',text: '正样开发',milestonskey: 'PROTOTYPE_DEVELOPMENT',parent: 'm12345678',start_date: '2025-04-02',end_date: '2025-05-02',},{id: 'm12345678-5',pCode: 'm12345678',startDate: '2025-05-02',endDate: '2025-07-02',text: '验证',milestonskey: 'VERIFY',parent: 'm12345678',start_date: '2025-05-02',end_date: '2025-07-02',},],children: null,parent: 'm1234',render: 'split',isChildrenNode: true,id: 'm12345678',},{id: 'm12345-1',pCode: 'm12345',startDate: '2025-01-02',endDate: '2025-02-02',text: 'Charter开发',milestonskey: 'CHARTER_DEVELOPMENT',parent: 'm12345',start_date: '2025-01-02',end_date: '2025-02-02',color: '#adbbff',},{id: 'm12345-2',pCode: 'm12345',startDate: '2025-02-02',endDate: '2025-03-02',text: '概念和计划',milestonskey: 'CONCEPT_AND_PLAN',parent: 'm12345',start_date: '2025-02-02',end_date: '2025-03-02',color: '#ffe179',},{id: 'm12345-3',pCode: 'm12345',startDate: '2025-03-02',endDate: '2025-04-02',text: '初样开发',milestonskey: 'INITIAL_SAMPLE_DEVELOPMENT',parent: 'm12345',start_date: '2025-03-02',end_date: '2025-04-02',color: '#adbbff',},{id: 'm12345-4',pCode: 'm12345',startDate: '2025-04-02',endDate: '2025-05-02',text: '正样开发',milestonskey: 'PROTOTYPE_DEVELOPMENT',parent: 'm12345',start_date: '2025-04-02',end_date: '2025-05-02',color: '#92d9fb',},{id: 'm12345-5',pCode: 'm12345',startDate: '2025-05-02',endDate: '2025-06-02',text: '验证',milestonskey: 'VERIFY',parent: 'm12345',start_date: '2025-05-02',end_date: '2025-06-02',color: '#64e0b7',},// 第二条{bid: 2,pCode: '0',type: 2,code: 'm12345',name: 'demo',projectRank: 'm1234',marketPositioning: 'm1234',costBenchmarking: 'm1234',performanceBenchmark: 'm1234',targetAudienceName: 'm1234',focusedIndustriesAndClientsName: 'm1234',newReachableCapacity: 'm1234',preparationOfKeyTechnologies: null,valueProposition: 'm1234',keyCompetitive: 'm1234',charterInitDate: '2025-01-02 00:00:00',charterTransferDate: '2025-02-02 00:00:00',pdcpDate: '2025-03-02 00:00:00',tr4aDate: '2025-04-02 00:00:00',edcpDate: '2025-05-02 00:00:00',adcpDate: '2025-06-02 00:00:00',insightFinishDate: '2025-07-02 00:00:00',relateOfferingName: null,rerlationshipName: null,serviceSolution: null,belongSolutionName: null,productClassL3Name: null,productSeriesL4Name: null,productOfferingL5Name: null,productBelongGroupsName: null,productBelongSpdtName: null,charterClass: null,charterTransferVersionTypeName: null,decisionLevelName: null,priorityName: null,businessPropertyName: null,bak: null,verison: null,state: 1,createUser: null,updatedUser: null,milestones: [{id: 'm12345-1',pCode: 'm12345',startDate: '2025-01-02',endDate: '2025-02-02',text: 'Charter开发',milestonskey: 'CHARTER_DEVELOPMENT',parent: 'm12345',start_date: '2025-01-02',end_date: '2025-02-02',},{id: 'm12345-2',pCode: 'm12345',startDate: '2025-02-02',endDate: '2025-03-02',text: '概念和计划',milestonskey: 'CONCEPT_AND_PLAN',parent: 'm12345',start_date: '2025-02-02',end_date: '2025-03-02',},{id: 'm12345-3',pCode: 'm12345',startDate: '2025-03-02',endDate: '2025-04-02',text: '初样开发',milestonskey: 'INITIAL_SAMPLE_DEVELOPMENT',parent: 'm12345',start_date: '2025-03-02',end_date: '2025-04-02',},{id: 'm12345-4',pCode: 'm12345',startDate: '2025-04-02',endDate: '2025-05-02',text: '正样开发',milestonskey: 'PROTOTYPE_DEVELOPMENT',parent: 'm12345',start_date: '2025-04-02',end_date: '2025-05-02',},{id: 'm12345-5',pCode: 'm12345',startDate: '2025-05-02',endDate: '2025-06-02',text: '验证',milestonskey: 'VERIFY',parent: 'm12345',start_date: '2025-05-02',end_date: '2025-06-02',},],children: null,render: 'split',id: 'm12345',},{id: 'm12346-1',pCode: 'm12346',startDate: '2025-01-02',endDate: '2025-02-02',text: 'Charter开发',milestonskey: 'CHARTER_DEVELOPMENT',parent: 'm12346',start_date: '2025-01-02',end_date: '2025-02-02',color: '#adbbff',},{id: 'm12346-2',pCode: 'm12346',startDate: '2025-02-02',endDate: '2025-03-02',text: '概念和计划',milestonskey: 'CONCEPT_AND_PLAN',parent: 'm12346',start_date: '2025-02-02',end_date: '2025-03-02',color: '#ffe179',},{id: 'm12346-3',pCode: 'm12346',startDate: '2025-03-02',endDate: '2025-04-02',text: '初样开发',milestonskey: 'INITIAL_SAMPLE_DEVELOPMENT',parent: 'm12346',start_date: '2025-03-02',end_date: '2025-04-02',color: '#adbbff',},{id: 'm12346-4',pCode: 'm12346',startDate: '2025-04-02',endDate: '2025-05-02',text: '正样开发',milestonskey: 'PROTOTYPE_DEVELOPMENT',parent: 'm12346',start_date: '2025-04-02',end_date: '2025-05-02',color: '#92d9fb',},{id: 'm12346-5',pCode: 'm12346',startDate: '2025-05-02',endDate: '2025-08-02',text: '验证',milestonskey: 'VERIFY',parent: 'm12346',start_date: '2025-05-02',end_date: '2025-08-02',color: '#64e0b7',},{bid: 3,pCode: '0',type: 2,code: 'm12346',name: 'karla',milestones: [{id: 'm12346-1',pCode: 'm12346',startDate: '2025-01-02',endDate: '2025-02-02',text: 'Charter开发',milestonskey: 'CHARTER_DEVELOPMENT',parent: 'm12346',start_date: '2025-01-02',end_date: '2025-02-02',},{id: 'm12346-2',pCode: 'm12346',startDate: '2025-02-02',endDate: '2025-03-02',text: '概念和计划',milestonskey: 'CONCEPT_AND_PLAN',parent: 'm12346',start_date: '2025-02-02',end_date: '2025-03-02',},{id: 'm12346-3',pCode: 'm12346',startDate: '2025-03-02',endDate: '2025-04-02',text: '初样开发',milestonskey: 'INITIAL_SAMPLE_DEVELOPMENT',parent: 'm12346',start_date: '2025-03-02',end_date: '2025-04-02',},{id: 'm12346-4',pCode: 'm12346',startDate: '2025-04-02',endDate: '2025-05-02',text: '正样开发',milestonskey: 'PROTOTYPE_DEVELOPMENT',parent: 'm12346',start_date: '2025-04-02',end_date: '2025-05-02',},{id: 'm12346-5',pCode: 'm12346',startDate: '2025-05-02',endDate: '2025-08-02',text: '验证',milestonskey: 'VERIFY',parent: 'm12346',start_date: '2025-05-02',end_date: '2025-08-02',},],children: null,render: 'split',id: 'm12346',},],
}gantt.init("gantt_here");
gantt.parse(tasks);function showTaskTooltip(event, domElement, key) {if (!tooltipConfig[key]) {return}let targetTaskId = gantt.locate(event);if (key == "cells") {targetTaskId = domElement.closest(".gantt_task_row").dataset.taskId}if (gantt.isTaskExists(targetTaskId)) {const task = gantt.getTask(targetTaskId);return gantt.templates.tooltip_text(task.start_date, task.end_date, task);}
}
gantt.ext.tooltips.tooltipFor({selector: ".gantt_row",html: function (event, domElement) {return showTaskTooltip(event, domElement, "grid")}
});gantt.ext.tooltips.tooltipFor({selector: ".gantt_task_line",html: function (event, domElement) {return showTaskTooltip(event, domElement, "bars")}
});gantt.ext.tooltips.tooltipFor({selector: ".gantt_task_row",html: function (event, domElement) {return showTaskTooltip(event, domElement, "cells")}
});gantt.ext.tooltips.tooltipFor({selector: ".gantt_task_link",html: function (event, domElement) {if (!tooltipConfig.links) {return}let targetLinkId = domElement.closest(".gantt_task_link").dataset.linkIdif (gantt.isLinkExists(targetLinkId)) {const link = gantt.getLink(targetLinkId);const source = gantt.getTask(link.source)const target = gantt.getTask(link.target)return `Link from <b>${source.text}</b> to <b>${target.text}</b>`}}
});gantt.ext.tooltips.tooltipFor({selector: ".gantt_scale_cell",html: function (event, domElement) {if (!tooltipConfig.scales) {return}return event.target.innerHTML}
});
1、html页面
import React, {forwardRef,useEffect,useRef,useState,useCallback,
} from 'react';
import { observer } from 'mobx-react';
import { gantt } from '@/lib/dhtmlx-gantt/dhtmlxgantt.js';
import '@/lib/dhtmlx-gantt/dhtmlxgantt.css';
import { zoomLevels } from './store';
import './index.less';import { languageConfig } from '@/language/language';
import moment from 'moment';const GanttChart = observer(forwardRef(<HTMLDivElement, GanttChartProps>(props: any, ref) => {const { infoData, onSelect } = props;const { timeScale, tableColunm, tableData } = infoData;console.log('甘特图接收参数', infoData);const ganttRef = useRef<any>(null);const currentZoomRef = useRef<string>('month'); // 用于存储当前时间刻度的引用/** 定义:第二网格列的配置 */const secondGridColumns = {columns: [{name: 'actions',label: '操作',width: 120,align: 'center',template: task => {// 自定义列内容的模板函数,接收任务对象作为参数return `<div class="gantt-actions"><!-- 编辑按钮,绑定任务的 id --><div class="edit-btn" data-task-id="${task.id}">编辑</div><!-- 详情按钮,绑定任务的 id --><div class="detail-btn" data-task-id="${task.id}">详情</div></div>`;},},],};/** 初始化:配置 Gantt */const initConfig = useCallback(() => {/*** 禁用系列*/// 禁止双击创建任务gantt.config.dblclick_create = false;// 禁止创建任务时弹出编辑弹窗gantt.config.edit_on_create = false;// 禁止双击任务时弹出详情弹窗gantt.config.details_on_dblclick = false;// 禁止双击创建链接gantt.config.dblclick_create_link = false;// 禁止创建链接时弹出编辑弹窗gantt.config.edit_on_create_link = false;// 禁止任务进度调整gantt.config.drag_progress = false;// 禁止拖拽连线gantt.config.drag_link = false;// 关闭报错弹窗gantt.config.show_errors = false;/*** end*/// 语言设置-中文gantt.i18n.setLocale('cn');// 行高gantt.config.row_height = 48;// 日期格式gantt.config.date_format = '%Y-%m-%d %H:%i';// 自适应gantt.config.autosize = 'y';/*** 设置列(第一列)* name - 数据字段;label-表头显示的名称;width-列宽;align-对齐方式;templage-自定义列内容的模板函数;*/gantt.config.columns = [{name: 'selected', // 勾选框列label: '', // 表头为空width: 50,align: 'center',template: task => {return `<div class="gantt-checkbox"><inputtype="checkbox"data-task-id="${task.id}"${task.selected ? 'checked' : ''}/></div>`;},},...tableColunm,];// 布局gantt.config.layout = {css: 'gantt_container', // 容器的 CSS 类名rows: [{cols: [// 左侧网格(任务列表){ view: 'grid', width: 320, scrollY: 'scrollVer' }, // // 网格视图,宽度为 320,垂直滚动条绑定到 'scrollVer'{ resizer: true, width: 1 }, // 可调整宽度的分隔条,宽度为// 时间线视图{ view: 'timeline', scrollX: 'scrollHor', scrollY: 'scrollVer' }, // 时间线视图,水平和垂直滚动条分别绑定到 'scrollHor' 和 'scrollVer'{ resizer: true, width: 1 },// 右侧网格(自定义操作列){view: 'grid', // 网格视图width: 120,bind: 'task', // 绑定到任务数据scrollY: 'scrollVer', // 垂直滚动条绑定到 'scrollVer'config: secondGridColumns, // 使用上面定义的 secondGridColumns 配置},// 垂直滚动条{ view: 'scrollbar', id: 'scrollVer' }, // 垂直滚动条视图,id 为 'scrollVer'],},// 水平滚动条{ view: 'scrollbar', id: 'scrollHor', height: 20 }, // 水平滚动条视图,id 为 'scrollHor',高度为 20],};// 设置 Tooltipgantt.templates.tooltip_text = function(start, end, task) {return `<div class="gantt_tooltip_box"><div><span>价值主张:</span> ${gantt.templates.tooltip_date_format(start,)}</div><div><span>关键竞争力:</span> ${gantt.templates.tooltip_date_format(end,)}</div></div>`;};// 启用当前日期标识插件gantt.plugins({marker: true, // 启用标记功能tooltip: true, // 启用tooltip插件功能});}, []);/** 初始化:缩放配置 */const initZoom = useCallback(() => {const zoomConfig = {levels: zoomLevels,element: () => ganttRef.current,};gantt.ext.zoom.init(zoomConfig); // 设置默认时间刻度gantt.ext.zoom.setLevel(currentZoomRef.current); // 设置默认时间刻度}, []);/** 初始化:加载任务数据 */const loadTasks = useCallback(() => {const tasks = tableData;gantt.parse(tasks);}, []);/** change:操作按钮点击事件 */const handleActionClick = useCallback(event => {const target = event.target;const taskId = target.getAttribute('data-task-id');if (!taskId) return;const actionMap = {'edit-btn': () => {console.log('点击了编辑:', taskId);onSelect({operationType: 'edit',operationId: taskId,});},'detail-btn': () => {console.log('点击了详情:', taskId);onSelect({operationType: 'detail',operationId: taskId,});},};// 调用对应的处理逻辑const action = actionMap[target.className];if (action) {action();}}, []);/** change:勾选框点击事件 */const handleCheckboxClick = useCallback(event => {const target = event.target;if (target.tagName === 'INPUT' && target.type === 'checkbox') {const taskId = target.getAttribute('data-task-id');const isChecked = target.checked;// 更新任务数据中的选中状态const task = gantt.getTask(taskId);task.selected = isChecked;gantt.updateTask(taskId);console.log('任务选中状态:', taskId, isChecked);}}, []);/** 监听 timeScale 的变化并动态更新时间刻度 */useEffect(() => {if (timeScale && timeScale !== currentZoomRef.current) {gantt.ext.zoom.setLevel(timeScale);currentZoomRef.current = timeScale; // 更新当前时间刻度}}, [timeScale]);/** 初始化 Gantt */useEffect(() => {// 甘特图配置initConfig();// 缩放配置initZoom();// 初始化gantt.init(ganttRef.current);// 加载数据loadTasks();// 设置初始时间刻度if (timeScale) {gantt.ext.zoom.setLevel(timeScale);currentZoomRef.current = timeScale; // 更新当前时间刻度}// 绑定操作按钮的点击事件const ganttContainer = ganttRef.current;ganttContainer.addEventListener('click', handleActionClick);// 绑定勾选框的点击事件ganttContainer.addEventListener('change', handleCheckboxClick);// 清理事件监听器return () => {ganttContainer.removeEventListener('click', handleActionClick);ganttContainer.removeEventListener('change', handleCheckboxClick);};}, [initConfig,initZoom,loadTasks,handleActionClick,handleCheckboxClick,]);function formatTime(date) {return moment(date).format('YYYY-MM-DD');}(gantt as any).attachEvent('onTaskDrag', function(id, mode, task) {const currentTask = gantt.getTask(id);console.log('currentTask',currentTask,formatTime(currentTask.start_date),formatTime(currentTask.end_date),);// 1. 获取上一个任务Idlet preTask: any = null;const preTaskId = gantt.getPrevSibling(id);if (preTaskId) {preTask = gantt.getTask(preTaskId);console.log('preTask',preTask,formatTime(preTask.start_date),formatTime(preTask.end_date),);}// 1. 获取下一个任务Idlet nextTask: any = null;const nextTaskId = gantt.getNextSibling(id);if (nextTaskId) {nextTask = gantt.getTask(nextTaskId);console.log('nextTask',nextTask,formatTime(nextTask.start_date),formatTime(nextTask.end_date),);}if (mode === 'move') {return false;}if (mode === 'resize') {// 首个任务if (nextTask && !preTask) {// console.log('start_date', currentTask.start_date);// console.log('end_date', currentTask.end_date);// console.log('计算', currentTask.end_date - currentTask.start_date);// if ((currentTask.end_date - currentTask.start_date) / (3600 * 24 * 1000) < 35) {// return;// }gantt.updateTask(nextTask.id, {...nextTask,start_date: currentTask.end_date,});// // 5. 自动刷新视图gantt.refreshTask(nextTask.id);}// 最后一个任务if (preTask && !nextTask) {gantt.updateTask(preTask.id, {...preTask,end_date: currentTask.start_date,});// // 5. 自动刷新视图gantt.refreshTask(preTask.id);}// 中间任务if (preTask && nextTask) {gantt.updateTask(preTask.id, {...preTask,end_date: currentTask.start_date,});gantt.updateTask(nextTask.id, {...nextTask,start_date: currentTask.end_date,});}}return true;});return (<>{/* Gantt 容器 */}<div ref={ganttRef} style={{ width: '100%', height: '100%' }}></div></>);}),
);export default GanttChart;
import React, {forwardRef,useEffect,useRef,useState,useCallback,
} from 'react';
import { Button } from 'choerodon-ui/pro';
import { ButtonColor, FuncType } from 'choerodon-ui/pro/lib/button/enum';
import { observer } from 'mobx-react';
import { gantt } from '@/lib/dhtmlx-gantt/dhtmlxgantt.js';
import '@/lib/dhtmlx-gantt/dhtmlxgantt.css';
import { zoomLevels } from './store';
import './index.less';
import DetailModal from '../detail/main';
import RecordModal from '../record/main';
import { languageConfig } from '@/language/language';const GanttChart = observer(forwardRef((props, ref) => {const [visible, setVisible] = useState(false); // 弹框(详情/编辑)const [recordVisible, setRecordVisible] = useState(false); // 记录弹框const [operationType, setOperationType] = useState('detail'); // 弹框类型const [operationId, setOperationId] = useState(''); // 操作ID(用于调用详情)const ganttRef = useRef<any>(null);const [currentZoom, setCurrentZoom] = useState('day'); // 时间刻度(默认值: day)const currentZoomRef = useRef('day');/** 配置 Gantt */const initConfig = useCallback(() => {// 语言设置-中文gantt.i18n.setLocale('cn');// 行高gantt.config.row_height = 48;// 日期格式gantt.config.date_format = '%Y-%m-%d %H:%i';// 自适应gantt.config.autosize = 'y';// 设置列(name为数据字段,label为表头显示的名称,width为列宽,align为对齐方式)gantt.config.columns = [{name: 'selected', // 勾选框列label: '', // 表头为空width: 50,align: 'center',template: task => {return `<div class="gantt-checkbox"><inputtype="checkbox"data-task-id="${task.id}"${task.selected ? 'checked' : ''}/></div>`;},},{name: 'text',label: '姓名',width: 150,tree: true,},{ name: 'start_date', label: '开始日期', width: 130, align: 'center' },{ name: 'duration', label: '持续时间', width: 80, align: 'center' },{ name: 'progress', label: '进度', width: 80, align: 'center' },{name: 'actions',label: '操作',width: 200,align: 'center',template: task => {return `<div class="gantt-actions"><div class="edit-btn" data-task-id="${task.id}">编辑</div><div class="detail-btn" data-task-id="${task.id}">详情</div><div class="record-btn" data-task-id="${task.id}">修改记录</div></div>`;},},];// 布局gantt.config.layout = {css: 'custom-gantt-style',rows: [{cols: [{ view: 'grid', width: 450 }, // 任务列表{ view: 'timeline', scrollX: 50, scrollY: 60 }, // 时间轴],},],resizer: {width: 10,handler: true,},};// 启用当前日期标识插件gantt.plugins({marker: true,});// 设置时间列的样式gantt.templates.timeline_cell_class = (task, date) => {const isDisabledZoom = ['month', 'year', 'quarter'].includes(currentZoomRef.current,);if (!isDisabledZoom && !gantt.isWorkTime(date)) return 'week_end';return '';};}, []);/** 设置当前日期标识线 */const initCurrentDateMarker = useCallback(() => {const dateToStr = gantt.date.date_to_str(gantt.config.task_date);const today = new Date(new Date().setHours(0, 0, 0, 0)); // 当天零点gantt.addMarker({start_date: today,css: 'today',text: '今日',title: `Today: ${dateToStr(today)}`,});}, []);/** 初始化缩放配置 */const initZoom = useCallback(() => {const zoomConfig = {levels: zoomLevels,element: () => gantt.$root.querySelector('.gantt_task'),};gantt.ext.zoom.init(zoomConfig);gantt.ext.zoom.setLevel('day');}, []);/** 加载任务数据 */const loadTasks = useCallback(() => {const tasks = {data: [{id: 1,text: 'Karla',start_date: '2019-08-01 00:00',duration: 3,parent: 0,progress: 0.5,open: true,},{id: 11,text: 'Karla-01',start_date: '2019-08-01 10:00',duration: 3,parent: 1,progress: 0.8,},{id: 2,text: '前端',start_date: '2019-08-02 00:00',duration: 2,parent: 1,progress: 0.7,},{id: 3,text: 'York',start_date: '2019-08-03 00:00',duration: 4,parent: 0,progress: 0.3,open: true,},{id: 4,text: '后端',start_date: '2019-08-04 00:00',duration: 1,parent: 3,progress: 0.6,open: true,},{id: 5,text: 'Coco',start_date: '2019-08-05 00:00',duration: 2,parent: 0,progress: 0.8,open: true,},{id: 6,text: '测试',start_date: '2019-08-06 00:00',duration: 5,parent: 5,progress: 0.4,},{id: 7,text: 'Happy',start_date: '2019-08-07 00:00',duration: 3,parent: 0,progress: 0.9,},{id: 9,text: 'Rose',start_date: '2019-08-09 00:00',duration: 1,parent: 0,progress: 0.1,},],};gantt.parse(tasks);}, []);/** change 操作按钮点击事件 */const handleActionClick = useCallback(event => {const target = event.target;const taskId = target.getAttribute('data-task-id');if (!taskId) return;setOperationId(taskId);const actionMap = {'edit-btn': () => {console.log('点击了编辑:', taskId);setOperationType('edit');setVisible(true);},'detail-btn': () => {console.log('点击了详情:', taskId);setOperationType('detail');setVisible(true);},'record-btn': () => {console.log('点击了修改记录:', taskId);setRecordVisible(true);},};// 调用对应的处理逻辑const action = actionMap[target.className];if (action) {action();}}, []);/**change 时间刻度视图 */const handleZoomChange = useCallback(zoom => {setCurrentZoom(zoom);currentZoomRef.current = zoom;gantt.ext.zoom.setLevel(zoom);}, []);/** 处理勾选框点击事件 */const handleCheckboxClick = useCallback(event => {const target = event.target;if (target.tagName === 'INPUT' && target.type === 'checkbox') {const taskId = target.getAttribute('data-task-id');const isChecked = target.checked;// 更新任务数据中的选中状态const task = gantt.getTask(taskId);task.selected = isChecked;gantt.updateTask(taskId);console.log('任务选中状态:', taskId, isChecked);}}, []);/** 初始化 Gantt */useEffect(() => {initConfig();initZoom();initCurrentDateMarker();// 初始化gantt.init(ganttRef.current);// 加载数据loadTasks();// 绑定操作按钮的点击事件const ganttContainer = ganttRef.current;ganttContainer.addEventListener('click', handleActionClick);// 绑定勾选框的点击事件ganttContainer.addEventListener('change', handleCheckboxClick);// 清理事件监听器return () => {ganttContainer.removeEventListener('click', handleActionClick);ganttContainer.removeEventListener('change', handleCheckboxClick);};}, [initConfig,initZoom,initCurrentDateMarker,loadTasks,handleActionClick,handleCheckboxClick,]);return (<div>{/* 时间刻度:切换按钮 */}<div style={{ margin: '12px 0' }}>{zoomLevels.map(item => (<Buttonkey={item.name}color={ButtonColor.primary}funcType={FuncType.raised}disabled={item.name === currentZoom}onClick={() => handleZoomChange(item.name)}style={{ marginRight: 6 }}>{item.label}</Button>))}</div>{/* Gantt 容器 */}<div style={{ height: '500px', overflow: 'auto' }}><div ref={ganttRef} style={{ width: '100%', height: '100%' }}></div></div>{/* 详情/编辑弹框 */}{visible && (<DetailModalvisible={visible}setVisible={setVisible}onSelect={() => {}}title={operationType === 'detail'? languageConfig('platformCharter.title.detail', '详情'): languageConfig('platformCharter.title.edit', '编辑')}infoData={{operationId,operationType,}}/>)}{/* 记录 */}{recordVisible && (<RecordModalvisible={recordVisible}setVisible={setRecordVisible}onSelect={() => {}}title={languageConfig('platformCharter.title.editRecord','修改记录',)}infoData={{operationId,}}/>)}</div>);}),
);export default GanttChart;
3、mock数据(暂时没用上)
data: [// 第一条数据{pCode: '0',type: 1,code: 'm1234',name: 'doudou',open: true, // 默认展开id: 'm1234',color: '#c7cfd8',text: '',},{id: 'm1234567-1',pCode: 'm1234567',startDate: '2025-01-02',endDate: '2025-02-02',text: 'Charter开发',milestonskey: 'CHARTER_DEVELOPMENT',parent: 'm1234567',start_date: '2025-01-02',end_date: '2025-02-02',color: '#adbbff',},{id: 'm1234567-2',pCode: 'm1234567',startDate: '2025-02-02',endDate: '2025-03-02',text: '概念和计划',milestonskey: 'CONCEPT_AND_PLAN',parent: 'm1234567',start_date: '2025-02-02',end_date: '2025-03-02',color: '#ffe179',},{id: 'm1234567-3',pCode: 'm1234567',startDate: '2025-03-02',endDate: '2025-04-02',text: '初样开发',milestonskey: 'INITIAL_SAMPLE_DEVELOPMENT',parent: 'm1234567',start_date: '2025-03-02',end_date: '2025-04-02',color: '#adbbff',},{id: 'm1234567-4',pCode: 'm1234567',startDate: '2025-04-02',endDate: '2025-05-02',text: '正样开发',milestonskey: 'PROTOTYPE_DEVELOPMENT',parent: 'm1234567',start_date: '2025-04-02',end_date: '2025-05-02',color: '#92d9fb',},{id: 'm1234567-5',pCode: 'm1234567',startDate: '2025-05-02',endDate: '2025-09-02',text: '验证',milestonskey: 'VERIFY',parent: 'm1234567',start_date: '2025-05-02',end_date: '2025-09-02',color: '#64e0b7',},// 第一条数据的:第一个子级{bid: 4,pCode: 'm1234',type: 2,code: 'm1234567',name: 'ububuu',milestones: [{id: 'm1234567-1',pCode: 'm1234567',startDate: '2025-01-02',endDate: '2025-02-02',text: 'Charter开发',milestonskey: 'CHARTER_DEVELOPMENT',parent: 'm1234567',start_date: '2025-01-02',end_date: '2025-02-02',},{id: 'm1234567-2',pCode: 'm1234567',startDate: '2025-02-02',endDate: '2025-03-02',text: '概念和计划',milestonskey: 'CONCEPT_AND_PLAN',parent: 'm1234567',start_date: '2025-02-02',end_date: '2025-03-02',},{id: 'm1234567-3',pCode: 'm1234567',startDate: '2025-03-02',endDate: '2025-04-02',text: '初样开发',milestonskey: 'INITIAL_SAMPLE_DEVELOPMENT',parent: 'm1234567',start_date: '2025-03-02',end_date: '2025-04-02',},{id: 'm1234567-4',pCode: 'm1234567',startDate: '2025-04-02',endDate: '2025-05-02',text: '正样开发',milestonskey: 'PROTOTYPE_DEVELOPMENT',parent: 'm1234567',start_date: '2025-04-02',end_date: '2025-05-02',},{id: 'm1234567-5',pCode: 'm1234567',startDate: '2025-05-02',endDate: '2025-09-02',text: '验证',milestonskey: 'VERIFY',parent: 'm1234567',start_date: '2025-05-02',end_date: '2025-09-02',},],children: null,parent: 'm1234',render: 'split',isChildrenNode: true,id: 'm1234567',},{id: 'm12345678-1',pCode: 'm12345678',startDate: '2025-01-02',endDate: '2025-02-02',text: 'Charter开发',milestonskey: 'CHARTER_DEVELOPMENT',parent: 'm12345678',start_date: '2025-01-02',end_date: '2025-02-02',color: '#adbbff',},{id: 'm12345678-2',pCode: 'm12345678',startDate: '2025-02-02',endDate: '2025-03-02',text: '概念和计划',milestonskey: 'CONCEPT_AND_PLAN',parent: 'm12345678',start_date: '2025-02-02',end_date: '2025-03-02',color: '#ffe179',},{id: 'm12345678-3',pCode: 'm12345678',startDate: '2025-03-02',endDate: '2025-04-02',text: '初样开发',milestonskey: 'INITIAL_SAMPLE_DEVELOPMENT',parent: 'm12345678',start_date: '2025-03-02',end_date: '2025-04-02',color: '#adbbff',},{id: 'm12345678-4',pCode: 'm12345678',startDate: '2025-04-02',endDate: '2025-05-02',text: '正样开发',milestonskey: 'PROTOTYPE_DEVELOPMENT',parent: 'm12345678',start_date: '2025-04-02',end_date: '2025-05-02',color: '#92d9fb',},{id: 'm12345678-5',pCode: 'm12345678',startDate: '2025-05-02',endDate: '2025-07-02',text: '验证',milestonskey: 'VERIFY',parent: 'm12345678',start_date: '2025-05-02',end_date: '2025-07-02',color: '#64e0b7',},// 第一第的第二子级{bid: 5,pCode: 'm1234',type: 2,code: 'm12345678',name: 'm1234',// 任务节点milestones: [{id: 'm12345678-1',pCode: 'm12345678',startDate: '2025-01-02',endDate: '2025-02-02',text: 'Charter开发',milestonskey: 'CHARTER_DEVELOPMENT',parent: 'm12345678',start_date: '2025-01-02',end_date: '2025-02-02',},{id: 'm12345678-2',pCode: 'm12345678',startDate: '2025-02-02',endDate: '2025-03-02',text: '概念和计划',milestonskey: 'CONCEPT_AND_PLAN',parent: 'm12345678',start_date: '2025-02-02',end_date: '2025-03-02',},{id: 'm12345678-3',pCode: 'm12345678',startDate: '2025-03-02',endDate: '2025-04-02',text: '初样开发',milestonskey: 'INITIAL_SAMPLE_DEVELOPMENT',parent: 'm12345678',start_date: '2025-03-02',end_date: '2025-04-02',},{id: 'm12345678-4',pCode: 'm12345678',startDate: '2025-04-02',endDate: '2025-05-02',text: '正样开发',milestonskey: 'PROTOTYPE_DEVELOPMENT',parent: 'm12345678',start_date: '2025-04-02',end_date: '2025-05-02',},{id: 'm12345678-5',pCode: 'm12345678',startDate: '2025-05-02',endDate: '2025-07-02',text: '验证',milestonskey: 'VERIFY',parent: 'm12345678',start_date: '2025-05-02',end_date: '2025-07-02',},],children: null,parent: 'm1234',render: 'split',isChildrenNode: true,id: 'm12345678',},{id: 'm12345-1',pCode: 'm12345',startDate: '2025-01-02',endDate: '2025-02-02',text: 'Charter开发',milestonskey: 'CHARTER_DEVELOPMENT',parent: 'm12345',start_date: '2025-01-02',end_date: '2025-02-02',color: '#adbbff',},{id: 'm12345-2',pCode: 'm12345',startDate: '2025-02-02',endDate: '2025-03-02',text: '概念和计划',milestonskey: 'CONCEPT_AND_PLAN',parent: 'm12345',start_date: '2025-02-02',end_date: '2025-03-02',color: '#ffe179',},{id: 'm12345-3',pCode: 'm12345',startDate: '2025-03-02',endDate: '2025-04-02',text: '初样开发',milestonskey: 'INITIAL_SAMPLE_DEVELOPMENT',parent: 'm12345',start_date: '2025-03-02',end_date: '2025-04-02',color: '#adbbff',},{id: 'm12345-4',pCode: 'm12345',startDate: '2025-04-02',endDate: '2025-05-02',text: '正样开发',milestonskey: 'PROTOTYPE_DEVELOPMENT',parent: 'm12345',start_date: '2025-04-02',end_date: '2025-05-02',color: '#92d9fb',},{id: 'm12345-5',pCode: 'm12345',startDate: '2025-05-02',endDate: '2025-06-02',text: '验证',milestonskey: 'VERIFY',parent: 'm12345',start_date: '2025-05-02',end_date: '2025-06-02',color: '#64e0b7',},// 第二条{bid: 2,pCode: '0',type: 2,code: 'm12345',name: 'demo',projectRank: 'm1234',marketPositioning: 'm1234',costBenchmarking: 'm1234',performanceBenchmark: 'm1234',targetAudienceName: 'm1234',focusedIndustriesAndClientsName: 'm1234',newReachableCapacity: 'm1234',preparationOfKeyTechnologies: null,valueProposition: 'm1234',keyCompetitive: 'm1234',charterInitDate: '2025-01-02 00:00:00',charterTransferDate: '2025-02-02 00:00:00',pdcpDate: '2025-03-02 00:00:00',tr4aDate: '2025-04-02 00:00:00',edcpDate: '2025-05-02 00:00:00',adcpDate: '2025-06-02 00:00:00',insightFinishDate: '2025-07-02 00:00:00',relateOfferingName: null,rerlationshipName: null,serviceSolution: null,belongSolutionName: null,productClassL3Name: null,productSeriesL4Name: null,productOfferingL5Name: null,productBelongGroupsName: null,productBelongSpdtName: null,charterClass: null,charterTransferVersionTypeName: null,decisionLevelName: null,priorityName: null,businessPropertyName: null,bak: null,verison: null,state: 1,createUser: null,updatedUser: null,milestones: [{id: 'm12345-1',pCode: 'm12345',startDate: '2025-01-02',endDate: '2025-02-02',text: 'Charter开发',milestonskey: 'CHARTER_DEVELOPMENT',parent: 'm12345',start_date: '2025-01-02',end_date: '2025-02-02',},{id: 'm12345-2',pCode: 'm12345',startDate: '2025-02-02',endDate: '2025-03-02',text: '概念和计划',milestonskey: 'CONCEPT_AND_PLAN',parent: 'm12345',start_date: '2025-02-02',end_date: '2025-03-02',},{id: 'm12345-3',pCode: 'm12345',startDate: '2025-03-02',endDate: '2025-04-02',text: '初样开发',milestonskey: 'INITIAL_SAMPLE_DEVELOPMENT',parent: 'm12345',start_date: '2025-03-02',end_date: '2025-04-02',},{id: 'm12345-4',pCode: 'm12345',startDate: '2025-04-02',endDate: '2025-05-02',text: '正样开发',milestonskey: 'PROTOTYPE_DEVELOPMENT',parent: 'm12345',start_date: '2025-04-02',end_date: '2025-05-02',},{id: 'm12345-5',pCode: 'm12345',startDate: '2025-05-02',endDate: '2025-06-02',text: '验证',milestonskey: 'VERIFY',parent: 'm12345',start_date: '2025-05-02',end_date: '2025-06-02',},],children: null,render: 'split',id: 'm12345',},{id: 'm12346-1',pCode: 'm12346',startDate: '2025-01-02',endDate: '2025-02-02',text: 'Charter开发',milestonskey: 'CHARTER_DEVELOPMENT',parent: 'm12346',start_date: '2025-01-02',end_date: '2025-02-02',color: '#adbbff',},{id: 'm12346-2',pCode: 'm12346',startDate: '2025-02-02',endDate: '2025-03-02',text: '概念和计划',milestonskey: 'CONCEPT_AND_PLAN',parent: 'm12346',start_date: '2025-02-02',end_date: '2025-03-02',color: '#ffe179',},{id: 'm12346-3',pCode: 'm12346',startDate: '2025-03-02',endDate: '2025-04-02',text: '初样开发',milestonskey: 'INITIAL_SAMPLE_DEVELOPMENT',parent: 'm12346',start_date: '2025-03-02',end_date: '2025-04-02',color: '#adbbff',},{id: 'm12346-4',pCode: 'm12346',startDate: '2025-04-02',endDate: '2025-05-02',text: '正样开发',milestonskey: 'PROTOTYPE_DEVELOPMENT',parent: 'm12346',start_date: '2025-04-02',end_date: '2025-05-02',color: '#92d9fb',},{id: 'm12346-5',pCode: 'm12346',startDate: '2025-05-02',endDate: '2025-08-02',text: '验证',milestonskey: 'VERIFY',parent: 'm12346',start_date: '2025-05-02',end_date: '2025-08-02',color: '#64e0b7',},{bid: 3,pCode: '0',type: 2,code: 'm12346',name: 'karla',milestones: [{id: 'm12346-1',pCode: 'm12346',startDate: '2025-01-02',endDate: '2025-02-02',text: 'Charter开发',milestonskey: 'CHARTER_DEVELOPMENT',parent: 'm12346',start_date: '2025-01-02',end_date: '2025-02-02',},{id: 'm12346-2',pCode: 'm12346',startDate: '2025-02-02',endDate: '2025-03-02',text: '概念和计划',milestonskey: 'CONCEPT_AND_PLAN',parent: 'm12346',start_date: '2025-02-02',end_date: '2025-03-02',},{id: 'm12346-3',pCode: 'm12346',startDate: '2025-03-02',endDate: '2025-04-02',text: '初样开发',milestonskey: 'INITIAL_SAMPLE_DEVELOPMENT',parent: 'm12346',start_date: '2025-03-02',end_date: '2025-04-02',},{id: 'm12346-4',pCode: 'm12346',startDate: '2025-04-02',endDate: '2025-05-02',text: '正样开发',milestonskey: 'PROTOTYPE_DEVELOPMENT',parent: 'm12346',start_date: '2025-04-02',end_date: '2025-05-02',},{id: 'm12346-5',pCode: 'm12346',startDate: '2025-05-02',endDate: '2025-08-02',text: '验证',milestonskey: 'VERIFY',parent: 'm12346',start_date: '2025-05-02',end_date: '2025-08-02',},],children: null,render: 'split',id: 'm12346',},],
相关文章:
甘特图开发代码(测试版)
场景:要实现的功能就是单行数据能左右拖动。 流程五个:ABCDE。(对应:Charter开发、概念和计划、初样开发、正样开发、验证) 1、A有开始时间,结束时间。B的开始时间必须是A的结束时间(相等或者…...
鸿蒙与DeepSeek深度整合:构建下一代智能操作系统生态
前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。 https://www.captainbed.cn/north 目录 技术融合背景与价值鸿蒙分布式架构解析DeepSeek技术体系剖析核心整合架构设计智能调度系统实现…...
Docker Desktop常见问题记录
1.docker pull报错,无法连接https://registry-1.docker.io/v2/ 报错信息如下: Error response from daemon: Get "https://registry-1.docker.io/v2/": net/http: request canceled while waiting for connection(Client.Timeout exceeded …...
Qt 的 Lambda 捕获局部变量导致 UI 更新异常的分析与解决
1. 问题描述 在 Qt 开发中,我们通常会使用 QTimer 进行周期性 UI 更新。例如,下面的代码用于在检测游戏窗口时,在 UI 界面上显示动态变化的“正在检测游戏窗口...”的文本,每 300 毫秒更新一次。 void MainWindow::detectAndPopulateGameList() {ui->game_record_stac…...
RAGflow采用docker-compose-continuous方式pull,把服务器充满了
采用docker-compose-continuous在后台下载,导致服务器被充满。 原因分析: 如果网络不稳定,可能导致下载任务异常中断,而 systemd 服务会不断重启并重新下载,从而占用大量空间。如果网络问题无法解决,可以…...
【第12节】C++设计模式(结构型模式)-Proxy(代理)模式
一、问题背景 使用 Proxy 模式优化对象访问 在某些情况下,直接访问对象可能会导致性能问题或安全性问题。Proxy 模式(代理模式)通过引入一个代理对象来控制对原始对象的访问,从而解决这些问题。以下是几种典型的应用场景…...
【C++】vector(上):vector的常用接口介绍
文章目录 前言一、vector的介绍二、vector的常用接口介绍1.vector类对象的常见构造2.vector iterator 的使用3.vector类对象的容量操作3.1 size、capacity 和 empty的使用3.2 reserve的使用3.3 resize的使用 4.vector类对象的访问(包含data:返回底层数组…...
【详细讲解在STM32的UART通信中使用DMA机制】
详细讲解在STM32的UART通信中使用DMA机制 目录 详细讲解在STM32的UART通信中使用DMA机制一、DMA机制概述二、DMA在UART中的作用三、DMA的配置步骤四、UART初始化与DMA结合五、DMA传输的中断处理六、DMA与中断的结合使用七、注意事项与常见问题八、代码示例九、总结 一、DMA机制…...
极狐GitLab 17.9 正式发布,40+ DevSecOps 重点功能解读【三】
GitLab 是一个全球知名的一体化 DevOps 平台,很多人都通过私有化部署 GitLab 来进行源代码托管。极狐GitLab 是 GitLab 在中国的发行版,专门为中国程序员服务。可以一键式部署极狐GitLab。 学习极狐GitLab 的相关资料: 极狐GitLab 官网极狐…...
快速生成viso流程图图片形式
我们在写详细设计文档的过程中总会不可避免的涉及到时序图或者流程图的绘制,viso这个软件大部分技术人员都会使用,但是想要画的好看,画的科学还是比较难的,现在我总结一套比较好的方法可以生成好看科学的viso图(图片格式)。主要思…...
设备管理系统功能与.NET+VUE(IVIEW)技术实现
在现代工业和商业环境中,设备管理系统(Equipment Management System,简称EMS)是确保设备高效运行和维护的关键工具。本文采用多租户设计的设备管理系统,基于.NET后端和VUE前端(使用IVIEW UI框架)…...
《深度学习实战》第11集:AI大模型压缩与加速
深度学习实战 | 第11集:AI大模型压缩与加速 在深度学习领域,随着模型规模的不断增大,模型的推理速度和部署效率成为实际应用中的关键挑战。本篇博客将带你深入了解模型压缩与加速的核心技术,并通过一个实战项目展示如何使用知识蒸…...
【大模型安全】大模型的技术风险
【大模型安全】大模型的技术风险 1.DDoS攻击2.常见的传统网络攻击方式3.恶意意图的识别4.AI生成虚假信息传播5.利用AI进行黑客攻击6.模型对抗攻击7.后门攻击8.Prompt攻击9.数据投毒攻击10.模型窃取攻击11.数据窃取攻击 1.DDoS攻击 2023年11月9日凌晨,OpenAI在官网公…...
git命令学习记录
1. git reset 参数说明 git reset 是用来回退版本的,它可以添加三个参数,常用的使用格式是这样的:git reset [--hard | --soft | --mixed] 版本号 一般使用git修改文件并提交需要三步,第一步在文本编辑器中编辑文件,也…...
Gartner:数据安全平台DSP提升数据流转及使用安全
2025 年 1 月 7 日,Gartner 发布“China Context:Market Guide for Data Security Platforms”(《数据安全平台市场指南——中国篇》,以下简称指南),报告主要聚焦中国数据安全平台(Data Securit…...
结构型模式---享元模式
概念 享元模式是一种结构型设计模式,他摒弃了在每个对象中保存所有数据的方式,通过共享多个对象所共有的相同状态,让你能在有限的内存容量中载入更多对象。享元模式将原始类中的数据分为内在状态数据和外在状态数据。 内在状态:就…...
一学就会:A*算法详细介绍(Python)
📢本篇文章是博主人工智能学习以及算法研究时,用于个人学习、研究或者欣赏使用,并基于博主对相关等领域的一些理解而记录的学习摘录和笔记,若有不当和侵权之处,指出后将会立即改正,还望谅解。文章分类在&am…...
【Elasticsearch】Elasticsearch 中使用 HDFS 存储快照
在 Elasticsearch 中使用 HDFS 存储快照的步骤如下: 1.安装 HDFS 插件 要使用 HDFS 存储 Elasticsearch 的索引快照,需要在 Elasticsearch 集群的所有节点上安装 HDFS 插件。 • 在线安装:适用于网络环境良好的场景,执行以下命…...
【每日十题系列】前端面试高频题目
以下是作为前端面试官常用的10道手撕代码题目,涵盖JavaScript核心、CSS、算法及框架原理,结合高频考点与实际开发场景设计: 1. 手写防抖(debounce)与节流(throttle) 要求:实现防抖函…...
Kafka 消息 0 丢失的最佳实践
文章目录 Kafka 消息 0 丢失的最佳实践生产者端的最佳实践使用带有回调的 producer.send(msg, callback) 方法设置 acks all设置 retries 为一个较大的值启用幂等性与事务(Kafka 0.11)正确关闭生产者与 flush() 方法 Broker 端的最佳实践设置 unclean.l…...
学网络安全报班可靠吗?
在当今社会,网络安全已经成为我们工作和生活中不可忽视的重要部分,而且市场上各大企业对网络安全人才的需求量非常之大,因此网络安全培训班应运而生,那么学网络安全报培训班靠谱吗?这是很多小伙伴都关心的问题,我们来…...
LeetCode 1745.分割回文串 IV:动态规划(用III或II能直接秒)
【LetMeFly】1745.分割回文串 IV:动态规划(用III或II能直接秒) 力扣题目链接:https://leetcode.cn/problems/palindrome-partitioning-iv/ 给你一个字符串 s ,如果可以将它分割成三个 非空 回文子字符串,…...
4 Redis4 List命令类型讲解
Redis 列表(List)命令详解 1. Redis 列表(List)简介 Redis 列表(List)是一个简单的字符串列表,按照插入顺序排序。它可以用作 栈(Stack) 和 队列(Queue&…...
鬼泣:项目前置设置杂项
能帮到你的话,就给个赞吧 😘 文章目录 插件niagara ui render:在 UI 中渲染 Niagara 特效skeletal editor:编辑骨骼,调整骨骼动画motion warping:根据目标自动调整角色动画。animation warping:…...
MyBatis-Plus 条件构造器的使用(左匹配查询)
在上一篇文章中,我们已经介绍了 MyBatis-Plus 条件构造器,包括 QueryWrapper 和 UpdateWrapper 的基本使用方法、常见查询条件(如等于、不等于、大于、小于)以及如何使用 Lambda 表达式来构建动态查询和更新条件。 在本文中&…...
#define GBB_DEPRECATED_MSG(msg) __declspec(deprecated(msg))
这个宏 #define GBB_DEPRECATED_MSG(msg) __declspec(deprecated(msg)) 是用来在 C++ 中标记某些函数、变量或者代码元素为已弃用(deprecated)的,并附带一个自定义的弃用消息。 具体解释: __declspec(deprecated(msg)): __declspec 是 Microsoft Visual C++ (MSVC) 的扩展…...
Vue输入框获取焦点
1. 元素未渲染完成 如果你在组件挂载或数据更新后立即调用 focus(),可能元素还未渲染到 DOM 中,导致 focus() 失效。 解决方法:确保在元素渲染完成后再调用 focus()。可以使用 nextTick 确保 DOM 更新完成。 2. ref 未正确绑定 确保 ref 正确…...
辛格迪客户案例 | 深圳善康医药科技GMP培训管理(TMS)项目
01 善康医药:创新药领域的探索者 深圳善康医药科技股份有限公司自2017年创立以来,便扎根于创新药研发领域,专注于成瘾治疗药物的研究、生产与销售。公司坐落于深圳,凭借自身独特的技术优势与研发实力,在行业内逐渐崭露…...
迷你世界脚本出生点接口:Spawnport
出生点接口:Spawnport 彼得兔 更新时间: 2023-04-26 10:19:56 具体函数名及描述如下: 序号 函数名 函数描述 1 getSpawnPoint(...) 获取默认出生点 2 setSpawnPoint(...) 设置出生点位置 3 getChunkValidSpawnPos(...) 获取区块有效刷新点…...
Android车机DIY开发之软件篇(二十)立创泰山派android编译
准备工作 sudo apt-get update sudo apt-get install git -y sudo apt install repo -ysudo apt-get install python2.7sudo apt-get install python3sudo update-alternatives --install /usr/bin/python python /usr/bin/python2.7 1 sudo update-alternatives --install /u…...
MDM 如何彻底改变医疗设备的远程管理
在现代医疗行业迅速发展的格局中,医院和诊所越来越依赖诸如医疗平板和移动工作站等移动设备。这些设备在提高工作效率和提供卓越的患者护理方面发挥着关键作用。然而,随着它们的广泛使用,也带来了一系列挑战,例如在不同地点确保数…...
Libgdx游戏开发系列教程(6)——游戏暂停
目录 最初方案1 优化方案2 - 事件拦截器监听按键 优化方案3 - 暂停状态重绘 优化方案4 优化方案5 补充 - 监听android手机的返回键 暂停也是一个游戏的必要功能了,本文研究了Libgdx实现游戏暂停 例子以桌面端游戏实现讲解为主,至于移动端,可能之后会进行补充... 本文最…...
车载测试:智能座舱测试中多屏联动与语音交互的挑战
智能座舱作为汽车智能化发展的核心,集成了多屏联动和语音交互功能,为驾驶员和乘客提供更便捷的体验。然而,这些功能的测试面临诸多挑战,包括多屏同步性、噪声干扰和复杂场景的处理。本文将详细分析这些挑战,探讨测试方…...
【踩坑随笔】`npm list axios echarts`查看npm依赖包报错
npm list axios echarts查看npm依赖包出现以下报错,原因就是包的版本匹配问题,按照提示降axios版本或者自己升找合适的got版本,我这里是选择了降版本。本文记录仅做解决思路参考不一定适配大家的实际情况。 weed-detection-system1.0.0 E:\P…...
用于管理 Elasticsearch Serverless 项目的 AI Agent
作者:来自 Elastic Fram Souza 由自然语言驱动的 AI 代理,可轻松管理 Elasticsearch Serverless 项目 - 支持项目创建、删除和状态检查。 这个小型命令行工具让你可以用简单的英语管理你的无服务器 Elasticsearch 项目。它通过AI(这里是 Ope…...
【文生图】windows 部署stable-diffusion-webui
windows 部署stable-diffusion-webui AUTOMATIC1111 stable-diffusion-webui Detailed feature showcase with images: 带图片的详细功能展示: Original txt2img and img2img modes 原始的 txt2img 和 img2img 模式 One click install and run script (but you still must i…...
STaR(Self-Taught Reasoner)方法:让语言模型自学推理能力(代码实现)
STaR(Self-Taught Reasoner)方法:让语言模型自学推理能力 在大型语言模型(LLM)的推理能力优化中,STaR(Self-Taught Reasoner) 是一种引人注目的技术,属于“修改提议分布…...
十大经典排序算法简介
一 概述 本文对十大经典排序算法做简要的总结(按常用分类方式排列),包含核心思想、时间/空间复杂度及特点。 二、比较类排序 1. 冒泡排序 (BUBBLE SORT) 思想:重复交换相邻逆序元素,像气泡上浮 复杂度: 时间:O(n^2)(最好情况O(n)) 空间:O(1) 特点:简单但效率低,稳…...
5.训练策略:优化深度学习训练过程的实践指南——大模型开发深度学习理论基础
在实际开发中,训练策略对神经网络的表现起着至关重要的作用。通过合理的训练策略,我们可以有效避免过拟合和欠拟合,加速模型收敛,并提升最终性能。本文将从实际开发角度详细介绍几种关键的训练策略,包括 Early Stoppin…...
道可云人工智能每日资讯|《奇遇三星堆》VR沉浸探索展(淮安站)开展
道可云元宇宙每日简报(2025年3月5日)讯,今日元宇宙新鲜事有: 《奇遇三星堆》VR沉浸探索展(淮安站)开展 近日,《奇遇三星堆》VR沉浸探索展(淮安站)开展。该展将三星堆文…...
Camera相关配置
一、 Purpose目的 通常, 感知模块使用雷达点云和相机图像来对物体进行检测和分类,感知数据分别来自雷达和相机,就传感器数据融合准确性,我们需要雷达和相机同一时间捕捉到同一物体(时间间隔尽可能短),否则一个真实的物…...
PHP Error处理指南
PHP Error处理指南 引言 在PHP开发过程中,错误处理是一个至关重要的环节。正确的错误处理不仅能够提高代码的健壮性,还能提升用户体验。本文将详细介绍PHP中常见的错误类型、错误处理机制以及最佳实践,帮助开发者更好地应对和处理PHP错误。 PHP错误类型 在PHP中,错误主…...
【Pandas】pandas Series argmax
Pandas2.2 Series Computations descriptive stats 方法描述Series.argsort([axis, kind, order, stable])用于返回 Series 中元素排序后的索引位置的方法Series.argmin([axis, skipna])用于返回 Series 中最小值索引位置的方法Series.argmax([axis, skipna])用于返回 Series…...
Gitlab配置personal access token
1.点击左上角个人账号 -> Preferences 2. 点击左边栏 Access Tokens 3. 点击Add new token ,输入token名称,勾选权限(注意截至日期 “Expiration date” 可不填) 4. 创建成功后,显示token信息,复制到本地…...
【MySQL、Oracle、SQLserver、postgresql】查询多条数据合并成一行
四大数据库多行合并为单行:函数详解与对比 一、MySQL**GROUP_CONCAT()** 函数说明:语法结构:参数解释:示例:注意事项: 二、Oracle**LISTAGG()** 函数说明:语法结构:参数解释…...
人机交互进化论:解码智能手机81种交互方式背后的用户体验革命
人机交互进化论:解码智能手机81种交互方式背后的用户体验革命 2023年艾瑞咨询报告显示:中国智能手机用户日均触屏交互超2500次,解锁屏幕达76次/天。在这看似简单的点击与滑动背后,隐藏着一场持续演进的人机交互革命。本文将深度解…...
OCPP扩展机制与自定义功能开发:协议灵活性设计与实践 - 慧知开源充电桩平台
OCPP扩展机制与自定义功能开发:协议灵活性设计与实践 引言 OCPP作为开放协议,其核心价值在于平衡标准化与可扩展性。面对不同充电桩厂商的硬件差异、区域能源政策及定制化业务需求,OCPP通过**扩展点(Extension Points)…...
网络编程之TCP协议
传输层协议:UDP和TCP的区别 UDP:用户数据报协议 1.面向数据报 2.无连接 3.不安全,不可靠(尽最大努力交付) TCP:传输控制协议 1.面向数据流(流式套接字) 2.建立连接 3.安全可靠的传输协议 TCP的传输过程 三次握手:TCP建立…...
策略模式的C++实现示例
核心思想 策略模式是一种行为型设计模式,它定义了一系列算法,并将每个算法封装在独立的类中,使得它们可以互相替换。策略模式让算法的变化独立于使用它的客户端,从而使得客户端可以根据需要动态切换算法,而不需要修改…...
keil软件下载安装使用(STM32篇)
一、前言 Keil软件是一款专为嵌入式系统开发设计的集成开发环境(IDE),由德国Keil公司创立,后被ARM公司收购并持续更新维护。它集成了代码编辑、编译、调试和项目管理等功能,广泛应用于各类单片机的程序开发࿰…...