yxh
8 天以前 fb16e99d60035949b870d0bed7f38b452458000a
src/views/project/fund/applyDetail/index.vue
@@ -12,7 +12,7 @@
          </el-col>
          <el-col :span="5">
            <el-form-item label="申请日期" prop="applyTime">
              <el-date-picker clearable :disabled="true" style="width: 100%" v-model="form.applyTime" type="date"
              <el-date-picker clearable :disabled="true" style="width: 100%" v-model="form.applyTime" type="date" format="yyyy-MM-dd"
                value-format="yyyy-MM-dd HH:mm:ss" placeholder="日   期">
              </el-date-picker>
            </el-form-item>
@@ -229,8 +229,9 @@
              v-if="businessType == '1' || businessType == '2'">
              <template slot-scope="scope">
                <el-form-item label-width="0">
                  <el-input :disabled="userprofile.nickName != '陈慕华'" v-model.number="scope.row.idcardno"
                    @input="tableParValueChange1(scope.row)" placeholder="身份证号" />
                  <el-input :disabled="userprofile.nickName != '陈慕华' || scope.row.isunit == 1"
                    :placeholder="scope.row.isunit == 1 ? '单位无需填写' : '身份证号'"
                    v-model.number="scope.row.idcardno" @input="tableParValueChange1(scope.row)" />
                </el-form-item>
              </template>
            </el-table-column>
@@ -690,13 +691,19 @@
    </el-dialog>
    <!-- 批量选取 -->
    <el-dialog title="批量生成明细信息" :visible.sync="batchdialogVisible" width="30%">
    <el-dialog title="批量生成明细信息" :visible.sync="batchdialogVisible" width="35%">
      <div style="margin-bottom: 12px">
        <el-input v-model="personKeyword" clearable size="small" prefix-icon="el-icon-search"
          placeholder="输入 姓名 / 单位 / 工号 / 身份证后4位 筛查" style="width: 330px" />
        <span style="margin-left: 10px; color: #909399; font-size: 12px">共 {{ filteredCityOptions.length }} 人</span>
      </div>
      <div style="max-height: 300px; overflow-y: auto">
        <el-checkbox :indeterminate="isIndeterminate" v-model="checkAll" @change="handleCheckAllChange">全选</el-checkbox>
        <el-checkbox :indeterminate="isIndeterminate" v-model="checkAll" @change="handleCheckAllChange">全选(当前筛查结果)</el-checkbox>
        <div style="margin: 15px 0;"></div>
        <el-checkbox-group v-model="checkedCities" @change="handleCheckedCitiesChange">
          <el-checkbox border v-for="city in cityOptions" :label="city" :key="city.reportNo">{{ city.reportName
          }}</el-checkbox>
          <el-checkbox border v-for="city in filteredCityOptions" :label="city"
            :key="city.reportNo + '_' + city.reportName">{{ city.reportName
            }}{{ city.unitname ? " · " + city.unitname : "" }}</el-checkbox>
        </el-checkbox-group>
      </div>
@@ -736,6 +743,7 @@
  addorupdateFund,
  ManualCalculation,
  getdetailsByItemId,
  validateExpertFee,
  listbypower
} from "@/api/project/fund";
@@ -787,6 +795,7 @@
import OrgSelecter from "@/views/project/components/orgselect";
import { getToken } from "@/utils/auth";
import { moneySum, moneyMul, moneySub, moneyRound } from "@/utils/money";
import { checkFileName, checkDetails } from "@/utils/fileNameCheck";
import CustomImageViewer from "@/components/CustomImageViewer"; // 根据你的路径调整
export default {
@@ -882,6 +891,8 @@
      // 网络请求头
      Networkheader: null,
      batchdialogVisible: false,
      //批量录入弹框的筛查关键字
      personKeyword: "",
      itemlist: [],
      cityOptions: [],
      checkAll: false,
@@ -964,7 +975,19 @@
        ],
        unitTaxNo: [{ required: true, message: "请输入税号", trigger: "blur" }],
        idcardno: [
          { required: true, message: '请输入身份证号', trigger: 'blur' },
          {
            // 账户类型选择"单位机构"时不需要身份证号
            validator: (rule, value, callback) => {
              if (this.hasUserType(this.accountselectform.usertype, "org")) {
                return callback();
              }
              if (!value || !String(value).trim()) {
                return callback(new Error("请输入身份证号"));
              }
              return callback();
            },
            trigger: "blur"
          },
          // 添加自定义校验规则
          { validator: validateIdCard, trigger: 'blur' }
        ],
@@ -1126,7 +1149,16 @@
  },
  //监听属性 类似于data概念
  computed: {},
  computed: {
    //批量录入弹框:按关键字(姓名/单位/工号/身份证后4位)筛查后的候选人
    filteredCityOptions() {
      const kw = (this.personKeyword || "").trim().toLowerCase();
      if (!kw) {
        return this.cityOptions;
      }
      return this.cityOptions.filter(item => this.personMatchKeyword(item, kw));
    }
  },
  //监控data中的数据变化
  watch: {},
  //方法集合
@@ -1530,6 +1562,11 @@
    },
    /** 提交按钮 */
    submitForm: debounce(function () {
      // 2026-10-15:保存前附件文件名规范校验(仅新增单据;老单据的历史附件由后端按"只校验本次变更"放行)
      if (!this.checkDetailFileNames()) {
        return;
      }
      if (!this.randomnumber) {
        this.randomnumber = this.generateRandomString(9);
        this.form.randomnumber = this.randomnumber;
@@ -1538,6 +1575,31 @@
      this.$refs["form"].validate(valid => {
        if (valid) {
          this.formData = this.form;
          // 专家劳务费专用:9 条费用控制规则校验(校验不通过不允许保存)
          if (this.businessType == "1") {
            this.$modal.loading("正在校验专家劳务费用,请稍候!");
            this.checkExpertFeeRules("保存").then((allowed) => {
              this.$modal.closeLoading();
              if (allowed) {
                this.doSubmitForm();
              }
            });
            return;
          }
          this.doSubmitForm();
        }
      });
    }, 500),
    /**
     * 保存申请单的实际落库逻辑。
     *
     * 从 submitForm 中拆出,便于在专家劳务费规则校验通过后再执行;
     * 其他业务类型由 submitForm 直接调用,行为与拆分前一致。
     */
    doSubmitForm() {
          //保存前校验数据
          for (let k = 0; k < this.rbDetails.length; k++) {
@@ -1736,9 +1798,84 @@
          //关闭窗口
          this.loading = false;
          // this.getfundList();
        }
    },
    /**
     * 专家劳务费 9 条费用控制规则校验(提交前拦截)。
     *
     * 适用范围:businessType === '1'(专家劳务费申请单),与后端 applytype === '1' 对应。
     * 其他业务类型(伦理专家费、医学成本、办公费用)不校验,直接放行。
     *
     * 校验口径(同一捐献案例 infoid 下累计)由后端统一实现:
     *   errors   阻断型:提示违反的具体规则,不允许继续保存/审核
     *   warnings 提示型:仅提醒经办人处理(规则 5、6),需二次确认后才继续
     *
     * @param {string} action 当前动作,用于拼接提示文案(如“保存”“提交审核”)
     * @returns {Promise<boolean>} true 允许继续,false 阻止继续
     */
    checkExpertFeeRules(action) {
      if (this.businessType != "1") {
        return Promise.resolve(true);
      }
      // 后端按「案例内已有单据 + 本次提交明细」合并计算,确保尚未落库的明细也参与校验
      const payload = Object.assign({}, this.form, {
        applytype: this.form.applytype || "1",
        serviceFunddetails: this.rbDetails,
      });
    }, 500),
      // 排查用:能直接从控制台看到到底送了什么,以及后端判定了什么
      console.log("[专家费校验] 提交校验", action, {
        businessType: this.businessType,
        applytype: payload.applytype,
        infoid: payload.infoid,
        fundId: payload.id,
        明细: (this.rbDetails || []).map((d) => ({
          itemid: d.itemid,
          amount: d.amount,
          taxedamount: d.taxedamount,
          场景: d.servicesscopename,
        })),
      });
      return validateExpertFee(payload)
        .then((res) => {
          const errors = (res && res.errors) || [];
          const warnings = (res && res.warnings) || [];
          console.log("[专家费校验] 后端返回", { errors, warnings });
          // 1) 阻断型:列出违反的具体规则,不允许继续
          //    用弹窗而非轻提示:错误可能有多条,轻提示会挤成一行且很快消失
          if (errors.length > 0) {
            this.$modal.alertError(
              "专家劳务费用不符合以下规定,无法" +
                action +
                ":\n\n" +
                errors.map((msg, i) => i + 1 + ". " + msg).join("\n\n")
            );
            return false;
          }
          // 2) 提示型:需经办人确认后才能继续(当前规则 5、6 已改为硬拦截,此分支保留备用)
          if (warnings.length > 0) {
            const confirmText =
              "专家劳务费用存在以下问题,请确认是否继续" +
              action +
              "?\n\n" +
              warnings.map((msg, i) => i + 1 + ". " + msg).join("\n\n");
            return this.$modal
              .confirm(confirmText)
              .then(() => true)
              .catch(() => false);
          }
          return true;
        })
        .catch((err) => {
          // 接口异常时按“不阻断”处理,避免校验服务不可用导致完全无法报销;
          // 后端 addOrUpdateNew / checkFund 仍有同样的硬拦截兜底。
          console.error("专家劳务费规则校验接口调用失败", err);
          return true;
        });
    },
    checksubmit() {
      //检查是否收到纸质材料
@@ -1770,8 +1907,19 @@
        return;
      }
      this.Savenow();
      console.log("保存并审批");
      // 专家劳务费专用:9 条费用控制规则校验(仅审核通过时拦截)
      const needExpertFeeCheck = this.checkObj.flowlevelone == 1;
      const expertFeePromise = needExpertFeeCheck
        ? this.checkExpertFeeRules("提交审核")
        : Promise.resolve(true);
      expertFeePromise.then((allowed) => {
        if (!allowed) {
          return;
        }
        this.Savenow();
        console.log("保存并审批");
      });
    },
    // 连续审批:获取当前业务类型下的待审批列表
@@ -1880,6 +2028,19 @@
      });
    },
    /** 人员类型(多值,逗号分隔)是否包含指定类型;org = 单位机构 */
    hasUserType(usertype, code) {
      if (!usertype) {
        return false;
      }
      return (
        String(usertype)
          .split(",")
          .map(t => t.trim())
          .indexOf(code) > -1
      );
    },
    /** 受益人/账户是否为"单位"(人员类型含 org) */
    //获取专家、单位机构的账户列表
    getAccountList() {
      listExternalperson().then(res => {
@@ -1888,7 +2049,7 @@
        for (let j = 0; j < res.rows.length; j++) {
          //单位账号列表
          if (res.rows[j].userstatus == "1") {
            if (res.rows[j].usertype == "org") {
            if (this.hasUserType(res.rows[j].usertype, "org")) {
              this.accountlist.push({
                id: res.rows[j].id,
                userno: res.rows[j].userno,
@@ -1899,6 +2060,7 @@
                bankcardno: res.rows[j].bankcardno,
                unitno: res.rows[j].unitno,
                unitname: res.rows[j].unitname,
                usertype: res.rows[j].usertype,
                userid:
                  res.rows[j].username + "(" + res.rows[j].bankcardno + ")"
              });
@@ -1913,6 +2075,7 @@
                bankcardno: res.rows[j].bankcardno,
                unitno: res.rows[j].unitno,
                unitname: res.rows[j].unitname,
                usertype: res.rows[j].usertype,
                userid:
                  res.rows[j].username + "(" + res.rows[j].idcardno + ")"
              });
@@ -2186,6 +2349,11 @@
    },
    // 接受提醒立刻保存
    Savenow() {
      // 2026-10-15:保存前附件文件名规范校验(仅新增单据;老单据的历史附件由后端按"只校验本次变更"放行)
      if (!this.checkDetailFileNames()) {
        return;
      }
      if (!this.randomnumber) {
        this.randomnumber = this.generateRandomString(9);
        this.form.randomnumber = this.randomnumber;
@@ -2193,192 +2361,198 @@
      this.Savereminder = false;
      this.Reminderquantity = 0;
      this.totalquantity = 0;
      this.$refs["form"].validate(valid => {
        if (valid) {
          this.formData = this.form;
          //保存前校验数据
          for (let k = 0; k < this.rbDetails.length; k++) {
            let tempDetail = this.rbDetails[k];
            if (tempDetail.itemid == null) {
              this.$modal.msgWarning(
                "服务项目表中,第" +
                (k + 1).toString() +
                "行的服务项目未选择,请先选择服务项目后再提交保存!"
              );
              return;
            }
            if (this.businessType == "1") {
              if (tempDetail.servicesscope == null) {
                this.$modal.msgWarning(
                  "服务项目表中,第" +
                  (k + 1).toString() +
                  "行的费用说明未选择,请选择费用说明后再提交保存!"
                );
                return;
              }
              if (tempDetail.beneficiaryno == null) {
                this.$modal.msgWarning(
                  "服务项目表中,第" +
                  (k + 1).toString() +
                  "行的人员姓名为空,请选择人员后再提交保存!"
                );
                return;
              }
              if (tempDetail.unitname == null) {
                this.$modal.msgWarning(
                  "服务项目表中,第" +
                  (k + 1).toString() +
                  "行的专家单位为空,请选择单位后再提交保存!"
                );
                return;
              }
            }
          }
          //处理明细
          for (let i = 0; i < this.rbDetails.length; i++) {
            let tempDetail = this.rbDetails[i];
            //获取服务项目明细
            let tempitemlist = tempDetail.itemlist;
            let itemIndex = tempitemlist.findIndex(
              item => tempDetail.itemid == item.id
            );
            if (itemIndex > -1) {
              tempDetail.itemname = tempitemlist[itemIndex].itemName;
              tempDetail.itemid = tempitemlist[itemIndex].id;
            }
            //获取applytypename、服务费用大类
            let applytypeIndex = this.rowfeeItemTypes.findIndex(
              item => tempDetail.applytype == item.value
            );
            if (applytypeIndex > -1) {
              tempDetail.applytypename = this.rowfeeItemTypes[
                applytypeIndex
              ].label;
              tempDetail.subjecttype = this.rowfeeItemTypes[
                applytypeIndex
              ].subjecttype;
              tempDetail.subjecttypename = this.rowfeeItemTypes[
                applytypeIndex
              ].subjecttypename;
            }
            this.rbDetails[i] = tempDetail;
          }
          //计算当前行报销内容费用合计,税前金额(amount)、扣税金额(taxamount)、税后金额(taxedamount)
          if (this.businessType == "2") {
            for (let j = 0; j < this.rbDetails.length; j++) {
              let tempDetail = this.rbDetails[j];
              if (
                isNaN(parseFloat(tempDetail.amount)) ||
                moneyRound(tempDetail.amount) == 0
              ) {
                this.$modal.msgWarning(
                  "服务项目表中,第" +
                  (j + 1).toString() +
                  "行的金额未输入,请先输入金额后再提交保存!"
                );
                return;
              }
            }
          } else if (this.businessType == "3" || this.businessType == "4") {
            for (let j = 0; j < this.rbDetails.length; j++) {
              let tempDetail = this.rbDetails[j];
              if (
                isNaN(parseFloat(tempDetail.amount)) ||
                moneyRound(tempDetail.amount) == 0
              ) {
                this.$modal.msgWarning(
                  "服务项目表中,第" +
                  (j + 1).toString() +
                  "行的金额未输入,请先输入金额后再提交保存!"
                );
                return;
              }
              if (
                isNaN(parseFloat(tempDetail.taxamount)) ||
                moneyRound(tempDetail.taxamount) == 0
              ) {
                tempDetail.taxedamount = parseFloat(tempDetail.amount);
              } else {
                tempDetail.taxedamount =
                  parseFloat(tempDetail.amount) +
                  parseFloat(tempDetail.taxamount);
              }
            }
          }
          //保存数据
          this.loading = true;
          this.form.serviceFunddetails = 0;
          this.rbDetails.map(item => {
            this.form.serviceFunddetails += item.amount;
          });
          this.form.serviceFunddetails = this.rbDetails;
          if (this.form.id != null) {
          } else {
            this.form.recordstatus = -1;
          }
          addorupdateFund(this.form).then(response => {
            if (response.code === 200) {
              this.$modal.msgSuccess("申请单保存成功!");
              // 非提醒状态下执行审批解决异步调用延迟问题
              if (!this.Savereminder) {
                let checkFundObj = {
                  // fundid: this.checkObj.fundid,
                  fundid: this.curId,
                  flowconclusion: this.checkObj.flowlevelone,
                  flowcontent: this.checkObj.flowconclusion
                };
                this.$modal.loading("正在提交审核,请稍候!");
                checkfund(checkFundObj).then(res => {
                  if (res.code == 200) {
                    //关闭窗口
                    this.$modal.closeLoading();
                    this.$modal.msgSuccess("审核完成!");
                    // 连续审批:询问是否继续审批下一单
                    this.continueOrExit();
                  } else {
                    this.$modal.closeLoading();
                    if (res.msg) {
                      this.$modal.msgError("操作失误");
                    } else {
                      this.$modal.msgError(res.msg);
                    }
                  }
                });
              }
              this.Getnetworkheader();
              this.getroute();
              this.getAccountList();
              this.getUserList();
              this.getUserProfile();
            } else {
              this.$modal.msgError("申请单保存失败:" + response.msg);
            }
          });
          //关闭窗口
          this.loading = false;
          // this.getfundList();
      // 专家劳务费专用:9 条费用控制规则校验(校验不通过不允许落库)
      this.checkExpertFeeRules("保存").then((allowed) => {
        if (!allowed) {
          return;
        }
        this.$refs["form"].validate(valid => {
          if (valid) {
            this.formData = this.form;
            //保存前校验数据
            for (let k = 0; k < this.rbDetails.length; k++) {
              let tempDetail = this.rbDetails[k];
              if (tempDetail.itemid == null) {
                this.$modal.msgWarning(
                  "服务项目表中,第" +
                  (k + 1).toString() +
                  "行的服务项目未选择,请先选择服务项目后再提交保存!"
                );
                return;
              }
              if (this.businessType == "1") {
                if (tempDetail.servicesscope == null) {
                  this.$modal.msgWarning(
                    "服务项目表中,第" +
                    (k + 1).toString() +
                    "行的费用说明未选择,请选择费用说明后再提交保存!"
                  );
                  return;
                }
                if (tempDetail.beneficiaryno == null) {
                  this.$modal.msgWarning(
                    "服务项目表中,第" +
                    (k + 1).toString() +
                    "行的人员姓名为空,请选择人员后再提交保存!"
                  );
                  return;
                }
                if (tempDetail.unitname == null) {
                  this.$modal.msgWarning(
                    "服务项目表中,第" +
                    (k + 1).toString() +
                    "行的专家单位为空,请选择单位后再提交保存!"
                  );
                  return;
                }
              }
            }
            //处理明细
            for (let i = 0; i < this.rbDetails.length; i++) {
              let tempDetail = this.rbDetails[i];
              //获取服务项目明细
              let tempitemlist = tempDetail.itemlist;
              let itemIndex = tempitemlist.findIndex(
                item => tempDetail.itemid == item.id
              );
              if (itemIndex > -1) {
                tempDetail.itemname = tempitemlist[itemIndex].itemName;
                tempDetail.itemid = tempitemlist[itemIndex].id;
              }
              //获取applytypename、服务费用大类
              let applytypeIndex = this.rowfeeItemTypes.findIndex(
                item => tempDetail.applytype == item.value
              );
              if (applytypeIndex > -1) {
                tempDetail.applytypename = this.rowfeeItemTypes[
                  applytypeIndex
                ].label;
                tempDetail.subjecttype = this.rowfeeItemTypes[
                  applytypeIndex
                ].subjecttype;
                tempDetail.subjecttypename = this.rowfeeItemTypes[
                  applytypeIndex
                ].subjecttypename;
              }
              this.rbDetails[i] = tempDetail;
            }
            //计算当前行报销内容费用合计,税前金额(amount)、扣税金额(taxamount)、税后金额(taxedamount)
            if (this.businessType == "2") {
              for (let j = 0; j < this.rbDetails.length; j++) {
                let tempDetail = this.rbDetails[j];
                if (
                  isNaN(parseFloat(tempDetail.amount)) ||
                  moneyRound(tempDetail.amount) == 0
                ) {
                  this.$modal.msgWarning(
                    "服务项目表中,第" +
                    (j + 1).toString() +
                    "行的金额未输入,请先输入金额后再提交保存!"
                  );
                  return;
                }
              }
            } else if (this.businessType == "3" || this.businessType == "4") {
              for (let j = 0; j < this.rbDetails.length; j++) {
                let tempDetail = this.rbDetails[j];
                if (
                  isNaN(parseFloat(tempDetail.amount)) ||
                  moneyRound(tempDetail.amount) == 0
                ) {
                  this.$modal.msgWarning(
                    "服务项目表中,第" +
                    (j + 1).toString() +
                    "行的金额未输入,请先输入金额后再提交保存!"
                  );
                  return;
                }
                if (
                  isNaN(parseFloat(tempDetail.taxamount)) ||
                  moneyRound(tempDetail.taxamount) == 0
                ) {
                  tempDetail.taxedamount = parseFloat(tempDetail.amount);
                } else {
                  tempDetail.taxedamount =
                    parseFloat(tempDetail.amount) +
                    parseFloat(tempDetail.taxamount);
                }
              }
            }
            //保存数据
            this.loading = true;
            this.form.serviceFunddetails = 0;
            this.rbDetails.map(item => {
              this.form.serviceFunddetails += item.amount;
            });
            this.form.serviceFunddetails = this.rbDetails;
            if (this.form.id != null) {
            } else {
              this.form.recordstatus = -1;
            }
            addorupdateFund(this.form).then(response => {
              if (response.code === 200) {
                this.$modal.msgSuccess("申请单保存成功!");
                // 非提醒状态下执行审批解决异步调用延迟问题
                if (!this.Savereminder) {
                  let checkFundObj = {
                    // fundid: this.checkObj.fundid,
                    fundid: this.curId,
                    flowconclusion: this.checkObj.flowlevelone,
                    flowcontent: this.checkObj.flowconclusion
                  };
                  this.$modal.loading("正在提交审核,请稍候!");
                  checkfund(checkFundObj).then(res => {
                    if (res.code == 200) {
                      //关闭窗口
                      this.$modal.closeLoading();
                      this.$modal.msgSuccess("审核完成!");
                      // 连续审批:询问是否继续审批下一单
                      this.continueOrExit();
                    } else {
                      this.$modal.closeLoading();
                      this.$modal.msgError(res.msg || "操作失败");
                    }
                  });
                }
                this.Getnetworkheader();
                this.getroute();
                this.getAccountList();
                this.getUserList();
                this.getUserProfile();
              } else {
                this.$modal.msgError("申请单保存失败:" + response.msg);
              }
            });
            //关闭窗口
            this.loading = false;
            // this.getfundList();
          }
        });
      });
    },
    handleCheckAllChange(val) {
      this.checkedCities = val ? this.cityOptions : [];
      this.checkedCities = val ? this.filteredCityOptions.slice() : [];
      this.isIndeterminate = false;
    },
    handleCheckedCitiesChange(value) {
      let checkedCount = value.length;
      this.checkAll = checkedCount === this.cityOptions.length;
      this.checkAll =
        this.filteredCityOptions.length > 0 &&
        checkedCount === this.filteredCityOptions.length;
      this.isIndeterminate =
        checkedCount > 0 && checkedCount < this.cityOptions.length;
        checkedCount > 0 && checkedCount < this.filteredCityOptions.length;
    },
    // 手动计算
    Manualcalculationfn() {
@@ -2414,19 +2588,57 @@
    // 批量录入点击
    Batchentry() {
      // ethical
      this.personKeyword = "";
      this.checkedCities = [];
      this.checkAll = false;
      this.isIndeterminate = false;
      listReportname("ethical").then(res => {
        this.cityOptions = res.data;
      });
      this.batchdialogVisible = true;
    },
    /** 批量录入弹框:姓名/单位/工号/身份证后4位 命中判断 */
    personMatchKeyword(item, kw) {
      const idcard = String(item.idcrad || "");
      return (
        String(item.reportName || "")
          .toLowerCase()
          .indexOf(kw) > -1 ||
        String(item.unitname || "")
          .toLowerCase()
          .indexOf(kw) > -1 ||
        String(item.reportNo || "")
          .toLowerCase()
          .indexOf(kw) > -1 ||
        (idcard.length >= 4 && idcard.slice(-4).indexOf(kw) > -1)
      );
    },
    // 确认录入
    BatchentryFn() {
      // 明细中已有的人员姓名,用于重复提醒
      const existed = {};
      this.rbDetails.forEach(row => {
        if (row.beneficiaryname) {
          existed[String(row.beneficiaryname).trim()] = true;
        }
      });
      const duplicated = [];
      this.checkedCities.forEach(item => {
        const nameKey = String(item.reportName || "").trim();
        if (nameKey && existed[nameKey]) {
          duplicated.push(item.reportName);
          return;
        }
        if (nameKey) {
          existed[nameKey] = true;
        }
        let rowData = {
          orderno: null,
          fundid: null,
          beneficiaryname: item.reportName,
          beneficiaryno: item.reportNo,
          isunit: 0,
          unitname: item.unitname,
          unitno: null,
          uintuserno: null,
@@ -2471,11 +2683,17 @@
        this.rbDetails.push(rowData);
      });
      for (let i = 0; i < this.checkedCities.length + 1; i++) {
      for (let i = 0; i < this.rbDetails.length; i++) {
        this.rbDetails[i].orderno = i + 1;
      }
      this.calculateTax();
      this.batchdialogVisible = false;
      this.checkedCities = [];
      if (duplicated.length) {
        this.$modal.msgWarning(
          "以下人员在明细中已存在,已跳过重复添加:" + duplicated.join("、")
        );
      }
    },
    //新增一行
    addRow(rowIndex) {
@@ -2485,6 +2703,7 @@
        fundid: null,
        beneficiaryname: null,
        beneficiaryno: null,
        isunit: 0,
        unitname: null,
        itemdescribe: null,
        unitno: null,
@@ -2719,7 +2938,43 @@
      }
    },
    /**
     * 2026-10-15:上传时校验附件文件名(业务要求:不合格立即提示且不加入列表)。
     * 规则与后端 AttachmentNameValidator 同口径:必须含中文 / 去掉扩展名后主干 ≤20 字 /
     * 不含空格及 / \ : * ? " < > | # % 等特殊字符。
     */
    checkUploadFileName(file) {
      const raw = file && file.raw ? file.raw.name : file && file.name;
      const r = checkFileName(raw);
      if (!r.ok) {
        this.$modal.msgError(r.msg);
        return false;
      }
      return true;
    },
    /**
     * 2026-10-15:保存前校验整单明细附件文件名(含同一行内重名)。
     * 口径(业务确认):只校验"本次变更"的附件——老单据里已存在的不合规附件名不能把保存卡死
     * (否则老单据改一个金额都存不进去)。后端按 id 读旧记录放行历史附件;前端拿不到"哪些是历史"
     * 的权威信息,因此**仅新增单据**做保存前拦截,编辑老单据时新增的不合规附件由后端二次校验拦下。
     */
    checkDetailFileNames() {
      if (this.form && this.form.id) {
        return true;
      }
      const c = checkDetails(this.rbDetails);
      if (!c.ok) {
        this.$modal.msgError(c.msg);
        return false;
      }
      return true;
    },
    uploadSccess(response, file, fileList) {
      if (!this.checkUploadFileName(file)) {
        return;
      }
      this.rbDetails;
      const config = {
        headers: { Authorization: "Bearer " + this.ICDtoken }
@@ -3121,11 +3376,13 @@
                  this.rbDetails[tempIndex].phone = res.telephone;
                  if (res.usertype == "org") {
                    this.$set(this.rbDetails[tempIndex], "isunit", 1);
                    this.rbDetails[tempIndex].idcardno = "";
                    this.rbDetails[tempIndex].title = "";
                    this.rbDetails[tempIndex].unitno = "";
                    this.rbDetails[tempIndex].unitname = "";
                  } else {
                    this.$set(this.rbDetails[tempIndex], "isunit", 0);
                    this.rbDetails[tempIndex].idcardno = res.idcardno;
                    this.rbDetails[tempIndex].title = res.title;
                    this.rbDetails[tempIndex].unitno = parseInt(res.unitno);
@@ -3231,12 +3488,14 @@
                      this.rbDetails[tempIndex].bankcardno = res.bankcardno;
                      this.rbDetails[tempIndex].phone = res.telephone;
                      if (res.usertype == "org") {
                      if (this.hasUserType(res.usertype, "org")) {
                        this.$set(this.rbDetails[tempIndex], "isunit", 1);
                        this.rbDetails[tempIndex].idcardno = "";
                        this.rbDetails[tempIndex].title = "";
                        this.rbDetails[tempIndex].unitno = "";
                        this.rbDetails[tempIndex].unitname = "";
                      } else {
                        this.$set(this.rbDetails[tempIndex], "isunit", 0);
                        this.rbDetails[tempIndex].idcardno = res.idcardno;
                        this.rbDetails[tempIndex].title = res.title;
                        this.rbDetails[tempIndex].unitno = parseInt(res.unitno);