yxh
2026-09-30 d21d7dd23f15a0ca395a4c9d1b3bb31330ff56c0
src/views/project/fund/applyDetail/index.vue
@@ -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";
@@ -882,6 +890,8 @@
      // 网络请求头
      Networkheader: null,
      batchdialogVisible: false,
      //批量录入弹框的筛查关键字
      personKeyword: "",
      itemlist: [],
      cityOptions: [],
      checkAll: false,
@@ -964,7 +974,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 +1148,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: {},
  //方法集合
@@ -1539,6 +1570,31 @@
        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++) {
            let tempDetail = this.rbDetails[k];
@@ -1736,9 +1792,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 +1901,19 @@
        return;
      }
      // 专家劳务费专用: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 +2022,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 +2043,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 +2054,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 +2069,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 + ")"
              });
@@ -2193,6 +2350,13 @@
      this.Savereminder = false;
      this.Reminderquantity = 0;
      this.totalquantity = 0;
      // 专家劳务费专用:9 条费用控制规则校验(校验不通过不允许落库)
      this.checkExpertFeeRules("保存").then((allowed) => {
        if (!allowed) {
          return;
        }
      this.$refs["form"].validate(valid => {
        if (valid) {
          this.formData = this.form;
@@ -2369,16 +2533,19 @@
          // 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 +2581,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 +2676,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 +2696,7 @@
        fundid: null,
        beneficiaryname: null,
        beneficiaryno: null,
        isunit: 0,
        unitname: null,
        itemdescribe: null,
        unitno: null,
@@ -3121,11 +3333,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 +3445,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);