阅读 853

前端之甘特图-dhtmlx-gantt

背景

最近遇到一个业务需求,是在开发管理后台时候,需要有用到日历以及甘特图去展示管理任务相关视图,因此进行相关甘特图的前端生成的调研-说是调研,不如说是找了一个半开源的库(但不得不说,这个库,很强)。 具体场景如图:

image.png

功能分析

  1. 基础元素:左侧任务树 & 右侧图例任务 Progress

  2. 新增任务

  3. 删除任务

  4. 编辑任务

  5. ...

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: '今日'     }); 复制代码

image.png

任务菜单以及事件

  • 右键菜单功能

// 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> 复制代码

效果 image.png

  • 其他事件 (禁用原来自带的弹窗)

 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;   },   {} ); 复制代码

image.png

体验和心得

  1. 总体而言视乎满足了需求要的样子

  2. 具体代码嘛,只处于一个 Dome 级别

  3. 至于npm源码方面,开源出来的功能从其官网看还是基本满足日常需求的

  4. 库的稳定和功能升级方面,每周下载还是处于活跃的状态

  5. 个人体会:官网Base是英文的,然后 Samples 样库例提供了很多功能的案例,需要开发着耐心去发掘。

image.png

最后

To be someone who needs it. 是自己成为别人需要的人

此文仅是本着目前需求出发,可能考虑的点和开源的方案并不是一个最佳的,所以如果有幸能被阅读到或者有更加成熟的方案,请留言交流,万分感谢。


作者:jason_xiewenqiang
链接:https://juejin.cn/post/7021824260540727309


文章分类
后端
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐