| package.json | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/api/project/fund.js | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/project/documentfiles/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/project/externalperson/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/project/fund/applyDetail/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/project/fund/expertFeeApply/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/project/fund/performancedetails/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/project/fundcheck/expertFeeCheck/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/project/travelexpenseapply/travelexpensedetail/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| vue.config.js | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
package.json
@@ -5,7 +5,7 @@ "author": "浙江亥尔时信息科技有限公司", "license": "MIT", "scripts": { "dev": "vue-cli-service serve", "serve": "vue-cli-service serve", "build": "vue-cli-service build", "build:stage": "vue-cli-service build --mode staging", "preview": "node build/index.js --preview", src/api/project/fund.js
@@ -160,6 +160,16 @@ }) } // 专家劳务费:保存前的费用控制校验(干跑,不落库) // 返回 { errors: [阻断性信息], warnings: [提示信息], blocked: 是否阻断 } export function validateExpertFee(data) { return request({ url: '/project/fund/validateExpertFee', method: 'post', data: data }) } // 伦理专家费打印 export function getdownloadLL(id) { return request({ src/views/project/documentfiles/index.vue
@@ -659,34 +659,100 @@ download() { let list = this.fileList; if (list.length > 0) { let downUrl = list[0].url; const link = document.createElement("a"); link.href = this.baseUrl + downUrl; link.setAttribute("download", this.getFileName(list[0].name)); document.body.appendChild(link); link.click(); this.saveFile(list[0].url, this.getFileName(list[0].name, list[0].url)); } else { this.$modal.msgWarning("未获取到文件地址,请确认"); } }, handleDownload(row) { if (row.fileurl != "" && row.fileurl != null) { const link = document.createElement("a"); link.href = this.baseUrl + row.fileurl; link.setAttribute("download", this.getFileName(row.fileurl)); document.body.appendChild(link); link.click(); this.saveFile( row.fileurl, this.getFileName(row.filename, row.fileurl, row.filename) ); } else { this.$modal.msgWarning("未获取到文件地址,请确认"); } }, // 获取文件名称 getFileName(name) { if (name.lastIndexOf("/") > -1) { return name.slice(name.lastIndexOf("/") + 1).toLowerCase(); } else { return ""; /** * 下载文件(必须带鉴权)。 * * 背景:/profile/** 静态资源未在 SecurityConfig 中放行(anyRequest().authenticated()), * 而后端 TokenService 只从请求头 Authorization 取 token。 * 因此用 <a href> 直接跳转下载不会携带 token,服务端返回 401,表现为“点下载没反应/下不到文件”。 * 这里改为 fetch 带 Authorization 头取回文件流,再用 blob 本地另存。 * * @param {string} fileUrl 文件地址(库中存的是 /profile/upload/... 相对路径) * @param {string} fileName 另存为的文件名 */ saveFile(fileUrl, fileName) { if (!fileUrl) { this.$modal.msgWarning("未获取到文件地址,请确认"); return; } // 兼容历史数据中已是绝对地址的情况,避免重复拼接 const isAbsolute = /^https?:\/\//i.test(fileUrl); const url = isAbsolute ? fileUrl : this.baseUrl + fileUrl; this.$modal.loading("正在下载文件,请耐心等待!"); fetch(url, { method: "get", headers: { Authorization: "Bearer " + getToken() }, }) .then((response) => { if (!response.ok) { // 401/403/404 必须显式提示,否则会下到一个内容为错误页的文件 throw new Error("HTTP " + response.status); } return response.blob(); }) .then((blob) => { const blobUrl = window.URL.createObjectURL(blob); const link = document.createElement("a"); link.href = blobUrl; link.setAttribute("download", fileName || "下载文件"); document.body.appendChild(link); link.click(); document.body.removeChild(link); window.URL.revokeObjectURL(blobUrl); }) .catch((err) => { console.error("文件下载失败:" + url, err); this.$modal.msgError("文件下载失败,请确认文件是否存在或登录是否过期"); }) .finally(() => { this.$modal.closeLoading(); }); }, /** * 获取另存为的文件名。 * * 优先级:带扩展名的原始文件名 > 文件地址末段 > 文档标题。 * 之所以不直接用 form.filename:该字段在表单中可手工编辑(如“人体器官移植条例”), * 不带扩展名,直接用作另存名会导致系统无法识别打开方式。 * * @param {string} storedName 原始文件名(如 form.filename 中存的 xxx.docx) * @param {string} fileUrl 文件地址 * @param {string} title 备用名称(文档标题) */ getFileName(storedName, fileUrl, title) { const hasExt = (name) => !!name && name.lastIndexOf(".") > -1; if (hasExt(storedName)) { return storedName; } const source = fileUrl || ""; if (source.lastIndexOf("/") > -1) { const fromUrl = source.slice(source.lastIndexOf("/") + 1); if (fromUrl) { return fromUrl; } } return title || storedName || "下载文件"; }, //文件上传成功回调 src/views/project/externalperson/index.vue
@@ -47,9 +47,10 @@ <el-table v-loading="loading" :data="externalpersonList" @selection-change="handleSelectionChange"> <el-table-column type="selection" width="55" align="center" /> <el-table-column label="账号类别" align="center" prop="usertype"> <el-table-column label="账号类别" align="center" prop="usertype" width="220"> <template slot-scope="scope"> <dict-tag :options="dict.type.sys_UserType" :value="scope.row.usertype" /> <dict-tag v-for="t in splitUserType(scope.row.usertype)" :key="t" :options="dict.type.sys_UserType" :value="t" /> </template> </el-table-column> <el-table-column label="账号编号" align="center" prop="userno" /> @@ -90,7 +91,7 @@ <el-form ref="form" :model="form" :rules="rules" label-width="120px"> <el-row> <el-col :span="12"><el-form-item label="账号类别" prop="usertype"> <el-select v-model="form.usertype" placeholder="请选择账号类别"> <el-select v-model="form.usertype" multiple collapse-tags placeholder="请选择账号类别(可多选)"> <el-option v-for="dict in dict.type.sys_UserType" :key="dict.value" :label="dict.label" :value="dict.value"></el-option> </el-select> @@ -118,14 +119,14 @@ </el-form-item> </el-col> </el-row> <el-row v-if="form.usertype != 'org'"> <el-row v-if="!isUnitForm"> <el-col :span="24"> <el-form-item label="身份证号" prop="idcardno"> <el-input v-model="form.idcardno" placeholder="请输入身份证号" /> </el-form-item> </el-col> </el-row> <el-row v-if="form.usertype != 'org'"><el-col :span="12"> <el-row v-if="!isUnitForm"><el-col :span="12"> <el-form-item label="性别" prop="sex"> <el-select v-model="form.sex" placeholder="请选择性别"> <el-option v-for="dict in dict.type.sys_user_sex" :key="dict.value" :label="dict.label" @@ -140,7 +141,7 @@ </el-form-item> </el-col> </el-row> <el-row v-if="form.usertype != 'org'"> <el-row v-if="!isUnitForm"> <el-col :span="12"> <el-form-item label="所在单位" prop="unitname"> <el-select v-model="form.unitno" placeholder="所在单位" clearable filterable allow-create style="width: 100%"> @@ -272,7 +273,19 @@ { required: true, message: "请输入账户名称", trigger: "blur" } ], idcardno: [ { required: true, message: "请输入身份证号码", trigger: "blur" } { //账号类别为"单位机构"时无需身份证号 validator: (rule, value, callback) => { if (this.hasUserType(this.form.usertype, "org")) { return callback(); } if (!value || !String(value).trim()) { return callback(new Error("请输入身份证号码")); } return callback(); }, trigger: "blur" } ], bankcardno: [ { required: true, message: "请输入银行账号", trigger: "blur" } @@ -282,6 +295,12 @@ ] }, }; }, computed: { /** 表单是否选择了"单位机构"(账号类别为多值,含 org 即为单位) */ isUnitForm() { return this.hasUserType(this.form.usertype, "org"); } }, created() { this.getUserProfile(); @@ -296,6 +315,28 @@ const managerList = ["admin", "048", "047"]; this.ismanager = managerList.includes(res.data.userName); }); }, /** 人员类型(多值,逗号分隔)转数组 */ splitUserType(usertype) { if (!usertype) { return []; } if (Array.isArray(usertype)) { return usertype; } return String(usertype) .split(",") .map(t => t.trim()) .filter(t => t); }, /** 人员类型数组转逗号分隔字符串 */ joinUserType(usertype) { return Array.isArray(usertype) ? usertype.join(",") : usertype; }, /** 人员类型是否包含指定类型(如 org = 单位机构) */ hasUserType(usertype, code) { return this.splitUserType(usertype).indexOf(code) > -1; }, /** 查询单位、人员账号列表 */ @@ -331,7 +372,7 @@ userno: null, username: null, userstatus: 0, usertype: null, usertype: [], personnelunitno: null, delFlag: null, createBy: null, @@ -383,6 +424,8 @@ const id = row.id || this.ids; getExternalperson(id).then(response => { this.form = response.data; //账号类别为多值(逗号分隔),表单用数组回显 this.form.usertype = this.splitUserType(response.data.usertype); this.open = true; this.title = "人员/单位账号信息修改"; }); @@ -396,6 +439,8 @@ if (res.organizationid == this.form.unitno) this.form.unitname = res.organizationname; }); //账号类别为多值:提交前转回逗号分隔的字符串 this.form.usertype = this.joinUserType(this.form.usertype); console.log(this.form); if (this.form.id != null) { updateExternalperson(this.form).then(response => { @@ -405,15 +450,8 @@ this.getList(); }); } else { //检查身份证唯一 getInfoByIDCardNo(this.form.idcardno).then(res => { if (res.data.length > 1) { this.$modal.msgWarning("身份证号信息已被登记,请检查身份证号!" ); return; } //保存信息 //保存信息(单位账号没有身份证号,跳过身份证查重) const saveNew = () => { getOnlyCode(this.seqnoParams).then(res => { this.form.userno = res.data.currentvalue; @@ -424,7 +462,21 @@ this.getList(); }); }); }; if (!this.form.idcardno || !String(this.form.idcardno).trim()) { saveNew(); return; } //检查身份证唯一 getInfoByIDCardNo(this.form.idcardno).then(res => { if (res.data.length > 1) { this.$modal.msgWarning("身份证号信息已被登记,请检查身份证号!" ); return; } saveNew(); }); } } 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; } 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 +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,192 +2350,202 @@ 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(); 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(); } }); }); }, 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); src/views/project/fund/expertFeeApply/index.vue
@@ -1112,7 +1112,8 @@ getdownloadYX, getdownloadBX, addorupdateFund, getdetailsByItemId getdetailsByItemId, validateExpertFee } from "@/api/project/fund"; import { @@ -1825,21 +1826,37 @@ // }); } addorupdateFund(this.form).then(response => { if (response.code === 200) { this.selectDonotor(this.curCase); this.$modal.msgSuccess("专家劳务费保存成功!"); } else { this.$modal.msgError("专家劳务费保存失败:" + response.msg); } }); // 保存前先做专家劳务费 9 条费用控制校验(同一案例累计) validateExpertFee(this.form).then(valid => { const errors = (valid && valid.errors) || []; const warnings = (valid && valid.warnings) || []; //关闭窗口 this.loading = false; this.dialogOpen = false; // this.getfundList(); this.getList(); this.selectDonotor(this.curCase); // warnings(规则 5、6)仅提示,不阻断保存 if (warnings.length > 0) { this.$modal.msgWarning(warnings.join(";")); } // errors 阻断保存,保留窗口让经办人先修正 if (errors.length > 0) { this.$modal.msgError(errors.join(";")); this.loading = false; return; } addorupdateFund(this.form).then(response => { if (response.code === 200) { //关闭窗口 this.loading = false; this.dialogOpen = false; this.selectDonotor(this.curCase); this.$modal.msgSuccess("专家劳务费保存成功!"); this.getList(); } else { this.$modal.msgError("专家劳务费保存失败:" + response.msg); this.loading = false; } }); }); } }); }, src/views/project/fund/performancedetails/index.vue
@@ -115,7 +115,7 @@ icon="el-icon-download" size="small" @click="Batchentry" >批量录入人员</el-button >批量导入绩效人员</el-button > </div> </el-col> @@ -266,7 +266,7 @@ <el-table-column prop="beneficiaryname" align="center" label="绩效人员或单位" label="绩效人员/单位" width="250" > <template slot-scope="scope"> @@ -283,13 +283,33 @@ </template> </el-table-column> <el-table-column prop="isunit" align="center" label="人员类别" width="100" > <template slot-scope="scope"> <el-tag :type="scope.row.isunit == 1 ? 'warning' : 'success'" size="mini" >{{ scope.row.isunit == 1 ? "单位" : "个人" }}</el-tag > </template> </el-table-column> <el-table-column prop="idcardno" align="center" label="身份证号" width="200" > <template slot-scope="scope"> <el-input v-model="scope.row.idcardno" placeholder="身份证号" /> <el-input v-model="scope.row.idcardno" :disabled="scope.row.isunit == 1" :placeholder=" scope.row.isunit == 1 ? '单位无需填写' : '身份证号' " /> </template> </el-table-column> @@ -470,34 +490,43 @@ :visible.sync="showSelectionDialog" :close-on-click-modal="false" :title="'绩效人员(含所在单位)账号选择'" width="500px" width="560px" style="text-align: center" v-loading="loading" > <el-form ref="funddetailForm" :model="funddetailForm"> <el-row> <el-form-item label="请输入选择:" prop="unitno" label-width="120px"> <el-form-item label="筛查人员:" prop="userKey" label-width="120px"> <el-select v-model="funddetailForm.userName" v-model="funddetailForm.userKey" filterable remote reserve-keyword clearable allow-create default-first-option placeholder="请输入姓名或单位名称" :remote-method="remoteMethod" :loading="loadingperformance" :filter-method="filterPersonOption" placeholder="输入 姓名 / 单位 / 工号 / 身份证后4位 筛查,也可直接输入姓名" style="width: 100%" > <el-option v-for="item in optionsperformance" :key="item.value" :label="item.label" :value="item.value" :key="item.id" :label="personOptionLabel(item)" :value="item.id" > <span style="float: left">{{ item.username }}</span> <span style="float: right; color: #8492a6; font-size: 13px"> {{ item.isUnitPerson ? "单位" : "工号 " + (item.userno || "-") }}{{ item.unitname ? " · " + item.unitname : "" }} </span> </el-option> </el-select> </el-form-item> </el-row> <el-row style="text-align: left; padding-left: 120px"> <span style="color: #909399; font-size: 12px"> 候选为人员类型含「参与绩效」的人员;选中后身份证号、银行账号等自动带出,单位为单位的行不校验身份证号。 </span> </el-row> </el-form> <span slot="footer" class="dialog-footer"> @@ -798,12 +827,25 @@ <el-radio-button :label="70">年度绩效</el-radio-button> </el-radio-group> </div> <div style="margin-bottom: 12px; margin-left: 10px;"> <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: 330px; overflow-y: auto"> <el-checkbox :indeterminate="isIndeterminate" v-model="checkAll" @change="handleCheckAllChange" >全选</el-checkbox >全选(当前筛查结果)</el-checkbox > <div style="margin: 15px 0;"></div> <el-checkbox-group @@ -812,10 +854,11 @@ > <el-checkbox border v-for="city in cityOptions" v-for="city in filteredCityOptions" :label="city" :key="city.userNo" >{{ city.userName }}</el-checkbox :key="city.id" >{{ city.username }}{{ city.isUnitPerson ? "(单位)" : "" }}{{ city.unitname ? " · " + city.unitname : "" }}</el-checkbox > </el-checkbox-group> </div> @@ -859,7 +902,6 @@ listOrganization, getOrganization, listReportname, listperformance, listUser } from "@/api/project/organization"; @@ -931,7 +973,10 @@ feeUnitList: [], batchdialogVisible: false, itemlist: [], //绩效人员候选(人员类型含「参与绩效」的人员,来自 service_externalperson) cityOptions: [], //批量录入弹框的筛查关键字 personKeyword: "", checkAll: false, checkedCities: [], Personnelforname: null, @@ -1064,6 +1109,7 @@ funddetailForm: { index: null, userKey: null, userName: null, beneficiaryname: null, beneficiaryno: null, @@ -1199,7 +1245,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: {}, //方法集合 @@ -1625,7 +1680,12 @@ ); return; } if (tempDetail.idcardno == null) { // 单位行(isunit=1)不需要身份证号 if ( tempDetail.isunit != 1 && (tempDetail.idcardno == null || String(tempDetail.idcardno).trim() === "") ) { this.$modal.msgWarning( "服务项目表中,第" + (k + 1).toString() + @@ -1653,6 +1713,22 @@ return; } } } //明细重复校验:同一人员/单位不允许出现多行 const detailNameMap = {}; for (let k = 0; k < this.rbDetails.length; k++) { const nm = String(this.rbDetails[k].beneficiaryname || "").trim(); if (!nm) { continue; } if (detailNameMap[nm]) { this.$modal.msgWarning( "服务项目表中,人员名称:" + nm + " 重复,请核对后再提交保存!" ); return; } detailNameMap[nm] = true; } //处理明细 @@ -1866,7 +1942,7 @@ this.personlist = res.rows; for (let j = 0; j < this.personlist.length; j++) { //获取费用报销机构列表 if (this.personlist[j].usertype == "org") { if (this.hasUserType(this.personlist[j].usertype, "org")) { this.feeUnitList.push({ id: this.personlist[j].id, reportNo: this.personlist[j].userno, @@ -1877,7 +1953,7 @@ }); } //专家人员列表 else if (res.rows[j].usertype == "expert") { else if (this.hasUserType(res.rows[j].usertype, "expert")) { this.expertlist.push({ expertNo: this.personlist[j].id, exportName: this.personlist[j].username @@ -2153,6 +2229,22 @@ } } //明细重复校验:同一人员/单位不允许出现多行 const detailNameMap = {}; for (let k = 0; k < this.rbDetails.length; k++) { const nm = String(this.rbDetails[k].beneficiaryname || "").trim(); if (!nm) { continue; } if (detailNameMap[nm]) { this.$modal.msgWarning( "服务项目表中,人员名称:" + nm + " 重复,请核对后再提交保存!" ); return; } detailNameMap[nm] = true; } //处理明细 for (let i = 0; i < this.rbDetails.length; i++) { let tempDetail = this.rbDetails[i]; @@ -2239,16 +2331,18 @@ } }); }, // 全选 // 全选(按当前筛查结果) 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() { @@ -2273,18 +2367,107 @@ }, // 批量录入点击 Batchentry() { // ethical this.listperformance(); this.personKeyword = ""; this.checkedCities = []; this.checkAll = false; this.isIndeterminate = false; this.batchdialogVisible = true; }, /** * 载入绩效人员候选。 * 改造前取自人员分组表(service_externalperson_group,group_id=1); * 2026-09-27 起改为「人员类型(UserType,可多选)含 参与绩效」的外围人员账户, * 不再依赖分组表。 */ listperformance() { listperformance(1).then(res => { this.cityOptions = res.data; this.listperformancea = []; this.listperformancea = this.cityOptions.map(item => { return { value: item.userName, label: item.userName }; }); listExternalperson({ usertype: "performance", pageNum: 1, pageSize: 10000 }).then(res => { const rows = res.rows || []; this.cityOptions = rows //只保留启用账号(与改造前分组表接口的 userstatus=1 口径一致)。 //注意:ServiceExternalperson 实体没有 del_flag 字段,接口不返回该字段, //不能用它做过滤,否则 undefined == 0 会把全部候选人滤空。 .filter( item => item.userstatus === undefined || item.userstatus === null || item.userstatus == 1 ) .map(item => { return Object.assign({}, item, { isUnitPerson: this.hasUserType(item.usertype, "org") }); }); }); }, /** 人员类型(多值,逗号分隔)是否包含指定类型 */ hasUserType(usertype, code) { if (!usertype) { return false; } return ( String(usertype) .split(",") .map(t => t.trim()) .indexOf(code) > -1 ); }, /** 关键字命中判断:姓名 / 单位 / 工号 / 身份证后4位 */ personMatchKeyword(item, kw) { const idcard = String(item.idcardno || ""); return ( String(item.username || "") .toLowerCase() .indexOf(kw) > -1 || String(item.unitname || "") .toLowerCase() .indexOf(kw) > -1 || String(item.userno || "") .toLowerCase() .indexOf(kw) > -1 || (idcard.length >= 4 && idcard.slice(-4).indexOf(kw) > -1) ); }, /** 识别人员类别:单位(人员类型含 org)还是个人 */ isUnitPerson(item) { return !!(item && this.hasUserType(item.usertype, "org")); }, /** 选择框下拉项文案:姓名(单位) */ personOptionLabel(item) { return ( (item.username || "") + (item.unitname ? "(" + item.unitname + ")" : "") + (item.isUnitPerson ? " - 单位" : "") ); }, /** 选择框候选:空关键字返回全部,否则按关键字筛查 */ filterPersonCandidates(kw) { if (!kw) { return this.cityOptions.slice(); } return this.cityOptions.filter(item => this.personMatchKeyword(item, kw)); }, /** 选择框本地筛查(姓名/单位/工号/身份证后4位) */ filterPersonOption(query) { const kw = (query || "").trim().toLowerCase(); this.optionsperformance = this.filterPersonCandidates(kw); // 已选中项要保留在下拉里,否则回显会空白 const picked = this.getPickedPerson(this.funddetailForm.userKey); if (picked && this.optionsperformance.indexOf(picked) === -1) { this.optionsperformance = [picked].concat(this.optionsperformance); } }, /** 按唯一键(人员 ID)取候选;手工输入的姓名返回 null */ getPickedPerson(key) { if (key === null || key === undefined || key === "") { return null; } return ( this.cityOptions.find(item => String(item.id) === String(key)) || null ); }, // 确认录入 BatchentryFn() { @@ -2292,23 +2475,42 @@ if (!this.rbDetails[0].applytype) { this.rbDetails = []; } // 明细中已有的人员姓名,用于重复提醒(与伦理专家费页的判重口径一致) 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.username || "").trim(); if (nameKey && existed[nameKey]) { duplicated.push(item.username); return; } if (nameKey) { existed[nameKey] = true; } const isunit = this.isUnitPerson(item); let rowData = { orderno: null, fundid: null, beneficiaryname: item.userName, beneficiaryno: item.usertNo, beneficiaryname: item.username, beneficiaryno: item.userno, isunit: isunit ? 1 : 0, unitname: item.unitname, unitno: null, unitno: item.unitno, uintuserno: null, title: item.title, idcardtype: null, idcardno: item.idcrad, idcardno: isunit ? "" : item.idcardno, sex: null, familyrelations: null, phone: null, depositbank: item.depositBank, bankcardno: item.bankCardNo, phone: item.telephone, depositbank: item.depositbank, bankcardno: item.bankcardno, branchbankname: item.branchbankname, annexbankcard: null, annexregistform: null, @@ -2351,6 +2553,11 @@ this.calculateTax(); this.batchdialogVisible = false; this.checkedCities = []; if (duplicated.length) { this.$modal.msgWarning( "以下人员在明细中已存在,已跳过重复添加:" + duplicated.join("、") ); } console.log(this.rowfeeItemTypes); }, //新增一行 @@ -2361,6 +2568,7 @@ fundid: null, beneficiaryname: null, beneficiaryno: null, isunit: 0, unitname: null, unitno: null, uintuserno: null, @@ -2460,64 +2668,78 @@ }); //全部删除后添加一条新行 }, //专家/医疗机构/费用报销机构选择 //绩效人员/单位选择:打开时预填当前行已选人员,避免"直接确定"把已有姓名清空 ShowDetailDialog(spoce, showType) { this.spocewl = spoce; this.selectionType = showType; this.selectionType = this.experthear; this.expertfrom = "1"; this.funddetailForm.index = spoce.$index; this.funddetailForm.beneficiaryno = spoce.row.beneficiaryno; this.funddetailForm.beneficiaryname = spoce.row.beneficiaryname; this.funddetailForm.unitno = spoce.row.unitno; this.funddetailForm.unitname = spoce.row.unitname; this.funddetailForm.expertname = ""; this.funddetailForm.expertidcardno = ""; this.funddetailForm.expertunitno = ""; this.funddetailForm.expertunitname = ""; this.funddetailForm.experttitle = ""; this.funddetailForm.expertdepositbank = ""; this.funddetailForm.expertbankcardno = ""; //当前行已选人员的回填:先按人员编号匹配,匹配不到再按姓名匹配 let cur = this.cityOptions.find( item => String(item.userno) === String(spoce.row.beneficiaryno) ) || null; if (!cur && spoce.row.beneficiaryname) { cur = this.cityOptions.find( item => item.username === spoce.row.beneficiaryname ) || null; } this.funddetailForm.userKey = cur ? cur.id : spoce.row.beneficiaryname || null; this.funddetailForm.userName = spoce.row.beneficiaryname || null; //打开时展示全量候选,便于直接浏览筛查 this.optionsperformance = this.cityOptions.slice(); this.showSelectionDialog = true; }, //专家/医疗机构/费用报销机构选择 /** * 确定选择:按唯一键(人员 ID)匹配候选, * 自动带出身份证号、银行账号、开户银行、电话、职称、人员编号、单位; * 单位(人员类型含 org)不写身份证号;手工输入的姓名不做带出。 */ ConfirmDetailDialog() { let tempIndex = this.funddetailForm.index; let tempExpertName = this.funddetailForm.userName; let singleDetail = this.rbDetails[tempIndex]; const tempIndex = this.funddetailForm.index; const singleDetail = this.rbDetails[tempIndex]; const key = this.funddetailForm.userKey; //从专家列表获取信息 this.cityOptions.map(res => { if (tempExpertName == res.userName) { singleDetail.beneficiaryno = res.userno; singleDetail.bankcardno = res.bankCardNo; singleDetail.branchbankname = res.branchbankname; singleDetail.depositbank = res.depositBank; singleDetail.idcardno = res.idcardNo; singleDetail.phone = res.telephone; singleDetail.title = res.title; singleDetail.unitname = res.unitname; singleDetail.unitno = parseInt(res.unitno); } }); singleDetail.beneficiaryname = tempExpertName; this.rbDetails[tempIndex] = singleDetail; this.showSelectionDialog = false; }, remoteMethod(query) { console.log(this.listperformancea); if (query !== "") { this.loadingperformance = true; setTimeout(() => { this.loadingperformance = false; this.optionsperformance = this.listperformancea.filter(item => { return item.label.toLowerCase().indexOf(query.toLowerCase()) > -1; }); }, 200); } else { this.optionsperformance = []; if (key === null || key === undefined || key === "") { this.$modal.msgWarning("请先筛查并选择绩效人员,或直接输入人员姓名!"); return; } const person = this.getPickedPerson(key); if (person) { // 命中人员库:自动带出全部信息 const isunit = this.isUnitPerson(person); singleDetail.beneficiaryname = person.username; singleDetail.beneficiaryno = person.userno; singleDetail.isunit = isunit ? 1 : 0; singleDetail.idcardno = isunit ? "" : person.idcardno; singleDetail.bankcardno = person.bankcardno; singleDetail.branchbankname = person.branchbankname; singleDetail.depositbank = person.depositbank; singleDetail.phone = person.telephone; singleDetail.title = person.title; singleDetail.unitname = person.unitname; singleDetail.unitno = person.unitno; } else { // 手工输入的人员:只写姓名,身份证号/银行账号由经办人填写 singleDetail.beneficiaryname = String(key); singleDetail.isunit = 0; this.$modal.msgWarning( "人员库中未找到「" + key + "」,已按手工录入处理,请自行填写身份证号、银行账号等信息。" ); } this.$set(this.rbDetails, tempIndex, singleDetail); this.showSelectionDialog = false; }, handleUploadError() {}, remove(file, fileList) { src/views/project/fundcheck/expertFeeCheck/index.vue
@@ -421,7 +421,8 @@ checkfund, listbypower, addFundSharedInfo, TotalcalculationFn TotalcalculationFn, validateExpertFee } from "@/api/project/fund"; import { onelistFunds, @@ -738,6 +739,27 @@ this.$modal.msgError("请选择审核结论!"); return; } // 审核“通过”前:先做专家劳务费 9 条费用控制校验(与后端拦截同口径) if (checkfundobj.flowconclusion == 1) { validateExpertFee({ id: this.form.id }).then(valid => { const errors = (valid && valid.errors) || []; const warnings = (valid && valid.warnings) || []; if (warnings.length > 0) { this.$modal.msgWarning(warnings.join(";")); } if (errors.length > 0) { this.$modal.msgError("不能审核通过:" + errors.join(";")); return; } this.doCheckfund(checkfundobj); }); return; } // 不通过:无需校验限额 this.doCheckfund(checkfundobj); }, doCheckfund(checkfundobj) { checkfund(checkfundobj).then(res => { if (res.code == 200) { this.reset(); @@ -745,11 +767,8 @@ this.getlistbypower(); this.dialogOpen = false; } else { if (res.msg) { this.$modal.msgError("操作失误"); } else { this.$modal.msgError(res.msg); } // 后端返回的具体原因(如超限额)优先展示 this.$modal.msgError(res.msg || "操作失误"); } }); }, src/views/project/travelexpenseapply/travelexpensedetail/index.vue
@@ -2538,7 +2538,7 @@ .map(item => ({ url: item, name: item })) : []; //审核状态 this.form.recordstatus = response.data.recordstatus + ""; this.form.recordstatus = this.toStatusText(response.data.recordstatus, "-1"); let ids = response.data.id; let listFundflowparams = { fundid: ids, @@ -2549,14 +2549,19 @@ }); //报销明细 getReimbursementdetaillist(ids).then(res => { this.rbDetails = res.data; //合计费用 // if (this.rbDetails.length > 0) this.sumTotalFee(); for (let i = 0; i < this.rbDetails.length; i++) { this.sumRowFee(this.rbDetails[i]); } }); getReimbursementdetaillist(ids) .then(res => { this.rbDetails = this.normalizeDetailList(res); //合计费用 // if (this.rbDetails.length > 0) this.sumTotalFee(); for (let i = 0; i < this.rbDetails.length; i++) { this.sumRowFee(this.rbDetails[i]); } }) .catch(err => { this.rbDetails = []; console.error("获取报销明细失败,id=" + ids, err); }); //付款信息 this.queryParamsPayee.rbid = ids; @@ -2592,7 +2597,8 @@ getReimbursement(id).then(response => { this.form = response.data; console.log(this.form.checkstatus, "checkstatus"); this.form.checkstatus = this.form.checkstatus.toString(); // checkstatus 在库中可为空(预审状态尚未产生时),直接 toString 会抛 TypeError 中断后续取数 this.form.checkstatus = this.toStatusText(this.form.checkstatus, "-1"); //票据 this.fileListto = this.form.annexfiles @@ -2619,16 +2625,22 @@ }); //报销明细 getReimbursementdetaillist(ids).then(res => { this.rbDetails = res.data; console.log(this.rbDetails); getReimbursementdetaillist(ids) .then(res => { this.rbDetails = this.normalizeDetailList(res); console.log("报销明细行数:", this.rbDetails.length, this.rbDetails); //合计费用 // if (this.rbDetails.length > 0) this.sumTotalFee(); for (let i = 0; i < this.rbDetails.length; i++) { this.sumRowFee(this.rbDetails[i]); } }); //合计费用 // if (this.rbDetails.length > 0) this.sumTotalFee(); for (let i = 0; i < this.rbDetails.length; i++) { this.sumRowFee(this.rbDetails[i]); } }) .catch(err => { // 取数失败时不再静默:置空并打出原因,避免“接口有数据但页面空白”无从排查 this.rbDetails = []; console.error("获取报销明细失败,id=" + ids, err); }); //付款信息 this.queryParamsPayee.rbid = ids; @@ -3251,6 +3263,45 @@ }); }, /** * 状态类字段转字符串(接口可能返回 null)。 * * 背景:checkstatus、recordstatus 等在库中可空,早期直接调用 .toString() * 会抛 TypeError 并中断其后的取数逻辑,表现为“接口已有数据但明细区空白”。 * * @param {*} value 原始状态值 * @param {string} fallback 为空时的兜底值 * @returns {string} */ toStatusText(value, fallback) { if (value === null || value === undefined || value === "") { return fallback; } return String(value); }, /** * 统一从明细接口响应中取出数组。 * * 正常为 AjaxResult 的 data 数组;若后端改为分页结构(total + rows), * res.data 将不是数组,直接绑定到 el-table 会导致“有数据但不显示”。 * * @param {Object} res 接口原始响应 * @returns {Array} 明细数组(异常时为空数组,不会让表格拿到非数组) */ normalizeDetailList(res) { if (!res) { return []; } if (Array.isArray(res.data)) { return res.data; } if (Array.isArray(res.rows)) { return res.rows; } return []; }, //计算一行合计 //计算当前行报销内容费用合计(金额一律走精确运算) sumRowFee(row) { vue.config.js
@@ -34,7 +34,7 @@ // detail: https://cli.vuejs.org/config/#devserver-proxy [process.env.VUE_APP_BASE_API]: { target:`http://localhost:8080`, // target:`http://192.168.100.10:8081`, // target:`http://192.168.100.110:8080`, // target:`http://192.168.100.137:8080`, // target: `https://slb.hospitalstar.com:9093`, changeOrigin: true,