gaoluyang
2026-06-16 ffe58e0cf73fa61d8b05de09645c5d8d89007ce5
src/views/productionManagement/productionOrder/index.vue
@@ -40,6 +40,8 @@
                       value="3" />
            <el-option label="已取消"
                       value="4" />
            <el-option label="已结束"
                       value="5" />
          </el-select>
        </el-form-item>
        <el-form-item>
@@ -65,12 +67,33 @@
                :tableLoading="tableLoading"
                :row-class-name="tableRowClassName"
                :isSelection="true"
                :selectable="row => !row.endOrder"
                @selection-change="handleSelectionChange"
                @pagination="pagination">
        <template #completionStatus="{ row }">
          <el-progress :percentage="toProgressPercentage(row?.completionStatus)"
                       :color="progressColor(toProgressPercentage(row?.completionStatus))"
                       :status="toProgressPercentage(row?.completionStatus) >= 100 ? 'success' : ''" />
        </template>
        <template #processRouteStatus="{ row }">
          <div v-if="row.processRouteStatus && row.processRouteStatus.length"
               class="process-progress-container">
            <div v-for="(item, index) in row.processRouteStatus"
                 :key="index"
                 class="process-step">
              <div class="step-content">
                <div class="step-circle"
                     :class="{ 'is-completed': item.percentage >= 100 }">
                  <span class="step-percentage"
                        :style="{ color: item.percentage >= 70 ? item.percentage >= 100 ? '#67c23a' : '#f56c6c' : '#000' }">{{ item.percentage }}%</span>
                </div>
                <div class="step-name">{{ item.name }}</div>
              </div>
              <div v-if="index < row.processRouteStatus.length - 1"
                   class="step-line"></div>
            </div>
          </div>
          <span v-else>-</span>
        </template>
      </PIMTable>
    </div>
@@ -169,15 +192,25 @@
        </div>
      </div>
    </el-dialog>
    <MaterialLedgerDialog v-model="materialDialogVisible"
    <!-- 领料/补料/领料详情功能已隐藏 -->
    <!-- <MaterialLedgerDialog v-model="materialDialogVisible"
                          :order-row="currentMaterialOrder"
                          @saved="getList" />
    <MaterialDetailDialog v-model="materialDetailDialogVisible"
                          :order-row="currentMaterialDetailOrder"
                          @confirmed="getList" />
    <MaterialSupplementDialog v-model="materialSupplementDialogVisible"
                              :order-row="currentMaterialSupplementOrder"
                              @saved="getList" /> -->
    <new-product-order v-if="isShowNewModal"
                       v-model:visible="isShowNewModal"
                       @completed="handleQuery" />
    <!-- 打印领料单功能已隐藏 -->
    <!-- <div class="print-requisition-wrapper">
      <PrintMaterialRequisition ref="printRef"
                                :order-row="printOrderRow"
                                :material-list="printMaterialList" />
    </div> -->
  </div>
</template>
@@ -201,12 +234,22 @@
    listProcessBom,
    delProductOrder,
    getProductOrderSource,
    updateProductOrder,
  } from "@/api/productionManagement/productionOrder.js";
  import { productWorkOrderPage } from "@/api/productionManagement/workOrder.js";
  import { listMain as getOrderProcessRouteMain } from "@/api/productionManagement/productProcessRoute.js";
  import MaterialLedgerDialog from "@/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue";
  import MaterialDetailDialog from "@/views/productionManagement/productionOrder/components/MaterialDetailDialog.vue";
  // 领料/补料/领料详情/打印领料单功能已隐藏
  // import MaterialLedgerDialog from "@/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue";
  // import MaterialDetailDialog from "@/views/productionManagement/productionOrder/components/MaterialDetailDialog.vue";
  // import MaterialSupplementDialog from "@/views/productionManagement/productionOrder/components/MaterialSupplementDialog.vue";
  // import PrintMaterialRequisition from "@/views/productionManagement/productionOrder/components/PrintMaterialRequisition.vue";
  import PIMTable from "@/components/PIMTable/PIMTable.vue";
  import { listPage } from "@/api/productionManagement/processRoute.js";
  // 领料功能已隐藏
  // import {
  //   listMaterialPickingDetail,
  //   listMaterialPickingBom,
  // } from "@/api/productionManagement/productionOrder.js";
  const NewProductOrder = defineAsyncComponent(() =>
    import("@/views/productionManagement/productionOrder/New.vue")
  );
@@ -222,13 +265,23 @@
    total: 0,
  });
  const tableColumn = ref([
  const processColumnWidth = computed(() => {
    if (!tableData.value || tableData.value.length === 0) return "200px";
    const maxProcesses = Math.max(
      ...tableData.value.map(row => row.processRouteStatus?.length || 0)
    );
    if (maxProcesses === 0) return "100px";
    // 每个工序圆圈 36px + 线条 30px = 66px,额外加 60px 边距和文字空间
    return `${maxProcesses * 66 + 60}px`;
  });
  const tableColumn = computed(() => [
    {
      label: "生产订单号",
      prop: "npsNo",
      width: "150px",
    },
    // 1.待开始、2.进行中、3.已完成、4.已取消
    // 1.待开始、2.进行中、3.已完成、4.已取消、5.已结束
    {
      label: "状态",
      prop: "status",
@@ -241,6 +294,8 @@
          ? "进行中"
          : val === 3
          ? "已完成"
          : val === 5
          ? "已结束"
          : "已取消",
      formatType: val =>
        val === 1
@@ -249,7 +304,9 @@
          ? "warning"
          : val === 3
          ? "success"
          : "danger",
          : val === 5
          ? "danger"
          : "info",
    },
    {
      label: "产品名称",
@@ -273,6 +330,13 @@
    {
      label: "完成数量",
      prop: "completeQuantity",
    },
    {
      label: "工序生产进度",
      prop: "processRouteStatus",
      dataType: "slot",
      slot: "processRouteStatus",
      width: processColumnWidth.value,
    },
    {
      dataType: "slot",
@@ -304,7 +368,7 @@
      label: "操作",
      align: "center",
      fixed: "right",
      width: 360,
      width: 280,
      operation: [
        {
          name: "工艺路线",
@@ -317,7 +381,7 @@
        {
          name: "绑定工艺路线",
          type: "text",
          showHide: row => !row.processRouteCode,
          showHide: row => !row.processRouteCode && !row.endOrder,
          clickFun: row => {
            openBindRouteDialog(row, "add");
          },
@@ -325,7 +389,7 @@
        {
          name: "更换工艺路线",
          type: "text",
          showHide: row => row.processRouteCode,
          showHide: row => row.processRouteCode && !row.endOrder,
          clickFun: row => {
            openBindRouteDialog(row, "change");
          },
@@ -337,18 +401,64 @@
            showSourceData(row);
          },
        },
        // 领料/补料/领料详情/打印领料单功能已隐藏
        // {
        //   name: "领料",
        //   type: "text",
        //   color: "#5EC7AB",
        //   showHide: row => !row.endOrder && !row.returned,
        //   clickFun: row => {
        //     openMaterialDialog(row);
        //   },
        // },
        // {
        //   name: "补料",
        //   type: "text",
        //   color: "#5EC7AB",
        //   showHide: row => !row.endOrder && !row.returned,
        //   clickFun: row => {
        //     openMaterialSupplementDialog(row);
        //   },
        // },
        // {
        //   name: "领料详情",
        //   type: "text",
        //   color: "#5EC7AB",
        //   clickFun: row => {
        //     openMaterialDetailDialog(row);
        //   },
        // },
        // {
        //   name: "打印领料单",
        //   type: "text",
        //   color: "#5EC7AB",
        //   showHide: row => !row.endOrder,
        //   clickFun: row => {
        //     handlePrint(row);
        //   },
        // },
        {
          name: "领料",
          name: "生产追溯",
          type: "text",
          color: "#409eff",
          clickFun: row => {
            openMaterialDialog(row);
            router.push({
              path: "/productionManagement/productionTraceability",
              query: {
                npsNo: row.npsNo,
                productName: row.productName,
                model: row.model,
              },
            });
          },
        },
        {
          name: "领料详情",
          name: "结束订单",
          type: "text",
          color: "red",
          showHide: row => !row.endOrder,
          clickFun: row => {
            openMaterialDetailDialog(row);
            handleEndOrder(row);
          },
        },
      ],
@@ -381,7 +491,7 @@
    if (!Number.isFinite(n)) return 0;
    if (n <= 0) return 0;
    if (n >= 100) return 100;
    return Math.round(n);
    return parseFloat(n.toFixed(2));
  };
  // 30/50/80/100 分段颜色:红/橙/蓝/绿
@@ -419,14 +529,52 @@
    orderId: null,
    routeId: null,
  });
  const materialDialogVisible = ref(false);
  const currentMaterialOrder = ref(null);
  const materialDetailDialogVisible = ref(false);
  const currentMaterialDetailOrder = ref(null);
  // 领料/补料/领料详情功能已隐藏
  // const materialDialogVisible = ref(false);
  // const currentMaterialOrder = ref(null);
  // const materialDetailDialogVisible = ref(false);
  // const currentMaterialDetailOrder = ref(null);
  // const materialSupplementDialogVisible = ref(false);
  // const currentMaterialSupplementOrder = ref(null);
  // 打印领料单功能已隐藏
  // const printOrderRow = ref(null);
  // const printMaterialList = ref([]);
  // const handlePrint = async row => {
  //   printOrderRow.value = row;
  //   proxy.$modal.loading("正在获取领料数据...");
  //   try {
  //     printMaterialList.value = [];
  //     const detailRes = await listMaterialPickingDetail(row.id);
  //     const detailList = Array.isArray(detailRes?.data)
  //       ? detailRes.data
  //       : detailRes?.data?.records || [];
  //
  //     if (detailList.length > 0) {
  //       printMaterialList.value = detailList;
  //     }
  //
  //     if (printMaterialList.value.length === 0) {
  //       proxy.$modal.msgWarning("暂无领料数据");
  //       return;
  //     }
  //
  //     proxy.$nextTick(() => {
  //       setTimeout(() => {
  //         window.print();
  //       }, 800);
  //     });
  //   } catch (e) {
  //     console.error("获取领料数据失败:", e);
  //     proxy.$modal.msgError("获取领料数据失败");
  //   } finally {
  //     proxy.$modal.closeLoading();
  //   }
  // };
  const openBindRouteDialog = async (row, type) => {
    bindForm.orderId = row.id;
    bindForm.routeId = type === "add" ? null : row.processRouteCode;
    bindForm.routeId = type === "add" ? null : row.technologyRoutingId;
    bindRouteDialogVisible.value = true;
    routeOptions.value = [];
    if (!row.productModelId) {
@@ -468,15 +616,21 @@
    }
  };
  const openMaterialDialog = row => {
    currentMaterialOrder.value = row;
    materialDialogVisible.value = true;
  };
  // 领料/补料/领料详情功能已隐藏
  // const openMaterialDialog = row => {
  //   currentMaterialOrder.value = row;
  //   materialDialogVisible.value = true;
  // };
  const openMaterialDetailDialog = async row => {
    currentMaterialDetailOrder.value = row;
    materialDetailDialogVisible.value = true;
  };
  // const openMaterialDetailDialog = async row => {
  //   currentMaterialDetailOrder.value = row;
  //   materialDetailDialogVisible.value = true;
  // };
  // const openMaterialSupplementDialog = row => {
  //   currentMaterialSupplementOrder.value = row;
  //   materialSupplementDialogVisible.value = true;
  // };
  const handleReset = () => {
    searchForm.value = {
@@ -519,10 +673,35 @@
    const params = { ...searchForm.value, ...page };
    params.entryDate = undefined;
    productOrderListPage(params)
      .then(res => {
        tableLoading.value = false;
        tableData.value = res.data.records;
      .then(async res => {
        const records = res.data.records || [];
        // 为每个订单查询对应的工序进度数据
        const processPromises = records.map(async item => {
          if (item.npsNo) {
            try {
              const workOrderRes = await productWorkOrderPage({
                npsNo: item.npsNo,
                size: 100,
              });
              const workOrders = workOrderRes.data.records || [];
              // 按照工序顺序排序(如果有顺序字段,假设为 orderNum 或按返回顺序)
              // 转换为 processRouteStatus 格式
              const processRouteStatus = workOrders.map(wo => ({
                name: wo.operationName || "未知工序",
                percentage: wo.completionStatus > 100 ? 100 : wo.completionStatus,
              }));
              return { ...item, processRouteStatus };
            } catch (error) {
              console.error(`获取工单 ${item.npsNo} 进度失败:`, error);
              return { ...item, processRouteStatus: [] };
            }
          }
          return { ...item, processRouteStatus: [] };
        });
        tableData.value = await Promise.all(processPromises);
        page.total = res.data.total;
        tableLoading.value = false;
      })
      .catch(() => {
        tableLoading.value = false;
@@ -549,8 +728,10 @@
          bomNo: row.bomNo || "",
          description: data.description || "",
          quantity: row.quantity || 0,
          technologyRoutingId: data.technologyRoutingId,
          orderId,
          type: "order",
          editable: !row.endOrder,
        },
      });
    } catch (e) {
@@ -635,14 +816,34 @@
    })
      .then(() => {
        proxy.download(
          "/productOrder/export",
          "/productionOrder/export",
          { ...searchForm.value },
          "生产订单.xlsx"
          "生产订单数据.xlsx"
        );
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
      });
  };
  // 结束订单
  const handleEndOrder = row => {
    ElMessageBox.confirm(`是否确认结束订单:${row.npsNo}?`, "提示", {
      confirmButtonText: "确定",
      cancelButtonText: "取消",
      type: "warning",
    })
      .then(() => {
        const params = {
          id: row.id,
          endOrder: true,
        };
        updateProductOrder(params).then(() => {
          proxy.$modal.msgSuccess("结束订单成功");
          getList();
        });
      })
      .catch(() => {});
  };
  const handleConfirmRoute = () => {};
@@ -681,6 +882,64 @@
  .table_list {
    margin-top: unset;
  }
  .process-progress-container {
    display: inline-flex;
    align-items: center;
    padding: 10px 0;
    white-space: nowrap;
    .process-step {
      display: flex;
      align-items: center;
      position: relative;
      .step-content {
        display: flex;
        flex-direction: column;
        align-items: center;
        z-index: 1;
        .step-circle {
          width: 36px;
          height: 36px;
          border-radius: 50%;
          border: 2px solid #409eff;
          display: flex;
          align-items: center;
          justify-content: center;
          background-color: #fff;
          margin-bottom: 4px;
          .step-percentage {
            font-size: 11px;
            font-weight: bold;
          }
          &.is-completed {
            border-color: #67c23a;
            .step-percentage {
              color: #67c23a;
            }
          }
        }
        .step-name {
          font-size: 12px;
          color: #606266;
          white-space: nowrap;
        }
      }
      .step-line {
        width: 30px;
        height: 1px;
        background-color: #dcdfe6;
        margin: 0 -2px;
        margin-top: -20px; // 向上偏移以对齐圆心
      }
    }
  }
</style>
<style lang="scss">
  .status-cell {