Skip to content

vue中使用dhtmlxGantt

1 准备dhtmlx-gantt模块

txt
npm install dhtmlx-gantt

2 编码

新建gantt.vue文件,在文件加入以下代码

txt
<template>
  <div class="app-container">
    <div ref="gantt" class="left-container" />
  </div>
</template>
<script>
import gantt from 'dhtmlx-gantt'  // 引入模块
import 'dhtmlx-gantt/codebase/dhtmlxgantt.css'
import 'dhtmlx-gantt/codebase/locale/locale_cn'  // 本地化
export default {
  name: 'DhtmlxGantt',
  data() {
      return {
      tasks: {
        data: [
          { id: 1, text: 'Task #1', start_date: '15-04-2017', personName: '张总', duration: 3, progress: 0.6 },
          { id: 2, text: 'Task #2', start_date: '18-04-2017', personName: '李总', duration: 3, progress: 0.4 },
          { id: 3, text: 'Task #2-1', start_date: '20-04-2017', personName: '赵总', duration: 3, progress: 0.4, parent: 2 }
        ],
        links: [
          { id: 1, source: 1, target: 2, type: '0' }
        ]
      }
    }
  }
  mounted() {
    // 初始化
    gantt.init(this.$refs.gantt)
    // 数据解析
    gantt.parse(this.$props.tasks)
  }
}
<script>
<style>
  .left-container {
    height: 600px;
  }
</style>

这样,基本的甘特图就已经搭建出来了,如下图

img

可以看到上图的时间轴上只有月和日,那加个年份的显示是不是更完美,说加就加
我们可在甘特图初始化之前加入配置,如下:

txt
mounted() {
    // 在时间线上增加一行年份显示
    gantt.config.subscales = [
      {
        unit: 'year',
        step: 1,
        date: '%Y'
      }
    ]
  // 初始化
  gantt.init(this.$refs.gantt)
}

img

甘特图样例

txt
自定义表头列
gantt.config.columns = [
      { name: "project_num", label: "项目编码", width: 80,align: "center"},
      { name: "project_type", label: "项目类别", width: 80, align: "center"},
      { name: "text", label: "项目名称", width: 100, align: "center" },
     // { name: "add", label: "" }, // ➕icon列显示 点击弹出添加框 隐藏没有
    ];

txt
时间安排显示代码块
gantt.config.scale_unit = "year"; //第一个时间尺度,即X轴的单位,包括:"minute", "hour", "day", "week", "month", "year"
gantt.config.step = 1;
gantt.config.date_scale="%Y";
gantt.config.start_date= new Date(2020,0,1) // 0代表1月 11代表12月
gantt.config.end_date= new Date(2025,11,31) // 0代表1月 11代表12月

// gantt.config.subscales = [ //第二个时间尺度单位
//         { unit: "day", step: 1, date: "%D" }
//     ];

data数据结构中 render 属性有无区别

txt
数据结构
 data: [
          {
            id: 1, // 任务ID
            project_num:'p1',
            project_type:'项目一',
            text: "任务一",
            start_date: "03-04-2020", // 日-月-年 
            type: "project",
            render: "split",
            parent: 0, //父任务ID  自己为父任务 ID为0
          },
          {
            id: 2,
            text: "第一阶段",
            start_date: "03-04-2020", //开始时间
            end_date: "03-12-2020", //结束时间
            parent: 1,
          },
          {
            id: 3,
            text: "第二阶段",
            start_date: "12-12-2020",
            end_date: "12-12-2021",
            duration: 2,
            parent: 1,
            color:'green'
          },
          {
            id: 4,
            text: "第三阶段",
            start_date: "12-12-2022",
            end_date: "12-12-2023",
            duration: 1,
            parent: 1,
          },
          {
            id: 5,
            project_num:'p2',
            project_type:'项目二',
            text: "任务二",
            start_date: "12-12-2024",
            type: "project",
            render: "split",
            parent: 0,
          },
        ],

Vue2使用DHTMLX Gantt

甘特图

成品展示

gantt安装与使用

vue2版---部分功能收费

  1. 安装gantt

    txt
    npm install dhtmlx-gantt -save
    ```
  2. 引入---组件

    xml
    `引入`
    <template>
        <div ref="gantt" class="container" />
    </template>
    <script>
      import { gantt } from 'dhtmlx-gantt'
    </script>
  3. 使用

    使用需要很多配置项,在原生版进行详情介绍,vue版只做基本使用

    功能(收费)文档链接简要说明
    动态加载Dynamic loading能通过接口更新甘特数据,gantt支持的接口方法
    时间刻度隐藏Ability to hide time units on the time scale右边视图刻度可以不显示某时间内容
    工作时间精度Assigning Calendar to Project精确工作时间到小时或小数点天数
    自动调度Auto scheduling使甘特图能够根据任务之间的关系自动安排任务,自动调度时间
    页面多个甘特图Enterprise and Ultimate licenses)单个页面创建多个甘特图
    关键任务计算Critical path calculation关键任务不可延迟,项目标红,计算项目可预算时间
    水平拖拽多个任务Decimal units for tasks durations可以同时修改多个任务的时间
    项目,里程碑,任务类型Projects and Milestones task types区别显示图标,里程碑(菱形图案)---原生版可用
    将项目拆分子任务Projects and Milestones task types项目收缩显示子任务 -----原生版可用
    分组任务Tasks grouping细化数据分类,不做汇总分区基本用不上

    以上内容只是部分收费内容,并非全部收费内容

原生版---推荐使用

引入

css
`页面部分` ----#节点高度要给,gantt不根据内容撑开
<div style="min-height:calc(78vh - 50px - 5px );width: 100%;overflow: hidden;" ref="gantt">
 </div>

`引入部分`
import {
  gantt
} from '@/assets/js/dhtmlx';
import "@/assets/css/dhtmlxgantt.css";
  1. css文件地址 examples/dhtmlx_gantt/dhtmlxgantt.css · 残星落影/博客 - 码云 - 开源中国 (gitee.com)
  2. js文件地址 examples/dhtmlx_gantt/dhtmlx · 残星落影/博客 - 码云 - 开源中国 (gitee.com)

使用

gantt加载数据格式
javascript
`定义数据格式` 
data(){
  return {
    tasks: {
            data: [],//数据
            links: [],//关联项目数据
          },
    }
}
`甘特数据载入`
 gantt.parse(this.tasks);//parse方法加载数据
 gantt.render();//建议每一次修改配置项调用一次render方法

tasks.data数据格式

txt
data=[
  { 
    id: 1,//必填
    text: "标题",//必填 
    type: "task",// 项目类型 task任务 project项目  milestone里程碑  
    start_date: "2023/3/15",
    duration: 5,//任务持续时间
    parent: 11,//存在这个属性说明此数据为子任务数据,父任务id为11
    progress: 0.3,//项目任务滑块的进度
    open: true,//是否展开显示
    ....
  },
  { 
    id: 2,
    text: "标题2", 
    start_date: "2023/3/15",
    duration: 5,
    progress: 0,
    open: true
    ....
  },
]
# data中有些数据可以直接被读取,其余数据都可以定义在左侧表格columns数据显示
`甘特可直接读取属性
    type  parent  progress open ...
`

tasks.links数据格式

bash
links=[
  {
    id:'111',//数据id
    source:'1'
    target:'2'
    type:'0'
  },//
  {
    id:'222'
    source:'2'
    target:'1'
    type:'1'
  },//数据说明 滑块任务2 的头部 指向滑块任务1的头部
]
#字段解释
格式 id:数据id   
    source:开始链接的项目id  ----为tasks.data中数据的id
    target:要链接项目的id  ----为tasks.data中数据的id
    type: 0--进行-开始  `尾部链接头部`  
          1--开始-开始  `头部链接头部`
          2--进行-进行  `尾部链接尾部`
          3--开始-进行  `头部链接尾部`

图例

配置项
  • 基础config

    ini
     gantt.config.branch_loading = true; // 启用动态加载
     gantt.config.xml_date = "%Y-%m-%d"; //日期格式化
     gantt.config.order_branch = true;
     gantt.config.order_branch_free = true;
     gantt.config.autofit = true;//左侧是否自适应
     gantt.config.drag_links = true;//连线
     gantt.config.readonly = false;  //只读
     gantt.config.smart_scales = true;
     gantt.config.date_scale = "%m月%d日"; //右侧显示列名
     gantt.config.layout = {//拖拽布局
            css: "gantt_container",
            rows: [
              {
                cols: [
                  { view: "grid", id: "grid", scrollX: "scrollHor", scrollY: "scrollVer" },
                  { resizer: true, width: 1 },
                  { view: "timeline", id: "timeline", scrollX: "scrollHor", scrollY: "scrollVer" },
                  { view: "scrollbar", scroll: "y", id: "scrollVer" }
                ]
              },
              { view: "scrollbar", scroll: "x", id: "scrollHor", height: 20 }
            ]
          };
    gantt.config.start_on_monday = true;//是否从周一显示起始时间---右侧条形图
    gantt.config.work_time = true;//显示工作时间
    gantt.config.fit_tasks = true;   //自动调整图表坐标轴区间用于适配task的长度
  • local---本地汉化

    说明

    (1)直接引入dhtmlx甘特实现的图表操作描述全是英文的,所以要对现有的属性数据显示要进行汉化文字代替

    (2)汉化分三种

    日期汉化属性汉化自定义属性汉化
    gantt.locale.dategantt.locale.labelsgantt.locale.labels
    csharp
     //汉化
          gantt.locale = {
            date: {
              month_full: ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"],
              month_short: ["1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月"],
              day_full: ["星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"],
              day_short: ["", "", "", "", "", "", ""]
            },
            labels: {
              dhx_cal_today_button: "今天",
              day_tab: "",
              week_tab: "",
              month_tab: "",
              new_event: "新建日程",
              icon_save: "保存",
              icon_cancel: "关闭",
              icon_details: "详细",
              icon_edit: "编辑",
              icon_delete: "删除",
              confirm_closing: "请确认是否撤销修改!", //Your changes will be lost, are your sure?
              confirm_deleting: "是否删除计划?",
              section_description: "描述:",
              section_time: "时间范围:",
              section_type: "类型:",
              section_text: "计划名称:",
              section_test: "测试:",
              #自定义属性汉化----->
              section_projectClass: "项目类型:",
              taskProjectType_0: "项目任务",
              taskProjectType_1: "普通任务",
              section_head: "负责人:",
              section_priority: '优先级:',
              taskProgress: '任务状态',
              taskProgress_0: "未开始",
              taskProgress_1: "进行中",
              taskProgress_2: "已完成",
              taskProgress_3: "已延期",
              taskProgress_4: "搁置中",
              #<-----自定义属性汉化结束
              section_template: 'Details',
              /* grid columns */
              column_text: "计划名称",
              column_start_date: "开始时间",
              column_duration: "持续时间",
              column_add: "",
              column_priority: "难度",
              /* link confirmation */
              link: "关联",
              confirm_link_deleting: "将被删除",
              link_start: " (开始)",
              link_end: " (结束)",
              type_task: "任务",
              type_project: "项目",
              type_milestone: "里程碑",
    
              minutes: "分钟",
              hours: "小时",
              days: "",
              weeks: "",
              months: "",
              years: ""
            }
          }

    (4)图例注释

  • 表格(左)配置

    属性说明
    name在tasks.data中定义的属性数据,add为例外,add为新增数据项触发gantt新增弹窗,显示为一个加号
    label标记描述
    align文字格式
    resize布尔值,可以拉伸内容宽度
    width宽度,可写max _width min_width
    height高度, 可写max _height min_height
    editor将表格内容设置为可编辑状态,官网描述
    template自定义渲染内容
    kotlin
      //左侧显示列名
          gantt.config.columns = [
            //{ name: "add", width: 44 }
            { name: "text", min_width:100,max_width:200, label:"任务", align: "left",resize: true,  tree: true, editor: { type: 'text', map_to: 'text' } },
            { name: "id", label: "", hide: true },
            { name: "start_date", label: "开始时间", width: 120, resize: true, align: "left" },
            {
              name: "head", width: 110, height: 40, label: "负责人",resize: true, align: "left",
              // editor: {
              //   map_to: "head_id", type: "select", options: gantt.serverList("staff"),
              // },
              #这里的template渲染的是任务头像跟名称,this.genttDealById 是在methods定义的方法根据id获取名称,gantt.serverList()是甘特图获取数据集分发
              template: (item) => {
                if (this.ganttDealById(gantt.serverList('staff'), item.head_id)) {
                  return `<span class='userIcon' style='background-color:${item.color ? item.color : "#6666"}'>${this.ganttDealById(gantt.serverList('staff'), item.head_id).slice(0, 1)}</span>${this.ganttDealById(gantt.serverList('staff'), item.head_id)}`
                }
              }
            },
            // { name: "end_date", label: "结束时间", align: "center" },
            {
              name: "taskProgress", label: "任务状态", align: "center", width: 130,editor: {
                type: "select", map_to: "taskProgress", options: [
                  #这里的labels.taskProgress_0属性是自定义汉化属性描述
                  { key: "0", label: gantt.locale.labels.taskProgress_0 },
                  { key: "1", label: gantt.locale.labels.taskProgress_1 },
                  { key: "2", label: gantt.locale.labels.taskProgress_2 },
                  { key: "3", label: gantt.locale.labels.taskProgress_3 },
                  { key: "4", label: gantt.locale.labels.taskProgress_4 },
                ],
              },
                #obj形参是单个的tasks.data中的数据
              template: function (obj) {
                let re = '';
                switch (obj.taskProgress) {
                  case "0":
                    #这里的样式类名只能通过css读取,写在less scss无法读取
                    re = `<div class='taskProgress color_bg_1' >未开始</div>`
                    break;
                  case "1":
                    re = `<div class='taskProgress color_bg_2' >进行中</div>`
                    break;
                  case "2":
                    re = `<div class='taskProgress color_bg_3'  >已完成</div>`
                    break;
                  case "3":
                    re = `<div  class='taskProgress color_bg_4'>已延期</div>`
                    break;
                  case "4":
                    re = `<div  class='taskProgress color_bg_5' >搁置中</div>`
                    break;
                }
                return re
              }
            },
    
          ];
  • 弹窗表单----见汉化图例

    less
      //弹出层
          gantt.config.lightbox.sections = [        { name: "text", height: 70, map_to: "text", type: "textarea", focus: true, width: "*" },        {          name: "time", height: 40, map_to: "auto", type: "duration",          time_format: ["%Y", "%m", "%d"],
            },
            {
              name: "projectClass", height: 30, map_to: "proTemplate", type: "template",
             
            },
            {
              name: "head", height: 22, map_to: "head_id", type: "select", options: gantt.serverList('staff',[]),
            },
            { name: "description", height: 70, map_to: "description", type: "textarea" },
            {
              name: "priority", height: 40, map_to: "priority", type: "radio", options: gantt.serverList("priority")
            },
    
          ];
  • gantt功能插件挂载

    php
     gantt.plugins({
            click_drag: true,
            drag_timeline: true,// 拖动图
            marker: true,// 时间标记
            fullscreen: true,// 全屏
            tooltip: true,// 鼠标经过时信息
            undo: true // 允许撤销
          })

    收费的甘特图功能一般在这放开

常用事件
事件参数说明 (参数:id:数据id,item:当个数据对象,mode:拖拽模式,e:事件event)
onGanttReady在 dhtmlx甘特图初始化完成后触发,但甘特图尚未在页面上呈现
onBeforeLightboxid打开弹窗之前修改
onAfterTaskAddid, item在用户将任务添加到甘特图后触发
onAfterTaskUpdateid, item在用户将修改甘特图任务后触发
onAfterTaskDeleteid, item在用户删除甘特图任务后触发
onAfterTaskDragid, mode, e在用户完成拖动并释放鼠标按钮后触发,移动滑块
onAfterLinkDeleteid, item删除连接任务的联系
onAfterLinkUpdateid, item修改连接项目关系
onBeforeLinkAddid, item新增连接项目关系
onLightboxSaveid, item弹窗新增修改

甘特图常用API方法

方法用例说明
gantt.serverList(‘数据集名称’,'数据集')gantt,serverList("数据集名称") 返回的数据集在甘特实例定义数据集,方便在甘特配置修改是调用
gantt.updateCollection('数据集名称',更新数据)gantt.updateCollection("staff", staffArr);更新数据集数据
gantt.render()更新gantt配置
gantt.clearAll()清空gantt配置
gantt.detachEvent(’事件名)重点因为切换页面甘特不会销毁,调用销毁后阻止事件反复调用
gantt.scrollTo(x,y)定位今日线功能需要,定位到某个位置
gantt.init()gantt.init(this.$refs.gantt);gantt初始化挂载节点
gantt.parse()gantt.parse({ data: [],links: [] })gantt挂载数据
gantt.getTask()gantt.getTask(id)gantt获取单个数据

甘特图功能

今日线与定位

javascript
  // 今日线
 createTodayLine() {
      var dateToStr = gantt.date.date_to_str("%Y年%M%d日");
      var markerId = gantt.addMarker({
        id: 'markerLine',
        start_date: new Date(),
        css: "today",
        text: "今日",
        title: dateToStr(new Date()) 
      });
      gantt.updateMarker(markerId);
    }
//定位到今日线
 changeToday() {
      this.$nextTick(() => {
        let ganTT = document.getElementsByClassName('gantt_marker today')
        gantt.scrollTo(ganTT[0].offsetLeft-300, null);
      })
 },

全屏(类F11)

scss
// 是否全屏
changeFull() {
   gantt.ext.fullscreen.toggle();
 },

搜索功能

typescript
//
<a-input allowClear v-model="searchTitle" placeholder="请输入任务名称"></a-input>
<a-button icon="search" type="primary" @click="searchDataClick">搜索</a-button>
 //点击按钮搜索
#methods
// 搜索判断数据
    hasSubstr(parentId,type){
      let task = gantt.getTask(parentId);
      if(type=='tilte'){
        if(task.text.toLowerCase().indexOf(this.searchTitle) !== -1)
          return true;
      }
      // }  
    },
    searchDataClick(){
      if(this.searchTitle  ){
        this.ganttEvent.onBeforeTaskDisplay=gantt.attachEvent("onBeforeTaskDisplay", (id, task)=>{
          if (this.hasSubstr(id,'tilte') ){ return true;}
            return false;
          });
          gantt.refreshData()
          gantt.render()
      }else{
        this.ganttEvent.onBeforeTaskDisplay=gantt.attachEvent("onBeforeTaskDisplay", (id, task)=>{
         return true
        })
        gantt.refreshData()
        gantt.render()
      }
    },

日期切换

ini
 // 切换 年 季 月 周 日视图 
    ganttChangeDateView(type) {
      switch (type) {
        case 'y':
          gantt.config.scale_unit = "year";
          gantt.config.step = 1;
          gantt.config.subscales = null;
          gantt.config.date_scale = "%Y年";
          gantt.templates.date_scale = null;
          break;

        case 'm':
          gantt.config.scale_unit = 'month';
          gantt.config.step = 1;
          gantt.config.date_scale = "%m月";
          gantt.templates.date_scale = null;
        
          break;
        case 'w':
          gantt.config.scale_unit = 'week';
          gantt.config.step = 1;
          gantt.config.date_scale = "第%w周";
          gantt.templates.date_scale = null;
       
          break;
        case 'd':
          gantt.config.scale_unit = 'day';
          gantt.config.step = 1;
          gantt.config.date_scale = "%m月%d日";
          gantt.templates.date_scale = null;
          gantt.config.subscales = null;
          
          break;
      }
      gantt.render();
    },

图例

feature

完整代码

kotlin
<template>
  <div style="height: 100%; width: 100%">
    <a-layout>
      <div class="content">
        <div style="margin: -5px 0px 5px;display: flex;justify-content: space-between;">
            <a-input allowClear v-model="searchTitle" placeholder="请输入任务名称"></a-input>
            <a-button icon="search" type="primary" @click="searchDataClick">搜索</a-button>
        </div>
        <!-- 中间 内容 -->
        <div class="centerContent">
          <!-- 甘特图 -->
          <div class="selectDate">
            <a-button @click="changeToday"></a-button>
            <!-- 日期切换 -->
            <a-select  default-value="d" style="width: 55px;margin-left: 5px;" @change="ganttChangeDateView">
              <a-select-option value="y"></a-select-option>
              <a-select-option value="m"></a-select-option>
              <a-select-option value="w"></a-select-option>
              <a-select-option value="d"></a-select-option>
            </a-select>
            <a-button style="margin-left: 5px;" @click="changeFull"><a-icon type="fullscreen" /></a-button>
          </div>
          <div class="rightGatt" style="min-height:calc(78vh - 50px - 5px );width: 100%;overflow: hidden;" ref="gantt">
          </div>
        </div>
      </div>
    </a-layout>
  </div>
</template>
<script>

import {
  gantt
} from '@/assets/js/dhtmlx';
import "@/assets/css/dhtmlxgantt.css";
import { util } from "@/components/utils/util.js"
import moment from 'moment'

export default {
  name: "ganttChart",
  data() {
    return {
      moment,
      timer: null,//定时
      item: {},//单行数据
      searchTitle: "",//搜索标题
      tasks: {
        data: [],//数据
        links: [],//关联项目数据
      },
      savetasks: {
        data: [],
        links: []
      },//暂存空数据
      ganttServerStaff:[],//设置gantt成员数据
      selectStaff: [],//下拉成员
      staff: [],//成员
      ganttEvent: {//销毁事件
      },
      urls: {
        staff: "",//项目成员
        tasklinks: '',//gantt数据
        linksEdit: '',//修改和新增连接
        linksDelete: '',//删除连接
        addTask: '',//新增项目PUT
        editTask: '',//编辑项目put请求 tid 
        deleteTask: '',//删除项目delete tid
      }
    }
  },
  watch: {
    searchTitle(newVal,oldVal){
      this.searchTitle = newVal;
    }
  },
  mounted() {
    this.axios.get(this.urls.staff, {
        params: { projectId:  this.$store.state.project_data.id },
      }).then(res => {
        let staffArr = [];
        res.data.code = 200 && res.data.result.forEach((item, index) => {
          staffArr[index] = {};
          staffArr[index].key = item.id;
          staffArr[index].label = item.realname;
        })
        this.selectStaff = res.data.result;
        // 补充gantt数据
        this.ganttServerStaff=staffArr;
       
      })
    this.$nextTick(()=>{
      this.ganttChangeEvent();//交互事件
      this.initGantt();//初始化
      
      this.createTodayLine();//今日线
      this.ganttServerList();//服务数据
    })
    this.onQuery();//查询数据
    this.ganttChangeDateView("d");//默认日格式
  },
  methods: {
    /*
      甘特图 
     */
    // 初始化gantt
    initGantt() {
      // 清空之前的配置
      // gantt.clearAll();
   
      // 启用动态加载
      gantt.config.branch_loading = true
      //日期格式化
      gantt.config.xml_date = "%Y-%m-%d";
      gantt.config.order_branch = true;
      gantt.config.order_branch_free = true;
      //左侧是否自适应
      gantt.config.autofit = true;
      gantt.config.drag_links = true;//连线
      gantt.config.readonly = false;  //只读
      gantt.config.date_scale = "%m月%d日"; //右侧显示列名
      gantt.config.layout = {//拖拽布局
        css: "gantt_container",
        rows: [
          {
            cols: [
              { view: "grid", id: "grid", scrollX: "scrollHor", scrollY: "scrollVer" },
              { resizer: true, width: 1 },
              { view: "timeline", id: "timeline", scrollX: "scrollHor", scrollY: "scrollVer" },
              { view: "scrollbar", scroll: "y", id: "scrollVer" }
            ]
          },
          { view: "scrollbar", scroll: "x", id: "scrollHor", height: 20 }
        ]
      };
      gantt.config.start_on_monday = true;
      gantt.config.work_time = true;
      gantt.config.fit_tasks = true;   //自动调整图表坐标轴区间用于适配task的长度
    

      //汉化
      gantt.locale = {
        date: {
          month_full: ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"],
          month_short: ["1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月"],
          day_full: ["星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"],
          day_short: ["日", "一", "二", "三", "四", "五", "六"]
        },
        labels: {
          dhx_cal_today_button: "今天",
          day_tab: "日",
          week_tab: "周",
          month_tab: "月",
          new_event: "新建日程",
          icon_save: "保存",
          icon_cancel: "关闭",
          icon_details: "详细",
          icon_edit: "编辑",
          icon_delete: "删除",
          confirm_closing: "请确认是否撤销修改!", //Your changes will be lost, are your sure?
          confirm_deleting: "是否删除计划?",
          section_description: "描述:",
          section_time: "时间范围:",
          section_type: "类型:",
          section_text: "计划名称:",
          section_test: "测试:",
          section_projectClass: "项目类型:",
          taskProjectType_0: "项目任务",
          taskProjectType_1: "普通任务",
          section_head: "负责人:",
          section_priority: '优先级:',
          taskProgress: '任务状态',
          taskProgress_0: "未开始",
          taskProgress_1: "进行中",
          taskProgress_2: "已完成",
          taskProgress_3: "已延期",
          taskProgress_4: "搁置中",
          section_template: 'Details',
          /* grid columns */
          column_text: "计划名称",
          column_start_date: "开始时间",
          column_duration: "持续时间",
          column_add: "",
          column_priority: "难度",
          /* link confirmation */
          link: "关联",
          confirm_link_deleting: "将被删除",
          message_ok:'确定',
          message_cancel:'取消',
          link_start: " (开始)",
          link_end: " (结束)",

          type_task: "任务",
          type_project: "项目",
          type_milestone: "里程碑",

          minutes: "分钟",
          hours: "小时",
          days: "天",
          weeks: "周",
          months: "月",
          years: "年"
        }
      }
      gantt.serverList("priority", [
        { key: 0, label: "最高" },
        { key: 1, label: "较高" },
        { key: 2, label: "普通" },
        { key: 3, label: "较低" },
        { key: 4, label: "最低" },
      ]);
      //左侧显示列名
      gantt.config.columns = [
        { name: "text", min_width:100,max_width:200, label:"任务", align: "left",resize: true,  tree: true, editor: { type: 'text', map_to: 'text' } },
        { name: "id", label: "", hide: true },
        { name: "start_date", label: "开始时间", width: 120, resize: true, align: "left" },
        {
          name: "head", min_width: 100, height: 40, label: "负责人",resize: true, align: "left",
          // editor: {
          //   map_to: "head_id", type: "select", options: gantt.serverList("staff"),
          // },
          template: (item) => {
            if (this.ganttDealById(gantt.serverList('staff'), item.head_id)) {
              return `<span class='userIcon' style='background-color:${item.color ? item.color : "#6666"}'>${this.ganttDealById(gantt.serverList('staff'), item.head_id).slice(0, 1)}</span>${this.ganttDealById(gantt.serverList('staff'), item.head_id)}`
            }
          }
        },
        // { name: "end_date", label: "结束时间", align: "center" },
        {
          name: "taskProgress", label: "任务状态", align: "center", min_width: 110,editor: {
            type: "select", map_to: "taskProgress", options: [
              { key: "0", label: gantt.locale.labels.taskProgress_0 },
              { key: "1", label: gantt.locale.labels.taskProgress_1 },
              { key: "2", label: gantt.locale.labels.taskProgress_2 },
              { key: "3", label: gantt.locale.labels.taskProgress_3 },
              { key: "4", label: gantt.locale.labels.taskProgress_4 },
            ],
          },
          template: function (obj) {
            let re = '';
            switch (obj.taskProgress) {
              case "0":
                re = `<div class='taskProgress color_bg_1' >未开始</div>`
                break;
              case "1":
                re = `<div class='taskProgress color_bg_2' >进行中</div>`
                break;
              case "2":
                re = `<div class='taskProgress color_bg_3'  >已完成</div>`
                break;
              case "3":
                re = `<div  class='taskProgress color_bg_4'>已延期</div>`
                break;
              case "4":
                re = `<div  class='taskProgress color_bg_5' >搁置中</div>`
                break;
            }
            return re

          }
        },

      ];

      //弹出层
      gantt.config.lightbox.sections = [
        { name: "text", height: 70, map_to: "text", type: "textarea", focus: true, width: "*" },
        {
          name: "time", height: 40, map_to: "auto", type: "duration",
          time_format: ["%Y", "%m", "%d"],
        },
        {
          name: "projectClass", height: 30, map_to: "proTemplate", type: "template",
         
        },
        {
          name: "head", height: 22, map_to: "head_id", type: "select", options: gantt.serverList('staff',[]),
        },
        { name: "description", height: 70, map_to: "description", type: "textarea" },
        {
          name: "priority", height: 40, map_to: "priority", type: "radio", options: gantt.serverList("priority")
        },

      ];

      gantt.config.smart_scales = true;
      gantt.plugins({
        click_drag: true,
        drag_timeline: true,// 拖动图
        marker: true,// 时间标记
        fullscreen: true,// 全屏
        tooltip: true,// 鼠标经过时信息
        undo: true // 允许撤销
      })
      gantt.init(this.$refs.gantt);
     
    },
  
  
    // gantt数据服务列表
    ganttServerList() {
      this.getProjectStaff();//获取项目成员
      // 项目难度
      gantt.serverList("priority", [
        { key: 0, label: "最高" },
        { key: 1, label: "较高" },
        { key: 2, label: "普通" },
        { key: 3, label: "较低" },
        { key: 4, label: "最低" },
      ]);
   
    },
    // gantt交互事件注册
    ganttChangeEvent() {
      // gantt渲染
      this.ganttEvent.onGanttReady= gantt.attachEvent("onGanttReady", ()=>{
         
          //弹窗标题 日期范围
          gantt.templates.task_time = function (start, end, task) {
            return "周期:" + moment(start).format('YYYY-MM-DD') + " 至 " + moment(end).format('YYYY-MM-DD');
          };
          // 浮窗
          gantt.templates.tooltip_text = (start, end, task) => {
            return "<b>项目名称:</b> " + task.text + "<br><b>负责人:</b>" + task.head + "<br/><b>开始时间:</b> "
              + moment(start).format('YYYY-MM-DD')
              + "<br/><b>结束时间:</b> "
              + moment(new Date(end).valueOf() - 1000*60*60*24 ).format('YYYY-MM-DD');
          }
          //弹窗标题 计划名称
          gantt.templates.task_text = function (start, end, task) {
            return task.text;
          };
          gantt.templates.timeline_cell_class = function (task, date) {
            if (!gantt.isWorkTime({ task: task, date: date })) {
              return "weekend";
            } else {
              return 'weekday'
            }
          };
          gantt.templates.task_end_date = (date)=>{
            return gantt.templates.task_date(this.moment(new Date(date.valueOf() - 1000*60*60*24)).format("YYYY-MM-DD")); 
          };
          gantt.templates.grid_date_format = (date, column)=>{
            if(column === "end_date"){
                return this.moment(new Date(date.valueOf() - 1000*60*60*24)).format("YYYY-MM-DD"); 
            }else{
                return this.moment(date).format("YYYY-MM-DD"); 
            }
          }
      });
      // 修改默认弹窗
      gantt.attachEvent("onBeforeLightbox",  (id)=> {
        var task = gantt.getTask(id);
        task.proTemplate = `${gantt.locale.labels.taskProjectType_0}`
        return true;
      });

      //添加后触发
      this.ganttEvent.onAfterTaskAdd = gantt.attachEvent("onAfterTaskAdd", (id, item) => {
        clearTimeout(this.timer)
        this.timer = setTimeout(() => {
          this.dealProject("add",item)
        }, 500)

      });
      // 修改任务
      this.ganttEvent.onAfterTaskUpdate = gantt.attachEvent("onAfterTaskUpdate", (id, data) => {
        clearTimeout(this.timer)
        this.timer = setTimeout(() => {
          this.dealProject("edit", data);
          gantt.render()
        }, 500)

      });
      // 删除项目
      this.ganttEvent.onAfterTaskDelete = gantt.attachEvent("onAfterTaskDelete", (id, data) => {
        clearTimeout(this.timer)
        this.timer = setTimeout(() => {
  
          this.dealProject("delete", data);
          gantt.render();
        }, 500)
      });
      // 移动项目
      this.ganttEvent.onAfterTaskDrag = gantt.attachEvent("onAfterTaskDrag", (id, mode, e) => {
        clearTimeout(this.timer)
        this.timer = setTimeout(() => {
          var task = gantt.getTask(id);
          this.dealProject("edit", task);
          gantt.render()
        }, 500)
      });
      // 用户完成拖动并释放鼠标
      this.ganttEvent.onAfterTaskChanged = gantt.attachEvent("onAfterTaskChanged", (id, mode, task) => {
        clearTimeout(this.timer)
        this.timer = setTimeout(() => {
          gantt.render();
        }, 500)
      });

      // 删除连接项目关系
      this.ganttEvent.onAfterLinkDelete = gantt.attachEvent("onAfterLinkDelete", (id, item) => {
        clearTimeout(this.timer)
        this.timer = setTimeout(() => {
      
          let param = Object.assign({}, { projectId:this.$store.state.project_data.id }, item)
          this.axios.$delete(this.urls.linksDelete, param).then(res => {
            res.code == 200 && this.$message.success("解除成功!")
            res.code != 200 && this.$message.error("解除失败!")
          })
          gantt.render();
        }, 500)
      });
      // 修改连接项目关系
      this.ganttEvent.onAfterLinkUpdate = gantt.attachEvent("onAfterLinkUpdate", (id, item) => {
        clearTimeout(this.timer)
        this.timer = setTimeout(() => {

          let param = Object.assign({}, { projectId:this.$store.state.project_data.id }, item)
          this.axios.$put(this.urls.linksEdit, param).then(res => {
            res.code == 200 && this.$message.success("关联成功!")
            res.code != 200 && this.$message.error("关联失败!")

          })
          gantt.render()
        }, 500)
      });
      // 新增连接项目关系
      this.ganttEvent.onBeforeLinkAdd = gantt.attachEvent("onBeforeLinkAdd", (id, item) => {
        this.timer = setTimeout(() => {
          clearTimeout(this.timer)
          let param = Object.assign({}, { projectId: this.$store.state.project_data.id }, item)
          this.axios.$put(this.urls.linksEdit, param).then(res => {
            res.code == 200 && this.$message.success("关联成功!");
            res.code != 200 && this.$message.error("关联失败!");
           
          })
          gantt.render()
        }, 20)

      });
      // 保存新增
      this.ganttEvent.onLightboxSave = gantt.attachEvent("onLightboxSave", (id, item) => {
        if (!item.text) {
          this.$message.error("请填写计划名称!");
          return false;
        }
        return true;
      });

    },
    // 处理id 对应名称label
    ganttDealById(list, id) {
      for (let i = 0; i < list.length; i++) {
        if (list[i].key == id)
          return list[i].label ;
      }
      return "";
    },
    // 切换 年 季 月 周 日视图 
    ganttChangeDateView(type) {
      switch (type) {
        case 'y':
          gantt.config.scale_unit = "year";
          gantt.config.step = 1;
          gantt.config.subscales = null;
          gantt.config.date_scale = "%Y年";
          gantt.templates.date_scale = null;
          break;

        case 'm':
          gantt.config.scale_unit = 'month';
          gantt.config.step = 1;
          gantt.config.date_scale = "%m月";
          gantt.templates.date_scale = null;
        
          break;
        case 'w':
          gantt.config.scale_unit = 'week';
          gantt.config.step = 1;
          gantt.config.date_scale = "第%w周";
          gantt.templates.date_scale = null;
       
          break;
        case 'd':
          gantt.config.scale_unit = 'day';
          gantt.config.step = 1;
          gantt.config.date_scale = "%m月%d日";
          gantt.templates.date_scale = null;
          gantt.config.subscales = null;
          
          break;
      }
      gantt.render();
    },
    // 今日线
    createTodayLine() {
      var dateToStr = gantt.date.date_to_str("%Y年%M%d日");
      var markerId = gantt.addMarker({
        id: 'markerLine',
        start_date: new Date(),
        css: "today",
        text: "今日",
        title: dateToStr(new Date()) 
      });
      gantt.updateMarker(markerId);
    },
    // 是否全屏
    changeFull() {
      gantt.ext.fullscreen.toggle();
    },
    // 定位到今日线
    changeToday() {
      this.$nextTick(() => {
        let ganTT = document.getElementsByClassName('gantt_marker today')
        gantt.scrollTo(ganTT[0].offsetLeft-300, null);
      })
    },
    /* 
    操作 
    */
    //  获取项目成员
    getProjectStaff(projectID) {
      this.axios.get(this.urls.staff, {
        params: { projectId: projectID ? projectID : this.$store.state.project_data.id },
      }).then(res => {
        let staffArr = [];
        res.data.code = 200 && res.data.result.forEach((item, index) => {
          staffArr[index] = {};
          staffArr[index].key = item.id;
          staffArr[index].label = item.realname;
        })
        this.selectStaff = res.data.result;
        // 补充gantt数据
        this.ganttServerStaff=staffArr;
        gantt.updateCollection("staff", staffArr);
        gantt.render()
      })
    },
    //计算进度
    evalProgess(start,end,update) {
      if (start && end && update) {
        let start_date = this.moment(start).format("YYYY-MM-DD");
        let end_date = this.moment(end).format("YYYY-MM-DD");
        let update_date = this.moment(update).format("YYYY-MM-DD");
        
        if((new Date(end_date) - new Date(update_date)>0)){
          let process= (new Date(update_date) - new Date(start_date)) / (new Date(end_date) - new Date(start_date));
          return process.toFixed(2)
        }else {
          return 0
        }
      }

      return 0
    },
    // 获取数据
    onQuery(param) {
      gantt.clearAll();
      gantt.parse(this.savetasks);
      gantt.render();
      param = Object.assign({}, { projectId: 
        this.$store.state.project_data.id ? this.$store.state.project_data.id :'1'
      }, param)
      this.axios.get(this.urls.tasklinks, {
        params: param
      }).then(res => {
        let result = res.data.result["taskList"];
        let pushArr = [];
        this.tasks.links = res.data.result["ganttchartList"];//连接项目
        this.tasks.data = [];
        result.forEach((item, index) => {
          this.tasks.data[index] = {};
          this.tasks.data[index].id = item.tid ? item.tid : '';//项目id
          this.tasks.data[index].text = item.title ? item.title : '空标题';//标题
          this.tasks.data[index].start_date = item.startTime;//开始时间
          // 负责人--成员
          this.tasks.data[index].head_id = item.headRole?.id?item.headRole?.id:'';//负责人id
          this.tasks.data[index].head = item.headRole?.realname ? item.headRole?.realname : '';//负责人
          this.tasks.data[index].progress = this.evalProgess(item.startTime,item.endTime,item.updateTime)//项目进展
          // 后台时间加一天 显示减一天 处理条形图时间左闭右开
          this.tasks.data[index].end_date = this.moment(new Date(item.endTime).valueOf() + 1000*60*60*24).format("YYYY-MM-DD");//结束时间
          // this.tasks.data[index].end_date = item.endTime;//结束时间
          this.tasks.data[index].priority = item.priority ? item.priority : '';//任务优先级
          this.tasks.data[index].projectClass = item.type ? item.type : '';//类型 0项目任务 1 普通任务

          this.tasks.data[index].taskProgress = item.taskStatus.toString();//任务状态
          this.tasks.data[index].description = item.describe ? item.describe : '';//描述
          this.tasks.data[index].color = item.stateDictionary.color ? item.stateDictionary.color : '';//颜色
          if (item.taskList && item.taskList.length != 0) {
            this.tasks.data[index].render = "split";//显示在单行
            this.tasks.data[index].open = true;//展开
            item.taskList.forEach((_item) => {
              pushArr.push(
                {
                  id: _item.tid,
                  text: _item.title ? _item.title : '空标题',
                  start_date: _item.startTime,
                  end_date: _item.endTime,
                  head_id: _item.headId,
                  head: _item.head,
                  priority: _item.priority,
                  projectClass: _item.type,
                  taskProgress: _item.taskStatus.toString(),
                  description: _item.describe,
                  parent: _item.mainTaskId,
                }
              );
            })
          }
        });
        this.tasks.data = this.tasks.data.concat(pushArr)
       
        this.$nextTick(() => {
          gantt.parse(this.tasks);
          gantt.render();
          gantt.refreshData();
        })

      })
    },

    // 项目新增 修改tid  删除tid
    dealProject(type, data) {
      let param = {};
      if (type != 'add') {
        param.tid = data.id;
        param.title = data.text;
        param.startTime = this.moment(data.start_date).format("YYYY-MM-DD");
        param.endTime = this.moment(data.end_date).format("YYYY-MM-DD");
      
        param.describe = data.description;
        param.priority = data.priority;
      
        param.head = data.head_id;
        param.taskStatus = data.taskProgress;
        param.projectId = this.$store.state.project_data.id;
      } else {
       
        param.title = data.text;
        param.startTime = this.moment(data.start_date).format("YYYY-MM-DD");
        param.endTime = this.moment(data.end_date).format("YYYY-MM-DD");
        
        param.describe = data.description;
        param.priority = data.priority;
      
        param.head = data.head_id;
        param.taskStatus = data.taskProgress;
        param.projectId = this.$store.state.project_data.id;
      }
      let formdata=new FormData();
      for(let i in param){
        formdata.append(i,param[i])
      }
      switch (type) {
        case "add"://新增
          this.axios.put(this.urls.addTask, formdata).then(res => {
           
            res.data.code == 200 && this.$message.success("新增成功!")
            res.data.code != 200 && this.$message.error("新增失败!")
          }).catch(err => {
            this.$message.error("请求失败!")
          })
          break;
        case "edit":
          this.axios.put(this.urls.editTask, formdata).then(res => {
            res.data.code == 200 && this.$message.success("修改成功!")
            res.data.code != 200 && this.$message.error("修改失败!")
          }).catch(err => {
            this.$message.error("请求失败!")
          })
          break;
        case "delete":
          this.axios.delete(this.urls.deleteTask, formdata).then(res => {
            res.data.code == 200 && this.$message.success("删除成功!")
            res.data.code != 200 && this.$message.error("删除失败!")
          }).catch(err => {
            this.$message.error("请求失败!")
          })
          break;

      }
    },
    selecthead(val){
      
      this.searchHead=val;//id
    },
    // 搜索判断数据
    hasSubstr(parentId,type){
      let task = gantt.getTask(parentId);
      if(type=='tilte'){
        if(task.text.toLowerCase().indexOf(this.searchTitle) !== -1)
          return true;
      }
      // }  
    },
    //点击按钮搜索
    searchDataClick(){
  
      if(this.searchTitle  ){
        this.ganttEvent.onBeforeTaskDisplay=gantt.attachEvent("onBeforeTaskDisplay", (id, task)=>{
          if (this.hasSubstr(id,'tilte') ){ return true;}
            return false;
          });
          gantt.refreshData()
          gantt.render()
      }else{
        this.ganttEvent.onBeforeTaskDisplay=gantt.attachEvent("onBeforeTaskDisplay", (id, task)=>{
         return true
        })
        gantt.refreshData()
        gantt.render()
      }
    },
    // 提交弹框
    handleOk() {

      if (this.modalTitle === 1) {
        this.editForm.validateFields((err, values) => {
          if (!err) {
            Array.isArray(values.participants) && (values.participants = values.participants.join());
            values = util.transformFields(values);
            let formdata = new FormData();
            for (let key in values) {
              formdata.append(key, values[key] ? values[key] : '');
            }
            this.axios.put(this.urls.addTask, formdata).then((res) => {
              if (res.data.code === 200) {
                this.visible = false;
                let msg = res.data.message
                this.$message.success(msg)
                this.editForm.resetFields()
                this.onQuery();
              }
            })
          }
        })
      }
    },
  },

  destroyed() {
    // 销毁gantt事件
    for (let i in this.ganttEvent) {
      gantt.detachEvent(this.ganttEvent[i])
    }
   gantt.ext.tooltips.tooltip.hide();
  }
}
</script>
<style scoped lang="less">
@import url(./gantt.css);
.ant-layout {
  background: #f8f9f9;
}

.ant-layout-header {
  background: #fdffff;
  color: rgb(29, 28, 28);
  border: 1px solid #dee0e0;
}

.header {
  // position: fixed;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.content {
  background: #fdffff;
  height: 99vh;
}

// 中间
.centerContent {
  position: relative;
  background: #fdffff;
  width: 100%;
  overflow-y: auto;
  display: flex;
  justify-content: space-between;
  .selectDate {
    width: 100px;
    position: fixed;
    top: 18%;
    right: 9%;
    z-index: 99;
    display: flex;
    justify-content: space-between;
  }
}
</style>

自定义修改gantt样式文件

css
.weekend {
  background: #fafafa !important;
}
.weekday{
  background: #fff;
}
.gantt_resource_task .gantt_task_content {
  color: inherit;
}

.gantt_resource_task .gantt_task_progress {
  background-color: rgba(33, 33, 33, 0.3);
}
.gantt_tree_content{
  color: #808080;
}
.gantt_cell:nth-child(1) .gantt_tree_content {
  border-radius: 16px;
  width: 100%;
  height: 80%;
  margin: 5% 0;
  line-height: 230%;
}
/* 今日线 */
.gantt_marker.today{
  background: #ffb121;
}
.gantt_cell,.gantt_grid_head_cell,.gantt_grid_data,.gantt_data_area,.gantt_scale_cell{
  background-color:#fff ;
}
/* 滑块 */
.gantt_task_content{
  color: #fff;
  font-size: 13px;
  font-weight: bold;
  outline: none;
}
/* 右边单元格 */
.gantt_task_line,.gantt_task_inline_color{
  border-radius: 10px;
  box-sizing: border-box;
  border-color: #fff !important;
}
.gantt_task_scale .gantt_scale_line{
  /* border-bottom: 1px solid #e6ebf2; */
}
.gantt_row, .gantt_task_row{
  /* border-bottom: none; */
}
/* 覆盖进度条 */
.gantt_task_line.gantt_task_inline_color .gantt_task_progress{
  opacity: none;
  /* background: repeating-linear-gradient(70deg, #666 0px, #666 10px, #fff 0px,#fff 20px); */
  /* animation: ani 1.5s ease-in-out 6; */
}
@keyframes ani {
    0%{
      background: repeating-linear-gradient(70deg, #666 0px, #666 10px, #fff 0px,#fff 20px);
    }
    100%{
      background: repeating-linear-gradient(70deg, #fff 0px, #fff 10px, #666 0px,#666 20px);
    }
}
.taskProgress{
  margin: 0 auto;
  margin-top: 5px;
  height: 24px;
  width: 65px;
  font-size: 12px;
  line-height: 24px;
  font-weight: bold;
  color: #f7fbfe;
  border-radius: 20px;
}
.color_bg_1{
  background-color:#60a3bc ;
}
.color_bg_2{
  background-color:#079992 ;
}
.color_bg_3{
  background-color:#78e08f ;
}
.color_bg_4{
  background-color:#e55039 ;
}
.color_bg_5{
  background-color:#f6b93b ;
}
.gantt_task_row .gantt_task_cell,.weekday{
  outline: none;
}
.gantt_grid_scale{
  background-color: #f7fbfe !important;
}
.gantt_task_row .gantt_selected .gantt_task_cell{
  background-color: none;
  border-right-color:none;
}
.gantt_grid_scale .gantt_grid_head_cell{
  font-weight: bold;
  font-size: 14px;
  border: none;
  color:#506270;
}
/* 滑动栏 */
/* 项目icon标 */
.gantt_tree_icon{
  width: 14px;
  margin-right: 2px;
  margin-left: -8px;
}
.gantt_tree_icon.gantt_file {
  /* 文件icon */
  background-image: url(../../assets/img/file.png);
}

.gantt_tree_icon.gantt_folder_open{
  /* 文件夹icon */
  background-image: none;
}

.gantt_tree_icon.gantt_open{
  /* 加号 */
  background-image: url(../../assets//img/project.png);
}

.gantt_tree_icon.gantt_close{
  /* 减号 */
  background-image: none;
}

.userIcon{
  display: inline-block;
  width: 25px;
  height: 25px;
  margin-right: 5px;
  text-align: center;
  line-height: 25px;
  color: #fafafa;
  border-radius: 50%;
}
#search{
  margin-left: 10px;
  outline: none;
  border: none;
  font-size: 12px;
  color: #666666;
}
/*定义滚动条宽高及背景,宽高分别对应横竖滚动条的尺寸*/
*::-webkit-scrollbar {
  width: 10px;
  height: 10px;
  background-color: rgba(255, 255, 255, 0);
}

/*定义滚动条的轨道,内阴影及圆角*/
*::-webkit-scrollbar-track {
  border-radius: 10px;
  background-color: rgba(230, 230, 230, 0.05);
}

*:hover::-webkit-scrollbar-track {
  background-color: rgba(230, 230, 230, 0.5);
}

/*定义滑块,内阴影及圆角*/

*::-webkit-scrollbar-thumb {
  height: 20px;
  border-radius: 10px;
  -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0);
  background-color: rgba(216, 216, 216, 0.4);
  transition: background-color 1s;
}

*:hover::-webkit-scrollbar-thumb {
  background-color: rgba(216, 216, 216, 1);
}

*::-webkit-scrollbar-thumb:hover {
  background-color: rgba(190, 190, 190, 1);
}

*::-webkit-scrollbar-thumb:active {
  background-color: rgba(160, 160, 160, 1);
}

附: Gantt - config配置信息

txt
gantt.config = {
	auto_scheduling_move_projects: '定义是否将整个项目移动(请参阅下面的详细信息)',
	preserve_scroll: '在重新绘制甘特图时保留垂直和水平滚动的当前位置',
	auto_scheduling_descendant_links: '允许或禁止创建从父任务(项目)到子任务的链接',
	task_date: '设置灯箱“时间段”部分中日期标签的格式',
	resource_render_empty_cells: '告诉资源时间线渲染元素并为未分配的单元格调用模板',
	tooltip_offset_x: '设置工具提示位置的右侧(如果为正)偏移',
	drag_links: '允许通过拖放创建依赖链接',
	root_id: '设置虚拟根元素的 id',
	readonly: '激活甘特图的只读模式',
	drag_multiple: '可以一次拖动多个选定的任务',
	scale_unit: '设置时间刻度的单位(X 轴)',
	task_scroll_offset: '设置距离时间轴左边框最近的任务的偏移量(以像素为单位)',
	fit_tasks: '甘特图自动扩展时间尺度以适应所有显示的任务',
	editor_types: '包含内联编辑器定义的对象',
	touch: '启用 / 禁用对甘特图的触摸支持',
	datastor: '一组数据存储方法',
	start_date: '设置时间刻度的起始值',
	details_on_create: '在通过单击“+”按钮创建新事件时打开灯箱',
	time_picker: '设置灯箱中时间下拉选择器的格式',
	layer_attribute: '设置任务层的 DOM 元素的属性名称',
	resource_attribute: '更改甘特图用来查找资源网格/时间线中的任务行所指的资源的属性的名称',
	min_column_width: '设置时间轴区域中列的最小宽度',
	version: '返回 dhtmlxGantt 的版本',
	quick_info_detached: '定义任务表单是从屏幕的左侧/右侧出现还是在所选任务附近出现',
	row_height: '设置表格行的默认高度',
	auto_scheduling_compatibility: '禁用任务的时间限制',
	keep_grid_width: '在调整列大小时保留初始网格的宽度',
	editable_property: '更改影响只读甘特图中任务/链接编辑能力的属性名称',
	lightbox: '指定灯箱对象',
	project_end: '指定项目的结束日期',
	touch_feedback: '在触摸设备上拖放之前/之后返回振动反馈',
	start_on_monday: '设置一周的开始日期',
	undo_actions: '设置撤消操作将恢复的操作',
	order_branch: '激活“分支”模式,允许在同一树级别内垂直重新排序任务',
	link_attribute: '设置将指定链接的 HTML 元素的 id 的属性的名称',
	link_line_width: '设置时间线区域中依赖链接的宽度',
	task_height: '设置时间线区域中任务栏的高度',
	rtl: '将甘特图切换到从右到左模式',
	tooltip_hide_timeout: '设置工具提示隐藏之前的时间长度,以毫秒为单位',
	container_resize_timeout: '指定在调整容器大小时重绘甘特图之前的延迟(以毫秒为单位)',
	utils: '各种辅助模块',
	scales: '定义时间刻度的配置设置',
	show_grid: '显示甘特图的网格区域',
	placeholder_task: '在任务列表的末尾添加一个空行以简化通过键盘编辑任务',
	open_tree_initially: '最初打开所有分支',
	drag_resize: '可以通过拖放来调整任务大小',
	env: '一组描述当前环境的标志',
	reorder_grid_columns: '可以通过拖放重新排列网格列 - 左侧树',
	smart_rendering: '为甘特图的任务和链接渲染启用智能渲染模式',
	keyboard_navigation_cells: '启用按单元格的键盘导航',
	resize_rows: '启用通过拖放调整行高的能力',
	sort: '启用表中的排序',
	drag_move: '允许通过拖放移动任务',
	drag_project: '允许拖放项目类型的项目',
	work_time: '可以在工作时间而不是日历时间计算任务的持续时间',
	button_left: '存储位于灯箱左下角的按钮集合',
	end_date: '设置时间刻度的结束值',
	constraint_types: '包含了所有可用的约束类型',
	branch_loading: '在甘特图中启用动态加载',
	auto_scheduling: '启用自动调度',
	task_grid_row_resizer_attribute: '',
	grid_resizer_attribute: '设置网格调整器的 DOM 元素的属性名称',
	min_grid_column_width: '在调整大小时设置网格的最小宽度(以像素为单位)',
	date_scale: '设置时间刻度的格式(X 轴)',
	auto_scheduling_strict: '启用自动调度模式,在这种模式下,任务将始终被重新安排到最早的日期',
	config: '定义日期、比例、控件的配置选项',
	deepcopy_on_parse: '定义甘特图是否对传递给 gantt.parse() 方法的数据对象执行深度复制',
	layout: '指定布局对象',
	quickinfo_buttons: '存储驻留在弹出任务的详细信息表单中的按钮集合',
	details_on_dblclick: '双击任务后打开灯箱',
	step: '设置时间刻度(X 轴)的步长',
	autoscroll: '在将任务或链接拖出当前浏览器屏幕时启用自动滚动',
	lightbox_additional_height: '增加灯箱的高度',
	project_start: '指定项目的开始日期',
	scroll_on_click: '指定在选择显示所选任务时是否滚动时间线区域',
	quickinfo_icons: '重新定义甘特图按钮的默认点击行为',
	calendar: '工作日历对象的接口',
	grid_width: '设置网格的宽度',
	date: '一组日期格式化方法',
	i18n: '一组用于甘特图本地化的方法',
	show_task_cells: '启用/禁用在(任务)图表区域中显示列边框',
	process_resource_assignments: '启用/禁用资源分配的解析',
	resource_property: '定义任务对象的属性,该对象存储与 resourceGrid/Timeline/Histogram/Calendar 关联的资源 ID',
	resource_assignment_store: '指定存储资源分配的数据存储的名称',
	date_format: '设置用于解析数据集中的数据并将日期发送回服务器的日期格式',
	container_resize_method: '定义甘特图是否应以时间间隔跟踪容器的大小调整',
	drag_lightbox: '可以通过标题拖动灯箱',
	show_chart: '显示甘特图的图表(时间线)区域',
	drag_mode: '存储可用拖放模式的类型',
	subscales: '指定第二个时间尺度(已弃用)',
	oldxml: '指定 dhtmlxGantt 1.0 的 XML 格式的序列化和解析',
	show_tasks_outside_timescale: '启用在甘特图中显示指定日期范围之外的任务',
	order_branch_free: '激活“分支”模式,允许在整个甘特图中重新排序任务',
	xml_date: '定义用于从数据集中解析数据并将数据发送到服务器的日期格式',
	autosize_min_width: '设置甘特图在水平“自动调整大小”模式下可以采用的最小宽度(以像素为单位)',
	button_right: '存储位于灯箱右下角的按钮集合',
	date_grid: '设置表格“开始时间”列中的日期格式',
	show_quick_info: '激活/禁用“quick_info”扩展(弹出任务的详细信息表单)',
	schedule_from_end: '启用反向调度',
	skins: '返回可用皮肤的对象',
	constants: '存储各种常量以减少代码中幻数的使用',
	multiselect: '在甘特图中启用/禁用多任务选择',
	redo: '启用甘特图的重做功能',
	duration_unit: '设置持续时间单位',
	touch_feedback_duration: '定义在触摸设备上拖放之前/之后振动反馈的持续时间(以毫秒为单位)',
	drag_timeline: '配置 drag_timeline 扩展的行为',
	drag_progress: '可以通过拖动进度旋钮来更改任务进度',
	horizontal_scroll_key: '通过 Shift|Alt|Meta 键 + 鼠标滚轮移动启用/禁用水平滚动',
	static_background_cells: '在 static_background 模式下启用突出显示的单元格的渲染',
	click_drag: '启用高级拖放',
	auto_scheduling_initial: '定义甘特图是否对数据加载/解析进行自动调度',
	grid_elastic_columns: '调整可滚动网格内列的宽度',
	autosize: '强制甘特图自动更改其大小以显示所有任务而无需滚动',
	bar_height: '设置时间线区域中任务栏的高度',
	ajax: '甘特图 ajax 模块',
	branch_loading_property: '指定任务有尚未从后端加载的子任务',
	templates: '定义甘特图中日期、标题、工具提示的格式模板',
	touch_drag: '定义用于区分长触摸手势和滚动手势的时间段(以毫秒为单位)',
	autoscroll_speed: '定义将任务或链接拖出当前浏览器屏幕时自动滚动的速度(以毫秒为单位)',
	show_errors: '在出现意外行为时启用显示错误警报',
	license: '返回 dhtmlxGantt 的许可证名称',
	calendar_property: '更改影响日历绑定到任务/任务组的属性的名称',
	prevent_default_scroll: '指定甘特容器是否应该阻止鼠标滚轮事件,或者应该将其传播到窗口元素',
	tooltip_offset_y: '设置工具提示位置的顶部(如果为正)偏移',
	show_links: '启用/禁用在甘特图中显示链接',
	inherit_scale_class: '指定子尺度是否默认使用 scale_cell_class 模板',
	scroll_size: '设置垂直(宽度)和水平(高度)滚动的大小',
	inline_editors_date_processing: '在编辑任务的开始/结束期间保持任务的持续时间不变',
	treeDatastore: '一组 treeDatastore 方法',
	select_task: '启用甘特图中的任务选择',
	xml: '指定 XML 序列化和解析',
	smart_scales: '指定仅在屏幕上呈现时间刻度的可见部分',
	wai_aria_attributes: '启用 WA:ARIA 支持以使屏幕阅读器可识别组件',
	wheel_scroll_sensitive: '指定鼠标滚轮滚动甘特图的速度',
	resource_calendars: '定义一组可以分配给特定资源(例如用户)的工作日历',
	external_render: '将外部组件渲染到 DOM 中',
	readonly_property: '更改影响任务/链接只读行为的属性的名称',
	duration_step: '设置与“duration”数据属性的一个单位对应的“gantt.config.duration_unit”单位数。',
	tooltip_timeout: '在显示任务的工具提示之前以毫秒为单位设置超时',
	show_markers: '在页面上显示/隐藏标记',
	scale_offset_minimal: '将最小比例单位(如果使用多个比例)设置为前导/关闭空白空间的间隔',
	ext: '一个存储各种扩展的对象',
	min_duration: '设置可以在调整大小期间为任务设置的最短持续时间(以毫秒为单位)。',
	keyboard_navigation: '在甘特图中启用键盘导航',
	json: '指定 JSON 序列化和解析',
	round_dnd_dates: '允许将任务的开始和结束日期四舍五入到最近的刻度线',
	grid_resizer_column_attribute: '设置列调整器的 DOM 元素的属性名称。该属性表示列的索引',
	locale: '甘特图的当前区域设置对象(特定于区域的标签)',
	keys: '定义甘特图的热键',
	skip_off_time: '从时间尺度隐藏非工作时间',
	auto_types: '自动将带有子任务的任务转换为项目,将没有子任务的项目转换回任务',
	time_step: '设置任务时间值的最小步长(以分钟为单位)',
	inherit_calendar: '定义任务是否应从其摘要父级继承工作日历',
	undo_types: '设置将应用撤消操作的实体类型',
	server_utc: '在将数据发送到服务器的同时将服务器端日期从 UTC 转换为本地时区(和向后)',
	show_unscheduled: '启用显示未计划的任务',
	cascade_delete: '允许的嵌套任务和链接级联删除',
	grid_resize: '通过拖动右侧网格的边框来调整网格的大小',
	right_work_time: '允许将任务的开始和结束日期调整为工作时间(拖动时)',
	min_task_grid_row_height: '设置在调整大小期间可以为任务设置的最小行高',
	static_background: '为时间线区域生成背景图像,而不是渲染实际的列和行行',
	csp: '定义日期格式化方法代码的内部实现',
	link_arrow_size: '设置链接箭头的大小',
	resource_store: '指定连接到 resourceGrid/resourceTimeline/resourceHistogram 视图的 dataStore 的名称',
	dynamic_resource_calendars: '启用将多个资源日历自动合并为一个',
	type_renderers: '重新定义负责显示不同类型任务的函数',
	scale_height: '设置时间刻度的高度和网格的标题',
	links: '存储链接依赖的类型',
	undo: '启用甘特图的撤消功能',
	task_attribute: '设置将指定任务的 HTML 元素的 id 的属性的名称',
	show_progress: '启用在任务栏中显示进度',
	columns: '配置表的列',
	initial_scroll: '设置时间线区域是否最初滚动以显示最早的任务',
	types: '存储灯箱结构的名称(用于不同类型的任务)',
	skin: '返回甘特图的当前皮肤',
	Wide_form: '将节及其标签设置在同一行',
	link_wrapper_width: '设置对点击敏感的区域的宽度(在链接上)',
	highlight_critical_path: '显示图表中的关键路径',
	multiselect_one_level: '指定多任务选择将在一个或任何级别内可用',
	undo_steps: '设置应由 undo 方法恢复的步骤数',
	autofit: '允许将网格的列自动调整为网格的宽度',
	open_split_tasks: '通过单击 +/- 按钮启用展开/折叠拆分任务的可能性'
}

用心去做高质量的内容网站,欢迎 star ⭐ 让更多人发现