| | |
| | | <el-form-item label="捐献案例" prop="name"> |
| | | <el-input :disabled="true" v-model="form.donorname" placeholder="请输入捐献者姓名" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="4"> |
| | | <div style="margin-left: 10px;"> |
| | | <dict-tag |
| | | :options="dict.type.sys_DonationStatus" |
| | | :value="form.invoicecount" |
| | | /> |
| | | </div> |
| | | </el-col> |
| | | </el-col> |
| | | <el-col :span="10"> |
| | | <el-form-item label="申请说明" prop="remark"> |
| | | <el-input v-model="form.remark" placeholder="申请说明" /> |
| | |
| | | </span> |
| | | </el-dialog> |
| | | <el-dialog v-dialogDrags :modal="false" :close-on-click-modal="false" :title="pdftitle" :visible.sync="pdfVisible" |
| | | width="60%"> |
| | | :fullscreen="pdfFullscreen" width="60%" custom-class="pdf-resize-dialog" @closed="pdfFullscreen = false"> |
| | | <template slot="title"> |
| | | <span>{{ pdftitle }}</span> |
| | | <el-button class="pdf-fullscreen-btn" size="mini" |
| | | :icon="pdfFullscreen ? 'el-icon-close' : 'el-icon-full-screen'" |
| | | @click="pdfFullscreen = !pdfFullscreen">{{ pdfFullscreen ? "还原" : "全屏" }}</el-button> |
| | | </template> |
| | | <el-alert |
| | | :title=" |
| | | activeName == 'invoice' |
| | | ? '当前正在操作:发票文件(请确认票据属于发票)' |
| | | : '当前正在操作:普通附件(请勿在此上传发票)' |
| | | " |
| | | :type="activeName == 'invoice' ? 'success' : 'warning'" |
| | | :closable="false" |
| | | show-icon |
| | | style="margin-bottom: 10px;" |
| | | /> |
| | | <div style="margin-bottom: 10px;"> |
| | | <el-table :data="tableDatatop" style="width: 100%"> |
| | | <el-table-column v-if="businessType == 4" prop="price" align="center" label="单价"> |
| | |
| | | </div> |
| | | </div> |
| | | |
| | | <div v-if="this.previewpdf && pdfimgsrcList.length" class="pdfimgmin"> |
| | | <!-- <img :src="pdfimg" /> --> |
| | | <el-image ref="imagePreview" style="width: 95%; height: 90%" :src="pdfimg" |
| | | @click="handleImageClick(initialIndex)"> |
| | | <!-- <div slot="error" class="image-slot"> |
| | | <i class="el-icon-picture-outline"></i> |
| | | </div> --> |
| | | </el-image> |
| | | <custom-image-viewer :url-list="pdfimgsrcList" :initial-index="currentIndex" :visible="viewerVisible" |
| | | @update:visible="viewerVisible = $event" @close="handleViewerClose" /> |
| | | </div> |
| | | <div v-else class="pdfimgmins">{{ hintitle }}</div> |
| | | <attachment-preview-pane :file="embeddedFile" style="flex: 1; min-width: 0;" /> |
| | | </div> |
| | | </el-tab-pane> |
| | | <el-tab-pane label="发票文件" name="invoice"> |
| | |
| | | </div> |
| | | </div> |
| | | |
| | | <div v-if="this.previewpdf && invoicepdfimgsrcList.length" class="pdfimgmin"> |
| | | <!-- <img :src="pdfimg" /> --> |
| | | <el-image style="width: 95%; height: 90%" :src="invoicepdfimg" |
| | | @click="invoicehandleImageClick(initialIndex)"> |
| | | <!-- <div slot="error" class="image-slot"> |
| | | <i class="el-icon-picture-outline"></i> |
| | | </div> --> |
| | | </el-image> |
| | | <custom-image-viewer :url-list="invoicepdfimgsrcList" :initial-index="invoicecurrentIndex" |
| | | :visible="invoiceviewerVisible" @update:visible="invoiceviewerVisible = $event" |
| | | @close="invoicehandleViewerClose" /> |
| | | </div> |
| | | <div v-else class="pdfimgmins">{{ hintitle }}</div> |
| | | <attachment-preview-pane :file="invoiceEmbeddedFile" style="flex: 1; min-width: 0;" /> |
| | | </div> |
| | | </el-tab-pane> |
| | | </el-tabs> |
| | |
| | | return callback(); |
| | | } |
| | | const regex = /^[1-9]\d{5}(19|20)\d{2}(0[1-9]|1[012])(0[1-9]|[12]\d|3[01])\d{3}([0-9]|X)$/; |
| | | if (regex.test(value)) { |
| | | } else { |
| | | callback(new Error("身份证号码格式错误")); |
| | | // 2026-10-19:格式不合法立即早退。原来写成 if/else 但 else 里没有 return, |
| | | // 会继续往下走异步查重,导致 callback 被调用两次、"身份证号码格式错误"被后续结果覆盖。 |
| | | if (!regex.test(value)) { |
| | | return callback(new Error("身份证号码格式错误")); |
| | | } |
| | | |
| | | // 2. 调用后端接口进行重复性校验 |
| | |
| | | }, |
| | | |
| | | dialogrules: { |
| | | // 2026-10-19:账户名称(账户来源=从账户库 时的下拉)也是必输项 |
| | | accountname: [ |
| | | { required: true, message: "请选择账户名称", trigger: "change" } |
| | | ], |
| | | usertype: [ |
| | | { required: true, message: "请输入账户类型", trigger: "blur" } |
| | | ], |
| | |
| | | unitTaxNo: [{ required: true, message: "请输入税号", trigger: "blur" }], |
| | | idcardno: [ |
| | | { |
| | | // 2026-10-19:加 required 标记只为让 el-form-item 标出必输星号(Element 的 isRequired 只认 rule.required)。 |
| | | // 本规则带 validator,async-validator 的 getValidationMethod 会直接返回该 validator、跳过内建 required 检查, |
| | | // 所以校验行为与改动前完全一致;另外**不能加 message**,否则会覆盖下面自定义 validator 的报错文案。 |
| | | required: true, |
| | | // 账户类型选择"单位机构"时不需要身份证号 |
| | | validator: (rule, value, callback) => { |
| | | if (this.hasUserType(this.accountselectform.usertype, "org")) { |
| | |
| | | invoicepdfimg: "", |
| | | invoicepdfimgsrcList: [], |
| | | invoDatatop: [], |
| | | activeName: "common", //文件类型 |
| | | activeName: "invoice", //文件类型:默认发票文件 |
| | | //上传附件路径 |
| | | uploadFileUrl: process.env.VUE_APP_BASE_API + "/common/upload", |
| | | ICDtoken: getToken(), |
| | |
| | | //票据文件 |
| | | pdftitle: "", |
| | | pdfimg: "", |
| | | invoiceEmbeddedFile: null, |
| | | pdfimgsrcList: [], |
| | | currentIndex: 0, // 初始索引 |
| | | invoicecurrentIndex: 0, // 初始索引 |
| | |
| | | }, |
| | | // 获取请求头 |
| | | Getnetworkheader() { |
| | | let string = window.location.href; |
| | | if (string.includes("9091")) { |
| | | const index = string.indexOf("9091"); |
| | | this.Networkheader = string.slice(0, index + 4); // 截取9091及其前部字符 |
| | | } else { |
| | | const index = string.indexOf("8032"); |
| | | this.Networkheader = string.slice(0, index + 4); // 截取8032及其前部字符 |
| | | } |
| | | // 以当前页面 origin 为准,兼容 80/8032/9091 及后续端口调整 |
| | | this.Networkheader = window.location.origin; |
| | | }, |
| | | /** 通过参数获取业务类型 */ |
| | | getroute() { |
| | |
| | | }); |
| | | this.batchdialogVisible = true; |
| | | }, |
| | | /** |
| | | * 明细行是否为「空记录」:服务项目、专家、专家所在单位 是否都为空 |
| | | * 用于批量录入专家时判断"复用最后一行"还是"追加新行" |
| | | */ |
| | | isBlankDetailRow(row) { |
| | | if (!row) { |
| | | return false; |
| | | } |
| | | const isEmpty = v => |
| | | v === null || v === undefined || String(v).trim() === ""; |
| | | return ( |
| | | isEmpty(row.itemid) && |
| | | isEmpty(row.beneficiaryname) && |
| | | isEmpty(row.unitno) && |
| | | isEmpty(row.unitname) |
| | | ); |
| | | }, |
| | | /** 批量录入弹框:姓名/单位/工号/身份证后4位 命中判断 */ |
| | | personMatchKeyword(item, kw) { |
| | | const idcard = String(item.idcrad || ""); |
| | |
| | | existed[String(row.beneficiaryname).trim()] = true; |
| | | } |
| | | }); |
| | | |
| | | // 2026-10-22:修正"导入后表格多出一行空记录"。 |
| | | // 规则:先看最后一条记录,若它的"服务项目 / 专家 / 专家所在单位"都是空的, |
| | | // 说明是一条空记录,就从这一行开始填;否则追加新行。 |
| | | let writeIndex = this.rbDetails.length; |
| | | if ( |
| | | writeIndex > 0 && |
| | | this.isBlankDetailRow(this.rbDetails[writeIndex - 1]) |
| | | ) { |
| | | writeIndex -= 1; |
| | | } |
| | | |
| | | const duplicated = []; |
| | | this.checkedCities.forEach(item => { |
| | |
| | | annexfiles: null |
| | | }; |
| | | |
| | | this.rbDetails.push(rowData); |
| | | if (writeIndex < this.rbDetails.length) { |
| | | // 复用最后那条空记录 |
| | | this.rbDetails.splice(writeIndex, 1, rowData); |
| | | } else { |
| | | this.rbDetails.push(rowData); |
| | | } |
| | | writeIndex++; |
| | | }); |
| | | for (let i = 0; i < this.rbDetails.length; i++) { |
| | | this.rbDetails[i].orderno = i + 1; |
| | |
| | | |
| | | /** |
| | | * 2026-10-15:上传时校验附件文件名(业务要求:不合格立即提示且不加入列表)。 |
| | | * 规则与后端 AttachmentNameValidator 同口径:必须含中文 / 去掉扩展名后主干 ≤20 字 / |
| | | * 规则与后端 AttachmentNameValidator 同口径:不强制含中文 / 去掉扩展名后主干 ≤20 字 / |
| | | * 不含空格及 / \ : * ? " < > | # % 等特殊字符。 |
| | | */ |
| | | checkUploadFileName(file) { |
| | |
| | | const config = { |
| | | headers: { Authorization: "Bearer " + this.ICDtoken } |
| | | }; |
| | | const pdfimg = this.Networkheader + "/prod-api" + response.fileName; |
| | | const pdfimg = this.Networkheader + process.env.VUE_APP_BASE_API + response.fileName; |
| | | //获取票据信息位置 |
| | | if (this.activeName == "common") { |
| | | if (response.code == 200) { |
| | | this.previewpdf = true; |
| | | |
| | | fetch(pdfimg, config) |
| | | .then(response => response.blob()) |
| | | .then(blob => { |
| | | // 将获取的数据流转换为URL |
| | | this.pdfimg = URL.createObjectURL(blob); |
| | | this.pdfimgsrcList.push(URL.createObjectURL(blob)); |
| | | this.fileListto.push({ |
| | | name: file.name, |
| | | url: URL.createObjectURL(blob) |
| | | }); |
| | | }) |
| | | .catch(error => { |
| | | console.error("Error loading image", error); |
| | | return; |
| | | }); |
| | | // 新上传文件直接使用浏览器本地 File,避免服务端文件地址/Token |
| | | // 异常时把 JSON 错误响应当成图片或 PDF 预览。 |
| | | const localBlob = file.raw; |
| | | const localUrl = URL.createObjectURL(localBlob); |
| | | this.pdfimg = localUrl; |
| | | this.embeddedFile = { |
| | | name: file.name, |
| | | url: localUrl, |
| | | type: /\.pdf$/i.test(file.name) ? "pdf" : "image" |
| | | }; |
| | | this.pdfimgsrcList.push(localUrl); |
| | | this.fileListto.push({ |
| | | name: file.name, |
| | | url: localUrl |
| | | }); |
| | | |
| | | this.$modal.msgSuccess(response.msg); |
| | | |
| | |
| | | } else { |
| | | if (response.code == 200) { |
| | | this.previewpdf = true; |
| | | fetch(pdfimg, config) |
| | | .then(response => response.blob()) |
| | | .then(blob => { |
| | | // 将获取的数据流转换为URL |
| | | this.invoicepdfimg = URL.createObjectURL(blob); |
| | | this.invoicepdfimgsrcList.push(URL.createObjectURL(blob)); |
| | | this.invoicefileListto.push({ |
| | | name: file.name, |
| | | url: URL.createObjectURL(blob) |
| | | }); |
| | | }) |
| | | .catch(error => { |
| | | console.error("Error loading image", error); |
| | | return; |
| | | }); |
| | | const localBlob = file.raw; |
| | | const localUrl = URL.createObjectURL(localBlob); |
| | | this.invoicepdfimg = localUrl; |
| | | this.invoiceEmbeddedFile = { |
| | | name: file.name, |
| | | url: localUrl, |
| | | type: /\.pdf$/i.test(file.name) ? "pdf" : "image" |
| | | }; |
| | | this.invoicepdfimgsrcList.push(localUrl); |
| | | this.invoicefileListto.push({ |
| | | name: file.name, |
| | | url: localUrl |
| | | }); |
| | | |
| | | this.$modal.msgSuccess(response.msg); |
| | | |
| | |
| | | |
| | | if (this.rbDetails[index].annexfilesList) { |
| | | // this.rbDetails[index].annexfilesList.forEach((value, indexson) => { |
| | | // const pdfimg = this.Networkheader + "/prod-api" + value.url; |
| | | // const pdfimg = this.Networkheader + process.env.VUE_APP_BASE_API + value.url; |
| | | // fetch(pdfimg, config) |
| | | // .then(response => response.blob()) |
| | | // .then(blob => { |
| | |
| | | |
| | | const fetchPromises = this.rbDetails[index].annexfilesList.map( |
| | | (value, indexson) => { |
| | | const pdfimg = this.Networkheader + "/prod-api" + value.url; |
| | | const pdfimg = this.Networkheader + process.env.VUE_APP_BASE_API + value.url; |
| | | return fetch(pdfimg, config) |
| | | .then(response => response.blob()) |
| | | .then(blob => { |
| | |
| | | if (this.rbDetails[index].invoicefilesList) { |
| | | const fetchPromises = this.rbDetails[index].invoicefilesList.map( |
| | | (value, indexson) => { |
| | | const pdfimg = this.Networkheader + "/prod-api" + value.url; |
| | | const pdfimg = this.Networkheader + process.env.VUE_APP_BASE_API + value.url; |
| | | return fetch(pdfimg, config) |
| | | .then(response => response.blob()) |
| | | .then(blob => { |
| | |
| | | console.log(this.rbDetails[index].annexfilesList, "annexfilesList"); |
| | | }, |
| | | |
| | | // 点击已上传文件 |
| | | // 费用报销:附件统一使用独立 FilePreview(录入、审批、详情一致) |
| | | downFile(item) { |
| | | if (this.activeName == "common") { |
| | | this.pdftitle = |
| | | "共" + this.pdfimgsrcList.length + "项,当前选中" + item.name; |
| | | let name = item.name.split("."); |
| | | if (name[1] == "pdf") { |
| | | this.$modal.msgWarning("当前文件暂不支持预览"); |
| | | this.previewpdf = false; |
| | | this.hintitle = "当前文件暂不支持预览"; |
| | | } else if (name[1] == "jpg" || "png") { |
| | | console.log(item, "展示"); |
| | | this.previewpdf = true; |
| | | if (item.url) { |
| | | this.pdfimg = item.url; |
| | | } else { |
| | | this.pdfimg = ""; |
| | | } |
| | | } else { |
| | | this.hintitle = "当前文件暂不支持预览"; |
| | | this.$modal.msgWarning("当前文件暂不支持预览"); |
| | | this.previewpdf = false; |
| | | } |
| | | } else { |
| | | this.pdftitle = |
| | | "共" + this.invoicepdfimgsrcList.length + "项,当前选中" + item.name; |
| | | let name = item.name.split("."); |
| | | if (name[1] == "pdf") { |
| | | this.$modal.msgWarning("当前文件暂不支持预览"); |
| | | this.previewpdf = false; |
| | | this.hintitle = "当前文件暂不支持预览"; |
| | | } else if (name[1] == "jpg" || "png") { |
| | | this.previewpdf = true; |
| | | if (item.url) { |
| | | this.invoicepdfimg = item.url; |
| | | } else { |
| | | this.invoicepdfimg = ""; |
| | | } |
| | | } else { |
| | | this.hintitle = "当前文件暂不支持预览"; |
| | | this.$modal.msgWarning("当前文件暂不支持预览"); |
| | | this.previewpdf = false; |
| | | } |
| | | const source = |
| | | this.activeName == "common" ? this.fileListto : this.invoicefileListto; |
| | | let list = (source || []).map(f => ({ name: f.name, url: f.url })); |
| | | let index = list.findIndex(f => f.url === item.url); |
| | | if (index < 0 && item && item.url) { |
| | | list = [{ name: item.name, url: item.url }]; |
| | | index = 0; |
| | | } |
| | | this.$previewFile(list, index > -1 ? index : 0); |
| | | }, |
| | | getIndexInArray(arr, obj) { |
| | | return arr.indexOf(obj); |
| | |
| | | //专家/医疗机构/费用报销机构选择 |
| | | confirmSelectionDialog() { |
| | | this.$refs["accountselectform"].validate(valid => { |
| | | // 2026-10-19:原来没有判断 valid,校验失败也照样往下走、照样保存(星号/红字形同虚设) |
| | | if (!valid) { |
| | | return; |
| | | } |
| | | let tempIndex = this.accountselectform.index; |
| | | if (this.selectionType == "account") { |
| | | if (this.accountfrom == "1") { |