<template>
|
<div class="ethics-review-detail">
|
<case-basic-info :case-id="caseId" :show-attachment="true" />
|
|
<el-card class="detail-card">
|
<!-- 伦理审查基本信息 -->
|
<div slot="header" class="clearfix">
|
<span class="detail-title">伦理审查基本信息</span>
|
<div style="float: right;">
|
<el-button type="primary" @click="handleSave" :loading="saveLoading">
|
保存
|
</el-button>
|
|
<el-button
|
type="success"
|
@click="handleCompleteReview"
|
:disabled="form.status == '3' || form.status == '2'"
|
:loading="completeLoading"
|
>
|
审查完成
|
</el-button>
|
|
<el-button
|
type="warning"
|
@click="handleSuspendReview"
|
:disabled="form.status == '2' || form.status == '3'"
|
:loading="suspendLoading"
|
>
|
审查中止
|
</el-button>
|
|
<el-button
|
type="danger"
|
@click="handleEndReview"
|
:disabled="form.status == '2'"
|
:loading="endLoading"
|
>
|
结束审查
|
</el-button>
|
</div>
|
</div>
|
|
<el-form :model="form" ref="form" :rules="rules" label-width="120px">
|
<el-row :gutter="20">
|
<el-col :span="8">
|
<el-form-item label="发起主题" prop="initiateTheme">
|
<el-input
|
v-model="form.initiateTheme"
|
placeholder="请输入发起主题"
|
/>
|
</el-form-item>
|
</el-col>
|
<el-col :span="8">
|
<el-form-item label="发起人" prop="initiatePerson">
|
<el-input v-model="form.initiatePerson" />
|
</el-form-item>
|
</el-col>
|
<!-- <el-col :span="8">
|
<el-form-item label="审查状态" prop="status">
|
<el-select v-model="form.status" style="width: 100%">
|
<el-option
|
v-for="dict in dict.type.sys_ethical"
|
:key="dict.value"
|
:label="dict.label"
|
:value="dict.value"
|
/>
|
</el-select>
|
</el-form-item>
|
</el-col> -->
|
</el-row>
|
|
<!-- 专家相关信息 -->
|
|
<el-row :gutter="20">
|
<el-col :span="8">
|
<el-form-item label="伦理审查结论" prop="expertConclusion">
|
<el-select v-model="form.expertConclusion" style="width: 100%">
|
<el-option label="同意" value="1" />
|
<el-option label="审查中" value="2" />
|
<el-option label="不同意" value="0" />
|
</el-select>
|
</el-form-item>
|
</el-col>
|
<el-col :span="8">
|
<el-form-item label="伦理审查结论时间" prop="expertTime">
|
<el-date-picker
|
v-model="form.expertTime"
|
type="datetime"
|
value-format="yyyy-MM-dd HH:mm:ss"
|
style="width: 100%"
|
/>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
|
<el-row :gutter="20">
|
<el-col :span="24">
|
<el-form-item label="审查意见" prop="expertOpinion">
|
<el-input
|
type="textarea"
|
:rows="2"
|
v-model="form.expertOpinion"
|
placeholder="请输入意见"
|
/>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
|
<el-row :gutter="20">
|
<el-col :span="24">
|
<el-form-item label="备注" prop="remark">
|
<el-input
|
type="textarea"
|
:rows="3"
|
v-model="form.remark"
|
placeholder="请输入备注信息"
|
/>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
</el-form>
|
</el-card>
|
|
<!-- 附件上传 -->
|
<el-card class="attachment-card">
|
<div slot="header" class="clearfix">
|
<span class="detail-title">相关附件</span>
|
</div>
|
|
<!-- 使用 UploadAttachment 组件 -->
|
<UploadAttachment
|
ref="uploadAttachment"
|
:file-list="attachmentFileList"
|
:limit="10"
|
accept=".pdf,.jpg,.jpeg,.png,.doc,.docx,.xls,.xlsx"
|
@change="handleAttachmentChange"
|
@upload-success="handleUploadSuccess"
|
@upload-error="handleUploadError"
|
@remove="handleAttachmentRemove"
|
/>
|
|
<!-- 附件列表 -->
|
<div
|
class="attachment-list"
|
v-if="form.annexfilesList && form.annexfilesList.length > 0"
|
>
|
<div class="list-title">
|
已上传附件 ({{ form.annexfilesList.length }})
|
</div>
|
<el-table :data="form.annexfilesList" style="width: 100%" size="small">
|
<el-table-column label="文件名" min-width="200">
|
<template slot-scope="scope">
|
<i
|
class="el-icon-document"
|
style="margin-right: 8px; color: #409EFF;"
|
></i>
|
<span class="file-name">{{ scope.row.fileName }}</span>
|
</template>
|
</el-table-column>
|
<el-table-column label="文件类型" width="100">
|
<template slot-scope="scope">
|
<el-tag size="small">{{
|
getFileType(scope.row.fileName)
|
}}</el-tag>
|
</template>
|
</el-table-column>
|
<el-table-column label="创建时间" width="160">
|
<template slot-scope="scope">
|
<span>{{ formatDateTime(scope.row.createTime) }}</span>
|
</template>
|
</el-table-column>
|
<el-table-column label="操作" width="266">
|
<template slot-scope="scope">
|
<el-button
|
size="mini"
|
type="primary"
|
@click="handlePreview(scope.row)"
|
>
|
预览
|
</el-button>
|
<el-button
|
size="mini"
|
type="success"
|
@click="handleDownload(scope.row)"
|
>
|
下载
|
</el-button>
|
<el-button
|
size="mini"
|
type="danger"
|
@click="handleRemoveAttachment(scope.$index)"
|
>
|
删除
|
</el-button>
|
</template>
|
</el-table-column>
|
</el-table>
|
</div>
|
|
<!-- 空状态 -->
|
<div
|
v-if="!form.annexfilesList || form.annexfilesList.length == 0"
|
class="empty-attachment"
|
>
|
<i
|
class="el-icon-folder-opened"
|
style="font-size: 60px; color: #C0C4CC; margin-bottom: 20px;"
|
></i>
|
<p style="color: #909399; font-size: 14px;">暂无附件,请上传相关文件</p>
|
</div>
|
</el-card>
|
|
<!-- 专家审查情况 -->
|
<el-card class="expert-card">
|
<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>
|
<el-button
|
size="mini"
|
type="primary"
|
@click="handleSendToNormalExperts"
|
:disabled="!canSendToNormalExperts"
|
>
|
发送专家
|
</el-button>
|
<el-button
|
size="mini"
|
type="success"
|
@click="handleSendToChiefExpert"
|
:disabled="!canSendToChiefExpert"
|
>
|
发送主委专家
|
</el-button>
|
</div>
|
</div>
|
|
<!-- 专家统计信息 -->
|
<div
|
class="expert-stats"
|
style="margin-top: 20px; padding: 15px; background: #f5f7fa; border-radius: 4px;"
|
>
|
<el-row :gutter="20">
|
<el-col :span="6">
|
<div class="stat-item">
|
<span class="stat-label">普通专家:</span>
|
<span class="stat-value">{{ normalExpertsCount }}人</span>
|
</div>
|
</el-col>
|
<el-col :span="6">
|
<div class="stat-item">
|
<span class="stat-label">主委专家:</span>
|
<span class="stat-value">{{ chiefExpertsCount }}人</span>
|
</div>
|
</el-col>
|
<el-col :span="6">
|
<div class="stat-item">
|
<span class="stat-label">已同意:</span>
|
<span class="stat-value"
|
>{{ approvedExpertsCount }}/{{ totalExpertsCount }}</span
|
>
|
</div>
|
</el-col>
|
<el-col :span="6">
|
<div class="stat-item">
|
<span class="stat-label">审查结果:</span>
|
<span class="stat-value">
|
<el-tag :type="overallConclusionFilter">
|
{{ overallConclusionText }}
|
</el-tag>
|
</span>
|
</div>
|
</el-col>
|
</el-row>
|
</div>
|
|
<!-- 专家审查表格 -->
|
<el-table
|
:data="ethicalreviewopinionsList"
|
v-loading="expertLoading"
|
style="width: 100%"
|
height="600"
|
:row-class-name="getExpertRowClassName"
|
>
|
<el-table-column label="序号" width="60" align="center" type="index" />
|
|
<el-table-column
|
label="专家姓名"
|
width="120"
|
align="center"
|
fixed="left"
|
>
|
<template slot-scope="scope">
|
<span
|
class="expert-name-link"
|
@click="handleViewExpertHistory(scope.row)"
|
>
|
{{ scope.row.expertname }}
|
</span>
|
<el-tag
|
v-if="scope.row.expertType == '1'"
|
size="mini"
|
type="danger"
|
style="margin-left: 5px;"
|
>主委</el-tag
|
>
|
</template>
|
</el-table-column>
|
|
<el-table-column label="专家编号" width="120" align="center">
|
<template slot-scope="scope">
|
<span>{{ scope.row.expertNo || "-" }}</span>
|
</template>
|
</el-table-column>
|
|
<el-table-column label="专家类型" width="100" align="center">
|
<template slot-scope="scope">
|
<span
|
:class="
|
scope.row.expertType == '1' ? 'chief-expert' : 'normal-expert'
|
"
|
>
|
{{ getExpertTypeText(scope.row.expertType) }}
|
</span>
|
</template>
|
</el-table-column>
|
|
<el-table-column label="审查状态" width="100" align="center">
|
<template slot-scope="scope">
|
<el-tag
|
:type="getReviewStatusFilter(scope.row.receiveStatus)"
|
size="small"
|
>
|
{{ getReviewStatusText(scope.row.receiveStatus) }}
|
</el-tag>
|
</template>
|
</el-table-column>
|
|
<el-table-column label="专家结论" width="120" align="center">
|
<template slot-scope="scope">
|
<el-tag
|
v-if="scope.row.expertconclusion"
|
:type="getConclusionFilter(scope.row.expertconclusion)"
|
size="small"
|
>
|
{{ getConclusionText(scope.row.expertconclusion) }}
|
</el-tag>
|
<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 }">
|
{{ scope.row.expertopinion || "暂无意见" }}
|
</span>
|
</template>
|
</el-table-column>
|
|
<el-table-column label="结论顺序" width="100" align="center">
|
<template slot-scope="scope">
|
<span>{{ scope.row.conclusionorder || "-" }}</span>
|
</template>
|
</el-table-column>
|
|
<el-table-column label="截止时间" width="160" align="center">
|
<template slot-scope="scope">
|
<span>{{
|
scope.row.endTime ? formatDateTime(scope.row.endTime) : "未设置"
|
}}</span>
|
</template>
|
</el-table-column>
|
|
<el-table-column label="审查时间" width="160" align="center">
|
<template slot-scope="scope">
|
<span>{{
|
scope.row.conclusiontime
|
? formatDateTime(scope.row.conclusiontime)
|
: "未审查"
|
}}</span>
|
</template>
|
</el-table-column>
|
<el-table-column label="发送时间" width="160" align="center">
|
<template slot-scope="scope">
|
<span>{{
|
scope.row.startTime
|
? formatDateTime(scope.row.startTime)
|
: "未发送"
|
}}</span>
|
</template>
|
</el-table-column>
|
|
<el-table-column label="操作" width="180" align="center" fixed="right">
|
<template slot-scope="scope">
|
<el-button
|
size="mini"
|
type="text"
|
icon="el-icon-s-promotion"
|
@click="handleSendToExpert(scope.row)"
|
:disabled="
|
scope.row.receiveStatus == '2' ||
|
scope.row.receiveStatus == '3' ||
|
scope.row.receiveStatus == '4'
|
"
|
:class="{
|
'sent-button':
|
scope.row.receiveStatus == '2' ||
|
scope.row.receiveStatus == '3'
|
}"
|
>
|
{{
|
scope.row.receiveStatus == "2" || scope.row.receiveStatus == "3"
|
? "已发送"
|
: "发送"
|
}}
|
</el-button>
|
<el-button
|
v-if="scope.row.receiveStatus == 0"
|
size="mini"
|
type="text"
|
icon="el-icon-delete"
|
@click="handleDeleteExpertReview(scope.row, scope.$index)"
|
style="color: #f56c6c;"
|
>
|
删除
|
</el-button>
|
</template>
|
</el-table-column>
|
</el-table>
|
</el-card>
|
|
<!-- 添加专家对话框 -->
|
<el-dialog
|
title="添加专家"
|
:visible.sync="expertDialogVisible"
|
width="900px"
|
@close="handleExpertDialogClose"
|
>
|
<div style="margin-bottom: 20px;">
|
<el-input
|
v-model="expertSearchQuery"
|
placeholder="请输入专家姓名或编号搜索"
|
style="width: 300px; margin-right: 10px;"
|
@keyup.enter.native="handleSearchExperts"
|
>
|
<el-button
|
slot="append"
|
icon="el-icon-search"
|
@click="handleSearchExperts"
|
></el-button>
|
</el-input>
|
<el-select
|
v-model="filterExpertType"
|
placeholder="专家类型"
|
style="width: 150px; margin-right: 10px;"
|
@change="handleSearchExperts"
|
>
|
<el-option label="全部" value=""></el-option>
|
<el-option label="普通专家" value="0"></el-option>
|
<el-option label="主任委员" value="1"></el-option>
|
</el-select>
|
<el-button type="primary" @click="handleSearchExperts">搜索</el-button>
|
<el-button @click="handleResetSearch">重置</el-button>
|
</div>
|
|
<el-table
|
:data="filteredExpertList"
|
v-loading="expertListLoading"
|
style="width: 100%"
|
max-height="600"
|
@selection-change="handleExpertSelectionChange"
|
>
|
<el-table-column type="selection" width="55"></el-table-column>
|
<el-table-column
|
label="专家姓名"
|
prop="username"
|
width="120"
|
></el-table-column>
|
<el-table-column
|
label="专家编号"
|
prop="userno"
|
width="120"
|
></el-table-column>
|
<el-table-column label="专家类型" width="100">
|
<template slot-scope="scope">
|
<el-tag
|
size="small"
|
:type="getIsChiefExpert(scope.row) ? 'danger' : ''"
|
>
|
{{ getIsChiefExpert(scope.row) ? "主任委员" : "普通专家" }}
|
</el-tag>
|
</template>
|
</el-table-column>
|
<el-table-column
|
label="单位名称"
|
prop="unitname"
|
show-overflow-tooltip
|
></el-table-column>
|
<el-table-column
|
label="职称"
|
prop="title"
|
width="100"
|
></el-table-column>
|
<el-table-column
|
label="联系电话"
|
prop="donorno"
|
width="120"
|
></el-table-column>
|
</el-table>
|
|
<div style="margin-top: 20px; text-align: center;">
|
<el-pagination
|
@size-change="handlePageSizeChange"
|
@current-change="handlePageChange"
|
:current-page="expertPage.pageNum"
|
:page-sizes="[10, 20, 50, 100]"
|
:page-size="expertPage.pageSize"
|
layout="total, sizes, prev, pager, next, jumper"
|
:total="filteredExpertTotal"
|
></el-pagination>
|
</div>
|
|
<div slot="footer">
|
<el-button @click="expertDialogVisible = false">取消</el-button>
|
<el-button
|
type="primary"
|
@click="handleConfirmAddExpert"
|
:disabled="selectedExperts.length == 0"
|
>确定添加</el-button
|
>
|
</div>
|
</el-dialog>
|
|
<!-- 发送专家对话框 -->
|
<el-dialog
|
:title="sendDialogTitle"
|
:visible.sync="sendDialogVisible"
|
width="500px"
|
@close="handleSendDialogClose"
|
>
|
<el-form :model="sendForm" ref="sendForm" label-width="100px">
|
<el-form-item label="专家类型" prop="expertType">
|
<el-radio-group
|
v-model="sendForm.expertType"
|
@change="handleExpertTypeChange"
|
>
|
<el-radio label="normal">普通专家</el-radio>
|
<el-radio label="chief">主委专家</el-radio>
|
</el-radio-group>
|
</el-form-item>
|
|
<el-form-item label="发送时间" prop="startTime" required>
|
<el-date-picker
|
v-model="sendForm.startTime"
|
type="datetime"
|
placeholder="请选择发送时间"
|
value-format="yyyy-MM-dd HH:mm:ss"
|
style="width: 100%"
|
/>
|
</el-form-item>
|
|
<el-form-item
|
label="截止时间"
|
prop="endTime"
|
:required="sendForm.expertType !== 'chief'"
|
>
|
<el-date-picker
|
v-model="sendForm.endTime"
|
type="datetime"
|
placeholder="请选择截止时间"
|
value-format="yyyy-MM-dd HH:mm:ss"
|
style="width: 100%"
|
:disabled="sendForm.expertType == 'chief'"
|
/>
|
<div v-if="sendForm.expertType !== 'chief'" style="margin-top: 5px;">
|
<el-button-group>
|
<el-button size="mini" @click="setEndTime(0.5)"
|
>半小时后</el-button
|
>
|
<el-button size="mini" @click="setEndTime(1)">一小时后</el-button>
|
<el-button size="mini" @click="setEndTime(2)">两小时后</el-button>
|
<el-button size="mini" @click="setEndTime(24)">一天后</el-button>
|
</el-button-group>
|
</div>
|
<div
|
v-if="sendForm.expertType == 'chief'"
|
style="font-size: 12px; color: #999; margin-top: 5px;"
|
>
|
主委专家无需设置截止时间
|
</div>
|
</el-form-item>
|
|
<el-form-item label="发送方式" prop="sendType" required>
|
<el-select
|
v-model="sendForm.sendType"
|
placeholder="请选择发送方式"
|
style="width: 100%"
|
>
|
<el-option label="系统发送" value="0"></el-option>
|
<el-option label="邮件发送" value="1"></el-option>
|
<el-option label="短信发送" value="2"></el-option>
|
<el-option label="其他方式" value="3"></el-option>
|
</el-select>
|
</el-form-item>
|
|
<el-form-item label="发送标题" prop="title" required>
|
<el-input v-model="sendForm.title" placeholder="请输入发送标题" />
|
</el-form-item>
|
|
<el-form-item label="发送内容" prop="content" required>
|
<el-input
|
type="textarea"
|
:rows="4"
|
v-model="sendForm.content"
|
placeholder="请输入发送给专家的审查内容说明"
|
/>
|
</el-form-item>
|
|
<el-form-item label="跳转链接" prop="url">
|
<el-input
|
v-model="sendForm.url"
|
placeholder="请输入跳转链接(可选)"
|
/>
|
</el-form-item>
|
</el-form>
|
<div slot="footer">
|
<el-button @click="sendDialogVisible = false">取消</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="专家历史审批情况"
|
:visible.sync="expertHistoryDialogVisible"
|
width="600px"
|
>
|
<div v-loading="expertHistoryLoading">
|
<div v-if="expertHistoryData" style="padding: 20px;">
|
<el-row :gutter="20" style="margin-bottom: 20px;">
|
<el-col :span="12">
|
<div class="history-stat-item">
|
<div class="history-stat-label">专家姓名</div>
|
<div class="history-stat-value">
|
{{ currentExpertInfo.expertname || "-" }}
|
</div>
|
</div>
|
</el-col>
|
<el-col :span="12">
|
<div class="history-stat-item">
|
<div class="history-stat-label">专家编号</div>
|
<div class="history-stat-value">
|
{{ currentExpertInfo.expertNo || "-" }}
|
</div>
|
</div>
|
</el-col>
|
</el-row>
|
|
<el-divider></el-divider>
|
|
<el-row :gutter="20">
|
<el-col :span="12">
|
<div class="history-stat-item">
|
<div class="history-stat-label">总审批数量</div>
|
<div
|
class="history-stat-value"
|
style="font-size: 24px; color: #409EFF;"
|
>
|
{{ expertHistoryData.count || 0 }}
|
</div>
|
</div>
|
</el-col>
|
<el-col :span="12">
|
<div class="history-stat-item">
|
<div class="history-stat-label">已接收数量</div>
|
<div class="history-stat-value">
|
{{ expertHistoryData.acceptCount || 0 }}
|
</div>
|
</div>
|
</el-col>
|
</el-row>
|
|
<el-row :gutter="20" style="margin-top: 20px;">
|
<el-col :span="12">
|
<div class="history-stat-item">
|
<div class="history-stat-label">未接收数量</div>
|
<div class="history-stat-value">
|
{{ expertHistoryData.notAcceptCount || 0 }}
|
</div>
|
</div>
|
</el-col>
|
<el-col :span="12">
|
<div class="history-stat-item">
|
<div class="history-stat-label">有意见数量</div>
|
<div class="history-stat-value">
|
{{ expertHistoryData.opinionCount || 0 }}
|
</div>
|
</div>
|
</el-col>
|
</el-row>
|
|
<el-row :gutter="20" style="margin-top: 20px;">
|
<el-col :span="12">
|
<div class="history-stat-item">
|
<div class="history-stat-label">无意见数量</div>
|
<div class="history-stat-value">
|
{{ expertHistoryData.notOpinionCount || 0 }}
|
</div>
|
</div>
|
</el-col>
|
<el-col :span="12">
|
<div class="history-stat-item">
|
<div class="history-stat-label">有附件数量</div>
|
<div class="history-stat-value">
|
{{ expertHistoryData.annexCount || 0 }}
|
</div>
|
</div>
|
</el-col>
|
</el-row>
|
|
<el-row :gutter="20" style="margin-top: 20px;">
|
<el-col :span="12">
|
<div class="history-stat-item">
|
<div class="history-stat-label">无附件数量</div>
|
<div class="history-stat-value">
|
{{ expertHistoryData.notAnnexCount || 0 }}
|
</div>
|
</div>
|
</el-col>
|
</el-row>
|
</div>
|
<div v-else style="text-align: center; padding: 40px 0;">
|
<i
|
class="el-icon-info"
|
style="font-size: 60px; color: #C0C4CC; margin-bottom: 20px;"
|
></i>
|
<p style="color: #909399; font-size: 14px;">暂无历史审批数据</p>
|
</div>
|
</div>
|
<div slot="footer">
|
<el-button @click="expertHistoryDialogVisible = false">关闭</el-button>
|
</div>
|
</el-dialog>
|
|
<!-- 文件预览弹窗 -->
|
<FilePreviewDialog
|
:visible="previewVisible"
|
:file="currentPreviewFile"
|
@close="previewVisible = false"
|
@download="handleDownload"
|
/>
|
</div>
|
</template>
|
|
<script>
|
import { getToken } from "@/utils/auth";
|
import {
|
reviewinitiateBaseInfoList,
|
ethicalreviewedit,
|
ethicalreviewadd,
|
ethicalreviewInfo,
|
ethicalreExpertTotal,
|
sendNotification,
|
sendcall
|
} from "@/api/businessApi";
|
import { listExternalperson } from "@/api/project/externalperson";
|
import CaseBasicInfo from "@/components/CaseBasicInfo";
|
import UploadAttachment from "@/components/UploadAttachment";
|
import FilePreviewDialog from "@/components/FilePreviewDialog";
|
import dayjs from "dayjs";
|
|
export default {
|
name: "EthicsReviewDetail",
|
components: { CaseBasicInfo, UploadAttachment, FilePreviewDialog },
|
dicts: ["sys_user_sex", "sys_ethical", "Review_status"],
|
|
data() {
|
return {
|
// 页面模式
|
isEdit: false,
|
// 基本信息
|
infoid: undefined,
|
id: undefined,
|
caseId: null,
|
caseNo: "",
|
|
// 表单数据
|
form: {
|
// 基础信息
|
id: undefined,
|
infoid: undefined,
|
caseNo: "",
|
initiateTheme: "",
|
initiatePerson: "",
|
|
// 状态和时间
|
status: "0", // 0:待审查, 1:审查中, 2:审查中止, 3:审查完成
|
startTime: "",
|
cutOffTime: "",
|
endTime: "",
|
|
// 专家信息
|
expertName: "",
|
expertNo: "",
|
expertType: "0",
|
expertConclusion: "",
|
expertOpinion: "",
|
expertTime: "",
|
orderNo: 1,
|
|
// 备注
|
remark: "",
|
|
// 附件信息
|
annexfilesList: [],
|
filePatch: "",
|
|
// 专家审查意见列表
|
ethicalreviewopinionsList: [],
|
|
// 系统字段
|
createBy: "",
|
createTime: "",
|
updateBy: "",
|
updateTime: "",
|
delFlag: "0"
|
},
|
|
// 表单验证规则
|
rules: {
|
initiateTheme: [
|
{ required: true, message: "发起主题不能为空", trigger: "blur" },
|
{
|
min: 2,
|
max: 100,
|
message: "长度在 2 到 100 个字符",
|
trigger: "blur"
|
}
|
],
|
initiatePerson: [
|
{ required: true, message: "发起人不能为空", trigger: "blur" }
|
],
|
status: [
|
{ required: true, message: "审查状态不能为空", trigger: "change" }
|
],
|
expertName: [
|
{ max: 50, message: "长度不能超过 50 个字符", trigger: "blur" }
|
],
|
expertNo: [
|
{ max: 50, message: "长度不能超过 50 个字符", trigger: "blur" }
|
],
|
expertConclusion: [
|
{ max: 2, message: "长度不能超过 2 个字符", trigger: "change" }
|
],
|
remark: [
|
{ 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,
|
suspendLoading: false,
|
endLoading: false,
|
sending: false,
|
|
// 附件相关
|
attachmentFileList: [],
|
|
// 预览相关
|
previewVisible: false,
|
currentPreviewFile: null,
|
|
// 专家审查数据
|
expertReviews: [],
|
expertLoading: false,
|
attachmentLoading: false,
|
|
// 添加专家对话框
|
expertDialogVisible: false,
|
expertList: [],
|
expertListLoading: false,
|
expertSearchQuery: "",
|
filterExpertType: "",
|
expertPage: {
|
pageNum: 1,
|
pageSize: 50
|
},
|
expertTotal: 0,
|
selectedExperts: [],
|
|
// 发送对话框
|
sendDialogVisible: false,
|
sendForm: {
|
expertType: "normal",
|
expertIds: [],
|
startTime: "",
|
endTime: "",
|
sendType: "0",
|
title: "伦理审查任务通知",
|
content: "",
|
url: ""
|
},
|
|
// 专家历史审批情况
|
expertHistoryDialogVisible: false,
|
expertHistoryLoading: false,
|
expertHistoryData: null,
|
currentExpertInfo: {},
|
|
// 当前发送的专家
|
currentSendExperts: []
|
};
|
},
|
computed: {
|
// 计算属性:获取专家列表(确保响应式)
|
ethicalreviewopinionsList() {
|
return this.form.ethicalreviewopinionsList || [];
|
},
|
|
// 计算属性:普通专家数量
|
normalExpertsCount() {
|
return this.ethicalreviewopinionsList.filter(
|
expert => expert.expertType == "0"
|
).length;
|
},
|
|
// 计算属性:主委专家数量
|
chiefExpertsCount() {
|
return this.ethicalreviewopinionsList.filter(
|
expert => expert.expertType == "1"
|
).length;
|
},
|
|
// 计算属性:总专家数量
|
totalExpertsCount() {
|
return this.ethicalreviewopinionsList.length;
|
},
|
|
// 计算属性:已同意专家数量
|
approvedExpertsCount() {
|
return this.ethicalreviewopinionsList.filter(
|
expert => expert.expertconclusion == "1"
|
).length;
|
},
|
|
// 计算属性:总体结论
|
overallConclusionText() {
|
const total = this.totalExpertsCount;
|
const approved = this.approvedExpertsCount;
|
|
if (total == 0) return "未审查";
|
if (approved >= Math.ceil(total * 0.7)) {
|
// 超过70%同意
|
return "通过";
|
} else if (approved >= Math.ceil(total * 0.5)) {
|
// 超过50%同意
|
return "修改后通过";
|
} else {
|
return "不通过";
|
}
|
},
|
|
overallConclusionFilter() {
|
const total = this.totalExpertsCount;
|
const approved = this.approvedExpertsCount;
|
|
if (total == 0) return "info";
|
if (approved >= Math.ceil(total * 0.7)) {
|
return "success";
|
} else if (approved >= Math.ceil(total * 0.5)) {
|
return "warning";
|
} else {
|
return "danger";
|
}
|
},
|
|
// 可发送的普通专家
|
availableNormalExperts() {
|
return this.ethicalreviewopinionsList.filter(
|
expert =>
|
expert.expertType == "0" &&
|
(!expert.receiveStatus ||
|
expert.receiveStatus == "0" ||
|
expert.receiveStatus == "1")
|
);
|
},
|
|
// 可发送的主委专家
|
availableChiefExperts() {
|
return this.ethicalreviewopinionsList.filter(
|
expert =>
|
expert.expertType == "1" &&
|
(!expert.receiveStatus ||
|
expert.receiveStatus == "0" ||
|
expert.receiveStatus == "1")
|
);
|
},
|
|
// 是否可以发送给普通专家
|
canSendToNormalExperts() {
|
return this.availableNormalExperts.length > 0;
|
},
|
|
// 是否可以发送给主委专家(需要至少12个普通专家同意)
|
canSendToChiefExpert() {
|
const normalApprovedCount = this.ethicalreviewopinionsList.filter(
|
expert => expert.expertType == "0" && expert.expertconclusion == "1"
|
).length;
|
return this.availableChiefExperts.length > 0 && normalApprovedCount >= 12;
|
},
|
|
// 已存在的专家编号列表
|
existingExpertNos() {
|
return this.ethicalreviewopinionsList
|
.map(expert => expert.expertNo)
|
.filter(no => no);
|
},
|
|
// 已存在的专家姓名列表
|
existingExpertNames() {
|
return this.ethicalreviewopinionsList
|
.map(expert => expert.expertname)
|
.filter(name => name);
|
},
|
|
// 过滤后的专家列表(排除已存在的专家)
|
filteredExpertList() {
|
if (!this.expertList.length) return [];
|
|
return this.expertList.filter(expert => {
|
// 如果专家有编号,检查编号是否已存在
|
if (expert.userno && this.existingExpertNos.includes(expert.userno)) {
|
return false;
|
}
|
// 如果专家有姓名,检查姓名是否已存在
|
if (
|
expert.username &&
|
this.existingExpertNames.includes(expert.username)
|
) {
|
return false;
|
}
|
return true;
|
});
|
},
|
|
// 过滤后的专家总数
|
filteredExpertTotal() {
|
return this.filteredExpertList.length;
|
},
|
|
// 当前用户信息
|
currentUser() {
|
return JSON.parse(sessionStorage.getItem("user") || "{}");
|
},
|
|
// 发送对话框标题
|
sendDialogTitle() {
|
if (this.sendForm.expertType == "chief") {
|
return "发送主委专家审查";
|
} else if (this.sendForm.expertType == "normal") {
|
return "发送普通专家审查";
|
} else {
|
return "发送专家审查";
|
}
|
}
|
},
|
watch: {
|
// 监听表单中的专家列表变化
|
"form.ethicalreviewopinionsList": {
|
handler(newVal) {
|
console.log("专家列表变化:", newVal);
|
},
|
deep: true
|
}
|
},
|
created() {
|
this.infoid = this.$route.query.infoid;
|
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: {
|
// 初始化新增数据
|
initNewData() {
|
this.form.infoid = this.infoid;
|
this.form.caseNo = this.$route.query.caseNo || "";
|
this.form.initiatePerson = this.currentUser.username || "当前用户";
|
this.form.startTime = new Date()
|
.toISOString()
|
.replace("T", " ")
|
.substring(0, 19);
|
this.form.createBy = this.currentUser.username || "admin";
|
},
|
|
// 获取详情
|
async getDetail(infoid, id) {
|
try {
|
this.expertLoading = true;
|
let response = {};
|
if (id) {
|
response = await ethicalreviewInfo(id);
|
} else if (infoid) {
|
response = await reviewinitiateBaseInfoList({ infoid: infoid });
|
}
|
|
if (response.code == 200) {
|
let detailData = {};
|
|
if (response.data && id) {
|
this.form = response.data;
|
// 解析 filePatch 字段
|
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];
|
this.parseFilePatch(this.form.filePatch);
|
this.initAttachmentFileList();
|
|
if (!this.form.ethicalreviewopinionsList) {
|
this.$set(this.form, "ethicalreviewopinionsList", []);
|
}
|
}
|
|
// 保存原始数据用于比较
|
this.saveOriginalData();
|
|
this.expertReviews = this.form.ethicalreviewopinionsList;
|
this.isDataLoaded = true;
|
|
this.$message.success("数据加载成功");
|
} else {
|
this.$message.error("获取详情失败:" + (response.msg || "未知错误"));
|
}
|
} catch (error) {
|
console.error("获取伦理审查详情失败:", error);
|
this.$message.error("数据加载失败");
|
} 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 字段
|
parseFilePatch(filePatch) {
|
if (!filePatch) {
|
this.form.annexfilesList = [];
|
return;
|
}
|
|
try {
|
this.form.annexfilesList = JSON.parse(filePatch);
|
} catch (error) {
|
console.error("解析 filePatch 字段失败:", error);
|
this.form.annexfilesList = [];
|
}
|
},
|
|
// 初始化附件文件列表
|
initAttachmentFileList() {
|
if (this.form.annexfilesList && this.form.annexfilesList.length > 0) {
|
this.attachmentFileList = this.form.annexfilesList.map(item => ({
|
uid:
|
item.id ||
|
Math.random()
|
.toString(36)
|
.substr(2, 9),
|
name: item.fileName,
|
url: item.path || item.fileUrl,
|
status: "success"
|
}));
|
} else {
|
this.attachmentFileList = [];
|
}
|
},
|
|
// 构建 filePatch 字段
|
buildFilePatch() {
|
if (!this.form.annexfilesList || this.form.annexfilesList.length == 0) {
|
return "";
|
}
|
return JSON.stringify(this.form.annexfilesList);
|
},
|
|
// 附件变化处理
|
handleAttachmentChange(fileList) {
|
this.attachmentFileList = fileList;
|
},
|
|
// 附件移除处理
|
handleAttachmentRemove(file) {
|
if (file.url) {
|
const index = this.form.annexfilesList.findIndex(
|
item => item.path == file.url || item.fileUrl == file.url
|
);
|
if (index > -1) {
|
this.form.annexfilesList.splice(index, 1);
|
}
|
}
|
},
|
|
// 手动删除附件
|
handleRemoveAttachment(index) {
|
this.form.annexfilesList.splice(index, 1);
|
this.attachmentFileList.splice(index, 1);
|
this.$message.success("附件删除成功");
|
},
|
|
// 上传成功处理
|
handleUploadSuccess({ file, fileList, response }) {
|
if (response.code == 200) {
|
const attachmentObj = {
|
fileName: file.name,
|
path: response.data || file.url,
|
fileUrl: response.data || file.url,
|
type: this.getFileExtension(file.name),
|
createTime: dayjs().format("YYYY-MM-DD HH:mm:ss"),
|
infoid: this.infoid,
|
delFlag: 0
|
};
|
|
this.form.annexfilesList.push(attachmentObj);
|
this.$message.success("文件上传成功");
|
}
|
},
|
|
// 上传错误处理
|
handleUploadError({ file, fileList, error }) {
|
console.error("附件上传失败:", error);
|
this.$message.error("文件上传失败,请重试");
|
},
|
|
// 文件预览
|
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;
|
},
|
|
// 文件下载
|
handleDownload(file) {
|
const fileUrl = file.path || file.fileUrl;
|
const fileName = file.fileName;
|
|
if (fileUrl) {
|
const link = document.createElement("a");
|
link.href = fileUrl;
|
link.download = fileName;
|
link.style.display = "none";
|
document.body.appendChild(link);
|
link.click();
|
document.body.removeChild(link);
|
this.$message.success("开始下载文件");
|
} else {
|
this.$message.warning("文件路径不存在,无法下载");
|
}
|
},
|
|
// 获取文件类型
|
getFileType(fileName) {
|
if (!fileName) return "other";
|
|
const extension = fileName
|
.split(".")
|
.pop()
|
.toLowerCase();
|
const imageTypes = ["jpg", "jpeg", "png", "gif", "bmp", "webp"];
|
const pdfTypes = ["pdf"];
|
const officeTypes = ["doc", "docx", "xls", "xlsx", "ppt", "pptx"];
|
|
if (imageTypes.includes(extension)) return "image";
|
if (pdfTypes.includes(extension)) return "pdf";
|
if (officeTypes.includes(extension)) return "office";
|
return "other";
|
},
|
|
// 获取文件扩展名
|
getFileExtension(filename) {
|
return filename
|
.split(".")
|
.pop()
|
.toLowerCase();
|
},
|
|
// 日期时间格式化
|
formatDateTime(dateTime) {
|
if (!dateTime) return "";
|
|
try {
|
const date = new Date(dateTime);
|
if (isNaN(date.getTime())) return dateTime;
|
|
const year = date.getFullYear();
|
const month = String(date.getMonth() + 1).padStart(2, "0");
|
const day = String(date.getDate()).padStart(2, "0");
|
const hours = String(date.getHours()).padStart(2, "0");
|
const minutes = String(date.getMinutes()).padStart(2, "0");
|
const seconds = String(date.getSeconds()).padStart(2, "0");
|
|
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
} catch (error) {
|
return dateTime;
|
}
|
},
|
|
// 判断是否为主任委员
|
getIsChiefExpert(expert) {
|
// 职称包含"主任委员"或者expertType为"1"
|
return (
|
(expert.title && expert.title.includes("主任委员")) ||
|
expert.expertType == "1"
|
);
|
},
|
|
// 专家类型文本转换
|
getExpertTypeText(type) {
|
return type == "1" ? "主委专家" : "普通专家";
|
},
|
|
// 审查状态过滤器
|
getReviewStatusFilter(status) {
|
const statusMap = {
|
"0": "info", // 待接收
|
"1": "warning", // 未接收
|
"2": "success", // 已接收
|
"3": "danger", // 超时
|
"4": "danger", // 中止
|
"5": "success" // 完成
|
};
|
return statusMap[status] || "info";
|
},
|
|
getReviewStatusText(status) {
|
const statusMap = {
|
"0": "待接收",
|
"1": "未接收",
|
"2": "已接收",
|
"3": "超时",
|
"4": "中止",
|
"5": "完成"
|
};
|
return statusMap[status] || "未知";
|
},
|
|
// 结论过滤器
|
getConclusionFilter(conclusion) {
|
const conclusionMap = {
|
"1": "success", // 同意
|
"2": "warning", // 审查中
|
"0": "danger" // 不同意
|
};
|
return conclusionMap[conclusion] || "info";
|
},
|
|
getConclusionText(conclusion) {
|
const conclusionMap = {
|
"1": "同意",
|
"2": "审查中",
|
"0": "不同意"
|
};
|
return conclusionMap[conclusion] || "未知";
|
},
|
|
// 专家行样式
|
getExpertRowClassName({ row }) {
|
return row.expertType == "1" ? "chief-expert-row" : "normal-expert-row";
|
},
|
|
// 获取专家唯一标识
|
getExpertKey(expert) {
|
return expert.id || expert.expertNo || expert.expertname;
|
},
|
|
// 专家类型变更处理
|
handleExpertTypeChange() {
|
if (this.sendForm.expertType == "chief") {
|
// 主委专家无需设置截止时间
|
this.sendForm.endTime = "";
|
} else {
|
// 普通专家重置截止时间为当前时间
|
this.sendForm.endTime = "";
|
}
|
},
|
|
// 设置截止时间快捷键
|
setEndTime(hours) {
|
const now = new Date();
|
const endTime = new Date(now.getTime() + hours * 60 * 60 * 1000);
|
this.sendForm.endTime = endTime
|
.toISOString()
|
.replace("T", " ")
|
.substring(0, 19);
|
},
|
|
// 保存信息
|
async handleSave() {
|
this.$refs.form.validate(async valid => {
|
if (valid) {
|
this.saveLoading = true;
|
// 保存清空id便于后端整体删除新增
|
this.form.ethicalreviewopinionsList.forEach(item => {
|
item.id = null;
|
});
|
try {
|
const submitData = {
|
...this.form,
|
// 确保必要字段
|
infoid: this.infoid,
|
caseNo: this.caseNo,
|
// 构建 filePatch 字段
|
filePatch: this.buildFilePatch()
|
};
|
|
let response = null;
|
|
if (submitData.id) {
|
response = await ethicalreviewedit(submitData);
|
} else {
|
response = await ethicalreviewadd(submitData);
|
}
|
|
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;
|
this.$router.replace({
|
query: { ...this.$route.query, id: this.form.id }
|
});
|
}
|
} else {
|
this.$message.error("保存失败:" + (response.msg || "未知错误"));
|
}
|
} catch (error) {
|
console.error("保存失败:", error);
|
this.$message.error("保存失败,请重试");
|
} finally {
|
this.saveLoading = false;
|
}
|
}
|
});
|
},
|
|
// 审查完成
|
async handleCompleteReview() {
|
this.$confirm("确定要将本次伦理审查状态设为完成吗?", "提示", {
|
confirmButtonText: "确定",
|
cancelButtonText: "取消",
|
type: "warning"
|
})
|
.then(async () => {
|
this.completeLoading = true;
|
try {
|
const updateData = {
|
...this.form,
|
status: "3", // 审查完成
|
endTime: new Date()
|
.toISOString()
|
.replace("T", " ")
|
.substring(0, 19)
|
};
|
|
const response = await ethicalreviewedit(updateData);
|
|
if (response.code == 200) {
|
this.$message.success("审查状态已更新为完成");
|
this.form.status = "3";
|
this.form.endTime = updateData.endTime;
|
} else {
|
this.$message.error("操作失败:" + (response.msg || "未知错误"));
|
}
|
} catch (error) {
|
console.error("更新审查状态失败:", error);
|
this.$message.error("更新审查状态失败");
|
} finally {
|
this.completeLoading = false;
|
}
|
})
|
.catch(() => {});
|
},
|
|
// 审查中止
|
async handleSuspendReview() {
|
this.$confirm(
|
"确定要中止本次伦理审查吗?所有专家的审查状态将变为中止。",
|
"提示",
|
{
|
confirmButtonText: "确定",
|
cancelButtonText: "取消",
|
type: "warning"
|
}
|
)
|
.then(async () => {
|
this.suspendLoading = true;
|
try {
|
// 更新所有专家的接收状态为中止
|
if (
|
this.form.ethicalreviewopinionsList &&
|
this.form.ethicalreviewopinionsList.length > 0
|
) {
|
this.form.ethicalreviewopinionsList.forEach(expert => {
|
expert.receiveStatus = "4"; // 中止状态
|
expert.updateTime = new Date()
|
.toISOString()
|
.replace("T", " ")
|
.substring(0, 19);
|
});
|
}
|
|
const updateData = {
|
...this.form,
|
status: "2" // 审查中止
|
};
|
|
const response = await ethicalreviewedit(updateData);
|
|
if (response.code == 200) {
|
this.$message.success("审查已中止,所有专家状态已更新");
|
this.form.status = "2";
|
} else {
|
this.$message.error("操作失败:" + (response.msg || "未知错误"));
|
}
|
} catch (error) {
|
console.error("中止审查失败:", error);
|
this.$message.error("中止审查失败");
|
} finally {
|
this.suspendLoading = false;
|
}
|
})
|
.catch(() => {});
|
},
|
|
// 结束审查
|
async handleEndReview() {
|
this.$confirm(
|
"确定要结束本次伦理审查吗?结束后将无法修改专家审查结果。",
|
"提示",
|
{
|
confirmButtonText: "确定",
|
cancelButtonText: "取消",
|
type: "warning"
|
}
|
)
|
.then(async () => {
|
this.endLoading = true;
|
try {
|
const updateData = {
|
...this.form,
|
status: "4", // 审查中止
|
endTime: new Date()
|
.toISOString()
|
.replace("T", " ")
|
.substring(0, 19)
|
};
|
|
const response = await ethicalreviewedit(updateData);
|
|
if (response.code == 200) {
|
this.$message.success("审查已结束");
|
this.form.status = "4";
|
this.form.endTime = updateData.endTime;
|
} else {
|
this.$message.error("操作失败:" + (response.msg || "未知错误"));
|
}
|
} catch (error) {
|
console.error("结束审查失败:", error);
|
this.$message.error("结束审查失败");
|
} finally {
|
this.endLoading = false;
|
}
|
})
|
.catch(() => {});
|
},
|
|
// 打开添加专家对话框
|
handleAddExpert() {
|
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: "ethical", // 伦理专家
|
pageNum: this.expertPage.pageNum,
|
pageSize: this.expertPage.pageSize
|
};
|
|
if (this.expertSearchQuery) {
|
params.username = this.expertSearchQuery;
|
}
|
|
const response = await listExternalperson(params);
|
if (response.code == 200) {
|
this.expertList = response.rows || [];
|
this.expertTotal = response.total || 0;
|
} else {
|
this.$message.error(
|
"加载专家列表失败:" + (response.msg || "未知错误")
|
);
|
}
|
} catch (error) {
|
console.error("加载专家列表失败:", error);
|
this.$message.error("加载专家列表失败");
|
} finally {
|
this.expertListLoading = false;
|
}
|
},
|
|
// 搜索专家
|
handleSearchExperts() {
|
this.expertPage.pageNum = 1;
|
this.loadExperts();
|
},
|
|
// 重置搜索
|
handleResetSearch() {
|
this.expertSearchQuery = "";
|
this.filterExpertType = "";
|
this.expertPage.pageNum = 1;
|
this.loadExperts();
|
},
|
|
// 专家选择变化
|
handleExpertSelectionChange(selection) {
|
this.selectedExperts = selection;
|
},
|
|
// 确认添加专家
|
handleConfirmAddExpert() {
|
if (this.selectedExperts.length == 0) {
|
this.$message.warning("请选择要添加的专家");
|
return;
|
}
|
|
// 确保ethicalreviewopinionsList存在
|
if (!this.form.ethicalreviewopinionsList) {
|
this.$set(this.form, "ethicalreviewopinionsList", []);
|
}
|
|
// 添加专家到列表
|
this.selectedExperts.forEach(expert => {
|
// 判断是否为主任委员
|
const isChief = this.getIsChiefExpert(expert);
|
|
const expertReview = {
|
id: undefined,
|
infoid: this.infoid,
|
nitiateId: this.form.id || undefined,
|
caseNo: this.form.caseNo,
|
expertname: expert.username,
|
expertNo: expert.userno,
|
expertType: isChief ? "1" : "0", // 主任委员设置为主委专家
|
deptName: expert.unitname || "",
|
title: expert.title || "",
|
donorno: expert.telephone || "",
|
receiveStatus: "0", // 待接收
|
expertconclusion: "",
|
expertopinion: "",
|
conclusionannex: "",
|
conclusionorder: this.form.ethicalreviewopinionsList.length + 1,
|
conclusiontime: "",
|
startTime: "",
|
endTime: "",
|
sendType: "",
|
createBy: this.currentUser.username || "admin",
|
createTime: new Date()
|
.toISOString()
|
.replace("T", " ")
|
.substring(0, 19),
|
updateBy: this.currentUser.username || "admin",
|
updateTime: new Date()
|
.toISOString()
|
.replace("T", " ")
|
.substring(0, 19),
|
delFlag: "0"
|
};
|
|
// 使用push添加,确保响应式
|
this.form.ethicalreviewopinionsList.push(expertReview);
|
});
|
|
this.$message.success(`成功添加 ${this.selectedExperts.length} 位专家`);
|
this.expertDialogVisible = false;
|
this.selectedExperts = [];
|
},
|
|
// 添加专家对话框关闭
|
handleExpertDialogClose() {
|
this.selectedExperts = [];
|
this.expertSearchQuery = "";
|
this.filterExpertType = "";
|
this.expertPage.pageNum = 1;
|
},
|
|
// 页码变化
|
handlePageChange(pageNum) {
|
this.expertPage.pageNum = pageNum;
|
this.loadExperts();
|
},
|
|
// 每页条数变化
|
handlePageSizeChange(pageSize) {
|
this.expertPage.pageSize = pageSize;
|
this.expertPage.pageNum = 1;
|
this.loadExperts();
|
},
|
|
// 发送给普通专家
|
handleSendToNormalExperts() {
|
this.currentSendExperts = this.availableNormalExperts;
|
this.sendForm.expertType = "normal";
|
this.sendForm.endTime = ""; // 重置截止时间
|
this.sendDialogVisible = true;
|
},
|
|
// 发送给主委专家
|
handleSendToChiefExpert() {
|
this.currentSendExperts = this.availableChiefExperts;
|
this.sendForm.expertType = "chief";
|
this.sendForm.endTime = ""; // 主委专家无需截止时间
|
this.sendDialogVisible = true;
|
},
|
|
// 发送给单个专家
|
handleSendToExpert(expert) {
|
this.currentSendExperts = [expert];
|
this.sendForm.expertType = expert.expertType == "1" ? "chief" : "normal";
|
this.sendForm.endTime = expert.expertType == "1" ? "" : ""; // 主委专家无需截止时间
|
this.sendDialogVisible = true;
|
},
|
|
// 发送对话框关闭
|
handleSendDialogClose() {
|
this.sendForm = {
|
expertType: "normal",
|
expertIds: [],
|
startTime: "",
|
endTime: "",
|
sendType: "0",
|
title: "伦理审查任务通知",
|
content: "",
|
url: ""
|
};
|
this.currentSendExperts = [];
|
},
|
|
// 确认发送
|
async handleSendConfirm() {
|
if (!this.sendForm.startTime) {
|
this.$message.warning("请选择发送时间");
|
return;
|
}
|
|
// 普通专家需要截止时间,主委专家不需要
|
if (this.sendForm.expertType !== "chief" && !this.sendForm.endTime) {
|
this.$message.warning("请选择截止时间");
|
return;
|
}
|
|
if (!this.sendForm.sendType) {
|
this.$message.warning("请选择发送方式");
|
return;
|
}
|
|
if (!this.sendForm.title) {
|
this.$message.warning("请输入发送标题");
|
return;
|
}
|
|
if (!this.sendForm.content) {
|
this.$message.warning("请输入发送内容");
|
return;
|
}
|
|
if (this.currentSendExperts.length == 0) {
|
this.$message.warning("没有找到可发送的专家");
|
return;
|
}
|
|
// 初始化发送状态
|
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 {
|
// 使用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 result = await this.sendSingleExpert(expert, i);
|
this.sendingResults.push(result);
|
|
if (result.success) {
|
this.sendingSuccessCount++;
|
|
// 更新专家状态
|
const index = this.form.ethicalreviewopinionsList.findIndex(
|
e =>
|
e.expertNo == expert.expertNo ||
|
e.expertname == expert.expertname
|
);
|
|
if (index != -1) {
|
this.form.ethicalreviewopinionsList[index].receiveStatus = "1"; // 已接收
|
this.form.ethicalreviewopinionsList[
|
index
|
].startTime = this.sendForm.startTime;
|
this.form.ethicalreviewopinionsList[
|
index
|
].endTime = this.sendForm.endTime;
|
this.form.ethicalreviewopinionsList[
|
index
|
].sendType = this.sendForm.sendType;
|
this.form.ethicalreviewopinionsList[
|
index
|
].updateTime = new Date()
|
.toISOString()
|
.replace("T", " ")
|
.substring(0, 19);
|
|
// 使用Vue.set确保响应式更新
|
this.$set(
|
this.form.ethicalreviewopinionsList,
|
index,
|
this.form.ethicalreviewopinionsList[index]
|
);
|
}
|
} else {
|
this.sendingFailCount++;
|
}
|
} catch (error) {
|
console.error(`发送给专家 ${expert.expertname} 失败:`, error);
|
this.sendingResults.push({
|
success: false,
|
expert: expert.expertname,
|
error: error.message
|
});
|
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",
|
expertIds: [],
|
startTime: "",
|
endTime: "",
|
sendType: "0",
|
title: "伦理审查任务通知",
|
content: "",
|
url: ""
|
};
|
this.currentSendExperts = [];
|
// 保存整个单据
|
this.handleSave();
|
} catch (error) {
|
console.error("发送过程中发生错误:", error);
|
progressDialog.close();
|
this.$message.error("发送过程中发生错误,请重试");
|
} finally {
|
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("确定要删除该专家的审查记录吗?", "提示", {
|
confirmButtonText: "确定",
|
cancelButtonText: "取消",
|
type: "warning"
|
})
|
.then(() => {
|
// 从数组中删除专家
|
this.form.ethicalreviewopinionsList.splice(index, 1);
|
this.$message.success("专家审查记录已删除");
|
})
|
.catch(() => {});
|
},
|
|
// 查看专家历史审批情况
|
async handleViewExpertHistory(expert) {
|
if (!expert.expertNo) {
|
this.$message.warning("该专家没有编号,无法查询历史审批情况");
|
return;
|
}
|
|
this.currentExpertInfo = expert;
|
this.expertHistoryLoading = true;
|
this.expertHistoryDialogVisible = true;
|
|
try {
|
const params = {
|
expertNo: expert.expertNo
|
};
|
|
const response = await ethicalreExpertTotal(params);
|
|
if (response && response.code == 200) {
|
this.expertHistoryData = response.data || response[0] || null;
|
} else {
|
this.$message.error(
|
"查询专家历史审批情况失败:" + (response?.msg || "未知错误")
|
);
|
this.expertHistoryData = null;
|
}
|
} catch (error) {
|
console.error("查询专家历史审批情况失败:", error);
|
this.$message.error("查询专家历史审批情况失败");
|
this.expertHistoryData = null;
|
} finally {
|
this.expertHistoryLoading = false;
|
}
|
},
|
|
// 时间格式化
|
parseTime(time) {
|
if (!time) return "";
|
const date = new Date(time);
|
if (isNaN(date.getTime())) return time;
|
|
return `${date.getFullYear()}-${(date.getMonth() + 1)
|
.toString()
|
.padStart(2, "0")}-${date
|
.getDate()
|
.toString()
|
.padStart(2, "0")} ${date
|
.getHours()
|
.toString()
|
.padStart(2, "0")}:${date
|
.getMinutes()
|
.toString()
|
.padStart(2, "0")}`;
|
}
|
}
|
};
|
</script>
|
|
<style scoped>
|
.ethics-review-detail {
|
padding: 20px;
|
background-color: #f5f7fa;
|
}
|
|
.detail-card {
|
margin-bottom: 20px;
|
border-radius: 8px;
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
}
|
|
.expert-card {
|
margin-bottom: 20px;
|
border-radius: 8px;
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
}
|
|
.attachment-card {
|
margin-bottom: 20px;
|
border-radius: 8px;
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
}
|
|
.detail-title {
|
font-size: 18px;
|
font-weight: 600;
|
color: #303133;
|
}
|
|
.expert-stats {
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
color: rgb(43, 181, 245);
|
border-radius: 8px;
|
margin-bottom: 20px;
|
}
|
|
.stat-item {
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
padding: 10px;
|
}
|
|
.stat-label {
|
font-size: 12px;
|
opacity: 0.9;
|
margin-bottom: 5px;
|
}
|
|
.stat-value {
|
font-size: 18px;
|
font-weight: bold;
|
}
|
|
.file-info {
|
display: flex;
|
align-items: center;
|
}
|
|
.empty-attachment {
|
text-align: center;
|
padding: 40px 0;
|
color: #909399;
|
}
|
|
/* 专家类型样式 */
|
.normal-expert {
|
color: #409eff;
|
font-weight: 500;
|
}
|
|
.chief-expert {
|
color: #f56c6c;
|
font-weight: 600;
|
}
|
|
/* 专家行样式 */
|
:deep(.normal-expert-row) {
|
background-color: #fafafa;
|
}
|
|
:deep(.chief-expert-row) {
|
background-color: #fff7e6;
|
}
|
|
:deep(.normal-expert-row:hover) {
|
background-color: #f0f7ff;
|
}
|
|
:deep(.chief-expert-row:hover) {
|
background-color: #ffecc2;
|
}
|
|
/* 无数据样式 */
|
.no-data {
|
color: #909399;
|
font-style: italic;
|
}
|
|
/* 专家意见样式 */
|
.expert-opinion {
|
color: #303133;
|
line-height: 1.5;
|
}
|
|
/* 已发送按钮样式 */
|
.sent-button {
|
color: #67c23a !important;
|
}
|
|
/* 专家姓名链接样式 */
|
.expert-name-link {
|
color: #409eff;
|
cursor: pointer;
|
text-decoration: none;
|
transition: color 0.3s;
|
}
|
|
.expert-name-link:hover {
|
color: #66b1ff;
|
text-decoration: underline;
|
}
|
|
/* 历史统计样式 */
|
.history-stat-item {
|
padding: 10px;
|
border-radius: 4px;
|
background-color: #f5f7fa;
|
text-align: center;
|
}
|
|
.history-stat-label {
|
font-size: 12px;
|
color: #909399;
|
margin-bottom: 5px;
|
}
|
|
.history-stat-value {
|
font-size: 18px;
|
font-weight: bold;
|
color: #303133;
|
}
|
|
.form-section {
|
margin-bottom: 16px;
|
}
|
|
.section-header {
|
display: flex;
|
align-items: center;
|
font-weight: bold;
|
color: #303133;
|
}
|
|
.dialog-footer {
|
text-align: right;
|
padding: 20px 0 0;
|
}
|
|
.attachment-section {
|
margin-bottom: 16px;
|
}
|
|
.attachment-header {
|
display: flex;
|
align-items: center;
|
margin-bottom: 16px;
|
padding: 8px 0;
|
border-bottom: 1px solid #ebeef5;
|
}
|
|
.attachment-title {
|
font-weight: bold;
|
margin: 0 8px;
|
}
|
|
.attachment-tip {
|
font-size: 12px;
|
color: #909399;
|
}
|
|
.attachment-list {
|
margin-top: 16px;
|
}
|
|
.list-title {
|
font-weight: bold;
|
margin-bottom: 12px;
|
color: #303133;
|
}
|
|
.file-name {
|
font-size: 13px;
|
}
|
|
/* 案例信息展示样式 */
|
.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 {
|
padding: 10px;
|
}
|
|
.expert-stats .el-col {
|
margin-bottom: 10px;
|
}
|
}
|
</style>
|