| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283 |
- <template>
- <ele-modal
- v-if="visible"
- custom-class="ele-dialog-form long-dialog-form plan-dot-line-dialog"
- :centered="true"
- :append-to-body="true"
- :visible.sync="dialogVisible"
- :close-on-click-modal="false"
- width="70%"
- :maxable="true"
- :resizable="true"
- :fullscreen.sync="modalFullscreen"
- :title="title"
- @close="onModalClose"
- @opened="onModalOpened"
- >
- <div class="plan-dot-line">
- <div v-if="currentPlan" class="plan-time-bar">
- <span class="plan-time-item">
- <span class="plan-time-label">计划开始时间:</span>
- <span class="plan-time-value">{{ planStartDisplay }}</span>
- </span>
- <span class="plan-time-item">
- <span class="plan-time-label">计划结束时间:</span>
- <span class="plan-time-value">{{ planEndDisplay }}</span>
- </span>
- <span class="plan-time-item">
- <span class="plan-time-label">计划数量:</span>
- <span class="plan-time-value">{{ planNumDisplay }}</span>
- </span>
- </div>
- <div class="top-route">
- <div class="panel-title">工艺路线</div>
- <el-empty
- v-if="taskList.length === 0"
- description="暂无工艺路线"
- ></el-empty>
- <el-steps
- v-else
- :active="routeStepsActive"
- space="20px"
- align-center
- finish-status="success"
- class="route-steps"
- >
- <el-step
- v-for="(item, index) in taskList"
- :key="`route-step-${item._taskKey}`"
- :title="routeStepTitle(item, index)"
- :class="{ active: routeDesIndex === index }"
- v-bind="routeErrorIndex === index ? { status: 'error' } : {}"
- ></el-step>
- </el-steps>
- </div>
- <div class="config-panel">
- <div class="panel-title">工艺配置</div>
- <el-empty
- v-if="taskList.length === 0"
- description="暂无工艺"
- ></el-empty>
- <div v-else class="task-config-table-wrap">
- <el-table
- :key="modalFullscreen ? 'fs' : 'nm'"
- ref="configTable"
- :height="modalFullscreen ? tableMaxHeight : undefined"
- :max-height="modalFullscreen ? undefined : tableMaxHeight"
- :data="taskList"
- border
- size="small"
- row-key="_taskKey"
- class="config-table"
- >
- <el-table-column
- type="index"
- label="序号"
- width="60"
- align="center"
- class-name="config-table-index-col"
- />
- <el-table-column
- label="工序名称"
- min-width="100"
- show-overflow-tooltip
- class-name="task-name-cell"
- align="center"
- >
- <template slot-scope="{ row, $index }">
- <span class="task-name-text">{{
- row.taskName || row.name || `工艺${$index + 1}`
- }}</span>
- </template>
- </el-table-column>
- <el-table-column label="工序并行" min-width="92" align="center">
- <template slot-scope="{ row }">
- <el-select
- v-model="row.isParallelism"
- placeholder="工序并行"
- class="config-table-control"
- @change="handleParallelismChange(row)"
- >
- <el-option label="否" :value="0" />
- <el-option label="是" :value="1" />
- </el-select>
- </template>
- </el-table-column>
- <el-table-column label="执行模式" min-width="100" align="center">
- <template slot-scope="{ row }">
- <el-select
- v-model="row.executionType"
- placeholder="执行模式"
- clearable
- class="config-table-control"
- @change="onExecutionTypeChange(row)"
- >
- <el-option
- v-for="opt in executionTypeOptions"
- :key="opt.value"
- :label="opt.label"
- :value="opt.value"
- />
- </el-select>
- </template>
- </el-table-column>
- <el-table-column label="执行对象" min-width="130" align="center">
- <template slot-scope="{ row }">
- <el-select
- v-if="execTypeNum(row) === EXEC_TYPE.HOMEMADE"
- v-model="row.executionTeamId"
- placeholder="请选择"
- clearable
- filterable
- class="config-table-control"
- @change="onExecutionObjectChange(row)"
- >
- <el-option
- v-for="team in teamOptions"
- :key="team.id"
- :label="team.name"
- :value="team.id"
- />
- </el-select>
- <el-select
- v-else-if="execTypeNum(row) === EXEC_TYPE.ENTRUST"
- v-model="row.executionFactoryId"
- placeholder="请选择"
- clearable
- filterable
- class="config-table-control"
- @change="onExecutionObjectChange(row)"
- >
- <el-option
- v-for="f in factoryList"
- :key="f.value"
- :label="f.label"
- :value="f.value"
- />
- </el-select>
- <el-select
- v-else-if="execTypeNum(row) === EXEC_TYPE.OUTSOURCE"
- :value="''"
- placeholder="委外无需选择"
- disabled
- class="config-table-control"
- />
- <el-select
- v-else
- :value="''"
- placeholder="请先选择执行模式"
- disabled
- class="config-table-control"
- />
- </template>
- </el-table-column>
- <el-table-column
- label="执行开始时间"
- min-width="168"
- align="center"
- >
- <template slot-scope="{ row }">
- <el-date-picker
- v-model="row.executionStartTime"
- type="datetime"
- value-format="yyyy-MM-dd HH:mm:ss"
- placeholder="执行开始时间"
- clearable
- class="config-table-control"
- @change="handleTimeChange(row, 'executionStartTime')"
- />
- </template>
- </el-table-column>
- <el-table-column
- label="执行结束时间"
- min-width="168"
- align="center"
- >
- <template slot-scope="{ row }">
- <div
- :class="{
- 'last-end-over-plan-wrap': isExecutionEndOverPlanEnd(row),
- 'end-over-delivery-wrap': isExecutionEndOverDelivery(row)
- }"
- >
- <el-date-picker
- v-model="row.executionEndTime"
- type="datetime"
- value-format="yyyy-MM-dd HH:mm:ss"
- placeholder="执行结束时间"
- clearable
- class="config-table-control"
- @change="handleTimeChange(row, 'executionEndTime')"
- />
- </div>
- </template>
- </el-table-column>
- </el-table>
- </div>
- </div>
- </div>
- <div slot="footer">
- <el-button size="mini" @click="onModalClose">取消</el-button>
- <el-button size="mini" type="warning" @click="handleClearAll"
- >一键清空</el-button
- >
- <el-button size="mini" type="success" @click="handleCache"
- >缓存</el-button
- >
- <el-button size="mini" type="primary" @click="handleSave">保存</el-button>
- </div>
- </ele-modal>
- </template>
- <script>
- import {
- getPlanDotLine,
- savePlanDotLine
- } from '@/api/productionPlan/planDotLine';
- import { getTaskListById } from '@/api/materialPlan';
- import { teamPage } from '@/api/mainData';
- import { getFactoryarea } from '@/api/saleOrder';
- const EXEC_TYPE = Object.freeze({ HOMEMADE: 0, ENTRUST: 1, OUTSOURCE: 2 });
- const EXEC_TYPE_OPTIONS = Object.freeze([
- { label: '自制', value: EXEC_TYPE.HOMEMADE },
- { label: '请托', value: EXEC_TYPE.ENTRUST },
- { label: '委外', value: EXEC_TYPE.OUTSOURCE }
- ]);
- function toSafeNumber(val) {
- if (val === '' || val == null) return undefined;
- const n = Number(val);
- return Number.isNaN(n) ? undefined : n;
- }
- function formatDateTime(val) {
- if (val == null || val === '') return '';
- const str = String(val).trim();
- if (/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/.test(str)) return str;
- const d = new Date(val);
- if (Number.isNaN(d.getTime())) return '';
- const p = (n) => String(n).padStart(2, '0');
- return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(
- d.getHours()
- )}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
- }
- /** 比较两个时间字符串,结束 < 开始 则返回 true */
- function isEndBeforeStart(startStr, endStr) {
- if (!startStr || !endStr) return false;
- const s = new Date(startStr).getTime();
- const e = new Date(endStr).getTime();
- return !Number.isNaN(s) && !Number.isNaN(e) && e < s;
- }
- /** 工序结束时间是否晚于计划结束时间(用于最后一道工序标红与提醒) */
- function isEndAfterPlanEnd(executionEndStr, planEndStr) {
- if (!executionEndStr || !planEndStr) return false;
- const e = new Date(executionEndStr).getTime();
- const p = new Date(planEndStr).getTime();
- return !Number.isNaN(e) && !Number.isNaN(p) && e > p;
- }
- /** 计划交付时间是日期字段,比较时应以该日期当天结束为上限 */
- function isTimeAfterDeliveryDate(timeStr, deliveryDateStr) {
- if (!timeStr || !deliveryDateStr) return false;
- const time = new Date(timeStr).getTime();
- const deliveryDate = new Date(deliveryDateStr);
- if (Number.isNaN(time) || Number.isNaN(deliveryDate.getTime()))
- return false;
- deliveryDate.setHours(23, 59, 59, 999);
- return time > deliveryDate.getTime();
- }
- export default {
- data() {
- return {
- visible: false,
- dialogVisible: false,
- title: '计划布点',
- taskList: [],
- teamOptions: [],
- currentPlan: null,
- planRoutingPayload: null,
- executionTypeOptions: EXEC_TYPE_OPTIONS,
- /** 与 ele-modal 全屏按钮同步,用于表格高度适配 */
- modalFullscreen: false,
- /** 普通弹窗下动态限制表格最大高度,避免写死 420 */
- tableMaxHeight: 420,
- /** 工艺路线步骤 error 状态(-1 表示无) */
- routeErrorIndex: -1,
- factoryList: [],
- /** 模板中比对执行模式用 */
- EXEC_TYPE
- };
- },
- computed: {
- /** 当前激活步骤:首个未配置执行模式的工序;全部已配置则为步骤总数(全部 finish) */
- routeStepsActive() {
- const list = this.taskList;
- if (!list.length) return 0;
- for (let i = 0; i < list.length; i++) {
- if (!this.isRouteItemDone(list[i])) return i;
- }
- return list.length;
- },
- /** 用于 :class="active" 高亮,与 active 同步(全部完成时高亮最后一环) */
- routeDesIndex() {
- const list = this.taskList;
- if (!list.length) return -1;
- const a = this.routeStepsActive;
- return a >= list.length ? list.length - 1 : a;
- },
- /** 顶部展示:与列表行字段 startTime / endTime 一致(见 productionPlan/index.vue 列配置) */
- planStartDisplay() {
- const t = this.currentPlan?.startTime;
- return t ? formatDateTime(t) || String(t) : '—';
- },
- planEndDisplay() {
- const t = this.currentPlan?.endTime;
- return t ? formatDateTime(t) || String(t) : '—';
- },
- planNumDisplay() {
- const t =
- this.currentPlan?.productNum + this.currentPlan?.measuringUnit;
- return t ? t : '—';
- },
- /** 最后一道工序执行结束时间是否晚于计划结束时间 */
- lastProcessEndAfterPlanEnd() {
- const list = this.taskList;
- const planEnd = this.currentPlan?.endTime;
- if (!list.length || !planEnd) return false;
- const last = list[list.length - 1];
- if (!last?.executionEndTime) return false;
- return isEndAfterPlanEnd(last.executionEndTime, planEnd);
- }
- },
- watch: {
- modalFullscreen() {
- // 先算好高度,key 变化会重建 el-table,重建后再 doLayout
- this.calcTableHeight();
- this.$nextTick(() => {
- this.$refs.configTable?.doLayout?.();
- // 全屏动画结束后再修正一次
- setTimeout(() => {
- this.calcTableHeight();
- this.$nextTick(() => {
- this.$refs.configTable?.doLayout?.();
- });
- }, 400);
- });
- }
- },
- beforeDestroy() {
- window.removeEventListener('resize', this.calcTableHeight);
- },
- methods: {
- onModalOpened() {
- window.addEventListener('resize', this.calcTableHeight);
- this.$nextTick(() => {
- this.calcTableHeight();
- });
- },
- calcTableHeight() {
- if (this.modalFullscreen) {
- const tableEl = this.$refs.configTable?.$el;
- const footerEl = tableEl
- ?.closest('.el-dialog')
- ?.querySelector('.el-dialog__footer');
- const footerH = footerEl ? footerEl.offsetHeight : 52;
- const wrapEl = tableEl?.closest('.task-config-table-wrap');
- if (wrapEl) {
- const top = wrapEl.getBoundingClientRect().top;
- const vh = window.innerHeight;
- this.tableMaxHeight = Math.max(
- 200,
- Math.floor(vh - top - footerH - 16)
- );
- } else {
- const vh = window.innerHeight || 800;
- this.tableMaxHeight = Math.max(200, vh - 260);
- }
- } else {
- const vh = window.innerHeight || 800;
- this.tableMaxHeight = Math.max(320, Math.floor(vh * 0.52));
- }
- this.$nextTick(() => {
- this.$refs.configTable?.doLayout?.();
- });
- },
- async open(data) {
- this.currentPlan = data || null;
- this.planRoutingPayload = null;
- await Promise.all([
- this.loadTeamOptions(),
- this.loadFactoryList(),
- this.loadTaskList()
- ]);
- this.visible = true;
- this.dialogVisible = true;
- },
- async loadFactoryList() {
- const par = { type: 1, size: 9999 };
- try {
- const res = await getFactoryarea(par);
- if (res.list?.length) {
- this.factoryList = res.list.map((el) => ({
- value: el.id,
- label: el.name
- }));
- } else {
- this.factoryList = [];
- }
- } catch {
- this.factoryList = [];
- }
- },
- async loadTaskList() {
- this.taskList = [];
- this.planRoutingPayload = null;
- if (!this.currentPlan?.id) return;
- let planData;
- try {
- planData = await getPlanDotLine({ planId: this.currentPlan.id });
- } catch (e) {
- this.$message.error(e.message || '获取工序布点信息失败');
- return;
- }
- this.planRoutingPayload = planData || {};
- const details = planData?.detailList;
- if (details?.length) {
- this.taskList = [...details]
- .sort((a, b) => (a.taskSort ?? 0) - (b.taskSort ?? 0))
- .map((item, i) => this.normalizeDetailRow(item, i));
- return;
- }
- if (!this.currentPlan.produceRoutingId) {
- this.$message.warning('暂无布点明细且缺少工艺路线,无法加载工序');
- return;
- }
- try {
- const list = await getTaskListById(this.currentPlan.produceRoutingId);
- this.taskList = (list || []).map((item, i) =>
- this.normalizeRoutingTaskRow(item, i)
- );
- } catch (e) {
- this.$message.error(e.message || '获取工序列表失败');
- }
- },
- normalizeDetailRow(item, index) {
- const execType = toSafeNumber(item.executionType) ?? EXEC_TYPE.HOMEMADE;
- const outsource = execType === EXEC_TYPE.OUTSOURCE;
- const entrust = execType === EXEC_TYPE.ENTRUST;
- const homemade = execType === EXEC_TYPE.HOMEMADE;
- return {
- ...item,
- _taskKey: item.id ?? `detail-${item.taskId ?? index}`,
- isParallelism: toSafeNumber(item.isParallelism) === 1 ? 1 : 0,
- executionStartTime: formatDateTime(item.executionStartTime),
- executionEndTime: formatDateTime(item.executionEndTime),
- executionType: execType,
- executionFactoryId: entrust
- ? item.executionFactoryId ?? item.executionTeamId ?? ''
- : '',
- executionFactoryName: entrust
- ? item.executionFactoryName ?? item.executionTeamName ?? ''
- : '',
- executionTeamId: homemade ? item.executionTeamId ?? '' : '',
- executionTeamLeader:
- outsource || entrust ? '' : item.executionTeamLeader ?? '',
- executionTeamLeaderId:
- outsource || entrust ? '' : item.executionTeamLeaderId ?? '',
- executionTeamName:
- outsource || entrust ? '' : item.executionTeamName ?? ''
- };
- },
- normalizeRoutingTaskRow(item, index) {
- const plan = this.currentPlan;
- const taskId = item.id;
- return {
- ...item,
- _taskKey: item.id ?? `new-${taskId ?? index}-${index}`,
- taskId: taskId ?? undefined,
- taskName: item.name ?? item.taskName,
- taskCode: item.code ?? item.taskCode,
- taskSort: item.taskSort ?? index + 1,
- type: item.type ?? undefined,
- sourceTaskId: item.sourceTaskId,
- planId: plan.id,
- planCode: plan.code,
- routingId: plan.produceRoutingId,
- routingName: plan.produceRoutingName ?? plan.routingName,
- isParallelism: 0,
- executionType: EXEC_TYPE.HOMEMADE,
- executionTeamId: '',
- executionTeamLeader: '',
- executionTeamLeaderId: '',
- executionTeamName: '',
- executionStartTime: '',
- executionEndTime: '',
- executionFactoryId: '',
- executionFactoryName: ''
- };
- },
- execTypeNum(row) {
- return toSafeNumber(row.executionType);
- },
- clearExecutionTeamFields(row) {
- this.$set(row, 'executionTeamId', '');
- this.$set(row, 'executionTeamName', '');
- this.$set(row, 'executionTeamLeader', '');
- this.$set(row, 'executionTeamLeaderId', '');
- this.$set(row, 'executionFactoryId', '');
- this.$set(row, 'executionFactoryName', '');
- },
- onExecutionTypeChange(row) {
- this.clearExecutionTeamFields(row);
- },
- onExecutionObjectChange(row) {
- const t = this.execTypeNum(row);
- if (t === EXEC_TYPE.HOMEMADE) {
- const team = this.teamOptions.find(
- (x) => x.id === row.executionTeamId
- );
- row.executionTeamName = team?.name ?? '';
- row.executionTeamLeader = team?.leaderUserName ?? '';
- row.executionTeamLeaderId = team?.leaderUserId ?? '';
- row.executionFactoryId = '';
- row.executionFactoryName = '';
- } else if (t === EXEC_TYPE.ENTRUST) {
- const f = this.factoryList.find(
- (x) => x.value === row.executionFactoryId
- );
- row.executionFactoryName = f?.label ?? '';
- row.executionTeamId = '';
- row.executionTeamName = '';
- row.executionTeamLeader = '';
- row.executionTeamLeaderId = '';
- }
- },
- handleClearAll() {
- this.taskList.forEach((row) => {
- this.$set(row, 'executionType', EXEC_TYPE.HOMEMADE);
- this.$set(row, 'isParallelism', 0);
- this.clearExecutionTeamFields(row);
- this.$set(row, 'executionStartTime', '');
- this.$set(row, 'executionEndTime', '');
- });
- this.$message.success('已清空');
- },
- async loadTeamOptions() {
- try {
- const factoryId = this.$store.state.user.info.factoryId;
- const res = await teamPage({ pageNum: 1, size: -1, factoryId });
- this.teamOptions = (res?.list || []).map(
- ({ id, name, leaderUserName, leaderUserId }) => ({
- id,
- name,
- leaderUserName,
- leaderUserId
- })
- );
- } catch {
- this.teamOptions = [];
- }
- },
- isRouteItemDone(row) {
- return row.executionType != null && row.executionType !== '';
- },
- routeStepTitle(item, index) {
- return (
- item.taskTypeName || item.taskName || item.name || `工艺${index + 1}`
- );
- },
- getTaskLabel(item, index) {
- return (
- item?.taskTypeName ||
- item?.taskName ||
- item?.name ||
- (typeof index === 'number' ? `工艺${index + 1}` : '当前工艺')
- );
- },
- compareTaskTime(start, end) {
- if (!start || !end) return false;
- const startTime = new Date(start).getTime();
- const endTime = new Date(end).getTime();
- return (
- !Number.isNaN(startTime) &&
- !Number.isNaN(endTime) &&
- startTime < endTime
- );
- },
- isTaskParallel(row) {
- return toSafeNumber(row?.isParallelism) === 1;
- },
- getCurrentPlanStartTime() {
- return (
- this.currentPlan?.startTime ||
- this.currentPlan?.planStartTime ||
- this.currentPlan?.executionStartTime ||
- ''
- );
- },
- isExecutionStartBeforePlanStart(executionStartTime) {
- if (!executionStartTime) {
- return false;
- }
- const planStartTime = this.getCurrentPlanStartTime();
- if (!planStartTime) {
- return false;
- }
- const executionStart = new Date(executionStartTime).getTime();
- const planStart = new Date(planStartTime).getTime();
- return (
- !Number.isNaN(executionStart) &&
- !Number.isNaN(planStart) &&
- executionStart < planStart
- );
- },
- validateTaskSequence(row, options = {}) {
- const index = this.taskList.findIndex((item) => item === row);
- if (index < 0) {
- return true;
- }
- const prev = this.taskList[index - 1];
- const next = this.taskList[index + 1];
- const resetKey = options.resetKey;
- const checkPrev =
- (!resetKey || resetKey === 'executionStartTime') &&
- !this.isTaskParallel(row);
- const checkNext =
- (!resetKey || resetKey === 'executionEndTime') &&
- !this.isTaskParallel(next);
- if (
- checkPrev &&
- prev?.executionEndTime &&
- row.executionStartTime &&
- this.compareTaskTime(row.executionStartTime, prev.executionEndTime)
- ) {
- this.routeErrorIndex = index;
- this.$message.warning(
- `${this.getTaskLabel(
- row,
- index
- )}执行开始时间不能小于上一道工序${this.getTaskLabel(
- prev,
- index - 1
- )}的执行结束时间`
- );
- if (resetKey) {
- this.$set(row, resetKey, '');
- }
- return false;
- }
- if (
- checkNext &&
- next?.executionStartTime &&
- row.executionEndTime &&
- this.compareTaskTime(next.executionStartTime, row.executionEndTime)
- ) {
- this.routeErrorIndex = index + 1;
- this.$message.warning(
- `下一道工序${this.getTaskLabel(
- next,
- index + 1
- )}的执行开始时间不能小于上一道工序${this.getTaskLabel(
- row,
- index
- )}的执行结束时间`
- );
- if (resetKey) {
- this.$set(row, resetKey, '');
- }
- return false;
- }
- return true;
- },
- handleTimeChange(row, changeKey) {
- if (isEndBeforeStart(row.executionStartTime, row.executionEndTime)) {
- this.$message.warning('执行结束时间不能小于执行开始时间');
- this.$set(row, changeKey, '');
- return;
- }
- if (
- changeKey === 'executionStartTime' &&
- this.isTaskParallel(row) &&
- this.isExecutionStartBeforePlanStart(row.executionStartTime)
- ) {
- this.$message.warning('执行开始时间不能小于计划开始时间');
- this.$set(row, changeKey, '');
- return;
- }
- if (!this.validateTaskSequence(row, { resetKey: changeKey })) {
- return;
- }
- this.routeErrorIndex = -1;
- const deadline = this.currentPlan?.planDeliveryTime;
- if (
- changeKey === 'executionStartTime' &&
- deadline &&
- row.executionStartTime
- ) {
- if (isTimeAfterDeliveryDate(row.executionStartTime, deadline)) {
- this.$message.warning('时间不能大于计划交付时间');
- this.$set(row, changeKey, '');
- return;
- }
- }
- if (changeKey === 'executionEndTime' && row.executionEndTime) {
- const planEnd = this.currentPlan?.endTime;
- const overDelivery =
- !!deadline &&
- isTimeAfterDeliveryDate(row.executionEndTime, deadline);
- const overPlanEnd =
- !!planEnd && isEndAfterPlanEnd(row.executionEndTime, planEnd);
- if (overDelivery && overPlanEnd) {
- this.$message.warning(
- '执行结束时间晚于计划交付时间及计划结束时间,已保留填写内容,请留意核对'
- );
- } else if (overDelivery) {
- this.$message.warning(
- '执行结束时间晚于计划交付时间,已保留填写内容,请留意核对'
- );
- } else if (overPlanEnd) {
- this.$message.warning(
- '执行结束时间晚于计划结束时间,已保留填写内容,请留意核对'
- );
- }
- }
- },
- handleParallelismChange(row) {
- if (!row?.executionStartTime) {
- return;
- }
- if (this.isTaskParallel(row)) {
- if (this.isExecutionStartBeforePlanStart(row.executionStartTime)) {
- this.$message.warning('执行开始时间不能小于计划开始时间');
- this.$set(row, 'executionStartTime', '');
- }
- return;
- }
- this.validateTaskSequence(row, { resetKey: 'executionStartTime' });
- },
- /** 执行结束时间晚于计划交付时间(选择框标红,可保存) */
- isExecutionEndOverDelivery(row) {
- const deadline = this.currentPlan?.planDeliveryTime;
- return (
- !!deadline &&
- !!row?.executionEndTime &&
- isTimeAfterDeliveryDate(row.executionEndTime, deadline)
- );
- },
- /** 执行结束时间晚于计划结束时间(任意工序行标红,可保存) */
- isExecutionEndOverPlanEnd(row) {
- const planEnd = this.currentPlan?.endTime;
- return (
- !!planEnd &&
- !!row?.executionEndTime &&
- isEndAfterPlanEnd(row.executionEndTime, planEnd)
- );
- },
- validateTaskList() {
- this.routeErrorIndex = -1;
- const label = (t) => this.getTaskLabel(t);
- for (const item of this.taskList) {
- const execType = toSafeNumber(item.executionType);
- if (execType === undefined) {
- this.$message.warning(`${label(item)}请选择执行模式`);
- return false;
- }
- if (execType === EXEC_TYPE.HOMEMADE) {
- if (
- !item.executionTeamId ||
- !item.executionStartTime ||
- !item.executionEndTime
- ) {
- this.$message.warning(
- `${label(
- item
- )}执行模式为自制时,需选择执行班组并填写执行开始和结束时间`
- );
- return false;
- }
- }
- if (execType === EXEC_TYPE.ENTRUST) {
- if (
- !item.executionFactoryId ||
- !item.executionStartTime ||
- !item.executionEndTime
- ) {
- this.$message.warning(
- `${label(
- item
- )}执行模式为请托时,需选择工厂并填写执行开始和结束时间`
- );
- return false;
- }
- }
- if (execType === EXEC_TYPE.OUTSOURCE) {
- if (!item.executionStartTime || !item.executionEndTime) {
- this.$message.warning(
- `${label(item)}执行模式为委外时,需填写执行开始和结束时间`
- );
- return false;
- }
- }
- if (
- isEndBeforeStart(item.executionStartTime, item.executionEndTime)
- ) {
- this.$message.warning(
- `${label(item)}执行结束时间不能小于执行开始时间`
- );
- return false;
- }
- if (
- this.isTaskParallel(item) &&
- this.isExecutionStartBeforePlanStart(item.executionStartTime)
- ) {
- this.$message.warning(
- `${label(item)}执行开始时间不能小于计划开始时间`
- );
- return false;
- }
- const deadline = this.currentPlan?.planDeliveryTime;
- if (deadline) {
- const checkStart = (val, name) => {
- if (!val) return true;
- if (!isTimeAfterDeliveryDate(val, deadline)) return true;
- this.$message.warning(
- `${label(item)}${name}不能大于计划交付时间`
- );
- return false;
- };
- if (!checkStart(item.executionStartTime, '执行开始时间'))
- return false;
- }
- }
- for (const item of this.taskList) {
- if (!this.validateTaskSequence(item)) {
- return false;
- }
- }
- return true;
- },
- /** @param {0|1} isCache 缓存传 1,保存传 0 */
- buildSavePayload(isCache = 0) {
- const plan = this.currentPlan;
- const head = this.planRoutingPayload || {};
- const detailList = this.taskList.map((row) => {
- const execType = toSafeNumber(row.executionType) ?? 0;
- const detail = {
- executionEndTime: row.executionEndTime || undefined,
- executionStartTime: row.executionStartTime || undefined,
- executionTeamId:
- execType === EXEC_TYPE.HOMEMADE
- ? row.executionTeamId || undefined
- : undefined,
- executionTeamLeader: row.executionTeamLeader || '',
- executionTeamLeaderId: row.executionTeamLeaderId || '',
- executionTeamName: row.executionTeamName || '',
- executionType: execType,
- isParallelism: this.isTaskParallel(row) ? 1 : 0,
- planCode: row.planCode || plan?.code,
- planId: row.planId ?? plan?.id,
- routingId:
- row.routingId ?? plan?.produceRoutingId ?? head.routingId,
- routingName:
- row.routingName || plan?.produceRoutingName || head.routingName,
- sourceTaskId: row.sourceTaskId ?? row.taskId,
- taskCode: row.taskCode,
- taskId: row.taskId,
- taskName: row.taskName,
- taskSort: row.taskSort,
- type: toSafeNumber(row.type)
- };
- if (execType === EXEC_TYPE.ENTRUST) {
- detail.executionFactoryId = row.executionFactoryId || undefined;
- detail.executionFactoryName = row.executionFactoryName || '';
- }
- if (row.id != null) detail.id = row.id;
- return detail;
- });
- return {
- detailList,
- fileParam: head.fileParam ?? [],
- id: head.id,
- isCache,
- planCode: plan?.code ?? head.planCode,
- planId: plan?.id ?? head.planId,
- produceVersionId: head.produceVersionId,
- produceVersionName: head.produceVersionName,
- releaseTime: head.releaseTime,
- routingCode: head.routingCode,
- routingId: head.routingId ?? plan?.produceRoutingId,
- routingName: head.routingName ?? plan?.produceRoutingName,
- routingVersion: head.routingVersion
- };
- },
- async handleCache() {
- if (!this.validateTaskList()) return;
- const payload = this.buildSavePayload(1);
- const loading = this.$loading({ lock: true, text: '缓存中...' });
- try {
- await savePlanDotLine(payload);
- this.$emit('save', {
- plan: this.currentPlan,
- taskList: this.taskList,
- payload
- });
- this.$message.success('缓存成功');
- this.onModalClose();
- } catch (e) {
- this.$message.error(e.message || '缓存失败');
- } finally {
- loading.close();
- }
- },
- async handleSave() {
- if (!this.validateTaskList()) return;
- const payload = this.buildSavePayload(0);
- const loading = this.$loading({ lock: true, text: '保存中...' });
- try {
- await savePlanDotLine(payload);
- this.$emit('save', {
- plan: this.currentPlan,
- taskList: this.taskList,
- payload
- });
- this.$message.success('保存成功');
- this.onModalClose();
- } catch (e) {
- this.$message.error(e.message || '保存失败');
- } finally {
- loading.close();
- }
- },
- onModalClose() {
- window.removeEventListener('resize', this.calcTableHeight);
- this.modalFullscreen = false;
- this.visible = false;
- this.dialogVisible = false;
- this.taskList = [];
- this.currentPlan = null;
- this.planRoutingPayload = null;
- this.routeErrorIndex = -1;
- this.$emit('success');
- }
- }
- };
- </script>
- <style lang="scss" scoped>
- .plan-dot-line,
- .top-route,
- .config-panel {
- width: 100%;
- max-width: 100%;
- min-width: 0;
- box-sizing: border-box;
- }
- .plan-dot-line {
- min-height: 360px;
- }
- .plan-time-bar {
- display: flex;
- flex-wrap: wrap;
- align-items: center;
- gap: 12px 28px;
- margin-bottom: 12px;
- padding: 10px 12px;
- border: 1px solid #ebeef5;
- border-radius: 4px;
- background: #fafafa;
- font-size: 14px;
- }
- .plan-time-item {
- display: inline-flex;
- align-items: baseline;
- gap: 4px;
- }
- .plan-time-label {
- color: #606266;
- white-space: nowrap;
- }
- .plan-time-value {
- color: #303133;
- font-weight: 500;
- }
- .last-end-over-plan-wrap,
- .end-over-delivery-wrap {
- width: 100%;
- }
- .last-end-over-plan-wrap ::v-deep .el-input__inner,
- .end-over-delivery-wrap ::v-deep .el-input__inner {
- color: #f56c6c;
- border-color: #f56c6c;
- }
- .last-end-over-plan-wrap ::v-deep .el-input__inner:focus,
- .end-over-delivery-wrap ::v-deep .el-input__inner:focus {
- border-color: #f56c6c;
- }
- .top-route,
- .config-panel {
- border: 1px solid #ebeef5;
- border-radius: 4px;
- padding: 12px;
- background: #fff;
- }
- /* 工艺路线区域随内容增高,不要出现纵向滚动条 */
- .top-route {
- overflow: visible;
- }
- .config-panel {
- margin-top: 12px;
- }
- .panel-title {
- font-size: 14px;
- font-weight: 600;
- margin-bottom: 10px;
- }
- .route-steps {
- width: 100%;
- overflow-x: auto;
- overflow-y: hidden;
- padding-bottom: 8px;
- box-sizing: content-box;
- }
- .route-steps ::v-deep {
- .el-steps {
- display: flex;
- flex-wrap: nowrap;
- align-items: flex-start;
- }
- .el-step {
- flex-shrink: 0;
- }
- .el-step__title {
- font-size: clamp(16px, 2.2vw, 16px);
- line-height: 1.35;
- max-width: 8em;
- margin-left: auto;
- margin-right: auto;
- white-space: normal;
- word-break: break-all;
- }
- .el-step__title:not(.is-error) {
- color: #157a2c;
- }
- .el-step.active .el-step__title:not(.is-error) {
- font-weight: 600;
- }
- .el-step__line {
- top: 14px;
- }
- /* 已完成步骤 success 色(默认绿)改为品牌绿 */
- .el-step__head.is-success {
- color: #157a2c;
- border-color: #157a2c;
- }
- .el-step__head.is-success .el-step__line {
- background-color: #157a2c;
- }
- .el-step__head.is-success .el-step__icon {
- color: #157a2c;
- border-color: #157a2c;
- }
- .el-step__head.is-success .el-step__line-inner {
- background-color: #157a2c;
- border-color: #157a2c;
- }
- /* 当前步骤 process:ele-admin 对 .is-text 使用主题色实心圆,需覆盖 background */
- .el-step__head.is-process {
- color: #157a2c;
- border-color: #157a2c;
- }
- .el-step__head.is-process .el-step__icon {
- color: #157a2c;
- border-color: #157a2c;
- }
- .el-step__head.is-process .el-step__icon.is-text {
- color: #fff;
- border-color: #157a2c;
- background: #157a2c;
- }
- .el-step__head.is-process .el-step__icon.is-text .el-step__icon-inner {
- color: #fff;
- }
- .el-step__description.is-process {
- color: #157a2c;
- }
- .el-step__icon {
- width: 30px;
- height: 30px;
- }
- .el-step__main {
- padding-top: 8px;
- margin-top: 0;
- }
- .el-step__description {
- margin-top: 0;
- padding-right: 0;
- }
- }
- @media (max-width: 576px) {
- .top-route,
- .config-panel {
- padding: 8px;
- }
- }
- .task-config-table-wrap {
- width: 100%;
- }
- .config-table ::v-deep {
- /* 序号列:column 上的 style 无效,需 class-name + td 内 .cell */
- td.config-table-index-col .cell {
- font-size: 14px !important;
- }
- .el-table td > .cell {
- font-size: 14px;
- text-align: center;
- }
- .el-table__body .cell {
- padding-left: 6px;
- padding-right: 6px;
- }
- td.task-name-cell .task-name-text {
- font-size: 14px;
- line-height: 1.4;
- }
- /* 与工序名称一致:覆盖 el-select / date-editor 的 small 字号 */
- .config-table-control .el-input__inner {
- font-size: 14px;
- }
- .config-table-control {
- width: 100% !important;
- }
- .config-table-control.el-date-editor {
- max-width: 100%;
- }
- }
- </style>
- <!-- 弹窗 append-to-body 后表头不在带 scoped 的节点链上,需用弹窗 class 单独写表头 -->
- <style lang="scss">
- .plan-dot-line-dialog {
- .task-config-table-wrap
- .el-table__header-wrapper
- th.el-table__cell
- > .cell {
- font-size: 15px !important;
- line-height: 1.4 !important;
- }
- .top-route {
- overflow: visible !important;
- max-height: none !important;
- }
- .route-steps .el-step__head.is-success {
- color: #157a2c;
- border-color: #157a2c;
- }
- .route-steps .el-step__head.is-success .el-step__line {
- background-color: #157a2c;
- }
- .route-steps .el-step__head.is-success .el-step__icon {
- color: #157a2c;
- border-color: #157a2c;
- }
- .route-steps .el-step__head.is-success .el-step__line-inner {
- background-color: #157a2c;
- border-color: #157a2c;
- }
- .route-steps .el-step__head.is-process {
- color: #157a2c;
- border-color: #157a2c;
- }
- .route-steps .el-step__head.is-process .el-step__icon {
- color: #157a2c;
- border-color: #157a2c;
- }
- .route-steps .el-step__head.is-process .el-step__icon.is-text {
- color: #fff;
- border-color: #157a2c;
- background: #157a2c;
- }
- .route-steps
- .el-step__head.is-process
- .el-step__icon.is-text
- .el-step__icon-inner {
- color: #fff;
- }
- .route-steps .el-step__title:not(.is-error) {
- color: #157a2c;
- }
- .route-steps .el-step__description.is-process {
- color: #157a2c;
- }
- }
- </style>
|