From 64ac7e195332cffa495e84c0e3fed3c1c232d20c Mon Sep 17 00:00:00 2001
From: huminmin <mac@MacBook-Pro.local>
Date: 星期五, 10 七月 2026 18:05:13 +0800
Subject: [PATCH] 修改报工

---
 src/views/productionManagement/productionReportingEntry/index.vue                              |  192 +++++++++++++
 src/views/officeProcessAutomation/ApproveManage/approve-list/useApproveList.js                 |   12 
 src/views/officeProcessAutomation/ApproveManage/approve-shared/approvalModuleRegistry.js       |    8 
 src/views/qualityManagement/nonconformingManagement/components/formDia.vue                     |   90 ++++++
 src/views/productionManagement/productionReporting/index.vue                                   |   57 +++
 src/components/AttachmentUpload/image/index.vue                                                |   72 ++++
 src/views/productionManagement/productionCosting/index.vue                                     |  177 +++++++++++-
 src/views/officeProcessAutomation/ApproveManage/approve-list/approveListConstants.js           |    1 
 src/views/officeProcessAutomation/ApproveManage/approve-list/components/ApproveDetailPanel.vue |   22 +
 src/views/qualityManagement/nonconformingManagement/index.vue                                  |   22 +
 src/views/productionManagement/productionReporting/components/formDia.vue                      |   19 +
 src/views/productionManagement/workOrderManagement/index.vue                                   |  129 +++-----
 12 files changed, 684 insertions(+), 117 deletions(-)

diff --git a/src/components/AttachmentUpload/image/index.vue b/src/components/AttachmentUpload/image/index.vue
index 8243f9c..889b679 100644
--- a/src/components/AttachmentUpload/image/index.vue
+++ b/src/components/AttachmentUpload/image/index.vue
@@ -27,6 +27,10 @@
     type: Array,
     default: () => ['png', 'jpg', 'jpeg', 'webp'],
   },
+  watermarkText: {
+    type: String,
+    default: '',
+  },
   buttonText: {
     type: String,
     default: '涓婁紶鍥剧墖',
@@ -161,6 +165,67 @@
   return true
 }
 
+async function addWatermarkToFile(rawFile) {
+  if (!props.watermarkText) {
+    return rawFile
+  }
+
+  const imageUrl = await readFileAsDataURL(rawFile)
+  const image = await loadImage(imageUrl)
+  const canvas = document.createElement('canvas')
+  const ctx = canvas.getContext('2d')
+
+  if (!ctx) {
+    return rawFile
+  }
+
+  canvas.width = image.width
+  canvas.height = image.height
+  ctx.drawImage(image, 0, 0, canvas.width, canvas.height)
+
+  const padding = Math.max(16, Math.round(canvas.width * 0.02))
+  const fontSize = Math.max(24, Math.round(canvas.width * 0.03))
+  const lineHeight = Math.round(fontSize * 1.35)
+  const lines = String(props.watermarkText).split('\n').filter(Boolean)
+
+  ctx.fillStyle = 'rgba(0, 0, 0, 0.32)'
+  const blockHeight = lines.length * lineHeight + padding * 2
+  ctx.fillRect(0, canvas.height - blockHeight, canvas.width, blockHeight)
+  ctx.fillStyle = 'rgba(255, 255, 255, 0.96)'
+  ctx.font = `${fontSize}px sans-serif`
+  ctx.textBaseline = 'bottom'
+
+  lines.forEach((line, index) => {
+    ctx.fillText(line, padding, canvas.height - padding - (lines.length - 1 - index) * lineHeight)
+  })
+
+  const mimeType = rawFile.type || 'image/jpeg'
+  const blob = await new Promise((resolve) => canvas.toBlob(resolve, mimeType, 0.92))
+  if (!blob) {
+    return rawFile
+  }
+
+  return new File([blob], rawFile.name, { type: mimeType, lastModified: Date.now() })
+}
+
+function readFileAsDataURL(file) {
+  return new Promise((resolve, reject) => {
+    const reader = new FileReader()
+    reader.onload = () => resolve(reader.result)
+    reader.onerror = reject
+    reader.readAsDataURL(file)
+  })
+}
+
+function loadImage(src) {
+  return new Promise((resolve, reject) => {
+    const img = new Image()
+    img.onload = () => resolve(img)
+    img.onerror = reject
+    img.src = src
+  })
+}
+
 function scheduleUpload(uploadFiles) {
   clearTimeout(uploadQueueTimer.value)
   uploadQueueTimer.value = setTimeout(() => {
@@ -196,9 +261,10 @@
   }
 
   const formData = new FormData()
-  validFiles.forEach((file) => {
-    formData.append(props.uploadFieldName, file.raw)
-  })
+  for (const file of validFiles) {
+    const uploadFileObj = await addWatermarkToFile(file.raw)
+    formData.append(props.uploadFieldName, uploadFileObj)
+  }
 
   uploading.value = true
   proxy.$modal.loading('鍥剧墖涓婁紶涓紝璇风◢鍊�...')
diff --git a/src/views/officeProcessAutomation/ApproveManage/approve-list/approveListConstants.js b/src/views/officeProcessAutomation/ApproveManage/approve-list/approveListConstants.js
index e2f1e45..9b29ff4 100644
--- a/src/views/officeProcessAutomation/ApproveManage/approve-list/approveListConstants.js
+++ b/src/views/officeProcessAutomation/ApproveManage/approve-list/approveListConstants.js
@@ -19,6 +19,7 @@
   { value: "quotation", label: "鎶ヤ环瀹℃壒", cellBg: "#f4ecfc", cellColor: "#9b59b6" },
   { value: "shipment", label: "鍙戣揣瀹℃壒", cellBg: "#e8faf6", cellColor: "#1abc9c" },
   { value: "enterprise_news", label: "浼佷笟鏂伴椈", cellBg: "#ecf5ff", cellColor: "#409eff" },
+  { value: "production_report", label: "鎶ュ伐瀹℃壒", cellBg: "#eef5ff", cellColor: "#1d4ed8" },
 ];
 
 /** 鍒楄〃鏌ヨ锛氬鎵圭姸鎬侊紙涓庡悗绔� status 鏋氫妇涓�鑷达級 */
diff --git a/src/views/officeProcessAutomation/ApproveManage/approve-list/components/ApproveDetailPanel.vue b/src/views/officeProcessAutomation/ApproveManage/approve-list/components/ApproveDetailPanel.vue
index 58b612b..77b37c2 100644
--- a/src/views/officeProcessAutomation/ApproveManage/approve-list/components/ApproveDetailPanel.vue
+++ b/src/views/officeProcessAutomation/ApproveManage/approve-list/components/ApproveDetailPanel.vue
@@ -36,7 +36,7 @@
     <div class="detail-block">
       <div class="detail-block-title">濉姤鍐呭</div>
       <!-- 榛樿琛ㄥ崟灞曠ず -->
-      <FormPayloadFields v-if="!isSpecialApprovalType"
+      <FormPayloadFields v-if="!isSpecialApprovalType && row.businessType !== 19"
                          :fields="formResolved.fields"
                          :form-payload="formResolved.formPayload"
                          readonly />
@@ -69,6 +69,23 @@
             </el-table>
           </div>
         </div>
+      </template>
+
+      <!-- 鎶ュ伐瀹℃壒璇︽儏 -->
+      <template v-else-if="row.businessType === 19">
+        <div v-if="formResolved.fields.length" class="report-detail">
+          <el-divider content-position="left">鎶ュ伐濉姤鍐呭</el-divider>
+          <el-descriptions :column="2" border>
+            <el-descriptions-item
+              v-for="field in formResolved.fields"
+              :key="field.key"
+              :label="field.label || field.key"
+            >
+              {{ formatFieldDisplayValue(field, formResolved.formPayload?.[field.key]) }}
+            </el-descriptions-item>
+          </el-descriptions>
+        </div>
+        <el-empty v-else description="鏆傛棤鎶ュ伐濉姤鍐呭" />
       </template>
       
       <!-- 閲囪喘瀹℃壒璇︽儏 -->
@@ -188,6 +205,7 @@
     approvalStatusLabel,
     approvalStatusTagType,
     resolveInstanceFormFields,
+    formatFieldDisplayValue,
   } from "../approveListConstants.js";
   import FormPayloadFields from "./FormPayloadFields.vue";
 
@@ -283,4 +301,4 @@
     align-items: center;
     flex-shrink: 0;
   }
-</style>
\ No newline at end of file
+</style>
diff --git a/src/views/officeProcessAutomation/ApproveManage/approve-list/useApproveList.js b/src/views/officeProcessAutomation/ApproveManage/approve-list/useApproveList.js
index 25b6ae5..2666741 100644
--- a/src/views/officeProcessAutomation/ApproveManage/approve-list/useApproveList.js
+++ b/src/views/officeProcessAutomation/ApproveManage/approve-list/useApproveList.js
@@ -323,6 +323,11 @@
         detailData.value = res?.data || res || {};
       }
     }
+
+    // 鎶ュ伐瀹℃壒锛氱洿鎺ュ睍绀哄鎵瑰~鎶ュ唴瀹�
+    else if (row.businessType === 19) {
+      detailData.value = {};
+    }
     
     // 鍏朵粬瀹℃壒绫诲瀷
     else {
@@ -367,6 +372,11 @@
         const res = await getDeliveryDetailByShippingNo({ shippingNo });
         detailData.value = res?.data || res || {};
       }
+    }
+
+    // 鎶ュ伐瀹℃壒锛氱洿鎺ュ睍绀哄鎵瑰~鎶ュ唴瀹�
+    else if (row.businessType === 19) {
+      detailData.value = {};
     }
 
     // 鍏朵粬瀹℃壒绫诲瀷
@@ -699,4 +709,4 @@
     openApprove,
     fetchApprovalList,
   };
-}
\ No newline at end of file
+}
diff --git a/src/views/officeProcessAutomation/ApproveManage/approve-shared/approvalModuleRegistry.js b/src/views/officeProcessAutomation/ApproveManage/approve-shared/approvalModuleRegistry.js
index fd56eca..45fad54 100644
--- a/src/views/officeProcessAutomation/ApproveManage/approve-shared/approvalModuleRegistry.js
+++ b/src/views/officeProcessAutomation/ApproveManage/approve-shared/approvalModuleRegistry.js
@@ -12,6 +12,7 @@
   TRAVEL_REIMBURSE: "travel_reimburse",
   COST_REIMBURSE: "cost_reimburse",
   ENTERPRISE_NEWS: "enterprise_news",
+  PRODUCTION_REPORT: "production_report",
 };
 
 /** 瀹℃壒瀹炰緥 listPage / 淇濆瓨 浣跨敤鐨� businessType 鏋氫妇 */
@@ -24,6 +25,7 @@
   [APPROVAL_MODULE_KEYS.TRAVEL_REIMBURSE]: 16,
   [APPROVAL_MODULE_KEYS.COST_REIMBURSE]: 17,
   [APPROVAL_MODULE_KEYS.ENTERPRISE_NEWS]: 18,
+  [APPROVAL_MODULE_KEYS.PRODUCTION_REPORT]: 19,
 };
 
 /** @type {Record<string, import('./approvalModuleRegistry.js').ApprovalModuleConfig>} */
@@ -81,6 +83,12 @@
     businessType: APPROVAL_BUSINESS_TYPE[APPROVAL_MODULE_KEYS.ENTERPRISE_NEWS],
     typeLabels: ["浼佷笟鏂伴椈", "鏂伴椈", "鏂伴椈鍙戝竷"],
   },
+  [APPROVAL_MODULE_KEYS.PRODUCTION_REPORT]: {
+    label: "鎶ュ伐瀹℃壒",
+    approvalType: "production_report",
+    businessType: APPROVAL_BUSINESS_TYPE[APPROVAL_MODULE_KEYS.PRODUCTION_REPORT],
+    typeLabels: ["鎶ュ伐", "鐢熶骇鎶ュ伐", "鎶ュ伐瀹℃壒"],
+  },
 };
 
 /**
diff --git a/src/views/productionManagement/productionCosting/index.vue b/src/views/productionManagement/productionCosting/index.vue
index 3e79b93..2ac2005 100644
--- a/src/views/productionManagement/productionCosting/index.vue
+++ b/src/views/productionManagement/productionCosting/index.vue
@@ -11,9 +11,29 @@
                 :xl="8"
                 class="left-col">
           <div class="left-panel">
+            <el-alert
+              title="宸ヨ祫鏍哥畻鍙e緞锛氫粎缁熻瀹℃牳閫氳繃鐨勫悎鏍兼姤宸ワ紱杞诲井杩斿伐銆佷弗閲嶈繑宸ャ�佹姤搴熷潎涓嶈钖��"
+              type="info"
+              :closable="false"
+              show-icon
+            />
             <div class="left-header">
               <el-form :model="searchForm"
                        inline>
+                <el-form-item label="鎶ュ伐浜哄憳锛�">
+                  <el-input v-model="searchForm.schedulingUserName"
+                            placeholder="璇疯緭鍏�"
+                            clearable
+                            style="width: 160px"
+                            @change="reloadData" />
+                </el-form-item>
+                <el-form-item label="宸ュ崟缂栧彿锛�">
+                  <el-input v-model="searchForm.workOrderNo"
+                            placeholder="璇疯緭鍏�"
+                            clearable
+                            style="width: 160px"
+                            @change="reloadData" />
+                </el-form-item>
                 <el-form-item prop="dateType">
                   <el-radio-group v-model="searchForm.dateType"
                                   size="small"
@@ -53,16 +73,17 @@
                 :xl="16"
                 class="right-col">
           <div class="right-panel">
-            <el-form inline>
-              <el-form-item>
-                <el-button type="primary"
-                           @click="handleOut">瀵煎嚭</el-button>
-              </el-form-item>
-            </el-form>
+            <div class="right-toolbar">
+              <el-button type="primary" @click="handleBatchAccounting">鎵归噺鏍哥畻</el-button>
+              <el-button type="warning" @click="handleSingleAdjust">鍗曚釜淇</el-button>
+              <el-button @click="handleOut">瀵煎嚭宸ヨ祫鏉�</el-button>
+            </div>
             <PIMTable rowKey="id"
                       :column="tableColumn"
                       :tableData="tableData"
                       :page="page1"
+                      :isSelection="true"
+                      @selection-change="handleSelectionChange"
                       :tableLoading="tableLoading1"
                       style="margin-right: 20px;"
                       @pagination="pagination1"></PIMTable>
@@ -70,11 +91,30 @@
         </el-col>
       </el-row>
     </div>
+    <el-dialog v-model="adjustDialogVisible"
+               title="鍗曚釜淇"
+               width="520px">
+      <el-form :model="adjustForm" label-width="120px">
+        <el-form-item label="鎶ュ伐浜哄憳">
+          <el-input v-model="adjustForm.schedulingUserName" disabled />
+        </el-form-item>
+        <el-form-item label="宸ヨ祫">
+          <el-input-number v-model="adjustForm.wages" :min="0" :precision="2" style="width: 100%" />
+        </el-form-item>
+        <el-form-item label="淇璇存槑">
+          <el-input v-model="adjustForm.remark" type="textarea" :rows="3" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="adjustDialogVisible = false">鍙栨秷</el-button>
+        <el-button type="primary" @click="confirmAdjust">纭</el-button>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
 <script setup>
-  import { onMounted, ref } from "vue";
+  import { onMounted, ref, reactive, toRefs, getCurrentInstance } from "vue";
   import { ElMessageBox } from "element-plus";
   import dayjs from "dayjs";
   import {
@@ -83,7 +123,18 @@
   } from "@/api/productionManagement/productionCosting.js";
   const { proxy } = getCurrentInstance();
 
+  const formatTag = (val, map) => {
+    const item = map[String(val)];
+    if (!item) return "-";
+    return item.label;
+  };
+
   const tableColumn = ref([
+    {
+      label: "鎶ュ伐鍗曞彿",
+      prop: "reportNo",
+      minWidth: 140,
+    },
     {
       label: "鐢熶骇鏃ユ湡",
       prop: "schedulingDate",
@@ -94,16 +145,51 @@
       prop: "schedulingUserName",
       minWidth: 100,
     },
-    // {
-    //   label: "鍚堝悓鍙�",
-    //   prop: "salesContractNo",
-    //   minWidth: 100,
-    // },
-    // {
-    //   label: "瀹㈡埛鍚嶇О",
-    //   prop: "customerName",
-    //   minWidth: 100,
-    // },
+    {
+      label: "瀹℃牳鐘舵��",
+      prop: "auditStatusName",
+      minWidth: 100,
+      dataType: "tag",
+      formatData: val => formatTag(val, {
+        0: { label: "寰呭鏍�" },
+        1: { label: "瀹℃牳閫氳繃" },
+        2: { label: "瀹℃牳涓嶉�氳繃" },
+      }),
+      formatType: val => {
+        const typeMap = {
+          0: "warning",
+          1: "success",
+          2: "danger",
+        };
+        return typeMap[String(val)] || "info";
+      },
+    },
+    {
+      label: "鎶ュ伐绫诲瀷",
+      prop: "reportTypeName",
+      minWidth: 100,
+      dataType: "tag",
+      formatData: val => formatTag(val, {
+        0: { label: "鍚堟牸" },
+        1: { label: "杞诲井杩斿伐" },
+        2: { label: "涓ラ噸杩斿伐" },
+        3: { label: "鎶ュ簾" },
+      }),
+      formatType: val => {
+        const typeMap = {
+          0: "success",
+          1: "warning",
+          2: "danger",
+          3: "info",
+        };
+        return typeMap[String(val)] || "info";
+      },
+    },
+    {
+      label: "閿�鍞悎鍚屽彿",
+      prop: "salesContractNo",
+      minWidth: 140,
+    },
     {
       label: "浜у搧澶х被",
       prop: "productName",
@@ -123,6 +209,11 @@
       label: "宸ュ簭",
       prop: "process",
       minWidth: 100,
+    },
+    {
+      label: "宸ュ崟缂栧彿",
+      prop: "workOrderNo",
+      minWidth: 140,
     },
     {
       label: "宸ユ椂锛坔锛�",
@@ -178,6 +269,14 @@
   const tableLoading = ref(false);
   const tableLoading1 = ref(false);
   const leftTableData = ref([]);
+  const selectedRows = ref([]);
+  const adjustDialogVisible = ref(false);
+  const adjustForm = reactive({
+    id: null,
+    schedulingUserName: "",
+    wages: 0,
+    remark: "",
+  });
   // 鏃� / 鏈� 鍒囨崲锛堥粯璁ゆ寜鏃ワ級
   const page = reactive({
     current: 1,
@@ -194,6 +293,7 @@
   const data = reactive({
     searchForm: {
       schedulingUserName: "",
+      workOrderNo: "",
       salesContractNo: "",
       dateType: "day",
       dateRange: dayjs().format("YYYY-MM-DD"),
@@ -214,6 +314,10 @@
     page1.current = obj.page;
     page1.size = obj.limit;
     getList1();
+  };
+
+  const handleSelectionChange = selection => {
+    selectedRows.value = selection || [];
   };
 
   const handleDateRangeChange = value => {
@@ -333,13 +437,44 @@
       .then(() => {
         proxy.download(
           "/salesLedger/productionAccounting/export",
-          {},
+          { ...searchForm.value },
           "鐢熶骇鏍哥畻.xlsx"
         );
       })
       .catch(() => {
         proxy.$modal.msg("宸插彇娑�");
       });
+  };
+
+  const handleBatchAccounting = () => {
+    proxy.$modal.msgWarning("褰撳墠椤甸潰宸叉寜瀹℃牳閫氳繃鐨勫悎鏍兼姤宸ユ眹鎬诲伐璧勶紝鎵归噺鏍哥畻鎺ュ彛鍚庣画鍙洿鎺ユ帴鍏ャ��");
+  };
+
+  const handleSingleAdjust = () => {
+    if (!selectedRows.value.length) {
+      proxy.$modal.msgWarning("璇峰厛鍕鹃�変竴鏉¤褰�");
+      return;
+    }
+    if (selectedRows.value.length > 1) {
+      proxy.$modal.msgWarning("鍗曚釜淇鍙兘閫夋嫨涓�鏉¤褰�");
+      return;
+    }
+    const row = selectedRows.value[0];
+    adjustForm.id = row.id;
+    adjustForm.schedulingUserName = row.schedulingUserName || "";
+    adjustForm.wages = Number(row.wages || 0);
+    adjustForm.remark = "";
+    adjustDialogVisible.value = true;
+  };
+
+  const confirmAdjust = () => {
+    proxy.$modal.msgSuccess("宸蹭繚瀛樹慨姝h鏄庯紝鍚庣画鍙帴鍏ュ伐璧勪慨姝f帴鍙c��");
+    adjustDialogVisible.value = false;
+  };
+
+  const handleRowAdjust = row => {
+    selectedRows.value = [row];
+    handleSingleAdjust();
   };
 
   onMounted(() => {
@@ -365,6 +500,12 @@
     min-width: 0;
   }
 
+  .right-toolbar {
+    display: flex;
+    justify-content: flex-end;
+    gap: 10px;
+  }
+
   .left-header {
     display: flex;
     align-items: center;
diff --git a/src/views/productionManagement/productionReporting/components/formDia.vue b/src/views/productionManagement/productionReporting/components/formDia.vue
index 15958e6..c565aa7 100644
--- a/src/views/productionManagement/productionReporting/components/formDia.vue
+++ b/src/views/productionManagement/productionReporting/components/formDia.vue
@@ -46,6 +46,16 @@
               <el-input v-model="form.totalPrice" placeholder="璇疯緭鍏�" clearable disabled/>
             </el-form-item>
           </el-col>
+          <el-col :span="12">
+            <el-form-item label="鎶ュ伐绫诲瀷锛�" prop="reportType">
+              <el-select v-model="form.reportType" style="width: 100%;" placeholder="璇烽�夋嫨">
+                <el-option label="鍚堟牸" :value="0" />
+                <el-option label="杞诲井杩斿伐" :value="1" />
+                <el-option label="涓ラ噸杩斿伐" :value="2" />
+                <el-option label="鎶ュ簾" :value="3" />
+              </el-select>
+            </el-form-item>
+          </el-col>
         </el-row>
         <el-row :gutter="30">
 					<el-col :span="12">
@@ -93,7 +103,7 @@
 </template>
 
 <script setup>
-import {ref} from "vue";
+import { getCurrentInstance, reactive, ref, toRefs } from "vue";
 // import {getStaffJoinInfo, staffJoinAdd, staffJoinUpdate} from "@/api/personnelManagement/onboarding.js";
 import {userListNoPageByTenantId} from "@/api/system/user.js";
 import {productionReport, productionReportUpdate} from "@/api/productionManagement/productionReporting.js";
@@ -110,8 +120,9 @@
 		finishedNum: "",
 		schedulingUserId: "",
 		schedulingDate: "",
-		unitPrice: "",
-		totalPrice: "",
+    unitPrice: "",
+    totalPrice: "",
+    reportType: 0,
   },
   rules: {
 		schedulingNum: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" },],
@@ -182,4 +193,4 @@
 
 <style scoped>
 
-</style>
\ No newline at end of file
+</style>
diff --git a/src/views/productionManagement/productionReporting/index.vue b/src/views/productionManagement/productionReporting/index.vue
index fa4e163..86b62d9 100644
--- a/src/views/productionManagement/productionReporting/index.vue
+++ b/src/views/productionManagement/productionReporting/index.vue
@@ -1,5 +1,6 @@
 <template>
   <div class="app-container">
+    <PageHeader content="鎶ュ伐鍙拌处" />
     <div class="search_form">
       <el-form :model="searchForm"
                :inline="true">
@@ -19,6 +20,18 @@
                     style="width: 200px;"
                     @change="handleQuery" />
         </el-form-item>
+        <el-form-item label="鎶ュ伐绫诲瀷:">
+          <el-select v-model="searchForm.reportType"
+                     placeholder="璇烽�夋嫨"
+                     clearable
+                     style="width: 200px;"
+                     @change="handleQuery">
+            <el-option label="鍚堟牸" :value="0" />
+            <el-option label="杞诲井杩斿伐" :value="1" />
+            <el-option label="涓ラ噸杩斿伐" :value="2" />
+            <el-option label="鎶ュ簾" :value="3" />
+          </el-select>
+        </el-form-item>
         <el-form-item>
           <el-button type="primary"
                      @click="handleQuery">鎼滅储</el-button>
@@ -28,8 +41,6 @@
     <div class="table_list">
       <div style="text-align: right"
            class="mb10">
-        <!-- <el-button type="primary"
-                   @click="openForm('add')">鐢熶骇鎶ュ伐</el-button> -->
         <el-button @click="handleOut">瀵煎嚭</el-button>
       </div>
       <PIMTable rowKey="id"
@@ -169,6 +180,7 @@
       nickName: "",
       workOrderNo: "",
       workOrderStatus: "",
+      reportType: "",
     },
   });
   const { searchForm } = toRefs(data);
@@ -185,6 +197,16 @@
       label: "鎶ュ伐浜哄憳",
       prop: "nickName",
       width: 120,
+    },
+    {
+      label: "鎶ュ伐绫诲瀷",
+      prop: "reportTypeName",
+      width: 100,
+    },
+    {
+      label: "瀹℃牳鐘舵��",
+      prop: "auditStatusName",
+      width: 100,
     },
     {
       label: "宸ユ椂锛坔锛�",
@@ -321,6 +343,10 @@
       });
     });
   };
+  const showInput = row => {
+    isShowInput.value = true;
+    isShowingId.value = row.id;
+  };
   const pagination = obj => {
     page.current = obj.page;
     page.size = obj.limit;
@@ -336,6 +362,8 @@
         tableLoading.value = false;
         tableData.value = res.data.records.map(item => ({
           ...item,
+          reportTypeName: item.reportTypeName || resolveReportTypeName(item.reportType),
+          auditStatusName: item.auditStatusName || resolveAuditStatusName(item.auditStatus),
           pendingFinishNum:
             (Number(item.schedulingNum) || 0) - (Number(item.finishedNum) || 0),
         }));
@@ -418,6 +446,23 @@
   const summarizeMainTable = param => {
     return proxy.summarizeTable(param, ["finishedNum"]);
   };
+  const resolveReportTypeName = value => {
+    const map = {
+      0: "鍚堟牸",
+      1: "杞诲井杩斿伐",
+      2: "涓ラ噸杩斿伐",
+      3: "鎶ュ簾",
+    };
+    return map[Number(value)] || "鈥�";
+  };
+  const resolveAuditStatusName = value => {
+    const map = {
+      0: "寰呭鏍�",
+      1: "瀹℃牳閫氳繃",
+      2: "瀹℃牳涓嶉�氳繃",
+    };
+    return map[Number(value)] || "鈥�";
+  };
   // 鎵撳紑寮规
   const openForm = (type, row) => {
     if (selectedRows.value.length !== 1) {
@@ -437,10 +482,6 @@
   // 鎵撳紑鎶曞叆妯℃�佹
   const isShowInput = ref(false);
   const isShowingId = ref(0);
-  const showInput = row => {
-    isShowInput.value = true;
-    isShowingId.value = row.id;
-  };
 
   // 瀵煎嚭
   const handleOut = () => {
@@ -450,7 +491,9 @@
       type: "warning",
     })
       .then(() => {
-        proxy.download("/productionProductMain/export", {}, "鐢熶骇鎶ュ伐.xlsx");
+        const params = { ...searchForm.value };
+        params.entryDate = undefined;
+        proxy.download("/productionProductMain/export", params, "鐢熶骇鎶ュ伐.xlsx");
       })
       .catch(() => {
         proxy.$modal.msg("宸插彇娑�");
diff --git a/src/views/productionManagement/productionReportingEntry/index.vue b/src/views/productionManagement/productionReportingEntry/index.vue
new file mode 100644
index 0000000..99d3467
--- /dev/null
+++ b/src/views/productionManagement/productionReportingEntry/index.vue
@@ -0,0 +1,192 @@
+<template>
+  <div class="app-container">
+    <PageHeader content="鐢熶骇鎶ュ伐" />
+    <div class="search_form mb20">
+      <div class="search-row">
+        <div class="search-item">
+          <span class="search_title">鎶ュ伐鍗曞彿锛�</span>
+          <el-input v-model="searchForm.productNo"
+                    style="width: 220px"
+                    placeholder="璇疯緭鍏�"
+                    clearable
+                    prefix-icon="Search"
+                    @change="handleQuery" />
+        </div>
+        <div class="search-item">
+          <span class="search_title">宸ュ崟缂栧彿锛�</span>
+          <el-input v-model="searchForm.workOrderNo"
+                    style="width: 220px"
+                    placeholder="璇疯緭鍏�"
+                    clearable
+                    prefix-icon="Search"
+                    @change="handleQuery" />
+        </div>
+        <div class="search-item">
+          <span class="search_title">鎶ュ伐浜哄憳锛�</span>
+          <el-input v-model="searchForm.nickName"
+                    style="width: 220px"
+                    placeholder="璇疯緭鍏�"
+                    clearable
+                    prefix-icon="Search"
+                    @change="handleQuery" />
+        </div>
+        <div class="search-item">
+          <span class="search_title">鎶ュ伐绫诲瀷锛�</span>
+          <el-select v-model="searchForm.reportType"
+                     style="width: 180px"
+                     clearable
+                     placeholder="璇烽�夋嫨"
+                     @change="handleQuery">
+            <el-option label="鍚堟牸" :value="0" />
+            <el-option label="杞诲井杩斿伐" :value="1" />
+            <el-option label="涓ラ噸杩斿伐" :value="2" />
+            <el-option label="鎶ュ簾" :value="3" />
+          </el-select>
+        </div>
+        <div class="search-item">
+          <el-button type="primary" @click="handleQuery">鎼滅储</el-button>
+        </div>
+      </div>
+    </div>
+
+    <div class="table_list">
+      <PIMTable rowKey="id"
+                :column="tableColumn"
+                :tableData="tableData"
+                :page="page"
+                :tableLoading="tableLoading"
+                @pagination="pagination"
+                :total="page.total" />
+    </div>
+
+    <input-modal v-if="isShowInput"
+                 v-model:visible="isShowInput"
+                 :production-product-main-id="isShowingId" />
+
+  </div>
+</template>
+
+<script setup>
+import { onMounted, reactive, ref, toRefs } from "vue";
+import { productionProductMainListPage } from "@/api/productionManagement/productionProductMain.js";
+import InputModal from "@/views/productionManagement/productionReporting/Input.vue";
+
+const tableLoading = ref(false);
+const tableData = ref([]);
+const page = reactive({
+  current: 1,
+  size: 10,
+  total: 0,
+});
+
+const data = reactive({
+  searchForm: {
+    productNo: "",
+    workOrderNo: "",
+    nickName: "",
+    reportType: "",
+  },
+});
+const { searchForm } = toRefs(data);
+
+const tableColumn = [
+  { label: "鎶ュ伐鍗曞彿", prop: "productNo", width: 140 },
+  { label: "鎶ュ伐浜哄憳", prop: "nickName", width: 120 },
+  { label: "宸ュ簭", prop: "process", width: 140 },
+  { label: "宸ュ崟缂栧彿", prop: "workOrderNo", width: 140 },
+  { label: "浜у嚭鏁伴噺", prop: "quantity", width: 120 },
+  { label: "鎶ュ簾鏁伴噺", prop: "scrapQty", width: 120 },
+  { label: "宸ユ椂锛坔锛�", prop: "workHour", width: 100 },
+  { label: "鎶ュ伐缁撴灉", prop: "reportTypeName", width: 100 },
+  { label: "瀹℃牳鐘舵��", prop: "auditStatusName", width: 100 },
+  { label: "鍒涘缓鏃堕棿", prop: "createTime", width: 160 },
+  {
+    label: "鎿嶄綔",
+    dataType: "action",
+    width: 120,
+    fixed: "right",
+    operation: [
+      {
+        name: "鏌ョ湅鎶曞叆",
+        type: "text",
+        clickFun: (row) => {
+          showInput(row);
+        },
+      },
+    ],
+  },
+];
+
+const isShowInput = ref(false);
+const isShowingId = ref(0);
+
+const handleQuery = () => {
+  page.current = 1;
+  getList();
+};
+
+const pagination = (obj) => {
+  page.current = obj.page;
+  page.size = obj.limit;
+  getList();
+};
+
+const getList = () => {
+  tableLoading.value = true;
+  productionProductMainListPage({ ...searchForm.value, ...page })
+    .then((res) => {
+      tableData.value = (res.data.records || []).map((item) => ({
+        ...item,
+        reportTypeName: item.reportTypeName || resolveReportTypeName(item.reportType),
+        auditStatusName: item.auditStatusName || resolveAuditStatusName(item.auditStatus),
+      }));
+      page.total = res.data.total || 0;
+    })
+    .finally(() => {
+      tableLoading.value = false;
+    });
+};
+
+const resolveReportTypeName = (val) => {
+  const map = {
+    0: "鍚堟牸",
+    1: "杞诲井杩斿伐",
+    2: "涓ラ噸杩斿伐",
+    3: "鎶ュ簾",
+  };
+  return map[Number(val)] || "鈥�";
+};
+
+const resolveAuditStatusName = (val) => {
+  const map = {
+    0: "寰呭鏍�",
+    1: "瀹℃牳閫氳繃",
+    2: "瀹℃牳涓嶉�氳繃",
+  };
+  return map[Number(val)] || "鈥�";
+};
+
+const showInput = (row) => {
+  isShowingId.value = row.id;
+  isShowInput.value = true;
+};
+
+onMounted(() => {
+  getList();
+});
+</script>
+
+<style scoped>
+.search-row {
+  display: flex;
+  align-items: center;
+  flex-wrap: wrap;
+  gap: 12px 16px;
+}
+
+.search-item {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+</style>
diff --git a/src/views/productionManagement/workOrderManagement/index.vue b/src/views/productionManagement/workOrderManagement/index.vue
index b721de6..4012821 100644
--- a/src/views/productionManagement/workOrderManagement/index.vue
+++ b/src/views/productionManagement/workOrderManagement/index.vue
@@ -40,79 +40,16 @@
         </template>
       </PIMTable>
     </div>
-    <!-- 娴佽浆鍗″脊绐� -->
+    <!-- 浜岀淮鐮佸脊绐� -->
     <el-dialog v-model="transferCardVisible"
-               title="娴佽浆鍗�"
-               width="1000px">
-      <div class="transfer-card-title">宸ュ崟娴佽浆鍗�</div>
-      <div class="transfer-card-container">
-        <div class="transfer-card-info">
-          <div class="info-group">
-            <div class="info-item">
-              <span class="info-label">宸ュ崟缂栧彿</span>
-              <span class="info-value">{{ transferCardRowData.workOrderNo }}</span>
-            </div>
-            <div class="info-item">
-              <span class="info-label">浜у搧鍚嶇О</span>
-              <span class="info-value">{{ transferCardRowData.productName }}</span>
-            </div>
-            <div class="info-item">
-              <span class="info-label">瑙勬牸鍨嬪彿</span>
-              <span class="info-value">{{ transferCardRowData.model }}</span>
-            </div>
-            <div class="info-item">
-              <span class="info-label">璁″垝寮�濮嬫椂闂�</span>
-              <span class="info-value">{{ transferCardRowData.planStartTime }}</span>
-            </div>
-            <div class="info-item">
-              <span class="info-label">璁″垝缁撴潫鏃堕棿</span>
-              <span class="info-value">{{ transferCardRowData.planEndTime }}</span>
-            </div>
-            <div class="info-item">
-              <span class="info-label">澶囨敞</span>
-              <span class="info-value">{{ transferCardRowData.remark }}</span>
-            </div>
-          </div>
-          <div class="info-group">
-            <div class="info-item">
-              <span class="info-label">闇�姹傛暟閲�</span>
-              <span class="info-value">{{ transferCardRowData.planQuantity }}</span>
-            </div>
-            <div class="info-item">
-              <span class="info-value">{{ transferCardRowData.completeQuantity }}</span>
-            </div>
-            <div class="info-item">
-              <span class="info-label">鑹搧鏁伴噺</span>
-              <span class="info-value">0</span>
-            </div>
-            <div class="info-item">
-              <span class="info-label">涓嶈壇鍝佹暟</span>
-              <span class="info-value">0</span>
-            </div>
-            <div class="info-item">
-              <span class="info-label">瀹為檯寮�濮嬫椂闂�</span>
-              <span class="info-value">{{ transferCardRowData.actualStartTime }}</span>
-            </div>
-            <div class="info-item">
-              <span class="info-label">瀹為檯缁撴潫鏃堕棿</span>
-              <span class="info-value">{{ transferCardRowData.actualEndTime }}</span>
-            </div>
-          </div>
-        </div>
-        <div class="transfer-card-qr">
-          <div class="qr-container">
-            <img :src="transferCardQrUrl"
-                 alt="娴佽浆鍗′簩缁寸爜"
-                 style="width: 200px; height: 200px;" />
-          </div>
-        </div>
-      </div>
-      <div class="print-button-container"
-           style=" text-align: center;
-      margin-bottom: 40px;">
-        <el-button type="primary"
-                   style="margin-top: 20px;"
-                   @click="printTransferCard">鎵撳嵃娴佽浆鍗�</el-button>
+               title="浜岀淮鐮�"
+               width="420px"
+               align-center>
+      <div class="qr-only-dialog">
+        <canvas ref="transferCardQrCanvas"
+                width="280"
+                height="280"
+                class="qr-canvas" />
       </div>
     </el-dialog>
     <!-- 鎶ュ伐寮圭獥 -->
@@ -147,6 +84,17 @@
                     style="width: 300px"
                     placeholder="璇疯緭鍏ユ姤搴熸暟閲�"
                     @input="handleScrapQtyInput" />
+        </el-form-item>
+        <el-form-item label="鎶ュ伐绫诲瀷"
+                      prop="reportType">
+          <el-select v-model="reportForm.reportType"
+                     style="width: 300px"
+                     placeholder="璇烽�夋嫨鎶ュ伐绫诲瀷">
+            <el-option label="鍚堟牸" :value="0" />
+            <el-option label="杞诲井杩斿伐" :value="1" />
+            <el-option label="涓ラ噸杩斿伐" :value="2" />
+            <el-option label="鎶ュ簾" :value="3" />
+          </el-select>
         </el-form-item>
         <el-form-item label="鐝粍淇℃伅">
           <el-select v-model="reportForm.userId"
@@ -358,7 +306,7 @@
     },
     {
       label: "鎿嶄綔",
-      width: "260",
+      width: "320",
       align: "center",
       dataType: "action",
       fixed: "right",
@@ -367,6 +315,12 @@
           name: "娴佽浆鍗�",
           clickFun: row => {
             downloadAndPrintWorkOrder(row);
+          },
+        },
+        {
+          name: "浜岀淮鐮�",
+          clickFun: row => {
+            showTransferCard(row);
           },
         },
         {
@@ -411,7 +365,7 @@
   const tableLoading = ref(false);
   const transferCardVisible = ref(false);
   const transferCardData = ref([]);
-  const transferCardQrUrl = ref("");
+  const transferCardQrCanvas = ref(null);
   const transferCardRowData = ref(null);
   const reportDialogVisible = ref(false);
   const fileDialogVisible = ref(false);
@@ -422,6 +376,7 @@
     planQuantity: 0,
     quantity: null,
     scrapQty: null,
+    reportType: 0,
     userName: "",
     workOrderId: "",
     reportWork: "",
@@ -640,15 +595,20 @@
 
   const showTransferCard = async row => {
     transferCardRowData.value = row;
-    const qrContent = String(row.id);
-
-    transferCardQrUrl.value = await QRCode.toDataURL(qrContent);
     transferCardVisible.value = true;
+    await nextTick();
+    const qrContent = String(row.id);
+    if (transferCardQrCanvas.value) {
+      const ctx = transferCardQrCanvas.value.getContext("2d");
+      ctx?.clearRect(0, 0, 280, 280);
+      await QRCode.toCanvas(transferCardQrCanvas.value, qrContent, {
+        width: 280,
+        margin: 1,
+        errorCorrectionLevel: "M",
+      });
+    }
   };
 
-  const printTransferCard = () => {
-    window.print();
-  };
   const currentWorkOrderRow = ref(null);
 
   const openWorkOrderFiles = row => {
@@ -679,6 +639,7 @@
     reportForm.planQuantity = remainingQuantity;
     reportForm.quantity =
       row.quantity !== undefined && row.quantity !== null ? row.quantity : null;
+    reportForm.reportType = row.reportType ?? 0;
     reportForm.productProcessRouteItemId = row.productProcessRouteItemId;
     reportForm.workOrderId = row.id;
     reportForm.reportWork = row.reportWork;
@@ -783,6 +744,7 @@
       const submitParams = {
         quantity: quantity,
         scrapQty: isNaN(scrapQty) ? 0 : scrapQty,
+        reportType: reportForm.reportType,
         userId: reportForm.userId,
         userName: reportForm.userName,
         productionOperationTaskId: reportForm.workOrderId,
@@ -931,6 +893,13 @@
     text-align: center;
     margin-top: 20px;
   }
+  .qr-only-dialog {
+    padding: 12px 0 28px;
+  }
+  .qr-canvas {
+    display: block;
+    margin: 0 auto;
+  }
   .param-grid {
     margin-top: 10px;
     border-top: 1px solid #ebe9f3;
diff --git a/src/views/qualityManagement/nonconformingManagement/components/formDia.vue b/src/views/qualityManagement/nonconformingManagement/components/formDia.vue
index 5e20709..9263626 100644
--- a/src/views/qualityManagement/nonconformingManagement/components/formDia.vue
+++ b/src/views/qualityManagement/nonconformingManagement/components/formDia.vue
@@ -132,6 +132,45 @@
         </el-row>
         <el-row :gutter="30">
           <el-col :span="12">
+            <el-form-item label="涓嶅悎鏍肩被鍨嬶細"
+                          prop="dealType">
+              <el-select v-model="form.dealType"
+                         placeholder="璇烽�夋嫨"
+                         clearable
+                         style="width: 100%">
+                <el-option label="杞诲井杩斿伐" :value="0" />
+                <el-option label="涓ラ噸杩斿伐" :value="1" />
+                <el-option label="鎶ュ簾" :value="2" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="淇悊宸ユ椂锛�"
+                          prop="repairWorkHour">
+              <el-input-number v-model="form.repairWorkHour"
+                               :min="0"
+                               :precision="2"
+                               style="width: 100%"
+                               :disabled="form.dealType !== 0" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="30">
+          <el-col :span="12">
+            <el-form-item label="閲嶆柊鐢熸垚鏂板崟锛�"
+                          prop="regenerateNewOrder">
+              <el-select v-model="form.regenerateNewOrder"
+                         placeholder="璇烽�夋嫨"
+                         clearable
+                         style="width: 100%">
+                <el-option label="鍚�" :value="0" />
+                <el-option label="鏄�" :value="1" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="30">
+          <el-col :span="12">
             <el-form-item label="澶勭悊浜猴細"
                           prop="dealName">
               <el-select v-model="form.dealName"
@@ -158,6 +197,19 @@
             </el-form-item>
           </el-col>
         </el-row>
+        <el-row :gutter="30">
+          <el-col :span="24">
+            <el-form-item label="涓嶅悎鏍煎浘鐗囷細"
+                          prop="photoUrls">
+              <AttachmentUploadImage
+                v-model:fileList="form.photoUrls"
+                :limit="6"
+                button-text="鎷嶇収/涓婁紶鍥剧墖"
+                :watermark-text="watermarkText"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
       </el-form>
       <template #footer>
         <div class="dialog-footer">
@@ -171,8 +223,9 @@
 </template>
 
 <script setup>
-  import { ref, reactive, toRefs } from "vue";
+  import { computed, getCurrentInstance, ref, reactive, toRefs, watch } from "vue";
   import { modelList, productTreeList } from "@/api/basicData/product.js";
+  import AttachmentUploadImage from "@/components/AttachmentUpload/image/index.vue";
   import {
     getQualityUnqualifiedInfo,
     qualityUnqualifiedAdd,
@@ -202,6 +255,9 @@
       inspectType: "",
       defectivePhenomena: "",
       dealResult: "",
+      dealType: 0,
+      repairWorkHour: 0,
+      regenerateNewOrder: 0,
       dealName: "",
       dealTime: "",
       productModelId: undefined,
@@ -223,6 +279,17 @@
   const productOptions = ref([]);
   const modelOptions = ref([]);
   const userList = ref([]); // 妫�楠屽憳/澶勭悊浜轰笅鎷夊垪琛�
+  const watermarkText = computed(() => {
+    const userName = userStore.nickName || userStore.user?.nickName || "鏈煡浜哄憳";
+    const now = new Date();
+    const pad = (n) => String(n).padStart(2, "0");
+    return `涓嶅悎鏍煎鐞哱n${userName}\n${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`;
+  });
+  watch(() => form.value.dealType, (val) => {
+    if (val !== 0) {
+      form.value.repairWorkHour = 0;
+    }
+  });
 
   // 鎵撳紑寮规
   const openDialog = async (type, row) => {
@@ -241,6 +308,10 @@
         dealName: "",
         dealTime: "",
         dealResult: "",
+        dealType: 0,
+        repairWorkHour: 0,
+        regenerateNewOrder: 0,
+        photoUrls: [],
         defectivePhenomena: "",
         inspectType: "",
         checkTime: "",
@@ -258,7 +329,15 @@
     if (operationType.value === "edit") {
       getQualityUnqualifiedInfo(row.id).then(res => {
         const { inspectState, ...rest } = res.data || {};
-        form.value = { ...rest };
+        form.value = {
+          ...rest,
+          photoUrls: rest.photoUrls
+            ? String(rest.photoUrls).split(",").filter(Boolean).map((url, index) => ({
+              name: `image-${index + 1}`,
+              url,
+            }))
+            : [],
+        };
       });
     }
   };
@@ -313,6 +392,13 @@
       if (valid) {
         // 鐘舵�佸瓧娈典笉鍦ㄨ〃鍗曞~鍐欙紝涔熶笉浼犵粰鍚庣
         const { inspectState, ...payload } = form.value || {};
+        if (payload.dealType === 0 && (!payload.repairWorkHour || payload.repairWorkHour <= 0)) {
+          proxy.$modal.msgError("杞诲井杩斿伐蹇呴』濉啓澶т簬0鐨勪慨鐞嗗伐鏃�");
+          return;
+        }
+        payload.photoUrls = Array.isArray(payload.photoUrls)
+          ? payload.photoUrls.map(item => item.url || item.previewURL || item.previewUrl || item).filter(Boolean).join(",")
+          : payload.photoUrls;
         if (operationType.value === "add") {
           qualityUnqualifiedAdd(payload).then(res => {
             proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
diff --git a/src/views/qualityManagement/nonconformingManagement/index.vue b/src/views/qualityManagement/nonconformingManagement/index.vue
index fc9d5d2..ea9a2cb 100644
--- a/src/views/qualityManagement/nonconformingManagement/index.vue
+++ b/src/views/qualityManagement/nonconformingManagement/index.vue
@@ -163,6 +163,28 @@
     width: 120
   },
   {
+    label: "涓嶅悎鏍肩被鍨�",
+    prop: "dealType",
+    width: 120,
+    formatData: val => {
+      if (val === 0) return "杞诲井杩斿伐";
+      if (val === 1) return "涓ラ噸杩斿伐";
+      if (val === 2) return "鎶ュ簾";
+      return "-";
+    }
+  },
+  {
+    label: "淇悊宸ユ椂",
+    prop: "repairWorkHour",
+    width: 100
+  },
+  {
+    label: "閲嶇敓鏂板崟",
+    prop: "regenerateNewOrder",
+    width: 100,
+    formatData: val => (val === 1 ? "鏄�" : "鍚�")
+  },
+  {
     label: "澶勭悊浜�",
     prop: "dealName",
     width: 120

--
Gitblit v1.9.3