| | |
| | | "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", |
| | |
| | | }) |
| | | } |
| | | |
| | | // 专家劳务费:保存前的费用控制校验(干跑,不落库) |
| | | // 返回 { errors: [阻断性信息], warnings: [提示信息], blocked: 是否阻断 } |
| | | export function validateExpertFee(data) { |
| | | return request({ |
| | | url: '/project/fund/validateExpertFee', |
| | | method: 'post', |
| | | data: data |
| | | }) |
| | | } |
| | | |
| | | // 伦理专家费打印 |
| | | export function getdownloadLL(id) { |
| | | return request({ |
| | |
| | | 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 || "下载文件"; |
| | | }, |
| | | |
| | | //文件上传成功回调 |
| | |
| | | |
| | | <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" /> |
| | |
| | | <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> |
| | |
| | | </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" |
| | |
| | | </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%"> |
| | |
| | | { 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" } |
| | |
| | | ] |
| | | }, |
| | | }; |
| | | }, |
| | | computed: { |
| | | /** 表单是否选择了"单位机构"(账号类别为多值,含 org 即为单位) */ |
| | | isUnitForm() { |
| | | return this.hasUserType(this.form.usertype, "org"); |
| | | } |
| | | }, |
| | | created() { |
| | | this.getUserProfile(); |
| | |
| | | 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; |
| | | }, |
| | | |
| | | /** 查询单位、人员账号列表 */ |
| | |
| | | userno: null, |
| | | username: null, |
| | | userstatus: 0, |
| | | usertype: null, |
| | | usertype: [], |
| | | personnelunitno: null, |
| | | delFlag: null, |
| | | createBy: null, |
| | |
| | | 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 = "人员/单位账号信息修改"; |
| | | }); |
| | |
| | | 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 => { |
| | |
| | | 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; |
| | | |
| | |
| | | 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(); |
| | | }); |
| | | } |
| | | } |
| | |
| | | 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> |
| | |
| | | </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> |
| | | |
| | |
| | | addorupdateFund, |
| | | ManualCalculation, |
| | | getdetailsByItemId, |
| | | validateExpertFee, |
| | | listbypower |
| | | } from "@/api/project/fund"; |
| | | |
| | |
| | | // 网络请求头 |
| | | Networkheader: null, |
| | | batchdialogVisible: false, |
| | | //批量录入弹框的筛查关键字 |
| | | personKeyword: "", |
| | | itemlist: [], |
| | | cityOptions: [], |
| | | checkAll: false, |
| | |
| | | ], |
| | | 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' } |
| | | ], |
| | |
| | | }, |
| | | |
| | | //监听属性 类似于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: {}, |
| | | //方法集合 |
| | |
| | | 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]; |
| | |
| | | //关闭窗口 |
| | | 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() { |
| | | //检查是否收到纸质材料 |
| | |
| | | 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("保存并审批"); |
| | | }); |
| | | }, |
| | | |
| | | // 连续审批:获取当前业务类型下的待审批列表 |
| | |
| | | }); |
| | | }, |
| | | |
| | | /** 人员类型(多值,逗号分隔)是否包含指定类型;org = 单位机构 */ |
| | | hasUserType(usertype, code) { |
| | | if (!usertype) { |
| | | return false; |
| | | } |
| | | return ( |
| | | String(usertype) |
| | | .split(",") |
| | | .map(t => t.trim()) |
| | | .indexOf(code) > -1 |
| | | ); |
| | | }, |
| | | /** 受益人/账户是否为"单位"(人员类型含 org) */ |
| | | //获取专家、单位机构的账户列表 |
| | | getAccountList() { |
| | | listExternalperson().then(res => { |
| | |
| | | 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, |
| | |
| | | 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 + ")" |
| | | }); |
| | |
| | | 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 + ")" |
| | | }); |
| | |
| | | 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; |
| | |
| | | // 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() { |
| | |
| | | // 批量录入点击 |
| | | 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, |
| | |
| | | |
| | | 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) { |
| | |
| | | fundid: null, |
| | | beneficiaryname: null, |
| | | beneficiaryno: null, |
| | | isunit: 0, |
| | | unitname: null, |
| | | itemdescribe: null, |
| | | unitno: null, |
| | |
| | | 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); |
| | |
| | | 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); |
| | |
| | | getdownloadYX, |
| | | getdownloadBX, |
| | | addorupdateFund, |
| | | getdetailsByItemId |
| | | getdetailsByItemId, |
| | | validateExpertFee |
| | | } from "@/api/project/fund"; |
| | | |
| | | import { |
| | |
| | | // }); |
| | | } |
| | | |
| | | // 保存前先做专家劳务费 9 条费用控制校验(同一案例累计) |
| | | validateExpertFee(this.form).then(valid => { |
| | | const errors = (valid && valid.errors) || []; |
| | | const warnings = (valid && valid.warnings) || []; |
| | | |
| | | // 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.selectDonotor(this.curCase); |
| | | this.$modal.msgSuccess("专家劳务费保存成功!"); |
| | | } else { |
| | | this.$modal.msgError("专家劳务费保存失败:" + response.msg); |
| | | } |
| | | }); |
| | | |
| | | //关闭窗口 |
| | | this.loading = false; |
| | | this.dialogOpen = false; |
| | | // this.getfundList(); |
| | | this.getList(); |
| | | this.selectDonotor(this.curCase); |
| | | this.$modal.msgSuccess("专家劳务费保存成功!"); |
| | | this.getList(); |
| | | } else { |
| | | this.$modal.msgError("专家劳务费保存失败:" + response.msg); |
| | | this.loading = false; |
| | | } |
| | | }); |
| | | }); |
| | | } |
| | | }); |
| | | }, |
| | |
| | | icon="el-icon-download" |
| | | size="small" |
| | | @click="Batchentry" |
| | | >批量录入人员</el-button |
| | | >批量导入绩效人员</el-button |
| | | > |
| | | </div> |
| | | </el-col> |
| | |
| | | <el-table-column |
| | | prop="beneficiaryname" |
| | | align="center" |
| | | label="绩效人员或单位" |
| | | label="绩效人员/单位" |
| | | width="250" |
| | | > |
| | | <template slot-scope="scope"> |
| | |
| | | </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> |
| | | |
| | |
| | | :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"> |
| | |
| | | <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 |
| | |
| | | > |
| | | <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> |
| | |
| | | listOrganization, |
| | | getOrganization, |
| | | listReportname, |
| | | listperformance, |
| | | listUser |
| | | } from "@/api/project/organization"; |
| | | |
| | |
| | | feeUnitList: [], |
| | | batchdialogVisible: false, |
| | | itemlist: [], |
| | | //绩效人员候选(人员类型含「参与绩效」的人员,来自 service_externalperson) |
| | | cityOptions: [], |
| | | //批量录入弹框的筛查关键字 |
| | | personKeyword: "", |
| | | checkAll: false, |
| | | checkedCities: [], |
| | | Personnelforname: null, |
| | |
| | | |
| | | funddetailForm: { |
| | | index: null, |
| | | userKey: null, |
| | | userName: null, |
| | | beneficiaryname: null, |
| | | beneficiaryno: null, |
| | |
| | | }, |
| | | |
| | | //监听属性 类似于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: {}, |
| | | //方法集合 |
| | |
| | | ); |
| | | return; |
| | | } |
| | | if (tempDetail.idcardno == null) { |
| | | // 单位行(isunit=1)不需要身份证号 |
| | | if ( |
| | | tempDetail.isunit != 1 && |
| | | (tempDetail.idcardno == null || |
| | | String(tempDetail.idcardno).trim() === "") |
| | | ) { |
| | | this.$modal.msgWarning( |
| | | "服务项目表中,第" + |
| | | (k + 1).toString() + |
| | |
| | | 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; |
| | | } |
| | | |
| | | //处理明细 |
| | |
| | | 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, |
| | |
| | | }); |
| | | } |
| | | //专家人员列表 |
| | | 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 |
| | |
| | | } |
| | | } |
| | | |
| | | //明细重复校验:同一人员/单位不允许出现多行 |
| | | 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]; |
| | |
| | | } |
| | | }); |
| | | }, |
| | | // 全选 |
| | | // 全选(按当前筛查结果) |
| | | 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() { |
| | |
| | | }, |
| | | // 批量录入点击 |
| | | 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() { |
| | |
| | | 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, |
| | |
| | | this.calculateTax(); |
| | | this.batchdialogVisible = false; |
| | | this.checkedCities = []; |
| | | if (duplicated.length) { |
| | | this.$modal.msgWarning( |
| | | "以下人员在明细中已存在,已跳过重复添加:" + duplicated.join("、") |
| | | ); |
| | | } |
| | | console.log(this.rowfeeItemTypes); |
| | | }, |
| | | //新增一行 |
| | |
| | | fundid: null, |
| | | beneficiaryname: null, |
| | | beneficiaryno: null, |
| | | isunit: 0, |
| | | unitname: null, |
| | | unitno: null, |
| | | uintuserno: null, |
| | |
| | | }); |
| | | //全部删除后添加一条新行 |
| | | }, |
| | | //专家/医疗机构/费用报销机构选择 |
| | | //绩效人员/单位选择:打开时预填当前行已选人员,避免"直接确定"把已有姓名清空 |
| | | 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); |
| | | if (key === null || key === undefined || key === "") { |
| | | this.$modal.msgWarning("请先筛查并选择绩效人员,或直接输入人员姓名!"); |
| | | return; |
| | | } |
| | | }); |
| | | 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); |
| | | |
| | | 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 { |
| | | this.optionsperformance = []; |
| | | // 手工输入的人员:只写姓名,身份证号/银行账号由经办人填写 |
| | | singleDetail.beneficiaryname = String(key); |
| | | singleDetail.isunit = 0; |
| | | this.$modal.msgWarning( |
| | | "人员库中未找到「" + |
| | | key + |
| | | "」,已按手工录入处理,请自行填写身份证号、银行账号等信息。" |
| | | ); |
| | | } |
| | | |
| | | this.$set(this.rbDetails, tempIndex, singleDetail); |
| | | this.showSelectionDialog = false; |
| | | }, |
| | | handleUploadError() {}, |
| | | remove(file, fileList) { |
| | |
| | | checkfund, |
| | | listbypower, |
| | | addFundSharedInfo, |
| | | TotalcalculationFn |
| | | TotalcalculationFn, |
| | | validateExpertFee |
| | | } from "@/api/project/fund"; |
| | | import { |
| | | onelistFunds, |
| | |
| | | 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(); |
| | |
| | | this.getlistbypower(); |
| | | this.dialogOpen = false; |
| | | } else { |
| | | if (res.msg) { |
| | | this.$modal.msgError("操作失误"); |
| | | } else { |
| | | this.$modal.msgError(res.msg); |
| | | } |
| | | // 后端返回的具体原因(如超限额)优先展示 |
| | | this.$modal.msgError(res.msg || "操作失误"); |
| | | } |
| | | }); |
| | | }, |
| | |
| | | .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, |
| | |
| | | }); |
| | | |
| | | //报销明细 |
| | | getReimbursementdetaillist(ids).then(res => { |
| | | this.rbDetails = res.data; |
| | | 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); |
| | | }); |
| | | |
| | | //付款信息 |
| | |
| | | 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 |
| | |
| | | }); |
| | | |
| | | //报销明细 |
| | | 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]); |
| | | } |
| | | }) |
| | | .catch(err => { |
| | | // 取数失败时不再静默:置空并打出原因,避免“接口有数据但页面空白”无从排查 |
| | | this.rbDetails = []; |
| | | console.error("获取报销明细失败,id=" + ids, err); |
| | | }); |
| | | |
| | | //付款信息 |
| | |
| | | }); |
| | | }, |
| | | |
| | | /** |
| | | * 状态类字段转字符串(接口可能返回 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) { |
| | |
| | | // 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, |