效果图
实现功能
- 年月日维度调整
- 回退恢复
- 删除
- 前进后退
- 全屏
- 等等…
代码
GanttComponent.vue
<template>
<div ref="ganttContainer" style="width: 100%; height: calc(100% - 48px)"></div>
</template>
<script>
import { gantt } from "dhtmlx-gantt";
import 'dhtmlx-gantt/codebase/sources/skins/dhtmlxgantt_material.css'
export default {
props: {
tasks: {
type: Object,
default() {
return {
data: [
],
collections: {
links: [],
}
};
},
},
config: {
type: Object,
default() {
return {};
},
}
},
data() {
return {
styleName: "dhtmlxgantt_material", // 默认样式文件名称
performAction: null,
ganttEvent: {}
};
},
watch: {
config: {
immediate: false,
deep: true,
handler(value) {
gantt.config.scales = this.setScale(value.dataScale);
gantt.render();
},
},
},
created() {
this.start = performance.now();
},
mounted: function () {
this.init();
},
methods: {
init() {
// 初始化事件
this.initGanttEvents();
// 配置
this.setConfig();
this.performAction = this.handleAction()
// 初始化
gantt.init(this.$refs.ganttContainer);
// 解析数据
gantt.parse(this.tasks);
// 初始化数据处理
this.initDataProcessor();
this.end = performance.now();
console.log("操作执行时间:", this.end - this.start, "毫秒");
},
// 配置信息
setConfig() {
// -------1、左侧区域--------
gantt.config.grid_width = 300; //左侧宽
gantt.config.autofit = false; //左侧是否自适应
gantt.config.date_grid = "%F %d"; //左侧日期数据展示格式
// --------1.1左侧区域头部-------
// 设置表头
this.setColumn();
// 拖拽排序
gantt.config.order_branch = true;
gantt.config.order_branch_free = true;
gantt.config.resize_rows = true;
gantt.config.scales = this.setScale(this.config.dataScale);
// gantt.config.row_height = 50; //进度条容器高
// 里程碑文本居右
gantt.templates.rightside_text = function (start, end, task) {
if (task.type == gantt.config.types.milestone) {
return task.text;
}
return "";
};
// ===========通用配置(国际化,日期格式等)=======
this.managePlugins();
gantt.i18n.setLocale("cn");
// 日期格式
gantt.config.date_format = "%Y-%m-%d";
gantt.config.readonly = false; //只读
gantt.config.fit_tasks = true; //自动调整图表坐标轴区间用于适配task的长度
gantt.config.wide_form = false; // 弹窗宽
},
// 设置右侧头部展示日期方式
setScale(value) {
gantt.config.scale_height = 100; //设置时间刻度的高度和网格的标题
const weekScaleTemplate = function (date) {
// 可以时使用dayjs 处理返回
const dateToStr = gantt.date.date_to_str("%d");
const mToStr = gantt.date.date_to_str("%M");
const endDate = gantt.date.add(
gantt.date.add(date, 1, "week"),
-1,
"day"
);
// 处理一下月份
return `${dateToStr(date)} 号 - ${dateToStr(endDate)} 号 (${mToStr(
date
)})`;
};
const daysStyle = function (date) {
if (date.getDay() === 0 || date.getDay() === 6) {
return "weekend";
}
return "";
};
switch (value) {
case "Days":
return [
{ unit: "week", step: 1, date: "%Y年 %W周" },
{ unit: "day", step: 1, date: "%m-%d", css: daysStyle },
];
case "Months":
return [
{ unit: "month", step: 1, date: "%M" },
{ unit: "week", step: 1, date: "%W周" },
];
case "Years":
return [
{ unit: "year", step: 1, date: "%Y年" },
{ unit: "month", step: 1, date: "%M" },
];
case "Week":
return [
{ unit: "year", step: 1, date: "%Y" },
{ unit: "week", step: 1, format: weekScaleTemplate },
{ unit: "day", step: 1, date: "%D", css: daysStyle },
];
default:
return {};
}
},
// 自定义表格列
setColumn() {
// gantt.config.columns = [
// {
// name: "text",
// label: "里程碑节点",
// width: 280,
// tree: true,
// template: function (obj) {
// return `节点:${obj.text}`; // 通过 template 回调可以指定返回内容值
// },
// },
// ];
},
managePlugins() {
// 插件
gantt.plugins({
click_drag: true,
drag_timeline: true, // 拖动图
marker: true, // 时间标记
fullscreen: true, // 全屏
tooltip: true, // 鼠标经过时信息
undo: true, // 允许撤销
multiselect: true, //eachSelectedTask可用
auto_scheduling: true, //自动调度
quick_info: false, //进度条点击展示快捷面板
});
// auto_scheduling管理
gantt.config.work_time = true; //自动调度须设置一下
gantt.config.min_column_width = 60;
gantt.config.auto_scheduling = true;
gantt.config.auto_scheduling_strict = true;
// multiselect(如果不设置会重复触发请求)
gantt.config.drag_multiple = false;
gantt.config.multiselect = false;
// 基线
var dateToStr = gantt.date.date_to_str(gantt.config.task_date);
let today = this.getEndOfDate(); // getEndOfDate 为获取今天结束时间的方法
gantt.addMarker({
start_date: today,
css: "status_line",
text: "今日",
});
var start = new Date(1907, 9, 9);
gantt.addMarker({
start_date: start,
css: "projectStartDate",
text: "开始时间",
title: "开始时间: " + dateToStr(start),
});
// drag
gantt.config.drag_links = true; //可连线
gantt.config.drag_progress = true; // 进度条可拖拽
// tooltip
// 自定义tooltip内容
gantt.templates.tooltip_text = function (start, end, task) {
const t = gantt;
const output = `<b>里程碑:</b>${task.text
}<br/><b>计划开始时间:</b>${t.templates.tooltip_date_format(
start
)}<br/><b>计划结束时间:</b>${t.templates.tooltip_date_format(end)}`;
return output;
};
gantt.ext.fullscreen.getFullscreenElement = function () {
console.log(document.getElementById("main-gantt"))
return document.getElementById("main-gantt");
}
},
// 事件监听
initGanttEvents: function () {
if (!gantt.$_eventsInitialized) {
// 选中
this.ganttEvent.onTaskSelected = gantt.attachEvent("onTaskSelected", (id) => {
let task = gantt.getTask(id);
this.$emit("task-selected", task);
});
this.ganttEvent.onTaskIdChange = gantt.attachEvent("onTaskIdChange", (id, new_id) => {
if (gantt.getSelectedId() == new_id) {
let task = gantt.getTask(new_id);
this.$emit("task-selected", task);
}
});
// 线条click
this.ganttEvent.onLinkClick = gantt.attachEvent("onLinkClick", function (id) {
var link = this.getLink(id),
src = this.getTask(link.source),
trg = this.getTask(link.target),
types = this.config.links;
var first = "",
second = "";
switch (link.type) {
case types.finish_to_start:
first = "finish";
second = "start";
break;
case types.start_to_start:
first = "start";
second = "start";
break;
case types.finish_to_finish:
first = "finish";
second = "finish";
break;
case types.start_to_finish:
first = "start";
second = "finish";
break;
}
gantt.message(
"Must " +
first +
" <b>" +
src.text +
"</b> to " +
second +
" <b>" +
trg.text +
"</b>"
);
});
// 任务添加完成后钩子
this.ganttEvent.onAfterTaskAdd = gantt.attachEvent("onAfterTaskAdd", function (id, item) {
console.log(id, item);
});
// 任务删除后钩子
this.ganttEvent.onAfterTaskDelete = gantt.attachEvent("onAfterTaskDelete", function (id, item) {
console.log(id, item);
});
// 修改任务
this.ganttEvent.onAfterTaskUpdate = gantt.attachEvent("onAfterTaskUpdate", (id, data) => {
console.log(id, data);
});
// 监听进度拖拽事件
this.ganttEvent.onTaskDrag = gantt.attachEvent("onTaskDrag", function (id, mode, e) {
console.log('66666', id, mode, e)
return true;
});
this.ganttEvent.onBeforeTaskDrag = gantt.attachEvent("onBeforeTaskDrag", function (id, mode, e) {
console.log(id, mode, e)
return true;
});
// 移动项目
this.ganttEvent.onAfterTaskDrag = gantt.attachEvent("onAfterTaskDrag", function (id, mode, task, original) {
console.log(id, mode, task, original)
return true
});
// 用户完成拖动并释放鼠标
this.ganttEvent.onAfterTaskChanged = gantt.attachEvent("onAfterTaskChanged", (id, mode, task) => {
console.log(id, mode, task);
});
// 删除连接项目关系
this.ganttEvent.onAfterLinkDelete = gantt.attachEvent("onAfterLinkDelete", (id, item) => {
console.log(id, item);
});
// 修改连接项目关系
this.ganttEvent.onAfterLinkUpdate = gantt.attachEvent("onAfterLinkUpdate", (id, item) => {
console.log(id, item);
});
// 新增连接项目关系
this.ganttEvent.onBeforeLinkAdd = gantt.attachEvent("onBeforeLinkAdd", (id, item) => {
console.log(id, item);
});
// 弹窗打开前钩子(可禁用自带编辑弹窗)
this.ganttEvent.onBeforeLightbox = gantt.attachEvent(
"onBeforeLightbox",
function (id) {
console.log(id);
return true; // 返回 false
},
{}
);
// 任务双击钩子
this.ganttEvent.onTaskDblClick = gantt.attachEvent(
"onTaskDblClick",
function (id, e) {
console.log("id", id, e);
return true;
},
{}
);
// 展示tooltip
this.ganttEvent.onGanttReady = gantt.attachEvent("onGanttReady", function () {
var tooltips = gantt.ext.tooltips;
tooltips.tooltip.setViewport(gantt.$task_data);
});
this.ganttEvent.onBeforeTaskAutoSchedule = gantt.attachEvent("onBeforeTaskAutoSchedule", function (task, predecessor) {
// 在此处执行自定义操作
console.log("Before auto-schedule for task:", task.text);
console.log("Predecessor:", predecessor);
// 返回 true 表示允许自动调度,返回 false 则取消自动调度
return true;
});
gantt.$_eventsInitialized = true;
}
},
// 数据变化监听
initDataProcessor: function () {
if (!gantt.$_dataProcessorInitialized) {
gantt.createDataProcessor((entity, action, data, id) => {
this.$emit(`${entity}-updated`, id, action, data);
});
gantt.$_dataProcessorInitialized = true;
}
},
getEndOfDate() {
const today = new Date();
// 将日期设置为当天的开始
today.setHours(23);
today.setMinutes(59);
today.setSeconds(59);
today.setMilliseconds(999);
return today;
},
// 进度条前进后退
shiftTask(task_id, direction) {
const 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);
},
handleAction() {
const that = this
const actions = {
undo: function () {
gantt.ext.undo.undo();
},
redo: function () {
gantt.ext.undo.redo();
},
del: function (task_id) {
if (gantt.isTaskExists(task_id)) gantt.deleteTask(task_id);
return task_id;
},
moveForward: function (task_id) {
that.shiftTask(task_id, 1);
},
moveBackward: function (task_id) {
that.shiftTask(task_id, -1);
}
};
const cascadeAction = {
del: true
};
const 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:
const indexes = {};
const 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
};
let currentId = task_id;
while (gantt.isTaskExists(gantt.getPrevSibling(currentId)) && gantt.isSelectedTask(gantt.getPrevSibling(currentId))) {
currentId = gantt.getPrevSibling(currentId);
}
siblings[task_id].first = currentId;
});
const updated = {};
gantt.eachSelectedTask(function (task_id) {
if (cascadeAction[actionName]) {
if (!updated[gantt.getParent(task_id)]) {
const updated_id = action(task_id, indexes, siblings);
updated[updated_id] = true;
} else {
updated[task_id] = true;
}
} else {
action(task_id, indexes);
}
});
});
};
return gantt.performAction
}
},
destroyed() {
// 销毁gantt事件
for (let i in this.ganttEvent) {
gantt.detachEvent(this.ganttEvent[i])
}
gantt.ext.tooltips.tooltip.hide();
}
};
</script>
<style>
.weekend {
background: #f4f7f4 !important;
}
.gantt_selected .weekend {
background: #fff3a1 !important;
}
.projectStartDate,
.projectEndDate {
background-color: #0dd1eb !important;
}
.gantt_row_project {
font-weight: bold;
}
</style>
index.vue
<template>
<div class="container" id="main-gantt">
<div class="tools-list">
<div class="tool-button">{{ "仅供学习" }} <a
href="https://gitee.com/lht1132950411/blog/blob/master/examples/Dhtmlx_gantt.zip">资源地址,感谢提供包的大佬</a></div>
<div class="data-scale">
<a-select v-model="config.dataScale" style="width: 50px">
<a-select-option value="Days">
天
</a-select-option>
<a-select-option value="Week">
周
</a-select-option>
<a-select-option value="Months">
月
</a-select-option>
<a-select-option value="Years">
年
</a-select-option>
</a-select>
</div>
<a-button title="回退" @click="undo" :loading="undoLoading" class="tool-button">
<a-icon type="undo" v-if="!undoLoading" />
</a-button>
<a-button title="重做" @click="redo" :loading="redoLoading" class="tool-button">
<a-icon type="redo" v-if="!redoLoading" />
</a-button>
<a-button title="删除" @click="del" :loading="delLoading" class="tool-button">
<a-icon type="delete" v-if="!delLoading" />
</a-button>
<a-button-group class="tool-button">
<a-button title="左移" @click="moveBackward" :loading="moveBackwardLoading"> <a-icon type="left"
v-if="!moveBackwardLoading" /></a-button>
<a-button title="右移" @click="moveForward" :loading="moveForwardLoading"> <a-icon type="right"
v-if="!moveForwardLoading" /> </a-button>
</a-button-group>
<a-button title="全屏" @click="fullscreen" class="tool-button">
<a-icon type="fullscreen" />
</a-button>
</div>
<!-- <a-spin :spinning="spinning">
<div class="spin-content">
<GanttComponent ref="gantt" :tasks="tasks" :config="config" @task-updated="taskUpdate"
@link-updated="linkUpdate" @task-selected="selectTask">
</GanttComponent>
</div>
</a-spin> -->
<GanttComponent ref="gantt" :tasks="tasks" :config="config" @task-updated="taskUpdate" @link-updated="linkUpdate"
@task-selected="selectTask">
</GanttComponent>
</div>
</template>
<script>
import GanttComponent from "./GanttComponent.vue";
const axios = require('axios');
export default {
name: "app",
components: { GanttComponent },
computed: {
ganttInstance() {
return this.$refs.gantt.$refs.ganttContainer.gantt
}
},
data() {
return {
value: "",
tasks: {
"data": [
{
"id": 5,
"text": "Task #1.1",
"start_date": "2017-04-07 00:00:00",
"duration": 7,
"progress": 0.34,
"parent": 1,
"sortorder": 2,
"open": true
},
{
"id": 2,
"text": "Task #1",
"start_date": "2017-04-11 00:00:00",
"duration": 4,
"progress": 0.639881,
"parent": 1,
"sortorder": 3,
"open": true
},
{
"id": 1,
"text": "Project #1",
"start_date": "2017-04-07 00:00:00",
"duration": 6,
"progress": 0.8,
"parent": 0,
"sortorder": 7,
"open": true
},
{
"id": 4,
"text": "Task #3",
"start_date": "2017-04-12 00:00:00",
"duration": 4,
"progress": 0,
"parent": 1,
"sortorder": 7,
"open": true
},
{
"id": 15,
"text": "新任无",
"start_date": "2017-04-11 00:00:00",
"duration": 1,
"progress": 0,
"parent": 0,
"sortorder": 11,
"open": true
},
{
"id": 18,
"text": "新任",
"start_date": "2017-04-11 00:00:00",
"duration": 4,
"progress": 0,
"parent": 0,
"sortorder": 22,
"open": true
}
],
"collections": {
"links": [
{
"id": 20,
"source": 15,
"target": 18,
"type": "1"
}
]
}
},
selectedTask: null,
config: {
dataScale: "Week",
},
undoPageData: false,
spinning: false,
undoLoading: false,
redoLoading: false,
indentLoading: false,
outdentLoading: false,
delLoading: false,
moveBackwardLoading: false,
moveForwardLoading: false,
loadingKey: ''
};
},
mounted() {
// this.loadData()
},
methods: {
loadData() {
const that = this
that.spinning = true
axios.get('api/data')
.then(function (response) {
// 处理成功情况
that.tasks = response.data
that.ganttInstance.parse(that.tasks)
that.ganttInstance.refreshData()
})
.catch(function (error) {
// 处理错误情况
console.log(error);
})
.finally(function () {
// 总是会执行
that.spinning = false
});
},
reqHandle(mode) {
let method = ''
let message = ''
let errorMessage = ''
switch (mode) {
case "update":
method = 'put'
message = '更新成功'
errorMessage = '更新失败'
break;
case "create":
console.log('create')
method = 'post'
message = '新增成功'
errorMessage = '新增失败'
break;
case "delete":
method = 'delete'
message = '删除成功'
errorMessage = '删除失败'
break;
default:
break;
}
return { method, message, errorMessage }
},
// 任务更新
taskUpdate(id, mode, task) {
// const that = this
// that.loadingKey && (that[that.loadingKey + 'Loading'] = true)
// // 仅回滚页面,则直接返回
// if (that.undoPageData) {
// return
// }
// const { method, message, errorMessage } = that.reqHandle(mode)
// axios[method](`api/data/task/${method != 'post' ? id : ''}`, task)
// .then(function (response) {
// console.log('then-----------')
// if (response.data.action == 'error') {
// throw new Error()
// }
// // 处理成功情况
// that.$message.success(message)
// })
// .catch(function (error) {
// console.log('catch-----------')
// // 处理错误情况
// that.$confirm({
// title: errorMessage,
// content: '请求失败是否回滚页面数据,以保持数据一致?',
// onOk() {
// return new Promise((resolve) => {
// that.undoPageData = true
// that.undo()
// resolve()
// }).then(() => { that.undoPageData = false });
// },
// onCancel() {
// that.undoPageData = false
// },
// });
// console.log(error);
// })
// .finally(function () {
// // 总是会执行
// that.loadingKey && (that[that.loadingKey + 'Loading'] = false)
// that.loadingKey = ''
// });
},
// 连线更新
linkUpdate(id, mode, link) {
// const that = this
// // 仅回滚页面,则直接返回
// if (that.undoPageData) {
// return
// }
// const { method, message, errorMessage } = that.reqHandle(mode)
// axios[method](`api/data/link/${method != 'post' ? id : ''}`, link)
// .then(function (response) {
// console.log('then-----------')
// if (response.data.action == 'error') {
// throw new Error()
// }
// // 处理成功情况
// that.$message.success(message)
// })
// .catch(function (error) {
// console.log('catch-----------')
// // 处理错误情况
// that.$confirm({
// title: errorMessage,
// content: '请求失败是否回滚页面数据,以保持数据一致?',
// onOk() {
// return new Promise((resolve) => {
// that.undoPageData = true
// that.undo()
// resolve()
// }).then(() => { that.undoPageData = false });
// },
// onCancel() {
// that.undoPageData = false
// },
// });
// console.log(error);
// })
// .finally(function () {
// // 总是会执行
// });
},
// 获取当前选中任务
selectTask: function (task) {
console.log('selected', task)
this.selectedTask = task;
},
undo() {
this.loadingKey = 'undo'
this.$refs.gantt.performAction('undo')
},
redo() {
this.loadingKey = 'redo'
this.$refs.gantt.performAction('redo')
},
indent() {
this.loadingKey = 'indent'
this.$refs.gantt.performAction('indent')
},
outdent() {
this.loadingKey = 'outdent'
this.$refs.gantt.performAction('outdent')
},
del() {
this.loadingKey = 'del'
this.$refs.gantt.performAction('del')
},
moveForward() {
this.loadingKey = 'moveForward'
this.$refs.gantt.performAction('moveForward')
},
moveBackward() {
this.loadingKey = 'moveBackward'
this.$refs.gantt.performAction('moveBackward')
},
fullscreen() {
this.ganttInstance.ext.fullscreen.toggle()
},
},
};
</script>
<style lang="scss" scoped>
html,
body {
height: 100%;
margin: 0;
padding: 0;
}
.container {
height: 100vh;
width: 100%;
}
#main-gantt {
z-index: 9999 !important;
background-color: #fff;
}
.left-container {
overflow: hidden;
position: relative;
height: 100%;
}
.right-container {
border-right: 1px solid #cecece;
float: right;
height: 100%;
width: 340px;
box-shadow: 0 0 5px 2px #aaa;
position: relative;
z-index: 2;
}
.gantt-messages {
list-style-type: none;
height: 50%;
margin: 0;
overflow-x: hidden;
overflow-y: auto;
padding-left: 5px;
}
.gantt-messages>.gantt-message {
background-color: #f4f4f4;
box-shadow: inset 5px 0 #d69000;
font-family: Geneva, Arial, Helvetica, sans-serif;
font-size: 14px;
margin: 5px 0;
padding: 8px 0 8px 10px;
}
.gantt-selected-info {
border-bottom: 1px solid #cecece;
box-sizing: border-box;
font-family: Geneva, Arial, Helvetica, sans-serif;
height: 50%;
line-height: 28px;
padding: 10px;
}
.gantt-selected-info h2 {
border-bottom: 1px solid #cecece;
}
.select-task-prompt h2 {
color: #d9d9d9;
}
.data-scale {
padding: 8px 0;
margin-right: 16px;
}
.tools-list {
display: flex;
justify-content: right;
align-items: center
}
.tool-button {
margin-right: 16px;
}
::v-deep.ant-btn {
padding: 0 8px !important;
}
</style>
资源地址
github地址
dhtmlx-gantt文档地址
node版本为15.0.0
声明
仅供学习参考喔