gaoluyang
5 天以前 d16397e58995f4acbd5e8a715ff9186a59258bd7
src/views/productionManagement/productionOrder/index.vue
@@ -1,198 +1,585 @@
<template>
   <div class="app-container">
      <div class="search_form">
         <div>
            <span class="search_title">客户名称:</span>
            <el-input
               v-model="searchForm.customerName"
               style="width: 240px"
               placeholder="请输入"
               @change="handleQuery"
               clearable
               prefix-icon="Search"
            />
            <span class="search_title ml10">项目名称:</span>
            <el-input
               v-model="searchForm.projectName"
               style="width: 240px"
               placeholder="请输入"
               @change="handleQuery"
               clearable
               prefix-icon="Search"
            />
            <span class="search_title ml10">录入日期:</span>
            <el-date-picker v-model="searchForm.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange"
                                    placeholder="请选择" clearable @change="changeDaterange" />
            <el-button type="primary" @click="handleQuery" style="margin-left: 10px"
            >搜索</el-button
            >
         </div>
         <div>
            <el-button @click="handleOut">导出</el-button>
         </div>
      </div>
      <div class="table_list">
         <PIMTable
            rowKey="id"
            :column="tableColumn"
            :tableData="tableData"
            :page="page"
            :tableLoading="tableLoading"
            @pagination="pagination"
         ></PIMTable>
      </div>
   </div>
  <div class="app-container">
    <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"
                    style="width: 160px;"
                    @change="handleQuery" />
        </el-form-item>
        <el-form-item>
          <el-button type="primary"
                     @click="handleQuery">搜索</el-button>
        </el-form-item>
      </el-form>
      <div>
        <el-button type="primary" @click="openCreateOrder">新增订单</el-button>
        <el-button @click="handleOut">导出</el-button>
        <el-button type="danger" plain :disabled="selectedRows.length === 0" @click="handleBatchDelete">批量删除</el-button>
      </div>
    </div>
    <div class="table_list">
      <PIMTable rowKey="id"
                :column="tableColumn"
                :tableData="tableData"
                :page="page"
                :tableLoading="tableLoading"
                :isSelection="true"
                @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>
      </PIMTable>
    </div>
    <el-dialog v-model="bindRouteDialogVisible"
               title="绑定工艺路线"
               width="500px">
      <el-form label-width="90px">
        <el-form-item label="工艺路线">
          <el-select v-model="bindForm.routeId"
                     placeholder="请选择工艺路线"
                     style="width: 100%;"
                     :loading="bindRouteLoading">
            <el-option v-for="item in routeOptions"
                       :key="item.id"
                       :label="`${item.processRouteCode || ''}`"
                       :value="item.id" />
          </el-select>
        </el-form-item>
      </el-form>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="bindRouteDialogVisible = false">取 消</el-button>
          <el-button type="primary"
                     :loading="bindRouteSaving"
                     @click="handleBindRouteConfirm">确 认</el-button>
        </span>
      </template>
    </el-dialog>
    <el-dialog v-model="orderFormVisible"
               title="新增生产订单"
               width="520px"
               @close="() => proxy?.resetForm?.('orderFormRef')">
      <el-form ref="orderFormRef"
               :model="orderForm"
               :rules="orderRules"
               label-width="120px"
               label-position="top">
        <el-form-item label="产品大类" prop="productId">
          <el-tree-select v-model="orderForm.productId"
                          placeholder="请选择"
                          clearable
                          check-strictly
                          :data="productOptions"
                          :render-after-expand="false"
                          style="width: 100%"
                          @change="getModels" />
        </el-form-item>
        <el-form-item label="规格型号" prop="productModelId">
          <el-select v-model="orderForm.productModelId"
                     placeholder="请选择"
                     clearable
                     style="width: 100%"
                     @change="getProductModel">
            <el-option v-for="item in modelOptions"
                       :key="item.id"
                       :label="item.model"
                       :value="item.id" />
          </el-select>
        </el-form-item>
        <el-form-item label="单位" prop="unit">
          <el-input v-model="orderForm.unit"
                    placeholder="请输入"
                    clearable />
        </el-form-item>
        <el-row :gutter="12">
          <el-col :span="12">
            <el-form-item label="数量" prop="quantity">
              <el-input-number v-model="orderForm.quantity"
                               :step="0.1"
                               :min="0"
                               :precision="2"
                               placeholder="请输入"
                               style="width: 100%"
                               @change="calculateFromQuantity" />
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <span class="dialog-footer">
          <el-button type="primary" @click="submitCreateOrder">确 认</el-button>
          <el-button @click="orderFormVisible = false">取 消</el-button>
        </span>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import {onMounted, ref} from "vue";
import { ElMessageBox } from "element-plus";
import dayjs from "dayjs";
import {schedulingListPage} from "@/api/productionManagement/productionOrder.js";
const { proxy } = getCurrentInstance();
  import { onMounted, ref, reactive, toRefs, getCurrentInstance } from "vue";
  import { ElMessageBox } from "element-plus";
  import dayjs from "dayjs";
  import { useRouter } from "vue-router";
  import {
    productOrderListPage,
    addProductOrder,
    listProcessRoute,
    bindingRoute,
    deleteProductOrder,
  } from "@/api/productionManagement/productionOrder.js";
  import { productTreeList, modelList } from "@/api/basicData/product.js";
  import { listMain as getOrderProcessRouteMain } from "@/api/productionManagement/productProcessRoute.js";
  const { proxy } = getCurrentInstance();
const tableColumn = ref([
   {
      label: "录入日期",
      prop: "entryDate",
      width: 120,
   },
   {
      label: "合同号",
      prop: "salesContractNo",
      width: 220,
   },
   {
      label: "客户合同号",
      prop: "customerContractNo",
      width: 250,
   },
   {
      label: "客户名称",
      prop: "customerName",
      width: 250,
   },
   {
      label: "项目名称",
      prop: "projectName",
      width:300
   },
   {
      label: "付款状态",
      prop: "status",
      dataType: "tag",
      formatType: (params) => {
         if (params == '未完成') {
            return "danger";
         } else if (params == '已完成') {
            return "success";
         } else {
            return null;
         }
      },
   },
   {
      label: "产品大类",
      prop: "productCategory",
      width: 160,
   },
   {
      label: "规格型号",
      prop: "specificationModel",
      width: 220,
   },
   {
      label: "单位",
      prop: "unit",
      width:90
   },
   {
      label: "数量",
      prop: "quantity",
   },
   {
      label: "排产数量",
      prop: "schedulingNum",
      width: 100,
   },
   {
      label: "完工数量",
      prop: "successNum",
      width: 100,
   },
]);
const tableData = ref([]);
const tableLoading = ref(false);
const page = reactive({
   current: 1,
   size: 100,
   total: 0,
});
  const router = useRouter();
const data = reactive({
   searchForm: {
      customerName: "",
      projectName: "",
      entryDate: null, // 录入日期
      entryDateStart: undefined,
      entryDateEnd: undefined,
   },
});
const { searchForm } = toRefs(data);
  const tableColumn = ref([
    {
      label: "生产订单号",
      prop: "npsNo",
      width: '120px',
    },
    {
      label: "产品大类",
      prop: "productCategory",
      width: '120px',
    },
    {
      label: "规格型号",
      prop: "specificationModel",
      width: '120px',
    },
    {
      label: "工艺路线编号",
      prop: "processRouteCode",
      width: '200px',
    },
    {
      label: "需求数量",
      prop: "quantity",
    },
    {
      label: "完成数量",
      prop: "completeQuantity",
    },
    {
      dataType: "slot",
      label: "完成进度",
      prop: "completionStatus",
      slot: "completionStatus",
      width: 180,
    },
    {
      label: "开始日期",
      prop: "startTime",
      formatData: val => (val ? dayjs(val).format("YYYY-MM-DD") : ""),
      width: 120,
    },
    {
      label: "结束日期",
      prop: "endTime",
      formatData: val => (val ? dayjs(val).format("YYYY-MM-DD") : ""),
      width: 120,
    },
    {
      dataType: "action",
      label: "操作",
      align: "center",
      fixed: "right",
      width: 200,
      operation: [
        {
          name: "工艺路线",
          type: "text",
          clickFun: row => {
            showRouteItemModal(row);
          },
        },
        {
          name: "绑定工艺路线",
          type: "text",
          showHide: row => !row.processRouteCode,
          clickFun: row => {
            openBindRouteDialog(row);
          },
        },
        {
          name: "产品结构",
          type: "text",
          clickFun: row => {
            showProductStructure(row);
          },
        },
      ],
    },
  ]);
  const tableData = ref([]);
  const tableLoading = ref(false);
  const selectedRows = ref([]);
  const page = reactive({
    current: 1,
    size: 100,
    total: 0,
  });
// 查询列表
/** 搜索按钮操作 */
const handleQuery = () => {
   page.current = 1;
   getList();
};
const pagination = (obj) => {
   page.current = obj.page;
   page.size = obj.limit;
   getList();
};
const changeDaterange = (value) => {
   if (value) {
      searchForm.value.entryDateStart = value[0];
      searchForm.value.entryDateEnd = value[1];
   } else {
      searchForm.value.entryDateStart = undefined;
      searchForm.value.entryDateEnd = undefined;
   }
   handleQuery();
};
const getList = () => {
   tableLoading.value = true;
   // 构造一个新的对象,不包含entryDate字段
   const params = { ...searchForm.value, ...page };
   params.entryDate = undefined
   schedulingListPage(params).then((res) => {
      tableLoading.value = false;
      tableData.value = res.data.records;
      page.total = res.data.total;
   }).catch(() => {
      tableLoading.value = false;
   })
};
  const data = reactive({
    searchForm: {
      customerName: "",
      salesContractNo: "",
      projectName: "",
      productCategory: "",
      specificationModel: "",
    },
  });
  const { searchForm } = toRefs(data);
// 导出
const handleOut = () => {
   ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
      confirmButtonText: "确认",
      cancelButtonText: "取消",
      type: "warning",
   })
      .then(() => {
         proxy.download("/salesLedger/scheduling/export", {}, "生产订单.xlsx");
      })
      .catch(() => {
         proxy.$modal.msg("已取消");
      });
};
  // 新增订单弹框数据
  const orderFormVisible = ref(false);
  const orderFormRef = ref(null);
  const orderFormState = reactive({
    orderForm: {
      productId: null,
      productCategory: "",
      productModelId: "",
      specificationModel: "",
      unit: "",
      quantity: null,
    },
    orderRules: {
      productId: [{ required: true, message: "请选择产品大类", trigger: "change" }],
      productModelId: [{ required: true, message: "请选择规格型号", trigger: "change" }],
      specificationModel: [{ required: true, message: "请选择规格型号", trigger: "change" }],
      unit: [{ required: true, message: "请输入单位", trigger: "blur" }],
      quantity: [{ required: true, message: "请输入数量", trigger: "blur" }],
    },
  });
  const { orderForm, orderRules } = toRefs(orderFormState);
  const productOptions = ref([]);
  const modelOptions = ref([]);
onMounted(() => {
   getList();
});
  const toProgressPercentage = val => {
    const n = Number(val);
    if (!Number.isFinite(n)) return 0;
    if (n <= 0) return 0;
    if (n >= 100) return 100;
    return Math.round(n);
  };
  // 30/50/80/100 分段颜色:红/橙/蓝/绿
  const progressColor = percentage => {
    const p = toProgressPercentage(percentage);
    if (p < 30) return "#f56c6c";
    if (p < 50) return "#e6a23c";
    if (p < 80) return "#409eff";
    return "#67c23a";
  };
  // 绑定工艺路线弹框
  const bindRouteDialogVisible = ref(false);
  const bindRouteLoading = ref(false);
  const bindRouteSaving = ref(false);
  const routeOptions = ref([]);
  const bindForm = reactive({
    orderId: null,
    routeId: null,
  });
  const openBindRouteDialog = async row => {
    bindForm.orderId = row.id;
    bindForm.routeId = null;
    bindRouteDialogVisible.value = true;
    routeOptions.value = [];
    if (!row.productModelId) {
      proxy.$modal.msgWarning("当前订单缺少产品型号,无法查询工艺路线");
      bindRouteDialogVisible.value = false;
      return;
    }
    bindRouteLoading.value = true;
    try {
      const res = await listProcessRoute({ productModelId: row.productModelId });
      routeOptions.value = res.data || [];
    } catch (e) {
      console.error("获取工艺路线列表失败:", e);
      proxy.$modal.msgError("获取工艺路线列表失败");
    } finally {
      bindRouteLoading.value = false;
    }
  };
  const handleBindRouteConfirm = async () => {
    if (!bindForm.routeId) {
      proxy.$modal.msgWarning("请选择工艺路线");
      return;
    }
    bindRouteSaving.value = true;
    try {
      await bindingRoute({
        id: bindForm.orderId,
        routeId: bindForm.routeId,
      });
      proxy.$modal.msgSuccess("绑定成功");
      bindRouteDialogVisible.value = false;
      getList();
    } catch (e) {
      console.error("绑定工艺路线失败:", e);
      proxy.$modal.msgError("绑定工艺路线失败");
    } finally {
      bindRouteSaving.value = false;
    }
  };
  // 查询列表
  /** 搜索按钮操作 */
  const handleQuery = () => {
    page.current = 1;
    getList();
  };
  const pagination = obj => {
    page.current = obj.page;
    page.size = obj.limit;
    getList();
  };
  const changeDaterange = value => {
    if (value) {
      searchForm.value.entryDateStart = value[0];
      searchForm.value.entryDateEnd = value[1];
    } else {
      searchForm.value.entryDateStart = undefined;
      searchForm.value.entryDateEnd = undefined;
    }
    handleQuery();
  };
  const getList = () => {
    tableLoading.value = true;
    // 构造一个新的对象,不包含entryDate字段
    const params = { ...searchForm.value, ...page };
    params.entryDate = undefined;
    productOrderListPage(params)
      .then(res => {
        tableLoading.value = false;
        tableData.value = res.data.records;
        page.total = res.data.total;
      })
      .catch(() => {
        tableLoading.value = false;
      });
  };
  const showRouteItemModal = async row => {
    const orderId = row.id;
    try {
      const res = await getOrderProcessRouteMain(orderId);
      const data = res.data || {};
      if (!data || !data.id) {
        proxy.$modal.msgWarning("未找到关联的工艺路线");
        return;
      }
      router.push({
        path: "/productionManagement/processRouteItem",
        query: {
          id: data.id,
          processRouteCode: data.processRouteCode || "",
          productName: data.productName || "",
          model: data.model || "",
          bomNo: data.bomNo || "",
          description: data.description || "",
          orderId,
          type: "order",
        },
      });
    } catch (e) {
      console.error("获取工艺路线主信息失败:", e);
      proxy.$modal.msgError("获取工艺路线信息失败");
    }
  };
  const showProductStructure = row => {
    router.push({
      path: "/productionManagement/productStructureDetail",
      query: {
        id: row.id,
        bomNo: row.bomNo || "",
        productName: row.productCategory || "",
        productModelName: row.specificationModel || "",
        orderId: row.id,
        type: "order",
      },
    });
  };
  // 导出
  const handleOut = () => {
    ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
      confirmButtonText: "确认",
      cancelButtonText: "取消",
      type: "warning",
    })
      .then(() => {
        proxy.download("/productOrder/export", {...searchForm.value}, "生产订单.xlsx");
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
      });
  };
  // 表格选择数据
  const handleSelectionChange = (selection) => {
    selectedRows.value = selection || [];
  };
  // 批量删除
  const handleBatchDelete = async () => {
    if (selectedRows.value.length === 0) {
      proxy.$modal.msgWarning("请选择要删除的数据");
      return;
    }
    const ids = selectedRows.value.map(item => item.productModelId).filter(Boolean);
    if (ids.length === 0) {
      proxy.$modal.msgWarning("选中数据缺少ID,无法删除");
      return;
    }
    try {
      await ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "提示", {
        confirmButtonText: "确认",
        cancelButtonText: "取消",
        type: "warning",
      });
      await deleteProductOrder(ids);
      proxy.$modal.msgSuccess("删除成功");
      selectedRows.value = [];
      getList();
    } catch (e) {
      // 取消或失败
      if (e !== 'cancel' && e !== 'close') {
        console.error("批量删除失败:", e);
        proxy.$modal.msgError("删除失败,请重试");
      }
    }
  };
  const handleConfirmRoute = () => {};
  const openCreateOrder = async () => {
    orderForm.value = {
      productId: null,
      productCategory: "",
      productModelId: "",
      specificationModel: "",
      unit: "",
      quantity: null,
    };
    await loadProductOptions();
    orderFormVisible.value = true;
  };
  const loadProductOptions = async () => {
    const tree = await productTreeList();
    productOptions.value = Array.isArray(tree?.data) ? convertIdToValue(tree.data) : convertIdToValue(tree || []);
  };
  const convertIdToValue = data => {
    return (data || []).map(item => {
      const { id, children, ...rest } = item;
      const newItem = { ...rest, value: id };
      if (children && children.length > 0) {
        newItem.children = convertIdToValue(children);
      }
      return newItem;
    });
  };
  const findNodeById = (nodes, productId) => {
    for (let i = 0; i < nodes.length; i++) {
      if (nodes[i].value === productId) {
        return nodes[i].label;
      }
      if (nodes[i].children && nodes[i].children.length > 0) {
        const foundNode = findNodeById(nodes[i].children, productId);
        if (foundNode) {
          return foundNode;
        }
      }
    }
    return null;
  };
  const getModels = async value => {
    orderForm.value.productId = value;
    orderForm.value.productCategory = findNodeById(productOptions.value, value);
    const res = await modelList({ id: value });
    modelOptions.value = res || [];
  };
  const getProductModel = value => {
    const index = modelOptions.value.findIndex(item => item.id === value);
    if (index !== -1) {
      orderForm.value.specificationModel = modelOptions.value[index].model;
      orderForm.value.unit = modelOptions.value[index].unit;
    } else {
      orderForm.value.specificationModel = null;
      orderForm.value.unit = null;
    }
  };
  const calculateFromQuantity = () => {
    // 占位:数量变更时可在此扩展逻辑
  };
  const submitCreateOrder = async () => {
    proxy.$refs["orderFormRef"]?.validate(async valid => {
      if (!valid) return;
      try {
        await addProductOrder(orderForm.value);
        proxy.$modal.msgSuccess("新增成功");
        orderFormVisible.value = false;
        getList();
      } catch (e) {
        console.error("新增生产订单失败", e);
        proxy.$modal.msgError("新增失败,请稍后重试");
      }
    });
  };
  onMounted(() => {
    getList();
  });
</script>
<style scoped lang="scss"></style>
<style scoped lang="scss">
.search_form{
  align-items: start;
}</style>