|
|
@@ -1,7 +1,7 @@
|
|
|
<template>
|
|
|
<ele-modal
|
|
|
v-if="visible"
|
|
|
- custom-class="ele-dialog-form long-dialog-form"
|
|
|
+ custom-class="ele-dialog-form long-dialog-form plan-dot-line-dialog"
|
|
|
:centered="true"
|
|
|
:append-to-body="true"
|
|
|
:visible.sync="dialogVisible"
|
|
|
@@ -9,8 +9,10 @@
|
|
|
width="70%"
|
|
|
:maxable="true"
|
|
|
:resizable="true"
|
|
|
+ :fullscreen.sync="modalFullscreen"
|
|
|
:title="title"
|
|
|
@close="onModalClose"
|
|
|
+ @opened="onModalOpened"
|
|
|
>
|
|
|
<div class="plan-dot-line">
|
|
|
<div class="top-route">
|
|
|
@@ -19,26 +21,22 @@
|
|
|
v-if="taskList.length === 0"
|
|
|
description="暂无工艺路线"
|
|
|
></el-empty>
|
|
|
- <div v-else class="route-line">
|
|
|
- <div
|
|
|
+ <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-seg-${item._taskKey}`"
|
|
|
- class="route-segment"
|
|
|
- >
|
|
|
- <span
|
|
|
- class="route-node"
|
|
|
- :class="{ 'route-node--done': isRouteItemDone(item) }"
|
|
|
- >
|
|
|
- {{ item.taskName || item.name || `工艺${index + 1}` }}
|
|
|
- </span>
|
|
|
- <span
|
|
|
- v-if="index < taskList.length - 1"
|
|
|
- class="route-arrow"
|
|
|
- aria-hidden="true"
|
|
|
- >→</span
|
|
|
- >
|
|
|
- </div>
|
|
|
- </div>
|
|
|
+ :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>
|
|
|
@@ -48,17 +46,20 @@
|
|
|
></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"
|
|
|
- max-height="420"
|
|
|
class="config-table"
|
|
|
>
|
|
|
<el-table-column
|
|
|
type="index"
|
|
|
label="序号"
|
|
|
- width="48"
|
|
|
+ width="60"
|
|
|
align="center"
|
|
|
/>
|
|
|
<el-table-column
|
|
|
@@ -81,6 +82,7 @@
|
|
|
placeholder="执行模式"
|
|
|
clearable
|
|
|
class="config-table-control"
|
|
|
+ @change="onExecutionTypeChange(row)"
|
|
|
>
|
|
|
<el-option
|
|
|
v-for="opt in executionTypeOptions"
|
|
|
@@ -98,6 +100,7 @@
|
|
|
placeholder="请选择班组"
|
|
|
clearable
|
|
|
filterable
|
|
|
+ :disabled="isTeamSelectDisabled(row)"
|
|
|
class="config-table-control"
|
|
|
@change="onExecutionTeamChange(row)"
|
|
|
>
|
|
|
@@ -148,6 +151,12 @@
|
|
|
</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>
|
|
|
@@ -205,10 +214,86 @@
|
|
|
teamOptions: [],
|
|
|
currentPlan: null,
|
|
|
planRoutingPayload: null,
|
|
|
- executionTypeOptions: EXEC_TYPE_OPTIONS
|
|
|
+ executionTypeOptions: EXEC_TYPE_OPTIONS,
|
|
|
+ /** 与 ele-modal 全屏按钮同步,用于表格高度适配 */
|
|
|
+ modalFullscreen: false,
|
|
|
+ /** 普通弹窗下动态限制表格最大高度,避免写死 420 */
|
|
|
+ tableMaxHeight: 420,
|
|
|
+ /** 工艺路线步骤 error 状态(-1 表示无) */
|
|
|
+ routeErrorIndex: -1
|
|
|
};
|
|
|
},
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ 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;
|
|
|
@@ -255,16 +340,19 @@
|
|
|
},
|
|
|
|
|
|
normalizeDetailRow(item, index) {
|
|
|
+ const execType = toSafeNumber(item.executionType) ?? EXEC_TYPE.HOMEMADE;
|
|
|
+ const noTeam =
|
|
|
+ execType === EXEC_TYPE.ENTRUST || execType === EXEC_TYPE.OUTSOURCE;
|
|
|
return {
|
|
|
...item,
|
|
|
_taskKey: item.id ?? `detail-${item.taskId ?? index}`,
|
|
|
executionStartTime: formatDateTime(item.executionStartTime),
|
|
|
executionEndTime: formatDateTime(item.executionEndTime),
|
|
|
- executionType: toSafeNumber(item.executionType) ?? EXEC_TYPE.HOMEMADE,
|
|
|
- executionTeamId: item.executionTeamId ?? '',
|
|
|
- executionTeamLeader: item.executionTeamLeader ?? '',
|
|
|
- executionTeamLeaderId: item.executionTeamLeaderId ?? '',
|
|
|
- executionTeamName: item.executionTeamName ?? ''
|
|
|
+ executionType: execType,
|
|
|
+ executionTeamId: noTeam ? '' : item.executionTeamId ?? '',
|
|
|
+ executionTeamLeader: noTeam ? '' : item.executionTeamLeader ?? '',
|
|
|
+ executionTeamLeaderId: noTeam ? '' : item.executionTeamLeaderId ?? '',
|
|
|
+ executionTeamName: noTeam ? '' : item.executionTeamName ?? ''
|
|
|
};
|
|
|
},
|
|
|
|
|
|
@@ -295,6 +383,24 @@
|
|
|
};
|
|
|
},
|
|
|
|
|
|
+ isTeamSelectDisabled(row) {
|
|
|
+ const t = Number(row.executionType);
|
|
|
+ return t === EXEC_TYPE.ENTRUST || t === EXEC_TYPE.OUTSOURCE;
|
|
|
+ },
|
|
|
+
|
|
|
+ clearExecutionTeamFields(row) {
|
|
|
+ this.$set(row, 'executionTeamId', '');
|
|
|
+ this.$set(row, 'executionTeamName', '');
|
|
|
+ this.$set(row, 'executionTeamLeader', '');
|
|
|
+ this.$set(row, 'executionTeamLeaderId', '');
|
|
|
+ },
|
|
|
+
|
|
|
+ onExecutionTypeChange(row) {
|
|
|
+ if (this.isTeamSelectDisabled(row)) {
|
|
|
+ this.clearExecutionTeamFields(row);
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
onExecutionTeamChange(row) {
|
|
|
const team = this.teamOptions.find((t) => t.id === row.executionTeamId);
|
|
|
row.executionTeamName = team?.name ?? '';
|
|
|
@@ -302,6 +408,16 @@
|
|
|
row.executionTeamLeaderId = team?.leaderUserId ?? '';
|
|
|
},
|
|
|
|
|
|
+ handleClearAll() {
|
|
|
+ this.taskList.forEach((row) => {
|
|
|
+ this.$set(row, 'executionType', EXEC_TYPE.HOMEMADE);
|
|
|
+ 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;
|
|
|
@@ -323,6 +439,12 @@
|
|
|
return row.executionType != null && row.executionType !== '';
|
|
|
},
|
|
|
|
|
|
+ routeStepTitle(item, index) {
|
|
|
+ return (
|
|
|
+ item.taskTypeName || item.taskName || item.name || `工艺${index + 1}`
|
|
|
+ );
|
|
|
+ },
|
|
|
+
|
|
|
handleTimeChange(row, changeKey) {
|
|
|
if (isEndBeforeStart(row.executionStartTime, row.executionEndTime)) {
|
|
|
this.$message.warning('执行结束时间不能小于执行开始时间');
|
|
|
@@ -394,7 +516,8 @@
|
|
|
return true;
|
|
|
},
|
|
|
|
|
|
- buildSavePayload() {
|
|
|
+ /** @param {0|1} isCache 缓存传 1,保存传 0 */
|
|
|
+ buildSavePayload(isCache = 0) {
|
|
|
const plan = this.currentPlan;
|
|
|
const head = this.planRoutingPayload || {};
|
|
|
|
|
|
@@ -428,6 +551,7 @@
|
|
|
detailList,
|
|
|
fileParam: head.fileParam ?? [],
|
|
|
id: head.id,
|
|
|
+ isCache,
|
|
|
planCode: plan?.code ?? head.planCode,
|
|
|
planId: plan?.id ?? head.planId,
|
|
|
produceVersionId: head.produceVersionId,
|
|
|
@@ -440,10 +564,29 @@
|
|
|
};
|
|
|
},
|
|
|
|
|
|
+ async handleCache() {
|
|
|
+ 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();
|
|
|
+ const payload = this.buildSavePayload(0);
|
|
|
const loading = this.$loading({ lock: true, text: '保存中...' });
|
|
|
try {
|
|
|
await savePlanDotLine(payload);
|
|
|
@@ -462,11 +605,14 @@
|
|
|
},
|
|
|
|
|
|
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');
|
|
|
}
|
|
|
}
|
|
|
@@ -476,8 +622,7 @@
|
|
|
<style lang="scss" scoped>
|
|
|
.plan-dot-line,
|
|
|
.top-route,
|
|
|
- .config-panel,
|
|
|
- .route-line {
|
|
|
+ .config-panel {
|
|
|
width: 100%;
|
|
|
max-width: 100%;
|
|
|
min-width: 0;
|
|
|
@@ -496,6 +641,11 @@
|
|
|
background: #fff;
|
|
|
}
|
|
|
|
|
|
+ /* 工艺路线区域随内容增高,不要出现纵向滚动条 */
|
|
|
+ .top-route {
|
|
|
+ overflow: visible;
|
|
|
+ }
|
|
|
+
|
|
|
.config-panel {
|
|
|
margin-top: 12px;
|
|
|
}
|
|
|
@@ -506,53 +656,51 @@
|
|
|
margin-bottom: 10px;
|
|
|
}
|
|
|
|
|
|
- .route-line {
|
|
|
- display: flex;
|
|
|
- flex-wrap: nowrap;
|
|
|
- align-items: stretch;
|
|
|
- color: #303133;
|
|
|
- }
|
|
|
-
|
|
|
- .route-segment {
|
|
|
- flex: 1 1 0;
|
|
|
- min-width: 0;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- }
|
|
|
-
|
|
|
- .route-node {
|
|
|
- flex: 0 1 auto;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- min-width: 0;
|
|
|
- min-height: 22px;
|
|
|
- border-radius: 4px;
|
|
|
- padding: 2px 10px;
|
|
|
- color: #fff;
|
|
|
- background: #909399;
|
|
|
- font-size: clamp(10px, 2.6vw, 12px);
|
|
|
- font-weight: 600;
|
|
|
- line-height: 1.2;
|
|
|
- overflow: hidden;
|
|
|
- text-overflow: ellipsis;
|
|
|
- white-space: nowrap;
|
|
|
- text-align: center;
|
|
|
- }
|
|
|
-
|
|
|
- .route-node--done {
|
|
|
- background: #56bf1d;
|
|
|
+ .route-steps {
|
|
|
+ width: 100%;
|
|
|
+ overflow-x: auto;
|
|
|
+ overflow-y: hidden;
|
|
|
+ padding-bottom: 8px;
|
|
|
+ box-sizing: content-box;
|
|
|
}
|
|
|
|
|
|
- .route-arrow {
|
|
|
- flex: 1 1 0;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- color: #909399;
|
|
|
- font-size: clamp(14px, 2.5vw, 18px);
|
|
|
- line-height: 1;
|
|
|
+ .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.active .el-step__title {
|
|
|
+ color: #409eff;
|
|
|
+ font-weight: 600;
|
|
|
+ }
|
|
|
+ .el-step__line {
|
|
|
+ top: 14px;
|
|
|
+ }
|
|
|
+ .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) {
|
|
|
@@ -560,12 +708,6 @@
|
|
|
.config-panel {
|
|
|
padding: 8px;
|
|
|
}
|
|
|
- .route-node {
|
|
|
- padding: 2px 4px;
|
|
|
- }
|
|
|
- .route-arrow {
|
|
|
- font-size: 12px;
|
|
|
- }
|
|
|
}
|
|
|
|
|
|
.task-config-table-wrap {
|
|
|
@@ -573,7 +715,6 @@
|
|
|
}
|
|
|
|
|
|
.config-table ::v-deep {
|
|
|
- .el-table th > .cell,
|
|
|
.el-table td > .cell {
|
|
|
font-size: 14px;
|
|
|
text-align: center;
|
|
|
@@ -595,3 +736,22 @@
|
|
|
}
|
|
|
}
|
|
|
</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;
|
|
|
+ font-weight: 600 !important;
|
|
|
+ line-height: 1.4 !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ .top-route {
|
|
|
+ overflow: visible !important;
|
|
|
+ max-height: none !important;
|
|
|
+ }
|
|
|
+ }
|
|
|
+</style>
|