spring
11 小时以前 6361501810a76b6809162cac99b0d9c1faba3715
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>