场景:要实现的功能就是单行数据能左右拖动。
流程五个: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">
<input
type="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.linkId
if (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页面
javascript
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">
<input
type="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
],
};
// 设置 Tooltip
gantt.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. 获取上一个任务Id
let 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. 获取下一个任务Id
let 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;
javascript
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">
<input
type="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 => (
<Button
key={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 && (
<DetailModal
visible={visible}
setVisible={setVisible}
onSelect={() => {}}
title={
operationType === 'detail'
? languageConfig('platformCharter.title.detail', '详情')
: languageConfig('platformCharter.title.edit', '编辑')
}
infoData={{
operationId,
operationType,
}}
/>
)}
{/* 记录 */}
{recordVisible && (
<RecordModal
visible={recordVisible}
setVisible={setRecordVisible}
onSelect={() => {}}
title={languageConfig(
'platformCharter.title.editRecord',
'修改记录',
)}
infoData={{
operationId,
}}
/>
)}
</div>
);
}),
);
export default GanttChart;
3、mock数据(暂时没用上)
javascript
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',
},
],