src/views/procurementManagement/procurementLedger/index.vue
@@ -19,7 +19,7 @@
                      clearable
                      :prefix-icon="Search" />
          </el-form-item>
          <el-form-item label="销售合同号:">
          <el-form-item label="订单编号:">
            <el-input v-model="searchForm.salesContractNo"
                      placeholder="请输入"
                      clearable
@@ -53,6 +53,7 @@
      <div style="display: flex;justify-content: flex-end;margin-bottom: 20px;">
        <el-button type="primary"
                   @click="openForm('add')">新增台账</el-button>
        <el-button type="primary" plain @click="handleImport">导入</el-button>
        <el-button @click="handleOut">导出</el-button>
        <el-button type="danger"
                   plain
@@ -111,7 +112,7 @@
                         prop="purchaseContractNumber"
                         width="160"
                         show-overflow-tooltip />
        <el-table-column label="销售合同号"
        <el-table-column label="订单编号"
                         prop="salesContractNo"
                          width="160"
                         show-overflow-tooltip />
@@ -155,6 +156,10 @@
        <el-table-column label="录入日期"
                         prop="entryDate"
                         width="100"
                         show-overflow-tooltip />
        <el-table-column label="备注"
                         prop="remarks"
                         width="200"
                         show-overflow-tooltip />
        <el-table-column fixed="right"
                         label="操作"
@@ -202,7 +207,7 @@
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="销售合同号:"
            <el-form-item label="订单编号:"
                          prop="salesLedgerId">
              <el-select v-model="form.salesLedgerId"
                         placeholder="请选择"
@@ -228,7 +233,7 @@
                <el-option v-for="item in supplierList"
                           :key="item.id"
                           :label="item.supplierName"
                           :value="item.id" />
                                        :value="item.id" >{{item.supplierName + '---' + item.supplierType}}</el-option>
              </el-select>
            </el-form-item>
          </el-col>
@@ -287,50 +292,6 @@
                              type="date"
                              placeholder="请选择"
                              clearable />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="24">
            <el-form-item>
              <template #label>
                <div style="display: flex; align-items: center; justify-content: space-between; width: 100%;">
                  <span>审批人选择:</span>
                  <el-button type="primary" size="small" @click="addApproverNode" icon="Plus">新增节点</el-button>
                </div>
              </template>
              <div class="approver-nodes-container">
                <div
                  v-for="(node, index) in approverNodes"
                  :key="node.id"
                  class="approver-node-item"
                >
                  <div class="approver-node-header">
                    <span class="approver-node-label">审批节点 {{ index + 1 }}</span>
                    <el-button
                      v-if="approverNodes.length > 1"
                      type="danger"
                      size="small"
                      text
                      @click="removeApproverNode(index)"
                      icon="Delete"
                    >删除</el-button>
                  </div>
                  <el-select
                    v-model="node.userId"
                    placeholder="请选择审批人"
                    filterable
                    style="width: 100%;"
                  >
                    <el-option
                      v-for="user in userList"
                      :key="user.userId"
                      :label="user.nickName"
                      :value="user.userId"
                    />
                  </el-select>
                </div>
              </div>
            </el-form-item>
          </el-col>
        </el-row>
@@ -449,8 +410,8 @@
        <el-row :gutter="30">
          <el-col :span="24">
            <el-form-item label="备注·:"
                          prop="remark">
              <el-input v-model="form.remark"
                          prop="remarks">
              <el-input v-model="form.remarks"
                        placeholder="请输入"
                        clearable
                        type="textarea"
@@ -461,7 +422,7 @@
        <el-row :gutter="30">
          <el-col :span="24">
            <el-form-item label="附件材料:"
                          prop="remark">
                          prop="purchaseLedgerFiles">
              <el-upload v-model:file-list="fileList"
                         :action="upload.url"
                         multiple
@@ -484,6 +445,41 @@
          </el-col>
        </el-row>
      </el-form>
    </FormDialog>
    <!-- 导入弹窗 -->
    <FormDialog
      v-model="importUpload.open"
      :title="importUpload.title"
      :width="'600px'"
      @close="importUpload.open = false"
      @confirm="submitImportFile"
      @cancel="importUpload.open = false"
    >
      <el-upload
        ref="importUploadRef"
        :limit="1"
        accept=".xlsx,.xls"
        :action="importUpload.url"
        :headers="importUpload.headers"
        :before-upload="importUpload.beforeUpload"
        :on-success="importUpload.onSuccess"
        :on-error="importUpload.onError"
        :on-progress="importUpload.onProgress"
        :on-change="importUpload.onChange"
        :auto-upload="false"
        drag
      >
        <i class="el-icon-upload"></i>
        <div class="el-upload__text">
          将文件拖到此处,或<em>点击上传</em>
        </div>
        <template #tip>
          <div class="el-upload__tip">
            仅支持 xls/xlsx,大小不超过 10MB。
            <el-button link type="primary" @click="downloadTemplate">下载导入模板</el-button>
          </div>
        </template>
      </el-upload>
    </FormDialog>
    <FormDialog v-model="productFormVisible"
               :title="productOperationType === 'add' ? '新增产品' : '编辑产品'"
@@ -544,12 +540,14 @@
                         placeholder="请选择"
                         clearable
                         @change="mathNum">
                <el-option label="0"
                           :value="0" />
                <el-option label="1"
                           value="1" />
                           :value="1" />
                <el-option label="6"
                           value="6" />
                           :value="6" />
                <el-option label="13"
                           value="13" />
                           :value="13" />
              </el-select>
            </el-form-item>
          </el-col>
@@ -559,7 +557,7 @@
            <el-form-item label="含税单价(元):"
                          prop="taxInclusiveUnitPrice">
              <el-input-number v-model="productForm.taxInclusiveUnitPrice"
                               :precision="2"
                               :precision="3"
                               :step="0.1"
                               :min="0"
                               clearable
@@ -572,7 +570,7 @@
                          prop="quantity">
              <el-input-number :step="0.1"
                               clearable
                               :precision="2"
                               :precision="3"
                               :min="0"
                               style="width: 100%"
                               v-model="productForm.quantity"
@@ -586,7 +584,7 @@
            <el-form-item label="含税总价(元):"
                          prop="taxInclusiveTotalPrice">
              <el-input-number v-model="productForm.taxInclusiveTotalPrice"
                               :precision="2"
                               :precision="3"
                               :step="0.1"
                               :min="0"
                               clearable
@@ -598,7 +596,7 @@
            <el-form-item label="不含税总价(元):"
                          prop="taxExclusiveTotalPrice">
              <el-input-number v-model="productForm.taxExclusiveTotalPrice"
                               :precision="2"
                               :precision="3"
                               :step="0.1"
                               :min="0"
                               clearable
@@ -625,7 +623,7 @@
            <el-form-item label="库存预警数量:"
                          prop="warnNum">
              <el-input-number v-model="productForm.warnNum"
                               :precision="2"
                               :precision="3"
                               :step="0.1"
                               :min="0"
                               clearable
@@ -717,16 +715,6 @@
  import dayjs from "dayjs";
  const userStore = useUserStore();
  // 审批人节点(仿销售台账发货审批人)
  const approverNodes = ref([{ id: 1, userId: null }]);
  let nextApproverId = 2;
  const addApproverNode = () => {
    approverNodes.value.push({ id: nextApproverId++, userId: null });
  };
  const removeApproverNode = (index) => {
    approverNodes.value.splice(index, 1);
  };
  // 订单审批状态显示文本
  const approvalStatusText = {
@@ -878,12 +866,9 @@
      purchaseContractNumber: [
        { required: true, message: "请输入", trigger: "blur" },
      ],
      approverId: [
        { required: true, message: "请选择审批人", trigger: "change" },
      ],
      projectName: [
        { required: true, message: "请输入项目名称", trigger: "blur" },
      ],
      // projectName: [
      //   { required: true, message: "请输入项目名称", trigger: "blur" },
      // ],
      supplierId: [{ required: true, message: "请输入", trigger: "blur" }],
      entryDate: [{ required: true, message: "请选择", trigger: "change" }],
      executionDate: [{ required: true, message: "请选择", trigger: "change" }],
@@ -915,7 +900,7 @@
      unit: "",
      quantity: "",
      taxInclusiveUnitPrice: "",
      taxRate: "",
      taxRate: 0,
      taxInclusiveTotalPrice: "",
      taxExclusiveTotalPrice: "",
      invoiceType: "",
@@ -930,7 +915,7 @@
      taxInclusiveUnitPrice: [
        { required: true, message: "请输入", trigger: "blur" },
      ],
      taxRate: [{ required: true, message: "请选择", trigger: "change" }],
      // taxRate: [{ required: true, message: "请选择", trigger: "change" }],
      warnNum: [{ required: true, message: "请选择", trigger: "change" }],
      taxInclusiveTotalPrice: [
        { required: true, message: "请输入", trigger: "blur" },
@@ -938,7 +923,7 @@
      taxExclusiveTotalPrice: [
        { required: true, message: "请输入", trigger: "blur" },
      ],
      invoiceType: [{ required: true, message: "请选择", trigger: "change" }],
      // invoiceType: [{ required: true, message: "请选择", trigger: "change" }],
      isChecked: [{ required: true, message: "请选择", trigger: "change" }],
    },
  });
@@ -949,6 +934,71 @@
    // 设置上传的请求头部
    headers: { Authorization: "Bearer " + getToken() },
  });
  // 导入相关
  const importUploadRef = ref(null);
  const importUpload = reactive({
    title: "导入采购台账",
    open: false,
    url: import.meta.env.VITE_APP_BASE_API + "/purchase/ledger/import",
    headers: { Authorization: "Bearer " + getToken() },
    isUploading: false,
    beforeUpload: (file) => {
      const isExcel = file.name.endsWith(".xlsx") || file.name.endsWith(".xls");
      const isLt10M = file.size / 1024 / 1024 < 10;
      if (!isExcel) {
        proxy.$modal.msgError("上传文件只能是 xlsx/xls 格式!");
        return false;
      }
      if (!isLt10M) {
        proxy.$modal.msgError("上传文件大小不能超过 10MB!");
        return false;
      }
      return true;
    },
    onChange: (file, fileList) => {
      // noop
    },
    onProgress: (event, file, fileList) => {
      // noop
    },
    onSuccess: (response, file, fileList) => {
      importUpload.isUploading = false;
      if (response?.code === 200) {
        proxy.$modal.msgSuccess("导入成功");
        importUpload.open = false;
        if (importUploadRef.value) {
          importUploadRef.value.clearFiles?.();
        }
        getList();
      } else {
        proxy.$modal.msgError(response?.msg || "导入失败");
      }
    },
    onError: () => {
      importUpload.isUploading = false;
      proxy.$modal.msgError("导入失败,请重试");
    },
  });
  const handleImport = () => {
    importUpload.title = "导入采购台账";
    importUpload.open = true;
    importUpload.isUploading = false;
    if (importUploadRef.value) {
      importUploadRef.value.clearFiles?.();
    }
  };
  // 下载导入模板(如后端路径不同,可在此处调整)
  const downloadTemplate = () => {
    proxy.download("/purchase/ledger/exportTemplate", {}, "采购台账导入模板.xlsx");
  };
  const submitImportFile = () => {
    importUpload.isUploading = true;
    proxy.$refs["importUploadRef"]?.submit?.();
  };
  const changeDaterange = value => {
    if (value) {
@@ -962,7 +1012,7 @@
  };
  const formattedNumber = (row, column, cellValue) => {
    return parseFloat(cellValue).toFixed(2);
    return parseFloat(cellValue).toFixed(3);
  };
  // 查询列表
  /** 搜索按钮操作 */
@@ -1014,19 +1064,12 @@
    }
    try {
      // 获取审批人ID字符串
      const approveUserIds = approverNodes.value
        .filter(node => node.userId)
        .map(node => node.userId)
        .join(",");
      let params = {
        productData: proxy.HaveJson(productData.value),
        supplierId: form.value.supplierId,
        paymentMethod: form.value.paymentMethod,
        recorderId: form.value.recorderId,
        projectName: form.value.projectName,
        approveUserIds: approveUserIds,
        templateName: templateName.value.trim(),
      };
      console.log("template params ===>", params, "currentTemplateId:", currentTemplateId.value);
@@ -1086,6 +1129,9 @@
      {
        ticketsNum: { noDecimal: true }, // 不保留小数
        futureTickets: { noDecimal: true }, // 不保留小数
        taxInclusiveUnitPrice: { decimalPlaces: 3 },
        taxInclusiveTotalPrice: { decimalPlaces: 3 },
        taxExclusiveTotalPrice: { decimalPlaces: 3 },
      }
    );
  };
@@ -1149,7 +1195,9 @@
  };
  // 主表合计方法
  const summarizeMainTable = param => {
    return proxy.summarizeTable(param, ["contractAmount"]);
    return proxy.summarizeTable(param, ["contractAmount"], {
      contractAmount: { decimalPlaces: 3 },
    });
  };
  // 子表合计方法
  const summarizeProTable = param => {
@@ -1157,7 +1205,11 @@
      "taxInclusiveUnitPrice",
      "taxInclusiveTotalPrice",
      "taxExclusiveTotalPrice",
    ]);
    ], {
      taxInclusiveUnitPrice: { decimalPlaces: 3 },
      taxInclusiveTotalPrice: { decimalPlaces: 3 },
      taxExclusiveTotalPrice: { decimalPlaces: 3 },
    });
  };
  // 打开弹框
  const openForm = async (type, row) => {
@@ -1177,9 +1229,6 @@
    templateName.value = "";
    filterInputValue.value = "";
    isTemplateNameDuplicate.value = false;
    // 重置审批人节点(默认一个空节点)
    approverNodes.value = [{ id: 1, userId: null }];
    nextApproverId = 2;
    try {
      // 并行加载基础数据
      const [userRes, salesRes, supplierRes] = await Promise.all([
@@ -1218,15 +1267,6 @@
          form.value = { ...purchaseRes };
          productData.value = purchaseRes.productData || [];
          fileList.value = purchaseRes.salesLedgerFiles || [];
          // 如果编辑时有审批人,解析审批人ID字符串并设置到节点中
          if (purchaseRes.approveUserIds) {
            const approverIds = purchaseRes.approveUserIds.split(",");
            approverNodes.value = approverIds.map((id, index) => ({
              id: index + 1,
              userId: Number(id)
            }));
            nextApproverId = approverIds.length + 1;
          }
        } catch (error) {
          console.error("加载采购台账数据失败:", error);
          proxy.$modal.msgError("加载数据失败");
@@ -1295,14 +1335,6 @@
  const submitForm = () => {
    proxy.$refs["formRef"].validate(valid => {
      if (valid) {
        // 审批人必填校验(所有节点都要选人)
        const hasEmptyApprover = approverNodes.value.some(node => !node.userId);
        if (hasEmptyApprover) {
          proxy.$modal.msgError("请为所有审批节点选择审批人!");
          return;
        }
        const approveUserIds = approverNodes.value.map(node => node.userId).join(",");
        if (productData.value.length > 0) {
          // 新增时,需要从每个产品对象中删除 id 字段
          let processedProductData = productData.value;
@@ -1323,7 +1355,6 @@
        }
        form.value.tempFileIds = tempFileIds;
        form.value.type = 2;
        form.value.approveUserIds = approveUserIds;
        // 如果salesLedgerId为空,则不传递salesContractNo
        if (!form.value.salesLedgerId) {
@@ -1347,16 +1378,16 @@
  // 关闭弹框
  const closeDia = () => {
    proxy.resetForm("formRef");
    // 重置审批人节点(默认一个空节点)
    approverNodes.value = [{ id: 1, userId: null }];
    nextApproverId = 2;
    dialogFormVisible.value = false;
  };
  // 打开产品弹框
  const openProductForm = async (type, row, index) => {
    productOperationType.value = type;
    productOperationIndex.value = index;
    productForm.value = {};
    productForm.value = {
      taxRate: 0,
      invoiceType: "增普票"
    };
    proxy.resetForm("productFormRef");
    productFormVisible.value = true;
    
@@ -1543,7 +1574,7 @@
          delProduct(ids).then(res => {
            proxy.$modal.msgSuccess("删除成功");
            closeProductDia();
            getSalesLedgerWithProducts({ id: currentId.value, type: 2 }).then(
            getPurchaseById({ id: currentId.value, type: 2 }).then(
              res => {
                productData.value = res.productData;
              }
@@ -1578,20 +1609,12 @@
  const handleDelete = () => {
    let ids = [];
    if (selectedRows.value.length > 0) {
      // 检查是否有他人维护的数据
      const unauthorizedData = selectedRows.value.filter(
        item => item.recorderName !== userStore.nickName
      );
      if (unauthorizedData.length > 0) {
        proxy.$modal.msgWarning("不可删除他人维护的数据");
        return;
      }
      ids = selectedRows.value.map(item => item.id);
      ids = selectedRows.value.filter(item => item.salesLedgerId === null).map(item => item.id);
    } else {
      proxy.$modal.msgWarning("请选择数据");
      return;
    }
    ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "导出", {
    ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除", {
      confirmButtonText: "确认",
      cancelButtonText: "取消",
      type: "warning",
@@ -1615,10 +1638,10 @@
    return `${year}-${month}-${day}`;
  }
  const mathNum = () => {
    if (!productForm.value.taxRate) {
      proxy.$modal.msgWarning("请先选择税率");
      return;
    }
    // if (!productForm.value.taxRate) {
    //   proxy.$modal.msgWarning("请先选择税率");
    //   return;
    // }
    if (!productForm.value.taxInclusiveUnitPrice) {
      return;
    }
@@ -1629,22 +1652,31 @@
    productForm.value.taxInclusiveTotalPrice =
      proxy.calculateTaxIncludeTotalPrice(
        productForm.value.taxInclusiveUnitPrice,
        productForm.value.quantity
        productForm.value.quantity,
        3
      );
    if (productForm.value.taxRate) {
      // 不含税总价计算
      productForm.value.taxExclusiveTotalPrice =
        proxy.calculateTaxExclusiveTotalPrice(
          productForm.value.taxInclusiveTotalPrice,
          productForm.value.taxRate
          productForm.value.taxRate,
          3
        );
    }else{
      productForm.value.taxExclusiveTotalPrice =
      proxy.calculateTaxIncludeTotalPrice(
          productForm.value.taxInclusiveUnitPrice,
          productForm.value.quantity,
          3
      );
    }
  };
  const reverseMathNum = field => {
    if (!productForm.value.taxRate) {
      proxy.$modal.msgWarning("请先选择税率");
      return;
    }
    // if (!productForm.value.taxRate) {
    //   proxy.$modal.msgWarning("请先选择税率");
    //   return;
    // }
    const taxRate = Number(productForm.value.taxRate);
    if (!taxRate) return;
@@ -1667,7 +1699,7 @@
        productForm.value.taxInclusiveUnitPrice = (
          Number(productForm.value.taxInclusiveTotalPrice) /
          Number(productForm.value.quantity)
        ).toFixed(2);
        ).toFixed(3);
        // 确保结果不为负数
        if (Number(productForm.value.taxInclusiveUnitPrice) < 0) {
          productForm.value.taxInclusiveUnitPrice = "0";
@@ -1678,7 +1710,7 @@
        productForm.value.quantity = (
          Number(productForm.value.taxInclusiveTotalPrice) /
          Number(productForm.value.taxInclusiveUnitPrice)
        ).toFixed(2);
        ).toFixed(3);
        // 确保结果不为负数
        if (Number(productForm.value.quantity) < 0) {
          productForm.value.quantity = "0";
@@ -1688,7 +1720,7 @@
      productForm.value.taxExclusiveTotalPrice = (
        Number(productForm.value.taxInclusiveTotalPrice) /
        (1 + taxRate / 100)
      ).toFixed(2);
      ).toFixed(3);
      // 确保结果不为负数
      if (Number(productForm.value.taxExclusiveTotalPrice) < 0) {
        productForm.value.taxExclusiveTotalPrice = "0";
@@ -1698,7 +1730,7 @@
      productForm.value.taxInclusiveTotalPrice = (
        Number(productForm.value.taxExclusiveTotalPrice) *
        (1 + taxRate / 100)
      ).toFixed(2);
      ).toFixed(3);
      // 确保结果不为负数
      if (Number(productForm.value.taxInclusiveTotalPrice) < 0) {
        productForm.value.taxInclusiveTotalPrice = "0";
@@ -1708,7 +1740,7 @@
        productForm.value.taxInclusiveUnitPrice = (
          Number(productForm.value.taxInclusiveTotalPrice) /
          Number(productForm.value.quantity)
        ).toFixed(2);
        ).toFixed(3);
        // 确保结果不为负数
        if (Number(productForm.value.taxInclusiveUnitPrice) < 0) {
          productForm.value.taxInclusiveUnitPrice = "0";
@@ -1719,7 +1751,7 @@
        productForm.value.quantity = (
          Number(productForm.value.taxInclusiveTotalPrice) /
          Number(productForm.value.taxInclusiveUnitPrice)
        ).toFixed(2);
        ).toFixed(3);
        // 确保结果不为负数
        if (Number(productForm.value.quantity) < 0) {
          productForm.value.quantity = "0";
@@ -1831,57 +1863,6 @@
  .select-button-group {
    display: flex;
    align-items: center;
  }
  // 审批人节点容器样式
  .approver-nodes-container {
    display: flex;
    flex-wrap: wrap;
    gap: 16px;
    padding: 16px;
    background-color: #f8f9fa;
    border-radius: 4px;
    border: 1px solid #e4e7ed;
  }
  .approver-node-item {
    flex: 0 0 calc(33.333% - 12px);
    min-width: 200px;
    padding: 12px;
    background-color: #fff;
    border-radius: 4px;
    border: 1px solid #dcdfe6;
    transition: all 0.3s;
    &:hover {
      border-color: #409eff;
      box-shadow: 0 2px 8px rgba(64, 158, 255, 0.1);
    }
  }
  .approver-node-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 8px;
  }
  .approver-node-label {
    font-size: 13px;
    font-weight: 500;
    color: #606266;
  }
  @media (max-width: 1200px) {
    .approver-node-item {
      flex: 0 0 calc(50% - 8px);
    }
  }
  @media (max-width: 768px) {
    .approver-node-item {
      flex: 0 0 100%;
    }
  }
  
  // 删除图标样式