From 6361501810a76b6809162cac99b0d9c1faba3715 Mon Sep 17 00:00:00 2001
From: spring <2396852758@qq.com>
Date: 星期四, 16 四月 2026 15:10:02 +0800
Subject: [PATCH] fix: 对退料请求做限制

---
 src/views/productionManagement/productionOrder/components/MaterialDetailDialog.vue |  264 ++++++++++
 src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue |  241 +++++++++
 src/views/productionManagement/workOrderManagement/components/MaterialDialog.vue   |  296 +++++++++++
 src/views/productionManagement/productionOrder/index.vue                           |  385 ---------------
 src/views/productionManagement/workOrderManagement/index.vue                       |  297 -----------
 5 files changed, 818 insertions(+), 665 deletions(-)

diff --git a/src/views/productionManagement/productionOrder/components/MaterialDetailDialog.vue b/src/views/productionManagement/productionOrder/components/MaterialDetailDialog.vue
new file mode 100644
index 0000000..f93199a
--- /dev/null
+++ b/src/views/productionManagement/productionOrder/components/MaterialDetailDialog.vue
@@ -0,0 +1,264 @@
+<template>
+  <div>
+    <el-dialog v-model="dialogVisible" title="棰嗘枡璇︽儏" width="1400px" @close="handleClose">
+      <el-table v-loading="materialDetailLoading" :data="materialDetailTableData" border row-key="id">
+        <el-table-column label="宸ュ簭鍚嶇О" prop="processName" min-width="180" />
+        <el-table-column label="鍘熸枡鍚嶇О" prop="materialName" min-width="160" />
+        <el-table-column label="鍘熸枡鍨嬪彿" prop="materialModel" min-width="180" />
+        <el-table-column label="闇�姹傛暟閲�" prop="requiredQty" min-width="110" />
+        <el-table-column label="璁¢噺鍗曚綅" prop="unit" width="100" />
+        <el-table-column label="棰嗙敤鏁伴噺" prop="pickQty" min-width="110" />
+        <el-table-column label="琛ユ枡鏁伴噺" min-width="120">
+          <template #default="{ row }">
+            <el-button type="primary" link @click="handleViewSupplementRecord(row)">
+              {{ row.supplementQty ?? 0 }}
+            </el-button>
+          </template>
+        </el-table-column>
+        <el-table-column label="閫�鏂欐暟閲�" prop="returnQty" min-width="110" />
+        <el-table-column label="瀹為檯鏁伴噺" prop="actualQty" min-width="110" />
+      </el-table>
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button
+            type="warning"
+            :loading="materialReturnConfirming"
+            :disabled="!canOpenReturnSummary"
+            @click="openReturnSummaryDialog"
+          >
+            閫�鏂欑‘璁�
+          </el-button>
+          <el-button @click="dialogVisible = false">鍙栨秷</el-button>
+        </span>
+      </template>
+    </el-dialog>
+
+    <el-dialog v-model="supplementRecordDialogVisible" title="琛ユ枡璁板綍" width="800px">
+      <el-table v-loading="supplementRecordLoading" :data="supplementRecordTableData" border row-key="id">
+        <el-table-column label="琛ユ枡鏁伴噺" prop="supplementQty" min-width="120" />
+        <el-table-column label="琛ユ枡鏃堕棿" prop="supplementTime" min-width="180" />
+        <el-table-column label="澶囨敞" prop="remark" min-width="200" />
+      </el-table>
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button @click="supplementRecordDialogVisible = false">鍏抽棴</el-button>
+        </span>
+      </template>
+    </el-dialog>
+
+    <el-dialog v-model="returnSummaryDialogVisible" title="閫�鏂欐眹鎬荤‘璁�" width="900px">
+      <el-table :data="returnSummaryList" border row-key="summaryKey">
+        <el-table-column label="鍘熸枡鍚嶇О" prop="materialName" min-width="180" />
+        <el-table-column label="鍘熸枡鍨嬪彿" prop="materialModel" min-width="180" />
+        <el-table-column label="璁¢噺鍗曚綅" prop="unit" min-width="100" />
+        <el-table-column label="閫�鏂欐眹鎬绘暟閲�" prop="returnQtyTotal" min-width="140" />
+      </el-table>
+
+      <el-card class="approver-card" shadow="never">
+        <template #header>
+          <div class="card-header-wrapper">
+            <span class="card-title">瀹℃壒浜洪�夋嫨</span>
+            <el-button type="primary" size="small" @click="addApproverNode">鏂板鑺傜偣</el-button>
+          </div>
+        </template>
+        <div class="approver-nodes-container">
+          <div v-for="(node, index) in approverNodes" :key="node.id" class="approver-node-item">
+            <div class="approver-node-label">
+              <span class="node-step">{{ index + 1 }}</span>
+              <span class="node-text">瀹℃壒浜�</span>
+            </div>
+            <el-select v-model="node.userId" placeholder="閫夋嫨浜哄憳" class="approver-select" clearable>
+              <el-option v-for="user in userList" :key="user.userId" :label="user.nickName" :value="user.userId" />
+            </el-select>
+            <el-button v-if="approverNodes.length > 1" type="danger" size="small" @click="removeApproverNode(index)">
+              鍒犻櫎
+            </el-button>
+          </div>
+        </div>
+      </el-card>
+
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button type="primary" :loading="materialReturnConfirming" @click="handleReturnConfirm">纭鎻愪氦</el-button>
+          <el-button @click="returnSummaryDialogVisible = false">鍙栨秷</el-button>
+        </span>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { computed, ref, watch } from "vue";
+import { ElMessage } from "element-plus";
+import { listMaterialPickingDetail, listMaterialSupplementRecord, confirmMaterialReturn } from "@/api/productionManagement/productionOrder.js";
+import { userListNoPageByTenantId } from "@/api/system/user.js";
+
+const props = defineProps({
+  modelValue: { type: Boolean, default: false },
+  orderRow: { type: Object, default: null },
+});
+const emit = defineEmits(["update:modelValue", "confirmed"]);
+
+const dialogVisible = computed({
+  get: () => props.modelValue,
+  set: val => emit("update:modelValue", val),
+});
+
+const materialDetailLoading = ref(false);
+const materialDetailTableData = ref([]);
+const materialReturnConfirming = ref(false);
+const supplementRecordDialogVisible = ref(false);
+const supplementRecordLoading = ref(false);
+const supplementRecordTableData = ref([]);
+const returnSummaryDialogVisible = ref(false);
+const returnSummaryList = ref([]);
+const userList = ref([]);
+const approverNodes = ref([{ id: Date.now(), userId: undefined }]);
+const canOpenReturnSummary = computed(() =>
+  materialDetailTableData.value.some(item => Number(item.returnQty || 0) > 0)
+);
+
+const loadDetailList = async () => {
+  if (!props.orderRow?.id) return;
+  materialDetailLoading.value = true;
+  materialDetailTableData.value = [];
+  try {
+    const res = await listMaterialPickingDetail({ orderId: props.orderRow.id });
+    materialDetailTableData.value = res.data || [];
+  } finally {
+    materialDetailLoading.value = false;
+  }
+};
+
+watch(
+  () => dialogVisible.value,
+  visible => {
+    if (visible) {
+      loadDetailList();
+    }
+  }
+);
+
+const handleClose = () => {
+  materialDetailTableData.value = [];
+};
+
+const handleViewSupplementRecord = async row => {
+  if (!row?.id) return;
+  supplementRecordDialogVisible.value = true;
+  supplementRecordLoading.value = true;
+  supplementRecordTableData.value = [];
+  try {
+    const res = await listMaterialSupplementRecord({ materialDetailId: row.id });
+    supplementRecordTableData.value = res.data || [];
+  } finally {
+    supplementRecordLoading.value = false;
+  }
+};
+
+const buildReturnSummary = () => {
+  const map = new Map();
+  materialDetailTableData.value.forEach(item => {
+    const key = `${item.materialModelId || ""}_${item.materialName || ""}_${item.materialModel || ""}_${item.unit || ""}`;
+    const old = map.get(key) || {
+      summaryKey: key,
+      materialName: item.materialName || "",
+      materialModel: item.materialModel || "",
+      unit: item.unit || "",
+      returnQtyTotal: 0,
+    };
+    old.returnQtyTotal += Number(item.returnQty || 0);
+    map.set(key, old);
+  });
+  return Array.from(map.values());
+};
+
+const loadUserList = async () => {
+  if (userList.value.length > 0) return;
+  const res = await userListNoPageByTenantId();
+  userList.value = res.data || [];
+};
+
+const openReturnSummaryDialog = async () => {
+  if (!canOpenReturnSummary.value) {
+    ElMessage.warning("閫�鏂欐暟閲忓ぇ浜�0鏃舵墠鑳介��鏂欑‘璁�");
+    return;
+  }
+  returnSummaryList.value = buildReturnSummary();
+  approverNodes.value = [{ id: Date.now(), userId: undefined }];
+  await loadUserList();
+  returnSummaryDialogVisible.value = true;
+};
+
+const addApproverNode = () => {
+  approverNodes.value.push({ id: Date.now() + Math.random(), userId: undefined });
+};
+
+const removeApproverNode = index => {
+  approverNodes.value.splice(index, 1);
+};
+
+const handleReturnConfirm = async () => {
+  if (!props.orderRow?.id) return;
+  const approverList = approverNodes.value
+    .filter(item => item.userId)
+    .map((item, index) => ({ userId: item.userId, sort: index + 1 }));
+  if (approverList.length === 0) {
+    ElMessage.warning("璇疯嚦灏戦�夋嫨涓�浣嶅鎵逛汉");
+    return;
+  }
+  materialReturnConfirming.value = true;
+  try {
+    await confirmMaterialReturn({
+      orderId: props.orderRow.id,
+      returnSummaryList: returnSummaryList.value,
+      approverList,
+    });
+    returnSummaryDialogVisible.value = false;
+    dialogVisible.value = false;
+    emit("confirmed");
+  } finally {
+    materialReturnConfirming.value = false;
+  }
+};
+</script>
+
+<style scoped lang="scss">
+.approver-card {
+  margin-top: 12px;
+}
+.card-header-wrapper {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+}
+.approver-nodes-container {
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+}
+.approver-node-item {
+  display: flex;
+  gap: 8px;
+  align-items: center;
+}
+.approver-node-label {
+  display: flex;
+  gap: 4px;
+  min-width: 88px;
+  align-items: center;
+}
+.node-step {
+  width: 20px;
+  height: 20px;
+  line-height: 20px;
+  text-align: center;
+  border-radius: 50%;
+  background: #409eff;
+  color: #fff;
+  font-size: 12px;
+}
+.approver-select {
+  flex: 1;
+}
+</style>
diff --git a/src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue b/src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue
new file mode 100644
index 0000000..f9db112
--- /dev/null
+++ b/src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue
@@ -0,0 +1,241 @@
+<template>
+  <div>
+    <el-dialog v-model="dialogVisible" title="棰嗘枡鍙拌处" width="1200px" @close="handleClose">
+      <div class="material-toolbar">
+        <el-button type="primary" @click="handleAddMaterialRow">鏂板</el-button>
+      </div>
+      <el-table v-loading="materialTableLoading" :data="materialTableData" border row-key="tempId">
+        <el-table-column label="宸ュ簭鍚嶇О" min-width="180">
+          <template #default="{ row }">
+            <el-select
+              v-model="row.processId"
+              placeholder="璇烽�夋嫨宸ュ簭"
+              clearable
+              filterable
+              style="width: 100%;"
+              @change="val => handleProcessChange(row, val)"
+            >
+              <el-option v-for="item in processOptions" :key="item.id" :label="item.name" :value="item.id" />
+            </el-select>
+          </template>
+        </el-table-column>
+        <el-table-column label="鍘熸枡鍚嶇О" min-width="160">
+          <template #default="{ row }">
+            <el-button type="primary" link @click="openMaterialProductSelect(row)">
+              {{ row.materialName || "閫夋嫨鍘熸枡" }}
+            </el-button>
+          </template>
+        </el-table-column>
+        <el-table-column label="鍘熸枡鍨嬪彿" min-width="180">
+          <template #default="{ row }">
+            {{ row.materialModel || "-" }}
+          </template>
+        </el-table-column>
+        <el-table-column label="闇�姹傛暟閲�" min-width="120">
+          <template #default="{ row }">
+            <el-input-number
+              v-model="row.requiredQty"
+              :min="0"
+              :precision="3"
+              :step="1"
+              controls-position="right"
+              style="width: 100%;"
+              @change="val => handleRequiredQtyChange(row, val)"
+            />
+          </template>
+        </el-table-column>
+        <el-table-column label="璁¢噺鍗曚綅" width="120">
+          <template #default="{ row }">
+            {{ row.unit || "-" }}
+          </template>
+        </el-table-column>
+        <el-table-column label="棰嗙敤鏁伴噺" min-width="120">
+          <template #default="{ row }">
+            <el-input-number
+              v-model="row.pickQty"
+              :min="0"
+              :precision="3"
+              :step="1"
+              controls-position="right"
+              style="width: 100%;"
+            />
+          </template>
+        </el-table-column>
+        <el-table-column label="鎿嶄綔" width="90" fixed="right">
+          <template #default="{ $index }">
+            <el-button type="danger" link @click="handleDeleteMaterialRow($index)">鍒犻櫎</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button type="primary" :loading="materialSaving" @click="handleMaterialSave">淇濆瓨</el-button>
+          <el-button @click="dialogVisible = false">鍙栨秷</el-button>
+        </span>
+      </template>
+    </el-dialog>
+
+    <ProductSelectDialog
+      v-model="materialProductDialogVisible"
+      @confirm="handleMaterialProductConfirm"
+      single
+    />
+  </div>
+</template>
+
+<script setup>
+import { computed, ref, watch } from "vue";
+import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
+import { processList } from "@/api/productionManagement/productionProcess.js";
+import { listMaterialPickingLedger, saveMaterialPickingLedger } from "@/api/productionManagement/productionOrder.js";
+
+const props = defineProps({
+  modelValue: { type: Boolean, default: false },
+  orderRow: { type: Object, default: null },
+});
+const emit = defineEmits(["update:modelValue", "saved"]);
+
+const dialogVisible = computed({
+  get: () => props.modelValue,
+  set: val => emit("update:modelValue", val),
+});
+
+const materialProductDialogVisible = ref(false);
+const materialTableLoading = ref(false);
+const materialSaving = ref(false);
+const materialTableData = ref([]);
+const processOptions = ref([]);
+const currentMaterialSelectRowIndex = ref(-1);
+let materialTempId = 0;
+
+const createMaterialRow = (row = {}) => ({
+  tempId: row.id || `temp_${++materialTempId}`,
+  id: row.id,
+  processId: row.processId,
+  processName: row.processName || "",
+  materialModelId: row.materialModelId,
+  materialName: row.materialName || "",
+  materialModel: row.materialModel || "",
+  requiredQty: Number(row.requiredQty ?? 0),
+  unit: row.unit || "",
+  pickQty: Number(row.pickQty ?? row.requiredQty ?? 0),
+});
+
+const getProcessOptions = async () => {
+  if (processOptions.value.length > 0) return;
+  const res = await processList({});
+  processOptions.value = res.data || [];
+};
+
+const loadMaterialData = async () => {
+  if (!props.orderRow?.id) return;
+  materialTableLoading.value = true;
+  materialTableData.value = [];
+  await getProcessOptions();
+  try {
+    const res = await listMaterialPickingLedger({ orderId: props.orderRow.id });
+    materialTableData.value = (res.data || []).map(item => createMaterialRow(item));
+  } finally {
+    materialTableLoading.value = false;
+  }
+};
+
+watch(
+  () => dialogVisible.value,
+  visible => {
+    if (visible) {
+      loadMaterialData();
+    }
+  }
+);
+
+const handleClose = () => {
+  materialTableData.value = [];
+  currentMaterialSelectRowIndex.value = -1;
+};
+
+const handleAddMaterialRow = () => {
+  materialTableData.value.push(createMaterialRow());
+};
+
+const handleDeleteMaterialRow = index => {
+  materialTableData.value.splice(index, 1);
+};
+
+const handleProcessChange = (row, processId) => {
+  const process = processOptions.value.find(item => item.id === processId);
+  row.processName = process?.name || "";
+};
+
+const handleRequiredQtyChange = (row, val) => {
+  const required = Number(val ?? 0);
+  row.requiredQty = required;
+  if (!row.pickQty || Number(row.pickQty) === 0) {
+    row.pickQty = required;
+  }
+};
+
+const openMaterialProductSelect = row => {
+  currentMaterialSelectRowIndex.value = materialTableData.value.findIndex(item => item.tempId === row.tempId);
+  materialProductDialogVisible.value = true;
+};
+
+const handleMaterialProductConfirm = products => {
+  if (!products || products.length === 0) return;
+  const index = currentMaterialSelectRowIndex.value;
+  if (index < 0 || !materialTableData.value[index]) return;
+  const product = products[0];
+  const row = materialTableData.value[index];
+  row.materialModelId = product.id;
+  row.materialName = product.productName || "";
+  row.materialModel = product.model || "";
+  row.unit = product.unit || "";
+  currentMaterialSelectRowIndex.value = -1;
+  materialProductDialogVisible.value = false;
+};
+
+const validateMaterialRows = () => {
+  if (materialTableData.value.length === 0) return false;
+  return !materialTableData.value.find(
+    item =>
+      !item.processId ||
+      !item.materialModelId ||
+      item.requiredQty === null ||
+      item.requiredQty === undefined ||
+      item.pickQty === null ||
+      item.pickQty === undefined
+  );
+};
+
+const handleMaterialSave = async () => {
+  if (!props.orderRow?.id || !validateMaterialRows()) return;
+  materialSaving.value = true;
+  try {
+    await saveMaterialPickingLedger({
+      orderId: props.orderRow.id,
+      items: materialTableData.value.map(item => ({
+        id: item.id,
+        processId: item.processId,
+        processName: item.processName,
+        materialModelId: item.materialModelId,
+        materialName: item.materialName,
+        materialModel: item.materialModel,
+        requiredQty: item.requiredQty,
+        unit: item.unit,
+        pickQty: item.pickQty,
+      })),
+    });
+    emit("saved");
+    dialogVisible.value = false;
+  } finally {
+    materialSaving.value = false;
+  }
+};
+</script>
+
+<style scoped lang="scss">
+.material-toolbar {
+  margin-bottom: 12px;
+  text-align: right;
+}
+</style>
diff --git a/src/views/productionManagement/productionOrder/index.vue b/src/views/productionManagement/productionOrder/index.vue
index c66a0ec..cc0de4e 100644
--- a/src/views/productionManagement/productionOrder/index.vue
+++ b/src/views/productionManagement/productionOrder/index.vue
@@ -91,161 +91,16 @@
       </template>
     </el-dialog>
 
-    <el-dialog
+    <MaterialLedgerDialog
       v-model="materialDialogVisible"
-      title="棰嗘枡鍙拌处"
-      width="1200px"
-      @close="handleMaterialDialogClose"
-    >
-      <div class="material-toolbar">
-        <el-button type="primary" @click="handleAddMaterialRow">鏂板</el-button>
-      </div>
-      <el-table
-        v-loading="materialTableLoading"
-        :data="materialTableData"
-        border
-        row-key="tempId"
-      >
-        <el-table-column label="宸ュ簭鍚嶇О" min-width="180">
-          <template #default="{ row }">
-            <el-select
-              v-model="row.processId"
-              placeholder="璇烽�夋嫨宸ュ簭"
-              clearable
-              filterable
-              style="width: 100%;"
-              @change="val => handleProcessChange(row, val)"
-            >
-              <el-option
-                v-for="item in processOptions"
-                :key="item.id"
-                :label="item.name"
-                :value="item.id"
-              />
-            </el-select>
-          </template>
-        </el-table-column>
-        <el-table-column label="鍘熸枡鍚嶇О" min-width="160">
-          <template #default="{ row }">
-            <el-button type="primary" link @click="openMaterialProductSelect(row)">
-              {{ row.materialName || "閫夋嫨鍘熸枡" }}
-            </el-button>
-          </template>
-        </el-table-column>
-        <el-table-column label="鍘熸枡鍨嬪彿" min-width="180">
-          <template #default="{ row }">
-            {{ row.materialModel || "-" }}
-          </template>
-        </el-table-column>
-        <el-table-column label="闇�姹傛暟閲�" min-width="120">
-          <template #default="{ row }">
-            <el-input-number
-              v-model="row.requiredQty"
-              :min="0"
-              :precision="3"
-              :step="1"
-              controls-position="right"
-              style="width: 100%;"
-              @change="val => handleRequiredQtyChange(row, val)"
-            />
-          </template>
-        </el-table-column>
-        <el-table-column label="璁¢噺鍗曚綅" width="120">
-          <template #default="{ row }">
-            {{ row.unit || "-" }}
-          </template>
-        </el-table-column>
-        <el-table-column label="棰嗙敤鏁伴噺" min-width="120">
-          <template #default="{ row }">
-            <el-input-number
-              v-model="row.pickQty"
-              :min="0"
-              :precision="3"
-              :step="1"
-              controls-position="right"
-              style="width: 100%;"
-            />
-          </template>
-        </el-table-column>
-        <el-table-column label="鎿嶄綔" width="90" fixed="right">
-          <template #default="{ $index }">
-            <el-button type="danger" link @click="handleDeleteMaterialRow($index)">鍒犻櫎</el-button>
-          </template>
-        </el-table-column>
-      </el-table>
-      <template #footer>
-        <span class="dialog-footer">
-          <el-button type="primary" :loading="materialSaving" @click="handleMaterialSave">淇濆瓨</el-button>
-          <el-button @click="materialDialogVisible = false">鍙栨秷</el-button>
-        </span>
-      </template>
-    </el-dialog>
-
-    <ProductSelectDialog
-      v-model="materialProductDialogVisible"
-      @confirm="handleMaterialProductConfirm"
-      single
+      :order-row="currentMaterialOrder"
+      @saved="getList"
     />
-
-    <el-dialog
+    <MaterialDetailDialog
       v-model="materialDetailDialogVisible"
-      title="棰嗘枡璇︽儏"
-      width="1400px"
-      @close="handleMaterialDetailDialogClose"
-    >
-      <el-table
-        v-loading="materialDetailLoading"
-        :data="materialDetailTableData"
-        border
-        row-key="id"
-      >
-        <el-table-column label="宸ュ簭鍚嶇О" prop="processName" min-width="180" />
-        <el-table-column label="鍘熸枡鍚嶇О" prop="materialName" min-width="160" />
-        <el-table-column label="鍘熸枡鍨嬪彿" prop="materialModel" min-width="180" />
-        <el-table-column label="闇�姹傛暟閲�" prop="requiredQty" min-width="110" />
-        <el-table-column label="璁¢噺鍗曚綅" prop="unit" width="100" />
-        <el-table-column label="棰嗙敤鏁伴噺" prop="pickQty" min-width="110" />
-        <el-table-column label="琛ユ枡鏁伴噺" min-width="120">
-          <template #default="{ row }">
-            <el-button type="primary" link @click="handleViewSupplementRecord(row)">
-              {{ row.supplementQty ?? 0 }}
-            </el-button>
-          </template>
-        </el-table-column>
-        <el-table-column label="閫�鏂欐暟閲�" prop="returnQty" min-width="110" />
-        <el-table-column label="瀹為檯鏁伴噺" prop="actualQty" min-width="110" />
-      </el-table>
-      <template #footer>
-        <span class="dialog-footer">
-          <el-button type="warning" :loading="materialReturnConfirming" @click="handleReturnConfirm">
-            閫�鏂欑‘璁�
-          </el-button>
-          <el-button @click="materialDetailDialogVisible = false">鍙栨秷</el-button>
-        </span>
-      </template>
-    </el-dialog>
-
-    <el-dialog
-      v-model="supplementRecordDialogVisible"
-      title="琛ユ枡璁板綍"
-      width="800px"
-    >
-      <el-table
-        v-loading="supplementRecordLoading"
-        :data="supplementRecordTableData"
-        border
-        row-key="id"
-      >
-        <el-table-column label="琛ユ枡鏁伴噺" prop="supplementQty" min-width="120" />
-        <el-table-column label="琛ユ枡鏃堕棿" prop="supplementTime" min-width="180" />
-        <el-table-column label="澶囨敞" prop="remark" min-width="200" />
-      </el-table>
-      <template #footer>
-        <span class="dialog-footer">
-          <el-button @click="supplementRecordDialogVisible = false">鍏抽棴</el-button>
-        </span>
-      </template>
-    </el-dialog>
+      :order-row="currentMaterialDetailOrder"
+      @confirmed="getList"
+    />
 
     <new-product-order v-if="isShowNewModal"
                          v-model:visible="isShowNewModal"
@@ -258,20 +113,15 @@
   import { ElMessageBox } from "element-plus";
   import dayjs from "dayjs";
   import { useRouter } from "vue-router";
-  import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
   import {
     productOrderListPage,
     listProcessRoute,
     bindingRoute,
     listProcessBom, delProductOrder,
-    listMaterialPickingLedger,
-    saveMaterialPickingLedger,
-    listMaterialPickingDetail,
-    listMaterialSupplementRecord,
-    confirmMaterialReturn,
   } from "@/api/productionManagement/productionOrder.js";
   import { listMain as getOrderProcessRouteMain } from "@/api/productionManagement/productProcessRoute.js";
-  import { processList } from "@/api/productionManagement/productionProcess.js";
+  import MaterialLedgerDialog from "@/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue";
+  import MaterialDetailDialog from "@/views/productionManagement/productionOrder/components/MaterialDetailDialog.vue";
   import PIMTable from "@/components/PIMTable/PIMTable.vue";
   const NewProductOrder = defineAsyncComponent(() => import("@/views/productionManagement/productionOrder/New.vue"));
 
@@ -454,35 +304,9 @@
     routeId: null,
   });
   const materialDialogVisible = ref(false);
-  const materialProductDialogVisible = ref(false);
-  const materialTableLoading = ref(false);
-  const materialSaving = ref(false);
-  const materialTableData = ref([]);
-  const processOptions = ref([]);
   const currentMaterialOrder = ref(null);
-  const currentMaterialSelectRowIndex = ref(-1);
   const materialDetailDialogVisible = ref(false);
-  const materialDetailLoading = ref(false);
-  const materialDetailTableData = ref([]);
-  const materialReturnConfirming = ref(false);
   const currentMaterialDetailOrder = ref(null);
-  const supplementRecordDialogVisible = ref(false);
-  const supplementRecordLoading = ref(false);
-  const supplementRecordTableData = ref([]);
-  let materialTempId = 0;
-
-  const createMaterialRow = (row = {}) => ({
-    tempId: row.id || `temp_${++materialTempId}`,
-    id: row.id,
-    processId: row.processId,
-    processName: row.processName || "",
-    materialModelId: row.materialModelId,
-    materialName: row.materialName || "",
-    materialModel: row.materialModel || "",
-    requiredQty: Number(row.requiredQty ?? 0),
-    unit: row.unit || "",
-    pickQty: Number(row.pickQty ?? row.requiredQty ?? 0),
-  });
 
   const openBindRouteDialog = async row => {
     bindForm.orderId = row.id;
@@ -528,199 +352,14 @@
     }
   };
 
-  const getProcessOptions = async () => {
-    if (processOptions.value.length > 0) return;
-    try {
-      const res = await processList({});
-      processOptions.value = res.data || [];
-    } catch (e) {
-      console.error("鑾峰彇宸ュ簭鍒楄〃澶辫触锛�", e);
-      proxy.$modal.msgError("鑾峰彇宸ュ簭鍒楄〃澶辫触");
-    }
-  };
-
-  const openMaterialDialog = async row => {
+  const openMaterialDialog = row => {
     currentMaterialOrder.value = row;
     materialDialogVisible.value = true;
-    materialTableLoading.value = true;
-    materialTableData.value = [];
-    await getProcessOptions();
-    try {
-      const res = await listMaterialPickingLedger({ orderId: row.id });
-      const list = res.data || [];
-      materialTableData.value = list.map(item => createMaterialRow(item));
-    } catch (e) {
-      console.error("鑾峰彇棰嗘枡鍙拌处澶辫触锛�", e);
-      proxy.$modal.msgError("鑾峰彇棰嗘枡鍙拌处澶辫触");
-    } finally {
-      materialTableLoading.value = false;
-    }
-  };
-
-  const handleMaterialDialogClose = () => {
-    materialTableData.value = [];
-    currentMaterialOrder.value = null;
-    currentMaterialSelectRowIndex.value = -1;
-  };
-
-  const handleAddMaterialRow = () => {
-    materialTableData.value.push(createMaterialRow());
-  };
-
-  const handleDeleteMaterialRow = index => {
-    materialTableData.value.splice(index, 1);
-  };
-
-  const handleProcessChange = (row, processId) => {
-    const process = processOptions.value.find(item => item.id === processId);
-    row.processName = process?.name || "";
-  };
-
-  const handleRequiredQtyChange = (row, val) => {
-    const required = Number(val ?? 0);
-    row.requiredQty = required;
-    if (!row.pickQty || Number(row.pickQty) === 0) {
-      row.pickQty = required;
-    }
-  };
-
-  const openMaterialProductSelect = row => {
-    currentMaterialSelectRowIndex.value = materialTableData.value.findIndex(item => item.tempId === row.tempId);
-    materialProductDialogVisible.value = true;
-  };
-
-  const handleMaterialProductConfirm = products => {
-    if (!products || products.length === 0) return;
-    const index = currentMaterialSelectRowIndex.value;
-    if (index < 0 || !materialTableData.value[index]) return;
-    const product = products[0];
-    const row = materialTableData.value[index];
-    row.materialModelId = product.id;
-    row.materialName = product.productName || "";
-    row.materialModel = product.model || "";
-    row.unit = product.unit || "";
-    currentMaterialSelectRowIndex.value = -1;
-    materialProductDialogVisible.value = false;
-  };
-
-  const validateMaterialRows = () => {
-    if (materialTableData.value.length === 0) {
-      proxy.$modal.msgWarning("璇疯嚦灏戞柊澧炰竴鏉¢鏂欒褰�");
-      return false;
-    }
-    const invalidRow = materialTableData.value.find(
-      item =>
-        !item.processId ||
-        !item.materialModelId ||
-        item.requiredQty === null ||
-        item.requiredQty === undefined ||
-        item.pickQty === null ||
-        item.pickQty === undefined
-    );
-    if (invalidRow) {
-      proxy.$modal.msgWarning("璇峰畬鍠勯鏂欏彴璐﹀繀濉瓧娈�");
-      return false;
-    }
-    return true;
-  };
-
-  const handleMaterialSave = async () => {
-    if (!currentMaterialOrder.value?.id) {
-      proxy.$modal.msgWarning("鏈幏鍙栧埌褰撳墠鐢熶骇璁㈠崟");
-      return;
-    }
-    if (!validateMaterialRows()) return;
-    materialSaving.value = true;
-    try {
-      await saveMaterialPickingLedger({
-        orderId: currentMaterialOrder.value.id,
-        items: materialTableData.value.map(item => ({
-          id: item.id,
-          processId: item.processId,
-          processName: item.processName,
-          materialModelId: item.materialModelId,
-          materialName: item.materialName,
-          materialModel: item.materialModel,
-          requiredQty: item.requiredQty,
-          unit: item.unit,
-          pickQty: item.pickQty,
-        })),
-      });
-      proxy.$modal.msgSuccess("淇濆瓨鎴愬姛");
-      materialDialogVisible.value = false;
-    } catch (e) {
-      console.error("淇濆瓨棰嗘枡鍙拌处澶辫触锛�", e);
-      proxy.$modal.msgError("淇濆瓨棰嗘枡鍙拌处澶辫触");
-    } finally {
-      materialSaving.value = false;
-    }
   };
 
   const openMaterialDetailDialog = async row => {
     currentMaterialDetailOrder.value = row;
     materialDetailDialogVisible.value = true;
-    materialDetailLoading.value = true;
-    materialDetailTableData.value = [];
-    try {
-      const res = await listMaterialPickingDetail({ orderId: row.id });
-      materialDetailTableData.value = res.data || [];
-    } catch (e) {
-      console.error("鑾峰彇棰嗘枡璇︽儏澶辫触锛�", e);
-      proxy.$modal.msgError("鑾峰彇棰嗘枡璇︽儏澶辫触");
-    } finally {
-      materialDetailLoading.value = false;
-    }
-  };
-
-  const handleMaterialDetailDialogClose = () => {
-    materialDetailTableData.value = [];
-    currentMaterialDetailOrder.value = null;
-  };
-
-  const handleViewSupplementRecord = async row => {
-    if (!row?.id) {
-      proxy.$modal.msgWarning("缂哄皯棰嗘枡鏄庣粏ID锛屾棤娉曟煡鐪嬭ˉ鏂欒褰�");
-      return;
-    }
-    supplementRecordDialogVisible.value = true;
-    supplementRecordLoading.value = true;
-    supplementRecordTableData.value = [];
-    try {
-      const res = await listMaterialSupplementRecord({ materialDetailId: row.id });
-      supplementRecordTableData.value = res.data || [];
-    } catch (e) {
-      console.error("鑾峰彇琛ユ枡璁板綍澶辫触锛�", e);
-      proxy.$modal.msgError("鑾峰彇琛ユ枡璁板綍澶辫触");
-    } finally {
-      supplementRecordLoading.value = false;
-    }
-  };
-
-  const handleReturnConfirm = async () => {
-    if (!currentMaterialDetailOrder.value?.id) {
-      proxy.$modal.msgWarning("鏈幏鍙栧埌褰撳墠鐢熶骇璁㈠崟");
-      return;
-    }
-    try {
-      await ElMessageBox.confirm("纭鎵ц閫�鏂欑‘璁わ紵", "鎻愮ず", {
-        confirmButtonText: "纭",
-        cancelButtonText: "鍙栨秷",
-        type: "warning",
-      });
-    } catch (e) {
-      return;
-    }
-    materialReturnConfirming.value = true;
-    try {
-      await confirmMaterialReturn({ orderId: currentMaterialDetailOrder.value.id });
-      proxy.$modal.msgSuccess("閫�鏂欑‘璁ゆ垚鍔�");
-      openMaterialDetailDialog(currentMaterialDetailOrder.value);
-    } catch (e) {
-      console.error("閫�鏂欑‘璁ゅけ璐ワ細", e);
-      proxy.$modal.msgError("閫�鏂欑‘璁ゅけ璐�");
-    } finally {
-      materialReturnConfirming.value = false;
-    }
   };
 
   // 鏌ヨ鍒楄〃
@@ -875,8 +514,4 @@
 	margin-top: unset;
 }
 
-.material-toolbar {
-  margin-bottom: 12px;
-  text-align: right;
-}
 </style>
diff --git a/src/views/productionManagement/workOrderManagement/components/MaterialDialog.vue b/src/views/productionManagement/workOrderManagement/components/MaterialDialog.vue
new file mode 100644
index 0000000..e5eee56
--- /dev/null
+++ b/src/views/productionManagement/workOrderManagement/components/MaterialDialog.vue
@@ -0,0 +1,296 @@
+<template>
+  <div>
+    <el-dialog
+      v-model="dialogVisible"
+      title="鐗╂枡"
+      width="1200px"
+      @close="handleCloseMaterialDialog"
+    >
+      <el-table v-loading="materialTableLoading" :data="materialTableData" border row-key="id">
+        <el-table-column label="宸ュ簭鍚嶇О" prop="processName" min-width="140" />
+        <el-table-column label="鍘熸枡鍚嶇О" prop="materialName" min-width="140" />
+        <el-table-column label="鍘熸枡鍨嬪彿" prop="materialModel" min-width="140" />
+        <el-table-column label="璁¢噺鍗曚綅" prop="unit" min-width="100" />
+        <el-table-column label="棰嗙敤鏁伴噺" prop="pickQty" min-width="100" />
+        <el-table-column label="琛ユ枡鏁伴噺" prop="supplementQty" min-width="100" />
+        <el-table-column label="閫�鏂欐暟閲�" prop="returnQty" min-width="100" />
+        <el-table-column label="瀹為檯鏁伴噺" prop="actualQty" min-width="100" />
+        <el-table-column label="鎿嶄綔" align="center" fixed="right" width="220">
+          <template #default="{ row }">
+            <el-button type="primary" link @click="openSupplementDialog(row)">琛ユ枡</el-button>
+            <el-button type="warning" link @click="openReturnDialog(row)">閫�鏂�</el-button>
+            <el-button type="info" link @click="openSupplementRecordDialog(row)">琛ユ枡璁板綍</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-dialog>
+
+    <FormDialog
+      v-model="supplementDialogVisible"
+      title="琛ユ枡"
+      width="500px"
+      @confirm="handleSubmitSupplement"
+    >
+      <el-form ref="supplementFormRef" :model="supplementForm" :rules="supplementRules" label-width="100px">
+        <el-form-item label="琛ユ枡鏁伴噺" prop="supplementQty">
+          <el-input-number
+            v-model="supplementForm.supplementQty"
+            :min="0.001"
+            :precision="3"
+            :step="1"
+            style="width: 100%;"
+          />
+        </el-form-item>
+        <el-form-item label="琛ユ枡鍘熷洜" prop="supplementReason">
+          <el-input
+            v-model="supplementForm.supplementReason"
+            type="textarea"
+            :rows="3"
+            maxlength="200"
+            show-word-limit
+            placeholder="璇疯緭鍏ヨˉ鏂欏師鍥�"
+          />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button type="primary" :loading="supplementSubmitting" @click="handleSubmitSupplement">纭畾</el-button>
+          <el-button @click="supplementDialogVisible = false">鍙栨秷</el-button>
+        </span>
+      </template>
+    </FormDialog>
+
+    <FormDialog
+      v-model="returnDialogVisible"
+      title="閫�鏂�"
+      width="500px"
+      @confirm="handleSubmitReturn"
+    >
+      <el-form ref="returnFormRef" :model="returnForm" :rules="returnRules" label-width="120px">
+        <el-form-item label="閫�鏂欐暟閲�" prop="returnQty">
+          <el-input-number
+            v-model="returnForm.returnQty"
+            :min="0.001"
+            :precision="3"
+            :step="1"
+            style="width: 100%;"
+          />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button type="primary" :loading="returnSubmitting" @click="handleSubmitReturn">纭畾</el-button>
+          <el-button @click="returnDialogVisible = false">鍙栨秷</el-button>
+        </span>
+      </template>
+    </FormDialog>
+
+    <el-dialog v-model="supplementRecordDialogVisible" title="琛ユ枡璁板綍" width="900px">
+      <el-table v-loading="supplementRecordLoading" :data="supplementRecordTableData" border row-key="id">
+        <el-table-column label="琛ユ枡鏁伴噺" prop="supplementQty" min-width="100" />
+        <el-table-column label="琛ユ枡鍘熷洜" prop="supplementReason" min-width="200" />
+        <el-table-column label="琛ユ枡浜�" prop="supplementUserName" min-width="120" />
+        <el-table-column label="琛ユ枡鏃ユ湡" prop="supplementTime" min-width="160" />
+      </el-table>
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button @click="supplementRecordDialogVisible = false">鍏抽棴</el-button>
+        </span>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { computed, nextTick, reactive, ref, watch } from "vue";
+import { ElMessage } from "element-plus";
+import FormDialog from "@/components/Dialog/FormDialog.vue";
+import {
+  listWorkOrderMaterialLedger,
+  addWorkOrderMaterialSupplement,
+  addWorkOrderMaterialReturn,
+  listWorkOrderMaterialSupplementRecord,
+} from "@/api/productionManagement/workOrder.js";
+
+const props = defineProps({
+  modelValue: {
+    type: Boolean,
+    default: false,
+  },
+  rowData: {
+    type: Object,
+    default: () => null,
+  },
+});
+
+const emit = defineEmits(["update:modelValue", "refresh"]);
+
+const dialogVisible = computed({
+  get: () => props.modelValue,
+  set: val => emit("update:modelValue", val),
+});
+
+const materialTableLoading = ref(false);
+const materialTableData = ref([]);
+const currentMaterialRow = ref(null);
+const currentMaterialOrderRow = ref(null);
+
+const supplementDialogVisible = ref(false);
+const supplementSubmitting = ref(false);
+const supplementFormRef = ref(null);
+const supplementForm = reactive({
+  supplementQty: null,
+  supplementReason: "",
+});
+
+const returnDialogVisible = ref(false);
+const returnSubmitting = ref(false);
+const returnFormRef = ref(null);
+const returnForm = reactive({
+  returnQty: null,
+});
+
+const supplementRecordDialogVisible = ref(false);
+const supplementRecordLoading = ref(false);
+const supplementRecordTableData = ref([]);
+
+const supplementRules = {
+  supplementQty: [{ required: true, message: "璇疯緭鍏ヨˉ鏂欐暟閲�", trigger: "blur" }],
+  supplementReason: [{ required: true, message: "璇疯緭鍏ヨˉ鏂欏師鍥�", trigger: "blur" }],
+};
+const returnRules = {
+  returnQty: [{ required: true, message: "璇疯緭鍏ラ��鏂欐暟閲�", trigger: "blur" }],
+};
+
+const loadMaterialTable = async row => {
+  if (!row?.id) return;
+  currentMaterialOrderRow.value = row;
+  materialTableLoading.value = true;
+  materialTableData.value = [];
+  try {
+    const res = await listWorkOrderMaterialLedger({
+      workOrderId: row.id,
+      processId: row.processId,
+      productProcessRouteItemId: row.productProcessRouteItemId,
+    });
+    materialTableData.value = res.data || [];
+  } catch (e) {
+    console.error("鑾峰彇鐗╂枡鍙拌处澶辫触", e);
+    ElMessage.error("鑾峰彇鐗╂枡鍙拌处澶辫触");
+  } finally {
+    materialTableLoading.value = false;
+  }
+};
+
+watch(
+  () => props.modelValue,
+  visible => {
+    if (visible && props.rowData) {
+      loadMaterialTable(props.rowData);
+    }
+  }
+);
+
+const handleCloseMaterialDialog = () => {
+  materialTableData.value = [];
+  currentMaterialRow.value = null;
+  currentMaterialOrderRow.value = null;
+};
+
+const openSupplementDialog = row => {
+  currentMaterialRow.value = row;
+  supplementForm.supplementQty = null;
+  supplementForm.supplementReason = "";
+  supplementDialogVisible.value = true;
+  nextTick(() => {
+    supplementFormRef.value?.clearValidate();
+  });
+};
+
+const handleSubmitSupplement = () => {
+  supplementFormRef.value?.validate(async valid => {
+    if (!valid || !currentMaterialRow.value?.id) {
+      ElMessage.warning("缂哄皯鐗╂枡鏄庣粏ID");
+      return;
+    }
+    supplementSubmitting.value = true;
+    try {
+      await addWorkOrderMaterialSupplement({
+        materialLedgerId: currentMaterialRow.value.id,
+        supplementQty: Number(supplementForm.supplementQty),
+        supplementReason: supplementForm.supplementReason,
+        workOrderId: currentMaterialOrderRow.value?.id,
+      });
+      supplementDialogVisible.value = false;
+      await loadMaterialTable(currentMaterialOrderRow.value);
+      ElMessage.success("琛ユ枡鎴愬姛");
+      emit("refresh");
+    } catch (e) {
+      console.error("琛ユ枡澶辫触", e);
+      ElMessage.error("琛ユ枡澶辫触");
+    } finally {
+      supplementSubmitting.value = false;
+    }
+  });
+};
+
+const openReturnDialog = row => {
+  currentMaterialRow.value = row;
+  returnForm.returnQty = null;
+  returnDialogVisible.value = true;
+  nextTick(() => {
+    returnFormRef.value?.clearValidate();
+  });
+};
+
+const handleSubmitReturn = () => {
+  returnFormRef.value?.validate(async valid => {
+    if (!valid || !currentMaterialRow.value?.id) {
+      ElMessage.warning("缂哄皯鐗╂枡鏄庣粏ID");
+      return;
+    }
+    const returnQty = Number(returnForm.returnQty);
+    const minQty =
+      Number(currentMaterialRow.value.pickQty || 0) +
+      Number(currentMaterialRow.value.supplementQty || 0);
+    if (returnQty < minQty) {
+      ElMessage.warning(`閫�鏂欐暟閲忎笉鑳戒綆浜庨鐢ㄦ暟閲�+琛ユ枡鏁伴噺锛�${minQty}锛塦);
+      return;
+    }
+    returnSubmitting.value = true;
+    try {
+      await addWorkOrderMaterialReturn({
+        materialLedgerId: currentMaterialRow.value.id,
+        returnQty,
+        workOrderId: currentMaterialOrderRow.value?.id,
+      });
+      returnDialogVisible.value = false;
+      await loadMaterialTable(currentMaterialOrderRow.value);
+      ElMessage.success("閫�鏂欐垚鍔�");
+      emit("refresh");
+    } catch (e) {
+      console.error("閫�鏂欏け璐�", e);
+      ElMessage.error("閫�鏂欏け璐�");
+    } finally {
+      returnSubmitting.value = false;
+    }
+  });
+};
+
+const openSupplementRecordDialog = async row => {
+  supplementRecordDialogVisible.value = true;
+  supplementRecordLoading.value = true;
+  supplementRecordTableData.value = [];
+  try {
+    const res = await listWorkOrderMaterialSupplementRecord({
+      materialLedgerId: row.id,
+    });
+    supplementRecordTableData.value = res.data || [];
+  } catch (e) {
+    console.error("鑾峰彇琛ユ枡璁板綍澶辫触", e);
+    ElMessage.error("鑾峰彇琛ユ枡璁板綍澶辫触");
+  } finally {
+    supplementRecordLoading.value = false;
+  }
+};
+</script>
diff --git a/src/views/productionManagement/workOrderManagement/index.vue b/src/views/productionManagement/workOrderManagement/index.vue
index 9ffbd3f..d0f9389 100644
--- a/src/views/productionManagement/workOrderManagement/index.vue
+++ b/src/views/productionManagement/workOrderManagement/index.vue
@@ -164,146 +164,11 @@
       </template>
     </el-dialog>
 
-    <el-dialog v-model="materialDialogVisible"
-               title="鐗╂枡"
-               width="1200px"
-               @close="handleCloseMaterialDialog">
-      <el-table v-loading="materialTableLoading"
-                :data="materialTableData"
-                border
-                row-key="id">
-        <el-table-column label="宸ュ簭鍚嶇О"
-                         prop="processName"
-                         min-width="140" />
-        <el-table-column label="鍘熸枡鍚嶇О"
-                         prop="materialName"
-                         min-width="140" />
-        <el-table-column label="鍘熸枡鍨嬪彿"
-                         prop="materialModel"
-                         min-width="140" />
-        <el-table-column label="璁¢噺鍗曚綅"
-                         prop="unit"
-                         min-width="100" />
-        <el-table-column label="棰嗙敤鏁伴噺"
-                         prop="pickQty"
-                         min-width="100" />
-        <el-table-column label="琛ユ枡鏁伴噺"
-                         prop="supplementQty"
-                         min-width="100" />
-        <el-table-column label="閫�鏂欐暟閲�"
-                         prop="returnQty"
-                         min-width="100" />
-        <el-table-column label="瀹為檯鏁伴噺"
-                         prop="actualQty"
-                         min-width="100" />
-        <el-table-column label="鎿嶄綔"
-                         align="center"
-                         fixed="right"
-                         width="220">
-          <template #default="{ row }">
-            <el-button type="primary"
-                       link
-                       @click="openSupplementDialog(row)">琛ユ枡</el-button>
-            <el-button type="warning"
-                       link
-                       @click="openReturnDialog(row)">閫�鏂�</el-button>
-            <el-button type="info"
-                       link
-                       @click="openSupplementRecordDialog(row)">琛ユ枡璁板綍</el-button>
-          </template>
-        </el-table-column>
-      </el-table>
-    </el-dialog>
-
-    <FormDialog v-model="supplementDialogVisible"
-                title="琛ユ枡"
-                width="500px"
-                @confirm="handleSubmitSupplement">
-      <el-form ref="supplementFormRef"
-               :model="supplementForm"
-               :rules="supplementRules"
-               label-width="100px">
-        <el-form-item label="琛ユ枡鏁伴噺"
-                      prop="supplementQty">
-          <el-input-number v-model="supplementForm.supplementQty"
-                           :min="0.001"
-                           :precision="3"
-                           :step="1"
-                           style="width: 100%;" />
-        </el-form-item>
-        <el-form-item label="琛ユ枡鍘熷洜"
-                      prop="supplementReason">
-          <el-input v-model="supplementForm.supplementReason"
-                    type="textarea"
-                    :rows="3"
-                    maxlength="200"
-                    show-word-limit
-                    placeholder="璇疯緭鍏ヨˉ鏂欏師鍥�" />
-        </el-form-item>
-      </el-form>
-      <template #footer>
-        <span class="dialog-footer">
-          <el-button type="primary"
-                     :loading="supplementSubmitting"
-                     @click="handleSubmitSupplement">纭畾</el-button>
-          <el-button @click="supplementDialogVisible = false">鍙栨秷</el-button>
-        </span>
-      </template>
-    </FormDialog>
-
-    <FormDialog v-model="returnDialogVisible"
-                title="閫�鏂�"
-                width="500px"
-                @confirm="handleSubmitReturn">
-      <el-form ref="returnFormRef"
-               :model="returnForm"
-               :rules="returnRules"
-               label-width="120px">
-        <el-form-item label="閫�鏂欐暟閲�"
-                      prop="returnQty">
-          <el-input-number v-model="returnForm.returnQty"
-                           :min="0.001"
-                           :precision="3"
-                           :step="1"
-                           style="width: 100%;" />
-        </el-form-item>
-      </el-form>
-      <template #footer>
-        <span class="dialog-footer">
-          <el-button type="primary"
-                     :loading="returnSubmitting"
-                     @click="handleSubmitReturn">纭畾</el-button>
-          <el-button @click="returnDialogVisible = false">鍙栨秷</el-button>
-        </span>
-      </template>
-    </FormDialog>
-
-    <el-dialog v-model="supplementRecordDialogVisible"
-               title="琛ユ枡璁板綍"
-               width="900px">
-      <el-table v-loading="supplementRecordLoading"
-                :data="supplementRecordTableData"
-                border
-                row-key="id">
-        <el-table-column label="琛ユ枡鏁伴噺"
-                         prop="supplementQty"
-                         min-width="100" />
-        <el-table-column label="琛ユ枡鍘熷洜"
-                         prop="supplementReason"
-                         min-width="200" />
-        <el-table-column label="琛ユ枡浜�"
-                         prop="supplementUserName"
-                         min-width="120" />
-        <el-table-column label="琛ユ枡鏃ユ湡"
-                         prop="supplementTime"
-                         min-width="160" />
-      </el-table>
-      <template #footer>
-        <span class="dialog-footer">
-          <el-button @click="supplementRecordDialogVisible = false">鍏抽棴</el-button>
-        </span>
-      </template>
-    </el-dialog>
+    <MaterialDialog
+      v-model="materialDialogVisible"
+      :row-data="currentMaterialOrderRow"
+      @refresh="getList"
+    />
     
     <FilesDia ref="workOrderFilesRef" />
   </div>
@@ -317,16 +182,12 @@
     productWorkOrderPage,
     addProductMain,
     downProductWorkOrder,
-    listWorkOrderMaterialLedger,
-    addWorkOrderMaterialSupplement,
-    addWorkOrderMaterialReturn,
-    listWorkOrderMaterialSupplementRecord,
   } from "@/api/productionManagement/workOrder.js";
   import { getUserProfile, userListNoPageByTenantId } from "@/api/system/user.js";
   import QRCode from "qrcode";
   import { getCurrentInstance, reactive, toRefs } from "vue";
   import FilesDia from "./components/filesDia.vue";
-  import FormDialog from "@/components/Dialog/FormDialog.vue";
+  import MaterialDialog from "./components/MaterialDialog.vue";
   const { proxy } = getCurrentInstance();
 
   const tableColumn = ref([
@@ -492,13 +353,6 @@
     quantity: [{ required: true, validator: validateQuantity, trigger: "blur" }],
     scrapQty: [{ validator: validateScrapQty, trigger: "blur" }],
   };
-  const supplementRules = {
-    supplementQty: [{ required: true, message: "璇疯緭鍏ヨˉ鏂欐暟閲�", trigger: "blur" }],
-    supplementReason: [{ required: true, message: "璇疯緭鍏ヨˉ鏂欏師鍥�", trigger: "blur" }],
-  };
-  const returnRules = {
-    returnQty: [{ required: true, message: "璇疯緭鍏ラ��鏂欐暟閲�", trigger: "blur" }],
-  };
 
   // 澶勭悊鏈鐢熶骇鏁伴噺杈撳叆锛岄檺鍒跺繀椤诲ぇ浜庣瓑浜�1
   const handleQuantityInput = value => {
@@ -556,26 +410,7 @@
   
   const currentReportRowData = ref(null);
   const materialDialogVisible = ref(false);
-  const materialTableLoading = ref(false);
-  const materialTableData = ref([]);
-  const currentMaterialRow = ref(null);
   const currentMaterialOrderRow = ref(null);
-  const supplementDialogVisible = ref(false);
-  const supplementSubmitting = ref(false);
-  const supplementFormRef = ref(null);
-  const supplementForm = reactive({
-    supplementQty: null,
-    supplementReason: "",
-  });
-  const returnDialogVisible = ref(false);
-  const returnSubmitting = ref(false);
-  const returnFormRef = ref(null);
-  const returnForm = reactive({
-    returnQty: null,
-  });
-  const supplementRecordDialogVisible = ref(false);
-  const supplementRecordLoading = ref(false);
-  const supplementRecordTableData = ref([]);
   const page = reactive({
     current: 1,
     size: 100,
@@ -727,127 +562,9 @@
     reportDialogVisible.value = true;
   };
 
-  const openMaterialDialog = async row => {
+  const openMaterialDialog = row => {
     currentMaterialOrderRow.value = row;
     materialDialogVisible.value = true;
-    materialTableLoading.value = true;
-    materialTableData.value = [];
-    try {
-      const res = await listWorkOrderMaterialLedger({
-        workOrderId: row.id,
-        processId: row.processId,
-        productProcessRouteItemId: row.productProcessRouteItemId,
-      });
-      materialTableData.value = res.data || [];
-    } catch (e) {
-      console.error("鑾峰彇鐗╂枡鍙拌处澶辫触", e);
-      proxy.$modal.msgError("鑾峰彇鐗╂枡鍙拌处澶辫触");
-    } finally {
-      materialTableLoading.value = false;
-    }
-  };
-
-  const handleCloseMaterialDialog = () => {
-    materialTableData.value = [];
-    currentMaterialRow.value = null;
-    currentMaterialOrderRow.value = null;
-  };
-
-  const openSupplementDialog = row => {
-    currentMaterialRow.value = row;
-    supplementForm.supplementQty = null;
-    supplementForm.supplementReason = "";
-    supplementDialogVisible.value = true;
-    nextTick(() => {
-      supplementFormRef.value?.clearValidate();
-    });
-  };
-
-  const handleSubmitSupplement = () => {
-    supplementFormRef.value?.validate(async valid => {
-      if (!valid) return;
-      if (!currentMaterialRow.value?.id) {
-        proxy.$modal.msgWarning("缂哄皯鐗╂枡鏄庣粏ID");
-        return;
-      }
-      supplementSubmitting.value = true;
-      try {
-        await addWorkOrderMaterialSupplement({
-          materialLedgerId: currentMaterialRow.value.id,
-          supplementQty: Number(supplementForm.supplementQty),
-          supplementReason: supplementForm.supplementReason,
-          workOrderId: currentMaterialOrderRow.value?.id,
-        });
-        proxy.$modal.msgSuccess("琛ユ枡鎴愬姛");
-        supplementDialogVisible.value = false;
-        await openMaterialDialog(currentMaterialOrderRow.value);
-      } catch (e) {
-        console.error("琛ユ枡澶辫触", e);
-        proxy.$modal.msgError("琛ユ枡澶辫触");
-      } finally {
-        supplementSubmitting.value = false;
-      }
-    });
-  };
-
-  const openReturnDialog = row => {
-    currentMaterialRow.value = row;
-    returnForm.returnQty = null;
-    returnDialogVisible.value = true;
-    nextTick(() => {
-      returnFormRef.value?.clearValidate();
-    });
-  };
-
-  const handleSubmitReturn = () => {
-    returnFormRef.value?.validate(async valid => {
-      if (!valid) return;
-      if (!currentMaterialRow.value?.id) {
-        proxy.$modal.msgWarning("缂哄皯鐗╂枡鏄庣粏ID");
-        return;
-      }
-      const returnQty = Number(returnForm.returnQty);
-      const minQty =
-        Number(currentMaterialRow.value.pickQty || 0) +
-        Number(currentMaterialRow.value.supplementQty || 0);
-      if (returnQty < minQty) {
-        proxy.$modal.msgWarning(`閫�鏂欐暟閲忎笉鑳戒綆浜庨鐢ㄦ暟閲�+琛ユ枡鏁伴噺锛�${minQty}锛塦);
-        return;
-      }
-      returnSubmitting.value = true;
-      try {
-        await addWorkOrderMaterialReturn({
-          materialLedgerId: currentMaterialRow.value.id,
-          returnQty,
-          workOrderId: currentMaterialOrderRow.value?.id,
-        });
-        proxy.$modal.msgSuccess("閫�鏂欐垚鍔�");
-        returnDialogVisible.value = false;
-        await openMaterialDialog(currentMaterialOrderRow.value);
-      } catch (e) {
-        console.error("閫�鏂欏け璐�", e);
-        proxy.$modal.msgError("閫�鏂欏け璐�");
-      } finally {
-        returnSubmitting.value = false;
-      }
-    });
-  };
-
-  const openSupplementRecordDialog = async row => {
-    supplementRecordDialogVisible.value = true;
-    supplementRecordLoading.value = true;
-    supplementRecordTableData.value = [];
-    try {
-      const res = await listWorkOrderMaterialSupplementRecord({
-        materialLedgerId: row.id,
-      });
-      supplementRecordTableData.value = res.data || [];
-    } catch (e) {
-      console.error("鑾峰彇琛ユ枡璁板綍澶辫触", e);
-      proxy.$modal.msgError("鑾峰彇琛ユ枡璁板綍澶辫触");
-    } finally {
-      supplementRecordLoading.value = false;
-    }
   };
 
   const handleReport = () => {

--
Gitblit v1.9.3