From 76b64433947bcb72ee922f890a9e1a7cbe409ea0 Mon Sep 17 00:00:00 2001
From: liding <756868258@qq.com>
Date: 星期五, 12 六月 2026 13:53:08 +0800
Subject: [PATCH] fix(production): 修复生产订单状态更新逻辑

---
 src/views/productionManagement/workOrderManagement/index.vue |  756 ++++++++++++++++++++++++++++++++++++++++++---------------
 1 files changed, 555 insertions(+), 201 deletions(-)

diff --git a/src/views/productionManagement/workOrderManagement/index.vue b/src/views/productionManagement/workOrderManagement/index.vue
index 9d18813..d8433b0 100644
--- a/src/views/productionManagement/workOrderManagement/index.vue
+++ b/src/views/productionManagement/workOrderManagement/index.vue
@@ -1,19 +1,10 @@
 <template>
   <div class="app-container">
-    <div class="search_form">
+    <div class="search_form mb20">
       <div class="search-row">
         <div class="search-item">
-          <span class="search_title">宸ュ崟缂栧彿锛�</span>
-          <el-input v-model="searchForm.workOrderNo"
-                    style="width: 240px"
-                    placeholder="璇疯緭鍏�"
-                    @change="handleQuery"
-                    clearable
-                    prefix-icon="Search" />
-        </div>
-        <div class="search-item">
           <span class="search_title">鐢熶骇璁㈠崟鍙凤細</span>
-          <el-input v-model="searchForm.productOrderNpsNo"
+          <el-input v-model="searchForm.npsNo"
                     style="width: 240px"
                     placeholder="璇疯緭鍏�"
                     @change="handleQuery"
@@ -27,20 +18,200 @@
       </div>
     </div>
     <div class="table_list">
-      <PIMTable rowKey="id"
-                :column="tableColumn"
-                :tableData="tableData"
-                :page="page"
-                :tableLoading="tableLoading"
-                @pagination="pagination">
-        <template #completionStatus="{ row }">
-          <el-progress :percentage="toProgressPercentage(row?.completionStatus)"
-                       :color="progressColor(toProgressPercentage(row?.completionStatus))"
-                       :status="toProgressPercentage(row?.completionStatus) >= 100 ? 'success' : ''" />
-        </template>
-      </PIMTable>
+      <el-table :data="tableData"
+                border
+                v-loading="tableLoading"
+                :expand-row-keys="expandedRowKeys"
+                :row-key="(row) => row.productionOrderId"
+                @expand-change="expandChange"
+                height="calc(100vh - 22em)">
+        <!-- 灞曞紑琛屽垪 -->
+        <el-table-column type="expand"
+                         width="60"
+                         fixed="left">
+          <template #default="props">
+            <el-table :data="props.row.children || []"
+                      border
+                      :row-class-name="({ row }) => getChildRowClassName(row)"
+                      v-loading="childLoading[props.row.productionOrderId]"
+                      style="margin: 10px 0;">
+              <el-table-column align="center"
+                               label="搴忓彿"
+                               type="index"
+                               width="60" />
+              <el-table-column label="宸ュ崟缂栧彿"
+                               prop="workOrderNo"
+                               width="140">
+                <template #default="scope">
+                  <span :class="{ 'rework-text': scope.row.workOrderNo?.startsWith('FG') }">
+                    {{ scope.row.workOrderNo }}
+                  </span>
+                </template>
+              </el-table-column>
+              <el-table-column label="宸ュ簭鍚嶇О"
+                               prop="operationName"
+                               width="100" />
+              <el-table-column label="宸ュ簭绫诲瀷"
+                               width="80"
+                               align="center">
+                <template #default="scope">
+                  <span>{{ scope.row.type === 0 ? '璁℃椂' : '璁′欢' }}</span>
+                </template>
+              </el-table-column>
+              <el-table-column label="璁″垝鏁伴噺"
+                               prop="planQuantity"
+                               width="100" />
+              <el-table-column label="瀹屾垚鏁伴噺"
+                               prop="completeQuantity"
+                               width="100" />
+              <el-table-column label="瀹屾垚杩涘害"
+                               width="140">
+                <template #default="scope">
+                  <el-progress :percentage="toProgressPercentage(scope.row?.completionStatus)"
+                               :color="progressColor(toProgressPercentage(scope.row?.completionStatus))"
+                               :status="toProgressPercentage(scope.row?.completionStatus) >= 100 ? 'success' : ''" />
+                </template>
+              </el-table-column>
+              <el-table-column label="鎶ュ簾鏁伴噺"
+                               prop="scrapQty"
+                               width="80">
+                <template #default="scope">
+                  <span :class="{ 'scrap-text': scope.row.scrapQty > 0 }">
+                    {{ scope.row.scrapQty || 0 }}
+                  </span>
+                </template>
+              </el-table-column>
+              <el-table-column label="鎶ュ伐浜哄憳"
+                               prop="userNames"
+                               width="150"
+                               show-overflow-tooltip />
+              <el-table-column label="璁″垝寮�濮嬫椂闂�"
+                               prop="planStartTime"
+                               width="120" />
+              <el-table-column label="璁″垝缁撴潫鏃堕棿"
+                               prop="planEndTime"
+                               width="120" />
+              <el-table-column label="瀹為檯寮�濮嬫椂闂�"
+                               prop="actualStartTime"
+                               width="120" />
+              <el-table-column label="瀹為檯缁撴潫鏃堕棿"
+                               prop="actualEndTime"
+                               width="120" />
+              <el-table-column label="鐘舵��"
+                               width="100"
+                               align="center">
+                <template #default="scope">
+                  <el-tag :type="getStatusTagType(scope.row.status)">
+                    {{ getStatusText(scope.row.status) }}
+                  </el-tag>
+                </template>
+              </el-table-column>
+              <el-table-column label="鎿嶄綔"
+                               width="200"
+                               align="center"
+                               fixed="right">
+                <template #default="scope">
+                  <el-button link
+                             type="primary"
+                             @click="downloadAndPrintWorkOrder(scope.row)">
+                    娴佽浆鍗�
+                  </el-button>
+                  <el-button link
+                             type="primary"
+                             @click="openWorkOrderFiles(scope.row)">
+                    闄勪欢
+                  </el-button>
+                  <el-button link
+                             type="primary"
+                             v-if="!scope.row.endOrder"
+                             :disabled="isReportDisabled(scope.row)"
+                             @click="showReportDialog(scope.row)">
+                    鎶ュ伐
+                  </el-button>
+                </template>
+              </el-table-column>
+            </el-table>
+          </template>
+        </el-table-column>
+        <!-- 涓昏〃鍒� -->
+        <el-table-column align="center"
+                         label="搴忓彿"
+                         type="index"
+                         width="60" />
+        <el-table-column label="鐢熶骇璁㈠崟鍙�"
+                         prop="npsNo"
+                         width="150" />
+        <el-table-column label="浜у搧鍚嶇О"
+                         prop="productName"
+                         width="140" />
+        <el-table-column label="瑙勬牸"
+                         prop="model"
+                         width="150" />
+        <el-table-column label="鍗曚綅"
+                         prop="unit"
+                         width="80" />
+        <el-table-column label="宸ュ簭鍚嶇О"
+                         prop="operationName"
+                         width="200"
+                         show-overflow-tooltip />
+        <el-table-column label="宸ュ崟鏁伴噺"
+                         prop="productionTaskCount"
+                         width="80">
+          <template #default="scope">
+            <span>鍏眥{ scope.row.productionTaskCount || 0 }}涓�</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="璁″垝鏁伴噺"
+                         prop="planQuantity"
+                         width="100" />
+        <el-table-column label="瀹屾垚鏁伴噺"
+                         prop="completeQuantity"
+                         width="100" />
+        <el-table-column label="瀹屾垚杩涘害"
+                         width="140">
+          <template #default="scope">
+            <el-progress :percentage="toProgressPercentage(scope.row?.completionStatus)"
+                         :color="progressColor(toProgressPercentage(scope.row?.completionStatus))"
+                         :status="toProgressPercentage(scope.row?.completionStatus) >= 100 ? 'success' : ''" />
+          </template>
+        </el-table-column>
+        <el-table-column label="鎶ュ簾鏁伴噺"
+                         prop="scrapQty"
+                         width="80">
+          <template #default="scope">
+            <span :class="{ 'scrap-text': scope.row.scrapQty > 0 }">
+              {{ scope.row.scrapQty || 0 }}
+            </span>
+          </template>
+        </el-table-column>
+        <el-table-column label="鐘舵��"
+                         width="100"
+                         align="center">
+          <template #default="scope">
+            <el-tag :type="getStatusTagType(scope.row.status)">
+              {{ getStatusText(scope.row.status) }}
+            </el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="璁″垝寮�濮嬫椂闂�"
+                         prop="planStartTime"
+                         width="120" />
+        <el-table-column label="璁″垝缁撴潫鏃堕棿"
+                         prop="planEndTime"
+                         width="120" />
+        <el-table-column label="瀹為檯寮�濮嬫椂闂�"
+                         prop="actualStartTime"
+                         width="120" />
+        <el-table-column label="瀹為檯缁撴潫鏃堕棿"
+                         prop="actualEndTime"
+                         width="120" />
+      </el-table>
+      <PaginationComp v-show="page.total > 0"
+                  :total="page.total"
+                  :page="page.current"
+                  :limit="page.size"
+                  @pagination="pagination" />
     </div>
-    
     <!-- 娴佽浆鍗″脊绐� -->
     <el-dialog v-model="transferCardVisible"
                title="娴佽浆鍗�"
@@ -116,7 +287,6 @@
                    @click="printTransferCard">鎵撳嵃娴佽浆鍗�</el-button>
       </div>
     </el-dialog>
-    
     <!-- 鎶ュ伐寮圭獥 -->
     <el-dialog v-model="reportDialogVisible"
                title="鎶ュ伐"
@@ -130,14 +300,14 @@
                     readonly
                     style="width: 300px" />
         </el-form-item>
-        <el-form-item label="鏈鐢熶骇鏁伴噺"
+        <el-form-item label="鐢熶骇鍚堟牸鏁伴噺"
                       prop="quantity">
           <el-input v-model.number="reportForm.quantity"
                     type="number"
-                    min="1"
+                    min="0"
                     step="1"
                     style="width: 300px"
-                    placeholder="璇疯緭鍏ユ湰娆$敓浜ф暟閲�"
+                    placeholder="璇疯緭鍏ョ敓浜у悎鏍兼暟閲�"
                     @input="handleQuantityInput" />
         </el-form-item>
         <el-form-item label="鎶ュ簾鏁伴噺"
@@ -163,6 +333,86 @@
                        :value="user.userId" />
           </el-select>
         </el-form-item>
+        <!-- 宸ユ椂 -->
+        <el-form-item label="宸ユ椂"
+                      v-if="currentReportRowData?.type == 0"
+                      prop="workHour">
+          <el-input v-model.number="reportForm.workHour"
+                    type="number"
+                    min="0"
+                    style="width: 280px"
+                    placeholder="璇疯緭鍏ュ伐鏃�" /><span style="margin-left:10px"
+                class="param-unit">h</span>
+        </el-form-item>
+        <div v-if="params.length > 0"
+             class="param-grid"
+             v-loading="paramLoading">
+          <el-form-item v-for="param in params"
+                        :key="param.id"
+                        :label="param.paramName"
+                        :label-width="120"
+                        class="param-item">
+            <template v-if="param.paramType == '1'">
+              <div class="param-input-group">
+                <el-input-number v-model="reportForm.paramGroups[param.id]"
+                                 controls-position="right"
+                                 :key="param.id"
+                                 style="width: 250px"
+                                 class="param-input" />
+                <span v-if="param.unit && param.unit != '/'"
+                      class="param-unit">{{ param.unit }}</span>
+              </div>
+            </template>
+            <template v-else-if="param.paramType == '2'">
+              <div class="param-input-group">
+                <el-input v-model="reportForm.paramGroups[param.id]"
+                          :key="param.id"
+                          style="width: 250px"
+                          class="param-input" />
+                <span v-if="param.unit && param.unit != '/'"
+                      class="param-unit">{{ param.unit }}</span>
+              </div>
+            </template>
+            <template v-else-if="param.paramType == '3'">
+              <div class="param-input-group">
+                <el-select v-model="reportForm.paramGroups[param.id]"
+                           placeholder="璇烽�夋嫨"
+                           :key="param.id"
+                           class="param-select"
+                           style="width: 250px">
+                  <el-option v-for="option in dictOptions[param.paramFormat] || []"
+                             :key="option.dictLabel"
+                             :label="option.dictLabel"
+                             :value="option.dictLabel" />
+                </el-select>
+                <span v-if="param.unit && param.unit != '/'"
+                      class="param-unit">{{ param.unit }}</span>
+              </div>
+            </template>
+            <template v-else-if="param.paramType == '4'">
+              <div class="param-input-group">
+                <el-date-picker :value-format="param.paramFormat.replace('yyyy', 'YYYY').replace('dd', 'DD')"
+                                :format="param.paramFormat.replace('yyyy', 'YYYY').replace('dd', 'DD')"
+                                :key="param.id"
+                                :type="param.paramFormat=='yyyy-MM-dd'?'date':'datetime'"
+                                v-model="reportForm.paramGroups[param.id]"
+                                class="param-input"
+                                style="width: 250px" />
+                <span v-if="param.unit && param.unit != '/'"
+                      class="param-unit">{{ param.unit }}</span>
+              </div>
+            </template>
+            <template v-else>
+              <div class="param-input-group">
+                <el-input v-model="reportForm.paramGroups[param.id]"
+                          :key="param.id"
+                          class="param-input" />
+                <span v-if="param.unit && param.unit != '/'"
+                      class="param-unit">{{ param.unit }}</span>
+              </div>
+            </template>
+          </el-form-item>
+        </div>
       </el-form>
       <template #footer>
         <span class="dialog-footer">
@@ -172,147 +422,74 @@
         </span>
       </template>
     </el-dialog>
-
-    <MaterialDialog
-      v-model="materialDialogVisible"
-      :row-data="currentMaterialOrderRow"
-      @refresh="getList"
-    />
-    
-    <FilesDia ref="workOrderFilesRef" />
+    <MaterialDialog v-model="materialDialogVisible"
+                    :row-data="currentMaterialOrderRow"
+                    @refresh="getList" />
+    <FileList v-if="fileDialogVisible"
+              v-model:visible="fileDialogVisible"
+              :editable="!currentWorkOrderRow?.endOrder"
+              :record-type="'production_operation_task'"
+              :record-id="currentWorkOrderId" />
   </div>
 </template>
 
 <script setup>
-  import { onMounted, ref, nextTick } from "vue";
+  import { onMounted, ref, nextTick, reactive, toRefs, getCurrentInstance } from "vue";
   import { ElMessageBox } from "element-plus";
   import dayjs from "dayjs";
   import {
     productWorkOrderPage,
+    getWorkOrderListByOrderId,
     addProductMain,
     downProductWorkOrder,
   } from "@/api/productionManagement/workOrder.js";
+  import { listMaterialPickingDetail } from "@/api/productionManagement/productionOrder.js";
+  import { findProcessParamListOrder } from "@/api/productionManagement/productProcessRoute.js";
   import { getUserProfile, userListNoPageByTenantId } from "@/api/system/user.js";
+  import { getDicts } from "@/api/system/dict/data";
   import QRCode from "qrcode";
-  import { getCurrentInstance, reactive, toRefs } from "vue";
-  import FilesDia from "./components/filesDia.vue";
+  import PaginationComp from "@/components/PIMTable/Pagination.vue";
   import MaterialDialog from "./components/MaterialDialog.vue";
-  const { proxy } = getCurrentInstance();
+  const FileList = defineAsyncComponent(() =>
+    import("@/components/Dialog/FileList.vue")
+  );
 
-  const tableColumn = ref([
-    {
-      label: "宸ュ崟绫诲瀷",
-      prop: "workOrderType",
-      width: "80",
-    },
-    {
-      label: "宸ュ崟缂栧彿",
-      prop: "workOrderNo",
-      width: "140",
-    },
-    {
-      label: "鐢熶骇璁㈠崟鍙�",
-      prop: "productOrderNpsNo",
-      width: "140",
-    },
-    {
-      label: "浜у搧鍚嶇О",
-      prop: "productName",
-      width: "140",
-    },
-    {
-      label: "瑙勬牸",
-      prop: "model",
-    },
-    {
-      label: "鍗曚綅",
-      prop: "unit",
-    },
-    {
-      label: "宸ュ簭鍚嶇О",
-      prop: "processName",
-    },
-    {
-      label: "闇�姹傛暟閲�",
-      prop: "planQuantity",
-      width: "140",
-    },
-    {
-      label: "瀹屾垚鏁伴噺",
-      prop: "completeQuantity",
-      width: "140",
-    },
-    {
-      label: "瀹屾垚杩涘害",
-      prop: "completionStatus",
-      dataType: "slot",
-      slot: "completionStatus",
-      width: "140",
-    },
-    {
-      label: "璁″垝寮�濮嬫椂闂�",
-      prop: "planStartTime",
-      width: "140",
-    },
-    {
-      label: "璁″垝缁撴潫鏃堕棿",
-      prop: "planEndTime",
-      width: "140",
-    },
-    {
-      label: "瀹為檯寮�濮嬫椂闂�",
-      prop: "actualStartTime",
-      width: "140",
-    },
-    {
-      label: "瀹為檯缁撴潫鏃堕棿",
-      prop: "actualEndTime",
-      width: "140",
-    },
-    {
-      label: "鎿嶄綔",
-      width: "260",
-      align: "center",
-      dataType: "action",
-      fixed: "right",
-      operation: [
-        {
-          name: "娴佽浆鍗�",
-          clickFun: row => {
-            downloadAndPrintWorkOrder(row);
-          },
-        },
-        {
-          name: "闄勪欢",
-          clickFun: row => {
-            openWorkOrderFiles(row);
-          },
-        },
-        {
-          name: "鎶ュ伐",
-          clickFun: row => {
-            showReportDialog(row);
-          },
-          disabled: row => row.planQuantity <= 0,
-        },
-        {
-          name: "鐗╂枡",
-          clickFun: row => {
-            openMaterialDialog(row);
-          },
-        },
-      ],
-    },
-  ]);
-  
+  import useUserStore from "@/store/modules/user";
+  const { proxy } = getCurrentInstance();
+  const userStore = useUserStore();
+
+  // 鐘舵�佹灇涓炬槧灏�
+  const statusMap = {
+    1: '寰呯‘璁�',
+    2: '寰呯敓浜�',
+    3: '鐢熶骇涓�',
+    4: '宸茬敓浜�',
+  };
+
+  const getStatusText = (status) => statusMap[status] || '鏈煡';
+
+  const getStatusTagType = (status) => {
+    switch (status) {
+      case 1: return 'info';
+      case 2: return 'warning';
+      case 3: return 'primary';
+      case 4: return 'success';
+      default: return 'info';
+    }
+  };
+
   const tableData = ref([]);
   const tableLoading = ref(false);
+  const expandedRowKeys = ref([]);
+  const childLoading = ref({});
+
   const transferCardVisible = ref(false);
   const transferCardData = ref([]);
   const transferCardQrUrl = ref("");
   const transferCardRowData = ref(null);
   const reportDialogVisible = ref(false);
-  const workOrderFilesRef = ref(null);
+  const fileDialogVisible = ref(false);
+  const currentWorkOrderId = ref(null);
   const reportFormRef = ref(null);
   const userOptions = ref([]);
   const reportForm = reactive({
@@ -325,18 +502,25 @@
     productProcessRouteItemId: "",
     userId: "",
     productMainId: null,
+    productionOrderRoutingOperationId: "",
+    productionOrderId: "",
+    workHour: 0,
+    paramGroups: {},
   });
 
-  // 鏈鐢熶骇鏁伴噺楠岃瘉瑙勫垯
+  const params = ref([]);
+  const dictOptions = ref({});
+  const paramLoading = ref(false);
+
+  // 鐢熶骇鍚堟牸鏁伴噺楠岃瘉瑙勫垯
   const validateQuantity = (rule, value, callback) => {
     if (value === null || value === undefined || value === "") {
-      callback(new Error("璇疯緭鍏ユ湰娆$敓浜ф暟閲�"));
+      callback(new Error("璇疯緭鍏ョ敓浜у悎鏍兼暟閲�"));
       return;
     }
     const num = Number(value);
-    // 鏁存暟涓斿ぇ浜庣瓑浜�1
-    if (isNaN(num) || !Number.isInteger(num) || num < 1) {
-      callback(new Error("鏈鐢熶骇鏁伴噺蹇呴』澶т簬绛変簬1"));
+    if (isNaN(num) || !Number.isInteger(num) || num < 0) {
+      callback(new Error("鐢熶骇鍚堟牸鏁伴噺蹇呴』澶т簬绛変簬0"));
       return;
     }
     callback();
@@ -349,7 +533,6 @@
       return;
     }
     const num = Number(value);
-    // 鏁存暟涓斿ぇ浜庣瓑浜�0
     if (isNaN(num) || !Number.isInteger(num) || num < 0) {
       callback(new Error("鎶ュ簾鏁伴噺蹇呴』澶т簬绛変簬0"));
       return;
@@ -363,7 +546,7 @@
     scrapQty: [{ validator: validateScrapQty, trigger: "blur" }],
   };
 
-  // 澶勭悊鏈鐢熶骇鏁伴噺杈撳叆锛岄檺鍒跺繀椤诲ぇ浜庣瓑浜�1
+  // 澶勭悊鐢熶骇鍚堟牸鏁伴噺杈撳叆
   const handleQuantityInput = value => {
     if (value === "" || value === null || value === undefined) {
       reportForm.quantity = null;
@@ -373,16 +556,13 @@
     if (isNaN(num)) {
       return;
     }
-    // 濡傛灉灏忎簬1锛屾竻闄�
-    if (num < 1) {
+    if (num < 0) {
       reportForm.quantity = null;
       return;
     }
-    // 濡傛灉鏄皬鏁板彇鏁存暟閮ㄥ垎
     if (!Number.isInteger(num)) {
       const intValue = Math.floor(num);
-      // 濡傛灉鍙栨暣鍚庡皬浜�1锛屾竻闄�
-      if (intValue < 1) {
+      if (intValue < 0) {
         reportForm.quantity = null;
         return;
       }
@@ -399,24 +579,20 @@
       return;
     }
     const num = Number(value);
-    // 濡傛灉鏄疦aN锛屼繚鎸佸師鍊�
     if (isNaN(num)) {
       return;
     }
-    // 濡傛灉鏄礋鏁帮紝娓呴櫎杈撳叆
     if (num < 0) {
       reportForm.scrapQty = null;
       return;
     }
-    // 濡傛灉鏄皬鏁帮紝鍙栨暣鏁伴儴鍒�
     if (!Number.isInteger(num)) {
       reportForm.scrapQty = Math.floor(num);
       return;
     }
-    // 鏈夋晥鐨勯潪璐熸暣鏁帮紙鍖呮嫭0锛�
     reportForm.scrapQty = num;
   };
-  
+
   const currentReportRowData = ref(null);
   const materialDialogVisible = ref(false);
   const currentMaterialOrderRow = ref(null);
@@ -428,11 +604,11 @@
 
   const data = reactive({
     searchForm: {
-      workOrderNo: "",
-      productOrderNpsNo: "",
+      npsNo: "",
     },
   });
   const { searchForm } = toRefs(data);
+
   const toProgressPercentage = val => {
     const n = Number(val);
     if (!Number.isFinite(n)) return 0;
@@ -440,6 +616,7 @@
     if (n >= 100) return 100;
     return Math.round(n);
   };
+
   const progressColor = percentage => {
     const p = toProgressPercentage(percentage);
     if (p < 30) return "#f56c6c";
@@ -449,25 +626,28 @@
   };
 
   // 鏌ヨ鍒楄〃
-  /** 鎼滅储鎸夐挳鎿嶄綔 */
   const handleQuery = () => {
     page.current = 1;
     getList();
   };
-  
+
   const pagination = obj => {
     page.current = obj.page;
     page.size = obj.limit;
     getList();
   };
-  
+
   const getList = () => {
     tableLoading.value = true;
+    expandedRowKeys.value = [];
     const params = { ...searchForm.value, ...page };
     productWorkOrderPage(params)
       .then(res => {
         tableLoading.value = false;
-        tableData.value = res.data.records;
+        tableData.value = res.data.records.map(item => ({
+          ...item,
+          children: [],
+        }));
         page.total = res.data.total;
       })
       .catch(() => {
@@ -475,7 +655,67 @@
       });
   };
 
-  // 涓嬭浇骞舵墦鍗板伐鍗曟祦杞崱锛堟枃浠舵祦锛�
+  // 灞曞紑琛屾噿鍔犺浇
+  const expandChange = (row, expandedRows) => {
+    if (expandedRows.length > 0) {
+      expandedRowKeys.value = [];
+      const orderId = row.productionOrderId;
+
+      // 妫�鏌ユ槸鍚﹀凡鏈夋暟鎹�
+      if (row.children && row.children.length > 0) {
+        expandedRowKeys.value.push(orderId);
+        return;
+      }
+
+      childLoading.value[orderId] = true;
+
+      getWorkOrderListByOrderId(orderId)
+        .then(res => {
+          childLoading.value[orderId] = false;
+          const index = tableData.value.findIndex(
+            item => item.productionOrderId === orderId
+          );
+          if (index > -1) {
+            tableData.value[index].children = res.data || [];
+          }
+          expandedRowKeys.value.push(orderId);
+        })
+        .catch(error => {
+          childLoading.value[orderId] = false;
+          console.error('鍔犺浇宸ュ崟鏄庣粏澶辫触:', error);
+        });
+    } else {
+      expandedRowKeys.value = [];
+    }
+  };
+
+  // 瀛愯〃琛屾牱寮忥紙杩斿伐杩斾慨绾㈣壊鏍囪锛�
+  const getChildRowClassName = (row) => {
+    if (row.workOrderNo?.startsWith('FG')) {
+      return 'row-rework';
+    }
+    return '';
+  };
+
+  // 鎶ュ伐鎸夐挳绂佺敤閫昏緫
+  const isReportDisabled = (row) => {
+    if (row.planQuantity <= 0) return true;
+    if (!row.userIds) return false;
+    try {
+      const userIds =
+        typeof row.userIds === "string"
+          ? JSON.parse(row.userIds)
+          : row.userIds;
+      if (Array.isArray(userIds)) {
+        return !userIds.some(id => String(id) === String(userStore.id));
+      }
+      return true;
+    } catch (e) {
+      return true;
+    }
+  };
+
+  // 涓嬭浇骞舵墦鍗板伐鍗曟祦杞崱
   const downloadAndPrintWorkOrder = async row => {
     if (!row || !row.id) {
       proxy.$modal.msgError("缂哄皯宸ュ崟ID锛屾棤娉曚笅杞芥祦杞崱");
@@ -485,7 +725,6 @@
       ? `宸ュ崟娴佽浆鍗${row.workOrderNo}.xlsx`
       : "宸ュ崟娴佽浆鍗�.xlsx";
     try {
-      // 璋冪敤鎺ュ彛锛屼互 responseType: 'blob' 鑾峰彇鏂囦欢娴�
       const blob = await downProductWorkOrder(row.id);
 
       if (!blob) {
@@ -493,14 +732,12 @@
         return;
       }
 
-      // 鍒涘缓 Blob URL
       const fileBlob =
         blob instanceof Blob
           ? blob
           : new Blob([blob], { type: blob.type || "application/octet-stream" });
       const url = window.URL.createObjectURL(fileBlob);
 
-      // 鍒涘缓闅愯棌 iframe锛岀敤浜庤Е鍙戞祻瑙堝櫒鎵撳嵃
       const iframe = document.createElement("iframe");
       iframe.style.position = "fixed";
       iframe.style.right = "0";
@@ -517,7 +754,6 @@
           iframe.contentWindow?.print();
         } catch (e) {
           console.error("鑷姩璋冪敤鎵撳嵃澶辫触", e);
-          // 閫�鑰屾眰鍏舵锛屾墦寮�鏂扮獥鍙g敱鐢ㄦ埛鎵嬪姩鎵撳嵃
           window.open(url);
         }
       };
@@ -538,14 +774,34 @@
   const printTransferCard = () => {
     window.print();
   };
+  const currentWorkOrderRow = ref(null);
 
   const openWorkOrderFiles = row => {
-    workOrderFilesRef.value?.openDialog(row);
+    currentWorkOrderId.value = row.id;
+    currentWorkOrderRow.value = row;
+    fileDialogVisible.value = true;
   };
 
-  const showReportDialog = row => {
+  const showReportDialog = async row => {
+    if (row.productionOrderId) {
+      try {
+        const res = await listMaterialPickingDetail(row.productionOrderId);
+        const records = Array.isArray(res.data)
+          ? res.data
+          : res.data?.records || [];
+        if (res.code === 200 && records.length === 0) {
+          proxy.$modal.msgError("鏈鏂欐棤娉曟姤宸�");
+          return;
+        }
+      } catch (error) {
+        console.error("鏌ヨ棰嗘枡璇︽儏澶辫触:", error);
+      }
+    }
     currentReportRowData.value = row;
-    reportForm.planQuantity = row.planQuantity;
+    const planQuantity = Number(row.planQuantity || 0);
+    const completeQuantity = Number(row.completeQuantity || 0);
+    const remainingQuantity = Math.max(0, planQuantity - completeQuantity);
+    reportForm.planQuantity = remainingQuantity;
     reportForm.quantity =
       row.quantity !== undefined && row.quantity !== null ? row.quantity : null;
     reportForm.productProcessRouteItemId = row.productProcessRouteItemId;
@@ -554,10 +810,20 @@
     reportForm.productMainId = row.productMainId;
     reportForm.scrapQty =
       row.scrapQty !== undefined && row.scrapQty !== null ? row.scrapQty : null;
+    reportForm.productionOrderRoutingOperationId =
+      row.productionOrderRoutingOperationId;
+    reportForm.productionOrderId = row.productionOrderId;
+    if (row.type == 0) {
+      reportForm.workHour = row.workHour || 0;
+    } else {
+      reportForm.workHour = 0;
+    }
     nextTick(() => {
       reportFormRef.value?.clearValidate();
+      if (row.productionOrderRoutingOperationId && row.productionOrderId) {
+        loadParams(row.productionOrderRoutingOperationId, row.productionOrderId);
+      }
     });
-    // 鑾峰彇褰撳墠鐧诲綍鐢ㄦ埛淇℃伅锛岃缃负榛樿閫変腑
     getUserProfile()
       .then(res => {
         if (res.code === 200) {
@@ -590,13 +856,12 @@
         return;
       }
 
-      // 楠岃瘉鏈鐢熶骇鏁伴噺
       if (
         reportForm.quantity === null ||
         reportForm.quantity === undefined ||
         reportForm.quantity === ""
       ) {
-        ElMessageBox.alert("璇疯緭鍏ユ湰娆$敓浜ф暟閲�", "鎻愮ず", {
+        ElMessageBox.alert("璇疯緭鍏ョ敓浜у悎鏍兼暟閲�", "鎻愮ず", {
           confirmButtonText: "纭畾",
         });
         return;
@@ -604,21 +869,13 @@
 
       const quantity = Number(reportForm.quantity);
 
-      if (isNaN(quantity) || quantity <= 0) {
-        ElMessageBox.alert("鏈鐢熶骇鏁伴噺蹇呴』澶т簬0", "鎻愮ず", {
+      if (isNaN(quantity) || quantity < 0) {
+        ElMessageBox.alert("鐢熶骇鍚堟牸鏁伴噺蹇呴』澶т簬绛変簬0", "鎻愮ず", {
           confirmButtonText: "纭畾",
         });
         return;
       }
 
-      if (quantity > reportForm.planQuantity) {
-        ElMessageBox.alert("鏈鐢熶骇鏁伴噺涓嶈兘瓒呰繃寰呯敓浜ф暟閲�", "鎻愮ず", {
-          confirmButtonText: "纭畾",
-        });
-        return;
-      }
-
-      // 楠岃瘉鎶ュ簾鏁伴噺
       const scrapQty = Number(reportForm.scrapQty);
       if (!isNaN(scrapQty) && scrapQty < 0) {
         ElMessageBox.alert("鎶ュ簾鏁伴噺涓嶈兘灏忎簬0", "鎻愮ず", {
@@ -627,25 +884,28 @@
         return;
       }
 
-      if (!isNaN(scrapQty) && scrapQty > quantity) {
-        ElMessageBox.alert("鎶ュ簾鏁伴噺涓嶈兘澶т簬鏈鐢熶骇鏁伴噺", "鎻愮ず", {
-          confirmButtonText: "纭畾",
-        });
-        return;
-      }
+      const productionOperationParamList = params.value.map(param => ({
+        ...param,
+        inputValue: reportForm.paramGroups[param.id] ?? "",
+      }));
 
-      const params = {
+      const submitParams = {
         quantity: quantity,
         scrapQty: isNaN(scrapQty) ? 0 : scrapQty,
         userId: reportForm.userId,
         userName: reportForm.userName,
-        workOrderId: reportForm.workOrderId,
+        productionOperationTaskId: reportForm.workOrderId,
         productProcessRouteItemId: reportForm.productProcessRouteItemId,
         reportWork: reportForm.reportWork,
         productMainId: reportForm.productMainId,
+        productionOrderRoutingOperationId:
+          reportForm.productionOrderRoutingOperationId,
+        productionOrderId: reportForm.productionOrderId,
+        workHour: reportForm.workHour,
+        productionOperationParamList: productionOperationParamList,
       };
 
-      addProductMain(params)
+      addProductMain(submitParams)
         .then(res => {
           proxy.$modal.msgSuccess("鎶ュ伐鎴愬姛");
           reportDialogVisible.value = false;
@@ -664,9 +924,54 @@
     reportForm.userName = user ? user.nickName : "";
   };
 
+  const getDictOptions = async dictType => {
+    if (!dictType) return [];
+    if (dictOptions.value[dictType]) return dictOptions.value[dictType];
+    try {
+      const res = await getDicts(dictType);
+      if (res.code === 200) {
+        dictOptions.value[dictType] = res.data;
+        return res.data;
+      }
+      return [];
+    } catch (error) {
+      console.error("鑾峰彇瀛楀吀鏁版嵁澶辫触:", error);
+      return [];
+    }
+  };
+
+  const loadParams = (productionOrderRoutingOperationId, productionOrderId) => {
+    paramLoading.value = true;
+    findProcessParamListOrder({
+      productionOrderRoutingOperationId,
+      productionOrderId,
+    })
+      .then(res => {
+        if (res.code === 200) {
+          const paramList = res.data || [];
+          params.value = paramList;
+          reportForm.paramGroups = {};
+          paramList.forEach(param => {
+            if (!reportForm.paramGroups[param.id]) {
+              reportForm.paramGroups[param.id] = "";
+            }
+            if (param.paramType == "3" && param.paramFormat) {
+              getDictOptions(param.paramFormat);
+            }
+          });
+        }
+      })
+      .catch(err => {
+        console.error("鑾峰彇宸ュ簭鍙傛暟澶辫触:", err);
+      })
+      .finally(() => {
+        paramLoading.value = false;
+      });
+  };
+
   onMounted(() => {
+    userStore.getInfo();
     getList();
-    // 鑾峰彇鐢ㄦ埛鍒楄〃
     userListNoPageByTenantId().then(res => {
       if (res.code === 200) {
         userOptions.value = res.data;
@@ -734,6 +1039,55 @@
     text-align: center;
     margin-top: 20px;
   }
+  .param-grid {
+    margin-top: 10px;
+    border-top: 1px solid #ebe9f3;
+    padding-top: 10px;
+  }
+  .param-item {
+    margin-bottom: 12px;
+  }
+  .param-input-group {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+  }
+  .param-input {
+    flex: 1;
+  }
+  .param-select {
+    flex: 1;
+  }
+  .param-unit {
+    color: #909399;
+    font-size: 12px;
+    min-width: 30px;
+  }
+
+  // 杩斿伐杩斾慨宸ュ崟绾㈣壊鏍囪
+  .row-rework {
+    background-color: #fef0f0 !important;
+
+    :deep(.el-table__cell) {
+      background-color: #fef0f0 !important;
+    }
+  }
+
+  .rework-text {
+    color: #f56c6c;
+    font-weight: bold;
+  }
+
+  // 鎶ュ簾鏁伴噺绾㈣壊鏍囪
+  .scrap-text {
+    color: #f56c6c;
+  }
+
+  // 瀛愯〃鏍兼牱寮�
+  :deep(.el-table__expanded-cell) {
+    padding: 10px 30px !important;
+    background-color: #fafafa;
+  }
 </style>
 
 <style  lang="scss">

--
Gitblit v1.9.3