| | |
| | | <el-input v-model="form.initiatePerson" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <!-- <el-col :span="8"> |
| | | <el-form-item label="审查状态" prop="status"> |
| | | <el-select v-model="form.status" style="width: 100%"> |
| | | <el-option |
| | |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-col> --> |
| | | </el-row> |
| | | |
| | | <!-- 专家相关信息 --> |
| | |
| | | <div slot="header" class="clearfix"> |
| | | <span class="detail-title">专家审查情况</span> |
| | | <div style="float: right;"> |
| | | <el-button |
| | | type="warning" |
| | | size="mini" |
| | | @click="handleRefresh" |
| | | icon="el-icon-refresh" |
| | | > |
| | | 刷新 |
| | | </el-button> |
| | | <el-button size="mini" type="primary" @click="handleAddExpert"> |
| | | 添加专家 |
| | | </el-button> |
| | |
| | | <span v-else class="no-data">-</span> |
| | | </template> |
| | | </el-table-column> |
| | | |
| | | <!-- 在"审查时间"列后面添加"手签附件"列 --> |
| | | <el-table-column label="手签附件" width="120" align="center"> |
| | | <template slot-scope="scope"> |
| | | <template v-if="scope.row.sigin"> |
| | | <!-- 有签名,显示可点击预览的图片 --> |
| | | <el-button |
| | | type="text" |
| | | size="mini" |
| | | @click="handlePreviewSignature(scope.row.sigin)" |
| | | class="signature-preview-btn" |
| | | > |
| | | <i class="el-icon-picture" style="margin-right: 4px;"></i> |
| | | 查看签名 |
| | | </el-button> |
| | | </template> |
| | | <template v-else> |
| | | <!-- 无签名,显示斜杠 --> |
| | | <span class="no-signature">/</span> |
| | | </template> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="审查意见" min-width="200" show-overflow-tooltip> |
| | | <template slot-scope="scope"> |
| | | <span :class="{ 'expert-opinion': scope.row.expertopinion }"> |
| | |
| | | <el-dialog |
| | | title="添加专家" |
| | | :visible.sync="expertDialogVisible" |
| | | width="800px" |
| | | width="900px" |
| | | @close="handleExpertDialogClose" |
| | | > |
| | | <div style="margin-bottom: 20px;"> |
| | |
| | | :data="filteredExpertList" |
| | | v-loading="expertListLoading" |
| | | style="width: 100%" |
| | | max-height="400" |
| | | max-height="600" |
| | | @selection-change="handleExpertSelectionChange" |
| | | > |
| | | <el-table-column type="selection" width="55"></el-table-column> |
| | |
| | | ></el-table-column> |
| | | <el-table-column |
| | | label="联系电话" |
| | | prop="telephone" |
| | | prop="donorno" |
| | | width="120" |
| | | ></el-table-column> |
| | | </el-table> |
| | |
| | | placeholder="请选择截止时间" |
| | | value-format="yyyy-MM-dd HH:mm:ss" |
| | | style="width: 100%" |
| | | :disabled="sendForm.expertType === 'chief'" |
| | | :disabled="sendForm.expertType == 'chief'" |
| | | /> |
| | | <div v-if="sendForm.expertType !== 'chief'" style="margin-top: 5px;"> |
| | | <el-button-group> |
| | |
| | | </el-button-group> |
| | | </div> |
| | | <div |
| | | v-if="sendForm.expertType === 'chief'" |
| | | v-if="sendForm.expertType == 'chief'" |
| | | style="font-size: 12px; color: #999; margin-top: 5px;" |
| | | > |
| | | 主委专家无需设置截止时间 |
| | |
| | | </el-form> |
| | | <div slot="footer"> |
| | | <el-button @click="sendDialogVisible = false">取消</el-button> |
| | | <el-button type="primary" @click="handleSendConfirm" :loading="sending" |
| | | >确认发送</el-button |
| | | <el-button |
| | | type="primary" |
| | | @click="handleSendConfirm" |
| | | :loading="sendingAll" |
| | | :disabled="sendingAll" |
| | | > |
| | | {{ |
| | | sendingAll |
| | | ? `发送中 (${sendingProgress}/${sendingTotal})` |
| | | : "确认发送" |
| | | }} |
| | | </el-button> |
| | | </div> |
| | | </el-dialog> |
| | | |
| | | <!-- 或者在页面中添加进度条 --> |
| | | <div v-if="sendingAll" class="send-progress-container"> |
| | | <el-progress |
| | | :percentage="Math.round((sendingProgress / sendingTotal) * 100)" |
| | | :text-inside="true" |
| | | :stroke-width="20" |
| | | status="success" |
| | | > |
| | | <span>已发送 {{ sendingProgress }} / {{ sendingTotal }}</span> |
| | | </el-progress> |
| | | <div class="send-stats"> |
| | | <span class="stat-item success">成功: {{ sendingSuccessCount }}</span> |
| | | <span class="stat-item fail">失败: {{ sendingFailCount }}</span> |
| | | </div> |
| | | </div> |
| | | <!-- 专家历史审批情况对话框 --> |
| | | <el-dialog |
| | | title="专家历史审批情况" |
| | |
| | | ethicalreviewadd, |
| | | ethicalreviewInfo, |
| | | ethicalreExpertTotal, |
| | | sendNotification |
| | | sendNotification, |
| | | sendcall |
| | | } from "@/api/businessApi"; |
| | | import { listExternalperson } from "@/api/project/externalperson"; |
| | | import CaseBasicInfo from "@/components/CaseBasicInfo"; |
| | |
| | | { max: 500, message: "长度不能超过 500 个字符", trigger: "blur" } |
| | | ] |
| | | }, |
| | | |
| | | sending: false, // 单个发送状态 |
| | | sendingAll: false, // 全局发送状态 |
| | | sendingProgress: 0, // 发送进度 |
| | | sendingTotal: 0, // 总发送数 |
| | | sendingSuccessCount: 0, // 成功数 |
| | | sendingFailCount: 0, // 失败数 |
| | | sendingResults: [], // 发送结果列表 |
| | | originalFormData: null, // 原始表单数据 |
| | | originalExpertList: null, // 原始专家列表 |
| | | originalAttachments: null, // 原始附件列表 |
| | | isDataLoaded: false, // 数据是否已加载 |
| | | // 保存加载状态 |
| | | saveLoading: false, |
| | | completeLoading: false, |
| | |
| | | // 计算属性:普通专家数量 |
| | | normalExpertsCount() { |
| | | return this.ethicalreviewopinionsList.filter( |
| | | expert => expert.expertType === "0" |
| | | expert => expert.expertType == "0" |
| | | ).length; |
| | | }, |
| | | |
| | | // 计算属性:主委专家数量 |
| | | chiefExpertsCount() { |
| | | return this.ethicalreviewopinionsList.filter( |
| | | expert => expert.expertType === "1" |
| | | expert => expert.expertType == "1" |
| | | ).length; |
| | | }, |
| | | |
| | |
| | | // 计算属性:已同意专家数量 |
| | | approvedExpertsCount() { |
| | | return this.ethicalreviewopinionsList.filter( |
| | | expert => expert.expertconclusion === "1" |
| | | expert => expert.expertconclusion == "1" |
| | | ).length; |
| | | }, |
| | | |
| | |
| | | const total = this.totalExpertsCount; |
| | | const approved = this.approvedExpertsCount; |
| | | |
| | | if (total === 0) return "未审查"; |
| | | if (total == 0) return "未审查"; |
| | | if (approved >= Math.ceil(total * 0.7)) { |
| | | // 超过70%同意 |
| | | return "通过"; |
| | |
| | | const total = this.totalExpertsCount; |
| | | const approved = this.approvedExpertsCount; |
| | | |
| | | if (total === 0) return "info"; |
| | | if (total == 0) return "info"; |
| | | if (approved >= Math.ceil(total * 0.7)) { |
| | | return "success"; |
| | | } else if (approved >= Math.ceil(total * 0.5)) { |
| | |
| | | availableNormalExperts() { |
| | | return this.ethicalreviewopinionsList.filter( |
| | | expert => |
| | | expert.expertType === "0" && |
| | | expert.expertType == "0" && |
| | | (!expert.receiveStatus || |
| | | expert.receiveStatus === "0" || |
| | | expert.receiveStatus === "1") |
| | | expert.receiveStatus == "0" || |
| | | expert.receiveStatus == "1") |
| | | ); |
| | | }, |
| | | |
| | |
| | | availableChiefExperts() { |
| | | return this.ethicalreviewopinionsList.filter( |
| | | expert => |
| | | expert.expertType === "1" && |
| | | expert.expertType == "1" && |
| | | (!expert.receiveStatus || |
| | | expert.receiveStatus === "0" || |
| | | expert.receiveStatus === "1") |
| | | expert.receiveStatus == "0" || |
| | | expert.receiveStatus == "1") |
| | | ); |
| | | }, |
| | | |
| | |
| | | // 是否可以发送给主委专家(需要至少12个普通专家同意) |
| | | canSendToChiefExpert() { |
| | | const normalApprovedCount = this.ethicalreviewopinionsList.filter( |
| | | expert => expert.expertType === "0" && expert.expertconclusion === "1" |
| | | expert => expert.expertType == "0" && expert.expertconclusion == "1" |
| | | ).length; |
| | | return this.availableChiefExperts.length > 0 && normalApprovedCount >= 12; |
| | | }, |
| | |
| | | |
| | | // 发送对话框标题 |
| | | sendDialogTitle() { |
| | | if (this.sendForm.expertType === "chief") { |
| | | if (this.sendForm.expertType == "chief") { |
| | | return "发送主委专家审查"; |
| | | } else if (this.sendForm.expertType === "normal") { |
| | | } else if (this.sendForm.expertType == "normal") { |
| | | return "发送普通专家审查"; |
| | | } else { |
| | | return "发送专家审查"; |
| | |
| | | this.id = this.$route.query.id; |
| | | this.caseId = this.$route.query.infoid; |
| | | this.getDetail(this.infoid, this.id); |
| | | // 监听路由变化,防止用户离开页面 |
| | | window.addEventListener("beforeunload", this.beforeUnloadHandler); |
| | | }, |
| | | beforeDestroy() { |
| | | // 移除事件监听器 |
| | | window.removeEventListener("beforeunload", this.beforeUnloadHandler); |
| | | }, |
| | | methods: { |
| | | // 初始化新增数据 |
| | |
| | | response = await reviewinitiateBaseInfoList({ infoid: infoid }); |
| | | } |
| | | |
| | | if (response.code === 200) { |
| | | if (response.code == 200) { |
| | | let detailData = {}; |
| | | |
| | | if (response.data && id) { |
| | |
| | | this.parseFilePatch(this.form.filePatch); |
| | | this.initAttachmentFileList(); |
| | | |
| | | // 如果专家审查意见列表不存在,初始化为空数组 |
| | | if (!this.form.ethicalreviewopinionsList) { |
| | | this.$set(this.form, "ethicalreviewopinionsList", []); |
| | | } |
| | | } else if (response.data && infoid) { |
| | | this.form = response.data[0]; |
| | | // 解析 filePatch 字段 |
| | | this.parseFilePatch(this.form.filePatch); |
| | | this.initAttachmentFileList(); |
| | | |
| | | // 如果专家审查意见列表不存在,初始化为空数组 |
| | | if (!this.form.ethicalreviewopinionsList) { |
| | | this.$set(this.form, "ethicalreviewopinionsList", []); |
| | | } |
| | | } |
| | | |
| | | // 设置 expertReviews 用于表格显示 |
| | | // 保存原始数据用于比较 |
| | | this.saveOriginalData(); |
| | | |
| | | this.expertReviews = this.form.ethicalreviewopinionsList; |
| | | this.isDataLoaded = true; |
| | | |
| | | this.$message.success("数据加载成功"); |
| | | } else { |
| | |
| | | } finally { |
| | | this.expertLoading = false; |
| | | } |
| | | }, |
| | | |
| | | // 保存原始数据 |
| | | saveOriginalData() { |
| | | // 深拷贝表单数据 |
| | | this.originalFormData = JSON.parse(JSON.stringify(this.form)); |
| | | |
| | | // 深拷贝专家列表 |
| | | this.originalExpertList = this.form.ethicalreviewopinionsList |
| | | ? JSON.parse(JSON.stringify(this.form.ethicalreviewopinionsList)) |
| | | : []; |
| | | |
| | | // 深拷贝附件列表 |
| | | this.originalAttachments = this.form.annexfilesList |
| | | ? JSON.parse(JSON.stringify(this.form.annexfilesList)) |
| | | : []; |
| | | }, |
| | | |
| | | // 解析 filePatch 字段 |
| | |
| | | |
| | | // 构建 filePatch 字段 |
| | | buildFilePatch() { |
| | | if (!this.form.annexfilesList || this.form.annexfilesList.length === 0) { |
| | | if (!this.form.annexfilesList || this.form.annexfilesList.length == 0) { |
| | | return ""; |
| | | } |
| | | return JSON.stringify(this.form.annexfilesList); |
| | |
| | | handleAttachmentRemove(file) { |
| | | if (file.url) { |
| | | const index = this.form.annexfilesList.findIndex( |
| | | item => item.path === file.url || item.fileUrl === file.url |
| | | item => item.path == file.url || item.fileUrl == file.url |
| | | ); |
| | | if (index > -1) { |
| | | this.form.annexfilesList.splice(index, 1); |
| | |
| | | |
| | | // 上传成功处理 |
| | | handleUploadSuccess({ file, fileList, response }) { |
| | | if (response.code === 200) { |
| | | if (response.code == 200) { |
| | | const attachmentObj = { |
| | | fileName: file.name, |
| | | path: response.data || file.url, |
| | |
| | | |
| | | // 文件预览 |
| | | handlePreview(file) { |
| | | console.log(file, "file"); |
| | | |
| | | this.currentPreviewFile = { |
| | | fileName: file.fileName, |
| | | fileUrl: file.path || file.fileUrl, |
| | | fileType: this.getFileType(file.fileName) |
| | | }; |
| | | this.previewVisible = true; |
| | | }, |
| | | // 文件预览 |
| | | handlePreviewSignature(file) { |
| | | console.log(file, "file"); |
| | | |
| | | this.currentPreviewFile = { |
| | | fileName: file, |
| | | fileUrl: file, |
| | | fileType: "png" |
| | | }; |
| | | this.previewVisible = true; |
| | | }, |
| | |
| | | // 职称包含"主任委员"或者expertType为"1" |
| | | return ( |
| | | (expert.title && expert.title.includes("主任委员")) || |
| | | expert.expertType === "1" |
| | | expert.expertType == "1" |
| | | ); |
| | | }, |
| | | |
| | | // 专家类型文本转换 |
| | | getExpertTypeText(type) { |
| | | return type === "1" ? "主委专家" : "普通专家"; |
| | | return type == "1" ? "主委专家" : "普通专家"; |
| | | }, |
| | | |
| | | // 审查状态过滤器 |
| | |
| | | |
| | | // 专家行样式 |
| | | getExpertRowClassName({ row }) { |
| | | return row.expertType === "1" ? "chief-expert-row" : "normal-expert-row"; |
| | | return row.expertType == "1" ? "chief-expert-row" : "normal-expert-row"; |
| | | }, |
| | | |
| | | // 获取专家唯一标识 |
| | |
| | | |
| | | // 专家类型变更处理 |
| | | handleExpertTypeChange() { |
| | | if (this.sendForm.expertType === "chief") { |
| | | if (this.sendForm.expertType == "chief") { |
| | | // 主委专家无需设置截止时间 |
| | | this.sendForm.endTime = ""; |
| | | } else { |
| | |
| | | this.saveLoading = true; |
| | | // 保存清空id便于后端整体删除新增 |
| | | this.form.ethicalreviewopinionsList.forEach(item=>{ |
| | | item.id=null |
| | | }) |
| | | item.id = null; |
| | | }); |
| | | try { |
| | | const submitData = { |
| | | ...this.form, |
| | |
| | | response = await ethicalreviewadd(submitData); |
| | | } |
| | | |
| | | if (response.code === 200) { |
| | | if (response.code == 200) { |
| | | this.$message.success("保存成功"); |
| | | // 保存成功后更新原始数据 |
| | | this.saveOriginalData(); |
| | | this.isEdit = false; |
| | | if (!this.form.id && response.data && response.data.id) { |
| | | this.form.id = response.data.id; |
| | |
| | | |
| | | const response = await ethicalreviewedit(updateData); |
| | | |
| | | if (response.code === 200) { |
| | | if (response.code == 200) { |
| | | this.$message.success("审查状态已更新为完成"); |
| | | this.form.status = "3"; |
| | | this.form.endTime = updateData.endTime; |
| | |
| | | |
| | | const response = await ethicalreviewedit(updateData); |
| | | |
| | | if (response.code === 200) { |
| | | if (response.code == 200) { |
| | | this.$message.success("审查已中止,所有专家状态已更新"); |
| | | this.form.status = "2"; |
| | | } else { |
| | |
| | | try { |
| | | const updateData = { |
| | | ...this.form, |
| | | status: "2", // 审查中止 |
| | | status: "4", // 审查中止 |
| | | endTime: new Date() |
| | | .toISOString() |
| | | .replace("T", " ") |
| | |
| | | |
| | | const response = await ethicalreviewedit(updateData); |
| | | |
| | | if (response.code === 200) { |
| | | if (response.code == 200) { |
| | | this.$message.success("审查已结束"); |
| | | this.form.status = "2"; |
| | | this.form.status = "4"; |
| | | this.form.endTime = updateData.endTime; |
| | | } else { |
| | | this.$message.error("操作失败:" + (response.msg || "未知错误")); |
| | |
| | | this.expertDialogVisible = true; |
| | | this.loadExperts(); |
| | | }, |
| | | /** |
| | | * 刷新页面数据 |
| | | */ |
| | | async refreshPageData() { |
| | | try { |
| | | // 重置数据状态 |
| | | this.isDataLoaded = false; |
| | | |
| | | // 清空当前数据 |
| | | this.form = { |
| | | id: undefined, |
| | | infoid: undefined, |
| | | caseNo: "", |
| | | initiateTheme: "", |
| | | initiatePerson: "", |
| | | status: "0", |
| | | startTime: "", |
| | | cutOffTime: "", |
| | | endTime: "", |
| | | expertName: "", |
| | | expertNo: "", |
| | | expertType: "0", |
| | | expertConclusion: "", |
| | | expertOpinion: "", |
| | | expertTime: "", |
| | | orderNo: 1, |
| | | remark: "", |
| | | annexfilesList: [], |
| | | filePatch: "", |
| | | ethicalreviewopinionsList: [], |
| | | createBy: "", |
| | | createTime: "", |
| | | updateBy: "", |
| | | updateTime: "", |
| | | delFlag: "0" |
| | | }; |
| | | |
| | | this.attachmentFileList = []; |
| | | this.originalFormData = null; |
| | | this.originalExpertList = null; |
| | | this.originalAttachments = null; |
| | | |
| | | // 重新获取数据 |
| | | if (this.id) { |
| | | await this.getDetail(this.infoid, this.id); |
| | | } else if (this.infoid) { |
| | | await this.getDetail(this.infoid, null); |
| | | } else { |
| | | this.$message.warning("无法刷新,缺少必要的参数"); |
| | | } |
| | | |
| | | this.$message.success("数据刷新成功"); |
| | | } catch (error) { |
| | | console.error("刷新数据失败:", error); |
| | | this.$message.error("刷新数据失败,请重试"); |
| | | } |
| | | }, |
| | | |
| | | /** |
| | | * 处理页面刷新 |
| | | * 检查是否有未保存数据,确认后刷新页面 |
| | | */ |
| | | handleRefresh() { |
| | | // 检查是否有未保存的编辑 |
| | | if (this.hasUnsavedChanges()) { |
| | | this.$confirm( |
| | | "当前有未保存的数据,刷新页面将丢失这些更改。是否继续刷新?", |
| | | "警告", |
| | | { |
| | | confirmButtonText: "继续刷新", |
| | | cancelButtonText: "取消", |
| | | type: "warning", |
| | | distinguishCancelAndClose: true, |
| | | beforeClose: (action, instance, done) => { |
| | | if (action === "confirm") { |
| | | instance.confirmButtonLoading = true; |
| | | instance.confirmButtonText = "刷新中..."; |
| | | |
| | | // 延迟执行以确保UI更新 |
| | | setTimeout(() => { |
| | | done(); |
| | | instance.confirmButtonLoading = false; |
| | | |
| | | // 用户确认刷新 |
| | | this.refreshPageData(); |
| | | }, 300); |
| | | } else { |
| | | this.$message({ |
| | | type: "info", |
| | | message: "已取消刷新" |
| | | }); |
| | | done(); |
| | | } |
| | | } |
| | | } |
| | | ).catch(action => { |
| | | if (action === "cancel") { |
| | | this.$message({ |
| | | type: "info", |
| | | message: "已取消刷新" |
| | | }); |
| | | } |
| | | }); |
| | | } else { |
| | | // 没有未保存的编辑,直接刷新 |
| | | this.refreshPageData(); |
| | | } |
| | | }, |
| | | // 检查是否有未保存的数据变化 |
| | | hasUnsavedChanges() { |
| | | if (!this.isDataLoaded) { |
| | | return false; // 数据未加载,无需检测 |
| | | } |
| | | |
| | | // 1. 检查表单字段变化 |
| | | const formFieldsChanged = this.checkFormFieldsChanged(); |
| | | |
| | | // 2. 检查专家列表变化 |
| | | const expertListChanged = this.checkExpertListChanged(); |
| | | |
| | | // 3. 检查附件列表变化 |
| | | const attachmentsChanged = this.checkAttachmentsChanged(); |
| | | |
| | | return formFieldsChanged || expertListChanged || attachmentsChanged; |
| | | }, |
| | | |
| | | // 检查表单字段是否有变化 |
| | | checkFormFieldsChanged() { |
| | | if (!this.originalFormData) return false; |
| | | |
| | | const formKeys = [ |
| | | "initiateTheme", |
| | | "initiatePerson", |
| | | "status", |
| | | "expertConclusion", |
| | | "expertOpinion", |
| | | "expertTime", |
| | | "remark" |
| | | ]; |
| | | |
| | | for (const key of formKeys) { |
| | | if (this.form[key] !== this.originalFormData[key]) { |
| | | console.log( |
| | | `表单字段变化: ${key}`, |
| | | this.form[key], |
| | | this.originalFormData[key] |
| | | ); |
| | | return true; |
| | | } |
| | | } |
| | | |
| | | return false; |
| | | }, |
| | | |
| | | // 检查专家列表变化 |
| | | checkExpertListChanged() { |
| | | if (!this.originalExpertList || !this.form.ethicalreviewopinionsList) { |
| | | return false; |
| | | } |
| | | |
| | | const original = this.originalExpertList; |
| | | const current = this.form.ethicalreviewopinionsList; |
| | | |
| | | // 1. 检查数量变化 |
| | | if (original.length !== current.length) { |
| | | console.log("专家数量变化:", original.length, "->", current.length); |
| | | return true; |
| | | } |
| | | |
| | | // 2. 检查每个专家的变化 |
| | | for (let i = 0; i < original.length; i++) { |
| | | const origExpert = original[i]; |
| | | const currExpert = current[i]; |
| | | |
| | | // 检查关键字段变化 |
| | | const fieldsToCheck = [ |
| | | "expertconclusion", |
| | | "expertopinion", |
| | | "receiveStatus", |
| | | "conclusiontime", |
| | | "startTime", |
| | | "endTime", |
| | | "sendType" |
| | | ]; |
| | | |
| | | for (const field of fieldsToCheck) { |
| | | if (origExpert[field] !== currExpert[field]) { |
| | | console.log( |
| | | `专家${i}的${field}字段变化:`, |
| | | origExpert[field], |
| | | "->", |
| | | currExpert[field] |
| | | ); |
| | | return true; |
| | | } |
| | | } |
| | | } |
| | | |
| | | return false; |
| | | }, |
| | | |
| | | // 检查附件列表变化 |
| | | checkAttachmentsChanged() { |
| | | if (!this.originalAttachments || !this.form.annexfilesList) { |
| | | return false; |
| | | } |
| | | |
| | | const original = this.originalAttachments; |
| | | const current = this.form.annexfilesList; |
| | | |
| | | // 检查数量变化 |
| | | if (original.length !== current.length) { |
| | | console.log("附件数量变化:", original.length, "->", current.length); |
| | | return true; |
| | | } |
| | | |
| | | // 检查文件名变化(通常附件不会修改,只增删) |
| | | const originalFileNames = original.map(f => f.fileName || f.name).sort(); |
| | | const currentFileNames = current.map(f => f.fileName || f.name).sort(); |
| | | |
| | | for (let i = 0; i < originalFileNames.length; i++) { |
| | | if (originalFileNames[i] !== currentFileNames[i]) { |
| | | console.log( |
| | | "附件文件名变化:", |
| | | originalFileNames[i], |
| | | "->", |
| | | currentFileNames[i] |
| | | ); |
| | | return true; |
| | | } |
| | | } |
| | | |
| | | return false; |
| | | }, |
| | | |
| | | // 浏览器离开页面检测 |
| | | beforeUnloadHandler(event) { |
| | | if (this.hasUnsavedChanges()) { |
| | | const message = "您有未保存的更改,确定要离开吗?"; |
| | | event.returnValue = message; // 标准方式 |
| | | return message; // 某些浏览器需要返回字符串 |
| | | } |
| | | }, |
| | | // 加载专家列表 |
| | | async loadExperts() { |
| | | try { |
| | | this.expertListLoading = true; |
| | | const params = { |
| | | usertype: "伦理专家", // 伦理专家 |
| | | usertype: "ethical", // 伦理专家 |
| | | pageNum: this.expertPage.pageNum, |
| | | pageSize: this.expertPage.pageSize |
| | | }; |
| | |
| | | } |
| | | |
| | | const response = await listExternalperson(params); |
| | | if (response.code === 200) { |
| | | if (response.code == 200) { |
| | | this.expertList = response.rows || []; |
| | | this.expertTotal = response.total || 0; |
| | | } else { |
| | |
| | | |
| | | // 确认添加专家 |
| | | handleConfirmAddExpert() { |
| | | if (this.selectedExperts.length === 0) { |
| | | if (this.selectedExperts.length == 0) { |
| | | this.$message.warning("请选择要添加的专家"); |
| | | return; |
| | | } |
| | |
| | | expertType: isChief ? "1" : "0", // 主任委员设置为主委专家 |
| | | deptName: expert.unitname || "", |
| | | title: expert.title || "", |
| | | deptname: expert.telephone || "", |
| | | donorno: expert.telephone || "", |
| | | receiveStatus: "0", // 待接收 |
| | | expertconclusion: "", |
| | | expertopinion: "", |
| | |
| | | // 发送给单个专家 |
| | | handleSendToExpert(expert) { |
| | | this.currentSendExperts = [expert]; |
| | | this.sendForm.expertType = expert.expertType === "1" ? "chief" : "normal"; |
| | | this.sendForm.endTime = expert.expertType === "1" ? "" : ""; // 主委专家无需截止时间 |
| | | this.sendForm.expertType = expert.expertType == "1" ? "chief" : "normal"; |
| | | this.sendForm.endTime = expert.expertType == "1" ? "" : ""; // 主委专家无需截止时间 |
| | | this.sendDialogVisible = true; |
| | | }, |
| | | |
| | |
| | | return; |
| | | } |
| | | |
| | | if (this.currentSendExperts.length === 0) { |
| | | if (this.currentSendExperts.length == 0) { |
| | | this.$message.warning("没有找到可发送的专家"); |
| | | return; |
| | | } |
| | | |
| | | this.sending = true; |
| | | // 初始化发送状态 |
| | | this.sendingAll = true; |
| | | this.sendingProgress = 0; |
| | | this.sendingTotal = this.currentSendExperts.length; |
| | | this.sendingSuccessCount = 0; |
| | | this.sendingFailCount = 0; |
| | | this.sendingResults = []; |
| | | |
| | | // 创建一个进度对话框 |
| | | const progressDialog = this.$message({ |
| | | type: "info", |
| | | message: `正在发送通知,请稍候... (0/${this.sendingTotal})`, |
| | | duration: 0, // 不会自动关闭 |
| | | showClose: true |
| | | }); |
| | | |
| | | try { |
| | | // 发送给每个专家 |
| | | const sendPromises = this.currentSendExperts.map(async expert => { |
| | | // 使用Promise数组来顺序执行发送 |
| | | for (let i = 0; i < this.currentSendExperts.length; i++) { |
| | | const expert = this.currentSendExperts[i]; |
| | | |
| | | // 更新进度 |
| | | this.sendingProgress = i; |
| | | progressDialog.message = `正在发送通知,请稍候... (${i}/${this.sendingTotal})`; |
| | | |
| | | try { |
| | | // 构建发送数据 |
| | | const sendData = { |
| | | number: expert.deptname || "", // 用户手机号 |
| | | title: this.sendForm.title, |
| | | url: this.sendForm.url || "", |
| | | // 发送单个专家通知 |
| | | const result = await this.sendSingleExpert(expert, i); |
| | | this.sendingResults.push(result); |
| | | |
| | | createTime: new Date() |
| | | .toISOString() |
| | | .replace("T", " ") |
| | | .substring(0, 19) |
| | | }; |
| | | if (result.success) { |
| | | this.sendingSuccessCount++; |
| | | |
| | | // 调用发送通知接口 |
| | | const response = await sendNotification(sendData); |
| | | |
| | | if (response.code === 200) { |
| | | // 更新专家状态 |
| | | const index = this.form.ethicalreviewopinionsList.findIndex( |
| | | e => |
| | | e.expertNo === expert.expertNo || |
| | | e.expertname === expert.expertname |
| | | e.expertNo == expert.expertNo || |
| | | e.expertname == expert.expertname |
| | | ); |
| | | |
| | | if (index !== -1) { |
| | | if (index != -1) { |
| | | this.form.ethicalreviewopinionsList[index].receiveStatus = "1"; // 已接收 |
| | | this.form.ethicalreviewopinionsList[ |
| | | index |
| | |
| | | this.form.ethicalreviewopinionsList[index] |
| | | ); |
| | | } |
| | | |
| | | return { success: true, expert: expert.expertname }; |
| | | } else { |
| | | return { |
| | | success: false, |
| | | expert: expert.expertname, |
| | | error: response.msg |
| | | }; |
| | | this.sendingFailCount++; |
| | | } |
| | | } catch (error) { |
| | | console.error(`发送给专家 ${expert.expertname} 失败:`, error); |
| | | return { |
| | | this.sendingResults.push({ |
| | | success: false, |
| | | expert: expert.expertname, |
| | | error: error.message |
| | | }; |
| | | } |
| | | }); |
| | | |
| | | // 等待所有发送完成 |
| | | const results = await Promise.all(sendPromises); |
| | | |
| | | // 统计发送结果 |
| | | const successCount = results.filter(r => r.success).length; |
| | | const failCount = results.filter(r => !r.success).length; |
| | | |
| | | if (failCount === 0) { |
| | | this.$message.success(`成功发送给 ${successCount} 位专家`); |
| | | } else if (successCount > 0) { |
| | | this.$message.warning( |
| | | `成功发送给 ${successCount} 位专家,失败 ${failCount} 位` |
| | | ); |
| | | } else { |
| | | this.$message.error("发送失败,请稍后重试"); |
| | | this.sendingFailCount++; |
| | | } |
| | | |
| | | // 如果不是最后一个,等待100ms再发送下一个 |
| | | if (i < this.currentSendExperts.length - 1) { |
| | | await this.sleep(100); |
| | | } |
| | | } |
| | | |
| | | // 完成进度 |
| | | this.sendingProgress = this.sendingTotal; |
| | | progressDialog.message = `发送完成,成功 ${this.sendingSuccessCount} 个,失败 ${this.sendingFailCount} 个`; |
| | | |
| | | // 延迟1秒后关闭进度对话框 |
| | | await this.sleep(1000); |
| | | progressDialog.close(); |
| | | |
| | | // 显示最终结果 |
| | | if (this.sendingFailCount == 0) { |
| | | this.$message.success( |
| | | `成功发送给 ${this.sendingSuccessCount} 位专家` |
| | | ); |
| | | } else if (this.sendingSuccessCount > 0) { |
| | | this.$message.warning( |
| | | `成功发送给 ${this.sendingSuccessCount} 位专家,失败 ${this.sendingFailCount} 位` |
| | | ); |
| | | // 如果有失败,可以显示详细失败信息 |
| | | this.showFailedDetails(); |
| | | } else { |
| | | this.$message.error("全部发送失败,请稍后重试"); |
| | | } |
| | | |
| | | // 关闭发送对话框 |
| | | this.sendDialogVisible = false; |
| | | this.sendForm = { |
| | | expertType: "normal", |
| | |
| | | url: "" |
| | | }; |
| | | this.currentSendExperts = []; |
| | | // 保存整个单据 |
| | | this.handleSave(); |
| | | } catch (error) { |
| | | console.error("发送失败:", error); |
| | | this.$message.error("发送失败,请重试"); |
| | | console.error("发送过程中发生错误:", error); |
| | | progressDialog.close(); |
| | | this.$message.error("发送过程中发生错误,请重试"); |
| | | } finally { |
| | | this.sending = false; |
| | | this.sendingAll = false; |
| | | } |
| | | }, |
| | | // 发送单个专家的方法 |
| | | async sendSingleExpert(expert, index) { |
| | | try { |
| | | // 构建发送数据 |
| | | const sendData = { |
| | | number: expert.deptname || "", // 用户手机号 |
| | | title: this.sendForm.title, |
| | | url: this.sendForm.url || "", |
| | | createTime: new Date() |
| | | .toISOString() |
| | | .replace("T", " ") |
| | | .substring(0, 19) |
| | | }; |
| | | |
| | | console.log(`正在发送第 ${index + 1} 个专家: ${expert.expertname}`); |
| | | |
| | | // 调用发送通知接口 |
| | | // const response = await sendNotification(sendData); |
| | | const response = await sendcall({ |
| | | tel: expert.donorno ? expert.donorno : 13634195431, // 这里应该是 expert.deptname 或 expert.phone |
| | | messageContent: |
| | | "青岛大学附属医院上报潜在捐献案例,请登录OPO系统查看详细信息,及时进行对接。登录链接:https://brdeddd.qduhosos.cn/dklejdj/deljf/index" |
| | | }); |
| | | |
| | | if (response.code == 200) { |
| | | return { |
| | | success: true, |
| | | expert: expert.expertname, |
| | | index: index |
| | | }; |
| | | } else { |
| | | return { |
| | | success: false, |
| | | expert: expert.expertname, |
| | | index: index, |
| | | error: response.msg |
| | | }; |
| | | } |
| | | } catch (error) { |
| | | console.error(`发送给专家 ${expert.expertname} 失败:`, error); |
| | | return { |
| | | success: false, |
| | | expert: expert.expertname, |
| | | index: index, |
| | | error: error.message |
| | | }; |
| | | } |
| | | }, |
| | | |
| | | // 显示失败详情的方法 |
| | | showFailedDetails() { |
| | | const failedExperts = this.sendingResults.filter(r => !r.success); |
| | | if (failedExperts.length > 0) { |
| | | this.$confirm( |
| | | `有 ${failedExperts.length} 位专家发送失败,是否查看失败详情?`, |
| | | "发送结果", |
| | | { |
| | | confirmButtonText: "查看详情", |
| | | cancelButtonText: "关闭", |
| | | type: "warning" |
| | | } |
| | | ) |
| | | .then(() => { |
| | | let detailMessage = "发送失败的专家:\n\n"; |
| | | failedExperts.forEach((expert, index) => { |
| | | detailMessage += `${index + 1}. ${expert.expert}: ${ |
| | | expert.error |
| | | }\n`; |
| | | }); |
| | | |
| | | this.$alert(detailMessage, "发送失败详情", { |
| | | confirmButtonText: "确定", |
| | | customClass: "failed-details-dialog" |
| | | }); |
| | | }) |
| | | .catch(() => {}); |
| | | } |
| | | }, |
| | | |
| | | // 睡眠函数,用于间隔 |
| | | sleep(ms) { |
| | | return new Promise(resolve => setTimeout(resolve, ms)); |
| | | }, |
| | | // 删除专家审查 |
| | | handleDeleteExpertReview(expert, index) { |
| | | this.$confirm("确定要删除该专家的审查记录吗?", "提示", { |
| | |
| | | |
| | | const response = await ethicalreExpertTotal(params); |
| | | |
| | | if (response && response.code === 200) { |
| | | if (response && response.code == 200) { |
| | | this.expertHistoryData = response.data || response[0] || null; |
| | | } else { |
| | | this.$message.error( |
| | |
| | | .selected-case-info { |
| | | margin-bottom: 20px; |
| | | } |
| | | /* 发送进度样式 */ |
| | | .send-progress-container { |
| | | margin: 20px 0; |
| | | padding: 20px; |
| | | background: #f5f7fa; |
| | | border-radius: 8px; |
| | | } |
| | | |
| | | .send-stats { |
| | | display: flex; |
| | | justify-content: center; |
| | | gap: 30px; |
| | | margin-top: 10px; |
| | | } |
| | | |
| | | .stat-item { |
| | | font-size: 14px; |
| | | font-weight: 500; |
| | | padding: 4px 12px; |
| | | border-radius: 4px; |
| | | } |
| | | |
| | | .stat-item.success { |
| | | color: #67c23a; |
| | | background: #f0f9eb; |
| | | } |
| | | |
| | | .stat-item.fail { |
| | | color: #f56c6c; |
| | | background: #fef0f0; |
| | | } |
| | | |
| | | /* 失败详情对话框 */ |
| | | .failed-details-dialog { |
| | | min-width: 400px; |
| | | max-width: 600px; |
| | | } |
| | | |
| | | .failed-details-dialog .el-message-box__content { |
| | | max-height: 400px; |
| | | overflow-y: auto; |
| | | white-space: pre-wrap; |
| | | word-break: break-word; |
| | | } |
| | | .case-info-card { |
| | | border-left: 4px solid #67c23a; |
| | | } |
| | | /* 在CSS中添加 */ |
| | | :deep(.el-message-box) { |
| | | max-width: 500px; |
| | | } |
| | | |
| | | /* 添加未保存状态样式 */ |
| | | .unsaved-hint { |
| | | position: absolute; |
| | | top: 10px; |
| | | right: 10px; |
| | | background: #e6a23c; |
| | | color: white; |
| | | padding: 4px 8px; |
| | | border-radius: 4px; |
| | | font-size: 12px; |
| | | animation: pulse 2s infinite; |
| | | } |
| | | |
| | | @keyframes pulse { |
| | | 0% { |
| | | opacity: 0.8; |
| | | } |
| | | 50% { |
| | | opacity: 1; |
| | | } |
| | | 100% { |
| | | opacity: 0.8; |
| | | } |
| | | } |
| | | /* 响应式设计 */ |
| | | @media (max-width: 768px) { |
| | | .ethics-review-detail { |