vue + element 甘特图

tech2022-09-18  104

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

效果图:

最新回复(0)