From 5cfebd7e46c0c53f79b5fb4a917e926194ab4398 Mon Sep 17 00:00:00 2001
From: 张诺 <zhang_12370@163.com>
Date: 星期五, 24 四月 2026 14:19:14 +0800
Subject: [PATCH] feat(生产订单): 完善绑定工艺路线弹窗功能及文件预览组件

---
 src/views/productionManagement/productionOrder/BindRouteDialog.vue |  381 ++++++++++++++++++++++++++++++++++++-----
 src/views/productionManagement/productionOrder/index.vue           |   28 ++-
 src/components/Upload/ActionFileUpload.vue                         |   68 +++++++
 src/components/filePreview/index.vue                               |    9 
 src/views/collaborativeApproval/knowledgeBase/index.vue            |    2 
 5 files changed, 420 insertions(+), 68 deletions(-)

diff --git a/src/components/Upload/ActionFileUpload.vue b/src/components/Upload/ActionFileUpload.vue
index 2d20b60..8a929e0 100644
--- a/src/components/Upload/ActionFileUpload.vue
+++ b/src/components/Upload/ActionFileUpload.vue
@@ -7,14 +7,17 @@
     ref="fileUploadRef"
     :auto-upload="autoUpload"
     :headers="headers"
+    :limit="limit"
+    :disabled="disabled"
     :before-upload="handleBeforeUpload"
+    :on-exceed="handleExceed"
     :on-error="handleUploadError"
     :on-success="handleUploadSuccess"
     :on-remove="handleRemove"
     :on-preview="handlePreview"
     :show-file-list="showFileList"
   >
-    <el-button type="primary">{{ buttonText }}</el-button>
+    <el-button v-if="!disabled" type="primary">{{ buttonText }}</el-button>
     <template #file="{ file }">
       <div style="display:flex; align-items:center; gap: 10px; width: 100%;">
         <span style="flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
@@ -23,7 +26,7 @@
         <div style="display:flex; align-items:center; gap: 6px;">
           <el-button link type="success" :icon="Download" @click="handleDownload(file)" />
           <el-button link type="primary" :icon="View" @click="handlePreview(file)" />
-          <el-button link type="danger" :icon="Delete" @click="triggerRemoveFile(file)" />
+          <el-button link type="danger" :icon="Delete" @click="triggerRemoveFile(file)" v-if="onView" />
         </div>
       </div>
     </template>
@@ -60,6 +63,14 @@
     type: Boolean,
     default: true,
   },
+  limit: {
+    type: Number,
+    default: undefined,
+  },
+  replaceOnExceed: {
+    type: Boolean,
+    default: false,
+  },
   autoUpload: {
     type: Boolean,
     default: true,
@@ -67,6 +78,10 @@
   showFileList: {
     type: Boolean,
     default: true,
+  },
+  disabled: {
+    type: Boolean,
+    default: false,
   },
   buttonText: {
     type: String,
@@ -100,6 +115,10 @@
     type: Function,
     default: null,
   },
+  onView: {
+    type: Boolean,
+    default: true,
+  },
 });
 
 const emit = defineEmits([
@@ -118,8 +137,27 @@
   set: (val) => emit("update:fileList", val),
 });
 
+const resolveUrl = (url) => {
+  const u = String(url || "");
+  if (!u) return "";
+  if (/^(https?:)?\/\//i.test(u)) return u;
+  if (/^(blob:|data:)/i.test(u)) return u;
+  const baseUrl = import.meta.env.VITE_APP_BASE_API || "";
+  if (!baseUrl) return u;
+  if (u.startsWith("/")) return baseUrl + u;
+  return baseUrl + "/" + u;
+};
+
 const getFileUrl = (file) => {
-  return file?.url || file?.response?.data?.tempPath || file?.response?.data?.url || "";
+  const respData = file?.response?.data;
+  const rawUrl =
+    file?.url ||
+    (Array.isArray(respData) ? respData?.[0]?.fileUrl : undefined) ||
+    respData?.fileUrl ||
+    respData?.tempPath ||
+    respData?.url ||
+    "";
+  return resolveUrl(rawUrl);
 };
 
 const triggerRemoveFile = (file) => {
@@ -138,7 +176,31 @@
   emit("error", ...args);
 };
 
+const handleExceed = (files) => {
+  if (!props.replaceOnExceed) return;
+  if (props.limit !== 1) return;
+  const file = files?.[0];
+  if (!file) return;
+  fileUploadRef.value?.clearFiles?.();
+  innerFileList.value = [];
+  fileUploadRef.value?.handleStart?.(file);
+  if (props.autoUpload) {
+    fileUploadRef.value?.submit?.();
+  }
+};
+
 const handleUploadSuccess = (...args) => {
+  const [response, uploadFile, uploadFiles] = args;
+  if (uploadFile && !uploadFile.url) {
+    const rawUrl = response?.data?.tempPath || response?.data?.url || response?.url || "";
+    const resolvedUrl = resolveUrl(rawUrl);
+    if (resolvedUrl) {
+      uploadFile.url = resolvedUrl;
+    }
+  }
+  if (props.limit === 1 && Array.isArray(uploadFiles) && uploadFiles.length) {
+    innerFileList.value = [uploadFiles[uploadFiles.length - 1]];
+  }
   props.onSuccess?.(...args);
   emit("success", ...args);
 };
diff --git a/src/components/filePreview/index.vue b/src/components/filePreview/index.vue
index cda5b56..82cee78 100644
--- a/src/components/filePreview/index.vue
+++ b/src/components/filePreview/index.vue
@@ -88,7 +88,6 @@
 });
 
 const isPdf = computed(() => {
-  console.log(fileUrl.value)
   return /\.pdf$/i.test(fileUrl.value);
 });
 
@@ -167,6 +166,11 @@
   fileUrl.value = window.location.protocol+'//'+window.location.host+ url;
   dialogVisible.value = true;
 };
+
+const openUrl = (url) => {
+  fileUrl.value = url;
+  dialogVisible.value = true;
+}
 const handleClose = () => {
   dialogVisible.value = false;
 };
@@ -183,7 +187,8 @@
 
 // 鏆撮湶open鏂规硶渚涘閮ㄨ皟鐢�
 defineExpose({
-  open
+  open,
+  openUrl
 })
 </script>
 
diff --git a/src/views/collaborativeApproval/knowledgeBase/index.vue b/src/views/collaborativeApproval/knowledgeBase/index.vue
index ff8da1b..1104880 100644
--- a/src/views/collaborativeApproval/knowledgeBase/index.vue
+++ b/src/views/collaborativeApproval/knowledgeBase/index.vue
@@ -767,6 +767,7 @@
 
 // 鏂囦欢棰勮/涓嬭浇
 const handleDownload = (file) => {
+  console.log(file)
   const url = getUploadFileUrl(file)
   if (!url) return
   proxy?.$modal?.loading?.("姝e湪涓嬭浇鏂囦欢锛岃绋嶅��...")
@@ -775,6 +776,7 @@
 }
 
 function handlePreview(file) {
+  console.log(file)
   const url = getUploadFileUrl(file)
   if (!url) return
   filePreviewRef.value?.open?.(url)
diff --git a/src/views/productionManagement/productionOrder/BindRouteDialog.vue b/src/views/productionManagement/productionOrder/BindRouteDialog.vue
index dcededa..484b950 100644
--- a/src/views/productionManagement/productionOrder/BindRouteDialog.vue
+++ b/src/views/productionManagement/productionOrder/BindRouteDialog.vue
@@ -1,7 +1,7 @@
 <template>
   <FormDialog
     v-model="visible"
-    :title="type === 'add' ? '缁戝畾宸ヨ壓璺嚎' : '缂栬緫宸ヨ壓璺嚎'"
+    :title="type === 'add' ? '缁戝畾宸ヨ壓璺嚎' : type === 'detail' ? '鏌ョ湅宸ヨ壓璺嚎' : '缂栬緫宸ヨ壓璺嚎'"
     width="1400px"
     :operation-type="type"
     :column="8"
@@ -9,6 +9,7 @@
     @confirm="handleConfirm"
     @cancel="handleClose"
   >
+    <div :class="{ 'is-detail': isDetail }">
     <!-- ================= 鍩烘湰淇℃伅 ================= -->
     <el-descriptions :column="3">
       <el-descriptions-item label="缂栧彿" align="center" v-if="formData.productOrderList">
@@ -34,7 +35,7 @@
       </el-descriptions-item>
 
       <el-descriptions-item label="鎴愬搧灏哄" :span="1" align="center">
-        {{formData.specificationModel || "--"}}
+        {{formData.finishedSize || "--"}}
       </el-descriptions-item>
 
       <el-descriptions-item label="浜у搧鍚嶇О" :span="2" align="center">
@@ -42,7 +43,7 @@
       </el-descriptions-item>
 
       <el-descriptions-item label="鍗曟嵁绫诲瀷" :span="2" align="center">
-        <el-checkbox-group v-model="introductionLetterList">
+        <el-checkbox-group v-model="introductionLetterList" :disabled="isDetail">
           <el-checkbox label="浠嬬粛淇�" value="浠嬬粛淇�" />
           <el-checkbox label="鍟嗘爣娉ㄥ唽涔�" value="鍟嗘爣娉ㄥ唽涔�" />
           <el-checkbox label="濮斿嵃鍗�" value="濮斿嵃鍗�" />
@@ -53,12 +54,13 @@
     <!-- ================= 鏉愭枡琛� ================= -->
     <div class="process-table-header">
      <div class="section-title">鏉愭枡淇℃伅</div>
-      <el-button type="primary" size="small" @click="addMaterialRow">鏂板涓�琛�</el-button>
+      <el-button v-if="!isDetail" type="primary" size="small" @click="addMaterialRow">鏂板涓�琛�</el-button>
     </div>
     <el-table border :data="formData.materialInfo" style="width: 100%">
       <el-table-column label="鏉愭枡鍚嶇О">
-        <template #default="{ row }">
+        <template #default="{ row }" >
           <el-tree-select
+          v-if="!isDetail"
             v-model="row.productId"
             placeholder="璇烽�夋嫨"
             clearable
@@ -66,18 +68,22 @@
             @change="(val) => getModels(val, row)"
             :data="productOptions"
             :render-after-expand="false"
+            :disabled="isDetail"
             style="width: 100%"
           />
-        </template>
+            <span v-else>{{ row.name }}</span>
+        </template> 
       </el-table-column>
       <el-table-column label="瑙勬牸">
         <template #default="{ row }">
           <el-select
+          v-if="!isDetail"
             v-model="row.productModelId"
             placeholder="璇烽�夋嫨瑙勬牸"
             filterable
             clearable
             @change="(val) => handleMaterialModelChange(val, row)"
+            :disabled="isDetail"
           >
             <el-option
               v-for="item in row.modelOptions || []"
@@ -86,33 +92,32 @@
               :value="item.id"
             />
           </el-select>
+          <span v-else>{{ row.model }}</span>
         </template>
       </el-table-column>
       <el-table-column label="鏁伴噺">
         <template #default="{ row }">
-          <el-input v-model="row.num" placeholder="鏁伴噺">
-            <template #append>{{ row.numSuffix }}</template>
+          <el-input v-model="row.num" placeholder="鏁伴噺" :disabled="isDetail">
           </el-input>
         </template>
       </el-table-column>
       <el-table-column label="璁¢噺鍗曚綅">
         <template #default="{ row }">
-          <el-input v-model="row.unit" placeholder="璁¢噺鍗曚綅" />
+          <el-input v-model="row.unit" placeholder="璁¢噺鍗曚綅" :disabled="isDetail" />
         </template>
       </el-table-column>
       <el-table-column label="鍗曚环">
         <template #default="{ row }">
-          <el-input v-model="row.price" placeholder="鍗曚环">
-            <template #append>{{ row.unitSuffix }}</template>
+          <el-input v-model="row.price" placeholder="鍗曚环" :disabled="isDetail">
           </el-input>
         </template>
       </el-table-column>
       <el-table-column label="閲戦">
         <template #default="{ row }">
-          <el-input v-model="row.totalAmount" placeholder="閲戦" />
+          <el-input v-model="row.totalAmount" placeholder="閲戦" :disabled="isDetail" />
         </template>
       </el-table-column>
-      <el-table-column label="鎿嶄綔" width="80">
+      <el-table-column v-if="!isDetail" label="鎿嶄綔" width="80">
         <template #default="{ $index }">
           <el-button type="danger" size="small" @click="removeMaterialRow($index)">鍒犻櫎</el-button>
         </template>
@@ -130,20 +135,34 @@
             :autosize="{ minRows: 2, maxRows: 4 }"
             type="textarea"
             placeholder="璇疯緭鍏ユ敞鎰忎簨椤�"
+            :disabled="isDetail"
         />
       </el-descriptions-item>
     </el-descriptions>
-    <hr>
     <!-- ================= 鍒囨枡鍥剧ず ================= -->
     <div class="section-title">鍒囨枡鍥剧ず</div>
     <ActionFileUpload
-        style="width: 50%;"
-        v-model:file-list="fileList"
+        style="width: 50%;float: left;"
+        v-model:file-list="formData.cuttingFileVo"
         :action="upload.url"
         :headers="upload.headers"
         :multiple="false"
+        :limit="1"
+        :replaceOnExceed="true"
+        :disabled="isDetail"
         :name="'files'"
-    tip-text="鏀寔鍥剧墖锛坖pg, jpeg, png锛夋牸寮�"
+        :onSuccess="onSuccess"
+        :onDownload="onDownload"
+        :onRemove="onRemove"
+        :onPreview="onPreview"
+        :onView="type === 'detail' ? false : true"
+        :tip-text="type === 'detail' ? '' : '鏀寔鍥剧墖锛坖pg, jpeg, png锛夋牸寮�'"
+    />
+    <el-image
+      v-if="formData.cuttingFileVo.length > 0"
+      style="width: 100px; height: 100px"
+      :src="resolveFileUrl(getUploadFileUrl(formData.cuttingFileVo[0]))"
+      fit="cover"
     />
     <!-- ================= 鍒囨枡淇℃伅 ================= -->
     <el-descriptions
@@ -154,22 +173,22 @@
       class="fixed-desc"
     >
       <el-descriptions-item label="鍒囨枡灏哄" align="center">
-        <el-input v-model="formData.cutNum" placeholder="鍒囨枡灏哄" />
+        <el-input v-model="formData.cutNum" placeholder="鍒囨枡灏哄" :disabled="isDetail" />
       </el-descriptions-item>
       <el-descriptions-item label="鍒囨枡鏁伴噺" align="center">
-        <el-input v-model="formData.cutSize" placeholder="鍒囨枡灏哄" />
+        <el-input v-model="formData.cutSize" placeholder="鍒囨枡灏哄" :disabled="isDetail" />
       </el-descriptions-item>
       <el-descriptions-item label="涓洅鏁伴噺" align="center">
-        <el-input v-model="formData.mediumBoxQty" placeholder="涓洅鏁伴噺" />
+        <el-input v-model="formData.mediumBoxQty" placeholder="涓洅鏁伴噺" :disabled="isDetail" />
       </el-descriptions-item>
       <el-descriptions-item label="灏忕洅鏁伴噺" align="center">
-        <el-input v-model="formData.smallBoxQty" placeholder="灏忕洅鏁伴噺" />
+        <el-input v-model="formData.smallBoxQty" placeholder="灏忕洅鏁伴噺" :disabled="isDetail" />
       </el-descriptions-item>
       <el-descriptions-item label="姝f暟" align="center">
-        <el-input v-model="formData.positiveQty" placeholder="姝f暟" />
+        <el-input v-model="formData.positiveQty" placeholder="姝f暟" :disabled="isDetail" />
       </el-descriptions-item>
       <el-descriptions-item label="鍔犳斁鏁�" align="center">
-        <el-input v-model="formData.allowanceQty" placeholder="鍔犳斁鏁�" />
+        <el-input v-model="formData.allowanceQty" placeholder="鍔犳斁鏁�" :disabled="isDetail" />
       </el-descriptions-item>
     </el-descriptions>
 
@@ -196,25 +215,25 @@
           </tr>
           <tr v-for="(plate, index) in formData.plateMaking" :key="index">
             <td>
-              <el-input v-model="plate.designProductionFee" placeholder="璇疯緭鍏ヨ璁″埗浣滆垂" />
+              <el-input v-model="plate.designProductionFee" placeholder="璇疯緭鍏ヨ璁″埗浣滆垂" :disabled="isDetail" />
             </td>
             <td>
-              <el-input v-model="plate.impositionFee" placeholder="璇疯緭鍏ユ嫾鐗堣垂" />
+              <el-input v-model="plate.impositionFee" placeholder="璇疯緭鍏ユ嫾鐗堣垂" :disabled="isDetail" />
             </td>
             <td>
-              <el-input v-model="plate.filmOutputFee" placeholder="璇疯緭鍏ュ嚭鐗囪垂" />
+              <el-input v-model="plate.filmOutputFee" placeholder="璇疯緭鍏ュ嚭鐗囪垂" :disabled="isDetail" />
             </td>
             <td>
-              <el-input v-model="plate.proofingFee" placeholder="璇疯緭鍏ユ墦鏍疯垂" />
+              <el-input v-model="plate.proofingFee" placeholder="璇疯緭鍏ユ墦鏍疯垂" :disabled="isDetail" />
             </td>
             <td>
-              <el-input v-model="plate.doctorBladePlateFee" placeholder="璇疯緭鍏ュ埆鍒�鐗堣垂" />
+              <el-input v-model="plate.doctorBladePlateFee" placeholder="璇疯緭鍏ュ埆鍒�鐗堣垂" :disabled="isDetail" />
             </td>
             <td>
-              <el-input v-model="plate.hotEmbossingPlateFee" placeholder="璇疯緭鍏ョ儷/鍑哥増璐�" />
+              <el-input v-model="plate.hotEmbossingPlateFee" placeholder="璇疯緭鍏ョ儷/鍑哥増璐�" :disabled="isDetail" />
             </td>
             <td>
-              <el-input v-model="plate.subtotalFee" placeholder="璇疯緭鍏ュ皬璁�" />
+              <el-input v-model="plate.subtotalFee" placeholder="璇疯緭鍏ュ皬璁�" :disabled="isDetail" />
             </td>
           </tr>
         </tbody>
@@ -224,17 +243,16 @@
     <!-- ================= 宸ヨ壓鍔犲伐 ================= -->
     <div class="process-table-header">
      <div class="section-title">宸ヨ壓鍔犲伐</div>
-      <el-button type="primary" size="small" @click="addProcessRow">鏂板涓�琛�</el-button>
+      <el-button v-if="!isDetail" type="primary" size="small" @click="addProcessRow">鏂板涓�琛�</el-button>
     </div>
     <el-table border :data="formData.processContent" style="width: 100%" :span-method="objectSpanMethod">
-      <el-table-column label="宸ュ簭" width="140">
-        <template #default="{ row }">
           <el-table-column label="宸ュ簭" width="140">
             <template #default="{ row }">
               <el-select
                   v-model="row.processId"
                   placeholder="璇烽�夋嫨宸ュ簭"
                   @change="(val) => onProcessChange(val, row)"
+                  :disabled="isDetail"
               >
                 <el-option
                     v-for="item in processOptions"
@@ -245,24 +263,25 @@
               </el-select>
             </template>
           </el-table-column>
-        </template>
-      </el-table-column>
       <el-table-column label="寮�鏁�">
         <template #default="{ row }">
-          <el-input v-model="row.openCount" placeholder="璇疯緭鍏ュ紑鏁�" />
+          <el-input v-model="row.openCount" placeholder="璇疯緭鍏ュ紑鏁�" :disabled="isDetail" />
         </template>
       </el-table-column>
       <el-table-column label="宸ヨ壓姝f暟">
         <template #default="{ row }">
-          <el-input v-model="row.processPositive" placeholder="璇疯緭鍏ュ伐鑹烘鏁�" />
+          <el-input v-model="row.processPositive" placeholder="璇疯緭鍏ュ伐鑹烘鏁�" :disabled="isDetail" />
         </template>
       </el-table-column>
       <el-table-column label="鍔犳斁鏁�">
         <template #default="{ row }">
-          <el-input v-model="row.allowanceQty" placeholder="璇疯緭鍏ュ姞鏀炬暟" />
+          <el-input v-model="row.allowanceQty" placeholder="璇疯緭鍏ュ姞鏀炬暟" :disabled="isDetail" />
         </template>
       </el-table-column>
-      <el-table-column label="鏈哄彴" width="180">
+      <el-table-column width="180">
+        <template #header>
+          <span class="required">*</span>鏈哄彴
+        </template>
         <template #default="{ row }">
           <el-select
             v-model="row.deviceId"
@@ -270,6 +289,7 @@
             filterable
             clearable
             @change="(val) => handleDeviceChange(val, row)"
+            :disabled="isDetail"
           >
             <el-option
               v-for="item in deviceOptions"
@@ -280,9 +300,13 @@
           </el-select>
         </template>
       </el-table-column>
-      <el-table-column label="鎶ュ伐浜�" width="220">
+      <el-table-column width="220">
+        <template #header>
+          <span class="required">*</span>鎶ュ伐浜�
+        </template>
         <template #default="{ row }">
           <el-select
+          v-if="!isDetail"
             v-model="row.reportUserIds"
             placeholder="璇烽�夋嫨鎶ュ伐浜�"
             filterable
@@ -291,6 +315,7 @@
             collapse-tags
             collapse-tags-tooltip
             @change="(val) => handleReportUsersChange(val, row)"
+            :disabled="isDetail"
           >
             <el-option
               v-for="item in userOptions"
@@ -299,6 +324,14 @@
               :value="item.userId"
             />
           </el-select>
+  <el-tag
+  v-else
+    v-for="item in row.reportWorkerList"
+    
+    :key="item.id"
+  >
+    {{ item.userName }}
+  </el-tag>
         </template>
       </el-table-column>
       <el-table-column label="宸ヨ壓瑕佹眰">
@@ -308,10 +341,11 @@
             type="textarea"
             :rows="6"
             placeholder="璇疯緭鍏ュ伐鑹鸿姹�"
+            :disabled="isDetail"
           />
         </template>
       </el-table-column>
-      <el-table-column label="鎿嶄綔" width="80">
+      <el-table-column v-if="!isDetail" label="鎿嶄綔" width="80">
         <template #default="{ $index }">
           <el-button type="danger" size="small" @click="removeProcessRow($index)">鍒犻櫎</el-button>
         </template>
@@ -321,19 +355,19 @@
     <!-- ================= 鍖呰淇℃伅 ================= -->
     <el-descriptions border :column="3" class="mt">
       <el-descriptions-item label="閫佽揣鍦扮偣" align="center">
-        <el-input v-model="formData.deliveryAddress" placeholder="閫佽揣鍦扮偣" />
+        <el-input v-model="formData.deliveryAddress" placeholder="閫佽揣鍦扮偣" :disabled="isDetail" />
       </el-descriptions-item>
 
       <el-descriptions-item label="鑱旂郴浜�" align="center">
-        <el-input v-model="formData.contactName" placeholder="鑱旂郴浜�" />
+        <el-input v-model="formData.contactName" placeholder="鑱旂郴浜�" :disabled="isDetail" />
       </el-descriptions-item>
 
       <el-descriptions-item label="鍖呰瑕佹眰" align="center">
-        <el-input v-model="formData.packagingRequirement" placeholder="鍖呰瑕佹眰" />
+        <el-input v-model="formData.packagingRequirement" placeholder="鍖呰瑕佹眰" :disabled="isDetail" />
       </el-descriptions-item>
 
       <el-descriptions-item label="灏哄" align="center">
-        <el-input v-model="formData.postProcessSize" placeholder="灏哄" />
+        <el-input v-model="formData.postProcessSize" placeholder="灏哄" :disabled="isDetail" />
       </el-descriptions-item>
 
       <el-descriptions-item label="瀹氳揣鏁伴噺" align="center">
@@ -341,14 +375,16 @@
       </el-descriptions-item>
 
       <el-descriptions-item label="瀹炰氦鏁伴噺" :span="3" align="center">
-        <el-input v-model="formData.actualDeliveryQty" placeholder="瀹炰氦鏁伴噺" />
+        <el-input v-model="formData.actualDeliveryQty" placeholder="瀹炰氦鏁伴噺" :disabled="isDetail" />
       </el-descriptions-item>
     </el-descriptions>
+    </div>
   </FormDialog>
+  <filePreview ref="filePreviewRef" />
 </template>
 
 <script setup>
-import { ref, reactive, computed, onMounted, watch } from 'vue'
+import { ref, reactive, computed, onMounted, watch, getCurrentInstance } from 'vue'
 import dayjs from 'dayjs'
 import FormDialog from '@/components/Dialog/FormDialog.vue'
 import ActionFileUpload from "@/components/Upload/ActionFileUpload.vue";
@@ -358,6 +394,8 @@
 import { getDeviceLedger } from "@/api/equipmentManagement/ledger.js"
 import { userListNoPageByTenantId } from "@/api/system/user.js"
 import { getToken } from "@/utils/auth";
+import filePreview from '@/components/filePreview/index.vue'
+import { ElMessage } from "element-plus";
 
 const props = defineProps({
   modelValue: {
@@ -379,30 +417,31 @@
 })
 
 const emit = defineEmits(['update:modelValue', 'confirm'])
+const { proxy } = getCurrentInstance()
 
 const visible = computed({
   get: () => props.modelValue,
   set: (val) => emit('update:modelValue', val)
 })
 
+const isDetail = computed(() => props.type === 'detail')
 const processOptions = ref([])
 const deviceOptions = ref([])
 const userOptions = ref([])
-const reportWorkerList = ref([])
 const productOptions = ref([])
 const introductionLetterList = ref([])
-const fileList = ref([])
 const upload = reactive({
   url: import.meta.env.VITE_APP_BASE_API + '/basic/customer-follow/upload',
   headers: { Authorization: 'Bearer ' + getToken() }
 })
 
+const filePreviewRef = ref()
 const formData = reactive({
   productOrderList:null,
   salesLedgerId: null,
   productOrderId: null,
   printOrderTime: dayjs().format('YYYY-MM-DD HH:mm:ss'),
-  fileList:[],
+  cuttingFileVo:[],
   finishTime: "",
   no: "",
   productName: "",
@@ -473,16 +512,158 @@
   specificationModel:"",
 })
 
+const getUploadFileUrl = (file) => {
+  console.log("file", file)
+  const response = file?.response
+  const data = response?.data
+  if (Array.isArray(data) && data.length) {
+    return data[0]?.fileUrl || data[0]?.url || data[0]?.tempPath || ""
+  }
+  
+  return file?.url || file?.fileUrl || data?.tempPath || data?.url || data?.fileUrl || ""
+}
+
 // 鐩戝惉 checkbox group 鍙樺寲骞跺悓姝ュ埌 introductionLetter 瀛楃涓�
 watch(introductionLetterList, (val) => {
   formData.introductionLetter = val.join(',')
 })
 const onProcessChange = (processId, row) => {
-  const selected = processOptions.find(item => item.id === processId)
+  const selected = processOptions.value.find(item => item.id === processId)
   row.processName = selected?.name || ''
 }
 const cloneDeep = (val) => JSON.parse(JSON.stringify(val))
 
+const createDefaultFormData = () => ({
+  productOrderList: null,
+  salesLedgerId: null,
+  productOrderId: null,
+  printOrderTime: dayjs().format('YYYY-MM-DD HH:mm:ss'),
+  cuttingFileVo: [],
+  finishTime: "",
+  no: "",
+  productName: "",
+  productDescription: "",
+  clientName: "",
+  finishedSize: "",
+  cutNum: "",
+  cutSize: "",
+  mediumBoxQty: "",
+  smallBoxQty: "",
+  positiveQty: "",
+  allowanceQty: "",
+  introductionLetter: "",
+  plateMaking: [
+    {
+      designProductionFee: "",
+      impositionFee: "",
+      filmOutputFee: "",
+      proofingFee: "",
+      doctorBladePlateFee: "",
+      hotEmbossingPlateFee: "",
+      subtotalFee: ""
+    }
+  ],
+  processContent: [
+    {
+      id: "1",
+      processId: "",
+      processName: "",
+      mediumBoxQty: "",
+      smallBoxQty: "",
+      openCount: "",
+      processPositive: "",
+      allowanceQty: "",
+      deviceId: "",
+      deviceName: "",
+      reportUserIds: [],
+      reportWorkerList: []
+    }
+  ],
+  materialInfo: [
+    {
+      id: "1",
+      productId: "",
+      name: "",
+      productModelId: "",
+      model: "",
+      modelOptions: [],
+      num: "",
+      numSuffix: "寮�",
+      unitSuffix: "鍏�/kg",
+      unit: "",
+      price: "",
+      totalAmount: ""
+    }
+  ],
+  processRequirement: "",
+  deliveryAddress: "",
+  contactName: "",
+  packagingRequirement: "",
+  postProcessSize: "",
+  orderQty: "",
+  actualDeliveryQty: "",
+  productionDept: "",
+  technicalDept: "",
+  warehouseDept: "",
+  productModelId: "",
+  specificationModel: "",
+  cuttingFileId:""
+})
+
+const resetForm = () => {
+  const next = createDefaultFormData()
+  Object.keys(next).forEach((key) => {
+    formData[key] = Array.isArray(next[key]) ? cloneDeep(next[key]) : next[key]
+  })
+  introductionLetterList.value = []
+}
+
+const onSuccess = (response, uploadFile, uploadFiles) => {
+    const data = response?.data
+
+  if (uploadFile && !uploadFile.url) {
+    uploadFile.url = (Array.isArray(data) ? data?.[0]?.fileUrl : data?.fileUrl) || data?.tempPath || data?.url || response?.url || ""
+  }
+  if (Array.isArray(uploadFiles) && uploadFiles.length) {
+    formData.cuttingFileVo = [uploadFiles[uploadFiles.length - 1]]
+    formData.cuttingFileId = data?.[0]?.id || ""
+  }
+}
+
+const JavaApi = computed(() => proxy?.javaApi || "")
+
+const onRemove = (file) => {
+  formData.cuttingFileVo = []
+  formData.cuttingFileId = ""
+}
+
+const resolveFileUrl = (rawUrl) => {
+  console.log("rawUrl", rawUrl)
+  const u = String(rawUrl || "")
+  if (!u) return ""
+  if (/^(https?:)?\/\//i.test(u)) return u
+  if (/^(blob:|data:)/i.test(u)) return u
+  const base = String(JavaApi.value || "").replace(/\/+$/, "")
+  if (!base) return u
+  if (u.startsWith("/")) return base + u
+  return base + "/" + u
+}
+
+// 鏂囦欢棰勮/涓嬭浇
+const onDownload = (file) => {
+  console.log(file)
+  const url = resolveFileUrl(getUploadFileUrl(file))
+  if (!url) return
+  proxy?.$modal?.loading?.("姝e湪涓嬭浇鏂囦欢锛岃绋嶅��...")
+  proxy.$download.name(url);
+  proxy?.$modal?.closeLoading?.()
+}
+
+const onPreview = (file) => {
+  const url = resolveFileUrl(getUploadFileUrl(file))
+  if (!url) return
+  filePreviewRef.value?.openUrl?.(url);
+}
 
 
 const mergeRowDataToForm = (source) => {
@@ -495,6 +676,19 @@
       formData[key] = Array.isArray(source[key]) ? cloneDeep(source[key]) : source[key]
     }
   })
+
+  if (formData.cuttingFileVo && !Array.isArray(formData.cuttingFileVo)) {
+    formData.cuttingFileVo = [formData.cuttingFileVo]
+  }
+  if (Array.isArray(formData.cuttingFileVo)) {
+    formData.cuttingFileVo = formData.cuttingFileVo
+      .filter(Boolean)
+      .map((f) => ({
+        ...f,
+        name: f?.name || f?.fileName || "",
+        url: f?.url || f?.fileUrl || "",
+      }))
+  }
 
   // 鍏煎 index.vue 閲屽父鐢ㄥ瓧娈靛悕涓庡脊绐楀瓧娈靛悕涓嶄竴鑷寸殑鎯呭喌
   if (source.productName === undefined && source.productCategory !== undefined) {
@@ -512,6 +706,11 @@
   if (source.productOrderId === undefined && source.id !== undefined) {
     formData.productOrderId = source.id
   }
+
+  introductionLetterList.value = String(formData.introductionLetter || "")
+    .split(",")
+    .map(s => s.trim())
+    .filter(Boolean)
 }
 
 // 鑾峰彇閿�鍞鍗�
@@ -704,7 +903,30 @@
 }
 
 const handleConfirm = () => {
-  emit('confirm', JSON.parse(JSON.stringify(formData)))
+  if (isDetail.value) {
+    return
+  }
+  const rows = Array.isArray(formData.processContent) ? formData.processContent : []
+  for (let i = 0; i < rows.length; i++) {
+    const row = rows[i] || {}
+    if (!row.deviceId) {
+      ElMessage.warning(`宸ヨ壓鍔犲伐绗�${i + 1}琛岋細鏈哄彴蹇呭~`)
+      return
+    }
+    if (!Array.isArray(row.reportUserIds) || row.reportUserIds.length === 0) {
+      ElMessage.warning(`宸ヨ壓鍔犲伐绗�${i + 1}琛岋細鎶ュ伐浜哄繀濉玚)
+      return
+    }
+  }
+  const payload = cloneDeep(formData)
+  delete payload.productOrderList
+  if (Array.isArray(payload.materialInfo)) {
+    payload.materialInfo = payload.materialInfo.map((item) => {
+      const { modelOptions, ...rest } = item || {}
+      return rest
+    })
+  }
+  emit('confirm', payload)
 }
 
 onMounted(() => {
@@ -714,7 +936,8 @@
   getMaterialProductOptions()
 })
 defineExpose({
-  getProductOrder
+  getProductOrder,
+  resetForm
 })
 </script>
 
@@ -784,7 +1007,59 @@
 .mt {
   margin-top: 20px;
 }
+:deep(.required) {
+  color: #f56c6c;
+}
 :deep(.el-textarea__inner){
   box-shadow: none;
 }
+
+.is-detail {
+  :deep(.el-input__wrapper) {
+    box-shadow: none !important;
+    background-color: transparent !important;
+  }
+
+  :deep(.el-input__inner) {
+    color: var(--el-text-color-regular) !important;
+    -webkit-text-fill-color: var(--el-text-color-regular) !important;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    overflow: hidden;
+  }
+
+  :deep(.el-input-group__append),
+  :deep(.el-input-group__prepend) {
+    background-color: transparent !important;
+    box-shadow: none !important;
+    color: var(--el-text-color-regular) !important;
+  }
+
+  :deep(.el-select__wrapper) {
+    box-shadow: none !important;
+    background-color: transparent !important;
+  }
+
+  :deep(.el-select__selected-item) {
+    color: var(--el-text-color-regular) !important;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    overflow: hidden;
+  }
+
+  :deep(.el-select__caret) {
+    display: none;
+  }
+
+  :deep(.el-textarea__inner) {
+    box-shadow: none !important;
+    background-color: transparent !important;
+    color: var(--el-text-color-regular) !important;
+    -webkit-text-fill-color: var(--el-text-color-regular) !important;
+    overflow: hidden;
+    display: -webkit-box;
+    -webkit-line-clamp: 2;
+    -webkit-box-orient: vertical;
+  }
+}
 </style>
diff --git a/src/views/productionManagement/productionOrder/index.vue b/src/views/productionManagement/productionOrder/index.vue
index 15e7cba..d2cf5f1 100644
--- a/src/views/productionManagement/productionOrder/index.vue
+++ b/src/views/productionManagement/productionOrder/index.vue
@@ -110,7 +110,13 @@
 
   const handleBindRouteSubmit =async (data)=>{
     const res = await saveProductionProductInput(data)
-    console.log(res)
+    if(res.code === 200){
+      proxy.$modal.msgSuccess("缁戝畾鎴愬姛");
+      bindRouteDialogVisible.value = false
+      handleQuery()
+    }else{
+      proxy.$modal.msgError(res.msg || "缁戝畾澶辫触")
+    }
 
   }
 
@@ -279,18 +285,20 @@
   const openBindRouteDialog = async (row,type) => {
     bindForm.orderId = row.id;
     bindForm.routeId = null;
-    bindRouteDialogVisible.value = true;
     routeOptions.value = [];
     bindRouteLoading.value = true;
-    if(type === "view") {
-      bindDialogType.value = "view"
-      let res = await viewGetByProductWordId(row.id)
-      console.log(res)
-    }
-    BindRouteDialogRef.value?.getProductOrder()
-
     try {
-      rowData.value = row;
+      BindRouteDialogRef.value?.resetForm?.()
+      if (type === "view") {
+        bindDialogType.value = "detail"
+        const res = await viewGetByProductWordId(row.id)
+        rowData.value = res?.data || res
+      } else {
+        bindDialogType.value = "add"
+        rowData.value = row
+        rowData.value.finishedSize = row.specificationModel
+      }
+      bindRouteDialogVisible.value = true;
     } catch (e) {
       console.error("鑾峰彇宸ヨ壓璺嚎鍒楄〃澶辫触锛�", e);
       proxy.$modal.msgError("鑾峰彇宸ヨ壓璺嚎鍒楄〃澶辫触");

--
Gitblit v1.9.3