From 8face2a434955f308ea8bb837022bbd47557ade7 Mon Sep 17 00:00:00 2001
From: spring <2396852758@qq.com>
Date: 星期二, 17 三月 2026 11:14:42 +0800
Subject: [PATCH] fix: 添加生产详情前端页面
---
src/views/productionManagement/productionOrder/index.vue | 31 ++
src/views/productionManagement/productionOrder/Detail/index.vue | 499 +++++++++++++++++++++++++++++++++++++++++++++++++
src/router/index.js | 15 +
3 files changed, 540 insertions(+), 5 deletions(-)
diff --git a/src/router/index.js b/src/router/index.js
index e5dc580..a358858 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -106,6 +106,21 @@
},
],
},
+
+ // 鐢熶骇璁㈠崟-鐢熶骇璇︽儏锛堝伐搴忚繘搴︼級
+ {
+ path: "/productionManagement/productionOrder/detail",
+ component: Layout,
+ hidden: true,
+ children: [
+ {
+ path: "",
+ component: () => import("@/views/productionManagement/productionOrder/Detail/index.vue"),
+ name: "ProductionOrderDetail",
+ meta: { title: "鐢熶骇璇︽儏", activeMenu: "/productionManagement/productionOrder" },
+ },
+ ],
+ },
];
// 鍔ㄦ�佽矾鐢憋紝鍩轰簬鐢ㄦ埛鏉冮檺鍔ㄦ�佸幓鍔犺浇
diff --git a/src/views/productionManagement/productionOrder/Detail/index.vue b/src/views/productionManagement/productionOrder/Detail/index.vue
new file mode 100644
index 0000000..8703826
--- /dev/null
+++ b/src/views/productionManagement/productionOrder/Detail/index.vue
@@ -0,0 +1,499 @@
+<template>
+ <div class="app-container production-order-detail">
+ <PageHeader content="鐢熶骇璇︽儏">
+ </PageHeader>
+
+ <el-card shadow="never" class="mb12">
+ <div class="header">
+ <div class="title">鍩虹淇℃伅</div>
+ <div class="sub">
+ <span class="mr12">鐢熶骇璁㈠崟鍙凤細{{ header.npsNo || "-" }}</span>
+ <span class="mr12">鐢熶骇鎵瑰彿锛歿{ header.lotNo || "-" }}</span>
+ <span class="mr12">浜у搧鍚嶇О锛歿{ header.productCategory || "-" }}</span>
+ <span class="mr12">瑙勬牸锛歿{ header.specificationModel || "-" }}</span>
+ </div>
+ </div>
+ </el-card>
+
+ <el-card shadow="never" class="mb12">
+ <div class="steps-head">
+ <div class="steps-title">宸ュ簭鎵ц杩涘害</div>
+ </div>
+ <div class="steps-body">
+ <div class="steps-left">
+ <div class="steps-wrap">
+ <el-steps
+ class="process-steps"
+ :active="active"
+ finish-status="success"
+ direction="vertical"
+ >
+ <el-step
+ v-for="(p, idx) in processes"
+ :key="p.processCode || idx"
+ >
+ <template #title>
+ <div
+ class="step-title"
+ :class="{ selected: idx === selectedIndex }"
+ @click="selectProcess(idx)"
+ >
+ {{ `${idx + 1}. ${p.processName || "-"}` }}
+ </div>
+ </template>
+ <template #description>
+ <div class="step-panel">
+ <div v-if="idx === active" class="current-progress">
+ <div class="current-progress-head">
+ <span class="current-progress-title">褰撳墠宸ュ簭杩涘害</span>
+ <!-- <span class="current-progress-value">{{ currentProcessPercentage }}%</span> -->
+ </div>
+ <el-progress
+ :percentage="currentProcessPercentage"
+ :status="currentProcessPercentage >= 100 ? 'success' : ''"
+ :stroke-width="10"
+ />
+ </div>
+ <div class="step-meta">
+ <span class="meta-item">
+ <span class="meta-label">宸ュ簭缂栧彿</span>
+ <span class="meta-value">{{ p.processCode || "-" }}</span>
+ </span>
+ <span class="meta-item">
+ <span class="meta-label">涓嶈壇鐜�</span>
+ <span class="meta-value danger">{{ defectRateText(p) }}</span>
+ </span>
+ </div>
+ <div class="step-grid">
+ <div class="grid-item">
+ <div class="grid-label">鎶曞叆鏁伴噺</div>
+ <div class="grid-value">{{ p.inputQty ?? 0 }}</div>
+ </div>
+ <div class="grid-item">
+ <div class="grid-label">浜у嚭鏁伴噺</div>
+ <div class="grid-value">{{ p.outputQty ?? 0 }}</div>
+ </div>
+ <div class="grid-item">
+ <div class="grid-label">鍚堟牸鏁伴噺</div>
+ <div class="grid-value success">{{ p.qualifiedQty ?? 0 }}</div>
+ </div>
+ <div class="grid-item">
+ <div class="grid-label">涓嶈壇鏁伴噺</div>
+ <div class="grid-value danger">{{ p.badQty ?? 0 }}</div>
+ </div>
+ </div>
+ </div>
+ </template>
+ </el-step>
+ </el-steps>
+ </div>
+ </div>
+
+ <div class="steps-right">
+ <div class="right-panel">
+ <div class="right-panel-head">
+ <div class="right-title">鎶ュ伐淇℃伅</div>
+ <div class="right-sub" v-if="selectedProcess">
+ 褰撳墠宸ュ簭锛歿{ selectedProcess.processName }}锛坽{ selectedProcess.processCode }}锛�
+ </div>
+ </div>
+
+ <div v-if="!selectedProcess" class="right-empty">
+ 鐐瑰嚮宸︿晶鏌愪釜宸ュ簭锛屽彸渚у睍绀鸿宸ュ簭鐨勬姤宸ユ槑缁嗐��
+ </div>
+
+ <div v-else class="right-content">
+ <el-table :data="mockReports" border height="420">
+ <el-table-column label="搴忓彿" type="index" width="60" align="center" />
+ <el-table-column label="鎶ュ伐鍗曞彿" prop="reportNo" min-width="140" show-overflow-tooltip />
+ <el-table-column label="鎶ュ伐浜哄憳" prop="reportUser" min-width="120" show-overflow-tooltip />
+ <el-table-column label="鎶ュ伐鏃堕棿" prop="reportTime" min-width="160" show-overflow-tooltip />
+ <el-table-column label="浜у嚭鏁伴噺" prop="outputQty" min-width="110" />
+ <el-table-column label="涓嶈壇鏁伴噺" prop="badQty" min-width="110" />
+ <el-table-column label="澶囨敞" prop="remark" min-width="160" show-overflow-tooltip />
+ </el-table>
+ </div>
+ </div>
+ </div>
+ </div>
+ </el-card>
+ </div>
+</template>
+
+<script setup>
+import { computed, ref } from "vue";
+import { useRoute, useRouter } from "vue-router";
+
+const route = useRoute();
+
+const header = computed(() => ({
+ orderId: route.query.orderId,
+ npsNo: route.query.npsNo,
+ lotNo: route.query.lotNo,
+ productCategory: route.query.productCategory,
+ specificationModel: route.query.specificationModel,
+}));
+
+// 妯℃嫙宸ュ簭鏁版嵁锛堝悗缁敤鎺ュ彛鏇挎崲锛�
+const processes = computed(() => [
+ {
+ processCode: "GX-001",
+ processName: "澶囨枡",
+ inputQty: 1000,
+ outputQty: 980,
+ qualifiedQty: 970,
+ badQty: 10,
+ status: "success",
+ },
+ {
+ processCode: "GX-002",
+ processName: "鎴愬瀷",
+ inputQty: 980,
+ outputQty: 960,
+ qualifiedQty: 948,
+ badQty: 12,
+ status: "process",
+ },
+ {
+ processCode: "GX-003",
+ processName: "鐑樺共",
+ inputQty: 960,
+ outputQty: 950,
+ qualifiedQty: 948,
+ badQty: 2,
+ status: "wait",
+ },
+ {
+ processCode: "GX-004",
+ processName: "鍖呰鍏ュ簱",
+ inputQty: 950,
+ outputQty: 920,
+ qualifiedQty: 918,
+ badQty: 2,
+ status: "wait",
+ },
+]);
+
+const selectedIndex = ref(null);
+
+const selectProcess = (idx) => {
+ selectedIndex.value = idx;
+};
+
+const selectedProcess = computed(() => {
+ if (selectedIndex.value === null || selectedIndex.value === undefined) return null;
+ return (processes.value || [])[selectedIndex.value] || null;
+});
+
+// 妯℃嫙鎶ュ伐淇℃伅锛堝悗缁敤鎺ュ彛鏇挎崲锛�
+const mockReports = computed(() => {
+ const p = selectedProcess.value;
+ if (!p) return [];
+ const code = p.processCode || "GX";
+ return [
+ {
+ reportNo: `${code}-BG-0001`,
+ reportUser: "寮犱笁",
+ reportTime: "2026-03-14 09:20",
+ outputQty: Math.floor((p.outputQty ?? 0) * 0.4),
+ badQty: Math.floor((p.badQty ?? 0) * 0.4),
+ remark: "姝e父鎶ュ伐",
+ },
+ {
+ reportNo: `${code}-BG-0002`,
+ reportUser: "鏉庡洓",
+ reportTime: "2026-03-14 13:45",
+ outputQty: Math.floor((p.outputQty ?? 0) * 0.35),
+ badQty: Math.floor((p.badQty ?? 0) * 0.35),
+ remark: "璁惧璋冭瘯鍚庢仮澶�",
+ },
+ {
+ reportNo: `${code}-BG-0003`,
+ reportUser: "鐜嬩簲",
+ reportTime: "2026-03-14 17:10",
+ outputQty: Math.max(0, (p.outputQty ?? 0) - Math.floor((p.outputQty ?? 0) * 0.75)),
+ badQty: Math.max(0, (p.badQty ?? 0) - Math.floor((p.badQty ?? 0) * 0.75)),
+ remark: "鏀跺熬",
+ },
+ ];
+});
+
+const clampPercentage = (val) => {
+ const n = Number(val);
+ if (!Number.isFinite(n)) return 0;
+ if (n <= 0) return 0;
+ if (n >= 100) return 100;
+ return Math.round(n);
+};
+
+// el-steps: active 涓哄綋鍓嶈繘琛屼腑鐨勬楠や笅鏍囷紙妯℃嫙锛�
+const active = computed(() => {
+ const list = processes.value || [];
+ const idx = list.findIndex((p) => p.status === "process");
+ return idx >= 0 ? idx : 0;
+});
+
+const currentProcess = computed(() => {
+ const list = processes.value || [];
+ return list[active.value] || null;
+});
+
+// 褰撳墠宸ュ簭杩涘害锛氱敤浜у嚭/鎶曞叆浼扮畻锛圲I 鍏堣窇閫氾紝鍚庣画鎸夌湡瀹炶鍒欐浛鎹級
+const currentProcessPercentage = computed(() => {
+ const p = currentProcess.value;
+ if (!p) return 0;
+ const input = Number(p.inputQty ?? 0);
+ const output = Number(p.outputQty ?? 0);
+ if (!Number.isFinite(input) || input <= 0) return 0;
+ return clampPercentage((output / input) * 100);
+});
+
+// 涓嶈壇鐜囷細涓嶈壇鏁伴噺 / 浜у嚭鏁伴噺锛堝厛鎸夋鍙e緞锛屽悗缁鎺ユ帴鍙e彲璋冩暣锛�
+const defectRateText = (p) => {
+ const bad = Number(p?.badQty ?? 0);
+ const output = Number(p?.outputQty ?? 0);
+ if (!Number.isFinite(bad) || bad <= 0) return "0%";
+ if (!Number.isFinite(output) || output <= 0) return "0%";
+ const rate = (bad / output) * 100;
+ return `${rate.toFixed(2)}%`;
+};
+</script>
+
+<style scoped lang="scss">
+.production-order-detail {
+ // 宸︿晶姝ラ鍖虹殑鍙楂樺害锛氶殢灞忓箷楂樺害鑷�傚簲
+ // 杩欓噷鍑忓幓椤甸潰椤堕儴锛圥ageHeader + 鍩虹淇℃伅鍗$墖 + 杈硅窛绛夛級鐨勫ぇ鑷撮珮搴�
+ --steps-left-height: calc(100vh - 320px);
+
+ .header {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ .title {
+ font-size: 16px;
+ font-weight: 600;
+ color: #303133;
+ }
+ .sub {
+ color: #606266;
+ display: flex;
+ flex-wrap: wrap;
+ row-gap: 6px;
+ }
+ }
+
+ .steps-head {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ margin-bottom: 12px;
+ .steps-title {
+ font-size: 14px;
+ font-weight: 600;
+ color: #303133;
+ }
+ .steps-desc {
+ font-size: 12px;
+ color: #909399;
+ }
+ }
+
+ .steps-wrap {
+ padding: 4px 0 0;
+ height: 100%;
+ overflow-y: auto;
+ }
+
+ .steps-body {
+ display: flex;
+ gap: 16px;
+ align-items: flex-start;
+ }
+ .steps-left {
+ flex: 0 0 50%;
+ max-width: 50%;
+ min-width: 0;
+ height: var(--steps-left-height);
+ overflow: hidden;
+ }
+ .steps-right {
+ flex: 1;
+ min-width: 0;
+ }
+
+ .step-title {
+ cursor: pointer;
+ display: inline-flex;
+ align-items: center;
+ padding: 2px 6px;
+ border-radius: 6px;
+ transition: background-color 0.15s ease;
+ &:hover {
+ background: #f5f7fa;
+ }
+ &.selected {
+ background: rgba(64, 158, 255, 0.12);
+ color: #409eff;
+ }
+ }
+
+ .process-steps {
+ width: 100%;
+ :deep(.el-step__title) {
+ font-weight: 600;
+ }
+ :deep(.el-step__description) {
+ font-size: 12px;
+ color: #606266;
+ line-height: 18px;
+ margin-top: 8px;
+ width: 100%;
+ }
+ :deep(.el-step__main) {
+ padding-bottom: 20px;
+ width: 100%;
+ }
+ :deep(.el-step__icon.is-text) {
+ border-color: #dcdfe6;
+ }
+ :deep(.el-step.is-vertical) {
+ align-items: flex-start;
+ }
+ :deep(.el-step__head) {
+ width: 28px;
+ flex: 0 0 28px;
+ }
+ :deep(.el-step__main) {
+ flex: 1;
+ min-width: 0;
+ padding-right: 6px;
+ }
+ }
+
+ .step-panel {
+ background: #f6f8fb;
+ border: 1px solid #ebeef5;
+ border-radius: 10px;
+ padding: 12px 12px 10px;
+ width: 100%;
+ max-width: none;
+ box-sizing: border-box;
+ }
+
+ .right-panel {
+ border: 1px solid #ebeef5;
+ border-radius: 10px;
+ background: #ffffff;
+ padding: 12px;
+ min-height: 520px;
+ box-sizing: border-box;
+ }
+ .right-panel-head {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ margin-bottom: 12px;
+ }
+ .right-title {
+ font-size: 14px;
+ font-weight: 700;
+ color: #303133;
+ }
+ .right-sub {
+ font-size: 12px;
+ color: #909399;
+ }
+ .right-empty {
+ height: 100%;
+ min-height: 460px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #909399;
+ background: #fafafa;
+ border: 1px dashed #dcdfe6;
+ border-radius: 10px;
+ }
+
+ .current-progress {
+ background: #ffffff;
+ border: 1px dashed #dcdfe6;
+ border-radius: 10px;
+ padding: 10px 12px 8px;
+ margin-bottom: 10px;
+ .current-progress-head {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 6px;
+ }
+ .current-progress-title {
+ color: #303133;
+ font-weight: 600;
+ font-size: 12px;
+ }
+ .current-progress-value {
+ color: #606266;
+ font-size: 12px;
+ font-weight: 600;
+ }
+ }
+
+ .step-meta {
+ display: flex;
+ gap: 16px;
+ margin-bottom: 10px;
+ .meta-item {
+ display: inline-flex;
+ gap: 8px;
+ align-items: center;
+ }
+ .meta-label {
+ color: #909399;
+ }
+ .meta-value {
+ color: #303133;
+ font-weight: 600;
+ &.danger {
+ color: #f56c6c;
+ }
+ }
+ }
+
+ .step-grid {
+ display: grid;
+ grid-template-columns: repeat(4, minmax(0, 1fr));
+ gap: 10px;
+ .grid-item {
+ background: #ffffff;
+ border: 1px solid #ebeef5;
+ border-radius: 10px;
+ padding: 10px 10px 8px;
+ }
+ .grid-label {
+ font-size: 12px;
+ color: #909399;
+ margin-bottom: 6px;
+ }
+ .grid-value {
+ font-size: 16px;
+ font-weight: 700;
+ color: #303133;
+ &.success {
+ color: #67c23a;
+ }
+ &.danger {
+ color: #f56c6c;
+ }
+ }
+ }
+
+ .mb12 {
+ margin-bottom: 12px;
+ }
+ .mr12 {
+ margin-right: 12px;
+ }
+}
+</style>
+
diff --git a/src/views/productionManagement/productionOrder/index.vue b/src/views/productionManagement/productionOrder/index.vue
index 5db077d..7018d63 100644
--- a/src/views/productionManagement/productionOrder/index.vue
+++ b/src/views/productionManagement/productionOrder/index.vue
@@ -58,11 +58,13 @@
@selection-change="handleSelectionChange"
@pagination="pagination">
<template #completionStatus="{ row }">
- <el-progress
- :percentage="toProgressPercentage(row?.completionStatus)"
- :color="progressColor(toProgressPercentage(row?.completionStatus))"
- :status="toProgressPercentage(row?.completionStatus) >= 100 ? 'success' : ''"
- />
+ <div class="progress-link" @click="goProductionDetail(row)">
+ <el-progress
+ :percentage="toProgressPercentage(row?.completionStatus)"
+ :color="progressColor(toProgressPercentage(row?.completionStatus))"
+ :status="toProgressPercentage(row?.completionStatus) >= 100 ? 'success' : ''"
+ />
+ </div>
</template>
</PIMTable>
</div>
@@ -213,6 +215,7 @@
{
name: "宸ヨ壓璺嚎",
type: "text",
+ showHide: row => row.processRouteCode,
clickFun: row => {
showRouteItemModal(row);
},
@@ -422,6 +425,20 @@
});
};
+ const goProductionDetail = (row) => {
+ if (!row) return;
+ router.push({
+ path: "/productionManagement/productionOrder/detail",
+ query: {
+ orderId: row.id,
+ npsNo: row.npsNo || "",
+ lotNo: row.lotNo || "",
+ productCategory: row.productCategory || "",
+ specificationModel: row.specificationModel || "",
+ },
+ });
+ };
+
// 琛ㄦ牸閫夋嫨鏁版嵁
const handleSelectionChange = (selection) => {
selectedRows.value = selection;
@@ -491,4 +508,8 @@
::v-deep .purple{
background-color: #F4DEFA;
}
+.progress-link {
+ cursor: pointer;
+}
+
</style>
--
Gitblit v1.9.3