| | |
| | | > |
| | | <el-row :gutter="20"> |
| | | <el-col :span="24"> |
| | | <div style="font-weight: 600; margin-bottom: 8px;">配置</div> |
| | | <div style="font-size: 12px; margin-bottom: 8px;"> |
| | | <span v-if="boundRouteName" style="color: #67c23a;">已绑定:{{ boundRouteName }}</span> |
| | | <span v-else style="color: #e6a23c;">未绑定</span> |
| | | <div class="dialog-topbar"> |
| | | <div> |
| | | <div style="font-weight: 600; margin-bottom: 8px;">配置</div> |
| | | <div style="font-size: 12px; margin-bottom: 8px;"> |
| | | <span v-if="boundRouteName" style="color: #67c23a;">已绑定:{{ boundRouteName }}</span> |
| | | <span v-else style="color: #e6a23c;">未绑定</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <el-select |
| | | v-model="selectedRouteId" |
| | |
| | | |
| | | <div style="font-weight: 600; margin-bottom: 8px;">步骤预览</div> |
| | | <div style="font-size: 12px; color: #909399; margin-bottom: 10px;"> |
| | | 根据所选配置展示流程图 |
| | | 根据所选配置展示流程图,勾选表示该工序已完成 |
| | | </div> |
| | | </el-col> |
| | | |
| | |
| | | class="process-diagram-segment" |
| | | > |
| | | <div class="process-diagram-node"> |
| | | <el-checkbox |
| | | v-model="step.isCompleted" |
| | | class="process-diagram-checkbox" |
| | | @change="() => handleStepCompletedChange(step)" |
| | | /> |
| | | <div class="process-diagram-index">{{ idx + 1 }}</div> |
| | | <div class="process-diagram-name">{{ step.processName }}</div> |
| | | <div class="process-diagram-status" :class="{ 'is-done': Number(step.isCompleted) === 1 }"> |
| | | {{ Number(step.isCompleted) === 1 ? "已完成" : "未完成" }} |
| | | </div> |
| | | <div v-if="step.isCompleted" style="margin-top: 8px;"> |
| | | <el-date-picker |
| | | v-model="step.completedTime" |
| | | type="datetime" |
| | | placeholder="选择时间" |
| | | format="YYYY-MM-DD HH:mm:ss" |
| | | value-format="YYYY-MM-DD HH:mm:ss" |
| | | size="small" |
| | | style="width: 100%;" |
| | | /> |
| | | </div> |
| | | </div> |
| | | <div v-if="idx < steps.length - 1" class="process-diagram-arrow">→</div> |
| | | </div> |
| | |
| | | visible: { type: Boolean, default: false }, |
| | | // 打开弹窗时的回显:若业务已绑定工艺路线则传入该 routeId;否则默认展示列表第一条 |
| | | defaultRouteId: { type: [Number, String, null], default: null }, |
| | | // 打开弹窗时的工序完成记录回显 |
| | | defaultRecordList: { type: Array, default: () => [] }, |
| | | // 页面提示:订单已绑定的工艺路线名称 |
| | | boundRouteName: { type: String, default: "" }, |
| | | }); |
| | |
| | | if (!Array.isArray(list)) return []; |
| | | return list.map((s, idx) => ({ |
| | | stepId: s.stepId ?? s.id ?? null, |
| | | processRouteItemId: s.processRouteItemId ?? s.process_route_item_id ?? s.id ?? null, |
| | | processId: s.processId ?? s.process_id ?? s.id ?? null, |
| | | processName: s.processName ?? s.process_name ?? s.name ?? "", |
| | | sortNo: s.sortNo ?? idx + 1, |
| | | isCompleted: Boolean(Number(s.isCompleted ?? s.completed ?? 0)), |
| | | })); |
| | | }; |
| | | |
| | |
| | | processRouteName: r.processRouteName ?? r.routeName ?? r.name ?? "", |
| | | isDefault: Boolean(r.isDefault), |
| | | })); |
| | | }; |
| | | |
| | | const applyRecordListToSteps = (stepList, recordList) => { |
| | | if (!Array.isArray(stepList) || stepList.length === 0) return stepList; |
| | | if (!Array.isArray(recordList) || recordList.length === 0) return stepList; |
| | | |
| | | const recordMap = new Map( |
| | | recordList |
| | | .filter((item) => item && item.processRouteItemId !== null && item.processRouteItemId !== undefined) |
| | | .map((item) => [String(item.processRouteItemId), item]) |
| | | ); |
| | | |
| | | return stepList.map((step) => { |
| | | const matched = recordMap.get(String(step.processRouteItemId)); |
| | | if (!matched) return step; |
| | | return { |
| | | ...step, |
| | | isCompleted: Boolean(Number(matched.isCompleted ?? 0)), |
| | | completedTime: matched.completedTime ?? matched.completed_time ?? null, |
| | | }; |
| | | }); |
| | | }; |
| | | |
| | | const fetchRouteList = async () => { |
| | |
| | | } |
| | | const res = await salesProcessFlowConfigItemList(routeId); |
| | | const raw = res?.data ?? res ?? []; |
| | | steps.value = normalizeStepsFromApi(raw); |
| | | const normalizedSteps = normalizeStepsFromApi(raw); |
| | | if (String(routeId) === String(props.defaultRouteId)) { |
| | | steps.value = applyRecordListToSteps(normalizedSteps, props.defaultRecordList); |
| | | return; |
| | | } |
| | | steps.value = normalizedSteps; |
| | | }; |
| | | |
| | | watch( |
| | |
| | | await fetchRouteSteps(selectedRouteId.value); |
| | | }; |
| | | |
| | | const formatDateTime = (date) => { |
| | | const pad = (n) => (n < 10 ? '0' + n : n); |
| | | return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`; |
| | | }; |
| | | |
| | | const handleStepCompletedChange = (step) => { |
| | | step.isCompleted = Boolean(step.isCompleted); |
| | | if (step.isCompleted && !step.completedTime) { |
| | | step.completedTime = proxy?.parseTime ? proxy.parseTime(new Date()) : formatDateTime(new Date()); |
| | | } |
| | | }; |
| | | |
| | | const handleClose = () => { |
| | | emit("update:visible", false); |
| | | saving.value = false; |
| | |
| | | } |
| | | saving.value = true; |
| | | try { |
| | | emit("confirm", selectedRouteId.value); |
| | | emit("confirm", { |
| | | routeId: selectedRouteId.value, |
| | | recordList: steps.value.map((step) => ({ |
| | | processRouteItemId: step.processRouteItemId, |
| | | isCompleted: Number(step.isCompleted ?? 0), |
| | | completedTime: step.completedTime || (step.isCompleted ? (proxy?.parseTime ? proxy.parseTime(new Date()) : formatDateTime(new Date())) : null) |
| | | })), |
| | | }); |
| | | } catch (e) { |
| | | proxy?.$modal?.msgError?.("确认失败,请稍后重试"); |
| | | } finally { |
| | |
| | | } |
| | | |
| | | .process-diagram-node { |
| | | width: 160px; |
| | | min-width: 160px; |
| | | height: 78px; |
| | | width: 180px; |
| | | min-width: 180px; |
| | | min-height: 78px; |
| | | border: 1px solid #ebeef5; |
| | | border-radius: 10px; |
| | | background: #fff; |
| | |
| | | padding: 10px 12px; |
| | | margin-right: 10px; |
| | | box-sizing: border-box; |
| | | position: relative; |
| | | } |
| | | |
| | | .process-diagram-checkbox { |
| | | position: absolute; |
| | | top: 8px; |
| | | right: 8px; |
| | | } |
| | | |
| | | .process-diagram-index { |
| | |
| | | white-space: nowrap; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | } |
| | | |
| | | .process-diagram-status { |
| | | margin-top: 4px; |
| | | font-size: 12px; |
| | | color: #909399; |
| | | } |
| | | |
| | | .process-diagram-status.is-done { |
| | | color: #67c23a; |
| | | font-weight: 600; |
| | | } |
| | | |
| | | .process-diagram-arrow { |
| | |
| | | justify-content: flex-end; |
| | | gap: 10px; |
| | | } |
| | | </style> |
| | | |
| | | .dialog-topbar { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: flex-start; |
| | | gap: 16px; |
| | | } |
| | | </style> |