1356 lines
34 KiB
Vue
1356 lines
34 KiB
Vue
<!--
|
||
* @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>
|