vue + element 甘特图

参考地址: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>

效果图:

wl-gantt是一个用于生成甘特图的工具。它可以帮助用户创建和管理项目计划,并以可视化的方式展示任务的时间安排和进度。使用wl-gantt,用户可以通过简单的命令行操作来定义任务、设置任务的开始和结束时间、添加依赖关系等。以下是wl-gantt的一些常用用法: 1. 创建任务:使用命令`wl-gantt add`可以添加一个新的任务。例如,`wl-gantt add "任务1"`将创建一个名为"任务1"的任务。 2. 设置任务的开始和结束时间:使用命令`wl-gantt set`可以设置任务的开始和结束时间。例如,`wl-gantt set "任务1" start 2022-01-01`将任务1的开始时间设置为2022年1月1日。 3. 添加依赖关系:使用命令`wl-gantt depends`可以添加任务之间的依赖关系。例如,`wl-gantt depends "任务2" on "任务1"`将任务2设置为依赖于任务1。 4. 查看甘特图:使用命令`wl-gantt show`可以查看生成的甘特图甘特图将以图形化的方式展示任务的时间安排和进度。 5. 导出甘特图:使用命令`wl-gantt export`可以将生成的甘特图导出为不同的格式,如PNG、PDF等。例如,`wl-gantt export --format png`将甘特图导出为PNG格式的图片。 这些只是wl-gantt的一些基本用法,还有更多高级功能和选项可以根据具体需求进行使用。你可以通过查看wl-gantt的官方文档或使用`wl-gantt --help`命令来获取更详细的使用说明和参数列表。
评论 29
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值