先上代码,后续更新文档
import moment from 'moment';
// 显示时间段
export const timeList = [
'00:00\n-\n02:00',
'02:00\n-\n04:00',
'04:00\n-\n06:00',
'06:00\n-\n08:00',
'08:00\n-\n10:00',
'10:00\n-\n12:00',
'12:00\n-\n14:00',
'14:00\n-\n16:00',
'16:00\n-\n18:00',
'18:00\n-\n20:00',
'20:00\n-\n22:00',
'22:00\n-\n24:00',
];
const timeArr = [];
for (let i = 0; i < 24; i++) {
timeArr.push((i < 10 ? '0' + i : i) + ':15:00');
timeArr.push((i < 10 ? '0' + i : i) + ':30:00');
timeArr.push((i < 10 ? '0' + i : i) + ':45:00');
if (i < 23) {
timeArr.push(((i + 1) < 10 ? '0' + (i + 1) : (i + 1)) + ':00:00');
} else {
timeArr.push('00:00:00');
}
}
// 单元格时间段
export const colTimeList = timeArr;
const dataArr = [];
for (let i = 0; i < 96; i++) {
dataArr.push(0);
}
// 单元格占位标识符
export const setDateArr = dataArr;
/**
* 右侧table数据
* @param showEl {boolean} 是否显示隐藏的占位单元
* @param title {string} 显示星期
* @param date {string} 显示日期
* @param dataList {Array<Object>} 已添加数据
* @param setDateArr {0 | 1} 单元格占位数据 0 无数据 1 有数据
*/
export function weekDataList(date) {
const dayList = [];
const times = [-1, 0, 1, 2, 3, 4, 5, 6, 7];
const weeks = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六', '星期日', '星期一'];
for (let i = 0; i < times.length; i++) {
const day = {
title: weeks[i],
date: moment().week(moment(new Date(date)).week()).weekday(times[i]).format('YYYY-MM-DD'),
showEl: false,
dataList: [],
};
dayList.push(day);
}
return dayList;
}
<template>
<div class="simulator-schedule-box">
<!-- 查询区域 -->
<div class="table-page-search-wrapper">
<a-form layout="inline" class="table-page-search-wrapper-form">
<div class="table-page-search-wrapper-form-left">
<a-form-item label="排课信息">
<a-select style="width: 120px">
<a-select-option value="jack">Jack</a-select-option>
<a-select-option value="lucy">Lucy</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="搜索">
<a-input placeholder="输入关键字进行过滤" />
</a-form-item>
<a-form-item>
<a-tooltip>
<template #title>隐藏/显示工号</template>
<a-switch/>
</a-tooltip>
</a-form-item>
<a-form-item>
<a-tooltip>
<template #title>显示全部/未排课程</template>
<a-switch/>
</a-tooltip>
</a-form-item>
<a-form-item>
<a-tooltip>
<template #title>折叠/展开课程</template>
<a-switch/>
</a-tooltip>
</a-form-item>
</div>
<div class="table-page-search-wrapper-form-right">
<a-button>上一周</a-button>
<a-button>下一周</a-button>
<a-form-item>
<a-week-picker v-model="week" @change="changeWeek" placeholder="请选择日期"/>
</a-form-item>
</div>
</a-form>
</div>
<!-- <!– 操作按钮区域 –>-->
<!-- <div class="table-operator" style="border-top: 5px">-->
<!-- <a-button type="primary" icon="plus">{{ $t('common.add') }}</a-button>-->
<!-- </div>-->
<div class="table-box">
<div class="left">
<ul>
<li
draggable="true"
@dragstart="dragstartList($event, {length: item, type: 'new'})"
@dragend="drag($event)"
v-for="item in 10"
:key="item">{{ item }}</li>
</ul>
</div>
<div class="right">
<div class="right-header">
</div>
<div class="gantt-box">
<div class="gantt-list">
<template v-if="tableList.length > 0">
<ul class="gantt-item" v-for="(gantt, g) in tableList" :key="g">
<!--甘特图头部 头部两小时划分显示的单元格-->
<li>
<ul class="gantt-header">
<li class="gantt-header-label">
日期
</li>
<li
class="gantt-header-item"
:style="{width: (colWidth * 8) + 'px'}"
v-for="item in timeList"
:key="item">
<pre>{{ item }}</pre>
</li>
</ul>
</li>
<!--甘特图内容-->
<li v-for="(item, i) of gantt.list" :key="i">
<ul class="gantt-row-list">
<!--日期title 左侧星期几与日期-->
<li class="gantt-row-list-label">
<span>
{{ item.date | dateFormat('MM-DD') }}
<br>
{{ item.title }}
</span>
</li>
<!--甘特图具体显示数据逻辑-->
<li class="gantt-row-content">
<!--以每两小时划分单元格-->
<ul class="gantt-row-time">
<li
:style="{width: (colWidth * 8) + 'px'}"
class="gantt-row-time-item"
v-for="date in timeList"
:key="date"></li>
</ul>
<!--以每十五分钟划分单元格-->
<ul class="gantt-row-col">
<li
@drop="drop($event, gantt.list, g, item, i, k)"
@dragover="dragoverList($event, gantt.list, item, i, k)"
class="gantt-row-col-item"
:class="{'gantt-row-col-item-border' : isDrag}"
:style="{width: colWidth + 'px'}"
v-for="(data, k) in colTimeList"
:key="k"></li>
</ul>
<!--甘特图排课数据-->
<ul class="gantt-row-data-list">
<li
v-for="(data, d) in item.dataList"
:key="d"
@dragstart="dragstartList($event, {length: data.width, type: 'old', index: d, item, data})"
@dragend="drag($event, {length: data.width, type: 'old', index: d, item, data})"
draggable="true"
@click="selectData(data)"
:style="{
left: (data.col * colWidth) + 'px',
width: (data.width * colWidth) + 'px',
}"
:class="{'gantt-row-data-item-border' : (courseData.indexKey === data.indexKey)}"
class="gantt-row-data-item">
<!--当前拖拽课程内部单元格-->
<ul class="gantt-row-data-item-col-list" v-show="data.event">
<li
@drop="drop($event, gantt.list, g, item, i, (data.col + c))"
@dragover="dragoverList($event, gantt.list, item, i, (data.col + c))"
class="gantt-row-data-item-col-item"
v-for="(col, c) in data.width"
:key="c"
:style="{
width: colWidth + 'px',
}"></li>
</ul>
</li>
</ul>
<!--元素拖拽显示预占位蒙层-->
<p
class="table-row-item-show"
:style="{left: showElLeft + 'px', width: showElWidth + 'px'}"
v-show="item.showEl"></p>
</li>
</ul>
</li>
</ul>
</template>
<div class="empty">
<a-empty description="暂未选择模拟器" v-if="tableList.length === 0"/>
</div>
</div>
</div>
<div class="right-footer">
<div class="simulator-select">
<a-checkbox-group @change="onChange" v-model="simulatorValue" >
<a-checkbox v-for="(item, i) in simulatorDataList" :key="i" :value="item.id">{{item.title}}</a-checkbox>
</a-checkbox-group>
</div>
<div class="simulator-load">
<a-button type="link" @click="colWidth--">W-</a-button>
<a-button type="link" @click="colWidth++">W+</a-button>
<a-button @click="loadSimulator">加载模拟机</a-button>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import moment from 'moment';
import {timeList, colTimeList, weekDataList} from '@views/inspect/services/SimulatorScheduleService'
export default {
name: "SimulatorSchedule",
data() {
return {
// 单元格宽度
colWidth: 15,
// 两小时单元格
timeList,
// 十五分钟单元格
colTimeList,
/**
* 右侧甘特图数据
* @param showEl {boolean} 是否显示隐藏的占位单元
* @param title {string} 显示星期
* @param date {string} 显示日期
* @param list {number} 单元格数量
* @param dataList {Array<Object>} 已添加数据,已排课课程
* @param setDateArr {0 | 1 | 2} 单元格占位数据 0 无数据 1 有数据 2 当前元素被拖拽
*/
tableList: [],
// 元素距左侧距离
showElLeft: 0,
// 元素单元宽度
showElWidth: this.colWidth,
// 拖拽元素是否是课程
isDrag: false,
// 当前拖拽单元时间跨度的长度
dataLength: 0,
// 已生成课程数据
oldData: {},
// 课程索引
courseData: '',
// 模拟机列表
simulatorDataList: [
{title: 'FFS-1', id: 'FFS-1'},
{title: 'FFS-2', id: 'FFS-2'},
{title: 'FFS-3', id: 'FFS-3'},
{title: 'FFS-4', id: 'FFS-4'},
{title: 'FFS-5', id: 'FFS-5'},
{title: 'MAX', id: 'MAX'},
{title: '外训', id: '外训'},
],
// 模拟机选中
simulatorValue: [],
// 当前周
week: moment(new Date())
}
},
filters: {
dateFormat: function (date, format) {
return moment(new Date(date)).format(format)
}
},
created() {
},
methods: {
// 加载模拟机
loadSimulator() {
this.tableList = [];
this.simulatorValue.forEach(item => {
this.tableList.push({
id: item,
list: weekDataList(this.week)
});
});
},
// 周选择
changeWeek() {
},
// 选择模拟器
onChange(event) {
console.log(event);
},
// 选中课程数据
selectData(data) {
this.courseData = data;
},
// 表格生成下载图片
save() {
// html2canvas(this.$refs['tableEl']).then(res => {
// const url = res.toDataURL('image/png');
// const a = document.createElement('a');
// a.href = url;
// a.download = '课程表';
// a.click();
// });
},
/**
* 拖拽元素释放将所有行的预占位块隐藏掉并且将课程拖拽状态置为false
* @param event {HTMLElement} 拖拽元素对象
* @param data {any?} 传入数据
*/
drag(event, data) {
if (data && data.type === 'old') {
data.data.event = false;
}
this.tableList.forEach(item => {
item.list.forEach(data => {
data.showEl = false
});
});
this.isDrag = false;
},
/**
* 当课程拖拽生成元素在时间段节点上释放以后
* 判断当前预置课程时间段是否与已经生成时间段存在交集,如果存在交集,则不允许生成课程
* @param event
* @param gantt {Array<Object>} 单独一项甘特图数据
* @param item {Object} 甘特图行数据
* @param g {number} 当前甘特图索引
* @param i {number} 当前所在行
* @param k {number} 所在单元格
* @returns {boolean}
*/
drop(event, gantt, g, item, i, k) {
event.stopPropagation();
event.preventDefault();
const num = this.dataLength + k;
let isDrop = false;
// 预设时间范围
const arr1 = [];
for (let j = k + 1; j <= num; j++) {
arr1.push(j);
}
item.dataList.forEach(data => {
// 课程时间范围
const arr2 = [];
if (this.courseData?.indexKey !== data.indexKey) {
for (let j = data.col + 1; j < (data.col + data.width + 1); j++) {
arr2.push(j);
}
// 计算范围交集
const arr3 = arr1.filter(function(num) {
return arr2.indexOf(num) !== -1;
});
if (arr3.length > 0) {
isDrop = true;
}
}
});
// 如果剩余时间小于课程时间,那么不允许添加课程
if (isDrop || num > this.colTimeList.length) {
this.$message.warning('当前剩余时间不足');
return false
}
if (this.courseData !== '') {
this.tableList.forEach(tableList => {
tableList.list.forEach(data => {
const list = data.dataList.filter(item => item.indexKey !== this.courseData.indexKey);
data.dataList = [...list];
});
});
}
// 如果剩余空闲时间大于等于课程时间那么将生成时间段课程数据
item.dataList.push({
col: k,
width: Number(JSON.stringify(JSON.parse(this.dataLength))),
event: false,
indexKey: (g + '' + i + k),
data: {
startTime: this.colTimeList[k],
endTime: this.colTimeList[num]
}
});
this.courseData = '';
return false;
},
/**
* 当前为模拟数据,当替换为真实数据时候需要将数据挂起
* @param event {Object} 数据(当前不存在数据)
* @param data {Object} 冗余传参数据
*/
dragstartList(event, data) {
if (data.type === 'old') {
data.data.event = true;
this.courseData = data.data;
}
this.showElWidth = (data.length * this.colWidth);
this.dataLength = data.length;
this.isDrag = true;
},
/**
* 当拖拽元素进入单元格时
* 进行判断是否是需要生成课程条目
*
* @param event
* @param gantt {Array<Object>} 甘特图数据
* @param item 当前行数据域
* @param i 当前在第几行
* @param k {number} 当前所在第几个单元格
* @returns {boolean}
*/
dragoverList(event, gantt, item, i, k) {
if (!this.isDrag) return false;
gantt.forEach(data => data.showEl = false);
item.showEl = true;
this.showElLeft = (k * this.colWidth);
event.stopPropagation();
event.preventDefault();
return false;
},
},
watch: {
}
}
</script>
<style lang="less" scoped>
.simulator-schedule-box {
.table-page-search-wrapper-form{
display: flex;
justify-content: space-between;
flex-wrap: wrap;
grid-column-gap: 30px;
.table-page-search-wrapper-form-left{
display: flex;
flex-wrap: wrap;
grid-column-gap: 10px;
}
.table-page-search-wrapper-form-right{
display: flex;
flex-wrap: wrap;
grid-column-gap: 10px;
}
}
.table-box {
width: 100%;
height: auto;
border: 1px solid #000;
display: flex;
.left {
width: 25%;
height: auto;
box-sizing: border-box;
border-right: 1px solid #000;
ul{
list-style: none;
padding: 0;
li{
width: 30px;
text-align: center;
background: blue;
margin: 10px;
}
}
}
.right {
width: 75%;
height: auto;
display: flex;
flex-direction: column;
justify-content: space-between;
.right-header {
width: 100%;
height: 65px;
background: #d3dce6;
}
.gantt-box {
width: 100%;
overflow: auto;
.gantt-list {
width: auto;
height: auto;
display: flex;
grid-column-gap: 30px;
ul {
list-style: none;
padding: 0;
margin: 0;
}
.gantt-item {
width: auto;
height: auto;
border: 1px solid #000;
margin: 10px;
.gantt-header {
width: auto;
height: 40px;
display: flex;
.gantt-header-label {
width: 60px;
line-height: 40px;
text-align: center;
}
.gantt-header-item {
text-align: center;
line-height: 10px;
border-left: 1px solid #000;
box-sizing: border-box;
pre {
margin: 5px;
overflow: hidden;
}
}
}
.gantt-row-list {
width: auto;
height: 40px;
display: flex;
border-top: 1px solid #000;
.gantt-row-list-label {
width: 60px;
text-align: center;
}
.gantt-row-content {
width: auto;
height: 40px;
display: flex;
position: relative;
overflow: hidden;
.gantt-row-time {
position: absolute;
width: auto;
height: 40px;
top: 0;
left: 0;
display: flex;
pointer-events: none;
.gantt-row-time-item {
border-left: 1px solid #000;
height: 40px;
pointer-events: none;
box-sizing: border-box;
}
}
.gantt-row-col{
width: auto;
height: 40px;
display: flex;
.gantt-row-col-item{
}
.gantt-row-col-item-border{
border-left: 1px solid #ddd;
}
}
.gantt-row-data-list{
width: 100%;
height: 40px;
pointer-events: none;
position: absolute;
box-sizing: border-box;
.gantt-row-data-item{
width: 0;
height: 40px;
position: absolute;
background: blue;
pointer-events: auto;
box-sizing: border-box;
z-index: 999;
top: -1px;
&.gantt-row-data-item-border{
border: 1px solid #000;
}
.gantt-row-data-item-col-list{
width: 100%;
height: 40px;
position: absolute;
top: 0;
left: 0;
display: flex;
.gantt-row-data-item-col-item{
height: 40px;
box-sizing: border-box;
}
}
}
}
.table-row-item-show{
width: 10px;
height: 40px;
position: absolute;
top: 0;
background: rgba(0,0,0,.3);
pointer-events: none;
z-index: 9999;
}
}
}
}
.empty{
width: 100%;
height: 300px;
display: flex;
align-items: center;
justify-content: center;
}
}
}
.right-footer{
width: 100%;
height: 40px;
background: #d3dce6;
display: flex;
justify-content: space-between;
padding: 0 10px;
.simulator-select{
width: auto;
height: 100%;
display: flex;
align-items: center;
}
.simulator-load{
width: auto;
display: flex;
justify-content: flex-end;
align-items: center;
}
}
}
}
}
</style>