src/views/procurementManagement/purchaseApplication/index.vue
@@ -95,10 +95,6 @@
                         prop="projectName"
                         width="280"
                         show-overflow-tooltip />
        <el-table-column label="销售合同号"
                         prop="salesContractNo"
                         width="170"
                         show-overflow-tooltip />
        <el-table-column label="申请人"
                         prop="applicantName"
                         width="100"
@@ -202,6 +198,16 @@
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="采购合同号:"
                          prop="purchaseContractNumber">
              <el-input v-model="form.purchaseContractNumber"
                        placeholder="请输入"
                        clearable />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="供应商名称:"
                          prop="supplierId">
              <el-select v-model="form.supplierId"
@@ -227,22 +233,6 @@
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="销售合同号:"
                          prop="salesLedgerId">
              <el-select v-model="form.salesLedgerId"
                         placeholder="请选择"
                         filterable
                         clearable
                         style="width: 100%"
                         @change="salesLedgerChange">
                <el-option v-for="item in salesContractList"
                           :key="item.id"
                           :label="item.salesContractNo"
                           :value="item.id" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="申请人:"
                          prop="applicantId">
              <el-select v-model="form.applicantId"
@@ -257,9 +247,7 @@
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="8">
          <el-col :span="12">
            <el-form-item label="期望到货日期:"
                          prop="expectedArrivalDate">
              <el-date-picker style="width: 100%"
@@ -271,7 +259,160 @@
                              clearable />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="最佳到站日期:"
                          prop="bestArrivalDate">
              <el-date-picker style="width: 100%"
                              v-model="form.bestArrivalDate"
                              value-format="YYYY-MM-DD"
                              format="YYYY-MM-DD"
                              type="date"
                              placeholder="请选择"
                              clearable />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="付款方式:"
                          prop="paymentMethod">
              <el-input v-model="form.paymentMethod"
                        placeholder="请输入"
                        clearable />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="8">
            <el-form-item label="录入人:"
                          prop="recorderId">
              <el-select v-model="form.recorderId"
                         placeholder="请选择"
                         filterable
                         clearable
                         style="width: 100%">
                <el-option v-for="item in userList"
                           :key="item.userId"
                           :label="item.nickName"
                           :value="item.userId" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label="签订日期:"
                          prop="executionDate">
              <el-date-picker style="width: 100%"
                              v-model="form.executionDate"
                              value-format="YYYY-MM-DD"
                              format="YYYY-MM-DD"
                              type="date"
                              placeholder="请选择"
                              clearable />
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label="录入日期:"
                          prop="entryDate">
              <el-date-picker style="width: 100%"
                              v-model="form.entryDate"
                              value-format="YYYY-MM-DD"
                              format="YYYY-MM-DD"
                              type="date"
                              placeholder="请选择"
                              clearable />
            </el-form-item>
          </el-col>
        </el-row>
        <div class="form-section-title">采购入库提交</div>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="申请提交时间:"
                          prop="applicationSubmitTime">
              <el-date-picker style="width: 100%"
                              v-model="form.applicationSubmitTime"
                              value-format="YYYY-MM-DD HH:mm:ss"
                              format="YYYY-MM-DD HH:mm:ss"
                              type="datetime"
                              placeholder="请选择"
                              clearable />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="采购类别:"
                          prop="purchaseCategory">
              <el-radio-group v-model="form.purchaseCategory"
                              @change="purchaseCategoryChange">
                <el-radio value="客存">客存</el-radio>
                <el-radio value="代储">代储</el-radio>
                <el-radio value="采购入库">采购入库</el-radio>
                <el-radio value="采购直销">采购直销</el-radio>
              </el-radio-group>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="物流方式:"
                          prop="logisticsMethod">
              <el-select v-model="form.logisticsMethod"
                         placeholder="请选择"
                         clearable
                         style="width: 100%">
                <el-option label="厂家配送"
                           value="厂家配送" />
                <el-option label="委外物流"
                           value="委外物流" />
                <el-option label="客户自提"
                           value="客户自提" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="提货方式:"
                          prop="pickupMethod">
              <el-radio-group v-model="form.pickupMethod">
                <el-radio value="暂不提货">暂不提货</el-radio>
                <el-radio value="提货">提货</el-radio>
              </el-radio-group>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="采购油库:"
                          prop="oilDepotId">
              <el-select v-model="form.oilDepotId"
                         placeholder="请选择"
                         clearable
                         popper-class="oil-depot-select-popper"
                         style="width: 100%">
                <el-option v-for="item in oilDepotList"
                           :key="item.id"
                           :label="item.depotName"
                           :value="item.id">
                  <div class="oil-depot-option">
                    <div class="oil-depot-option-title">{{ item.depotName }}</div>
                    <div class="oil-depot-option-line">负责人:{{ item.chargePerson }}</div>
                    <div class="oil-depot-option-line">联系电话:{{ item.phone }}</div>
                    <div class="oil-depot-option-line">所属公司:{{ item.company }}</div>
                    <div class="oil-depot-option-line">启用日期:{{ item.enableDate }}</div>
                  </div>
                </el-option>
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="客户名称:"
                          prop="customerName">
              <el-input v-model="form.customerName"
                        placeholder="仅采购类别为代储/采购直销时可填写"
                        clearable
                        :disabled="!['代储', '采购直销'].includes(form.purchaseCategory)" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="合同金额(元):"
                          prop="contractAmount">
              <el-input-number v-model="form.contractAmount"
@@ -282,12 +423,34 @@
                               style="width: 100%" />
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label="付款方式:"
                          prop="paymentMethod">
              <el-input v-model="form.paymentMethod"
                        placeholder="请输入"
                        clearable />
          <el-col :span="12">
            <el-form-item label="采购数量(吨):"
                          prop="purchaseQuantity">
              <el-input-number v-model="form.purchaseQuantity"
                               :precision="0"
                               :step="1"
                               :min="0"
                               placeholder="请输入"
                               style="width: 100%" />
            </el-form-item>
          </el-col>
        </el-row>
        <div class="form-section-title">系统信息</div>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="采购申请订单状态:"
                          prop="purchaseOrderStatus">
              <el-select v-model="form.purchaseOrderStatus"
                         placeholder="请选择"
                         clearable
                         style="width: 100%">
                <el-option label="已申请,未装车"
                           value="已申请,未装车" />
                <el-option label="以出厂,未入库"
                           value="以出厂,未入库" />
                <el-option label="已入库"
                           value="已入库" />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
@@ -327,6 +490,10 @@
          <el-table-column label="数量"
                           prop="quantity"
                           width="80" />
          <el-table-column label="库存预警数量"
                           prop="warnNum"
                           width="120"
                           show-overflow-tooltip />
          <el-table-column label="税率(%)"
                           prop="taxRate"
                           width="80" />
@@ -381,6 +548,14 @@
                        clearable
                        type="textarea"
                        :rows="2" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="24">
            <el-form-item label="附件材料:"
                          prop="storageBlobDTOS">
              <FileUpload v-model:file-list="fileList" />
            </el-form-item>
          </el-col>
        </el-row>
@@ -518,6 +693,19 @@
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="库存预警数量:"
                          prop="warnNum">
              <el-input-number v-model="productForm.warnNum"
                               :precision="2"
                               :step="0.1"
                               :min="0"
                               clearable
                               style="width: 100%" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="是否质检:"
                          prop="isChecked">
              <el-radio-group v-model="productForm.isChecked">
@@ -569,12 +757,11 @@
    delApplication,
    convertToOrder,
  } from "@/api/procurementManagement/purchaseApplication.js";
  import {
    getSalesNo,
    getOptions,
  } from "@/api/procurementManagement/procurementLedger.js";
  import { getOptions } from "@/api/procurementManagement/procurementLedger.js";
  import { modelList, productTreeList } from "@/api/basicData/product.js";
  import { allOilDepot } from "@/api/basicData/oilDepot.js";
  import { userListNoPage } from "@/api/system/user.js";
  import FileUpload from "@/components/AttachmentUpload/file/index.vue";
  const { proxy } = getCurrentInstance();
  const { tax_rate } = proxy.useDict("tax_rate");
@@ -591,8 +778,9 @@
  const productSelectedRows = ref([]);
  const supplierOptions = ref([]);
  const supplierList = ref([]);
  const salesContractList = ref([]);
  const userList = ref([]);
  const oilDepotList = ref([]);
  const fileList = ref([]);
  const productOptions = ref([]);
  const modelOptions = ref([]);
  const tableLoading = ref(false);
@@ -626,19 +814,46 @@
    form: {
      id: "",
      applicationNo: "",
      // 以下字段与「新增采购台账」保持一致
      purchaseContractNumber: "",
      supplierId: "",
      projectName: "",
      salesContractNo: "",
      salesLedgerId: "",
      applicantId: "",
      applyDate: "",
      expectedArrivalDate: "",
      contractAmount: "",
      bestArrivalDate: "",
      paymentMethod: "",
      recorderId: "",
      executionDate: "",
      entryDate: "",
      applicationSubmitTime: "",
      purchaseCategory: "",
      logisticsMethod: "",
      pickupMethod: "",
      oilDepotId: "",
      customerName: "",
      purchaseQuantity: "",
      purchaseOrderStatus: "",
      contractAmount: "",
      remarks: "",
    },
    rules: {
      applyDate: [{ required: true, message: "请选择", trigger: "change" }],
      projectName: [
        { required: true, message: "请输入项目名称", trigger: "blur" },
      ],
      supplierId: [{ required: true, message: "请输入", trigger: "blur" }],
      entryDate: [{ required: true, message: "请选择", trigger: "change" }],
      executionDate: [{ required: true, message: "请选择", trigger: "change" }],
      purchaseCategory: [
        { required: true, message: "请选择采购类别", trigger: "change" },
      ],
      oilDepotId: [
        { required: true, message: "请选择采购油库", trigger: "change" },
      ],
      purchaseQuantity: [
        { required: true, message: "请输入采购数量", trigger: "change" },
      ],
    },
  });
  const { form, rules } = toRefs(data);
@@ -656,6 +871,7 @@
      taxInclusiveTotalPrice: "",
      taxExclusiveTotalPrice: "",
      invoiceType: "",
      warnNum: "",
      isChecked: false,
      remark: "",
    },
@@ -668,6 +884,7 @@
      taxInclusiveUnitPrice: [
        { required: true, message: "请输入", trigger: "blur" },
      ],
      warnNum: [{ required: true, message: "请选择", trigger: "change" }],
    },
  });
  const { productForm, productRules } = toRefs(productFormData);
@@ -747,14 +964,14 @@
    supplierList.value = list.filter(item => item.isWhite === 0);
  };
  const getSalesContractList = async () => {
    const res = await getSalesNo();
    salesContractList.value = Array.isArray(res) ? res : res?.data || [];
  };
  const getUserList = async () => {
    const res = await userListNoPage();
    userList.value = res.data || [];
  };
  const getOilDepotList = async () => {
    const res = await allOilDepot();
    oilDepotList.value = res?.data || [];
  };
  const getProductOptions = async () => {
@@ -905,9 +1122,11 @@
    productFormVisible.value = false;
  };
  const salesLedgerChange = value => {
    const matched = salesContractList.value.find(item => item.id === value);
    form.value.salesContractNo = matched ? matched.salesContractNo : "";
  // 采购类别改变:仅代储/采购直销时客户名称可用,切换后清空
  const purchaseCategoryChange = val => {
    if (!["代储", "采购直销"].includes(val)) {
      form.value.customerName = "";
    }
  };
  const openForm = async (type, row) => {
@@ -917,26 +1136,38 @@
    form.value = {
      id: "",
      applicationNo: "",
      purchaseContractNumber: "",
      supplierId: "",
      projectName: "",
      salesContractNo: "",
      salesLedgerId: "",
      applicantId: "",
      applyDate: dayjs().format("YYYY-MM-DD"),
      expectedArrivalDate: "",
      contractAmount: "",
      bestArrivalDate: "",
      paymentMethod: "",
      recorderId: "",
      executionDate: "",
      entryDate: dayjs().format("YYYY-MM-DD"),
      applicationSubmitTime: "",
      purchaseCategory: "",
      logisticsMethod: "",
      pickupMethod: "",
      oilDepotId: "",
      customerName: "",
      purchaseQuantity: "",
      purchaseOrderStatus: "",
      contractAmount: "",
      remarks: "",
    };
    productData.value = [];
    productSelectedRows.value = [];
    fileList.value = [];
    try {
      await Promise.all([
        getSupplierOptions(),
        getSalesContractList(),
        getUserList(),
        getProductOptions(),
        getOilDepotList(),
      ]);
    } catch (error) {
      proxy.$modal.msgError("加载基础数据失败");
@@ -952,6 +1183,7 @@
      const res = await getApplicationById({ id: row.id });
      form.value = { ...(res.data || {}), id: row.id };
      productData.value = res.data?.productData || [];
      fileList.value = res.data?.storageBlobVOS || [];
    } catch (error) {
      proxy.$modal.msgError("加载采购申请详情失败");
      return;
@@ -969,14 +1201,28 @@
    const payload = {
      supplierId: form.value.supplierId || null,
      projectName: form.value.projectName,
      salesContractNo: form.value.salesContractNo,
      salesLedgerId: form.value.salesLedgerId || null,
      applyDate: form.value.applyDate,
      expectedArrivalDate: form.value.expectedArrivalDate || null,
      contractAmount:
        form.value.contractAmount === "" ? null : form.value.contractAmount,
      paymentMethod: form.value.paymentMethod,
      remarks: form.value.remarks,
      // 与「新增采购台账」对齐的字段
      purchaseContractNumber: form.value.purchaseContractNumber,
      bestArrivalDate: form.value.bestArrivalDate || null,
      recorderId: form.value.recorderId || null,
      executionDate: form.value.executionDate || null,
      entryDate: form.value.entryDate || null,
      applicationSubmitTime: form.value.applicationSubmitTime || null,
      purchaseCategory: form.value.purchaseCategory,
      logisticsMethod: form.value.logisticsMethod,
      pickupMethod: form.value.pickupMethod,
      oilDepotId: form.value.oilDepotId || null,
      customerName: form.value.customerName,
      purchaseQuantity:
        form.value.purchaseQuantity === "" ? null : form.value.purchaseQuantity,
      purchaseOrderStatus: form.value.purchaseOrderStatus,
      storageBlobDTOS: fileList.value,
      productData: productData.value.map(
        ({ id, purchaseApplicationId, ...rest }) => rest
      ),
@@ -1030,49 +1276,69 @@
      .catch(() => {});
  };
  const handleConvert = row => {
    ElMessageBox.confirm(
      `确认将申请单「${row.applicationNo}」转为采购订单吗?转订单后将自动提交采购审批。`,
      "转订单",
      {
        confirmButtonText: "确认",
        cancelButtonText: "取消",
        type: "warning",
      }
    )
      .then(async () => {
        const res = await convertToOrder(row.id);
        getList();
        ElMessageBox.confirm(
          `转订单成功,已生成采购台账(id:${res.data})。是否前往采购台账查看?`,
          "提示",
          {
            confirmButtonText: "前往查看",
            cancelButtonText: "留在本页",
            type: "success",
          }
        )
          .then(() => {
            const path = findLedgerPath(permissionStore.sidebarRouters);
            if (path) {
              router.push(path);
            } else {
              ElMessage.info("请前往「采购台账」查看生成的订单");
            }
          })
          .catch(() => {});
      })
      .catch(() => {});
  const handleConvert = async row => {
    try {
      await ElMessageBox.confirm(
        `确认将申请单「${row.applicationNo}」转为采购订单吗?转订单后将自动提交采购审批。`,
        "转订单",
        {
          confirmButtonText: "确认",
          cancelButtonText: "取消",
          type: "warning",
        }
      );
    } catch {
      return;
    }
    try {
      const res = await convertToOrder(row.id);
      getList();
      await ElMessageBox.confirm(
        `转订单成功,已生成采购台账(id:${res?.data})。是否前往采购台账查看?`,
        "提示",
        {
          confirmButtonText: "前往查看",
          cancelButtonText: "留在本页",
          type: "success",
        }
      );
      await gotoLedger();
    } catch {
      // 转订单失败由请求拦截器提示;点「留在本页」也走这里,不额外处理
    }
  };
  // 从侧边栏菜单中定位采购台账路由,找不到时不跳转仅提示
  // 跳转采购台账。路由匹配不上或 push 被拦截都给出可见提示,不静默
  const gotoLedger = async () => {
    const path = resolveLedgerPath();
    try {
      await router.push(path);
    } catch {
      ElMessage.warning(`跳转采购台账失败(${path}),请从侧边栏进入查看`);
    }
  };
  // 菜单标题可能与页面标题有空格差异,归一化后再比
  const normalizeMenuTitle = title => String(title ?? "").replace(/\s+/g, "").trim();
  const resolveLedgerPath = () => {
    const path =
      findLedgerPath(permissionStore.defaultRoutes) ||
      findLedgerPath(permissionStore.sidebarRouters);
    // 找不到菜单时兜底到仓库里多处使用的固定地址(采购管理首页卡片、首页快捷入口)
    return path || "/procurementManagement/procurementLedger";
  };
  // 从侧边栏菜单中定位采购台账路由
  const findLedgerPath = (routes, prefix = "") => {
    for (const route of routes || []) {
      if (!route) continue;
      const fullPath = String(route.path || "").startsWith("/")
        ? route.path
        : `${prefix}/${route.path || ""}`;
      if (route.meta?.title === "采购台账") {
        return fullPath;
      if (normalizeMenuTitle(route.meta?.title ?? route.title) === "采购台账") {
        return fullPath.replace(/\/{2,}/g, "/").replace(/\/$/, "");
      }
      const found = findLedgerPath(route.children, fullPath);
      if (found) return found;
@@ -1085,3 +1351,57 @@
    getSupplierOptions();
  });
</script>
<style scoped lang="scss">
  // 表单分栏标题:蓝色竖条 + 浅蓝底色,突出分区
  .form-section-title {
    margin: 18px 0 14px;
    padding: 8px 14px;
    font-size: 15px;
    font-weight: 600;
    color: #1d6fd8;
    background: linear-gradient(90deg, rgba(64, 158, 255, 0.14), rgba(64, 158, 255, 0.02));
    border-left: 4px solid #409eff;
    border-radius: 3px;
  }
  // 采购油库下拉选项:小卡片样式
  .oil-depot-option {
    padding: 8px 10px;
    .oil-depot-option-title {
      font-size: 14px;
      font-weight: 600;
      color: #303133;
      margin-bottom: 4px;
    }
    .oil-depot-option-line {
      font-size: 12px;
      color: #909399;
      line-height: 1.8;
    }
  }
</style>
<style lang="scss">
  // 下拉面板渲染在 body 下,需全局样式
  .oil-depot-select-popper {
    .el-select-dropdown__item {
      height: auto;
      line-height: normal;
      padding: 4px 0;
    }
    .oil-depot-option {
      border: 1px solid #e4e7ed;
      border-radius: 6px;
      background-color: #fafbfc;
      &:hover {
        border-color: #409eff;
        background-color: #f0f7ff;
      }
    }
  }
</style>