<template>
|
<view class="ethics-review-list">
|
<!-- 统计卡片 -->
|
<view class="stats-card">
|
<view class="stat-item">
|
<text class="count">{{ stats.totalReviews }}</text>
|
<text class="label">总审查量</text>
|
</view>
|
<view class="divider"></view>
|
<view class="stat-item">
|
<text class="count">{{ stats.approvedReviews }}</text>
|
<text class="label">审查通过</text>
|
</view>
|
<view class="divider"></view>
|
<view class="stat-item">
|
<text class="count">{{ stats.rejectedReviews }}</text>
|
<text class="label">审查驳回</text>
|
</view>
|
<view class="divider"></view>
|
<view class="stat-item">
|
<text class="count">{{ stats.abandonedReviews }}</text>
|
<text class="label">已放弃</text>
|
</view>
|
</view>
|
|
<!-- 筛选栏 -->
|
<view class="filter-bar">
|
<view class="status-filter">
|
<text
|
v-for="status in statusOptions"
|
:key="status.value"
|
:class="{ active: currentStatus === status.value }"
|
@tap="selectStatus(status.value)"
|
>
|
{{ status.label }}
|
</text>
|
</view>
|
|
<view class="search-filter">
|
<u-input
|
v-model="searchKeyword"
|
placeholder="搜索捐献者姓名或住院号"
|
prefix-icon="search"
|
clearable
|
@confirm="handleSearch"
|
></u-input>
|
</view>
|
</view>
|
|
<!-- 审查记录列表 -->
|
<scroll-view
|
scroll-y
|
class="review-list"
|
refresher-enabled
|
:refresher-triggered="refreshing"
|
@refresherrefresh="onRefresh"
|
@scrolltolower="onLoadMore"
|
>
|
<view
|
v-for="(review, index) in filteredReviews"
|
:key="review.id"
|
class="review-item card"
|
@tap="viewDetail(review)"
|
>
|
<!-- 头部信息 -->
|
<view class="review-header">
|
<view class="case-info">
|
<view class="hospital-badge">
|
<u-icon name="order" size="16" color="#fff" />
|
</view>
|
<view class="info-content">
|
<text class="donor-name">{{ review.donorName }}</text>
|
<text class="hospital-no">{{ review.hospitalNo }}</text>
|
<text class="expert-type" v-if="review.expertType">{{ review.expertType }}</text>
|
</view>
|
</view>
|
<view class="status-tag" :class="review.status">
|
{{ getStatusText(review.status) }}
|
</view>
|
</view>
|
|
<!-- 基本信息 -->
|
<view class="basic-info">
|
<view class="info-row">
|
<view class="info-col">
|
<text class="info-label">性别/年龄</text>
|
<text class="info-value">{{ review.gender }}/{{ review.age }}岁</text>
|
</view>
|
<view class="info-col">
|
<text class="info-label">血型</text>
|
<text class="info-value">{{ review.bloodType }}</text>
|
</view>
|
<view class="info-col">
|
<text class="info-label">疾病诊断</text>
|
<text class="info-value">{{ review.diagnosis }}</text>
|
</view>
|
</view>
|
</view>
|
|
<!-- 审查详情 -->
|
<view class="review-details">
|
<view class="detail-item">
|
<u-icon name="clock" size="14" color="#909399" />
|
<text class="detail-text">提交时间:{{ review.submitTime }}</text>
|
</view>
|
<view class="detail-item" v-if="review.reviewTime">
|
<u-icon name="checkmark-circle" size="14" color="#909399" />
|
<text class="detail-text">审查时间:{{ review.reviewTime }}</text>
|
</view>
|
<view class="detail-item" v-if="review.reviewer">
|
<u-icon name="account" size="14" color="#909399" />
|
<text class="detail-text">审查人:{{ review.reviewer }}</text>
|
</view>
|
</view>
|
|
<!-- 审查结论 -->
|
<view class="conclusion-section" v-if="review.status !== 'abandoned'">
|
<text class="conclusion-label">审查结论:</text>
|
<text class="conclusion-content">{{ review.conclusion || '暂无结论' }}</text>
|
</view>
|
|
<!-- 放弃原因 -->
|
<view class="abandon-reason" v-if="review.status === 'abandoned'">
|
<text class="reason-label">放弃原因:</text>
|
<text class="reason-content">{{ review.abandonReason || '用户主动放弃' }}</text>
|
</view>
|
|
<!-- 操作按钮 -->
|
<view class="action-buttons">
|
<button
|
class="action-btn detail-btn"
|
@tap.stop="viewDetail(review)"
|
>
|
<u-icon name="eye" size="14" color="#747CF9" />
|
<text>查看详情</text>
|
</button>
|
|
<button
|
v-if="review.status === 'approved'"
|
class="action-btn download-btn"
|
@tap.stop="downloadReport(review)"
|
>
|
<u-icon name="download" size="14" color="#52c41a" />
|
<text>下载报告</text>
|
</button>
|
|
<button
|
v-if="review.status === 'rejected'"
|
class="action-btn appeal-btn"
|
@tap.stop="submitAppeal(review)"
|
>
|
<u-icon name="arrow-up" size="14" color="#fa8c16" />
|
<text>提起申诉</text>
|
</button>
|
|
<button
|
v-if="review.status === 'abandoned'"
|
class="action-btn restart-btn"
|
@tap.stop="restartReview(review)"
|
>
|
<u-icon name="play-circle" size="14" color="#747CF9" />
|
<text>重新开始</text>
|
</button>
|
</view>
|
</view>
|
|
<!-- 加载状态 -->
|
<!-- <view class="load-more" v-if="hasMore">
|
<u-loading size="24" color="#747CF9"></u-loading>
|
<text>加载更多...</text>
|
</view> -->
|
<u-loading-icon :show="hasMore" text="提交中..."></u-loading-icon>
|
|
|
<!-- 空状态 -->
|
<view class="empty-state" v-if="!loading && filteredReviews.length === 0">
|
<u-icon name="file-remove" size="80" color="#C0C4CC" />
|
<text class="empty-text">暂无审查记录</text>
|
<text class="empty-desc">当前筛选条件下没有找到相关记录</text>
|
<button class="empty-action" @tap="resetFilters">
|
<text>重置筛选条件</text>
|
</button>
|
</view>
|
</scroll-view>
|
</view>
|
</template>
|
|
<script setup>
|
import { ref, computed, onMounted } from 'vue'
|
import { onLoad, onShow } from '@dcloudio/uni-app'
|
|
// 响应式数据
|
const loading = ref(false)
|
const refreshing = ref(false)
|
const hasMore = ref(true)
|
const pageNum = ref(1)
|
const pageSize = ref(10)
|
|
// 筛选条件
|
const currentStatus = ref('all')
|
const searchKeyword = ref('')
|
|
// 统计数据
|
const stats = ref({
|
totalReviews: 0,
|
approvedReviews: 0,
|
rejectedReviews: 0,
|
abandonedReviews: 0
|
})
|
|
// 状态选项 - 根据您的要求设置
|
const statusOptions = ref([
|
{ label: '全部', value: 'all' },
|
{ label: '审查通过', value: 'approved' },
|
{ label: '审查驳回', value: 'rejected' },
|
{ label: '放弃', value: 'abandoned' }
|
])
|
|
// 模拟数据
|
const reviews = ref([
|
{
|
id: 1,
|
hospitalNo: 'D230415',
|
donorName: '张某某',
|
gender: '男',
|
age: 45,
|
bloodType: 'A型',
|
diagnosis: '终末期肝病',
|
status: 'approved',
|
expertType: '主委专家',
|
submitTime: '2025-12-01 10:30',
|
reviewTime: '2025-12-02 14:20',
|
reviewer: '孔心涓',
|
conclusion: '符合伦理要求,同意开展器官捐献工作'
|
},
|
{
|
id: 2,
|
hospitalNo: 'D230416',
|
donorName: '李某某',
|
gender: '女',
|
age: 38,
|
bloodType: 'O型',
|
diagnosis: '终末期肾病',
|
status: 'rejected',
|
expertType: '专家',
|
submitTime: '2025-12-01 14:20',
|
reviewTime: '2025-12-03 09:15',
|
reviewer: '陶昊',
|
conclusion: '风险评估不足,需要补充材料后重新审查'
|
},
|
{
|
id: 3,
|
hospitalNo: 'D230417',
|
donorName: '王某某',
|
gender: '男',
|
age: 52,
|
bloodType: 'B型',
|
diagnosis: '终末期心脏病',
|
status: 'abandoned',
|
expertType: '专家',
|
submitTime: '2025-11-30 16:45',
|
abandonReason: '家属要求停止审查流程',
|
reviewer: '刘斌'
|
},
|
{
|
id: 4,
|
hospitalNo: 'D230418',
|
donorName: '赵某某',
|
gender: '女',
|
age: 29,
|
bloodType: 'AB型',
|
diagnosis: '急性肝功能衰竭',
|
status: 'approved',
|
expertType: '主委专家',
|
submitTime: '2025-12-02 08:15',
|
reviewTime: '2025-12-03 16:30',
|
reviewer: '孔心涓',
|
conclusion: '紧急情况处理得当,同意立即开展捐献程序'
|
}
|
])
|
|
// 计算属性
|
const filteredReviews = computed(() => {
|
let result = reviews.value
|
|
// 状态筛选
|
if (currentStatus.value !== 'all') {
|
result = result.filter(review => review.status === currentStatus.value)
|
}
|
|
// 关键词搜索
|
if (searchKeyword.value) {
|
const keyword = searchKeyword.value.toLowerCase()
|
result = result.filter(review =>
|
review.donorName.toLowerCase().includes(keyword) ||
|
review.hospitalNo.toLowerCase().includes(keyword) ||
|
review.diagnosis.toLowerCase().includes(keyword) ||
|
(review.reviewer && review.reviewer.toLowerCase().includes(keyword))
|
)
|
}
|
|
return result
|
})
|
|
// 方法
|
const getStatusText = (status) => {
|
const statusMap = {
|
approved: '审查通过',
|
rejected: '审查驳回',
|
abandoned: '已放弃'
|
}
|
return statusMap[status] || '未知状态'
|
}
|
|
const selectStatus = (status) => {
|
currentStatus.value = status
|
}
|
|
const handleSearch = () => {
|
console.log('搜索关键词:', searchKeyword.value)
|
}
|
|
const resetFilters = () => {
|
currentStatus.value = 'all'
|
searchKeyword.value = ''
|
}
|
|
const onRefresh = async () => {
|
refreshing.value = true
|
setTimeout(() => {
|
refreshing.value = false
|
loadInitialData()
|
}, 1000)
|
}
|
|
const onLoadMore = async () => {
|
if (!hasMore.value || loading.value) return
|
loading.value = true
|
setTimeout(() => {
|
loading.value = false
|
}, 500)
|
}
|
|
const viewDetail = (review) => {
|
uni.navigateTo({
|
url: `/pages/ethicalReview/ethicalInfo?id=${review.id}&status=${review.status}`
|
})
|
}
|
|
const downloadReport = (review) => {
|
uni.showToast({
|
title: '开始下载审查报告',
|
icon: 'success'
|
})
|
}
|
|
const submitAppeal = (review) => {
|
uni.navigateTo({
|
url: `/pages/ethics/appeal?id=${review.id}`
|
})
|
}
|
|
const restartReview = (review) => {
|
uni.showModal({
|
title: '重新开始审查',
|
content: '确定要重新开始这个审查流程吗?',
|
success: (res) => {
|
if (res.confirm) {
|
uni.showToast({
|
title: '审查已重新开始',
|
icon: 'success'
|
})
|
}
|
}
|
})
|
}
|
|
// 生命周期
|
onLoad(() => {
|
loadInitialData()
|
})
|
|
const loadInitialData = () => {
|
// 计算统计数据
|
stats.value = {
|
totalReviews: reviews.value.length,
|
approvedReviews: reviews.value.filter(r => r.status === 'approved').length,
|
rejectedReviews: reviews.value.filter(r => r.status === 'rejected').length,
|
abandonedReviews: reviews.value.filter(r => r.status === 'abandoned').length
|
}
|
}
|
</script>
|
|
<style lang="scss" scoped>
|
.ethics-review-list {
|
min-height: 100vh;
|
background: #f5f7fa;
|
padding: 20rpx;
|
|
.stats-card {
|
background: linear-gradient(135deg, #747CF9, #9B7CF9);
|
border-radius: 16rpx;
|
padding: 40rpx 20rpx;
|
display: flex;
|
align-items: center;
|
margin-bottom: 24rpx;
|
box-shadow: 0 4rpx 20rpx rgba(116, 124, 249, 0.3);
|
|
.stat-item {
|
flex: 1;
|
text-align: center;
|
|
.count {
|
font-size: 36rpx;
|
color: #fff;
|
font-weight: bold;
|
margin-bottom: 8rpx;
|
display: block;
|
}
|
|
.label {
|
font-size: 24rpx;
|
color: rgba(255, 255, 255, 0.9);
|
}
|
}
|
|
.divider {
|
width: 2rpx;
|
height: 50rpx;
|
background: rgba(255, 255, 255, 0.2);
|
}
|
}
|
|
.filter-bar {
|
background: #fff;
|
border-radius: 16rpx;
|
padding: 24rpx;
|
margin-bottom: 24rpx;
|
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
|
|
.status-filter {
|
display: flex;
|
margin-bottom: 20rpx;
|
|
text {
|
flex: 1;
|
text-align: center;
|
font-size: 26rpx;
|
color: #606266;
|
padding: 16rpx 0;
|
position: relative;
|
|
&.active {
|
color: #747CF9;
|
font-weight: 500;
|
|
&::after {
|
content: '';
|
position: absolute;
|
left: 50%;
|
bottom: 0;
|
transform: translateX(-50%);
|
width: 40rpx;
|
height: 4rpx;
|
background: #747CF9;
|
border-radius: 2rpx;
|
}
|
}
|
}
|
}
|
}
|
|
.review-list {
|
height: calc(100vh - 300rpx);
|
|
.review-item {
|
background: #fff;
|
border-radius: 16rpx;
|
padding: 32rpx;
|
margin-bottom: 24rpx;
|
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
|
|
.review-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: flex-start;
|
margin-bottom: 24rpx;
|
|
.case-info {
|
display: flex;
|
align-items: center;
|
|
.hospital-badge {
|
background: linear-gradient(135deg, #747CF9, #9B7CF9);
|
width: 64rpx;
|
height: 64rpx;
|
border-radius: 12rpx;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
margin-right: 20rpx;
|
}
|
|
.info-content {
|
.donor-name {
|
font-size: 32rpx;
|
color: #303133;
|
font-weight: 600;
|
display: block;
|
margin-bottom: 4rpx;
|
}
|
|
.hospital-no {
|
font-size: 26rpx;
|
color: #909399;
|
margin-right: 16rpx;
|
}
|
|
.expert-type {
|
font-size: 22rpx;
|
color: #747CF9;
|
background: #f0f2ff;
|
padding: 4rpx 12rpx;
|
border-radius: 12rpx;
|
}
|
}
|
}
|
|
.status-tag {
|
padding: 8rpx 16rpx;
|
border-radius: 20rpx;
|
font-size: 24rpx;
|
font-weight: 500;
|
|
&.approved {
|
background: #f6ffed;
|
color: #52c41a;
|
}
|
|
&.rejected {
|
background: #fff2e8;
|
color: #fa541c;
|
}
|
|
&.abandoned {
|
background: #f5f5f5;
|
color: #8c8c8c;
|
}
|
}
|
}
|
|
.basic-info {
|
margin-bottom: 20rpx;
|
|
.info-row {
|
display: grid;
|
grid-template-columns: 1fr 1fr 1fr;
|
gap: 20rpx;
|
|
.info-col {
|
.info-label {
|
font-size: 24rpx;
|
color: #909399;
|
display: block;
|
margin-bottom: 4rpx;
|
}
|
|
.info-value {
|
font-size: 26rpx;
|
color: #303133;
|
font-weight: 500;
|
}
|
}
|
}
|
}
|
|
.review-details {
|
display: flex;
|
flex-direction: column;
|
gap: 12rpx;
|
margin-bottom: 20rpx;
|
|
.detail-item {
|
display: flex;
|
align-items: center;
|
gap: 8rpx;
|
|
.detail-text {
|
font-size: 24rpx;
|
color: #606266;
|
}
|
}
|
}
|
|
.conclusion-section {
|
background: #f6ffed;
|
border-radius: 8rpx;
|
padding: 20rpx;
|
margin-bottom: 20rpx;
|
|
.conclusion-label {
|
font-size: 24rpx;
|
color: #52c41a;
|
font-weight: 500;
|
}
|
|
.conclusion-content {
|
font-size: 24rpx;
|
color: #303133;
|
}
|
}
|
|
.abandon-reason {
|
background: #f5f5f5;
|
border-radius: 8rpx;
|
padding: 20rpx;
|
margin-bottom: 20rpx;
|
|
.reason-label {
|
font-size: 24rpx;
|
color: #8c8c8c;
|
font-weight: 500;
|
}
|
|
.reason-content {
|
font-size: 24rpx;
|
color: #303133;
|
}
|
}
|
|
.action-buttons {
|
display: flex;
|
justify-content: space-between;
|
gap: 16rpx;
|
|
.action-btn {
|
flex: 1;
|
height: 64rpx;
|
border: none;
|
border-radius: 32rpx;
|
font-size: 26rpx;
|
font-weight: 500;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
gap: 6rpx;
|
|
&.detail-btn {
|
background: #f5f5f5;
|
color: #747CF9;
|
}
|
|
&.download-btn {
|
background: #f6ffed;
|
color: #52c41a;
|
border: 1rpx solid #b7eb8f;
|
}
|
|
&.appeal-btn {
|
background: #fff2e8;
|
color: #fa8c16;
|
border: 1rpx solid #ffbb96;
|
}
|
|
&.restart-btn {
|
background: #f0f2ff;
|
color: #747CF9;
|
border: 1rpx solid #adc6ff;
|
}
|
}
|
}
|
}
|
|
.load-more {
|
text-align: center;
|
padding: 32rpx;
|
color: #909399;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
gap: 12rpx;
|
}
|
|
.empty-state {
|
text-align: center;
|
padding: 120rpx 0;
|
|
.empty-text {
|
display: block;
|
font-size: 32rpx;
|
color: #909399;
|
margin: 24rpx 0 12rpx;
|
}
|
|
.empty-desc {
|
font-size: 26rpx;
|
color: #c0c4cc;
|
margin-bottom: 32rpx;
|
}
|
|
.empty-action {
|
background: linear-gradient(135deg, #747CF9, #9B7CF9);
|
color: #fff;
|
border: none;
|
border-radius: 32rpx;
|
padding: 16rpx 32rpx;
|
font-size: 28rpx;
|
}
|
}
|
}
|
}
|
|
/* 响应式设计 */
|
@media (max-width: 768px) {
|
.ethics-review-list {
|
padding: 20rpx;
|
|
.review-item .basic-info .info-row {
|
grid-template-columns: 1fr;
|
gap: 16rpx;
|
}
|
|
.action-buttons {
|
flex-direction: column;
|
}
|
}
|
}
|
</style>
|