参考地址:https://github.com/hql7/wl-gantt

安装:npm i wl-gantt --save 或 npm i wl-gantt -S

无需自定义时可用:

配置文件:
import wlGantt from 'wl-gantt'

import "wl-gantt/lib/wl-gantt.css"

Vue.use(wlGantt)


App.vue
<wlGantt></wlGantt>

 自定义:

所需文件:

src\util\array.js

src\pages\wl-gantt\index.vue

src\pages\wl-gantt\index.js

src\pages\wl-gantt\components\wl-contextmenu\index.vue

src\pages\wl-gantt\components\wl-contextmenu\index.js

注: util与pages中文件可直接在参考地址中获取

App.vue

<template>
  <div id="app">
    <wlGantt
      ref="wl-gantt-demo"
      lazy
      use-real-time
      use-check-column
      use-index-column
      end-date="2020-01-01"
      start-date="2019-09-01"
      date-type="monthAndDay"
      :data="data"
      :contextMenuOptions="contextMenuOptions"
      :expandRowKeys="expandRowKeys"
      @selection-change="selectionChange"
      @expand-change="expandChange"
      @timeChange="timeChange"
      @taskRemove="taskRemove"
      @preChange="preChange"
      @taskAdd="taskAdd"
    ></wlGantt>
  </div>
</template>

<script>
import wlGantt from "@/pages/wl-gantt";
export default {
  name: "App",
  components: {
    wlGantt,
  },
  data() {
    return {
      data: [
        {
          id: "1",
          pid: "0",
          name: "旅行",
          startDate: "2019-09-07",
          realStartDate: "2019-09-10",
          endDate: "2019-10-31",
          realEndDate: "2019-10-19",
          children: [
            {
              id: "1-1",
              pid: "1",
              name: "云台之间",
              startDate: "2019-09-10",
              realStartDate: "2019-09-10",
              endDate: "2019-09-13",
              realEndDate: "2019-09-13",
              children: [
                {
                  id: "1-1-1",
                  pid: "1-1",
                  name: "日落云巅",
                  startDate: "2019-09-10",
                  endDate: "2019-09-13",
                },
              ],
            },
            {
              id: "1-2",
              pid: "1",
              name: "天空之镜",
              startDate: "2019-09-17",
              endDate: "2019-09-22",
            },
            {
              id: "1-3",
              name: "蓬莱之岛",
              pid: "1",
              startDate: "2019-09-25",
              endDate: "2019-09-30",
            },
            {
              id: "1-4",
              pid: "1",
              name: "西塘之南",
              startDate: "2019-10-03",
              endDate: "2019-10-07",
            },
            {
              pid: "1",
              id: "1-5",
              name: "凤凰之缘",
              startDate: "2019-10-11",
              endDate: "2019-10-19",
            },
          ],
        },
      ], // 数据
      selected: [], // 选中数据
      contextMenuOptions: [
        { label: "任务名称", prop: "name" },
        { label: "开始时间", prop: "startDate" },
        { label: "结束时间", prop: "endDate" },
      ],
      expandRowKeys: ["1"]
    };
  },
  methods: {
    /**
     * 时间发生更改
     * row: Object 当前行数据c
     */
    timeChange(row) {
      console.log("时间修改:", row);
    },
    //
    /**
     * 前置任务发生更改
     * row: Object 当前行数据
     * oldval: [String, Array] 前置修改前的旧数据
     * handle: Boolean 是否用户编辑产生的改变
     */
    preChange(row, oldval, handle) {
      console.log("前置修改:", row, oldval, handle);
    },
    // 数表展开行
    expandChange(row, expanded) {
      console.log("展开行:", row, expanded);
    },
    // 多选选择
    selectionChange(val) {
      console.log("多选:", val);
    },
    // 删除任务
    taskRemove(item) {
      console.log("删除任务:", item);
    },
    // 添加任务
    taskAdd(item) {
      console.log("添加任务:", item);
      // 非懒加载方式直接设置子数据
      /* this.$set(
        item,
        "children",
        item.children.concat([
          {
            pid: item.id,
            id: "###",
            name: "一轮新月",
            startDate: "2019-10-11",
            endDate: "2019-10-19"
          }
        ])
      ); */
      this.$refs["wl-gantt-demo"].loadTreeAdd(item.id, [
        {
          pid: item.id,
          id: "###",
          name: "一轮新月",
          startDate: "2019-10-11",
          endDate: "2019-10-19",
        },
      ]);
    },
    // 懒加载
    lazyLoad(tree, treeNode, resolve) {
      setTimeout(() => {
        resolve([
          {
            id: "1-1-1",
            pid: tree.id,
            name: "日落云巅",
            startDate: "2019-09-10",
            endDate: "2019-09-13",
          },
        ]);
      }, 1000);
    },
  },
};
</script>

自定义样式需加入 important 

src\pages\wl-gantt\index.vue 

<template>
  <div class="wl-gantt" id="wl-gantt">
    <!-- 甘特图区 -->
    <el-table
      ref="wl-gantt"
      class="wl-gantt-table"
      :fit="fit"
      :size="size"
      :load="load"
      :lazy="lazy"
      :border="border"
      :data="selfData"
      :stripe="stripe"
      :height="height"
      :row-key="rowKey"
      :row-style="rowStyle"
      :class="dateTypeClass"
      :cell-style="cellStyle"
      :max-height="maxHeight"
      :tree-props="selfProps"
      :current-row-key="rowKey"
      :row-class-name="rowClassName"
      :cell-class-name="cellClassName"
      :expand-row-keys="expandRowKeys"
      :header-row-style="headerRowStyle"
      :header-cell-style="headerCellStyle"
      :default-expand-all="defaultExpandAll"
      :header-row-class-name="headerRowClassName"
      :highlight-current-row="highlightCurrentRow"
      :header-cell-class-name="headerCellClassName"
      @header-contextmenu="handleHeaderContextMenu"
      @selection-change="handleSelectionChange"
      @row-contextmenu="handleRowContextMenu"
      @contextmenu.native="handleContextmenu"
      @current-change="handleCurrentChange"
      @cell-mouse-enter="handleMouseEnter"
      @cell-mouse-leave="handleMouseLeave"
      @expand-change="handleExpandChange"
      @filter-change="handleFilterChange"
      @cell-dblclick="handleCellDbClick"
      @header-click="handleHeaderClick"
      @row-dblclick="handleRowDbClick"
      @sort-change="handleSortChange"
      @cell-click="handleCellClick"
      @select-all="handleSelectAll"
      @row-click="handleRowClick"
      @select="handleSelect"
    >
      <template v-if="!ganttOnly">
        <slot name="prv"></slot>
        <el-table-column
          v-if="useCheckColumn"
          fixed
          type="selection"
          width="55"
          align="center"
        ></el-table-column>
        <el-table-column
          v-if="useIndexColumn"
          fixed
          type="index"
          width="50"
          label="序号"
        ></el-table-column>
        <el-table-column
          fixed
          label="名称"
          min-width="200"
          class-name="name-col"
          :prop="selfProps.name"
          :formatter="nameFormatter"
          :show-overflow-tooltip="name_show_tooltip"
        >
          <template slot-scope="scope">
            <el-input
              v-if="self_cell_edit === '_n_m_' + scope.$index"
              v-model="scope.row[selfProps.name]"
              @change="nameChange(scope.row)"
              @blur="nameBlur()"
              size="medium"
              class="u-full"
              ref="wl-name"
              placeholder="请输入名称"
            ></el-input>
            <strong v-else class="h-full">
              <span @click="cellEdit('_n_m_' + scope.$index, 'wl-name')">
                {{
                  nameFormatter
                    ? nameFormatter(
                        scope.row,
                        scope.column,
                        scope.treeNode,
                        scope.$index
                      )
                    : scope.row[selfProps.name]
                }}
              </span>
              <span class="name-col-edit">
                <i
                  class="el-icon-remove-outline name-col-icon task-remove"
                  @click="emitTaskRemove(scope.row)"
                ></i>
                <i
                  class="el-icon-circle-plus-outline name-col-icon task-add"
                  @click="emitTaskAdd(scope.row)"
                ></i>
              </span>
            </strong>
          </template>
        </el-table-column>
        <el-table-column
          :resizable="false"
          fixed
          width="160"
          align="center"
          :prop="selfProps.startDate"
          label="开始日期"
        >
          <template slot-scope="scope">
            <el-date-picker
              v-if="self_cell_edit === '_s_d_' + scope.$index"
              v-model="scope.row[selfProps.startDate]"
              @change="startDateChange(scope.row)"
              @blur="self_cell_edit = null"
              type="date"
              size="medium"
              class="u-full"
              :clearable="false"
              ref="wl-start-date"
              value-format="yyyy-MM-dd"
              placeholder="请选择开始日期"
            ></el-date-picker>
            <div
              v-else
              class="h-full"
              @click="cellEdit('_s_d_' + scope.$index, 'wl-start-date')"
            >
              {{ timeFormat(scope.row[selfProps.startDate]) }}
            </div>
          </template>
        </el-table-column>
        <el-table-column
          fixed
          :resizable="false"
          width="160"
          align="center"
          :prop="selfProps.endDate"
          label="结束日期"
        >
          <template slot-scope="scope">
            <el-date-picker
              v-if="self_cell_edit === '_e_d_' + scope.$index"
              v-model="scope.row[selfProps.endDate]"
              @change="endDateChange(scope.row)"
              @blur="self_cell_edit = null"
              type="date"
              size="medium"
              class="u-full"
              :clearable="false"
              ref="wl-end-date"
              value-format="yyyy-MM-dd"
              placeholder="请选择结束日期"
            ></el-date-picker>
            <div
              v-else
              class="h-full"
              @click="cellEdit('_e_d_' + scope.$index, 'wl-end-date')"
            >
              {{ timeFormat(scope.row[selfProps.endDate]) }}
            </div>
          </template>
        </el-table-column>
        <el-table-column
          v-if="usePreColumn"
          align="center"
          min-width="140"
          label="前置任务"
          show-overflow-tooltip
          :prop="selfProps.endDate"
        >
          <template slot-scope="scope">
            <!-- @blur="self_cell_edit = null" @blur="preEditBlur" -->
            <el-select
              v-if="self_cell_edit === '_p_t_' + scope.$index"
              @change="preChange"
              v-model="scope.row[selfProps.pre]"
              collapse-tags
              :multiple="preMultiple"
              ref="wl-pre-select"
              placeholder="请选择前置任务"
            >
              <el-option
                v-for="item in pre_options"
                :key="item[selfProps.id]"
                :label="item[selfProps.name]"
                :value="item[selfProps.id]"
              ></el-option>
            </el-select>
            <div
              v-else
              class="h-full"
              @click="
                preCellEdit(scope.row, '_p_t_' + scope.$index, 'wl-pre-select')
              "
            >
              {{ preFormat(scope.row) }}
            </div>
          </template>
        </el-table-column>
        <slot></slot>
      </template>
      <!-- year and mouth gantt -->
      <template v-if="self_date_type === 'yearAndMonth'">
        <el-table-column
          :resizable="false"
          v-for="year in ganttTitleDate"
          :label="year.name"
          :key="year.id"
        >
          <el-table-column
            class-name="wl-gantt-item"
            v-for="month in year.children"
            :resizable="false"
            :key="month.id"
            :label="month.name"
          >
            <template slot-scope="scope">
              <div
                :class="dayGanttType(scope.row, month.full_date, 'months')"
              ></div>
              <div
                v-if="useRealTime"
                :class="realDayGanttType(scope.row, month.full_date, 'months')"
              ></div>
            </template>
          </el-table-column>
        </el-table-column>
      </template>
      <!-- year and week gantt -->
      <template v-else-if="self_date_type === 'yearAndWeek'">
        <el-table-column
          :resizable="false"
          v-for="i in ganttTitleDate"
          :label="i.full_date"
          :key="i.id"
        >
          <el-table-column
            class-name="wl-gantt-item"
            v-for="t in i.children"
            :resizable="false"
            :key="t.id"
            :label="t.name"
          >
            <template slot-scope="scope">
              <div :class="dayGanttType(scope.row, t.full_date, 'week')"></div>
              <div
                v-if="useRealTime"
                :class="realDayGanttType(scope.row, t.full_date, 'week')"
              ></div>
            </template>
          </el-table-column>
        </el-table-column>
      </template>
      <!-- mouth and day gantt -->
      <template v-else>
        <el-table-column
          :resizable="false"
          v-for="i in ganttTitleDate"
          :label="i.full_date"
          :key="i.id"
        >
          <el-table-column
            class-name="wl-gantt-item"
            v-for="t in i.children"
            :resizable="false"
            :key="t.id"
            :label="t.name"
          >
            <template slot-scope="scope">
              <div :class="dayGanttType(scope.row, t.full_date)"></div>
              <div
                v-if="useRealTime"
                :class="realDayGanttType(scope.row, t.full_date)"
              ></div>
            </template>
          </el-table-column>
        </el-table-column>
      </template>
    </el-table>
    <!-- 组件区 -->
    <context-menu
      :visible.sync="contextMenu.show"
      :x="contextMenu.x"
      :y="contextMenu.y"
      :menuList="contextMenu.data"
    ></context-menu>
  </div>
</template>

<script>
import dayjs from "dayjs"; // 导入日期js
const uuidv4 = require("uuid/v4"); // 导入uuid生成插件
import isBetween from "dayjs/plugin/isBetween";
dayjs.extend(isBetween);
import {
  flattenDeep,
  getMax,
  flattenDeepParents,
  regDeepParents,
} from "@/util/array.js"; // 导入数组操作函数
import ContextMenu from "./components/wl-contextmenu";
import "@/assets/css/clear.css";

export default {
  name: "WlGantt",
  components: { ContextMenu },
  data() {
    return {
      self_start_date: "", // 项目开始时间
      self_end_date: "", // 项目结束时间
      self_data_list: [], // 一维化后的gantt数据
      self_date_type: "", // 自身日期类型
      self_id: 1, // 自增id
      self_cell_edit: null, // 正在编辑的单元格
      self_dependent_store: [], // 自身依赖库
      multipleSelection: [], // 多选数据
      currentRow: null, // 单选数据
      pre_options: [], // 可选前置节点
      name_show_tooltip: true, // 名称列是否开启超出隐藏
      update: true, // 更新视图
      selectionList: [], // 多选选中数据
      contextMenu: {
        show: false, // 显示
        x: 0, // 坐标点
        y: 0, // 坐标点
        data: [], // 整理后要显示的数据
      }, // 右键菜单配置项
    };
  },
  props: {
    /**
     * @name 右键扩展菜单
     * @param {String} label 展示名称
     * @param {String} prop 绑定的字段
     * @param {String} icon 可选 字体图标class
     */
    contextMenuOptions: Array,
    // gantt数据
    data: {
      type: Array,
      default: () => {
        return [];
      },
    },
    // 日期类型
    dateType: {
      type: String,
      default: "yearAndMonth", // monthAndDay,yearAndMonth,yearAndWeek
    },
    // 树表配置项
    props: Object,
    // 开始日期
    startDate: {
      type: [String, Object],
      required: true,
    },
    // 结束时间
    endDate: {
      type: [String, Object],
      required: true,
    },
    // 是否使用实际开始时间、实际结束时间
    useRealTime: {
      type: Boolean,
      default: false,
    },
    // 是否检查源数据符合规则,默认开启,如果源数据已遵循project规则,可设置为false以提高性能
    checkSource: {
      type: Boolean,
      default: true,
    },
    // 废弃:反而会因为频繁的判断而影响性能
    // 是否生成自增id并组成parents来满足树链的查询条件,如果数据本身已有自增id,可设置为false以提高性能
    // 如果设置为false,则数据内必须包含自增id字段:identityId,parents字段必须以`,`分割。
    // 字段名可通过props配置,自增id必须唯一并尽可能的短,如1,2,3...,parents应为祖先自增id通过`,`拼接直至父级
    recordParents: {
      type: Boolean,
      default: true,
    },
    // 是否使用id来作为自增id,如果是请保证id本来就简短的数字型而不是较长的字符串或guid
    treatIdAsIdentityId: {
      type: Boolean,
      default: false,
    },
    // 自动变化gantt标题日期模式
    autoGanttDateType: {
      type: Boolean,
      default: true,
    },
    nameFormatter: Function, // 名称列的格式化内容函数
    // 是否使用内置前置任务列
    usePreColumn: {
      type: Boolean,
      default: false,
    },
    // 是否使用复选框列
    useCheckColumn: {
      type: Boolean,
      default: false,
    },
    // 是否使用序号列
    useIndexColumn: {
      type: Boolean,
      default: false,
    },
    // 是否可编辑
    edit: {
      type: Boolean,
      default: true,
    },
    // 复选框是否父子关联
    parentChild: {
      type: Boolean,
      default: true,
    },
    // 是否开启前置任务多选 如果开启多选则pre字段必须是Array,否则可以是Number\String
    preMultiple: {
      type: Boolean,
      default: true,
    },
    preFormatter: Function, // 前置任务列的格式化内容函数
    // 空单元格占位符
    emptyCellText: {
      type: String,
      default: "-",
    },
    // 多选时,是否可以点击行快速选中复选框
    /* quickCheck: {
      type: Boolean,
      default: false
    }, */
    ganttOnly: {
      type: Boolean,
      default: false,
    }, // 是否只显示图形
    // ---------------------------------------------以下为el-table Attributes--------------------------------------------
    defaultExpandAll: {
      type: Boolean,
      default: false,
    }, // 是否全部展开
    rowKey: {
      type: String,
      default: "id",
    }, // 必须指定key来渲染树形数据
    height: [String, Number], // 列表高度
    maxHeight: [String, Number], // 列表最大高度
    stripe: {
      type: Boolean,
      default: false,
    }, // 是否为斑马纹
    highlightCurrentRow: {
      type: Boolean,
      default: false,
    }, // 是否要高亮当前行
    border: {
      type: Boolean,
      default: true,
    }, // 是否带有纵向边框
    fit: {
      type: Boolean,
      default: true,
    }, // 列的宽度是否自撑开
    size: String, // Table 的尺寸
    rowClassName: Function, // 行的 className 的回调方法
    rowStyle: Function, // 行的 style 的回调方法
    cellClassName: Function, // 单元格的 className 的回调方法
    cellStyle: Function, // 单元格的 style 的回调方法
    headerRowClassName: {
      type: [Function, String],
      default: "wl-gantt-header",
    }, // 表头行的 className 的回调方法
    headerRowStyle: [Function, Object], // 表头行的 style 的回调方法
    headerCellClassName: [Function, String], // 表头单元格的 className 的回调方法
    headerCellStyle: [Function, Object], // 表头单元格的 style 的回调方法
    expandRowKeys: Array, // 可以通过该属性设置 Table 目前的展开行
    lazy: {
      type: Boolean,
      default: false,
    }, // 是否懒加载子节点数据
    load: Function, // 加载子节点数据的函数,lazy 为 true 时生效
    // 是否使用一维数据组成树
    /* arrayToTree: {
      type: Boolean,
      default: false
    } */
  },
  computed: {
    // 甘特图标题日期分配
    ganttTitleDate() {
      // 分解开始和结束日期
      let start_date_spilt = dayjs(this.self_start_date)
        .format("YYYY-M-D")
        .split("-");
      let end_date_spilt = dayjs(this.self_end_date)
        .format("YYYY-M-D")
        .split("-");
      let start_year = Number(start_date_spilt[0]);
      let start_mouth = Number(start_date_spilt[1]);
      let end_year = Number(end_date_spilt[0]);
      let end_mouth = Number(end_date_spilt[1]);
      // 自动更新日期类型以适应任务时间范围跨度
      if (this.autoGanttDateType) {
        // 计算日期跨度
        let mouth_diff = this.timeDiffTime(
          this.self_start_date,
          this.self_end_date,
          "months"
        );
        if (mouth_diff > 12) {
          // 12个月以上的分到yearAndMouth
          this.setDataType("yearAndMonth");
        } else if (mouth_diff > 2) {
          // 2个月以上的分到yearAndWeek
          this.setDataType("yearAndWeek");
        } else {
          this.setDataType("monthAndDay");
        }
      }
      // 不自动更新日期类型,以dateType固定展示
      if (this.self_date_type === "yearAndWeek") {
        return this.yearAndWeekTitleDate(
          start_year,
          start_mouth,
          end_year,
          end_mouth
        );
      } else if (this.self_date_type === "monthAndDay") {
        return this.mouthAndDayTitleDate(
          start_year,
          start_mouth,
          end_year,
          end_mouth
        );
      } else {
        return this.yearAndMouthTitleDate(
          start_year,
          start_mouth,
          end_year,
          end_mouth
        );
      }
    },
    // 数据
    selfData() {
      let _data = this.data || [];
      // 生成一维数据
      this.setListData();
      // 处理源数据合法性
      this.handleData(_data);
      // 处理前置依赖
      this.handleDependentStore();
      return _data;
    },
    // 树表配置项
    selfProps() {
      return {
        hasChildren: "hasChildren", // 字段来指定哪些行是包含子节点
        children: "children", // children字段来表示有子节点
        name: "name", // 任务名称字段
        id: "id", // id字段
        pid: "pid", // pid字段
        startDate: "startDate", // 开始时间字段
        realStartDate: "realStartDate", // 实际开始时间字段
        endDate: "endDate", // 结束时间字段
        realEndDate: "realEndDate", // 实际结束时间字段
        identityId: "identityId",
        parents: "parents",
        pre: "pre", // 前置任务字段【注意:如果使用recordParents,则pre值应是目标对象的identityId字段(可配置)】
        ...this.props,
      };
    },
    // 根据日期类型改样式
    dateTypeClass() {
      if (this.self_date_type === "yearAndMonth") {
        return "year-and-month";
      } else if (this.self_date_type === "monthAndDay") {
        return "month-and-day";
      } else if (this.self_date_type === "yearAndWeek") {
        return "year-and-week";
      }
      return "";
    },
  },
  methods: {
    // 设置dateType
    setDataType(type) {
      this.self_date_type = type;
    },
    // 生成一维数据
    setListData() {
      this.self_data_list = flattenDeep(this.data, this.selfProps.children);
    },
    /**
     * 开始时间改变
     * row: object 当前行数据
     */
    startDateChange(row) {
      // 如果将开始时间后移,结束时间也应后移
      let _delay = this.timeIsBefore(
        row._oldStartDate,
        row[this.selfProps.startDate]
      );
      if (_delay) {
        row[this.selfProps.endDate] = this.timeAdd(
          row[this.selfProps.endDate],
          row._cycle
        );
      }
      // 如果开始早于项目开始,则把项目开始提前
      let _early_project_start = this.timeIsBefore(
        row[this.selfProps.startDate],
        this.self_start_date
      );
      if (_early_project_start) {
        this.self_start_date = row[this.selfProps.startDate];
      }
      this.emitTimeChange(row);
    },
    /**
     * 结束时间改变
     * row: object 当前行数据
     */
    endDateChange(row) {
      this.emitTimeChange(row);
      // 如果开始晚于结束,提示
      /* if (
        this.timeIsBefore(
          row[this.selfProps.endDate],
          row[this.selfProps.startDate]
        )
      ) {
        row[this.selfProps.startDate] = row._oldStartDate;
        this.$message({
          showClose: true,
          message: "开始时间不可以晚于结束时间",
          type: "error"
        });
        return;
      } */
    },
    /**
     * 前置任务改变
     * row: object 当前行数据
     */
    preChange(row) {
      this.emitTimeChange(row);
      this.self_cell_edit = null;
      // 如果开始晚于结束,提示
      /* if (
        this.timeIsBefore(
          row[this.selfProps.endDate],
          row[this.selfProps.startDate]
        )
      ) {
        row[this.selfProps.startDate] = row._oldStartDate;
        this.$message({
          showClose: true,
          message: "开始时间不可以晚于结束时间",
          type: "error"
        });
        return;
      } */
    },
    /**
     * 前置任务内容格式化函数
     * data:[String, Array] 前置任务
     */
    preFormat(row) {
      // 自定义格式化前置任务列函数
      if (this.preFormatter) {
        return this.preFormatter(row);
      }
      let data = row[this.selfProps.pre];
      if (!data) return this.emptyCellText;
      if (Array.isArray(data)) {
        if (data.length === 0) return this.emptyCellText;
        let _pre_text = "";
        data.forEach((i) => {
          let _act = this.self_data_list.find(
            (t) => t[this.selfProps.id] === i
          );
          if (_act) _pre_text += `${_act[this.selfProps.name]},`;
        });
        return _pre_text;
      }
      let _act = this.self_data_list.find((t) => t[this.selfProps.id] === data);
      return _act ? _act[this.selfProps.name] : this.emptyCellText;
    },
    // 前置下拉框失去焦点事件,change会触发blur,如果不延时则chang失效,如果延时则也只是延迟触发,会造成选一次就关闭无法多选
    /* preEditBlur(){
      setTimeout(()=>{
        this.self_cell_edit = null
      },500)
    }, */
    /**
     * 前置任务编辑
     */
    preCellEdit(row, key, ref) {
      /* let _parents = row._parents.split(","); // 父祖节点不可选
      let _children = row._all_children.map(i => i._identityId); // 子孙节点不可选
      let _self = row[this.selfProps.id]; // 自己不可选
      let _parents_and_children = _children.concat(_parents, [_self]);
      let filter_options = this.self_data_list.filter(
        i => !_parents_and_children.some(t => t == i._identityId)
      );
      this.pre_options = filter_options; */
      if (!this.edit) return;
      this.pre_options = [];
      this.self_data_list.forEach((i) => {
        if (i[this.selfProps.id] !== row[this.selfProps.id]) {
          this.pre_options.push({ ...i, [this.selfProps.children]: null });
        }
      });
      // 再剔除所有前置链涉及到的节点
      this.deepFindToSelf(row);
      // 调用单元格编辑
      this.cellEdit(key, ref);
    },
    /**
     * 找出to为当前元素的form,并将form作为to继续查找
     * item: Object 当前元素
     * targets: Array 需要过滤的数据(废弃)
     */
    deepFindToSelf(item) {
      let _parents = item._parents.split(","); // 父祖节点不可选
      let _children = item._all_children.map((i) => i._identityId); // 子孙节点不可选
      let _parents_and_children = _children.concat(_parents);
      this.pre_options = this.pre_options.filter(
        (i) => !_parents_and_children.some((t) => t == i._identityId)
      );
      this.self_dependent_store.forEach((i) => {
        let _tag = this.preMultiple
          ? i.to.some((t) => t[this.selfProps.id] === item[this.selfProps.id])
          : i.to[this.selfProps.id] === item[this.selfProps.id];
        if (_tag) {
          this.pre_options = this.pre_options.filter(
            (t) => t[this.selfProps.id] !== i.form[this.selfProps.id]
          );
          this.deepFindToSelf(i.form);
        }
      });
    },
    /**
     * 单元格编辑
     * key: string 需要操作的单元格key
     * ref:object 需要获取焦点的dom
     */
    cellEdit(key, ref) {
      if (!this.edit) return;
      if (ref === "wl-name") {
        this.name_show_tooltip = false;
      }
      this.self_cell_edit = key;
      this.$nextTick(() => {
        this.$refs[ref].focus();
      });
    },
    // 名称编辑事件
    nameChange(row) {
      this.self_cell_edit = null;
      this.name_show_tooltip = true;
      this.emitNameChange(row);
    },
    // 名称列编辑输入框blur事件
    nameBlur() {
      this.self_cell_edit = null;
      this.name_show_tooltip = true;
    },
    // 以下是表格-日期-gantt生成函数----------------------------------------生成gantt表格-------------------------------------
    /**
     * 年-月模式gantt标题
     * start_year: 起始年
     * start_mouth:起始月
     * end_year:结束年
     * end_mouth:结束月
     */
    yearAndMouthTitleDate(start_year, start_mouth, end_year, end_mouth) {
      // 日期数据盒子
      let dates = [
        {
          name: `${start_year}年`,
          date: start_year,
          id: uuidv4(),
          children: [],
        },
      ];
      // 处理年份
      let year_diff = end_year - start_year;
      // 年间隔小于一年
      if (year_diff === 0) {
        let isLeap = this.isLeap(start_year); // 是否闰年
        let mouths = this.generationMonths(
          start_year,
          start_mouth,
          end_mouth + 1,
          isLeap,
          false
        ); // 处理月份
        dates[0].children = mouths;
        return dates;
      }
      // 处理开始月份
      let startIsLeap = this.isLeap(start_year);
      let start_mouths = this.generationMonths(
        start_year,
        start_mouth,
        13,
        startIsLeap,
        false
      );
      // 处理结束月份
      let endIsLeap = this.isLeap(end_year);
      let end_mouths = this.generationMonths(
        end_year,
        1,
        end_mouth + 1,
        endIsLeap,
        false
      );
      // 年间隔等于一年
      if (year_diff === 1) {
        dates[0].children = start_mouths;
        dates.push({
          name: `${end_year}年`,
          date: end_year,
          children: end_mouths,
          id: uuidv4(),
        });
        return dates;
      }
      // 年间隔大于1年
      if (year_diff > 1) {
        dates[0].children = start_mouths;
        for (let i = 1; i < year_diff; i++) {
          let item_year = start_year + i;
          let isLeap = this.isLeap(item_year);
          let month_and_day = this.generationMonths(
            item_year,
            1,
            13,
            isLeap,
            false
          );
          dates.push({
            name: `${item_year}年`,
            date: item_year,
            id: uuidv4(),
            children: month_and_day,
          });
        }
        dates.push({
          name: `${end_year}年`,
          date: end_year,
          children: end_mouths,
          id: uuidv4(),
        });
        return dates;
      }
    },
    /**
     * 年-周模式gantt标题
     * start_year: 起始年
     * start_mouth:起始月
     * end_year:结束年
     * end_mouth:结束月
     */
    yearAndWeekTitleDate(start_year, start_mouth, end_year, end_mouth) {
      // 处理年份
      let year_diff = end_year - start_year;
      // 只存在同年或前后年的情况
      if (year_diff === 0) {
        // 年间隔为同一年
        let isLeap = this.isLeap(start_year); // 是否闰年
        let mouths = this.generationMonths(
          start_year,
          start_mouth,
          end_mouth + 1,
          isLeap,
          true,
          true
        ); // 处理月份
        return mouths;
      }
      // 处理开始月份
      let startIsLeap = this.isLeap(start_year);
      let start_mouths = this.generationMonths(
        start_year,
        start_mouth,
        13,
        startIsLeap,
        true,
        true
      );
      // 处理结束月份
      let endIsLeap = this.isLeap(end_year);
      let end_mouths = this.generationMonths(
        end_year,
        1,
        end_mouth + 1,
        endIsLeap,
        true,
        true
      );
      return start_mouths.concat(end_mouths);
    },
    /**
     * 月-日模式gantt标题
     * start_year: 起始年
     * start_mouth:起始月
     * end_year:结束年
     * end_mouth:结束月
     */
    mouthAndDayTitleDate(start_year, start_mouth, end_year, end_mouth) {
      // 处理年份
      let year_diff = end_year - start_year;
      // 只存在同年或前后年的情况
      if (year_diff === 0) {
        // 年间隔为同一年
        let isLeap = this.isLeap(start_year); // 是否闰年
        let mouths = this.generationMonths(
          start_year,
          start_mouth,
          end_mouth + 1,
          isLeap
        ); // 处理月份
        return mouths;
      }
      // 处理开始月份
      let startIsLeap = this.isLeap(start_year);
      let start_mouths = this.generationMonths(
        start_year,
        start_mouth,
        13,
        startIsLeap
      );
      // 处理结束月份
      let endIsLeap = this.isLeap(end_year);
      let end_mouths = this.generationMonths(
        end_year,
        1,
        end_mouth + 1,
        endIsLeap
      );
      return start_mouths.concat(end_mouths);
    },
    /**
     * 生成月份函数
     * year: Number 当前年份
     * start_num: Number 开始月分
     * end_num:Number 结束月份
     * isLeap: Boolean 是否闰年
     * insert_days: Boolean 是否需要插入 日
     * week: 是否以周的间隔
     */
    generationMonths(
      year,
      start_num = 1,
      end_num = 13,
      isLeap = false,
      insert_days = true,
      week = false
    ) {
      let months = [];
      if (insert_days) {
        // 无需 日 的模式
        for (let i = start_num; i < end_num; i++) {
          // 需要 日 的模式
          let days = this.generationDays(year, i, isLeap, week);
          months.push({
            name: `${i}月`,
            date: i,
            full_date: `${year}-${i}`,
            children: days,
            id: uuidv4(),
          });
        }
        return months;
      }
      for (let i = start_num; i < end_num; i++) {
        // 需要 日 的模式
        months.push({
          name: `${i}月`,
          date: i,
          full_date: `${year}-${i}`,
          id: uuidv4(),
        });
      }
      return months;
    },
    /**
     * 生成日期函数
     * year: Number 当前年份
     * month: Number 当前月份
     * isLeap: Boolean 是否闰年
     * week: Boolean 是否间隔一周
     */
    generationDays(year, month, isLeap = false, week = false) {
      let big_month = [1, 3, 5, 7, 8, 10, 12].includes(month);
      let small_month = [4, 6, 9, 11].includes(month);
      let dates_num = big_month ? 32 : small_month ? 31 : isLeap ? 30 : 29;
      let days = [];
      if (week) {
        let _day = 1; // 从周日开始
        let _start_day_inweek = this.timeInWeek(`${year}-${month}-1`);
        if (_start_day_inweek !== 0) {
          _day = 8 - _start_day_inweek;
        }
        for (let i = _day; i < dates_num; i += 7) {
          days.push({
            date: i,
            name: `${i}日`,
            id: uuidv4(),
            full_date: `${year}-${month}-${i}`,
          });
        }
      } else {
        for (let i = 1; i < dates_num; i++) {
          days.push({
            date: i,
            name: `${i}日`,
            id: uuidv4(),
            full_date: `${year}-${month}-${i}`,
          });
        }
      }
      return days;
    },
    /**
     * 是否闰年函数
     * year: Number 当前年份
     */
    isLeap(year) {
      return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
    },
    /**
     * 当前日期gantt状态
     * row: object 当前行信息
     * date: string 当前格子日期
     * unit: string 时间单位,以天、月、年计算
     */
    dayGanttType(row, date, unit = "days") {
      let start_date = row[this.selfProps.startDate];
      let end_date = row[this.selfProps.endDate];
      let between = dayjs(date).isBetween(start_date, end_date, unit);
      if (row.pid === "0" && between) {
        return "wl-item-root";
      }
      if (row.pid !== "0" && between) {
        return "wl-item-on";
      }
      let start = dayjs(start_date).isSame(date, unit);
      let end = dayjs(end_date).isSame(date, unit);
      if (row.pid === "0" && start) {
        return "wl-item-root wl-item-root-start";
      }
      if (row.pid === "0" && end) {
        return "wl-item-root wl-item-root-end";
      }
      if (start && end) {
        return "wl-item-on wl-item-full";
      }
      if (row.pid !== "0" && start) {
        return "wl-item-on wl-item-start";
      }
      if (row.pid !== "0" && end) {
        return "wl-item-on wl-item-end";
      }
    },
    /**
     * 实际日期gantt状态
     * row: object 当前行信息
     * date: string 当前格子日期
     * unit: string 时间单位,以天、月、年计算
     */
    realDayGanttType(row, date, unit = "days") {
      let start_date = row[this.selfProps.realStartDate];
      let end_date = row[this.selfProps.realEndDate];
      let between = dayjs(date).isBetween(start_date, end_date, unit);
      if (row.pid !== "0" && between) {
        return "wl-real-on";
      }
      let start = dayjs(start_date).isSame(date, unit);
      let end = dayjs(end_date).isSame(date, unit);
      if (start && end) {
        return "wl-real-on wl-real-full";
      }
      if (row.pid !== "0" && start) {
        return "wl-real-on wl-real-start";
      }
      if (row.pid !== "0" && end) {
        return "wl-real-on wl-real-end";
      }
    },
    // 以下是时间计算类函数 ------------------------------------------------------时间计算---------------------------------------
    /**
     * 计算时差
     * startDate:开始时间
     * endDate:结束时间
     * unit:单位 days、months、yesrs
     */
    timeDiffTime(startDate, endDate, unit = "days") {
      return dayjs(endDate).diff(startDate, unit);
    },
    /**
     * 比较时间,是否之前
     * startDate:开始时间
     * endDate:结束时间
     * unit:单位 days、months、yesrs
     */
    timeIsBefore(startDate, endDate, unit = "days") {
      return dayjs(startDate).isBefore(endDate, unit);
    },
    /**
     * 时间加计算函数
     * date:原时间
     * num:需要增加的时间数量
     * nuit:增加时间的单位 day year
     */
    timeAdd(date, num = 1, nuit = "day", format = "YYYY-MM-DD") {
      return dayjs(date)
        .add(num, nuit)
        .format(format);
    },
    /**
     * 时间格式化函数
     * date 需要格式化的数据
     * format 格式化的格式
     */
    timeFormat(date, format = "YYYY-MM-DD") {
      return date ? dayjs(date).format(format) : this.emptyCellText;
    },
    /**
     * 查询时间是周几
     */
    timeInWeek(date) {
      return dayjs(date).day();
    },
    // 以下为输出数据函数 --------------------------------------------------------------输出数据------------------------------------
    // 删除任务
    emitTaskRemove(item) {
      this.$emit("taskRemove", item);
    },
    // 添加任务
    emitTaskAdd(item) {
      this.$emit("taskAdd", item);
    },
    // 任务名称更改
    emitNameChange(item) {
      this.$emit("nameChange", item);
    },
    // 任务时间更改
    emitTimeChange(item) {
      this.$emit("timeChange", item);
      this.$nextTick(() => {
        this.$set(item, "_oldStartDate", item[this.selfProps.startDate]);
        this.$set(item, "_oldEndDate", item[this.selfProps.endDate]);
      });
    },
    /**
     * 前置任务更改
     * item: Object 发生更改的行数据
     * oldval: [String, Array] 修改前数据
     * handle: Boolean true为操作选择框修改 false为源数据不符合规范的修正更改
     */
    emitPreChange(item, oldval, handle = false) {
      this.$emit("preChange", item, oldval, handle);
    },
    // 处理外部数据 ---------------------------------------------------------------原始数据处理-------------------------------------
    handleData(data, parent = null, level = 0) {
      level++;
      data.forEach((i) => {
        this.$set(i, "_parent", parent); // 添加父级字段
        this.$set(i, "_level", level); // 添加层级字段
        if (!i._oldStartDate) {
          this.$set(i, "_oldStartDate", i[this.selfProps.startDate]);
        }
        if (!i._oldEndDate) {
          this.$set(i, "_oldEndDate", i[this.selfProps.endDate]);
        }
        // 当结束时间早于开始时间时,自动处理结束时间为开始时间延后一天
        let _end_early_start = this.timeIsBefore(
          i[this.selfProps.endDate],
          i[this.selfProps.startDate]
        );
        if (_end_early_start) {
          this.$set(i, this.selfProps.endDate, i[this.selfProps.startDate]);
          this.$set(i, "_cycle", 1); // 添加工期字段
          this.emitTimeChange(i); // 将发生时间更新的数据输出
        } else {
          let _time_diff = this.timeDiffTime(
            i[this.selfProps.startDate],
            i[this.selfProps.endDate]
          );
          this.$set(i, "_cycle", _time_diff + 1); // 添加工期字段
        } // 添加工期字段
        // 添加自增id字段及树链组成的parents字段
        this.recordIdentityIdAndParents(i);
        // 处理前置任务
        // this.handlePreTask(i);
        // 如果当前节点的开始时间早于父节点的开始时间,则将开始时间与父节点相同
        this.parentStartDateToChild(i);
        // 校验结束时间是否晚于子节点,如不则将节点结束时间改为最晚子节点
        this.childEndDateToParent(i);
        if (Array.isArray(i[this.selfProps.children])) {
          this.$set(i, "_isLeaf", false); // 添加是否叶子节点字段
          let _all_children = flattenDeep(
            i[this.selfProps.children],
            this.selfProps.children
          );
          this.$set(i, "_all_children", _all_children); // 添加全部子节点字段
          this.handleData(i[this.selfProps.children], i, level);
        } else {
          this.$set(i, "_isLeaf", true); // 添加是否叶子节点字段
          this.$set(i, "_all_children", []); // 添加全部子节点字段
        }
      });
    },
    // 取父节点开始时间给早于父节点开始时间的子节点
    parentStartDateToChild(item) {
      if (!item._parent) return;
      // 如果子节点时间早于父节点,则将子节点开始时间后移至父节点开始时间,并将结束时间平移【即工期不变】
      let _child_early_parent = this.timeIsBefore(
        item[this.selfProps.startDate],
        item._parent[this.selfProps.startDate]
      );
      if (_child_early_parent) {
        // 修正子节点开始时间
        this.$set(
          item,
          this.selfProps.startDate,
          item._parent[this.selfProps.startDate]
        );
        // 修正子节点结束时间
        let _to_endDate = this.timeAdd(
          item[this.selfProps.startDate],
          item._cycle
        );
        this.$set(item, this.selfProps.endDate, _to_endDate);
        this.emitTimeChange(item); // 将发生时间更新的数据输出
      }
    },
    // 取数组结束时间最大值,如果最大值比父级结束时间大,更新父级结束时间
    childEndDateToParent(item) {
      if (!Array.isArray(item[this.selfProps.children])) return;
      let _child_max = getMax(
        item[this.selfProps.children],
        this.selfProps.endDate,
        true
      ); // 取子节点中最晚的结束时间
      let _parent_end = dayjs(item[this.selfProps.endDate]).valueOf();
      if (_child_max > _parent_end) {
        // 如果子节点结束时间比父节点晚,则将父节点结束时间退后
        this.$set(item, this.selfProps.endDate, this.timeFormat(_child_max));
        this.emitTimeChange(item); // 将发生时间更新的数据输出
      }
    },
    // 处理前置任务节点    /// ---- 此使前置任务校验处理还没开始,因此出错,前置处理后手动调用vue视图更新试试
    handlePreTask(item) {
      // 统一在一维化数据中处理前置任务
      let _pre_target = this.self_dependent_store.find(
        (i) => i.form[this.selfProps.id] === item[this.selfProps.id]
      );
      if (!_pre_target) return;
      let _pre_end_date = this.preMultiple
        ? getMax(_pre_target.to, this.selfProps.endDate, true) // 取前置点中最晚的结束时间
        : _pre_target.to[this.selfProps.endDate];
      /* 在数据循环中处理前置
      let pres = item[this.selfProps.pre];
      if(!pres) return;
      let _pre_target = null, _pre_end_date = null;
      if(this.preMultiple){
        if(!Array.isArray(pres) || pres.length ===0) return;
        _pre_target = this.self_data_list.filter(i => pres.includes(i[this.selfProps.id]));
        _pre_end_date = getMax(_pre_target, this.selfProps.endDate, true);
      }else{
        _pre_target = this.self_data_list.find(i => i[this.selfProps.id] === pres);
        if(!_pre_target) return;
        _pre_end_date = _pre_target[this.selfProps.endDate]
      } */
      // 查看是否需要根据前置时间,如果不符合规则,更新后置时间
      let _start_early_prvend = this.timeIsBefore(
        item[this.selfProps.startDate],
        _pre_end_date
      );
      if (_start_early_prvend) {
        let _cycle = item._cycle - 1;
        let _to_startDate = this.timeAdd(_pre_end_date, 1);
        let _to_endDate = this.timeAdd(_to_startDate, _cycle);
        this.$set(item, this.selfProps.startDate, _to_startDate);
        this.$set(item, this.selfProps.endDate, _to_endDate);
      }
    },
    /**
     * 检查前置任务合法性
     * !!已废弃:改为从一维数据列收集form、to并校验,不再在递归中检查 -> handleDependentStore
     */
    checkPreTaskValidity(item) {
      // 没有前置任务退出
      if (!item[this.selfProps.pre]) return false;
      // 多前置任务模式
      if (this.preMultiple) {
        let _pres = item[this.selfProps.pre];
        // 不是数组退出
        if (!Array.isArray(_pres)) {
          this.emitPreChange(item, item[this.selfProps.pre]);
          this.$set(item, this.selfProps.pre, []);
          return false;
        }
        // 数组为空退出
        if (_pres.length === 0) return false;
        // 前置任务有自己时,剔除自己
        let _net_self_pres = _pres.filter((i) => i !== item[this.selfProps.id]);
        if (_net_self_pres.length !== _pres.length) {
          this.emitPreChange(item, item[this.selfProps.pre]);
          this.$set(item, this.selfProps.pre, _net_self_pres);
        }
        // 剔除前置任务找不到目标数据的元素
        let _pre_exist = _net_self_pres.filter((i) => this.targetInAllData(i));
        if (_pre_exist.length !== _net_self_pres.length) {
          this.emitPreChange(item, item[this.selfProps.pre]);
          this.$set(item, this.selfProps.pre, _pre_exist);
        }
        let _no_par_chi = []; // 声明非父、祖、子、孙节点的盒子
        for (let i of _pre_exist) {
          let _pre_target = this.self_data_list.find(
            (t) => t[this.selfProps.id] === i
          );
          if (!_pre_target) continue;
          let _pre_par_chi = this.targetInParentsOrChildren(item, _pre_target);
          _pre_par_chi || _no_par_chi.push(i);
        }
        // 前置任务是自己的父祖或子孙节点, 剔除此前置
        if (_no_par_chi.length !== _pre_exist.length) {
          this.emitPreChange(item, item[this.selfProps.pre]);
          this.$set(item, this.selfProps.pre, _no_par_chi);
        }
        // 处理前置任务链链中产生了回环【A->B,B->C,C->D,D->B】即前置链中形成了相互前置的节点,剔除其错误前置数据
        this.targetLinkLoopback(item);
        return true;
      }
      // 单前置任务模式
      if (item[this.selfProps.pre] === item[this.selfProps.id]) {
        this.$set(item, this.selfProps.pre, null);
        return false;
      } // 前置任务是自己退出
      // 找到前置目标节点
      let _pre_target = this.self_data_list.find(
        (i) => i[this.selfProps.id] == item[this.selfProps.pre]
      );
      // 没找到前置任务节点数据退出
      if (!_pre_target) {
        this.$set(item, this.selfProps.pre, null);
        return false;
      }
      // 前置任务是自己的父祖或子孙节点退出
      let is_pre_standard = this.targetInParentsOrChildren(item, _pre_target);
      if (is_pre_standard) {
        this.$set(item, this.selfProps.pre, null);
        return false;
      }
      // 处理前置任务链链中产生了回环【A->B,B->C,C->D,D->B】即前置链中形成了相互前置的节点,剔除其错误前置数据
      this.targetLinkLoopback(item);
      return true;
    },
    // 处理数据生成自增id和树链parents
    recordIdentityIdAndParents(item) {
      // if (!this.recordParents) return;
      if (this.treatIdAsIdentityId) {
        let _parents = item._parent
          ? item._parent._parents + "," + item._parent[this.selfProps.id]
          : "";
        this.$set(item, "_parents", _parents);
        this.$set(item, "_identityId", item[this.selfProps.id]);
        return;
      }
      // 添加自增id
      this.$set(item, "_identityId", this.self_id);
      this.self_id++;
      // 添加parents字段
      let _parents = item._parent
        ? item._parent._parents + "," + item._parent._identityId
        : "";
      this.$set(item, "_parents", _parents);
    },
    /**
     * 查询目标是否在父级链或者全部子集中
     * item 当前节点
     * pre 前置节点
     */
    targetInParentsOrChildren(item, pre) {
      let _parents = item._parents.split(",");
      let _children = item._all_children.map((i) => i._identityId);
      return _children.concat(_parents).some((i) => i == pre._identityId);
    },
    // 查询目标节点是否在数据中存在,并返回数据
    targetInAllData(target_id) {
      return this.self_data_list.find(
        (i) => i[this.selfProps.id] === target_id
      );
    },
    /**
     * 处理前置任务链链中产生了回环【A->B,B->C,C->D,D->B】即前置链中形成了相互前置的节点,剔除其错误前置数据
     * item: Object 当前节点数据
     * pre_tesk: Array 前置链上所有id
     * !!已废弃:下方尝试改成form to结构收集起来处理,不再循环中反复循环处理 -> terseTargetLinkLoopback
     */
    targetLinkLoopback(item, pre_tesk = []) {
      pre_tesk.push(item[this.selfProps.id]);
      let _pres = item[this.selfProps.pre];
      let _legal_pres = _pres.filter((i) => !pre_tesk.includes(i));
      if (this.preMultiple) {
        if (_legal_pres.length !== _pres.length) {
          this.emitPreChange(item, item[this.selfProps.pre]);
          this.$set(item, this.selfProps.pre, _legal_pres);
        }
        _legal_pres.forEach((i) => {
          let _pre_target = this.self_data_list.find(
            (t) => t[this.selfProps.id] === i
          );
          if (
            _pre_target &&
            Array.isArray(_pre_target[this.selfProps.pre]) &&
            _pre_target[this.selfProps.pre].length > 0
          ) {
            this.targetLinkLoopback(_pre_target, pre_tesk);
          }
        });
      } else {
        if (pre_tesk.includes(_pres)) {
          this.emitPreChange(item, item[this.selfProps.pre]);
          this.$set(item, this.selfProps.pre, _legal_pres);
        }
        let _pre_target = this.self_data_list.find(
          (t) => t[this.selfProps.id] === item[this.selfProps.id]
        );
        if (_pre_target) {
          this.targetLinkLoopback(_pre_target, pre_tesk);
        }
      }
    },
    /**
     * 处理前置任务链链中产生了回环【A->B,B->C,C->D,D->B】即前置链中形成了相互前置的节点,剔除其错误前置数据
     * item: Object 当前节点数据
     * flow_pre_tesk: Array 前置链上所有id
     */
    terseTargetLinkLoopback(item, flow_pre_tesk = []) {
      flow_pre_tesk.push(item.form[this.selfProps.id]);
      if (this.preMultiple) {
        let _legal_pre = [], // 收集合法数据
          _next_form = []; // 收集所有前置的前置
        for (let i of item.to) {
          let _to_id = i[this.selfProps.id];
          if (flow_pre_tesk.includes(_to_id)) continue;
          _legal_pre.push(_to_id);
          flow_pre_tesk.push(_to_id);
          let _store_next_form = this.self_dependent_store.filter(
            (t) => t.form[this.selfProps.id] === _to_id
          );
          _next_form = _next_form.concat(_store_next_form);
        }
        // 剔除不合法前置
        if (_legal_pre.length !== item.to.length) {
          this.emitPreChange(item.form, item.form[this.selfProps.pre]);
          this.$set(item.form, this.selfProps.pre, _legal_pre);
        }
        // 向前置的前置递归
        _next_form.forEach((t) => {
          this.terseTargetLinkLoopback(t, flow_pre_tesk);
        });
      } else {
        let _to_id = item.to[this.selfProps.id];
        if (flow_pre_tesk.includes(_to_id)) {
          this.emitPreChange(item.form, item.form[this.selfProps.pre]);
          this.$set(item.form, this.selfProps.pre, null);
          return;
        }
        let _next_form = this.self_dependent_store.find(
          (t) => t.form[this.selfProps.id] === _to_id
        );
        if (!_next_form) return;
        this.terseTargetLinkLoopback(_next_form, flow_pre_tesk);
      }
    },
    // 简洁处理数据
    terseHandleData(data, parent = null, level = 0) {
      level++;
      data.forEach((i) => {
        this.$set(i, "_parent", parent); // 添加父级字段
        this.$set(i, "_level", level); // 添加层级字段
        let _time_diff = this.timeDiffTime(
          i[this.selfProps.startDate],
          i[this.selfProps.endDate]
        );
        i._cycle = _time_diff + 1;
        if (!i._oldStartDate) {
          // 添加开始时间字段
          this.$set(i, "_oldStartDate", i[this.selfProps.startDate]);
        }
        if (!i._oldEndDate) {
          // 添加结束字段时间
          this.$set(i, "_oldEndDate", i[this.selfProps.endDate]);
        }
        // 添加自增id字段及树链组成的parents字段
        this.recordIdentityIdAndParents(i);
        if (Array.isArray(i[this.selfProps.children])) {
          this.$set(i, "_isLeaf", false); // 添加是否叶子节点字段
          let _all_children = flattenDeep(
            i[this.selfProps.children],
            this.selfProps.children
          );
          this.$set(i, "_all_children", _all_children); // 添加全部子节点字段
          this.terseHandleData(i[this.selfProps.children], i, level);
        } else {
          this.$set(i, "_isLeaf", true); // 添加是否叶子节点字段
        }
        // 处理前置任务
        // this.handlePreTask(i);
      });
    },
    // 生成前置依赖库, 校验前置合法性并剔除不合法数据
    handleDependentStore() {
      this.self_dependent_store = [];
      // 多选前置模式
      if (this.preMultiple) {
        for (let i of this.self_data_list) {
          let _pres = i[this.selfProps.pre];
          if (!_pres) continue;
          // 不是数组退出
          if (!Array.isArray(_pres)) {
            this.emitPreChange(i, i[this.selfProps.pre]);
            this.$set(i, this.selfProps.pre, []);
            continue;
          }
          // 数组为空退出
          if (_pres.length === 0) continue;
          // 查询不到数据的不收集,是父、祖、子、孙节点的不收集
          let _pre_exist_node = [],
            _pre_exist_id = [];
          for (let t of _pres) {
            let target_node = this.targetInAllData(t);
            if (!target_node) continue; // 查询不到数据的不收集
            let in_per_chi = this.targetInParentsOrChildren(i, target_node);
            if (in_per_chi) continue; // 是父、祖、子、孙节点的不收集
            _pre_exist_node.push(target_node);
            _pre_exist_id.push(target_node[this.selfProps.id]);
          }
          if (_pre_exist_node.length !== _pres.length) {
            this.emitPreChange(i, i[this.selfProps.pre]);
            this.$set(i, this.selfProps.pre, _pre_exist_id);
          }
          this.self_dependent_store.push({
            form: i,
            to: _pre_exist_node,
          });
        }
      } else {
        // 单选前置模式
        for (let i of this.self_data_list) {
          if (!i[this.selfProps.pre]) continue;
          let _pre_target = this.targetInAllData(i[this.selfProps.pre]);
          // 处理前置任务找不到的情况
          if (!_pre_target) {
            this.emitPreChange(i, i[this.selfProps.pre]);
            this.$set(i, this.selfProps.pre, null);
            continue;
          }
          // 处理前置任务是父祖子孙节点的情况
          let in_per_chi = this.targetInParentsOrChildren(i, _pre_target);
          if (in_per_chi) {
            this.emitPreChange(i, i[this.selfProps.pre]);
            this.$set(i, this.selfProps.pre, null);
            continue;
          }
          this.self_dependent_store.push({
            form: i,
            to: _pre_target,
          });
        }
      }
      // 处理合格前置任务
      this.self_dependent_store.forEach((i) => {
        this.terseTargetLinkLoopback(i);
      });
      // 处理前置依赖
      this.self_data_list.forEach((i) => {
        this.handlePreTask(i);
      });
      // 暂时强制更新视图
      if (this.update) {
        this.update = false;
        this.selfData.sort();
      }
    },
    // 父子关联
    tableSelect(val, row) {
      if (!this.parentChild) return;
      // 选中
      if (
        val.some((item) => item[this.selfProps.id] == row[this.selfProps.id])
      ) {
        // 父元素选中全选所有子孙元素
        // for (let item of val) {
        row._all_children.forEach((i) => {
          this.$refs["wl-gantt"].toggleRowSelection(i, true);
        });
        // }
        // 子元素全选向上查找所有满足条件的祖先元素
        regDeepParents(row, "_parent", (parents) => {
          let reg =
            parents &&
            parents[this.selfProps.children].every((item) => {
              return val.some(
                (it) => it[this.selfProps.id] == item[this.selfProps.id]
              );
            });
          if (reg) this.$refs["wl-gantt"].toggleRowSelection(parents, true);
        });
      } else {
        // 非选中将所有子孙元素及支线上祖先元素清除
        let cancel_data = [
          ...row._all_children,
          ...flattenDeepParents([row], "_parent"),
        ];
        for (let item of cancel_data) {
          this.$refs["wl-gantt"].toggleRowSelection(item, false);
        }
      }
    },
    // el-table事件----------------------------------------------以下为原el-table事件输出------------------------------------------------
    handleSelectionChange(val) {
      this.$emit("selection-change", val);
      this.multipleSelection = val;
    }, // 当选择项发生变化时会触发该事件
    handleCurrentChange(val, oldVal) {
      this.$emit("current-change", val, oldVal);
      this.currentRow = val;
    }, // 当表格的当前行发生变化的时候会触发该事件
    handleSelectAll(val) {
      let is_check = val.length > 0;
      this.self_data_list.forEach((i) => {
        this.$refs["wl-gantt"].toggleRowSelection(i, is_check);
      });
      this.$emit("select-all", val);
    }, // 当用户手动勾选全选 Checkbox 时触发的事件
    handleSelect(selection, row) {
      this.tableSelect(selection, row);
      let _is_add = selection.some((i) => i[this.rowKey] === row[this.rowKey]);
      this.selectionList = selection;
      this.$emit("select", selection, row, _is_add);
    }, // 当用户手动勾选全选 Checkbox 时触发的事件
    handleMouseEnter(row, column, cell, event) {
      this.$emit("cell-mouse-enter", row, column, cell, event);
    }, // 当单元格 hover 进入时会触发该事件
    handleMouseLeave(row, column, cell, event) {
      this.$emit("cell-mouse-leave", row, column, cell, event);
    }, // 当单元格 hover 退出时会触发该事件
    handleCellClick(row, column, cell, event) {
      this.$emit("cell-click", row, column, cell, event);
    }, // 当某个单元格被点击时会触发该事件
    handleCellDbClick(row, column, cell, event) {
      this.$emit("cell-dblclick", row, column, cell, event);
    }, // 当某个单元格被双击击时会触发该事件
    handleRowClick(row, column, event) {
      /* if (this.useCheckColumn && this.quickCheck) {
        let is_check = this.selectionList.some(
          i => i[this.rowKey] == row[this.rowKey]
        );
        this.$refs["wl-gantt"].toggleRowSelection(row, !is_check);
        this.$nextTick(() => {
          this.handleSelect(this.selectionList, row, !is_check);
        });
      } */
      this.$emit("row-click", row, column, event);
    }, // 当某一行被点击时会触发该事件
    handleRowContextMenu(row, column, event) {
      this.$emit("row-contextmenu", row, column, event);
      // 处理右键菜单浮窗
      if (!Array.isArray(this.contextMenuOptions)) return;
      this.contextMenu.data = [];
      this.contextMenuOptions.forEach((i) => {
        let _item = {
          label: i.label,
          icon: i.icon,
          value: row[i.prop],
        };
        this.contextMenu.data.push(_item);
      });
      this.contextMenu.x = event.x;
      this.contextMenu.y = event.y;
      this.contextMenu.show = true;
    }, // 当某一行被鼠标右键点击时会触发该事件
    handleContextmenu() {
      event.preventDefault();
      event.stopPropagation();
    }, // 右键菜单事件
    handleRowDbClick(row, column, event) {
      this.$emit("row-dblclick", row, column, event);
    }, // 当某一行被双击时会触发该事件
    handleHeaderClick(column, event) {
      this.$emit("header-click", column, event);
    }, // 当某一列的表头被点击时会触发该事件
    handleHeaderContextMenu(column, event) {
      this.$emit("header-contextmenu", column, event);
    }, // 当某一列的表头被鼠标右键点击时触发该事件
    handleSortChange(e) {
      this.$emit("sort-change", e);
    }, // 当表格的排序条件发生变化的时候会触发该事件
    handleFilterChange(filters) {
      this.$emit("filter-change", filters);
    }, // 当表格的筛选条件发生变化的时候会触发该事件
    handleExpandChange(row, expanded) {
      this.$emit("expand-change", row, expanded);
    }, // 当表格的筛选条件发生变化的时候会触发该事件
    // ------------------------------------------- 以下为提供方法 ------------------------------------
    /**
     * 手动调用树表懒加载
     * row 要展开的行信息
     */
    loadTree(row) {
      this.$refs["tableRef"].store.loadOrToggle(row);
    },
    /**
     * 更新树表懒加载后的子节点
     * 要更新的节点id
     * 要添加的节点list
     */
    loadTreeAdd(id, list) {
      let _children =
        this.$refs["wl-gantt"].store.states.lazyTreeNodeMap[id] || [];
      this.$set(
        this.$refs["wl-gantt"].store.states.lazyTreeNodeMap,
        id,
        list.concat(_children)
      );
    },
    /**
     * 更新树表懒加载后的子节点
     * 要更新的节点id
     * 要删掉的字节的rowKey
     */
    loadTreeRemove(id, key) {
      let _children = this.$refs["wl-gantt"].store.states.lazyTreeNodeMap[id];
      let _new_children = _children.filter((i) => i[this.rowKey] != key);
      this.$set(
        this.$refs["wl-gantt"].store.states.lazyTreeNodeMap,
        id,
        _new_children
      );
    },
  },

  created() {
    this.self_date_type = this.dateType;
    this.self_start_date = this.startDate;
    this.self_end_date = this.endDate;
  },
};
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style lang="scss">
$gantt_item: 6px;
$gantt_item_half: 8px;

.wl-gantt {
  .wl-gantt-header > th {
    text-align: center;
  }

  .h-full {
    height: 100%;
  }

  .wl-gantt-item {
    position: relative;
    transition: all 0.3s;
    > .cell {
      padding: 0;
    }
  }

  .u-full.el-input {
    width: 100%;
  }
  // 总长度
  .wl-item-root {
    position: absolute;
    top: 40%;
    left: 0;
    right: -1px;
    margin-top: -$gantt_item_half;
    height: $gantt_item;
    background: #000;
    transition: all 0.4s;
    position: absolute;
  }
  .wl-item-root-start {
    left: 50%;
    &:after {
    position: absolute;
    top: 3px;
    left: 0;
    z-index: 1;
    content: "";
    width: 0;
    height: 12px;
    border-color: #000;
    border-width: 3px;
    border-style: solid;
    }
  }
  .wl-item-root-end {
    right: 50%;
    &:after {
      position: absolute;
      top: 3px;
      right: 0;
      z-index: 1;
      content: "";
      width: 0;
      height: 12px;
      border-color: #000;
      border-width: 3px;
      border-style: solid;
    }
  }

  // 计划时间gantt开始
  .wl-item-on {
    position: absolute;
    top: 50%;
    left: 0;
    right: -1px;
    margin-top: -$gantt_item_half;
    height: $gantt_item;
    background: #409eff;
    transition: all 0.4s;
    position: absolute;
  }

  .wl-item-start {
    left: 50%;
    &:after {
      position: absolute;
      top: 16px;
      left: 0;
      z-index: 1;
      content: "";
      width: 0;
      height: 0;
      // border-color: #409eff transparent transparent;
      // border-width: 6px 6px 6px 0;
      // border-style: solid;
      border: none !important;
    }
  }

  .wl-item-end {
    right: 50%;
    &:after {
      position: absolute;
      top: $gantt_item;
      right: 0;
      z-index: 1;
      content: "";
      width: 0;
      height: 0;
      // border-color: transparent #409eff;
      // border-width: 0 6px 6px 0;
      // border-style: solid;
      border: none !important;
    }
  }

  .wl-item-full {
    left: 0;
    right: 0;
    position: absolute;
    top: 60%;
    left: 0;
    right: -1px;
    margin-top: -$gantt_item_half;
    height: $gantt_item;
    background: #6e7072;
    transition: all 0.4s;
    &:before {
      position: absolute;
      top: $gantt_item;
      left: 0;
      z-index: 1;
      content: "";
      width: 0;
      height: 0;
      border-color: #409eff transparent transparent;
      border-width: 6px 6px 6px 0;
      border-style: solid;
      border: none !important;
    }
    &:after {
      position: absolute;
      top: $gantt_item;
      right: 0;
      z-index: 1;
      content: "";
      width: 0;
      height: 0;
      border-color: transparent #409eff;
      border-width: 0 6px 6px 0;
      border-style: solid;
      border: none !important;
    }
  }
  // 计划时间gantt结束

  // 实际时间gantt开始
  .wl-real-on {
    position: absolute;
    top: 62% !important;
    left: 0;
    right: -1px;
    margin-top: -$gantt_item_half;
    height: $gantt_item !important;
    background: #00f !important; //rgba(250, 167, 146, .6);
    transition: all 0.4s;
  }
  .wl-real-start {
    left: 50%;
    &:after {
      position: absolute;
      top: $gantt_item;
      left: 0;
      z-index: 1;
      content: "";
      width: 0;
      height: 0;
      border-color: transparent !important;
      // border-color: #faa792 transparent transparent;
      border-width: 6px 6px 6px 0;
      border-style: solid;
    }
  }

  .wl-real-end {
    right: 50%;
    &:after {
      position: absolute;
      top: $gantt_item;
      right: 0;
      z-index: 1;
      content: "";
      width: 0;
      height: 0;
      border-color: transparent !important;
      // border-color: transparent #faa792;
      border-width: 0 6px 6px 0;
      border-style: solid;
    }
  }

  .wl-real-full {
    left: 0;
    right: 0;
    &:before {
      position: absolute;
      top: $gantt_item;
      left: 0;
      z-index: 1;
      content: "";
      width: 0;
      height: 0;
      border-color: transparent !important;
      // border-color: #faa792 transparent transparent;
      border-width: 6px 6px 6px 0;
      border-style: solid;
    }
    &:after {
      position: absolute;
      top: $gantt_item;
      right: 0;
      z-index: 1;
      content: "";
      width: 0;
      height: 0;
      border-color: transparent !important;
      // border-color: transparent #faa792;
      border-width: 0 6px 6px 0;
      border-style: solid;
    }
  }
  // 实际时间gantt结束

  // 名称列
  .name-col {
    position: relative;
    &:hover .name-col-edit {
      display: inline-block;
    }

    .name-col-edit {
      display: none;
      position: absolute;
      right: 0;
    }

    .name-col-icon {
      padding: 6px 3px;
      cursor: pointer;
      font-size: 16px;
    }

    .task-remove {
      color: #f56c6c;
    }
    .task-add {
      color: #409eff;
    }
  }
}

.year-and-month {
  .wl-item-start {
    left: 5%;
    &:after {
      position: absolute;
      top: $gantt_item;
      left: 0;
      z-index: 1;
      content: "";
      width: 0;
      height: 0;
      border-color: #409eff transparent transparent;
      border-width: 6px 6px 6px 0;
      border-style: solid;
    }
  }

  .wl-item-end {
    right: 5%;
    &:after {
      position: absolute;
      top: $gantt_item;
      right: 0;
      z-index: 1;
      content: "";
      width: 0;
      height: 0;
      border-color: transparent #409eff;
      border-width: 0 6px 6px 0;
      border-style: solid;
    }
  }

  .wl-item-full {
    left: 5%;
    right: 5%;
    &:before {
      position: absolute;
      top: $gantt_item;
      left: 0;
      z-index: 1;
      content: "";
      width: 0;
      height: 0;
      border-color: #409eff transparent transparent;
      border-width: 6px 6px 6px 0;
      border-style: solid;
    }
    &:after {
      position: absolute;
      top: $gantt_item;
      right: 0;
      z-index: 1;
      content: "";
      width: 0;
      height: 0;
      border-color: transparent #409eff;
      border-width: 0 6px 6px 0;
      border-style: solid;
    }
  }
}
</style>

效果图:

Logo

前往低代码交流专区

更多推荐