dhtmlxgantt甘特图示例
-
示例图片:
-
代码:
<!DOCTYPE html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<script src="./dhtmlxgantt.js"></script>
<script src='https://cdn.dhtmlx.com/edge/dhtmlx.js?v=7.1.12'></script>
<script src="https://cdn.staticfile.org/moment.js/2.29.1/moment.min.js"></script>
<!-- 图片生成 -->
<script src="https://cdn.bootcss.com/html2canvas/0.5.0-beta4/html2canvas.js"></script>
<link rel='stylesheet' href='./dhtmlxgantt_material.css'>
<link rel='stylesheet' href='https://cdn.dhtmlx.com/edge/skins/terrace/dhtmlx.css?v=7.1.12'>
<script src="https://export.dhtmlx.com/gantt/api.js"></script>
<script src="https://code.jquery.com/jquery-3.3.1.min.js?v=5.2.4" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/chosen/1.8.7/chosen.jquery.js?v=5.2.4"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/chosen/1.8.7/chosen.css?v=5.2.4">
<link rel="stylesheet" href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<style type="text/css">
html,
body {
height: 100%;
padding: 0px;
margin: 0px;
overflow: hidden;
}
/* 多选框宽度问题 */
.chosen-container-multi {
width: auto !important;
min-width: 150px;
}
.gantt_grid_scale .gantt_grid_head_cell {
color: #a6a6a6;
border-top: none !important;
border-right: 1px solid #cecece !important;
}
.gantt_grid_data .gantt_cell {
border-right: 1px solid #cecece !important;
}
/** 进度条样式 */
.gantt_slider {
width: 530px;
height: 20px;
margin-left: 10px;
display: inline-block;
}
.gantt_slider input {
width: 34px;
height: 18px;
border: none;
}
.gantt_slider div:first-child,
.gantt_slider .gantt_slider_value {
display: inline-block;
vertical-align: middle;
line-height: 13px;
}
.gantt_slider .gantt_slider_value {
font-size: 15px;
color: black;
margin: 5px 10px;
}
.gantt_task_cell.week_end {
background-color: #e5e2e2;
}
.gantt_task_row.gantt_selected .gantt_task_cell.week_end {
background-color: #e5e2e2;
}
/** 日期选择器 */
.gantt-lb-datepicker {
text-align: center;
}
.gantt-lb-datepicker input {
width: 100px;
padding: 4px;
margin: 0 10px;
}
/* 网格线 */
.gantt_row {
border-bottom: 1px solid #cecece !important;
}
.gantt_task_row {
border-bottom: 1px solid #cecece !important;
}
.gantt_last_cell {
border-bottom: none !important;
}
.gantt_task_line,
.gantt_line_wrapper {
margin-top: -9px;
}
.gantt_side_content {
margin-bottom: 7px;
}
.gantt_task_link .gantt_link_arrow {
margin-top: -12px
}
.gantt_side_content.gantt_right {
bottom: 0;
}
.baseline {
position: absolute;
/* border-radius: 50px; */
opacity: 0.9;
margin-top: -7px;
height: 16px;
background: #ffc107;
/* border: 1px solid rgb(255, 153, 0); */
}
/* .gantt_grid, .gantt_task {
overflow: auto !important;
} */
</style>
</head>
<body>
<div style="height: 40px;display: flex;width: 900px;justify-content: space-between;align-items: center;" id="btn-group">
<input value="导出" type="button" onclick='exportToMSProject()' />
<input value="导入" type="button" onclick="upfile()" />
<input value="导出图片" type="button" onclick="exportImage()" />
<input type="file" style="visibility: hidden;display: none;" accept=".xml,.mpp" id="cs"></input>
<input value="新增" type="button" onclick="addTask()" />
<input value="修改" type="button" onclick="updateTask()" />
<input value="删除" type="button" onclick="deleteTask()" />
<input value="上移" type="button" onclick="upMove()" />
<input value="下移" type="button" onclick="downMove()" />
<input value="升级" type="button" onclick="outdent()" />
<input value="降级" type="button" onclick="indent()" />
<input value="撤销" type="button" onclick="undo()" />
<input value="重做" type="button" onclick="redo()" />
<input value="展开所有" type="button" onclick="expandAll()" />
<input value="折叠所有" type="button" onclick="unExpandAll()" />
<input value="清空" type="button" onclick="clearAll()" />
<input value="自动排程" type="button" onclick="autoSchedule()" />
<input value="显示/隐藏关键路径" type="button" onclick="updateCriticalPath(this)" />
</div>
<div id="gantt" style="width: 100%;height:calc(100% - 40px);"></div>
<script type="text/javascript">
// api doc address is https://docs.dhtmlx.com/gantt/api__gantt_addtasklayer.html
window.gantt = gantt
// gantt.old_calculateDuration = gantt.calculateDuration
// gantt.calculateDuration = (a, b) => gantt.old_calculateDuration(a, b) + 1
// gantt.old_calculateEndDate = gantt.calculateEndDate
// gantt.calculateEndDate = (a, b, c, d) => {
// let dateTime = gantt.old_calculateEndDate(a, b, c, d)
// if (dateTime) {
// dateTime = dateTime.setDate(dateTime.getDate());
// dateTime = new Date(dateTime);
// }
// return dateTime
// }
// 设置语言
gantt.i18n.setLocale('cn')
// 设置日期格式
gantt.config.date_format = "%Y-%m-%d";
// 天数操作
function dateTimeDay (date = new Date(), dayNum = 0) {
if (!date) return null
return new Date(date.getTime() + (dayNum * 24 * 60 * 60 * 1000))
}
// 显示任务状态
function showTaskStatus (taskData) {
if (taskData.real_start_date && taskData.real_end_date) {
const planEndMs = taskData.end_date.getTime()
const realEndMs = dateTimeDay(taskData.real_end_date, 1).getTime()
if (realEndMs > planEndMs) return '滞后'
if (realEndMs === planEndMs) return '准时'
if (realEndMs < planEndMs) return '提前'
}
return ''
}
// 设置列字段
gantt.config.columns =
[
{
"name": "text",
"tree": true,
"width": 156,
"resize": true
},
{
"name": "duration",
"label": "工期(天)",
"align": "center",
"resize": true,
"width": 70
},
{
"name": "start_date",
"label": "开始时间",
"align": "center",
"resize": true,
"width": 90
},
{
"name": "end_date",
"label": "结束时间",
"align": "center",
"resize": true,
template: obj => {
return moment(dateTimeDay(obj.end_date, -1)).format('YYYY-MM-DD')
},
"width": 90
},
{
"name": "real_start_date",
"label": "实际开始时间",
"align": "center",
"resize": true,
// editor: {
// type: "date",
// map_to: "real_start_date"
// },
"width": 90
},
{
"name": "real_end_date",
"label": "实际结束时间",
"align": "center",
// editor: {
// type: "date",
// map_to: "real_end_date"
// // },
// template: obj => {
// return obj.real_end_date ? moment(dateTimeDay(obj.real_end_date, -1)).format('YYYY-MM-DD') : ''
// },
"resize": true,
"width": 90
},
{
"name": "progress",
"label": "完成度",
"align": "center",
"resize": true,
"width": 70, template: obj => `${Math.round(obj.progress * 100, 2) + '%'}`
},
{
"name": "taskStatus",
"label": "状态",
"align": "center",
"resize": true,
"width": 70, template: obj => showTaskStatus(obj)
},
{
name: "ownerIds", label: '负责人', "align": "center", "resize": true, template: obj => (obj.ownerIds || []).join(',')
},
{
name: "memIds", label: '成员', "align": "center", "resize": true, template: obj => (obj.memIds || []).join(',')
},
{
"name": "add", // 新增按钮
"width": 44,
"min_width": 44,
"max_width": 44
}
]
const weekScaleTemplate = function (date) {
var dateToStr = gantt.date.date_to_str("%d %M");
var weekNum = gantt.date.date_to_str("(week %W)");
var endDate = gantt.date.add(gantt.date.add(date, 1, "week"), -1, "day");
return dateToStr(date) + " - " + dateToStr(endDate) + " " + weekNum(date);
};
// 右侧时间轴的表头单位,可以多个,step是步长
gantt.config.scales = [
{ unit: "month", step: 1, date: "%Y月%m月" },
// { unit: "week", step: 1, format: weekScaleTemplate },
{ unit: "day", step: 1, date: "%d日" },
{ unit: "day", step: 1, date: "周%D" },
]
// 布局,左右滚轮都独立
gantt.config.layout = {
css: "gantt_container",
cols: [
{
// width: 700,
// min_width: 500,
rows: [
{ view: "grid", scrollX: "gridScroll", scrollable: true, scrollY: "scrollVer" },
{ view: "scrollbar", id: "gridScroll" }
]
},
{ resizer: true, width: 1 },
{
rows: [
{ view: "timeline", scrollX: "scrollHor", scrollY: "scrollVer" },
{ view: "scrollbar", id: "scrollHor" }
]
},
{ view: "scrollbar", id: "scrollVer" }
]
}
gantt.plugins({
undo: true, // 允许您撤消/重做所做的更改
multiselect: true,// 多选
tooltip: true, // 提示框
auto_scheduling: true, // 自动计划
critical_path: true // 关键路径
});
// 设置开始时间和结束时间
// gantt.config.start_date = new Date(2017, 9, 25) // 0代表1月 11代表12月
// gantt.config.end_date = new Date(2022, 10, 10) // 0代表1月 11代表12月
// 表头的高度,
gantt.config.scale_height = 70;
// 行高
gantt.config.row_height = 50;
// 柱子的高度
gantt.config.bar_height = 20;
gantt.config.autoscroll = true;
// 自动类型,会根据子父级调整开始时间和结束时间,但是父级的弹出会是固定的,严重影响撤销功能
// gantt.config.auto_types = true;
gantt.config.fit_tasks = true;
// 使用工作日
gantt.config.work_time = true;
// 单位
gantt.config.duration_unit = "day";
gantt.config.reorder_grid_columns = true;
gantt.config.min_column_width = 50;
// gantt.config.auto_scheduling = true;
// gantt.config.auto_scheduling_strict = true;
// gantt.config.auto_scheduling_compatibility = true;
//弹窗标题 日期范围
gantt.templates.task_time = function (start, end, task) {
return moment(start).format('YYYY-MM-DD') + " - " + moment(dateTimeDay(end, -1)).format('YYYY-MM-DD');
};
// 多选
gantt.form_blocks["multiselect"] = {
render: function (sns) {
var height = (sns.height || "23") + "px";
var html = "<div class='gantt_cal_ltext gantt_cal_chosen gantt_cal_multiselect'" +
"style='height:" + height + ";width:auto;'><select data-placeholder='请选择负责人'" +
"class='chosen-select' multiple>";
if (sns.options) {
for (var i = 0; i < sns.options.length; i++) {
if (sns.unassigned_value !== undefined && sns.options[i].key == sns.unassigned_value) {
continue;
}
html += "<option value='" + sns.options[i].key + "'>" + sns.options[i].label + "</option>";
}
}
html += "</select></div>";
return html;
},
set_value: function (node, value, ev, sns) {
node.style.overflow = "visible";
node.parentNode.style.overflow = "visible";
node.style.display = "inline-block";
var select = $(node.firstChild);
if (value) {
value = (value + "").split(",");
select.val(value);
}
else {
select.val([]);
}
select.chosen();
if (sns.onchange) {
select.change(function () {
sns.onchange.call(this);
})
}
select.trigger('chosen:updated');
select.trigger("change");
},
get_value: function (node, ev) {
var value = $(node.firstChild).val();
//value = value ? value.join(",") : null
return value;
},
focus: function (node) {
$(node.firstChild).focus();
}
};
// 滑块
gantt.form_blocks["dhx_slider"] = {
render: function (sns) {
return '<div class="gantt_slider"><div><input type="text" readonly="true"/></div></div>';
},
set_value: function (node, value, task, data) {
if (!node._slider) {
node._slider = new dhtmlXSlider({
parent: node,
size: 270,
max: 100,
tooltip: true,
step: data.step ? data.step : 1,
skin: data.skin ? data.skin : ''
});
node._count = document.createElement('div');
node._count.className = "gantt_slider_value";
node.appendChild(node._count);
var slider_id = node._slider.attachEvent("onChange", function (newValue, sliderObj) {
node._count.innerHTML = newValue + "%";
});
var id = gantt.attachEvent("onAfterLightbox", function () {
node._slider.detachEvent(slider_id);
node._slider.unload();
node._slider = null;
this.detachEvent(id);
});
}
if (task.progress || task.progress == 0) {
node._slider.setValue(parseInt(task.progress * 100));
node._count.innerHTML = parseInt(task.progress * 100) + "%";
}
},
get_value: function (node, task) {
return node._slider ? node._slider.getValue() / 100 : 0;
},
focus: function (node) {
}
};
// jq日期选择器
// gantt.config.editor_types.custom_datepicker_editor = {
// show: function (id, column, config, placeholder) {
// placeholder.innerHTML = "<div><input type='text' id='datepicker' name='" +
// column.name + "'></div>";
// $("#datepicker").datepicker({
// dateFormat: "yy-mm-dd"
// });
// },
// hide: function (node) {
// $("#datepicker").datepicker("destroy");
// },
// set_value: function (value, id, column, node) {
// $("#datepicker").datepicker("setDate", value);
// },
// get_value: function (id, column, node) {
// return $("#datepicker").datepicker("getDate");
// },
// is_changed: function (value, id, column, node) {
// return (+$("#datepicker").datepicker("getDate") !== +value);
// },
// is_valid: function (value, id, column, node) {
// return !(isNaN(+$("#datepicker").datepicker("getDate")))
// },
// save: function (id, column, node) {
// },
// focus: function (node) {
// }
// };
//弹出层
gantt.config.lightbox.sections = [
{ name: "text", height: 70, map_to: "text", type: "textarea", focus: true, width: 200 },
// { name: "type", type: "typeselect", map_to: "type" },
{ name: "time", height: 45, map_to: "auto", type: "datepicker2" },
{ name: "real_time", height: 30, map_to: { start_date: "real_start_date", end_date: "real_end_date" }, type: "datepicker", time_format: ["%Y", "%m", "%d"] },
// {name: 'time', type: 'duration', map_to: 'auto'},
// {
// name: "color", height: 30, map_to: "color", type: "select", options: [
// { key: "#3db9d3", label: "蓝色" },
// { key: "#33cc33", label: "绿色" },
// { key: "#FF8247", label: "橙色" },
// { key: "#ff6633", label: "红色" }
// ]
// },
{
name: "ownerIds", type: "multiselect", options: [
{ key: '张三', label: "张三" },
{ key: '李四', label: "李四" }
], width: 200,
map_to: "ownerIds"
},
{
name: "memIds", type: "multiselect", options: [
{ key: '张三', label: "张三" },
{ key: '李四', label: "李四" }
], width: 200,
map_to: "memIds"
},
{ name: "progress", type: "dhx_slider", map_to: "progress", step: 5 },
{ name: "description", height: 70, map_to: "description", type: "textarea" },
];
gantt.config.lightbox.project_sections = [
{ name: "text", height: 70, map_to: "text", type: "textarea", focus: true, width: 200 },
// { name: "type", type: "typeselect", map_to: "type" },
{ name: "time", type: "duration", readonly: true, map_to: "auto" },
{ name: "real_time", height: 30, map_to: { start_date: "real_start_date", end_date: "real_end_date" }, type: "datepicker", time_format: ["%Y", "%m", "%d"] },
{
name: "ownerIds", type: "multiselect", options: [
{ key: '张三', label: "张三" },
{ key: '李四', label: "李四" }
], width: 200,
map_to: "ownerIds"
},
{
name: "memIds", type: "multiselect", options: [
{ key: '张三', label: "张三" },
{ key: '李四', label: "李四" }
], width: 200,
map_to: "memIds"
},
];
gantt.config.lightbox.milestone_sections = [
{ name: "description", height: 70, map_to: "text", type: "textarea", focus: true },
// { name: "type", type: "typeselect", map_to: "type" },
{ name: "time", type: "duration", single_date: true, map_to: "auto" },
{
name: "ownerIds", type: "multiselect", options: [
{ key: '张三', label: "张三" },
{ key: '李四', label: "李四" }
], width: 200,
map_to: "ownerIds"
},
{
name: "memIds", type: "multiselect", options: [
{ key: '张三', label: "张三" },
{ key: '李四', label: "李四" }
], width: 200,
map_to: "memIds"
},
];
gantt.locale.labels.section_text = "任务名";
gantt.locale.labels.section_time = "计划时间";
gantt.locale.labels.section_real_time = "实际时间";
gantt.locale.labels.section_description = "描述";
gantt.locale.labels.section_ownerIds = "负责人";
gantt.locale.labels.section_memIds = "成员";
gantt.locale.labels.section_progress = "进度";
// 时间线class控制
gantt.templates.timeline_cell_class = function (task, date) {
if (!gantt.isWorkTime(date))
return "week_end";
return "";
};
// 提示框模板
gantt.templates.tooltip_text = function (start, end, task) {
return `任务名:${task.text}<br>开始时间:${moment(start).format('YYYY-MM-DD')}<br>结束时间:${moment(dateTimeDay(end, -1)).format('YYYY-MM-DD')}`
}
// 自定义时间轴上的柱子文本
gantt.templates.task_text = function (start, end, task) {
return `${task.text}-${Math.round(task.progress * 100, 2) + '%'}`;
};
// 实际时间的轴
gantt.addTaskLayer(function draw_planned (task) {
if (task.real_start_date && task.real_end_date) {
var sizes = gantt.getTaskPosition(task, task.real_start_date, dateTimeDay(task.real_end_date, 1));
var el = document.createElement('div');
el.className = 'baseline';
el.style.left = sizes.left + 'px';
el.style.width = sizes.width + 'px';
el.style.top = sizes.top + gantt.config.bar_height + 16 + 'px';
return el;
}
return false;
});
//获取所有节点
// gantt.getDatastore("task").pull
// 这个比上面的好用
// gantt.serialize().data
// gantt.setWorkTime({ day: 0, hours: [0, 24] });
// gantt.setWorkTime({ day: 1, hours: [0, 24] });
// gantt.setWorkTime({ day: 2, hours: [0, 24] });
// gantt.setWorkTime({ day: 3, hours: [0, 24] });
// gantt.setWorkTime({ day: 4, hours: [0, 24] });
// gantt.setWorkTime({ day: 5, hours: [0, 24] });
// gantt.setWorkTime({ day: 6, hours: [0, 24] });
gantt.attachEvent("onTaskLoading", function (task) {
console.log('onTaskLoading', task)
task.real_start_date = gantt.date.parseDate(task.real_start_date, "xml_date");
task.real_end_date = gantt.date.parseDate(task.real_end_date, "xml_date");
return true;
});
gantt.init("gantt");
// 数据加载
gantt.parse({
data: [
{
id: 1, text: "甘特图实现", start_date: "2022-10-25",
duration: 10, progress: 0, open: true, ownerIds: ['张三', '李四'], real_start_date: null, real_end_date: null
},
{
id: 2, text: "需求策划", start_date: "2022-10-26",
duration: 2, progress: 0, parent: 1, ownerIds: ['张三'], real_start_date: '2022-10-26', real_end_date: '2022-10-27'
},
{
id: 3, text: "A功能实现", start_date: "2022-10-28",
duration: 6, progress: 0, parent: 1, ownerIds: ['李四'], real_start_date: null, real_end_date: null
},
{
id: 4, text: "B功能实现", start_date: "2022-10-29",
duration: 6, progress: 0, parent: 1, ownerIds: ['李四'], real_start_date: null, real_end_date: null
}
],
links: [
{ id: 2, source: 2, target: 3, type: "0" },
{ id: 3, source: 2, target: 4, type: "0" }
]
});
/**
{key:"#3db9d3", label: "蓝色"},
{key:"#33cc33", label: "绿色"},
{key:"#FF8247", label: "橙色"},
{key:"#ff6633", label: "红色"}
*/
//添加后触发
gantt.attachEvent("onAfterTaskAdd", function (id, item) {
console.log("添加后触发", id, item);
});
//移动进度后触发,同时也会出发【修改任务后触发】
// gantt.attachEvent("onAfterTaskDrag", function (id, mode, e) {
// console.log("移动进度后触发");
// });
//移动任务后触发,同时也会出发【修改任务后触发】
// gantt.attachEvent("onAfterTaskMove", function (id, parent, tindex) {
// console.log("移动任务后触发");
// });
//删除任务后触发
gantt.attachEvent("onAfterTaskDelete", function (id, item) {
console.log("删除任务后触发");
});
//修改任务后触发
gantt.attachEvent("onAfterTaskUpdate", function (id, item) {
console.log("修改任务后触发", id, item);
});
//保存验证
gantt.attachEvent("onLightboxSave", function (id, item) {
if (!item.text) {
gantt.message({ type: "error", text: "请填写任务名!", expire: 2000 });
return false;
}
if (!item.start_date || !item.end_date) {
gantt.message({ type: "error", text: "请填写计划时间!", expire: 2000 });
return false;
}
return true;
});
const fileUploader = document.getElementById('cs');
fileUploader.addEventListener('change', (event) => {
const files = event.target.files;
gantt.importFromMSProject({
data: files[0],
callback: function (project) {
if (project) {
gantt.clearAll();
if (project.config.duration_unit) {
gantt.config.duration_unit = project.config.duration_unit;
}
gantt.parse(project.data);
}
}
});
});
// 日期选择器
(function () {
const startDatepicker = (node) => $(node).find("input[name='start']");
const endDateInput = (node) => $(node).find("input[name='end']");
gantt.form_blocks["datepicker"] = {
render: (sns) => {
const height = sns.height || 45;
return "<div class='gantt-lb-datepicker' style='height:" + height + "px;'>" +
"<input type='text' name='start'> - " +
"<input type='text' name='end'>" +
"</div>";;
},
set_value: (node, value, task, section) => {
value = {
start_date: task[section.map_to.start_date] || null,
end_date: task[section.map_to.end_date] || null
}
const datepickerConfig = {
format: 'yyyy-mm-dd',
autoclose: true,
container: gantt.$container
};
startDatepicker(node).datepicker(datepickerConfig);
startDatepicker(node).datepicker('setDate',
value ? value.start_date : null
);
endDateInput(node).datepicker(datepickerConfig);
endDateInput(node).datepicker('setDate',
value ? value.end_date : null
);
// startDatepicker(node).datepicker().on('changeDate', function (e) {
// const endValue = endDateInput(node).datepicker('getDate');
// const startValue = startDatepicker(node).datepicker('getDate');
// if (startValue && endValue) {
// if (endValue.valueOf() <= startValue.valueOf()) {
// endDateInput(node).datepicker('setDate',
// gantt.calculateEndDate({
// start_date: startValue, duration: 1, task: task
// })
// );
// }
// }
// });
},
get_value: (node, task, section) => {
const start = startDatepicker(node).datepicker('getDate');
let end = endDateInput(node).datepicker('getDate');
return {
start_date: start,
end_date: end,
// duration: task.duration
};
// if (end.valueOf() <= start.valueOf()) {
// end = gantt.calculateEndDate({
// start_date: start,
// duration: 1,
// task: task
// });
// }
// if (task.start_date && task.end_date) {
// task.start_date = start;
// task.end_date = end;
// }
// task.duration = gantt.calculateDuration(task);
// return {
// start_date: start,
// end_date: end,
// duration: task.duration
// };
},
focus: (node) => {
}
}
gantt.form_blocks["datepicker2"] = {
render: (sns) => {
const height = sns.height || 45;
return "<div class='gantt-lb-datepicker' style='height:" + height + "px;'>" +
"<input type='text' name='start'> - " +
"<input type='text' name='end'>" +
"</div>";;
},
set_value: (node, value, task, section) => {
value = {
start_date: task.start_date || null,
end_date: task.end_date ? dateTimeDay(task.end_date, -1) : null
}
const datepickerConfig = {
format: 'yyyy-mm-dd',
autoclose: true,
container: gantt.$container
};
startDatepicker(node).datepicker(datepickerConfig);
startDatepicker(node).datepicker('setDate',
value ? value.start_date : task.start_date
);
endDateInput(node).datepicker(datepickerConfig);
endDateInput(node).datepicker('setDate',
value ? value.end_date : task.end_date
);
startDatepicker(node).datepicker().on('changeDate', function (e) {
const endValue = endDateInput(node).datepicker('getDate');
const startValue = startDatepicker(node).datepicker('getDate');
if (startValue && endValue) {
if (endValue.valueOf() <= startValue.valueOf()) {
endDateInput(node).datepicker('setDate',
gantt.calculateEndDate({
start_date: startValue, duration: 1, task: task
})
);
}
}
});
},
get_value: (node, task, section) => {
const start = startDatepicker(node).datepicker('getDate');
let end = endDateInput(node).datepicker('getDate');
if (!start || !end) {
gantt.message({ type: "error", text: "请填写计划时间!", expire: 2000 });
throw new Error()
}
if (end.valueOf() <= start.valueOf()) {
end = gantt.calculateEndDate({
start_date: start,
duration: 1,
task: task
});
}
if (task.start_date && task.end_date) {
task.start_date = start;
task.end_date = dateTimeDay(end, 1);
}
task.duration = gantt.calculateDuration(task);
return {
start_date: start,
end_date: dateTimeDay(end, 1),
duration: task.duration
};
},
focus: (node) => {
}
}
})();
// indent-outdent implementation
(function () {
function shiftTask (task_id, direction) {
var task = gantt.getTask(task_id);
task.start_date = gantt.date.add(task.start_date, direction, "day");
task.end_date = gantt.calculateEndDate(task.start_date, task.duration);
gantt.updateTask(task.id);
}
var actions = {
undo: function () {
gantt.ext.undo.undo();
},
redo: function () {
gantt.ext.undo.redo();
},
indent: function indent (task_id) {
var prev_id = gantt.getPrevSibling(task_id);
while (gantt.isSelectedTask(prev_id)) {
var prev = gantt.getPrevSibling(prev_id);
if (!prev) break;
prev_id = prev;
}
if (prev_id) {
var new_parent = gantt.getTask(prev_id);
gantt.moveTask(task_id, gantt.getChildren(new_parent.id).length, new_parent.id);
// new_parent.type = gantt.config.types.project;
new_parent.$open = true;
gantt.updateTask(task_id);
gantt.updateTask(new_parent.id);
return task_id;
}
return null;
},
outdent: function outdent (task_id, initialIndexes, initialSiblings) {
var cur_task = gantt.getTask(task_id);
var old_parent = cur_task.parent;
if (gantt.isTaskExists(old_parent) && old_parent != gantt.config.root_id) {
var index = gantt.getTaskIndex(old_parent) + 1;
var prevSibling = initialSiblings[task_id].first;
if (gantt.isSelectedTask(prevSibling)) {
index += (initialIndexes[task_id] - initialIndexes[prevSibling]);
}
gantt.moveTask(task_id, index, gantt.getParent(cur_task.parent));
if (!gantt.hasChild(old_parent))
// gantt.getTask(old_parent).type = gantt.config.types.task;
gantt.updateTask(task_id);
gantt.updateTask(old_parent);
return task_id;
}
return null;
},
del: function (task_id) {
if (gantt.isTaskExists(task_id)) gantt.deleteTask(task_id);
return task_id;
},
moveForward: function (task_id) {
shiftTask(task_id, 1);
},
moveBackward: function (task_id) {
shiftTask(task_id, -1);
}
};
var cascadeAction = {
indent: true,
outdent: true,
del: true
};
var singularAction = {
undo: true,
redo: true
};
gantt.performAction = function (actionName) {
var action = actions[actionName];
if (!action)
return;
if (singularAction[actionName]) {
action();
return;
}
gantt.batchUpdate(function () {
// need to preserve order of items on indent/outdent,
// remember order before changing anything:
var indexes = {};
var siblings = {};
gantt.eachSelectedTask(function (task_id) {
gantt.ext.undo.saveState(task_id, "task");
indexes[task_id] = gantt.getTaskIndex(task_id);
siblings[task_id] = {
first: null
};
var currentId = task_id;
while (gantt.isTaskExists(gantt.getPrevSibling(currentId)) && gantt.isSelectedTask(gantt.getPrevSibling(currentId))) {
currentId = gantt.getPrevSibling(currentId);
}
siblings[task_id].first = currentId;
});
var updated = {};
gantt.eachSelectedTask(function (task_id) {
if (cascadeAction[actionName]) {
if (!updated[gantt.getParent(task_id)]) {
var updated_id = action(task_id, indexes, siblings);
updated[updated_id] = true;
} else {
updated[task_id] = true;
}
} else {
action(task_id, indexes);
}
});
});
};
})();
// recalculate progress of summary tasks when the progress of subtasks changes
(function dynamicProgress () {
function calculateSummaryProgress (task) {
if (task.type != gantt.config.types.project)
return task.progress;
var totalToDo = 0;
var totalDone = 0;
gantt.eachTask(function (child) {
if (child.type != gantt.config.types.project) {
totalToDo += child.duration;
totalDone += (child.progress || 0) * child.duration;
}
}, task.id);
if (!totalToDo) return 0;
else return totalDone / totalToDo;
}
function refreshSummaryProgress (id, submit) {
if (!gantt.isTaskExists(id))
return;
var task = gantt.getTask(id);
var newProgress = calculateSummaryProgress(task);
if (newProgress !== task.progress) {
task.progress = newProgress;
if (!submit) {
gantt.refreshTask(id);
} else {
gantt.updateTask(id);
}
}
if (!submit && gantt.getParent(id) !== gantt.config.root_id) {
refreshSummaryProgress(gantt.getParent(id), submit);
}
}
gantt.attachEvent("onParse", function () {
gantt.eachTask(function (task) {
task.progress = calculateSummaryProgress(task);
});
});
gantt.attachEvent("onAfterTaskUpdate", function (id) {
refreshSummaryProgress(gantt.getParent(id), true);
});
gantt.attachEvent("onTaskDrag", function (id) {
refreshSummaryProgress(gantt.getParent(id), false);
});
gantt.attachEvent("onAfterTaskAdd", function (id) {
refreshSummaryProgress(gantt.getParent(id), true);
});
gantt.attachEvent("onBeforeTaskDelete", function (id, item) {
//any custom logic here
return true;
});
(function () {
var idParentBeforeDeleteTask = 0;
gantt.attachEvent("onBeforeTaskDelete", function (id) {
idParentBeforeDeleteTask = gantt.getParent(id);
});
gantt.attachEvent("onAfterTaskDelete", function () {
refreshSummaryProgress(idParentBeforeDeleteTask, true);
});
})();
})();
</script>
</body>
<script>
// 导入
function upfile () {
document.getElementById('cs').click()
}
function exportImage () {
// gantt.exportToPNG()
domToImage(document.getElementById('gantt'), (dataURL)=> {
var a = document.createElement('a')// 创建一个a标签 用来下载
a.download = 'gantt.png' // 设置下载的图片名称
var event = new MouseEvent('click')// 增加一个点击事件
a.href = dataURL// 此处的url为base64格式的图片资源
a.dispatchEvent(event) // 触发a的单击事件 即可完成下载
})
}
// 撤销
function undo () {
gantt.undo();
}
// 重做
function redo () {
gantt.redo();
}
// 展开所有任务:
function expandAll () {
gantt.eachTask(function (task) {
task.$open = true;
});
gantt.render();
}
// 折叠所有任务:
function unExpandAll () {
gantt.eachTask(function (task) {
task.$open = false;
});
gantt.render();
}
// 新增任务
function addTask () {
const id = gantt.getSelectedId()
gantt.createTask({
id: new Date().getTime(),
text: "新任务",
start_date: moment(new Date()).format('YYYY-MM-DD'),
duration: 1,
}, id);
}
// 删除任务
function deleteTask () {
const id = gantt.getSelectedId()
if (id === null) return
const info = gantt.getTask(id)
gantt.confirm({
text: `是否删除任务[${info.text}]`,
ok: "是",
cancel: "否",
callback: function (result) {
if (result) {
gantt.deleteTask(id);
}
}
});
}
// 向上移动
function upMove () {
const id = gantt.getSelectedId()
if (id === null) return
const prevId = gantt.getPrevSibling(gantt.getSelectedId())
if (prevId === null) return
gantt.moveTask(id, gantt.getTaskIndex(prevId), gantt.getParent(id))
}
// 向下移动
function downMove () {
const id = gantt.getSelectedId()
if (id === null) return
const nextId = gantt.getNextSibling(gantt.getSelectedId())
if (nextId === null) return
gantt.moveTask(id, gantt.getTaskIndex(nextId), gantt.getParent(id))
}
// 打开修改的弹窗
function updateTask () {
const id = gantt.getSelectedId()
id !== null && gantt.showLightbox(id);
}
// 降级
function indent () {
gantt.performAction('indent')
}
// 升级
function outdent () {
gantt.performAction('outdent')
}
// 清空
function clearAll () {
gantt.clearAll()
}
// 自动计划
function autoSchedule () {
gantt.autoSchedule()
}
// 关键路径
function updateCriticalPath (toggle) {
toggle.enabled = !toggle.enabled;
if (toggle.enabled) {
// toggle.innerHTML = "Hide Critical Path";
gantt.config.highlight_critical_path = true;
} else {
// toggle.innerHTML = "Show Critical Path";
gantt.config.highlight_critical_path = false;
}
gantt.render();
}
// 导出project的xml文件
function exportToMSProject () {
gantt.exportToMSProject({
// project: {
// HoursPerDay: function () {
// return 24;
// },
// MinutesPerDay: function () {
// return 24 * 60;
// }
// },
// tasks: {
// 'Manual': function (task) {
// return '1'
// },
// 'Start': function (task) {
// return task.start_date
// },
// 'Stop': function (task) {
// return task.end_date
// },
// 'ActualStart': function (task) {
// return task.real_start_date
// },
// 'ActualFinish': function (task) {
// return task.real_end_date
// }
// }
});
}
function domToImage(dom, callback) {
if (dom) {
setTimeout(() => {
$('body,html').scrollTop(0)
function getPixelRatio (context) { // 获取设备的pixel ratio
const backingStore = context.backingStorePixelRatio ||
context.webkitBackingStorePixelRatio ||
context.mozBackingStorePixelRatio ||
context.msBackingStorePixelRatio ||
context.oBackingStorePixelRatio ||
context.backingStorePixelRatio || 1
return (window.devicePixelRatio || 1) / backingStore
}
const shareContent = dom // 需要绘制的部分的 (原生)dom 对象 ,注意容器的宽度不要使用百分比,使用固定宽度,避免缩放问题
shareContent.style.width = shareContent.offsetWidth * 2
const width = shareContent.offsetWidth // 获取(原生)dom 宽度
const height = shareContent.offsetHeight // 获取(原生)dom 高
const offsetTop = shareContent.offsetTop // 元素距离顶部的偏移量
const canvas = document.createElement('canvas') // 创建canvas 对象
const context = canvas.getContext('2d')
const scaleBy = getPixelRatio(context) // 获取像素密度的方法 (也可以采用自定义缩放比例)
canvas.width = width * scaleBy // 这里 由于绘制的dom 为固定宽度,居中,所以没有偏移
canvas.height = (height + offsetTop) * scaleBy // 注意高度问题,由于顶部有个距离所以要加上顶部的距离,解决图像高度偏移问题
context.scale(scaleBy, scaleBy)
// var scrollY = document.getElementsByClassName('chart-box-main')[0].scrollWidth
// var scrollX = document.getElementsByClassName('chart-box-main')[0].scrollHeight
const opts = {
// backgroundColor: 'transparent',
// allowTaint: true, // 允许加载跨域的图片 (使用这个会报错)
useCORS: true, // 允许加载跨域图片
tainttest: true, // 检测每张图片都已经加载完成
// scale: scaleBy, // 添加的scale 参数
// canvas: canvas, // 自定义 canvas
logging: true, // 日志开关,发布的时候记得改成false
width: width, // dom 原始宽度
height: height // dom 原始高度,
// scrollY: -scrollY,
// scrollX: -scrollX
}
html2canvas(shareContent, opts).then(function (canvas) {
const dataURL = canvas.toDataURL('image/jpeg', 1.0)
if (callback) callback(dataURL)
})
}, 500)
}
}
</script>