| | |
| | | <span v-else style="color: #e6a23c;">未绑定</span> |
| | | </div> |
| | | </div> |
| | | <div class="export-toolbar"> |
| | | <el-date-picker |
| | | v-model="exportDateRange" |
| | | type="daterange" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | range-separator="至" |
| | | start-placeholder="开始日期" |
| | | end-placeholder="结束日期" |
| | | clearable |
| | | style="width: 280px;" |
| | | /> |
| | | <el-button type="success" plain @click="exportSelectedSteps">导出已勾选</el-button> |
| | | </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> |
| | | |
| | |
| | | > |
| | | <div class="process-diagram-node"> |
| | | <el-checkbox |
| | | v-model="step.checked" |
| | | v-model="step.isCompleted" |
| | | class="process-diagram-checkbox" |
| | | @change="() => handleStepCheckedChange(step)" |
| | | @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> |
| | | <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: "" }, |
| | | }); |
| | |
| | | const routeList = ref([]); |
| | | const selectedRouteId = ref(null); |
| | | const steps = ref([]); |
| | | const exportDateRange = ref([]); |
| | | const saving = ref(false); |
| | | |
| | | const normalizeStepsFromApi = (list) => { |
| | | 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: Number(s.isCompleted ?? s.completed ?? 0), |
| | | checked: Boolean(s.checked ?? false), |
| | | 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 handleStepCheckedChange = step => { |
| | | step.checked = Boolean(step.checked); |
| | | const handleStepCompletedChange = (step) => { |
| | | step.isCompleted = Boolean(step.isCompleted); |
| | | }; |
| | | |
| | | const handleClose = () => { |
| | |
| | | } |
| | | 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), |
| | | })), |
| | | }); |
| | | } catch (e) { |
| | | proxy?.$modal?.msgError?.("确认失败,请稍后重试"); |
| | | } finally { |
| | | saving.value = false; |
| | | } |
| | | }; |
| | | |
| | | const exportSelectedSteps = () => { |
| | | const selectedSteps = steps.value.filter(step => step.checked); |
| | | if (selectedSteps.length === 0) { |
| | | proxy?.$modal?.msgWarning?.("请先勾选要导出的工序"); |
| | | return; |
| | | } |
| | | const payload = { |
| | | exportDateRange: Array.isArray(exportDateRange.value) ? exportDateRange.value : [], |
| | | routeId: selectedRouteId.value, |
| | | routeName: routeList.value.find(item => String(item.routeId) === String(selectedRouteId.value))?.processRouteName || "", |
| | | steps: selectedSteps.map(step => ({ |
| | | processId: step.processId, |
| | | processName: step.processName, |
| | | sortNo: step.sortNo, |
| | | isCompleted: Number(step.isCompleted ?? 0), |
| | | })), |
| | | }; |
| | | const blob = new Blob([JSON.stringify(payload, null, 2)], { |
| | | type: "application/json;charset=utf-8", |
| | | }); |
| | | const url = URL.createObjectURL(blob); |
| | | const a = document.createElement("a"); |
| | | const dateText = |
| | | Array.isArray(exportDateRange.value) && exportDateRange.value.length === 2 |
| | | ? `${exportDateRange.value[0]}_${exportDateRange.value[1]}` |
| | | : "all"; |
| | | a.href = url; |
| | | a.download = `工艺路线导出_${dateText}.json`; |
| | | a.click(); |
| | | URL.revokeObjectURL(url); |
| | | }; |
| | | </script> |
| | | |
| | |
| | | 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 { |
| | | font-size: 18px; |
| | | color: #909399; |
| | |
| | | justify-content: space-between; |
| | | align-items: flex-start; |
| | | gap: 16px; |
| | | } |
| | | |
| | | .export-toolbar { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 10px; |
| | | flex-wrap: wrap; |
| | | } |
| | | </style> |