Vue 甘特图 gantt 安装使用

Vue 甘特图 gantt 安装使用

(gantt-elastic)参考文章@shenjuncaci
(dhtmlx)参考文章@秃头的铲屎官
dhtmlx@官方文档

安装

复制代码
npm i dhtmlx-gantt

使用

创建一个容器

复制代码
  <div ref="gantt" class="gantt-container"></div>

引入依赖

复制代码
import { gantt } from "dhtmlx-gantt";
import "dhtmlx-gantt/codebase/dhtmlxgantt.css";

初始化及数据解析

复制代码
        //  初始化
          gantt.init(this.$refs.gantt)
          //  数据解析
          gantt.parse(this.tasks)

其他配置可根据自己需求参考官网添加。

dhtmlx 实操案例 (简易 demo)

html 复制代码
<template>
  <div class="container">
    <div class="select-wrap">
      <el-select v-model="value" placeholder="请选择" @change="selectChange">
        <el-option
          v-for="item in options"
          :key="item.value"
          :label="item.label"
          :value="item.value"
        >
        </el-option>
      </el-select>
    </div>
    <div ref="gantt" class="gantt-container"></div>
  </div>
</template>
<script>
  import { gantt } from "dhtmlx-gantt";
  import "dhtmlx-gantt/codebase/dhtmlxgantt.css";

  export default {
    name: "gantt",
    data() {
      return {
        tasks: {
          data: [],
        },
        options: [
          {
            value: "1",
            label: "全部",
          },
          {
            value: "2",
            label: "完成",
          },
          {
            value: "3",
            label: "正常",
          },
          {
            value: "4",
            label: "异常",
          },
          {
            value: "5",
            label: "未启动",
          },
        ],
        value: "1",
      };
    },
    methods: {
      //开始时间-结束时间参数
      DateDifference: function (strDateStart, strDateEnd) {
        var begintime_ms = Date.parse(
          new Date(strDateStart.replace(/-/g, "/"))
        ); //begintime 为开始时间
        var endtime_ms = Date.parse(new Date(strDateEnd.replace(/-/g, "/"))); // endtime 为结束时间
        var date3 = endtime_ms - begintime_ms; //时间差的毫秒数
        var days = Math.floor(date3 / (24 * 3600 * 1000));
        return days;
      },
      initData: function () {
        this.tasks.data = [
          {
            id: 1,
            text: "概念设计",
            start_date: "2020-04-08",
            duration: 10,
            open: true, //默认打开,
            toolTipsTxt: "xxx项目概念设计",
            progress: 0.6,
            status: "parent",
          },
          {
            toolTipsTxt: "xxx项目-项目启动会",
            text: "项目启动会-外部", // 任务名
            start_date: "2020-04-08", // 开始时间
            id: 11, // 任务id
            duration: 3, // 任务时长,从start_date开始计算
            parent: 1, // 父任务ID
            type: 1,
            progress: 0.5,
            status: "yellow",
          },
          {
            toolTipsTxt: "xxx项目-项目启动会议",
            text: "项目启动会-内部",
            start_date: "2020-04-11",
            id: 12,
            duration: 2,
            parent: 1,
            type: 2,
            progress: 0.6,
            status: "pink",
          },
          {
            toolTipsTxt: "xxx项目开工会",
            text: "项目开工会",
            start_date: "2020-04-13",
            id: 13,
            duration: 4,
            parent: 1,
            type: 3,
            progress: 1,
            status: "green",
          },
          {
            toolTipsTxt: "xxx项目-项目分析",
            text: "项目分析",
            start_date: "2020-04-13",
            id: 14,
            duration: 4,
            parent: 1,
            type: 4,
            progress: 0.6,
            status: "popular",
          },

          {
            id: 2,
            text: "方案设计",
            start_date: "2020-04-08",
            duration: 8,
            open: true,
            toolTipsTxt: "xxx方案设计",
            state: "default",
            // color:"#409EFF", //设置颜色
            progress: 0.6,
            status: "parent",
          },
          {
            toolTipsTxt: "xxx新项目原型图设计",
            text: "原型图设计",
            start_date: "2020-04-08",
            id: 21,
            duration: 2,
            parent: 2,
            type: 1,
            progress: 0.6,
            status: "yellow",
          },
          {
            toolTipsTxt: "xxx项目-项目设计图",
            text: "设计图设计",
            start_date: "2020-04-09",
            id: 22,
            duration: 2,
            parent: 2,
            type: 2,
            progress: 0.6,
            status: "pink",
          },
          {
            toolTipsTxt: "xxx项目-项目确认",
            text: "项目确认",
            start_date: "2020-04-11",
            id: 23,
            duration: 2,
            parent: 2,
            type: 3,
            progress: 1,
            status: "green",
          },
        ].map(function (current, ind, arry) {
          var newObj = {};
          if (current.type) {
            //存在type字段 说明非一级菜单,判断阶段的具体类型 设置不同颜色
            if (current.type == 1) {
              //冒烟
              newObj = Object.assign({}, current, {
                color: "#fcca02",
              });
            } else if (current.type == 2) {
              //单元
              newObj = Object.assign({}, current, {
                color: "#fec0dc",
              });
            } else if (current.type == 3) {
              //回归
              newObj = Object.assign({}, current, {
                color: "#62ddd4",
              });
            } else if (current.type == 4) {
              newObj = Object.assign({}, current, {
                color: "#d1a6ff",
              });
            }
          } else {
            //一级菜单是蓝色的
            newObj = Object.assign({}, current, {
              color: "#5692f0",
            });
          }

          return newObj;
        });
      },
      selectChange(val) {
        console.log(val);

        //测试用例
        var obj = {
          toolTipsTxt: "新增任务",
          text: "新增任务", // 任务名
          start_date: "2020-04-15", // 开始时间
          id: 24, // 任务id
          duration: 2, // 任务时长,从start_date开始计算
          parent: 2, // 父任务ID
          type: 4,
          progress: 0,
          status: "popular",
        };
        this.tasks.data.push(obj);

        // 数据解析
        gantt.parse(this.tasks);
        // 刷新数据
        gantt.refreshData();
      },
    },
    mounted() {
      this.initData();

      //自适应甘特图的尺寸大小, 使得在不出现滚动条的情况下, 显示全部任务
      gantt.config.autosize = true;
      //只读模式
      gantt.config.readonly = true;
      //是否显示左侧树表格
      gantt.config.show_grid = true;
      //表格列设置
      gantt.config.columns = [
        {
          name: "text",
          label: "阶段名字",
          tree: true,
          width: "280",
          onrender: function (task, node) {
            node.setAttribute(
              "class",
              "gantt_cell gantt_last_cell gantt_cell_tree " + task.status
            );
          },
        },
        {
          name: "duration",
          label: "时长",
          align: "center",
          template: function (obj) {
            return obj.duration + "天";
          },
          hide: true,
        },
      ];

      var weekScaleTemplate = function (date) {
        var dateToStr = gantt.date.date_to_str("%m %d");
        var endDate = gantt.date.add(
          gantt.date.add(date, 1, "week"),
          -1,
          "day"
        );
        var weekNum = gantt.date.date_to_str("第 %W 周");
        return weekNum(date);
      };
      var daysStyle = function (date) {
        var dateToStr = gantt.date.date_to_str("%D");
        if (dateToStr(date) == "六" || dateToStr(date) == "日")
          return "weekend";
        return "";
      };
      gantt.config.subscales = [
        {
          unit: "week",
          step: 1,
          template: weekScaleTemplate,
        },
        {
          unit: "day",
          step: 1,
          format: "%d",
        },
      ];

      gantt.plugins({
        tooltip: true,
      });
      gantt.attachEvent("onGanttReady", function () {
        var tooltips = gantt.ext.tooltips;
        gantt.templates.tooltip_text = function (start, end, task) {
          return (
            task.toolTipsTxt +
            "<br/>" +
            "阶段:" +
            task.text +
            "<br/>" +
            gantt.templates.tooltip_date_format(start)
          );
        };
      });

      //设置任务条进度内容
      gantt.templates.progress_text = function (start, end, task) {
        return (
          "<div style='text-align:left;color:#fff;padding-left:20px'>" +
          Math.round(task.progress * 100) +
          "% </div>"
        );
      };

      //任务条显示内容
      gantt.templates.task_text = function (start, end, task) {
        // return task.text + '(' + task.duration + '天)';
        return (
          "<div style='text-align:center;color:#fff'>" +
          task.text +
          "(" +
          task.duration +
          "天)" +
          "</div>"
        );
      };

      // gantt.templates.scale_cell_class = function(date) {
      //     /*if(date.getDay()== 0 || date.getDay()== 6){
      //       return "weekend";
      //     }*/
      //     return 'weekend'
      // }

      //任务栏周末亮色
      /*gantt.templates.task_cell_class = function(item,date){
              if(date.getDay()== 0 || date.getDay()== 6){
                return "weekend";
              }
            };*/

      //任务条上的文字大小 以及取消border自带样式
      gantt.templates.task_class = function (start, end, item) {
        return item.$level == 0 ? "firstLevelTask" : "secondLevelTask";
      };

      gantt.config.layout = {
        css: "gantt_container",
        cols: [
          {
            width: 280,
            min_width: 280,
            rows: [
              {
                view: "grid",
                scrollX: "gridScroll",
                scrollable: true,
                scrollY: "scrollVer",
              },
              {
                view: "scrollbar",
                id: "gridScroll",
                group: "horizontal",
              },
            ],
          },
          {
            resizer: true,
            width: 1,
          },
          {
            rows: [
              {
                view: "timeline",
                scrollX: "scrollHor",
                scrollY: "scrollVer",
              },
              {
                view: "scrollbar",
                id: "scrollHor",
                group: "horizontal",
              },
            ],
          },
          {
            view: "scrollbar",
            id: "scrollVer",
          },
        ],
      };

      //时间轴图表中,任务条形图的高度
      // gantt.config.task_height = 28
      //时间轴图表中,甘特图的高度
      // gantt.config.row_height = 36
      //时间轴图表中,如果不设置,只有行边框,区分上下的任务,设置之后带有列的边框,整个时间轴变成格子状。
      gantt.config.show_task_cells = true;
      //当task的长度改变时,自动调整图表坐标轴区间用于适配task的长度
      gantt.config.fit_tasks = true;
      gantt.config.min_column_width = 50;
      gantt.config.auto_types = true;
      gantt.config.xml_date = "%Y-%m-%d";
      gantt.config.scale_unit = "month";
      gantt.config.step = 1;
      gantt.config.date_scale = "%Y年%M";
      gantt.config.start_on_monday = true;
      gantt.config.scale_height = 90;
      gantt.config.autoscroll = true;
      gantt.config.calendar_property = "start_date";
      gantt.config.calendar_property = "end_date";
      gantt.config.readonly = true;
      gantt.i18n.setLocale("cn");

      // 初始化
      gantt.init(this.$refs.gantt);
      // 数据解析
      gantt.parse(this.tasks);
    },
  };
</script>
<style lang="scss">
  .firstLevelTask {
    border: none;

    .gantt_task_content {
      font-size: 13px;
    }
  }

  .secondLevelTask {
    border: none;
  }

  .thirdLevelTask {
    border: 2px solid #da645d;
    color: #da645d;
    background: #da645d;
  }

  .milestone-default {
    border: none;
    background: rgba(0, 0, 0, 0.45);
  }

  .milestone-unfinished {
    border: none;
    background: #5692f0;
  }

  .milestone-finished {
    border: none;
    background: #84bd54;
  }

  .milestone-canceled {
    border: none;
    background: #da645d;
  }

  html,
  body {
    margin: 0px;
    padding: 0px;
    height: 100%;
    overflow: hidden;
  }

  .container {
    height: 100%;
    width: 100%;
    position: relative;
    .gantt_grid_head_cell {
      padding-left: 20px;
      text-align: left !important;
      font-size: 14px;
      color: #333;
    }

    .select-wrap {
      position: absolute;
      top: 25px;
      z-index: 99;
      width: 90px;
      left: 180px;

      .el-input__inner {
        border: none;
      }
    }

    .left-container {
      height: 100%;
    }

    //   .parent {
    //     .gantt_tree_icon {
    //       &.gantt_folder_open {
    //         background-image: url(assets/gantt-icon.svg) !important;
    //       }
    //       &.gantt_folder_closed {
    //         background-image: url(assets/gantt-icon-up.svg) !important;
    //       }
    //     }
    //   }

    .green,
    .yellow,
    .pink,
    .popular {
      .gantt_tree_icon.gantt_file {
        background: none;
        position: relative;

        &::before {
          content: "";
          width: 10px;
          height: 10px;
          border-radius: 50%;
          position: absolute;
          left: 50%;
          top: 50%;
          transform: translate(-50%, -50%);
        }
      }
    }

    .green {
      .gantt_tree_icon.gantt_file {
        &::before {
          background: #84bd54;
        }
      }
    }

    .yellow {
      .gantt_tree_icon.gantt_file {
        &::before {
          background: #fcca02;
        }
      }
    }

    .pink {
      .gantt_tree_icon.gantt_file {
        &::before {
          background: #da645d;
        }
      }
    }

    .popular {
      .gantt_tree_icon.gantt_file {
        &::before {
          background: #d1a6ff;
        }
      }
    }
  }

  .left-container {
    height: 100%;
  }

  .gantt_task_content {
    text-align: left;
    padding-left: 10px;
  }
</style>

踩坑记录

样式不生效问题

再给 style 标签添加了 scoped 属性后 可能会出现样式不生效的问题

解决办法

  • scss 环境使用 ::v-deep进行样式穿透
  • less 环境使用 /deep/ 进行样式穿透
  • 删掉 scoped 属性。

甘特图数据未渲染

再调用接口的数据赋值后,甘特图的数据为渲染。

可能原因:

gant 在实例的时候 还没有获取到 data 数据

解决办法:

  • 使用 async await 在获取的数据之后,再让 gant 进行实例
  • 使用 this.$nextTick(()=>{}) 再拿到最新的数据后 进行更新。
javascript 复制代码
//  数据初始化处理的方法
   initData: function () {
      this.tasks.data = this.jsondata.map((item) => {
        return {
          id: item.id,
          text: item.workItem,
          start_date: transitionYmdTime(item.planStartDate),
          end_date: transitionYmdTime(item.planCompletedDate),
          open: true, // 默认打开,
          toolTipsTxt: item.workItem
        }
      })
    },
//  甘特图数据接口
 gantDataSearch() {
      let bodyData = { apqpKey: this.apqpKey }
      API.apqpPowerMeeting(bodyData).then((res) => {
        this.jsondata = res.workItems
        this.$nextTick(() => {
            // 获取到数据后 进行数据处理
          this.initData()
          // 使用gantt实例 进行 初始化
          gantt.init(this.$refs.gantt)
          // 使用gantt实例  数据解析
          gantt.parse(this.tasks)
        })
        console.log('gant数据', res)
      })
    }
相关推荐
mldong6 小时前
你的 Vue3 项目也能有钉钉同款审批流设计器:npm 装包,10 分钟画出第一条审批流
前端·vue.js
2分钟速写快排7 小时前
什么是 RAG?如何用 RAG 实现一个用户记忆?
前端·后端·ai编程
passerby60618 小时前
如何自己造一个时间处理库
前端·javascript·github
走到天涯海角9 小时前
react里面的长列表渲染优化
前端·react.js·前端框架
小羊没烦恼!9 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
北岛贰9 小时前
迷茫焦虑期,我做了一个带支付带官网的 AI 聊天虚拟恋人 App
前端·人工智能·后端
mayaairi11 小时前
Vue2 组件通讯(三):全局事件总线、PubSub、插槽与组件实例属性
前端·javascript·vue.js
kyriewen11 小时前
面试官问我:AI 都能写代码了,前端凭什么还值 25K
前端·javascript·人工智能
风骏时光牛马12 小时前
AI源码分析:拆解模型底层实现逻辑
前端
IT_陈寒12 小时前
React子组件莫名其妙重渲染?你可能漏了这个Hook
前端·人工智能·后端