Files
yudao-dev/src/views/group/Schedule/add-or-updata.vue
2025-12-11 12:51:41 +08:00

1356 lines
34 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!--
* @Author: zwq
* @Date: 2025-10-13 15:07:24
* @LastEditors: zwq
* @LastEditTime: 2025-12-03 15:48:26
* @Description:
-->
<template>
<div>
<!-- 产线信息-工艺工序圆圈 -->
<div class="circle-container">
<div v-for="(item, index) in dotArr" :key="index" class="circle-wrapper">
<div
class="circle"
:style="{
background: stepNum == index + 1 ? '#0B58FF' : '',
}">
{{ index + 1 }}
</div>
<div
class="circle-text"
:style="{
color: stepNum == index + 1 ? '#0B58FF' : '',
}">
{{ item.name }}
</div>
<!-- 圆点后面的虚线 -->
<div v-if="index < 2" class="connector" />
</div>
</div>
<div v-if="stepNum == 1">
<el-form
:model="dataForm"
:rules="dataRule"
ref="dataForm"
@keyup.enter.native="dataFormSubmit()"
label-position="top"
label-width="80px">
<el-row :gutter="10">
<el-col :span="6">
<el-form-item label="计划编号" prop="code">
<el-input
v-model="dataForm.code"
clearable
placeholder="请输入计划编号" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="计划名称" prop="name">
<el-input
v-model="dataForm.name"
clearable
placeholder="请输入计划名称" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="部门" prop="deptId">
<dept-select
style="width: 100%"
ref="deptSelect"
v-model="dataForm.deptId"
@DeptId="setDeptId"></dept-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="周末休假方式" prop="weekType">
<el-select
style="width: 100%"
v-model="dataForm.weekType"
placeholder="请选择周末休假方式">
<el-option
v-for="item in options1"
:key="item.value"
:label="item.label"
:value="item.value"></el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="10">
<el-col :span="6">
<el-form-item label="开始时间" prop="startDay">
<el-date-picker
style="width: 100%"
v-model="dataForm.startDay"
type="datetime"
value-format="timestamp"
placeholder="选择日期时间"></el-date-picker>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="结束时间" prop="endDay">
<el-date-picker
style="width: 100%"
v-model="dataForm.endDay"
type="datetime"
value-format="timestamp"
placeholder="选择日期时间"></el-date-picker>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="倒班方式" prop="shiftType">
<el-select
style="width: 100%"
v-model="dataForm.shiftType"
placeholder="请选择倒班方式">
<el-option
v-for="item in options2"
:key="item.value"
:label="item.label"
:value="item.value"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="6" v-if="dataForm.shiftType > 1">
<el-form-item label="同班次连排" prop="shiftSustainedNum">
<el-input
v-model="dataForm.shiftSustainedNum"
oninput="value=value.replace(/^(0+)|[^\d]+/g,'')">
<span slot="append"></span>
</el-input>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="备注" prop="remark">
<el-input
v-model="dataForm.remark"
clearable
placeholder="请输入备注" />
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
<div v-if="stepNum == 2">
<small-title style="margin: 16px 0" size="sm" :no-padding="true">
班次
<el-tag type="warning" style="color: black" v-if="!isClassTimePass">
<i class="el-icon-warning" style="color: #ffbd02"></i>
两班倒和三班倒的班次设置时间
<span style="color: red">不能重叠</span>
在24小时
</el-tag>
<el-tag type="warning" style="color: black" v-if="!covers24Hours">
<i class="el-icon-warning" style="color: #ffbd02"></i>
两班倒和三班倒的班次设置时间,班次时间
<span style="color: red">必须连续</span>
在24小时
</el-tag>
</small-title>
<base-table
:table-props="tableProps1"
:page="1"
:limit="10"
:table-data="tableData1">
<method-btn
v-if="tableBtn1.length"
slot="handleBtn"
:width="80"
label="操作"
:method-list="tableBtn1"
@clickBtn="handleClick1" />
</base-table>
<small-title style="margin: 16px 0" size="sm" :no-padding="true">
班组
<el-tag type="warning" style="color: black">
<i class="el-icon-warning" style="color: #ffbd02"></i>
排班从计划开始时间对应的第一个班次开始
<span style="color: red">班组列表顺序</span>
依次循环执行
</el-tag>
</small-title>
<base-table
:table-props="tableProps2"
:page="1"
:limit="10"
:table-data="tableData2"
:add-button-show="'新增'"
@emitFun="handleSort"
@emitButtonClick="addNewGroup">
<method-btn
v-if="tableBtn2.length"
slot="handleBtn"
:width="110"
label="操作"
:method-list="tableBtn2"
@clickBtn="handleClick2" />
</base-table>
</div>
<div v-if="stepNum == 3">
<small-title
style="margin: 16px 0; font-size: 16px"
size="de"
:no-padding="true">
排班计划预览
<span style="font-size: 14px; color: #ff1c15">
系统将根据
<el-popover
placement="right"
title="比如三班两倒同班次连排2天不休周末排班如下"
width="300"
trigger="hover">
<el-table :data="gridData">
<el-table-column property="date"></el-table-column>
<el-table-column
property="class1"
label="班次一"></el-table-column>
<el-table-column
property="class2"
label="班次二"></el-table-column>
</el-table>
<i
slot="reference"
class="el-icon-chat-dot-square"
style="color: #409eff; font-size: 18px"></i>
</el-popover>
设置按班组轮班顺序依次循环每日班次生成正式排班计划请确认
</span>
</small-title>
<el-tag>
{{
'计划时间:' +
parseTime(dataForm.startDay) +
' 至 ' +
parseTime(dataForm.endDay)
}}
</el-tag>
<div
style="
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
gap: 10px;
margin: 15px;
">
<div style="display: grid; grid-template-columns: 40px auto; gap: 10px">
<div class="daobanpng" />
<div>
<div style="color: rgba(0, 0, 0, 0.6)">倒班方式</div>
<div style="color: #000000">
{{ ['长白班', '两班倒', '三班倒'][dataForm.shiftType - 1] }}
</div>
</div>
</div>
<div style="display: grid; grid-template-columns: 40px auto; gap: 10px">
<div class="lianpaipng" />
<div>
<div style="color: rgba(0, 0, 0, 0.6)">同班次连排</div>
<div style="color: #000000">
{{
(dataForm.shiftSustainedNum ? dataForm.shiftSustainedNum : '') +
['日', '周', '月', '季'][dataForm.shiftSustainedType - 1]
}}
</div>
</div>
</div>
<div style="display: grid; grid-template-columns: 40px auto; gap: 10px">
<div class="xiujiapng" />
<div>
<div style="color: rgba(0, 0, 0, 0.6)">周末休假方式</div>
<div style="color: #000000">
{{ ['双休', '周六休', '周日休', '不休'][dataForm.weekType - 1] }}
</div>
</div>
</div>
<div style="display: grid; grid-template-columns: 40px auto; gap: 10px">
<div class="banzupng" />
<div>
<div style="color: rgba(0, 0, 0, 0.6)">参与班组</div>
<div style="color: #000000">
{{
tableData2
.map((item) => {
return item.name;
})
.join('/')
}}
</div>
</div>
</div>
</div>
<small-title
style="margin: 16px 0; font-size: 16px"
size="de"
:no-padding="true">
预览周期
<span>
<el-select
style="height: 20px"
v-model="period"
size="mini"
@change="setPeriod"
placeholder="请选择">
<el-option
v-for="item in periodArr"
:key="item.value"
:label="item.label"
:value="item.value"></el-option>
</el-select>
</span>
</small-title>
<base-table
v-if="tableProps3.length > 0"
:table-props="tableProps3"
:table-data="tableData3"></base-table>
</div>
<base-dialog
:dialogTitle="'修改班次'"
:dialogVisible="addOrUpdateVisible1"
@cancel="cancel1"
@confirm="handleConfirm1"
:before-close="cancel1"
:destroy-on-close="true"
append-to-body
width="40%">
<edit-class
ref="editClassRef"
@refreshTableData="refreshTableData1"></edit-class>
</base-dialog>
<base-dialog
:dialogTitle="'班组选择'"
:dialogVisible="addOrUpdateVisible2"
@cancel="cancel2"
@confirm="handleConfirm2"
:before-close="cancel2"
:destroy-on-close="true"
append-to-body
width="40%">
<add-group
ref="addGroupRef"
@refreshTableData="refreshTableData2"></add-group>
</base-dialog>
<base-dialog
:dialogTitle="'绑定产线'"
:dialogVisible="addOrUpdateVisible3"
@cancel="cancel3"
@confirm="handleConfirm3"
:before-close="cancel3"
:destroy-on-close="true"
append-to-body
width="50%">
<bind-line
ref="bindLineRef"
@refreshTableData="refreshTableData3"></bind-line>
</base-dialog>
</div>
</template>
<script>
import deptSelect from './../deptSelect.vue';
import SmallTitle from './SmallTitle';
import propfirstSpan from './propfirstSpan.vue';
import propSpan from './propSpan.vue';
import { parseTime } from '@/filter/code-filter';
import editClass from './edit-class.vue';
import addGroup from './add-group.vue';
import bindLine from './bind-line.vue';
import sortSet from './sortSet.vue';
import {
getCode,
createStepOne,
returnStepOne,
createStepTwo,
returnStepTwo,
getPerView,
draftEditing,
cancelStepThree,
checkPlan,
createStepFour,
} from '@/api/group/Schedule';
const tableProps1 = [
{
prop: 'name',
label: '班次名称',
},
{
prop: 'startTime',
label: '开始时间',
filter: (val) => {
return val ? val.slice(0, -3) : '-';
},
},
{
prop: 'endTime',
label: '结束时间',
filter: (val) => {
return val ? val.slice(0, -3) : '-';
},
},
{
prop: 'remark',
label: '备注',
},
];
const tableProps2 = [
{
prop: 'shunxu',
label: '顺序',
width: 70,
subcomponent: sortSet,
},
{
prop: 'code',
label: '班组编号',
width: 140,
showOverflowtooltip: true,
},
{
prop: 'name',
label: '班组名称',
},
{
prop: 'isProduction',
label: '是否生产班组',
filter: (val) => {
return val ? '是' : '否';
},
width: 110,
},
{
prop: 'lineName',
label: '产线及工段',
showOverflowtooltip: true,
},
];
export default {
components: {
deptSelect,
SmallTitle,
editClass,
addGroup,
bindLine,
propfirstSpan,
propSpan,
},
data() {
return {
dotArr: [
{
name: '基础信息',
},
{
name: '班组班次',
},
{
name: '计划确认',
},
],
stepNum: 1, // 当前第几步
isEdit: false, //是否是编辑状态,编辑的时候,让前端上一步和取消都不用接口
shiftTypeEdit: undefined, //该值为编辑时获取的shiftType到第二步判断dataForm.shiftType是否改变同时改变班次信息
//第一步参数
dataForm: {
id: undefined,
code: undefined,
name: undefined,
startDay: undefined,
endDay: undefined,
deptId: undefined,
weekType: undefined,
shiftType: undefined,
shiftSustainedNum: undefined,
shiftSustainedType: 1,
remark: undefined,
},
options1: [
{
value: 1,
label: '双休',
},
{
value: 2,
label: '周六休',
},
{
value: 3,
label: '周日休',
},
{
value: 4,
label: '不休',
},
],
options2: [
{
value: 1,
label: '长白班',
},
{
value: 2,
label: '两班倒',
},
{
value: 3,
label: '三班倒',
},
],
dataRule: {
code: [
{ required: true, message: '计划编号不能为空', trigger: 'blur' },
],
name: [
{ required: true, message: '计划名称不能为空', trigger: 'blur' },
],
deptId: [{ required: true, message: '部门不能为空', trigger: 'blur' }],
startDay: [
{ required: true, message: '开始时间不能为空', trigger: 'change' },
],
endDay: [
{ required: true, message: '结束时间不能为空', trigger: 'change' },
],
weekType: [
{
required: true,
message: '周末休假方式不能为空',
trigger: 'change',
},
],
shiftType: [
{
required: true,
message: '倒班方式方式不能为空',
trigger: 'change',
},
],
shiftSustainedNum: [
{ required: true, message: '同班次连排不能为空', trigger: 'blur' },
],
},
//第二步参数
tableProps1,
tableBtn1: [
{
type: 'edit',
btnName: '编辑',
},
].filter((v) => v),
isClassTimePass: true, //修改班次时间后需要判断时间是否有重叠且是否在24小时内 true是没问题的
covers24Hours: true, //修改班次时间后需要判断时间是否覆盖24小时连续的时间 true是没问题的
tableData1: [],
tableProps2,
tableBtn2: [
{
type: 'bind',
btnName: '绑定产线',
showParam: {
type: '&',
data: [
{
type: 'equal',
name: 'isProduction',
value: true,
},
],
},
},
{
type: 'delete',
btnName: '删除',
},
].filter((v) => v),
tableData2: [],
addOrUpdateVisible1: false,
addOrUpdateVisible2: false,
addOrUpdateVisible3: false,
//第三步参数
gridData: [
{
date: '第1天',
class1: 'A',
class2: 'B',
},
{
date: '第2天',
class1: 'A',
class2: 'B',
},
{
date: '第3天',
class1: 'C',
class2: 'A',
},
{
date: '第4天',
class1: 'C',
class2: 'A',
},
{
date: '第5天',
class1: 'B',
class2: 'C',
},
{
date: '第6天',
class1: 'B',
class2: 'C',
},
{
date: '第7天',
class1: 'A',
class2: 'B',
},
{
date: '第8天',
class1: 'A',
class2: 'B',
},
],
period: 7, //预览周期
periodArr: [
{
value: 7,
label: '7天',
},
{
value: 14,
label: '14天',
},
{
value: 30,
label: '30天',
},
],
tableProps3: [],
tableData3: [],
};
},
methods: {
init(id, isEdit) {
this.dataForm.id = id || undefined;
this.isEdit = isEdit || false;
this.$nextTick(() => {
this.$refs['dataForm'].resetFields();
if (this.dataForm.id) {
draftEditing(this.dataForm.id).then((res) => {
this.dataForm = res.data?.stepOne;
this.shiftTypeEdit = res.data?.stepOne.shiftType;
this.setDataForm();
this.tableData1 = res.data?.stepTwo.groupPlanClassesBaseVOList;
this.tableData2 = res.data?.stepTwo.groupPlanTeamBaseVOList;
this.tableData2.forEach((item, index) => {
let lineName = [];
if (item.isProduction) {
lineName = this.setLineName(item.bindLineTree);
this.$set(
this.tableData2[index],
'lineName',
lineName.join(';')
);
}
});
this.stepNum = 3;
this.getThreeGroup(7); // 第三步,进来加载预览排班,默认7天预览周期
});
} else {
getCode().then((res) => {
this.dataForm.code = res.data;
});
}
});
},
//上一步
upSubmit() {
if (this.stepNum == 2) {
if (!this.isEdit) {
//编辑的时候,让前端上一步和取消都不用接口,
returnStepOne(this.dataForm.id).then((res) => {
this.stepNum -= 1;
this.$emit('setSN', this.stepNum);
this.$nextTick(() => {
this.setDataForm();
});
});
} else {
this.stepNum -= 1;
this.$emit('setSN', this.stepNum);
this.$nextTick(() => {
this.setDataForm();
});
}
} else if (this.stepNum == 3) {
if (!this.dataForm.id) {
//编辑的时候,让前端上一步和取消都不用接口,
returnStepTwo(this.dataForm.id).then((res) => {
this.stepNum -= 1;
this.$emit('setSN', this.stepNum);
});
} else {
this.stepNum -= 1;
this.$emit('setSN', this.stepNum);
}
}
},
//下一步
nextSubmit() {
if (this.stepNum == 1) {
this.$refs['dataForm'].validate((valid) => {
if (!valid) {
return false;
}
if (!this.dataForm.deptId) {
this.$message('部门不能为空');
return;
}
const compareDate = this.compareDates(
this.dataForm.startDay,
this.dataForm.endDay
);
if (compareDate > 0) {
this.$message('开始时间不能大于结束时间');
return;
}
createStepOne(this.dataForm).then((res) => {
if (!this.dataForm.id) {
this.dataForm.id = res.data;
}
this.stepNum += 1;
this.$emit('setSN', this.stepNum);
if (
!this.dataForm.id ||
this.dataForm.shiftType !== this.shiftTypeEdit
) {
this.setTWOclass(); // (新增时或者编辑时倒班方式变了)第一步提交后,根据倒班方式设置第二部班次
}
});
});
return;
}
if (this.stepNum == 2) {
//班组验证
if (!this.tableData2 || this.tableData2.length == 0) {
this.$message('班组不能为空');
return;
}
let isbindLine = false;
this.tableData2.forEach((item) => {
if (
item.isProduction &&
(!item.bindLineTree || item.bindLineTree.length == 0)
) {
isbindLine = true;
}
});
if (isbindLine) {
this.$message('生产班组必须绑定产线或工段');
return;
}
const ids = this.tableData2.map((item) => item.id);
const uniqueIds = [...new Set(ids)];
if (ids.length !== uniqueIds.length) {
this.$message('存在重复的班组,请检查!');
return;
}
if (this.dataForm.shiftType == 1 && this.tableData2.length > 1) {
this.$message('白班,班组数量仅支持 1个 班组');
return;
} else if (this.dataForm.shiftType == 2 && this.tableData2.length < 2) {
this.$message('两班倒,班组数量至少 2个 班组');
return;
} else if (this.dataForm.shiftType == 3 && this.tableData2.length < 3) {
this.$message('三班倒,班组数量至少 3个 班组');
return;
}
//班次验证
if (!this.isClassTimePass || !this.covers24Hours) {
this.$message('班次内时间有误,请重新填写!');
return;
}
let data = {};
data.planId = this.dataForm.id;
data.groupPlanClassesBaseVOList = []; //排班-班次信息list
this.tableData1.forEach((item, index) => {
const obj = {
sort: index + 1,
name: item.name,
remark: item.remark,
startTime: item.startTime,
endTime: item.endTime,
daySpan: item.daySpan,
code: item.code,
};
data.groupPlanClassesBaseVOList.push(obj);
});
data.groupPlanTeamBaseVOList = []; //排班-班组信息list
this.tableData2.forEach((item, index) => {
const obj = {
sort: index + 1,
teamId: item.teamId || item.id,
name: item.name,
leaderName: item.leaderName,
leaderPhone: item.leaderPhone,
bindData: JSON.stringify(item.bindLineTree),
};
data.groupPlanTeamBaseVOList.push(obj);
});
createStepTwo(data).then((res) => {
if (res.code === 200 || res.code === 0) {
this.stepNum += 1;
this.$emit('setSN', this.stepNum);
this.getThreeGroup(7); // 第三步,进来加载预览排班,默认7天预览周期
} else {
this.$message(res.msg);
}
});
return;
}
if (this.stepNum == 3) {
let data = { planId: this.dataForm.id };
data.groupPlanTeamBaseVOList = []; //排班-班组信息list
this.tableData2.forEach((item) => {
const obj = {
teamId: item.teamId || item.id,
name: item.name,
bindData: JSON.stringify(item.bindLineTree),
};
data.groupPlanTeamBaseVOList.push(obj);
});
checkPlan(data).then((RES) => {
createStepFour(this.dataForm.id).then((res) => {
if (res.code === 200 || res.code === 0) {
this.$modal.msgSuccess('创建计划成功');
this.stepNum = 1;
this.$emit('setSN', this.stepNum);
this.$emit('refreshDataList');
} else {
this.$message(res.msg);
}
});
});
return;
}
},
//第一步
//子组件获取的部门id
setDeptId(val) {
this.dataForm.deptId = val;
},
//详情时传给子组件的部门id
setDataForm() {
this.$refs.deptSelect.setID(this.dataForm.deptId);
},
//比较时间,开始时间在结束时间前
compareDates(date1, date2) {
const d1 = date1;
const d2 = date2;
if (d1 === d2) {
return 0; // 日期相等
} else if (d1 < d2) {
return -1; // date1 在 date2 之前
} else {
return 1; // date1 在 date2 之后
}
},
//第二步
//设置第二部班次
setTWOclass() {
this.tableData1 = [];
if (this.dataForm.shiftType == 1) {
const obj = {
sort: 1,
name: '长白班',
startTime: '08:00:00',
endTime: '17:00:00',
daySpan: 0, //是否跨天
code: 1, //排版日历页面,根据这个字段显示颜色
};
this.tableData1.push(obj);
} else if (this.dataForm.shiftType == 2) {
const obj1 = {
sort: 1,
name: '白班',
startTime: '08:00:00',
endTime: '20:00:00',
daySpan: 0,
code: 1,
};
const obj2 = {
sort: 2,
name: '夜班',
startTime: '20:00:00',
endTime: '08:00:00',
daySpan: 1,
code: 3,
};
this.tableData1.push(obj1, obj2);
} else if (this.dataForm.shiftType == 3) {
const obj1 = {
sort: 1,
name: '早班',
startTime: '08:00:00',
endTime: '16:00:00',
daySpan: 0,
code: 1,
};
const obj2 = {
sort: 2,
name: '中班',
startTime: '16:00:00',
endTime: '00:00:00',
daySpan: 1,
code: 2,
};
const obj3 = {
sort: 3,
name: '夜班',
startTime: '00:00:00',
endTime: '08:00:00',
daySpan: 0,
code: 3,
};
this.tableData1.push(obj1, obj2, obj3);
}
},
/**
* 将时间字符串转换为分钟数
* @param {string} timeStr - 时间字符串,如 "08:00:00"
* @returns {number} 分钟数
*/
timeToMinutes(timeStr) {
const [hours, minutes, s] = timeStr.split(':').map(Number);
return hours * 60 + minutes;
},
/**
* 修改班次时间后需要判断时间是否有覆盖且是否覆盖24小时
* @param {string} start1 - 开始时间1
* @param {string} end1 - 结束时间1
* @param {string} start2 - 开始时间2
* @param {string} end2 - 结束时间2
* @returns {boolean} 是否重叠 true则重叠false则可以使用
*/
isTimeOverlap(start1, end1, start2, end2) {
const start1Min = this.timeToMinutes(start1);
const end1Min = this.timeToMinutes(end1);
const start2Min = this.timeToMinutes(start2);
const end2Min = this.timeToMinutes(end2);
// 处理跨天情况
const totalMinutes = 24 * 60;
// 如果结束时间小于开始时间,说明跨天
const isEnd1NextDay = end1Min < start1Min;
const isEnd2NextDay = end2Min < start2Min;
if (isEnd1NextDay && isEnd2NextDay) {
// 两个时间段都跨天,直接比较
return true;
} else if (isEnd1NextDay) {
// 第一个时间段跨天
return start2Min < end1Min || end2Min > start1Min;
} else if (isEnd2NextDay) {
// 第二个时间段跨天
return start1Min < end2Min || end1Min > start2Min;
} else {
// 都不跨天,正常比较
return start1Min < end2Min && end1Min > start2Min;
}
},
/**
* 检查多个班次是否连续覆盖24小时
* @param {Array} shifts - 班次数组每个班次包含startTime和endTime
* @returns {boolean} 是否连续覆盖24小时
*/
isCover24Hours(shifts) {
if (!shifts || shifts.length === 0) return false;
const totalMinutes = 24 * 60;
// 将班次转换为时间段对象
const intervals = shifts.map((shift) => ({
start: this.timeToMinutes(shift.startTime),
end: this.timeToMinutes(shift.endTime),
}));
// 处理跨天情况如果结束时间小于开始时间加上24小时
const normalizedIntervals = intervals.map((interval) => {
let { start, end } = interval;
if (end < start) {
end += totalMinutes;
}
return { start, end, originalEnd: interval.end };
});
// 按开始时间排序
normalizedIntervals.sort((a, b) => a.start - b.start);
// 检查是否连续覆盖
let coveredUntil = normalizedIntervals[0].end;
let hasGap = false;
for (let i = 1; i < normalizedIntervals.length; i++) {
const current = normalizedIntervals[i];
// 如果当前班次的开始时间大于已覆盖的结束时间,说明有间隔
if (current.start > coveredUntil) {
hasGap = true;
break;
}
// 更新已覆盖的结束时间
coveredUntil = Math.max(coveredUntil, current.end);
}
// 检查是否覆盖完整的24小时
const firstStart = normalizedIntervals[0].start;
// 判断条件没有间隔且覆盖时间达到24小时
return !hasGap && coveredUntil - firstStart >= totalMinutes;
},
//编辑班次
handleClick1(val) {
this.addOrUpdateVisible1 = true;
this.$nextTick(() => {
this.$refs.editClassRef.init(val.data);
});
},
//班组操作
handleClick2(val) {
if (val.type === 'bind') {
this.addOrUpdateVisible3 = true;
this.$nextTick(() => {
this.$refs.bindLineRef.init(val.data);
});
} else if (val.type === 'delete') {
this.tableData2.splice(val.data._pageIndex - 1, 1);
}
},
//班组改变顺序
handleSort(val) {
if (val.type == 'up') {
const temp = this.tableData2[val.index];
this.tableData2.splice(val.index, 1);
let i = 0;
//如果是数组第一个,再向上则放到最后
if (val.index == 0) {
i = this.tableData2.length;
} else {
i = val.index - 1;
}
this.tableData2.splice(i, 0, temp);
} else {
const temp = this.tableData2[val.index];
this.tableData2.splice(val.index, 1);
let i = 0;
//如果是数组最后一个,再向下则放到第一个(因为先splice(val.index, 1)所以这里length不用-1)
if (val.index == this.tableData2.length) {
i = 0;
} else {
i = val.index + 1;
}
this.tableData2.splice(i, 0, temp);
}
},
// 新增班组
addNewGroup() {
this.addOrUpdateVisible2 = true;
this.$nextTick(() => {
this.$refs.addGroupRef.init(this.dataForm.deptId, this.tableData2);
});
},
cancel1() {
this.addOrUpdateVisible1 = false;
},
cancel2() {
this.addOrUpdateVisible2 = false;
},
cancel3() {
this.addOrUpdateVisible3 = false;
},
handleConfirm1() {
this.$refs.editClassRef.dataFormSubmit();
},
handleConfirm2() {
this.$refs.addGroupRef.dataFormSubmit();
},
handleConfirm3() {
this.$refs.bindLineRef.dataFormSubmit();
},
refreshTableData1(index, val) {
this.tableData1.splice(index, 1, val);
this.tableData1.sort((a, b) => {
if (a.sort === b.sort) {
return -1; // sort相等时a排在b前面
}
return a.sort - b.sort;
});
this.tableData1.forEach((item, index) => {
item.sort = index + 1;
});
//修改班次时间后需要判断时间是否有重叠且是否在24小时内,isTimeOverlap方法返回false才是校验通过
if (this.dataForm.shiftType == 2) {
const twoClass = this.tableData1.map(({ startTime, endTime }) => ({
startTime,
endTime,
}));
this.isClassTimePass = !this.isTimeOverlap(
twoClass[0].startTime,
twoClass[0].endTime,
twoClass[1].startTime,
twoClass[1].endTime
);
} else if (this.dataForm.shiftType == 3) {
const threeClass = this.tableData1.map(({ startTime, endTime }) => ({
startTime,
endTime,
}));
const result1 = this.isTimeOverlap(
threeClass[0].startTime,
threeClass[0].endTime,
threeClass[1].startTime,
threeClass[1].endTime
);
const result2 = this.isTimeOverlap(
threeClass[1].startTime,
threeClass[1].endTime,
threeClass[2].startTime,
threeClass[2].endTime
);
const result3 = this.isTimeOverlap(
threeClass[0].startTime,
threeClass[0].endTime,
threeClass[2].startTime,
threeClass[2].endTime
);
this.isClassTimePass = !(result1 || result2 || result3);
}
//修改班次时间后两班倒和三班倒需要判断时间是否连续覆盖24小时内
if (this.dataForm.shiftType > 1) {
this.covers24Hours = this.isCover24Hours(this.tableData1);
console.log(1111, this.covers24Hours);
}
this.cancel1();
},
refreshTableData2(val) {
this.tableData2.push(...val);
this.cancel2();
},
refreshTableData3(index, val) {
this.tableData2[index].bindLineTree = val;
let lineName = this.setLineName(val);
if (lineName) {
this.$set(this.tableData2[index], 'lineName', lineName.join(';'));
}
this.cancel3();
},
//提取绑定的产线工段名展示出来
setLineName(data) {
const paths = [];
const processNode = (node, currentPath = []) => {
const newPath = [...currentPath, node.name];
if (node.children && node.children.length > 0) {
// 检查子节点是否都是叶子节点
const allChildrenAreLeaves = node.children.every(
(child) => !child.children || child.children.length === 0
);
if (allChildrenAreLeaves && node.children.length > 1) {
// 如果所有子节点都是叶子节点且多于1个用括号合并显示
const leafNames = node.children
.map((child) => child.name)
.join(' 、 ');
paths.push(`${newPath.join('/')}/(${leafNames})`);
} else if (allChildrenAreLeaves && node.children.length === 1) {
// 只有一个叶子子节点,用括号显示
paths.push(`${newPath.join('/')}/${node.children[0].name}`);
} else {
// 继续处理子节点
node.children.forEach((child) => processNode(child, newPath));
}
} else {
// 没有子节点的节点如原片产线1用括号显示
paths.push(`${currentPath.join('/')}/${node.name}`);
}
};
data.forEach((node) => processNode(node));
return paths;
},
//第三步
// 进来加载预览排班
getThreeGroup(day) {
const data = {
id: this.dataForm.id,
count: day,
};
this.period = day;
this.tableProps3 = [];
this.tableData3 = [];
getPerView(data).then((res) => {
this.setThreeData(res.data);
});
},
setPeriod(val) {
this.getThreeGroup(val);
},
setThreeData(data) {
//表头第一个为班次名称
const propfirst = {
prop: 'name',
label: '',
align: 'center',
fixed: true,
subcomponent: propfirstSpan,
};
this.tableProps3.push(propfirst);
data.forEach((item, index) => {
//设置所有日期为表头
const obj = {
prop: 'daydata' + index,
label: item.day,
work: item.work,
subcomponent: propSpan,
showOverflowtooltip: true,
};
this.tableProps3.push(obj);
item.det.forEach((sItem, sIndex) => {
if (index === 0) {
let sObj = {
name: sItem.classesName + '/' + sItem.time,
sort: sItem.sort,
};
sObj['daydata' + index] = item.work
? sItem.teamName
: item.holidayName;
this.tableData3.push(sObj);
} else {
this.$set(
this.tableData3[sIndex],
'daydata' + index,
item.work ? sItem.teamName : item.holidayName
);
this.$set(this.tableData3[sIndex], 'sort', sItem.sort);
}
});
});
},
cancelStep() {
if (this.stepNum > 1 && !this.isEdit) {
//编辑的时候,让前端上一步和取消都不用接口,
cancelStepThree(this.dataForm.id).then((res) => {
this.$message('已取消计划');
this.$emit('refreshDataList');
});
} else {
this.$emit('refreshDataList');
}
},
},
};
</script>
<style scoped>
.daobanpng {
width: 40px;
height: 40px;
background-image: url('~@/assets/images/daoban.png');
background-size: 100% 100%;
}
.lianpaipng {
width: 40px;
height: 40px;
background-image: url('~@/assets/images/lianpai.png');
background-size: 100% 100%;
}
.xiujiapng {
width: 40px;
height: 40px;
background-image: url('~@/assets/images/xiujia.png');
background-size: 100% 100%;
}
.banzupng {
width: 40px;
height: 40px;
background-image: url('~@/assets/images/banzu.png');
background-size: 100% 100%;
}
</style>
<!-- //序号圆点 -->
<style scoped>
.circle-container {
height: 110px;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
width: 90%;
overflow-x: auto;
margin: auto;
}
.circle-wrapper {
position: relative;
display: flex;
align-items: center;
}
.circle {
width: 52px;
height: 52px;
border-radius: 50%;
background: #8db1ff;
font-weight: 500;
font-size: 31px;
color: #ffffff;
display: flex;
justify-content: center;
align-items: center;
position: relative;
z-index: 2;
cursor: pointer;
}
.circle-text {
position: absolute;
top: 60px;
left: -15px;
color: #8db1ff;
width: 82px;
text-align: center;
}
/* 下半圆虚线边框 */
.circle::after {
content: '';
position: absolute;
bottom: -6px; /* 2px边框 + 2px间隙 */
left: -4px;
right: -4px;
height: 30px; /* 半圆高度 */
border-radius: 0 0 60px 60px;
border: 1px dashed #0b58ff;
border-top: none;
z-index: 1;
}
.connector {
width: 160px; /* 计算连接线长度 */
height: 2px;
border-bottom: 1px dashed rgb(11, 88, 255, 1);
margin: 0 5px;
z-index: 1;
}
</style>