前端之甘特图-dhtmlx-gantt
背景
最近遇到一个业务需求,是在开发管理后台时候,需要有用到日历以及甘特图去展示管理任务相关视图,因此进行相关甘特图的前端生成的调研-说是调研,不如说是找了一个半开源的库(但不得不说,这个库,很强)。 具体场景如图:
功能分析
基础元素:左侧任务树 & 右侧图例任务 Progress
新增任务
删除任务
编辑任务
...
Gantt 的 NPM地址 docs.dhtmlx.com
官网 Gannt
开发 Demo
1. 安装
npm i dhtmlx-gantt
2. 组件导入
<script lang="ts"> import { defineComponent, onMounted, ref } from 'vue'; import { gantt } from 'dhtmlx-gantt'; // 核心模块 import 'dhtmlx-gantt/codebase/dhtmlxgantt.css'; // 样式模块 .... </script> 复制代码
3. 准备引入DOM
<template> <div class="gantt-no" ref="ganttRef"></div> </template> <script> setup() { const ganttRef = ref<HTMLElement | null>(null); ... return { ganttRef } } </script> 复制代码
4. 准备 MOCK 数据
const tasks = { data: [ { id: 1, text: '任务 1', start_date: '2021-10-17', duration: 3, progress: 0.6 }, { id: 2, text: '任务 2', start_date: '2021-10-20', duration: 10, progress: 0.4 } ], links: [] } 复制代码
参数简析:
整体数据是以对象的形式存放,其中的data是一个 Task[],links是任务连线,其结构是 Link[]
单个 Task 可能包含以下的字段
id: 任务唯一标识
text: 任务名称
start_date: 任务开始时间
duration: 任务时长
progress: 任务进度
parent: 父级的ID(树结构关系)
... 其他参数要查阅其官方文档
单个 Link 可能包含:
id: 连线的唯一标识
source: 源节点
target: 目标节点
type: 连线类型(0|1|2)标识是否有箭头
5. 初始化以及传入 tasks
onMounted((0 => { if (ganttRef.value) { gantt.init(ganttRef.value); // 初始化 DOM gantt.parse(tasks); // 传入 tasks } }) 复制代码
6. 配置图例参数
禁用连线 (本需求是不需要连线功能的)
gantt.config.show_links = false; 复制代码
禁用工作进度拖拽 (必须通过界面弹窗的方式进行修改信息)
gantt.config.drag_progress = false; 复制代码
设置任务分段参数以及单位
gantt.config.duration_unit = 'day'; gantt.config.duration_step = 1; 复制代码
配置左侧表格栏目
gantt.config.columns = [ { name: 'text', label: '任务名称', tree: true, width: '*', align: 'left', template: function (obj: any) { return obj.text; } }, { name: 'start_date', label: '时间', width: '*', align: 'center', template: function (obj: any) { return obj.start_date; } }, { name: 'progress', label: '进度', width: '*', align: 'center', template: function (obj: any) { return `${obj.progress * 100}%`; } } ]; 参数简析: **ColumsItem[]** 1. name: 'text' [String] , 索取的 tasks 里 **Task[]** 的 Task 的属性 2. label: 'xxx' [String], 当前栏显示的文本 3. tree: true [Boolean],当前的任务是否为树结构这样 4. align: [String: left|right|center],label文本位置属性 5. template: [Function],函数类型,入参是 obj,即为当前的 Task 对象 6. ... 其他参数要查阅文档 复制代码
配置右侧表头日期栏
gantt.config.xml_date = '%Y-%m-%d'; // 日期格式化的匹配格式 gantt.config.scale_height = 90; // 日期栏的高度 const weekScaleTemplate = function (date: any) { const mouthMap: { [key: string]: string } = { Jan: '一月', Feb: '二月', Mar: '三月', Apr: '四月', May: '五月', Jun: '六月', Jul: '七月', Aug: '八月', Sept: '九月', Oct: '十月', Nov: '十一月', Dec: '十二月' }; // 可以时使用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)} 号 (${ mouthMap[mToStr(date) as string] })`; }; const dayFormat = function (date: any) { const weeks = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']; return weeks[Dayjs(date).day()]; }; gantt.config.scales = [ { unit: 'year', step: 1, format: '%Y' }, { unit: 'week', step: 1, format: weekScaleTemplate }, { unit: 'day', step: 1, format: dayFormat } ]; 复制代码
添加今日的 Marker Line
gantt.plugins({ marker: true }); gantt.addMarker({ start_date: new Date(), text: '今日' }); 复制代码
任务菜单以及事件
右键菜单功能
// menu.vue <template> <div class="menu" :style="{ left: x + 'px', top: y + 'px' }"> <el-menu @select="handleSelect" background-color="#545c64" text-color="#fff" active-text-color="#fff" > <el-menu-item index="add">新增任务</el-menu-item> <el-menu-item index="edit">编辑任务</el-menu-item> <el-menu-item index="del">删除任务</el-menu-item> </el-menu> </div> </template> <script lang="ts"> import { defineComponent } from 'vue'; export default defineComponent({ props: { x: { type: Number, default: 0 }, y: { type: Number, default: 0 } }, emits: ['menu-item'], setup(props, ctx) { const handleSelect = (action: string) => { ctx.emit('menu-item', action); }; return { handleSelect }; } }); </script> <style lang="less" scoped> .menu { position: fixed; transition: all 1s ease; ::v-deep(.el-menu-item) { height: 40px; line-height: 40px; width: 200px; } } </style> // Gantt.vue <template> <transition name="el-fade-in-linear"> <Menu :x="menuX" :y="menuY" v-show="menuVisible" @menu-item="handleItemClick" /> </transition> </template> <script> const menuVisible = ref<boolean>(false); // 控制菜单显示 const menuX = ref<number>(0); // left const menuY = ref<number>(0); // top const handleItemClick = (item: any) => { menuVisible.value = false; // 隐藏菜单 dialogVisible.value = true; // 显示编辑弹窗 }; gantt.attachEvent( 'onContextMenu', function (taskId, linkId, event) { var x = event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft, y = event.clientY + document.body.scrollTop + document.documentElement.scrollTop; // 判断要是在树上的右键菜单才有效果 if (taskId && event.target.className === 'gantt_tree_content') { console.log('task ContentMenu', taskId, linkId, event); menuX.value = x; menuY.value = y; menuVisible.value = true; } if (taskId || linkId) { return false; } return true; }, {} ); // 取消菜单显示 gantt.attachEvent( 'onEmptyClick', function (e) { //any custom logic here menuVisible.value = false; }, {} ); </script> 复制代码
效果
其他事件 (禁用原来自带的弹窗)
gantt.attachEvent( 'onBeforeLightbox', function (id) { console.log(1); return false; // 返回 false }, {} ); 复制代码
任务双击进入编辑事件
gantt.attachEvent( 'onTaskDblClick', function (id, e) { console.log('id', id, e); dialogVisible.value = true; return false; }, {} ); 复制代码
体验和心得
总体而言视乎满足了需求要的样子
具体代码嘛,只处于一个 Dome 级别
至于npm源码方面,开源出来的功能从其官网看还是基本满足日常需求的
库的稳定和功能升级方面,每周下载还是处于活跃的状态
个人体会:官网Base是英文的,然后 Samples 样库例提供了很多功能的案例,需要开发着耐心去发掘。
最后
To be someone who needs it. 是自己成为别人需要的人
此文仅是本着目前需求出发,可能考虑的点和开源的方案并不是一个最佳的,所以如果有幸能被阅读到或者有更加成熟的方案,请留言交流,万分感谢。
作者:jason_xiewenqiang
链接:https://juejin.cn/post/7021824260540727309