| | |
| | | <template> |
| | | <base-stage :stage-data="stageData" :case-info="caseInfo"> |
| | | <template #header> |
| | | <el-alert |
| | | title="伦理审查阶段" |
| | | :type="getAlertType()" |
| | | :description="getAlertDescription()" |
| | | show-icon |
| | | :closable="false" |
| | | /> |
| | | </template> |
| | | |
| | | <el-row :gutter="20" style="margin-top: 20px;"> |
| | | <el-col :span="12"> |
| | | <el-card> |
| | | <div slot="header" class="card-header"> |
| | | <span>审查委员会信息</span> |
| | | </div> |
| | | <el-descriptions :column="1" border> |
| | | <el-descriptions-item label="委员会名称"> |
| | | {{ reviewCommittee.name }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="审查会议时间"> |
| | | {{ formatTime(reviewCommittee.meetingTime) }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="参会委员"> |
| | | {{ reviewCommittee.members.length }} 人 |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="审查结论"> |
| | | <el-tag :type="reviewCommittee.conclusion ? 'success' : 'warning'"> |
| | | {{ reviewCommittee.conclusion ? '审查通过' : '审查中' }} |
| | | </el-tag> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="主席签字"> |
| | | {{ reviewCommittee.chairman }} |
| | | </el-descriptions-item> |
| | | </el-descriptions> |
| | | </el-card> |
| | | </el-col> |
| | | |
| | | <el-col :span="12"> |
| | | <el-card> |
| | | <div slot="header" class="card-header"> |
| | | <span>审查流程进度</span> |
| | | </div> |
| | | <el-steps direction="vertical" :active="reviewProgress.active" space="80px"> |
| | | <el-step |
| | | v-for="step in reviewProgress.steps" |
| | | :key="step.title" |
| | | :title="step.title" |
| | | :description="step.description" |
| | | :status="step.status" |
| | | /> |
| | | </el-steps> |
| | | </el-card> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-card style="margin-top: 20px;"> |
| | | <div slot="header" class="card-header"> |
| | | <span>委员审查意见</span> |
| | | <div class="ethics-review-detail"> |
| | | <el-card class="detail-card"> |
| | | <!-- 基础信息 --> |
| | | <div slot="header" class="clearfix"> |
| | | <span class="detail-title">伦理审查基本信息</span> |
| | | </div> |
| | | <el-table :data="reviewComments" border> |
| | | <el-table-column label="委员姓名" prop="memberName" width="120" /> |
| | | <el-table-column label="专业领域" prop="specialty" width="120" /> |
| | | <el-table-column label="审查意见" prop="comment" min-width="200" /> |
| | | <el-table-column label="投票结果" width="100"> |
| | | |
| | | <el-form :model="form" ref="form" :rules="rules" label-width="120px"> |
| | | |
| | | <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"> |
| | | <el-tag :type="scope.row.vote === '同意' ? 'success' : 'danger'"> |
| | | {{ scope.row.vote }} |
| | | </el-tag> |
| | | <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="160"> |
| | | |
| | | <el-table-column label="文件类型" width="100" align="center"> |
| | | <template slot-scope="scope"> |
| | | {{ formatTime(scope.row.reviewTime) }} |
| | | <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 style="margin-top: 20px;"> |
| | | <div slot="header" class="card-header"> |
| | | <span>审查决议文件</span> |
| | | <el-button type="primary" size="small" @click="handleViewResolution"> |
| | | 查看决议文件 |
| | | </el-button> |
| | | </div> |
| | | <div class="resolution-content"> |
| | | <p><strong>伦理审查决议:</strong></p> |
| | | <p>{{ resolutionContent }}</p> |
| | | <el-divider /> |
| | | <div class="signature-area"> |
| | | <p>伦理委员会主席:{{ reviewCommittee.chairman }}</p> |
| | | <p>日期:{{ formatTime(reviewCommittee.meetingTime) }}</p> |
| | | <!-- 专家审查情况 --> |
| | | <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> |
| | | </el-card> |
| | | </base-stage> |
| | | </template> |
| | | <!-- 专家统计信息 --> |
| | | <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 BaseStage from './BaseStage.vue'; |
| | | import { |
| | | getEthicsReviewDetail, |
| | | updateEthicsReview, |
| | | sendExpertReview, |
| | | endEthicsReview, |
| | | uploadAttachment, |
| | | deleteAttachment, |
| | | getAttachments |
| | | } from "./api/ethicsReview"; |
| | | |
| | | export default { |
| | | name: 'EthicalReviewStage', |
| | | components: { BaseStage }, |
| | | props: { |
| | | stageData: { |
| | | type: Object, |
| | | default: () => ({}) |
| | | }, |
| | | caseInfo: { |
| | | type: Object, |
| | | default: () => ({}) |
| | | } |
| | | }, |
| | | name: "EthicsReviewDetail", |
| | | data() { |
| | | return { |
| | | reviewCommittee: { |
| | | name: '医院伦理审查委员会', |
| | | meetingTime: '2023-12-03 15:20:00', |
| | | members: ['张教授', '李主任', '王医生', '赵委员', '钱专家'], |
| | | conclusion: true, |
| | | chairman: '张教授' |
| | | // 表单数据 |
| | | form: { |
| | | id: undefined, |
| | | hospitalNo: "", |
| | | donorName: "", |
| | | gender: "", |
| | | age: "", |
| | | diagnosis: "", |
| | | ethicsConclusion: "reviewing", |
| | | ethicsOpinion: "", |
| | | reviewTime: "", |
| | | registrant: "", |
| | | registrationTime: new Date() |
| | | .toISOString() |
| | | .replace("T", " ") |
| | | .substring(0, 19) |
| | | }, |
| | | reviewProgress: { |
| | | active: 4, |
| | | steps: [ |
| | | { |
| | | title: '材料初审', |
| | | description: '申请材料完整性审查', |
| | | status: 'finish' |
| | | }, |
| | | { |
| | | title: '委员评审', |
| | | description: '各委员独立审查', |
| | | status: 'finish' |
| | | }, |
| | | { |
| | | title: '会议讨论', |
| | | description: '委员会集体讨论', |
| | | status: 'finish' |
| | | }, |
| | | { |
| | | title: '形成决议', |
| | | description: '投票形成最终决议', |
| | | status: 'finish' |
| | | } |
| | | // 表单验证规则 |
| | | rules: { |
| | | donorName: [ |
| | | { required: true, message: "捐献者姓名不能为空", trigger: "blur" } |
| | | ], |
| | | ethicsConclusion: [ |
| | | { required: true, message: "伦理结论不能为空", trigger: "change" } |
| | | ], |
| | | reviewTime: [ |
| | | { required: true, message: "审查时间不能为空", trigger: "change" } |
| | | ] |
| | | }, |
| | | reviewComments: [ |
| | | // 保存加载状态 |
| | | saveLoading: false, |
| | | |
| | | // 附件数据 |
| | | attachments: [], |
| | | expertReviews: [ |
| | | // 专家(18位)- 初始状态为申请中 |
| | | { |
| | | memberName: '张教授', |
| | | specialty: '医学伦理', |
| | | comment: '捐献程序符合伦理规范,同意通过', |
| | | vote: '同意', |
| | | reviewTime: '2023-12-03 14:30:00' |
| | | id: 1, |
| | | expertName: "陶昊", |
| | | isChief: false, |
| | | reviewStatus: "applying", |
| | | expertConclusion: "", |
| | | expertOpinion: "", |
| | | reviewTime: "" |
| | | }, |
| | | { |
| | | memberName: '李主任', |
| | | specialty: '临床医学', |
| | | comment: '医疗程序规范,无伦理问题', |
| | | vote: '同意', |
| | | reviewTime: '2023-12-03 14:45:00' |
| | | id: 2, |
| | | expertName: "刘斌", |
| | | isChief: false, |
| | | reviewStatus: "applying", |
| | | expertConclusion: "", |
| | | expertOpinion: "", |
| | | reviewTime: "" |
| | | }, |
| | | { |
| | | memberName: '王医生', |
| | | specialty: '法律医学', |
| | | comment: '法律文件齐全,程序合法', |
| | | vote: '同意', |
| | | reviewTime: '2023-12-03 15:00:00' |
| | | 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: "" |
| | | } |
| | | ], |
| | | resolutionContent: '经伦理审查委员会审查,该器官捐献案例符合医学伦理要求,捐献程序规范,家属意愿真实有效,同意进行器官捐献。' |
| | | 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: { |
| | | getAlertType() { |
| | | const status = this.stageData.status; |
| | | return status === 'completed' ? 'success' : |
| | | status === 'in_progress' ? 'warning' : 'info'; |
| | | // 生成住院号 |
| | | generateHospitalNo() { |
| | | const timestamp = Date.now().toString(); |
| | | this.form.hospitalNo = "D" + timestamp.slice(-6); |
| | | }, |
| | | getAlertDescription() { |
| | | const status = this.stageData.status; |
| | | return status === 'completed' ? '伦理审查已通过,可以进行器官分配' : |
| | | status === 'in_progress' ? '伦理审查流程正在进行中' : '等待开始伦理审查流程'; |
| | | getExpertRowClassName({ row }) { |
| | | return row.isChief ? "chief-expert-row" : "normal-expert-row"; |
| | | }, |
| | | handleViewResolution() { |
| | | this.$message.info('查看伦理审查决议文件功能'); |
| | | // 获取详情 |
| | | 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> |
| | | .resolution-content { |
| | | .ethics-review-detail { |
| | | padding: 20px; |
| | | line-height: 1.8; |
| | | background-color: #f5f7fa; |
| | | } |
| | | |
| | | .signature-area { |
| | | text-align: right; |
| | | margin-top: 30px; |
| | | .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> |