From 6eb2429eafd0ed40bf10df64258bc541d8b16512 Mon Sep 17 00:00:00 2001
From: huminmin <mac@MacBook-Pro.local>
Date: 星期二, 24 三月 2026 14:06:29 +0800
Subject: [PATCH] 实际合格率低于【合格率】时,每个【生产工单】、【生产报工】台账需要标红显示

---
 src/views/productionManagement/productionOrder/Detail/index.vue |  351 +++++++++++++++++++++++++++++++++++++++++-----------------
 1 files changed, 248 insertions(+), 103 deletions(-)

diff --git a/src/views/productionManagement/productionOrder/Detail/index.vue b/src/views/productionManagement/productionOrder/Detail/index.vue
index ae30acd..14d11d2 100644
--- a/src/views/productionManagement/productionOrder/Detail/index.vue
+++ b/src/views/productionManagement/productionOrder/Detail/index.vue
@@ -50,7 +50,13 @@
                     }"
                     @click="selectProcess(idx)"
                   >
-                    <span v-if="idx === active" class="step-current-badge">鐢熶骇涓�</span>
+                    <span
+                      v-if="p?.status"
+                      class="step-status-badge"
+                      :class="`step-status-badge-${p.status}`"
+                    >
+                      {{ statusTagText(p.status) }}
+                    </span>
                     <div v-if="p.status !== 'wait'" class="current-progress">
                       <div class="current-progress-head">
                         <span class="current-progress-title">宸ュ簭杩涘害</span>
@@ -112,15 +118,15 @@
             </div>
 
             <div v-else class="right-content">
-              <el-table :data="mockReports" border height="420">
+              <el-table :data="reports" border height="420" v-loading="reportLoading">
                 <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="productNo" min-width="140" show-overflow-tooltip />
+                <el-table-column label="鎶ュ伐浜哄憳" prop="nickName" min-width="120" show-overflow-tooltip />
+                <el-table-column label="鎶ュ伐鏃堕棿" prop="createTime" min-width="160" show-overflow-tooltip />
+                <el-table-column label="浜у嚭鏁伴噺" prop="quantity" min-width="110" />
                 <el-table-column label="鍚堟牸鏁伴噺" prop="qualifiedQty" 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-column label="涓嶈壇鏁伴噺" prop="scrapQty" min-width="110" />
+                <el-table-column label="涓嶅悎鏍煎鐞�" prop="dealResult" min-width="160" show-overflow-tooltip />
                 <el-table-column label="鎿嶄綔" width="150" fixed="right">
                   <template #default="{ row }">
                     <el-button type="primary" link @click="viewReportRecord(row)">
@@ -134,28 +140,57 @@
         </div>
       </div>
     </el-card>
-    <el-dialog
-      v-model="reportRecordDialogVisible"
-      title="鎶ュ伐鐢熶骇璁板綍"
-      width="680px"
-      destroy-on-close
-    >
-      <div class="report-record-placeholder">
-        <div>鎶ュ伐鍗曞彿锛歿{ currentReportRow?.reportNo || "-" }}</div>
-        <div>宸ュ簭锛歿{ selectedProcess?.processName || "-" }}</div>
-        <div class="placeholder-tip">寮规鍐呭寰呭畾锛堝悗缁ˉ鍏呰缁嗙敓浜ц褰曪級銆�</div>
-      </div>
-      <template #footer>
-        <el-button @click="reportRecordDialogVisible = false">鍏抽棴</el-button>
-      </template>
-    </el-dialog>
+<!--    <el-dialog-->
+<!--      v-model="reportRecordDialogVisible"-->
+<!--      title="鎶ュ伐鐢熶骇璁板綍"-->
+<!--      width="680px"-->
+<!--      destroy-on-close-->
+<!--    >-->
+<!--      <div class="report-record-placeholder">-->
+<!--        <div>鎶ュ伐鍗曞彿锛歿{ currentReportRow?.reportNo || "-" }}</div>-->
+<!--        <div>宸ュ簭锛歿{ selectedProcess?.processName || "-" }}</div>-->
+<!--        <div class="placeholder-tip">寮规鍐呭寰呭畾锛堝悗缁ˉ鍏呰缁嗙敓浜ц褰曪級銆�</div>-->
+<!--      </div>-->
+<!--      <template #footer>-->
+<!--        <el-button @click="reportRecordDialogVisible = false">鍏抽棴</el-button>-->
+<!--      </template>-->
+<!--    </el-dialog>-->
+
+    <CopperPrintingForm
+        v-if="copperPrintingFormVisible"
+        v-model:isShow="copperPrintingFormVisible"
+        :isEdit="false"
+        :row="currentReportRow"
+        @refreshData="fetchReportsForProcess(selectedProcess.value)"/>
+    <VoltageSortingForm
+        v-if="voltageSortingFormVisible"
+        v-model:isShow="voltageSortingFormVisible"
+        :isEdit="false"
+        :row="currentReportRow"
+        @refreshData="fetchReportsForProcess(selectedProcess.value)"/>
+    <GranulationForm
+        v-if="granulationFormVisible"
+        v-model:isShow="granulationFormVisible"
+        :isEdit="false"
+        :row="currentReportRow"
+        @refreshData="fetchReportsForProcess(selectedProcess.value)"/>
+    <Detail
+        v-if="reportRecordDialogVisible"
+        v-model:isShow="reportRecordDialogVisible"
+        @refreshData="fetchReportsForProcess(selectedProcess.value)"
+        :row="currentReportRow"/>
   </div>
 </template>
 
 <script setup>
-import { computed, ref, watch } from "vue";
+import { computed, onMounted, ref, watch } from "vue";
 import { useRoute } from "vue-router";
-
+import { getByProductOrderId } from "@/api/productionManagement/workOrder.js";
+import { getByProductWorkOrderId } from "@/api/productionManagement/productionProductMain.js";
+const VoltageSortingForm = defineAsyncComponent(() => import("@/views/productionManagement/workOrder/components/VoltageSortingForm.vue"));
+const CopperPrintingForm = defineAsyncComponent(() => import("@/views/productionManagement/workOrder/components/CopperPrintingForm.vue"));
+const GranulationForm = defineAsyncComponent(() => import("@/views/productionManagement/workOrder/components/GranulationForm.vue"));
+const Detail = defineAsyncComponent(() => import("@/views/productionManagement/productionReporting/components/Detail.vue"));
 const route = useRoute();
 
 const header = computed(() => ({
@@ -166,45 +201,75 @@
   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",
-  },
-]);
+// 宸ュ簭鏁版嵁锛堟帴鍙f浛鎹級
+const processes = ref([]);
+
+const copperPrintingFormVisible = ref(false);
+const voltageSortingFormVisible = ref(false);
+const granulationFormVisible = ref(false);
+
+const normalizeStatus = (statusText, completionStatus, inputQty, outputQty) => {
+  const s = statusText === null || statusText === undefined ? "" : String(statusText).trim();
+
+  // 鎸夋帴鍙e疄闄呬笁绉嶇姸鎬侊細宸茬敓鎴� / 鐢熶骇涓� / 寰呯敓浜�
+  if (s.includes("鐢熶骇涓�")) return "process";
+  if (s.includes("寰呯敓浜�")) return "wait";
+  if (s.includes("宸茬敓浜�")) return "success";
+
+  // 鍏滃簳锛氫粛鎸� completionStatus 鍋� 0~100 鍒ゆ柇
+  const cs = Number(completionStatus);
+  if (Number.isFinite(cs)) {
+    if (cs >= 100) return "success";
+    if (cs > 0) return "process";
+    return "wait";
+  }
+
+  // 鍐嶅厹搴曪細鐢ㄦ暟閲忓垽鏂�
+  if (Number.isFinite(inputQty) && inputQty > 0 && Number.isFinite(outputQty) && outputQty >= inputQty) return "success";
+  if (Number.isFinite(outputQty) && outputQty > 0) return "process";
+  return "wait";
+};
+
+const normalizeProcess = (item) => {
+  // 瀛楁浠ユ帴鍙g害瀹氫负鍑嗭紙浣犵粰鐨勬埅鍥惧瓧娈垫槧灏勶級
+  // 宸ュ簭锛歝ompletionStatus/statusText/processNo/scrapRate/planQuantity/completeQuantity/completeQty/scrapQty
+  const inputQty = Number(item?.planQuantity ?? item?.inputQty ?? 0);
+  const outputQty = Number(item?.completeQuantity ?? item?.outputQty ?? 0);
+  const qualifiedQty = Number(item?.completeQty ?? item?.qualifiedQty ?? item?.goodQty ?? 0);
+  const badQty = Number(item?.scrapQty ?? item?.badQty ?? item?.defectQty ?? 0);
+  const completionStatus = Number(item?.completionStatus ?? 0);
+  const scrapRate = Number(item?.scrapRate ?? NaN);
+
+  const status = normalizeStatus(item?.statusText ?? item?.status ?? item?.workStatus ?? item?.processStatus ?? item?.state, completionStatus, inputQty, outputQty);
+
+  return {
+    processCode: item?.processNo ?? item?.processCode ?? item?.processWorkOrderCode ?? "",
+    processName: item?.processName ?? item?.processWorkOrderName ?? item?.processNo ?? "",
+    productWorkOrderId: item?.productWorkOrderId ?? item?.workOrderId ?? item?.id ?? null,
+    inputQty: Number.isFinite(inputQty) ? inputQty : 0,
+    outputQty: Number.isFinite(outputQty) ? outputQty : 0,
+    qualifiedQty: Math.max(0, Number.isFinite(qualifiedQty) ? qualifiedQty : 0),
+    badQty: Math.max(0, Number.isFinite(badQty) ? badQty : 0),
+    completionStatus: Number.isFinite(completionStatus) ? completionStatus : 0,
+    scrapRate: Number.isFinite(scrapRate) ? scrapRate : null,
+    status,
+  };
+};
+
+onMounted(async () => {
+  const productOrderId = header.value?.orderId;
+  if (!productOrderId) return;
+
+  try {
+    const res = await getByProductOrderId(productOrderId);
+    const payload = res?.data;
+    const list = Array.isArray(payload) ? payload : payload?.records || payload?.data || [];
+    processes.value = list.map((it) => normalizeProcess(it));
+  } catch (e) {
+    console.error("鑾峰彇宸ュ簭宸ュ崟鍒楄〃澶辫触锛�", e);
+    processes.value = [];
+  }
+});
 
 // 榛樿閫変腑绗竴閬撳簭锛堟帴鍙f暟鎹氨缁悗浠嶅彲浠� 0 寮�濮嬶級
 const selectedIndex = ref(0);
@@ -233,47 +298,65 @@
   return list[idx] || null;
 });
 
-// 妯℃嫙鎶ュ伐淇℃伅锛堝悗缁敤鎺ュ彛鏇挎崲锛�
-const mockReports = computed(() => {
-  const p = selectedProcess.value;
-  if (!p) return [];
-  const code = p.processCode || "GX";
-  const reports = [
-    {
-      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: "鏀跺熬",
-    },
-  ];
-  return reports.map((item) => ({
+const reports = ref([]);
+const reportLoading = ref(false);
+
+const normalizeReport = (item) => {
+  return {
     ...item,
-    qualifiedQty: Math.max(0, Number(item.outputQty ?? 0) - Number(item.badQty ?? 0)),
-  }));
-});
+    quantity:  Math.max(0, Number.isFinite(item.quantity) ? item.quantity : 0),
+    scrapQty:  Math.max(0, Number.isFinite(item.scrapQty) ? item.scrapQty : 0),
+    qualifiedQty:  Math.max(0, Number.isFinite(item.qualifiedQty) ? item.qualifiedQty : 0),
+  };
+};
+
+const fetchReportsForProcess = async (p) => {
+  if (!p) {
+    reports.value = [];
+    return;
+  }
+  const productWorkOrderId = p.productWorkOrderId ?? p.id ?? p.workOrderId ?? null;
+  if (!productWorkOrderId) {
+    reports.value = [];
+    return;
+  }
+
+  reportLoading.value = true;
+  try {
+    const res = await getByProductWorkOrderId(productWorkOrderId);
+    const payload = res?.data;
+    const list = Array.isArray(payload)
+      ? payload
+      : payload?.records || payload?.data || payload?.list || [];
+    reports.value = list.map((it) => normalizeReport(it));
+  } catch (e) {
+    console.error("鑾峰彇鎶ュ伐淇℃伅澶辫触锛�", e);
+    reports.value = [];
+  } finally {
+    reportLoading.value = false;
+  }
+};
+
+watch(
+  () => selectedProcess.value,
+  (val) => {
+    fetchReportsForProcess(val);
+  },
+  { immediate: true }
+);
 
 const viewReportRecord = (row) => {
-  if (!row?.reportNo) return;
+  if (!row?.productNo) return;
   currentReportRow.value = row;
-  reportRecordDialogVisible.value = true;
+  if (row.process ==='鍗伴摐' || row.process ==='鍗伴摱') {
+    copperPrintingFormVisible.value = true;
+  } else if (row.process === '鐢靛帇鍒嗛��') {
+    voltageSortingFormVisible.value = true;
+  } else if (row.process === '閫犵矑') {
+    granulationFormVisible.value = true;
+  } else {
+    reportRecordDialogVisible.value = true;
+  }
 };
 
 const reportRecordDialogVisible = ref(false);
@@ -287,16 +370,36 @@
   return Math.round(n);
 };
 
+const statusTagText = (status) => {
+  if (status === "success") return "宸茬敓浜�";
+  if (status === "process") return "鐢熶骇涓�";
+  if (status === "wait") return "寰呯敓浜�";
+  return String(status ?? "");
+};
+
 // el-steps: active 涓哄綋鍓嶈繘琛屼腑鐨勬楠や笅鏍囷紙妯℃嫙锛�
 const active = computed(() => {
   const list = processes.value || [];
-  const idx = list.findIndex((p) => p.status === "process");
-  return idx >= 0 ? idx : 0;
+  // 婵�娲荤姸鎬佷负鈥滃緟鐢熶骇鈥濈殑涓婁竴鏉�
+  const firstWaitIdx = list.findIndex((p) => p?.status === "wait");
+  if (firstWaitIdx > 0) return firstWaitIdx - 1;
+
+  // 濡傛灉娌℃湁寰呯敓浜э細
+  // - 涓旀渶鍚庝竴鏉℃槸鐢熶骇涓細婵�娲绘渶鍚庝竴鏉�
+  // - 鍚﹀垯锛氭病鏈夋縺娲绘牱寮�
+  const lastIdx = list.length - 1;
+  if (lastIdx >= 0 && list[lastIdx]?.status === "process") return lastIdx;
+  return null;
 });
 
 // 宸ュ簭杩涘害锛氱敤浜у嚭/鎶曞叆浼扮畻锛圲I 鍏堣窇閫氾紝鍚庣画鎸夌湡瀹炶鍒欐浛鎹級
 const processPercentage = (p) => {
   if (!p) return 0;
+  // 浼樺厛浣跨敤鎺ュ彛瀛楁 completionStatus锛堜綘缁欑殑鎴浘鈥滃伐搴忚繘搴︹�濓級
+  const cs = Number(p?.completionStatus ?? NaN);
+  if (Number.isFinite(cs)) return clampPercentage(cs);
+
+  // 鍏滃簳锛氱敤浜у嚭/鎶曞叆浼扮畻
   const input = Number(p.inputQty ?? 0);
   const output = Number(p.outputQty ?? 0);
   if (!Number.isFinite(input) || input <= 0) return 0;
@@ -314,6 +417,15 @@
 
 // 涓嶈壇鐜囷細涓嶈壇鏁伴噺 / 浜у嚭鏁伴噺锛堝厛鎸夋鍙e緞锛屽悗缁鎺ユ帴鍙e彲璋冩暣锛�
 const defectRateText = (p) => {
+  // 浼樺厛浣跨敤鎺ュ彛瀛楁 scrapRate锛堜綘缁欑殑鎴浘鈥滀笉鑹巼鈥濓級
+  const scrapRate = Number(p?.scrapRate ?? NaN);
+  if (Number.isFinite(scrapRate)) {
+    // 鏈変簺鎺ュ彛 scrapRate 鍙兘鏄� 0~1 鎴� 0~100锛岃繖閲屽仛涓�涓畝鍗曞垽鏂�
+    const percent = scrapRate <= 1 ? scrapRate * 100 : scrapRate;
+    return `${percent.toFixed(2)}%`;
+  }
+
+  // 鍏滃簳锛氫笉鑹暟閲� / 浜у嚭鏁伴噺
   const bad = Number(p?.badQty ?? 0);
   const output = Number(p?.outputQty ?? 0);
   if (!Number.isFinite(bad) || bad <= 0) return "0%";
@@ -502,6 +614,39 @@
     pointer-events: none;
   }
 
+  .step-status-badge {
+    position: absolute;
+    top: 8px;
+    right: 10px;
+    z-index: 1;
+    font-size: 11px;
+    font-weight: 600;
+    border-radius: 4px;
+    padding: 2px 8px;
+    line-height: 1.2;
+    pointer-events: none;
+    white-space: nowrap;
+    border: 1px solid transparent;
+  }
+
+  .step-status-badge-success {
+    color: #67c23a;
+    background: rgba(103, 194, 58, 0.14);
+    border-color: rgba(103, 194, 58, 0.35);
+  }
+
+  .step-status-badge-process {
+    color: #b88230;
+    background: rgba(230, 162, 60, 0.18);
+    border-color: rgba(230, 162, 60, 0.45);
+  }
+
+  .step-status-badge-wait {
+    color: #909399;
+    background: rgba(144, 147, 153, 0.12);
+    border-color: rgba(144, 147, 153, 0.35);
+  }
+
   .right-panel {
     border: 1px solid #ebeef5;
     border-radius: 10px;

--
Gitblit v1.9.3