<template>
|
<div class="ethics-review-detail">
|
<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="warning"
|
@click="handleEndReview"
|
:disabled="form.ethicsConclusion === 'terminated'"
|
>
|
结束审查
|
</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="hospitalNo">
|
<el-input v-model="form.hospitalNo" readonly />
|
</el-form-item>
|
</el-col>
|
<el-col :span="8">
|
<el-form-item label="捐献者姓名" prop="donorName">
|
<el-input v-model="form.donorName" />
|
</el-form-item>
|
</el-col>
|
<el-col :span="8">
|
<el-form-item label="性别" prop="gender">
|
<el-select v-model="form.gender" style="width: 100%">
|
<el-option label="男" value="0" />
|
<el-option label="女" value="1" />
|
</el-select>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
|
<el-row :gutter="20">
|
<el-col :span="8">
|
<el-form-item label="年龄" prop="age">
|
<el-input v-model="form.age" />
|
</el-form-item>
|
</el-col>
|
<el-col :span="16">
|
<el-form-item label="疾病诊断" prop="diagnosis">
|
<el-input v-model="form.diagnosis" />
|
</el-form-item>
|
</el-col>
|
</el-row>
|
|
<el-row :gutter="20">
|
<el-col :span="8">
|
<el-form-item label="伦理结论" prop="ethicsConclusion">
|
<el-select v-model="form.ethicsConclusion" style="width: 100%">
|
<el-option label="审查中" value="reviewing" />
|
<el-option label="同意" value="approved" />
|
<el-option
|
label="修改后同意"
|
value="approved_with_modifications"
|
/>
|
<el-option label="修改后重审" value="re-review" />
|
<el-option label="不同意" value="disapproved" />
|
<el-option label="终止审查" value="terminated" />
|
</el-select>
|
</el-form-item>
|
</el-col>
|
<el-col :span="8">
|
<el-form-item label="审查时间" prop="reviewTime">
|
<el-date-picker
|
v-model="form.reviewTime"
|
type="datetime"
|
value-format="yyyy-MM-dd HH:mm:ss"
|
style="width: 100%"
|
/>
|
</el-form-item>
|
</el-col>
|
<el-col :span="8">
|
<el-form-item label="登记人" prop="registrant">
|
<el-input v-model="form.registrant" />
|
</el-form-item>
|
</el-col>
|
</el-row>
|
|
<el-row :gutter="20">
|
<el-col :span="24">
|
<el-form-item label="伦理意见" prop="ethicsOpinion">
|
<el-input
|
type="textarea"
|
:rows="3"
|
v-model="form.ethicsOpinion"
|
placeholder="请输入伦理审查意见"
|
/>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
|
<el-form-item label="登记时间" prop="registrationTime">
|
<el-date-picker
|
v-model="form.registrationTime"
|
type="datetime"
|
value-format="yyyy-MM-dd HH:mm:ss"
|
style="width: 100%"
|
/>
|
</el-form-item>
|
</el-form>
|
</el-card>
|
<!-- 附件上传 -->
|
<el-card class="attachment-card">
|
<div slot="header" class="clearfix">
|
<span class="detail-title">相关附件</span>
|
<el-button type="primary" size="mini" @click="handleUploadAttachment">
|
上传附件
|
</el-button>
|
</div>
|
|
<el-table :data="attachments" style="width: 100%">
|
<el-table-column label="文件名称" min-width="200">
|
<template slot-scope="scope">
|
<div class="file-info">
|
<i
|
class="el-icon-document"
|
style="margin-right: 8px; color: #409EFF;"
|
></i>
|
<span>{{ scope.row.fileName }}</span>
|
</div>
|
</template>
|
</el-table-column>
|
|
<el-table-column label="文件类型" width="100" align="center">
|
<template slot-scope="scope">
|
<el-tag size="small">{{ getFileType(scope.row.fileName) }}</el-tag>
|
</template>
|
</el-table-column>
|
|
<el-table-column label="文件大小" width="100" align="center">
|
<template slot-scope="scope">
|
<span>{{ formatFileSize(scope.row.fileSize) }}</span>
|
</template>
|
</el-table-column>
|
|
<el-table-column label="上传时间" width="160" align="center">
|
<template slot-scope="scope">
|
<span>{{ parseTime(scope.row.uploadTime) }}</span>
|
</template>
|
</el-table-column>
|
|
<el-table-column label="上传人" width="100" align="center">
|
<template slot-scope="scope">
|
<span>{{ scope.row.uploader }}</span>
|
</template>
|
</el-table-column>
|
|
<el-table-column label="操作" width="120" align="center">
|
<template slot-scope="scope">
|
<el-button
|
size="mini"
|
type="text"
|
icon="el-icon-view"
|
@click="handlePreviewAttachment(scope.row)"
|
>预览</el-button
|
>
|
<el-button
|
size="mini"
|
type="text"
|
icon="el-icon-download"
|
@click="handleDownloadAttachment(scope.row)"
|
>下载</el-button
|
>
|
</template>
|
</el-table-column>
|
</el-table>
|
</el-card>
|
<!-- 专家审查情况 -->
|
<el-card class="expert-card">
|
<div slot="header" class="clearfix">
|
<span class="detail-title"
|
>专家审查情况 (18位专家 + 1位主委专家)</span
|
>
|
<div style="float: right;">
|
<el-button
|
size="mini"
|
type="primary"
|
@click="handleSendToNormalExperts"
|
:disabled="!canSendToNormalExperts"
|
>
|
发送专家
|
</el-button>
|
<el-button
|
size="mini"
|
type="success"
|
@click="handleSendToChiefExpert"
|
:disabled="!canSendToChiefExpert"
|
>
|
发送主委专家
|
</el-button>
|
<el-button
|
size="mini"
|
type="warning"
|
@click="handleBatchSend"
|
:disabled="!canBatchSend"
|
>
|
批量发送
|
</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">{{ approvedNormalExperts }}/18</span>
|
</div>
|
</el-col>
|
<el-col :span="6">
|
<div class="stat-item">
|
<span class="stat-label">主委专家状态:</span>
|
<span class="stat-value">{{ chiefExpertStatus }}</span>
|
</div>
|
</el-col>
|
<el-col :span="6">
|
<div class="stat-item">
|
<span class="stat-label">总完成进度:</span>
|
<span class="stat-value">{{ completionRate }}%</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="expertReviews"
|
v-loading="expertLoading"
|
style="width: 100%"
|
heiht="300"
|
: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>{{ scope.row.expertName }}</span>
|
<el-tag
|
v-if="scope.row.isChief"
|
size="mini"
|
type="danger"
|
style="margin-left: 5px;"
|
>主委</el-tag
|
>
|
</template>
|
</el-table-column>
|
|
<el-table-column label="专家类型" width="100" align="center">
|
<template slot-scope="scope">
|
<span :class="scope.row.isChief ? 'chief-expert' : 'normal-expert'">
|
{{ scope.row.isChief ? "主委专家" : "专家" }}
|
</span>
|
</template>
|
</el-table-column>
|
|
<el-table-column label="审查状态" width="100" align="center">
|
<template slot-scope="scope">
|
<el-tag :type="statusFilter(scope.row.reviewStatus)" size="small">
|
{{ statusTextFilter(scope.row.reviewStatus) }}
|
</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="conclusionFilter(scope.row.expertConclusion)"
|
size="small"
|
>
|
{{ conclusionTextFilter(scope.row.expertConclusion) }}
|
</el-tag>
|
<span v-else class="no-data">-</span>
|
</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="160" align="center">
|
<template slot-scope="scope">
|
<span>{{
|
scope.row.reviewTime ? parseTime(scope.row.reviewTime) : "未审查"
|
}}</span>
|
</template>
|
</el-table-column>
|
<el-table-column label="发送时间" width="160" align="center">
|
<template slot-scope="scope">
|
<span>{{
|
scope.row.reviewTime ? parseTime(scope.row.reviewTime) : "未发送"
|
}}</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.reviewStatus === 'submitted'"
|
:class="{ 'sent-button': scope.row.reviewStatus === 'submitted' }"
|
>
|
{{ scope.row.reviewStatus === "submitted" ? "已发送" : "发送" }}
|
</el-button>
|
<el-button
|
size="mini"
|
type="text"
|
icon="el-icon-edit"
|
@click="handleEditExpertReview(scope.row)"
|
:disabled="scope.row.reviewStatus !== 'submitted'"
|
>
|
编辑
|
</el-button>
|
<el-button
|
size="mini"
|
type="text"
|
icon="el-icon-view"
|
@click="handleViewExpertReview(scope.row)"
|
>
|
详情
|
</el-button>
|
</template>
|
</el-table-column>
|
</el-table>
|
|
|
</el-card>
|
|
<!-- 发送专家对话框 -->
|
<el-dialog
|
title="发送专家审查"
|
:visible.sync="sendDialogVisible"
|
width="500px"
|
>
|
<el-form :model="sendForm" ref="sendForm" label-width="100px">
|
<el-form-item label="专家类型" prop="expertType">
|
<el-radio-group v-model="sendForm.expertType">
|
<el-radio label="normal">专家</el-radio>
|
<el-radio label="chief">主委专家</el-radio>
|
</el-radio-group>
|
</el-form-item>
|
<el-form-item
|
label="选择专家"
|
prop="expertIds"
|
v-if="sendForm.expertType === 'normal'"
|
>
|
<el-select
|
v-model="sendForm.expertIds"
|
multiple
|
placeholder="请选择专家"
|
style="width: 100%"
|
>
|
<el-option
|
v-for="expert in availableExperts"
|
:key="expert.id"
|
:label="expert.name"
|
:value="expert.id"
|
/>
|
</el-select>
|
</el-form-item>
|
<el-form-item label="发送内容" prop="content">
|
<el-input
|
type="textarea"
|
:rows="4"
|
v-model="sendForm.content"
|
placeholder="请输入发送给专家的审查内容说明"
|
/>
|
</el-form-item>
|
</el-form>
|
<div slot="footer">
|
<el-button @click="sendDialogVisible = false">取消</el-button>
|
<el-button type="primary" @click="handleSendConfirm"
|
>确认发送</el-button
|
>
|
</div>
|
</el-dialog>
|
</div>
|
</template>
|
<script>
|
import {
|
getEthicsReviewDetail,
|
updateEthicsReview,
|
sendExpertReview,
|
endEthicsReview,
|
uploadAttachment,
|
deleteAttachment,
|
getAttachments
|
} from "./ethicsReview";
|
|
export default {
|
name: "EthicsReviewDetail",
|
data() {
|
return {
|
// 表单数据
|
form: {
|
id: undefined,
|
hospitalNo: "",
|
donorName: "",
|
gender: "",
|
age: "",
|
diagnosis: "",
|
ethicsConclusion: "reviewing",
|
ethicsOpinion: "",
|
reviewTime: "",
|
registrant: "",
|
registrationTime: new Date()
|
.toISOString()
|
.replace("T", " ")
|
.substring(0, 19)
|
},
|
// 表单验证规则
|
rules: {
|
donorName: [
|
{ required: true, message: "捐献者姓名不能为空", trigger: "blur" }
|
],
|
ethicsConclusion: [
|
{ required: true, message: "伦理结论不能为空", trigger: "change" }
|
],
|
reviewTime: [
|
{ required: true, message: "审查时间不能为空", trigger: "change" }
|
]
|
},
|
// 保存加载状态
|
saveLoading: false,
|
|
// 附件数据
|
attachments: [],
|
expertReviews: [
|
// 专家(18位)- 初始状态为申请中
|
{
|
id: 1,
|
expertName: "陶昊",
|
isChief: false,
|
reviewStatus: "applying",
|
expertConclusion: "",
|
expertOpinion: "",
|
reviewTime: ""
|
},
|
{
|
id: 2,
|
expertName: "刘斌",
|
isChief: false,
|
reviewStatus: "applying",
|
expertConclusion: "",
|
expertOpinion: "",
|
reviewTime: ""
|
},
|
{
|
id: 3,
|
expertName: "于海初 ",
|
isChief: false,
|
reviewStatus: "applying",
|
expertConclusion: "",
|
expertOpinion: "",
|
reviewTime: ""
|
},
|
{
|
id: 4,
|
expertName: "王红梅",
|
isChief: false,
|
reviewStatus: "applying",
|
expertConclusion: "",
|
expertOpinion: "",
|
reviewTime: ""
|
},
|
{
|
id: 5,
|
expertName: "王春光",
|
isChief: false,
|
reviewStatus: "applying",
|
expertConclusion: "",
|
expertOpinion: "",
|
reviewTime: ""
|
},
|
{
|
id: 6,
|
expertName: "王静",
|
isChief: false,
|
reviewStatus: "applying",
|
expertConclusion: "",
|
expertOpinion: "",
|
reviewTime: ""
|
},
|
{
|
id: 7,
|
expertName: "边文超",
|
isChief: false,
|
reviewStatus: "applying",
|
expertConclusion: "",
|
expertOpinion: "",
|
reviewTime: ""
|
},
|
{
|
id: 8,
|
expertName: "闫志勇",
|
isChief: false,
|
reviewStatus: "applying",
|
expertConclusion: "",
|
expertOpinion: "",
|
reviewTime: ""
|
},
|
{
|
id: 9,
|
expertName: "许凤",
|
isChief: false,
|
reviewStatus: "applying",
|
expertConclusion: "",
|
expertOpinion: "",
|
reviewTime: ""
|
},
|
{
|
id: 10,
|
expertName: "许传屾",
|
isChief: false,
|
reviewStatus: "applying",
|
expertConclusion: "",
|
expertOpinion: "",
|
reviewTime: ""
|
},
|
{
|
id: 11,
|
expertName: "张红岩",
|
isChief: false,
|
reviewStatus: "applying",
|
expertConclusion: "",
|
expertOpinion: "",
|
reviewTime: ""
|
},
|
{
|
id: 12,
|
expertName: "杨苏民",
|
isChief: false,
|
reviewStatus: "applying",
|
expertConclusion: "",
|
expertOpinion: "",
|
reviewTime: ""
|
},
|
{
|
id: 13,
|
expertName: "宋玉强",
|
isChief: false,
|
reviewStatus: "applying",
|
expertConclusion: "",
|
expertOpinion: "",
|
reviewTime: ""
|
},
|
{
|
id: 14,
|
expertName: "周传利",
|
isChief: false,
|
reviewStatus: "applying",
|
expertConclusion: "",
|
expertOpinion: "",
|
reviewTime: ""
|
},
|
{
|
id: 15,
|
expertName: "荆凡波",
|
isChief: false,
|
reviewStatus: "applying",
|
expertConclusion: "",
|
expertOpinion: "",
|
reviewTime: ""
|
},
|
{
|
id: 16,
|
expertName: "矫文捷",
|
isChief: false,
|
reviewStatus: "applying",
|
expertConclusion: "",
|
expertOpinion: "",
|
reviewTime: ""
|
},
|
{
|
id: 17,
|
expertName: "董震",
|
isChief: false,
|
reviewStatus: "applying",
|
expertConclusion: "",
|
expertOpinion: "",
|
reviewTime: ""
|
},
|
{
|
id: 18,
|
expertName: "蔡金贞",
|
isChief: false,
|
reviewStatus: "applying",
|
expertConclusion: "",
|
expertOpinion: "",
|
reviewTime: ""
|
},
|
// 主委专家(1位)
|
{
|
id: 19,
|
expertName: "孔心涓",
|
isChief: true,
|
reviewStatus: "applying",
|
expertConclusion: "",
|
expertOpinion: "",
|
reviewTime: ""
|
}
|
],
|
expertLoading: false,
|
attachmentLoading: false,
|
// 发送对话框
|
sendDialogVisible: false,
|
sendForm: {
|
expertType: "normal",
|
expertIds: [],
|
content: ""
|
},
|
// 上传相关
|
uploadDialogVisible: false,
|
uploadLoading: false,
|
tempFileList: [],
|
// 可用专家列表
|
availableExperts: [
|
{ id: 1, name: "张教授", type: "normal" },
|
{ id: 2, name: "李教授", type: "normal" },
|
{ id: 3, name: "王教授", type: "normal" },
|
{ id: 4, name: "赵主委", type: "chief" }
|
]
|
};
|
},
|
computed: {
|
// 计算属性:专家同意数量
|
approvedNormalExperts() {
|
return this.expertReviews.filter(
|
expert => !expert.isChief && expert.expertConclusion === "approved"
|
).length;
|
},
|
// 计算属性:主委专家状态
|
chiefExpertStatus() {
|
const chiefExpert = this.expertReviews.find(expert => expert.isChief);
|
return chiefExpert
|
? this.statusTextFilter(chiefExpert.reviewStatus)
|
: "未分配";
|
},
|
// 计算属性:完成进度
|
completionRate() {
|
const totalExperts = this.expertReviews.length;
|
const completedExperts = this.expertReviews.filter(
|
expert => expert.reviewStatus === "submitted"
|
).length;
|
return totalExperts > 0
|
? Math.round((completedExperts / totalExperts) * 100)
|
: 0;
|
},
|
// 计算属性:总体结论
|
overallConclusionText() {
|
if (this.approvedNormalExperts >= 12) {
|
return "通过";
|
} else if (this.approvedNormalExperts >= 9) {
|
return "修改后通过";
|
} else {
|
return "不通过";
|
}
|
},
|
overallConclusionFilter() {
|
if (this.approvedNormalExperts >= 12) {
|
return "success";
|
} else if (this.approvedNormalExperts >= 9) {
|
return "warning";
|
} else {
|
return "danger";
|
}
|
},
|
// 是否可以发送给专家
|
canSendToNormalExperts() {
|
return (
|
this.expertReviews.filter(
|
expert => !expert.isChief && expert.reviewStatus === "applying"
|
).length > 0
|
);
|
},
|
// 是否可以发送给主委专家(需要至少12个专家同意)
|
canSendToChiefExpert() {
|
return (
|
this.approvedNormalExperts >= 12 &&
|
this.expertReviews.filter(
|
expert => expert.isChief && expert.reviewStatus === "applying"
|
).length > 0
|
);
|
},
|
// 是否可以批量发送
|
canBatchSend() {
|
return (
|
this.expertReviews.filter(expert => expert.reviewStatus === "applying")
|
.length > 0
|
);
|
},
|
// 是否可以发送专家审查
|
canSendToExperts() {
|
return this.form.id && this.form.ethicsConclusion === "reviewing";
|
},
|
// 当前用户信息
|
currentUser() {
|
return JSON.parse(sessionStorage.getItem("user") || "{}");
|
}
|
},
|
created() {
|
const id = this.$route.query.id;
|
if (id) {
|
this.getDetail(id);
|
this.getAttachments(id);
|
// 不再需要从接口获取专家列表,使用固定的expertReviews数据
|
} else if (this.$route.path.includes("/add")) {
|
this.generateHospitalNo();
|
this.form.registrant = this.currentUser.username || "当前用户";
|
}
|
},
|
methods: {
|
// 生成住院号
|
generateHospitalNo() {
|
const timestamp = Date.now().toString();
|
this.form.hospitalNo = "D" + timestamp.slice(-6);
|
},
|
getExpertRowClassName({ row }) {
|
return row.isChief ? "chief-expert-row" : "normal-expert-row";
|
},
|
// 获取详情
|
getDetail(id) {
|
getEthicsReviewDetail(id)
|
.then(response => {
|
if (response.code === 200) {
|
this.form = response.data;
|
}
|
})
|
.catch(error => {
|
console.error("获取伦理审查详情失败:", error);
|
this.$message.error("获取详情失败");
|
});
|
},
|
|
// 获取专家审查列表
|
getExpertReviews(ethicsReviewId) {
|
this.expertLoading = true;
|
// 模拟数据 - 实际项目中从接口获取
|
setTimeout(() => {
|
this.expertReviews = [
|
// 专家(18位)
|
{
|
id: 1,
|
expertName: "张教授",
|
isChief: false,
|
reviewStatus: "submitted",
|
expertConclusion: "approved",
|
expertOpinion: "符合伦理要求",
|
reviewTime: "2025-12-01 10:30:00"
|
},
|
{
|
id: 2,
|
expertName: "李教授",
|
isChief: false,
|
reviewStatus: "submitted",
|
expertConclusion: "approved",
|
expertOpinion: "方案设计合理",
|
reviewTime: "2025-12-01 11:20:00"
|
},
|
{
|
id: 3,
|
expertName: "王教授",
|
isChief: false,
|
reviewStatus: "applying",
|
expertConclusion: "",
|
expertOpinion: "",
|
reviewTime: ""
|
},
|
// 主委专家(1位)
|
{
|
id: 19,
|
expertName: "赵主委",
|
isChief: true,
|
reviewStatus: "applying",
|
expertConclusion: "",
|
expertOpinion: "",
|
reviewTime: ""
|
}
|
];
|
this.expertLoading = false;
|
}, 500);
|
},
|
|
// 获取附件列表
|
getAttachments(ethicsReviewId) {
|
this.attachmentLoading = true;
|
getAttachments(ethicsReviewId)
|
.then(response => {
|
if (response.code === 200) {
|
this.attachments = response.data;
|
}
|
this.attachmentLoading = false;
|
})
|
.catch(error => {
|
console.error("获取附件列表失败:", error);
|
this.attachmentLoading = false;
|
});
|
},
|
|
// 状态过滤器
|
statusFilter(status) {
|
const statusMap = {
|
applying: "info",
|
submitted: "success"
|
};
|
return statusMap[status] || "info";
|
},
|
|
statusTextFilter(status) {
|
const statusMap = {
|
applying: "申请中",
|
submitted: "已提交"
|
};
|
return statusMap[status] || "未知";
|
},
|
|
// 结论过滤器
|
conclusionFilter(conclusion) {
|
const conclusionMap = {
|
approved: "success",
|
approved_with_modifications: "warning",
|
disapproved: "danger"
|
};
|
return conclusionMap[conclusion] || "info";
|
},
|
|
conclusionTextFilter(conclusion) {
|
const conclusionMap = {
|
approved: "同意",
|
approved_with_modifications: "修改后同意",
|
disapproved: "不同意"
|
};
|
return conclusionMap[conclusion] || "未知";
|
},
|
|
// 保存信息
|
handleSave() {
|
this.$refs.form.validate(valid => {
|
if (valid) {
|
this.saveLoading = true;
|
const apiMethod = this.form.id ? updateEthicsReview : addEthicsReview;
|
|
apiMethod(this.form)
|
.then(response => {
|
if (response.code === 200) {
|
this.$message.success("保存成功");
|
if (!this.form.id) {
|
this.form.id = response.data.id;
|
this.$router.replace({
|
query: { ...this.$route.query, id: this.form.id }
|
});
|
}
|
}
|
})
|
.catch(error => {
|
console.error("保存失败:", error);
|
this.$message.error("保存失败");
|
})
|
.finally(() => {
|
this.saveLoading = false;
|
});
|
}
|
});
|
},
|
|
// 发送专家审查
|
handleSendToExperts() {
|
this.sendDialogVisible = true;
|
},
|
|
// 发送给专家
|
handleSendToNormalExperts() {
|
const normalExperts = this.expertReviews.filter(
|
expert => !expert.isChief && expert.reviewStatus === "applying"
|
);
|
this.sendForm.expertIds = normalExperts.map(expert => expert.id);
|
this.sendForm.expertType = "normal";
|
this.sendDialogVisible = true;
|
},
|
|
// 发送给主委专家
|
handleSendToChiefExpert() {
|
const chiefExpert = this.expertReviews.find(
|
expert => expert.isChief && expert.reviewStatus === "applying"
|
);
|
if (chiefExpert) {
|
this.sendForm.expertIds = [chiefExpert.id];
|
this.sendForm.expertType = "chief";
|
this.sendDialogVisible = true;
|
}
|
},
|
|
// 批量发送
|
handleBatchSend() {
|
const applyingExperts = this.expertReviews.filter(
|
expert => expert.reviewStatus === "applying"
|
);
|
this.sendForm.expertIds = applyingExperts.map(expert => expert.id);
|
this.sendForm.expertType = "batch";
|
this.sendDialogVisible = true;
|
},
|
|
// 发送给单个专家
|
handleSendToExpert(expert) {
|
this.sendForm.expertIds = [expert.id];
|
this.sendForm.expertType = expert.isChief ? "chief" : "normal";
|
this.sendDialogVisible = true;
|
},
|
|
// 确认发送
|
handleSendConfirm() {
|
if (this.sendForm.expertIds.length === 0) {
|
this.$message.warning("请选择要发送的专家");
|
return;
|
}
|
|
sendExpertReview({
|
ethicsReviewId: this.form.id,
|
expertIds: this.sendForm.expertIds,
|
content: this.sendForm.content
|
})
|
.then(response => {
|
if (response.code === 200) {
|
this.$message.success("发送成功");
|
this.sendDialogVisible = false;
|
this.getExpertReviews(this.form.id);
|
this.sendForm = {
|
expertType: "normal",
|
expertIds: [],
|
content: ""
|
};
|
}
|
})
|
.catch(error => {
|
console.error("发送失败:", error);
|
this.$message.error("发送失败");
|
});
|
},
|
|
// 结束审查
|
handleEndReview() {
|
this.$confirm(
|
"确定要结束本次伦理审查吗?结束后将无法修改专家审查结果。",
|
"提示",
|
{
|
confirmButtonText: "确定",
|
cancelButtonText: "取消",
|
type: "warning"
|
}
|
)
|
.then(() => {
|
endEthicsReview(this.form.id)
|
.then(response => {
|
if (response.code === 200) {
|
this.$message.success("审查已结束");
|
this.form.ethicsConclusion = "terminated";
|
}
|
})
|
.catch(error => {
|
console.error("结束审查失败:", error);
|
this.$message.error("结束审查失败");
|
});
|
})
|
.catch(() => {});
|
},
|
|
// 编辑专家审查
|
handleEditExpertReview(expert) {
|
this.$prompt("请输入审查意见", "编辑专家审查", {
|
confirmButtonText: "确定",
|
cancelButtonText: "取消",
|
inputValue: expert.expertOpinion || "",
|
inputValidator: value => {
|
if (!value || value.trim() === "") {
|
return "审查意见不能为空";
|
}
|
return true;
|
}
|
})
|
.then(({ value }) => {
|
// 模拟更新专家审查
|
const index = this.expertReviews.findIndex(e => e.id === expert.id);
|
if (index !== -1) {
|
this.expertReviews[index].expertOpinion = value;
|
this.$message.success("审查意见已更新");
|
}
|
})
|
.catch(() => {});
|
},
|
|
// 查看专家审查详情
|
handleViewExpertReview(expert) {
|
this.$alert(
|
`
|
<div>
|
<p><strong>专家姓名:</strong>${expert.expertName}</p>
|
<p><strong>专家类型:</strong>${
|
expert.isChief ? "主委专家" : "专家"
|
}</p>
|
<p><strong>审查状态:</strong>${this.statusTextFilter(
|
expert.reviewStatus
|
)}</p>
|
<p><strong>专家结论:</strong>${
|
expert.expertConclusion
|
? this.conclusionTextFilter(expert.expertConclusion)
|
: "未提交"
|
}</p>
|
<p><strong>审查意见:</strong>${expert.expertOpinion || "无"}</p>
|
<p><strong>审查时间:</strong>${expert.reviewTime || "未审查"}</p>
|
</div>
|
`,
|
"专家审查详情",
|
{
|
dangerouslyUseHTMLString: true,
|
customClass: "expert-review-detail-dialog"
|
}
|
);
|
},
|
|
// 上传附件
|
handleUploadAttachment() {
|
this.uploadDialogVisible = true;
|
},
|
|
// 上传前校验
|
beforeUpload(file) {
|
const allowedTypes = [
|
"application/pdf",
|
"image/jpeg",
|
"image/png",
|
"application/msword",
|
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
"application/vnd.ms-excel",
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
];
|
|
const maxSize = 10 * 1024 * 1024;
|
|
const isTypeOk =
|
allowedTypes.includes(file.type) ||
|
file.name.endsWith(".pdf") ||
|
file.name.endsWith(".jpg") ||
|
file.name.endsWith(".jpeg") ||
|
file.name.endsWith(".png") ||
|
file.name.endsWith(".doc") ||
|
file.name.endsWith(".docx") ||
|
file.name.endsWith(".xls") ||
|
file.name.endsWith(".xlsx");
|
|
if (!isTypeOk) {
|
this.$message.error("文件格式不支持");
|
return false;
|
}
|
|
if (file.size > maxSize) {
|
this.$message.error("文件大小不能超过10MB");
|
return false;
|
}
|
|
return true;
|
},
|
|
// 文件选择变化
|
handleFileChange(file, fileList) {
|
this.tempFileList = fileList;
|
},
|
|
// 提交上传
|
submitUpload() {
|
if (this.tempFileList.length === 0) {
|
this.$message.warning("请先选择要上传的文件");
|
return;
|
}
|
|
this.uploadLoading = true;
|
|
const uploadPromises = this.tempFileList.map(file => {
|
const formData = new FormData();
|
formData.append("file", file.raw);
|
formData.append("ethicsReviewId", this.form.id);
|
|
return uploadAttachment(formData);
|
});
|
|
Promise.all(uploadPromises)
|
.then(responses => {
|
this.$message.success("文件上传成功");
|
this.uploadDialogVisible = false;
|
this.tempFileList = [];
|
this.getAttachments(this.form.id);
|
})
|
.catch(error => {
|
console.error("上传失败:", error);
|
this.$message.error("文件上传失败");
|
})
|
.finally(() => {
|
this.uploadLoading = false;
|
});
|
},
|
|
// 预览附件
|
handlePreviewAttachment(attachment) {
|
if (attachment.fileName.endsWith(".pdf")) {
|
window.open(attachment.fileUrl, "_blank");
|
} else if (attachment.fileName.match(/\.(jpg|jpeg|png)$/i)) {
|
this.$alert(
|
`<img src="${attachment.fileUrl}" style="max-width: 100%;" alt="${attachment.fileName}">`,
|
"图片预览",
|
{
|
dangerouslyUseHTMLString: true,
|
customClass: "image-preview-dialog"
|
}
|
);
|
} else {
|
this.$message.info("该文件类型暂不支持在线预览,请下载后查看");
|
}
|
},
|
|
// 下载附件
|
handleDownloadAttachment(attachment) {
|
const link = document.createElement("a");
|
link.href = attachment.fileUrl;
|
link.download = attachment.fileName;
|
link.click();
|
this.$message.success(`开始下载: ${attachment.fileName}`);
|
},
|
|
// 删除附件
|
handleRemoveAttachment(attachment) {
|
this.$confirm("确定要删除这个附件吗?", "提示", {
|
confirmButtonText: "确定",
|
cancelButtonText: "取消",
|
type: "warning"
|
})
|
.then(() => {
|
deleteAttachment(attachment.id)
|
.then(response => {
|
if (response.code === 200) {
|
this.$message.success("附件删除成功");
|
this.getAttachments(this.form.id);
|
}
|
})
|
.catch(error => {
|
console.error("删除附件失败:", error);
|
this.$message.error("删除附件失败");
|
});
|
})
|
.catch(() => {});
|
},
|
|
// 获取文件类型
|
getFileType(fileName) {
|
const ext = fileName
|
.split(".")
|
.pop()
|
.toLowerCase();
|
const typeMap = {
|
pdf: "PDF",
|
doc: "DOC",
|
docx: "DOCX",
|
xls: "XLS",
|
xlsx: "XLSX",
|
jpg: "JPG",
|
jpeg: "JPEG",
|
png: "PNG"
|
};
|
return typeMap[ext] || ext.toUpperCase();
|
},
|
|
// 文件大小格式化
|
formatFileSize(size) {
|
if (size === 0) return "0 B";
|
const k = 1024;
|
const sizes = ["B", "KB", "MB", "GB"];
|
const i = Math.floor(Math.log(size) / Math.log(k));
|
return parseFloat((size / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
|
},
|
|
// 时间格式化
|
parseTime(time) {
|
if (!time) return "";
|
const date = new Date(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;
|
}
|
|
.upload-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
margin-bottom: 15px;
|
padding: 10px;
|
background-color: #f8f9fa;
|
border-radius: 4px;
|
}
|
|
.upload-title {
|
font-size: 14px;
|
font-weight: 600;
|
color: #303133;
|
}
|
|
.file-info {
|
display: flex;
|
align-items: center;
|
}
|
|
.empty-attachment {
|
text-align: center;
|
padding: 40px 0;
|
color: #909399;
|
}
|
|
/* 表单样式优化 */
|
:deep(.el-form-item__label) {
|
font-weight: 500;
|
}
|
|
:deep(.el-input__inner) {
|
border-radius: 4px;
|
}
|
|
:deep(.el-textarea__inner) {
|
border-radius: 4px;
|
resize: vertical;
|
}
|
|
/* 表格样式优化 */
|
:deep(.el-table) {
|
border-radius: 8px;
|
overflow: hidden;
|
}
|
|
:deep(.el-table th) {
|
background-color: #f5f7fa;
|
color: #606266;
|
font-weight: 500;
|
}
|
|
:deep(.el-table .cell) {
|
padding: 8px 12px;
|
}
|
|
/* 按钮样式优化 */
|
:deep(.el-button--primary) {
|
background: linear-gradient(135deg, #409eff 0%, #3375e0 100%);
|
border: none;
|
border-radius: 4px;
|
}
|
|
:deep(.el-button--success) {
|
background: linear-gradient(135deg, #67c23a 0%, #529b2f 100%);
|
border: none;
|
border-radius: 4px;
|
}
|
|
:deep(.el-button--warning) {
|
background: linear-gradient(135deg, #e6a23c 0%, #d18c2a 100%);
|
border: none;
|
border-radius: 4px;
|
}
|
|
:deep(.el-button--danger) {
|
background: linear-gradient(135deg, #f56c6c 0%, #e05b5b 100%);
|
border: none;
|
border-radius: 4px;
|
}
|
|
/* 标签样式 */
|
:deep(.el-tag) {
|
border-radius: 12px;
|
border: none;
|
font-weight: 500;
|
}
|
|
/* 对话框样式优化 */
|
:deep(.el-dialog) {
|
border-radius: 8px;
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
}
|
|
:deep(.el-dialog__header) {
|
background: linear-gradient(135deg, #f5f7fa 0%, #e4e7ed 100%);
|
border-bottom: 1px solid #e4e7ed;
|
padding: 15px 20px;
|
}
|
|
:deep(.el-dialog__title) {
|
font-weight: 600;
|
color: #303133;
|
}
|
|
/* 上传组件样式 */
|
:deep(.el-upload-dragger) {
|
border: 2px dashed #dcdfe6;
|
border-radius: 6px;
|
background-color: #fafafa;
|
transition: all 0.3s ease;
|
}
|
|
:deep(.el-upload-dragger:hover) {
|
border-color: #409eff;
|
background-color: #f0f7ff;
|
}
|
|
/* 响应式设计 */
|
@media (max-width: 768px) {
|
.ethics-review-detail {
|
padding: 10px;
|
}
|
|
.expert-stats .el-col {
|
margin-bottom: 10px;
|
}
|
|
.upload-header {
|
flex-direction: column;
|
align-items: flex-start;
|
gap: 10px;
|
}
|
}
|
|
/* 动画效果 */
|
.fade-enter-active,
|
.fade-leave-active {
|
transition: opacity 0.3s ease;
|
}
|
|
.fade-enter,
|
.fade-leave-to {
|
opacity: 0;
|
}
|
|
/* 加载状态 */
|
.loading-container {
|
display: flex;
|
justify-content: center;
|
align-items: center;
|
height: 200px;
|
}
|
/* 专家类型样式 */
|
.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;
|
}
|
|
/* 表格行悬停效果 */
|
:deep(.el-table__row:hover) {
|
transform: translateY(-1px);
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
transition: all 0.3s ease;
|
}
|
/* 自定义滚动条 */
|
:deep(::-webkit-scrollbar) {
|
width: 6px;
|
height: 6px;
|
}
|
|
:deep(::-webkit-scrollbar-track) {
|
background: #f1f1f1;
|
border-radius: 3px;
|
}
|
|
:deep(::-webkit-scrollbar-thumb) {
|
background: #c1c1c1;
|
border-radius: 3px;
|
}
|
|
:deep(::-webkit-scrollbar-thumb:hover) {
|
background: #a8a8a8;
|
}
|
|
/* 专家审查表格特殊样式 */
|
.expert-table-special :deep(.el-table__row) {
|
transition: all 0.3s ease;
|
}
|
|
.expert-table-special :deep(.el-table__row:hover) {
|
background-color: #f0f7ff;
|
transform: translateY(-1px);
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
}
|
|
/* 主委专家行高亮 */
|
:deep(.chief-expert-row) {
|
background-color: #fff7e6 !important;
|
}
|
|
:deep(.chief-expert-row:hover) {
|
background-color: #ffecc2 !important;
|
}
|
</style>
|