src/views/productionManagement/productionOrder/index.vue
@@ -3,32 +3,8 @@
    <div class="search_form">
      <el-form :model="searchForm"
               :inline="true">
        <el-form-item label="客户名称:">
          <el-input v-model="searchForm.customerName"
                    placeholder="请输入"
                    clearable
                    prefix-icon="Search"
                    style="width: 160px;"
                    @change="handleQuery" />
        </el-form-item>
        <el-form-item label="合同号:">
          <el-input v-model="searchForm.salesContractNo"
                    placeholder="请输入"
                    clearable
                    prefix-icon="Search"
                    style="width: 160px;"
                    @change="handleQuery" />
        </el-form-item>
        <el-form-item label="产品名称:">
          <el-input v-model="searchForm.productCategory"
                    placeholder="请输入"
                    clearable
                    prefix-icon="Search"
                    style="width: 160px;"
                    @change="handleQuery" />
        </el-form-item>
        <el-form-item label="规格:">
          <el-input v-model="searchForm.specificationModel"
                    placeholder="请输入"
                    clearable
                    prefix-icon="Search"
@@ -41,6 +17,8 @@
        </el-form-item>
      </el-form>
      <div>
        <el-button type="primary" @click="isShowNewModal = true">新增</el-button>
        <el-button type="danger" @click="handleDelete">删除</el-button>
        <el-button @click="handleOut">导出</el-button>
      </div>
    </div>
@@ -51,6 +29,8 @@
                :page="page"
                :tableLoading="tableLoading"
                :row-class-name="tableRowClassName"
                :isSelection="true"
                @selection-change="handleSelectionChange"
                @pagination="pagination">
        <template #completionStatus="{ row }">
          <el-progress
@@ -86,24 +66,107 @@
        </span>
      </template>
    </el-dialog>
    <new-product-order v-if="isShowNewModal"
                         v-model:visible="isShowNewModal"
                         type="qualified"
                         @completed="handleQuery" />
    <!-- 查看投入弹框 -->
    <el-dialog v-model="inputDialogVisible"
               title="投入"
               width="1000px">
      <PIMTable
        rowKey="id"
        :column="inputTableColumn"
        :tableData="inputTableData"
        :page="inputPage"
        :tableLoading="inputTableLoading"
        @pagination="handleInputPagination"
      />
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="inputDialogVisible = false">关闭</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- 领料弹框 -->
    <el-dialog v-model="pickingDialogVisible"
               title="工单领料"
               width="1200px"
               :close-on-click-modal="false">
      <el-table
        :data="pickingTableData"
        border
        size="small"
        @selection-change="handlePickingSelectionChange"
        :header-cell-style="{ background: '#f5f7fa' }"
        show-summary
        :summary-method="summarizePickingTable"
      >
        <el-table-column type="selection" width="50" align="center" />
        <el-table-column label="产品名称" prop="productName" min-width="120" />
        <el-table-column label="图纸编号" prop="model" min-width="100" />
        <el-table-column label="单位用量" prop="unitQuantity" min-width="100" align="center" />
        <el-table-column label="单位" prop="unit" min-width="100" align="center" />
        <el-table-column label="需求数量" prop="demandedQuantity" min-width="100" align="center" />
        <el-table-column label="已领料数量" prop="completedQuantity" min-width="100" align="center" />
        <el-table-column label="未领料数量" prop="unpickedQuantity" min-width="100" align="center" />
        <el-table-column label="领料数量" min-width="180" align="center" prop="quantity">
          <template #default="{ row }">
            <el-input-number
              v-model="row.quantity"
              :min="0"
              :max="row.unpickedQuantity"
              :precision="0"
              size="small"
              style="width: 160px"
              @change="(val) => handlePickingQuantityChange(val, row)"
            />
          </template>
        </el-table-column>
      </el-table>
      <div class="picking-footer-info">
        <span>已选 {{ pickingSelectedRows.length }} 条</span>
        <span>{{ pickingTableData.length }} 条记录</span>
      </div>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="success" @click="handlePickingNext">确认</el-button>
          <el-button @click="pickingDialogVisible = false">取消</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
  import { onMounted, ref } from "vue";
  import { onMounted, ref, computed } from "vue";
  import { ElMessageBox } from "element-plus";
  import { Setting } from '@element-plus/icons-vue';
  import dayjs from "dayjs";
  import { useRouter } from "vue-router";
  import {
    productOrderListPage,
    listProcessRoute,
    bindingRoute,
    listProcessBom,
    listProcessBom, delProductOrder, startOrPause,
  } from "@/api/productionManagement/productionOrder.js";
  import { listMain as getOrderProcessRouteMain } from "@/api/productionManagement/productProcessRoute.js";
  import { productionProductInputListPage } from "@/api/productionManagement/productionProductInput.js";
  import { listPage as listProductStructureRecord, pick as pickMaterial } from "@/api/productionManagement/productStructureRecord.js";
  import {fileDel} from "@/api/financialManagement/revenueManagement.js";
  import PIMTable from "@/components/PIMTable/PIMTable.vue";
  const NewProductOrder = defineAsyncComponent(() => import("@/views/productionManagement/productionOrder/New.vue"));
  const { proxy } = getCurrentInstance();
  const router = useRouter();
  const isShowNewModal = ref(false);
  const tableColumn = ref([
    {
@@ -112,24 +175,14 @@
      width: '120px',
    },
    {
      label: "销售合同号",
      prop: "salesContractNo",
      width: '150px',
    },
    {
      label: "客户名称",
      prop: "customerName",
      width: '200px',
    },
    {
      label: "产品名称",
      prop: "productCategory",
      width: '120px',
    },
    {
      label: "规格",
      label: "图纸编号",
      prop: "specificationModel",
      width: '120px',
      width: '160px',
    },
    {
      label: "工艺路线编号",
@@ -143,6 +196,10 @@
    {
      label: "完成数量",
      prop: "completeQuantity",
    },
    {
      label: "库存数量",
      prop: "inventoryQuantity",
    },
    {
      dataType: "slot",
@@ -174,8 +231,31 @@
      label: "操作",
      align: "center",
      fixed: "right",
      width: 200,
      width: 360,
      operation: [
        {
          name: "开始",
          type: "success",
          showHide: row => row.status === '待生产',
          clickFun: row => {
            handleStartOrPause(row);
          },
        },
        {
          name: "暂停",
          type: "danger",
          showHide: row => row.status === '生产中',
          clickFun: row => {
            handleStartOrPause(row);
          },
        },
        {
          name: "领料",
          type: "success",
          clickFun: row => {
            showPickingDialog(row);
          },
        },
        {
          name: "工艺路线",
          type: "text",
@@ -192,10 +272,17 @@
          },
        },
        {
          name: "产品结构",
          name: "物料清单",
          type: "text",
          clickFun: row => {
            showProductStructure(row);
          },
        },
        {
          name: "查看投入",
          type: "text",
          clickFun: row => {
            showInputDialog(row);
          },
        },
      ],
@@ -208,14 +295,49 @@
    size: 100,
    total: 0,
  });
  const selectedRows = ref([]);
  // 查看投入相关
  const inputDialogVisible = ref(false);
  const inputTableData = ref([]);
  const inputTableLoading = ref(false);
  const inputCurrentRow = ref(null);
  const inputPage = reactive({
    current: 1,
    size: 100,
    total: 0,
  });
  const inputTableColumn = ref([
    {
      label: '投入产品名称',
      prop: 'productName',
    },
    {
      label: '图纸编号',
      prop: 'model'
    },
    {
      label: '投入数量',
      prop: 'quantity',
    },
    {
      label: '单位',
      prop: 'unit',
    },
  ]);
  // 领料相关
  const pickingDialogVisible = ref(false);
  const pickingTableData = ref([]);
  const pickingSelectedRows = ref([]);
  const pickingForm = reactive({
    orderId: null,
  });
  const data = reactive({
    searchForm: {
      customerName: "",
      salesContractNo: "",
      projectName: "",
      productCategory: "",
      specificationModel: "",
    },
  });
  const { searchForm } = toRefs(data);
@@ -239,9 +361,12 @@
  // 添加表行类名方法
  const tableRowClassName = ({ row }) => {
    if (!row.deliveryDate) return '';
    if (row.isFh) return '';
    const diff = row.deliveryDaysDiff;
    if (diff === undefined || diff === null || diff === '' || diff < 0) return '';
    if (diff === 15) {
      return 'yellow';
    } else if (diff === 10) {
@@ -348,17 +473,19 @@
    const orderId = row.id;
    try {
      const res = await getOrderProcessRouteMain(orderId);
      const data = res.data || {};
      if (!data || !data.id) {
      const dataList = res.data || [];
      if (!dataList || dataList.length === 0 || !dataList[0].id) {
        proxy.$modal.msgWarning("未找到关联的工艺路线");
        return;
      }
      const data = dataList[0];
      router.push({
        path: "/productionManagement/processRouteItem",
        query: {
          id: data.id,
          processRouteCode: data.processRouteCode || "",
          productName: data.productName || "",
          drawingNumber: data.drawingNumber || "",
          model: data.model || "",
          bomNo: data.bomNo || "",
          description: data.description || "",
@@ -372,17 +499,70 @@
    }
  };
  const showProductStructure = row => {
  const handleStartOrPause = async (row) => {
    const operation = row.status === '待生产' ? 1 : 2;
    const operationText = operation === 1 ? "开始" : "暂停";
    try {
      await startOrPause({ id: row.id, operation });
      proxy.$modal.msgSuccess(`${operationText}成功`);
      getList();
    } catch (e) {
      console.error(`${operationText}失败:`, e);
      proxy.$modal.msgError(`${operationText}失败`);
    }
  };
  const showProductStructure = async row => {
    let bomNo = row.bomNo || "";
    if (!bomNo && row.id) {
      try {
        const res = await getOrderProcessRouteMain(row.id);
        const dataList = res.data || [];
        if (dataList && dataList.length > 0 && dataList[0].bomNo) {
          bomNo = dataList[0].bomNo;
        }
      } catch (e) {
        console.error("获取BOM编号失败:", e);
      }
    }
    router.push({
      path: "/productionManagement/productStructureDetail",
      query: {
        id: row.id,
        bomNo: row.bomNo || "",
        bomNo: bomNo,
        drawingNumber: row.drawingNumber || "",
        productName: row.productCategory || "",
        productModelName: row.specificationModel || "",
        orderId: row.id,
        type: "order",
      },
    });
  };
  // 表格选择数据
  const handleSelectionChange = (selection) => {
    selectedRows.value = selection;
  };
  const handleDelete = () => {
    let ids = [];
    if (selectedRows.value.length > 0) {
      ids = selectedRows.value.map((item) => item.id);
    } else {
      proxy.$modal.msgWarning("请选择数据");
      return;
    }
    ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "导出", {
      confirmButtonText: "确认",
      cancelButtonText: "取消",
      type: "warning",
    }).then(() => {
      delProductOrder(ids).then((res) => {
        proxy.$modal.msgSuccess("删除成功");
        getList();
      });
    }).catch(() => {
      proxy.$modal.msg("已取消");
    });
  };
@@ -403,6 +583,117 @@
  const handleConfirmRoute = () => {};
  // 显示查看投入弹框
  const showInputDialog = (row) => {
    inputCurrentRow.value = row;
    inputDialogVisible.value = true;
    inputPage.current = 1;
    inputPage.total = 0;
    fetchInputData();
  };
  // 查看投入分页
  const handleInputPagination = (obj) => {
    inputPage.current = obj.page;
    inputPage.size = obj.limit;
    fetchInputData();
  };
  // 获取投入数据
  const fetchInputData = () => {
    inputTableLoading.value = true;
    const params = { productOrderId: inputCurrentRow.value.id, ...inputPage };
    productionProductInputListPage(params)
      .then(res => {
        inputTableLoading.value = false;
        inputTableData.value = res.data.records;
        inputPage.total = res.data.total;
      })
      .catch(err => {
        inputTableLoading.value = false;
        console.error("获取投入数据失败:", err);
      });
  };
  // 显示领料弹框
  const showPickingDialog = async (row) => {
    pickingForm.orderId = row.id;
    pickingDialogVisible.value = true;
    pickingTableData.value = [];
    // 获取物料清单数据
    try {
      const res = await listProductStructureRecord({ productOrderId: row.id });
      const materials = res.data?.records || [];
      pickingTableData.value = materials.map(item => ({
        ...item,
        quantity: 0,
        unpickedQuantity: (item.demandedQuantity || 0) - (item.pickedQuantity || 0),
      }));
    } catch (e) {
      console.error("获取物料清单失败:", e);
      proxy.$modal.msgError("获取物料清单失败");
    }
  };
  // 领料表格选择变化
  const handlePickingSelectionChange = (selection) => {
    pickingSelectedRows.value = selection;
  };
  // 领料数量变化处理
  const handlePickingQuantityChange = (val, row) => {
    if (val > row.unpickedQuantity) {
      proxy.$modal.msgWarning("领料数量不能超过未领料数量");
      row.quantity = row.unpickedQuantity;
    }
  };
  // 确认领料
  const handlePickingNext = async () => {
    if (pickingSelectedRows.value.length === 0) {
      proxy.$modal.msgWarning("请选择要领料的物料");
      return;
    }
    // 校验领料数量
    for (const row of pickingSelectedRows.value) {
      if (row.quantity > row.unpickedQuantity) {
        proxy.$modal.msgWarning(`${row.productName} 的领料数量不能超过未领料数量`);
        return;
      }
      if (row.quantity <= 0) {
        proxy.$modal.msgWarning(`${row.productName} 的领料数量必须大于0`);
        return;
      }
    }
    // 提交领料数据
    try {
      const pickData = pickingSelectedRows.value.map(row => ({
        productOrderId: row.productOrderId,
        productStructureRecordId: row.id,
        productModelId: row.productModelId,
        quantity: row.quantity,
      }));
      await pickMaterial(pickData);
      proxy.$modal.msgSuccess("领料成功");
      pickingDialogVisible.value = false;
    } catch (e) {
      console.error("领料失败:", e);
      proxy.$modal.msgError("领料失败");
    }
  };
  // 领料表格合计方法
  const summarizePickingTable = (param) => {
    return proxy.summarizeTable(param, [
      "quantity",
      "unpickedQuantity",
      "inventoryQuantity",
      "demandedQuantity",
    ]);
  };
  onMounted(() => {
    getList();
  });
@@ -413,19 +704,28 @@
  align-items: start;
}
::v-deep .yellow {
:deep(.yellow) {
  background-color: #FAF0DE;
}
::v-deep .pink {
:deep(.pink) {
  background-color: #FAE1DE;
}
::v-deep .red {
  background-color: #f80202;
:deep(.red) {
  background-color: #FFCCCC;
}
::v-deep .purple{
:deep(.purple){
  background-color: #F4DEFA;
}
.picking-footer-info {
  display: flex;
  justify-content: space-between;
  padding: 10px;
  font-size: 14px;
  color: #606266;
}
</style>