官方文档:如何从 dhtmlx甘特甘特图文档开始
这个组件比较古老,对vue的兼容性很不友好。以下是一些开发心得:
1.安装dhtmlx 甘特图
npm install @dhtmlx/trial-vue-gantt
复制
2.创建Gantt.vue 组件
<template> <div class="xh-radio-switch"> <el-select v-model="test" @change="radioSwitchChange"> <el-option v-for="item in options" :key="item.val" :label="item.name" :value="item.val"></el-option> </el-select> </div> <div style="width: 100%;" ref="ganttRef"></div> </template> <script setup lang="ts"> import { onMounted, ref, nextTick } from 'vue' import { gantt } from 'dhtmlx-gantt' import { ElMessage, ElMessageBox, dayjs } from 'element-plus'; const emits = defineEmits<{ (e: 'openDetail', id: number): void (e: 'openShow', id: number): void (e: 'handle', success: true): void }>() const test = ref('') const options = ref( [ { name: '日', val: 'day' }, { name: '周', val: 'week' }, { name: '月', val: 'month' }, { name: '季度', val: 'quarter' }, ] ) const props = defineProps<{ // 任务对象 tasks?: any // 显示列设置 columns?: Array<any> // 显示单位 scaleUnit?: { type: String, default: 'day' // “minute”, “hour”, “day”, “week”, “quarter”, “month”, “year” }, // 时间显示格式 dateScale?: { type: String, default: '%Y-%m-%d' } }>() // 挂载ref const ganttRef = ref() onMounted(() => { // 清空之前的配置 gantt.clearAll() // 默认配置 gantt.config.xml_date = '%Y-%m-%d' gantt.i18n.setLocale('cn') // 设置中文 gantt.config.readonly = false // 设置为可编辑 gantt.config.autosize = true//自适应尺寸 gantt.config.autofit = true// 表格列宽自适应 gantt.config.autoscroll = true// 把任务或者连线拖拽到浏览器屏幕外时,自动触发滚动效果 gantt.config.drag_progress = false//取消任务进度条进度拖动 //时间栏配置 gantt.config.scales = [ { unit: 'month', step: 1, format: '%Y年%m月' }, { unit: 'day', step: 1, format: '%m/%d' }, ] gantt.config.scale_height = 60 //更改树状的图标 gantt.templates.grid_open = (item: any) => { props.tasks.data.forEach((item: any) => { changeItemStyle(item) }) return "<div data-icon='" + (item.$open ? "close" : "open") + "' class='gantt_tree_icon gantt_" + (item.$open ? "close" : "open") + "'></div>" } //更改父项图标 gantt.templates.grid_folder = (item: any) => { return "" } //更改子项图标 gantt.templates.grid_file = (item: any) => { return "" } //自定义配置左侧表格列 gantt.config.columns = [ { name: "text", label: "事务名称", align: "left", tree: true, width: 260 }, { name: "status", label: " ", width: 80, align: "center", template: function (task: any) { switch (task.status) { case 1: return ( `<div data-show class="progress progress-success">已完成</div> <div data-show class="avatar"><img src="${task.avatar}" /></div>` ); break; case 2: return ( `<div data-show class="progress progress-todo">待 办</div> <div data-show class="avatar"><img src="${task.avatar}" /></div>` ); break; case 3: return ( `<div data-show class="progress progress-doing">进行中</div> <div data-show class="avatar"><img src="${task.avatar}" /></div>` ); break; break; default: return ( `<div data-show class="progress progress-todo">待办</div> <div data-show class="avatar"><img src="${task.avatar}" /></div>` ); break; } } }, { name: "add_item", label: "", width: 44, template: () => { return `<div ref="addRef" id="button" class="add-item" data-action="add"></div>` } } ] //自定义右侧任务栏颜色 gantt.templates.task_class = (start: string, end: string, task: any) => { if (task.parent) return 'task-child' return 'task-' + task.id } //获取任务时 gantt.attachEvent("onTaskLoading", (task: any) => { changeItemStyle(task) return true }) //打开分支时候 gantt.attachEvent("onTaskOpened", function () { props.tasks.data.forEach((item: any) => { changeItemStyle(item) }) }); //关闭分支时 gantt.attachEvent("onTaskClosed", function () { props.tasks.data.forEach((item: any) => { changeItemStyle(item) }) }); //拖动任务时 gantt.attachEvent("onTaskDrag", function (id: number, mode: any, task: any, original: any) { changeItemStyle(task) }); //甘特图 任务栏更新后 可获取当前任务对象 gantt.attachEvent("onAfterTaskUpdate", async function (id: number, item: any) { changeItemStyle(item) const params_start = { issues_id: id, name: 'start_date', values: dayjs(item.start_date).format('YYYY-MM-DD') } const params_end = { issues_id: id, name: 'end_date', values: dayjs(item.end_date).format('YYYY-MM-DD') } const res1 = await xxxxx(params_start) if (res1.code !== 0) return ElMessage.error(res1.message) const res2 = await xxxxx(params_end) if (res2.code !== 0) return ElMessage.error(res2.message) }); //甘特图 点击事件 gantt.attachEvent("onTaskClick", function (id: number, e: any) { //点击显示更多按钮 const buttonAdd = e.target.closest("[data-action]") if (buttonAdd) { emits('openDetail', id) } //点击状态按钮 const buttonShow = e.target.closest("[data-show]") if (buttonShow) { emits('openShow', id) } //点击展开/收起按钮 const buttonIcon = e.target.closest("[data-icon]") if (buttonIcon) { const active = buttonIcon.getAttribute("data-icon") switch (active) { case 'open': gantt.open(id) break; case 'close': gantt.close(id) break; default: break; } } }) //取消默认双击事件 gantt.attachEvent("onTaskDblClick", function (id: number, e: any) { return false }); // 处理连线完成事件 let linkAddHandler = null linkAddHandler = async function (id: number, item: any) { props.tasks.data.forEach((item: any) => { changeItemStyle(item) }) const params = { issues_id: Number(item.source), relate_type: 1, relate_issues_id: Number(item.target) } const res = await xxxxxxxx(params) if (res.code !== 0) return ElMessage.error(res.message) ElMessage.success(res.data) emits('handle', true) } gantt.attachEvent("onAfterLinkAdd", linkAddHandler) // 在需要取消监听时执行 gantt.detachEvent("onAfterLinkAdd", linkAddHandler) // 处理连接线双击事件 let linkDblClickHandler = null linkDblClickHandler = function (id: number, e: any) { ElMessageBox.confirm( '此操作不可恢复', '删除此关联事务', { confirmButtonText: '删除', cancelButtonText: '取消', type: 'warning', } ).then(async () => { const res = await xxxxxx(id) if (res.code !== 0) return ElMessage.error(res.message) ElMessage.success(res.data) gantt.deleteLink(id) emits('handle', true) }) } gantt.attachEvent('onLinkDblClick', linkDblClickHandler); // 初始化甘特图 gantt.init(ganttRef.value) // 渲染数据 gantt.parse(props.tasks) }) //----------------自定义函数 const backgroundColorFun = (number1: number, number2: number) => { return `linear-gradient(to right, #68d390 ${0}%, #68d390 ${number1}%,#0085ff ${number1}%,#0085ff ${number2}%, #c4c4c4 ${number2}%, #c4c4c4 ${100}%)` } const changeItemStyle = (task: any) => { if (!task.parent) { const total = task.done + task.backlog + task.under_way + task.lay_aside if (total) { const percentDone = (task.done / total) * 100 const percentUnderWay = (task.under_way / total) * 100 nextTick(() => { const taskClass = document.querySelector(`.task-${task.id}`) as HTMLElement if (taskClass) { taskClass.style.background = `${backgroundColorFun(percentDone, percentDone + percentUnderWay)}` taskClass.style.borderRadius = '50px' } }) } else { nextTick(() => { const taskClass = document.querySelector(`.task-${task.id}`) as HTMLElement if (taskClass) { taskClass.style.background = `${backgroundColorFun(0, 0)}` taskClass.style.borderRadius = '50px' } }) } } } //日 周 月 季度 切换 const switchToDay = () => { gantt.config.scale_unit = "day" gantt.config.step = 1 gantt.render() } const switchToWeek = () => { gantt.config.scale_unit = "week" gantt.config.step = 1 gantt.render() } const switchToMonth = () => { gantt.config.scale_unit = "month" gantt.config.step = 1 gantt.render() } const switchToQuarter = () => { gantt.config.scale_unit = "month" gantt.config.step = 3 gantt.render() } const radioSwitchChange = (e: string) => { switch (e) { case 'day': switchToDay() break; case 'week': switchToWeek() break; case 'month': switchToMonth() break; case 'quarter': switchToQuarter() break; default: break; } } </script> <style lang="less"> .xh-radio-switch { display: flex; justify-content: flex-end; } .add-item { cursor: pointer; width: 20px; height: 20px; background-size: cover; background-image: url(xxxxxx) } /* 进度盒 */ .progress { display: flex; justify-content: center; align-items: center; width: 43px; height: 20px; border-radius: 5px; font-size: 12px; padding: 3px; &-success { background: rgba(63, 200, 114, 0.15); color: #67d390; } &-todo { background: #dfe1e6; color: #84526e; } &-doing { background: rgba(0, 133, 255, 0.15); color: #0085FF; } } /**用户头像 */ .avatar { display: flex; img { width: 24px; height: 24px; border-radius: 50%; margin-left: 10px; } } /** 左侧树状 新增按钮 */ .gantt_add, .gantt_grid_head_add { background-image: url(xxxxxxxx) !important; opacity: .9 !important; } .gantt_tree_content { display: flex; justify-content: center; align-items: center; } /* 定义任务栏颜色 */ .gantt_task_line { border-color: rgba(0, 0, 0, 0.25); } .gantt_task_line .gantt_task_progress { background-color: rgba(0, 0, 0, 0.25); } /* task */ .gantt_task_line { .task-5 { border-radius: 50px; // background: v-bind(backgroundColor); } } .gantt_task_line.task { border-radius: 50px; // background: v-bind(backgroundColor); } .gantt_task_line.task-child { background-color: #ffe176; border-radius: 50px; } .gantt_task_line .gantt_task_progress { background-color: #68d390; } .gantt_task_line.task .gantt_task_content { color: #fff; } </style> <style> @import "/node_modules/dhtmlx-gantt/codebase/dhtmlxgantt.css" </style>
复制
3.在vue中使用
<template> <Gantt v-if="ganttTasks.data.length" :tasks="ganttTasks" :columns="ganttColumns" style="height: 500px;" @handle="getGanttList()"></Gantt> </template> <script setup lang="ts"> import Gantt from './components/Gantt.vue' //-----------------------------------生命周期 onMounted(() => { Promise.all([getGanttList()]) }) //-----------------------------------甘特图 const ganttData = ref<xxxx[]>([]) //后端数据 const linkData = ref<any[]>([])//相关事务数据 const ganttKeywords = ref<xxxx>({ synergy_type_id: 1 //事务类型 }) const ganttTasks = ref<any>({ //数据 data: [], //链接 links: [] }) const ganttColumns = ref([ { align: 'left', name: 'text', label: ' ', tree: true }, ]) const getGanttList = async () => { const res = await xxxxxxxxxx(ganttKeywords.value) if (res.code !== 0) return ElMessage.error(res.message) ganttData.value = res.data ganttTasks.value.data = ganttData.value.map((item: xxxx) => { //这里的返回值 就是gantt回调函数中的task形参 return { id: item.id, parent: item.parent_id, text: 'XH-' + item.id + ' ' + item.name, start_date: item.start_date, end_date: item.end_date, progress: item.completion_progress, status: item.status, avatar: item.principal.avatar || defaultImage, level: item.level, done: item.done, backlog: item.backlog, under_way: item.under_way, lay_aside: item.lay_aside } }) linkData.value = ganttData.value.filter(item => item.issues_relation && item.issues_relation.length) if (linkData.value.length) { ganttTasks.value.links = linkData.value.map(item => item.issues_relation).flat(Infinity).map((item: xxxx) => { return { id: item.id, source: item.synergy_issues_id, target: item.relate_issues_id, type: '0' } }) } } </script>
复制