WXL
4 天以前 4d9da000fbe74d344e0e4580b138e79d4ad98ede
pages/ethicalReview/index.vue
@@ -3,22 +3,22 @@
    <!-- 统计卡片 -->
    <view class="stats-card">
      <view class="stat-item">
        <text class="count">{{ stats.totalReviews }}</text>
        <text class="count">{{ stats.count }}</text>
        <text class="label">总审查量</text>
      </view>
      <view class="divider"></view>
      <view class="stat-item">
        <text class="count">{{ stats.approvedReviews }}</text>
        <text class="count">{{ stats.throughCount }}</text>
        <text class="label">审查通过</text>
      </view>
      <view class="divider"></view>
      <view class="stat-item">
        <text class="count">{{ stats.rejectedReviews }}</text>
        <text class="count">{{ stats.rejectCount }}</text>
        <text class="label">审查驳回</text>
      </view>
      <view class="divider"></view>
      <view class="stat-item">
        <text class="count">{{ stats.abandonedReviews }}</text>
        <text class="count">{{ stats.waiveCount }}</text>
        <text class="label">已放弃</text>
      </view>
    </view>
@@ -26,16 +26,16 @@
    <!-- 筛选栏 -->
    <view class="filter-bar">
      <view class="status-filter">
        <text
          v-for="status in statusOptions"
        <text
          v-for="status in statusOptions"
          :key="status.value"
          :class="{ active: currentStatus === status.value }"
          :class="{ active: currentStatus == status.value }"
          @tap="selectStatus(status.value)"
        >
          {{ status.label }}
        </text>
      </view>
      <view class="search-filter">
        <u-input
          v-model="searchKeyword"
@@ -48,17 +48,17 @@
    </view>
    <!-- 审查记录列表 -->
    <scroll-view
      scroll-y
    <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"
      <view
        v-for="(review, index) in reviewList"
        :key="review.id || index"
        class="review-item card"
        @tap="viewDetail(review)"
      >
@@ -69,13 +69,17 @@
              <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>
              <text class="donor-name">{{ review.name || "未填写姓名" }}</text>
              <text class="hospital-no">{{
                review.inpatientno || "无住院号"
              }}</text>
              <text class="expert-type" v-if="review.expertname">
                专家: {{ review.expertname }}
              </text>
            </view>
          </view>
          <view class="status-tag" :class="review.status">
            {{ getStatusText(review.status) }}
          <view class="status-tag" :class="getReviewStatusClass(review)">
            {{ getReviewStatusText(review) }}
          </view>
        </view>
@@ -84,77 +88,91 @@
          <view class="info-row">
            <view class="info-col">
              <text class="info-label">性别/年龄</text>
              <text class="info-value">{{ review.gender }}/{{ review.age }}岁</text>
              <text class="info-value"
                >{{ review.sex == 1 ? "男" : "女" }}/{{
                  getAgeWithUnit(review)
                }}</text
              >
            </view>
            <view class="info-col">
              <text class="info-label">血型</text>
              <text class="info-value">{{ review.bloodType }}</text>
              <text class="info-value">{{
                getDictLabel("sys_BloodType", review.bloodtype) || "未知"
              }}</text>
            </view>
            <view class="info-col">
              <text class="info-label">疾病诊断</text>
              <text class="info-value">{{ review.diagnosis }}</text>
              <text class="info-value">{{
                review.diagnosisname || "未填写"
              }}</text>
            </view>
          </view>
        </view>
        <!-- 审查详情 -->
        <view class="review-details">
          <view class="detail-item">
          <view class="detail-item" v-if="review.createTime">
            <u-icon name="clock" size="14" color="#909399" />
            <text class="detail-text">提交时间:{{ review.submitTime }}</text>
            <text class="detail-text"
              >创建时间:{{ formatDate(review.createTime) }}</text
            >
          </view>
          <view class="detail-item" v-if="review.reviewTime">
          <view class="detail-item" v-if="review.conclusiontime">
            <u-icon name="checkmark-circle" size="14" color="#909399" />
            <text class="detail-text">审查时间:{{ review.reviewTime }}</text>
            <text class="detail-text"
              >审查时间:{{ formatDate(review.conclusiontime) }}</text
            >
          </view>
          <view class="detail-item" v-if="review.reviewer">
          <view class="detail-item" v-if="review.expertname">
            <u-icon name="account" size="14" color="#909399" />
            <text class="detail-text">审查人:{{ review.reviewer }}</text>
            <text class="detail-text">审查人:{{ review.expertname }}</text>
          </view>
        </view>
        <!-- 审查结论 -->
        <view class="conclusion-section" v-if="review.status !== 'abandoned'">
        <view
          class="conclusion-section"
          v-if="review.expertconclusion && review.expertconclusion !== 2"
        >
          <text class="conclusion-label">审查结论:</text>
          <text class="conclusion-content">{{ review.conclusion || '暂无结论' }}</text>
          <text class="conclusion-content">{{
            getConclusionText(review.expertconclusion)
          }}</text>
        </view>
        <!-- 放弃原因 -->
        <view class="abandon-reason" v-if="review.status === 'abandoned'">
          <text class="reason-label">放弃原因:</text>
          <text class="reason-content">{{ review.abandonReason || '用户主动放弃' }}</text>
        <!-- 专家意见 -->
        <view class="opinion-section" v-if="review.expertopinion">
          <text class="opinion-label">专家意见:</text>
          <text class="opinion-content">{{ review.expertopinion }}</text>
        </view>
        <!-- 操作按钮 -->
        <view class="action-buttons">
          <button
            class="action-btn detail-btn"
            @tap.stop="viewDetail(review)"
          >
          <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'"
          <button
            v-if="review.expertconclusion == 1"
            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'"
          <button
            v-if="review.expertconclusion == 2"
            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'"
          <button
            v-if="review.expertconclusion == 2 || review.expertconclusion == 3"
            class="action-btn restart-btn"
            @tap.stop="restartReview(review)"
          >
@@ -165,15 +183,11 @@
      </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>
      <u-loading-icon :show="loading" text="加载中..."></u-loading-icon>
      <!-- 空状态 -->
      <view class="empty-state" v-if="!loading && filteredReviews.length === 0">
      <view class="empty-state" v-if="!loading && reviewList.length == 0">
        <view> {{ loading }}-{{ reviewList.length }} </view>
        <u-icon name="file-remove" size="80" color="#C0C4CC" />
        <text class="empty-text">暂无审查记录</text>
        <text class="empty-desc">当前筛选条件下没有找到相关记录</text>
@@ -181,214 +195,523 @@
          <text>重置筛选条件</text>
        </button>
      </view>
      <!-- 加载完成提示 -->
      <view class="load-complete" v-if="!hasMore && reviewList.length > 0">
        <text>已加载全部数据</text>
      </view>
    </scroll-view>
  </view>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { onLoad, onShow } from '@dcloudio/uni-app'
import { ref, computed, onMounted, watch } from "vue";
import {
  onLoad,
  onShow,
  onPullDownRefresh,
  onReachBottom,
} from "@dcloudio/uni-app";
import { useDict } from "@/utils/dict";
// 响应式数据
const loading = ref(false)
const refreshing = ref(false)
const hasMore = ref(true)
const pageNum = ref(1)
const pageSize = ref(10)
const loading = ref(false);
const refreshing = ref(false);
const hasMore = ref(true);
const pageNum = ref(1);
const pageSize = ref(10);
const dict = ref({});
// 筛选条件
const currentStatus = ref('all')
const searchKeyword = ref('')
const currentStatus = ref("all");
const searchKeyword = ref("");
// 统计数据
const stats = ref({
  totalReviews: 0,
  approvedReviews: 0,
  rejectedReviews: 0,
  abandonedReviews: 0
})
  count: 0,
  throughCount: 0,
  rejectCount: 0,
  waiveCount: 0,
});
// 状态选项 - 根据您的要求设置
// 审查列表数据
const reviewList = ref([]);
const total = ref(0);
// 状态选项
const statusOptions = ref([
  { label: '全部', value: 'all' },
  { label: '审查通过', value: 'approved' },
  { label: '审查驳回', value: 'rejected' },
  { label: '放弃', value: 'abandoned' }
])
  { label: "全部", value: "all" },
  { label: "待审查", value: "pending" },
  { 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 statusDict = {
  pending: "待审查",
  approved: "审查通过",
  rejected: "审查驳回",
  abandoned: "已放弃",
};
// 计算属性
const filteredReviews = computed(() => {
  let result = reviews.value
// 结论映射
const conclusionDict = {
  0: "未审核",
  1: "审查通过",
  2: "审查驳回",
  3: "放弃",
  4: "修改后同意",
};
  // 状态筛选
  if (currentStatus.value !== 'all') {
    result = result.filter(review => review.status === currentStatus.value)
  }
// 获取字典标签
const getDictLabel = (dictType, dictValue) => {
  if (!dict.value[dictType] || !dictValue) return "";
  const dictItem = dict.value[dictType].find(
    (item) => item.dictValue == String(dictValue),
  );
  return dictItem ? dictItem.dictLabel : dictValue;
};
  // 关键词搜索
  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))
    )
  }
// 获取年龄和单位
const getAgeWithUnit = (review) => {
  if (!review.age) return "未知";
  const unit = getDictLabel("sys_AgeUnit", review.ageunit) || "岁";
  return `${review.age}${unit}`;
};
  return result
})
// 格式化日期
const formatDate = (dateString) => {
  if (!dateString) return "";
  const date = new Date(dateString);
  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")}`;
};
// 获取审查状态文本
const getReviewStatusText = (review) => {
  if (review.receiveStatus == 0) return "待推送";
  if (review.receiveStatus == 1) return "未阅读";
  if (review.receiveStatus == 2) return "已阅读";
  if (review.receiveStatus == 3) return "超时";
  if (review.receiveStatus == 4) return "中止";
  if (review.receiveStatus == 5) return "完成";
  if (!review.receiveStatus && review.receiveStatus !== 0) return "待推送";
  return "待推送";
};
// 获取审查状态类名
const getReviewStatusClass = (review) => {
  if (review.receiveStatus == 0) return "abandoned";
  if (review.receiveStatus == 1) return "abandoned";
  if (review.receiveStatus == 2) return "pending";
  if (review.receiveStatus == 3) return "unknown";
  if (review.receiveStatus == 4) return "unknown";
  if (review.receiveStatus == 5) return "approved";
  if (!review.receiveStatus && review.receiveStatus !== 0) return "pending";
  return "unknown";
};
// 获取结论文本
const getConclusionText = (conclusion) => {
  return conclusionDict[conclusion] || "未知结论";
};
// 方法
const getStatusText = (status) => {
  const statusMap = {
    approved: '审查通过',
    rejected: '审查驳回',
    abandoned: '已放弃'
  }
  return statusMap[status] || '未知状态'
}
const selectStatus = (status) => {
  currentStatus.value = status
}
  currentStatus.value = status;
  resetAndLoad();
};
const handleSearch = () => {
  console.log('搜索关键词:', searchKeyword.value)
}
  resetAndLoad();
};
const resetFilters = () => {
  currentStatus.value = 'all'
  searchKeyword.value = ''
}
  currentStatus.value = "all";
  searchKeyword.value = "";
  resetAndLoad();
};
// 构建查询参数
const buildQueryParams = () => {
  const params = {
    pageNum: pageNum.value,
    pageSize: pageSize.value,
    // receiveStatus: "1,2,3,4,5",
  };
  // 添加搜索关键词
  if (searchKeyword.value) {
    params.name = searchKeyword.value;
  }
  // 添加状态筛选
  if (currentStatus.value !== "all") {
    switch (currentStatus.value) {
      case "pending":
        // 待审查:expertconclusion为空
        params.expertconclusion = null;
        break;
      case "approved":
        params.expertconclusion = 1; // 同意
        break;
      case "rejected":
        params.expertconclusion = 2; // 不同意
        break;
      case "abandoned":
        params.expertconclusion = 3; // 放弃
        break;
    }
  }
  return params;
};
// 重置并加载
const resetAndLoad = () => {
  pageNum.value = 1;
  hasMore.value = true;
  loadCaseData();
};
// 下拉刷新
const onRefresh = async () => {
  refreshing.value = true
  setTimeout(() => {
    refreshing.value = false
    loadInitialData()
  }, 1000)
}
  refreshing.value = true;
  await resetAndLoad();
  refreshing.value = false;
};
// 上拉加载更多
const onLoadMore = async () => {
  if (!hasMore.value || loading.value) return
  loading.value = true
  setTimeout(() => {
    loading.value = false
  }, 500)
}
  if (!hasMore.value || loading.value) return;
  pageNum.value++;
  await loadCaseData(true);
};
// 统计
const stateTotal = async () => {
  const resTotal = await uni.$uapi.get(
    `/project/ethicalreviewopinions/stateTotal`,
  );
  if (resTotal.code == 200)
    // 更新统计数据
    stats.value = resTotal.data[0];
};
// 加载案例数据
const loadCaseData = async (isLoadMore = false) => {
  if (loading.value) return;
  loading.value = true;
  try {
    const params = buildQueryParams();
    const res = await uni.$uapi.get(
      `/project/ethicalreviewopinions/listnew`,
      params,
    );
    console.log(res, "11");
    if (res.code == 200) {
      const list = res.rows || [];
      const totalCount = res.total || 0;
      if (isLoadMore) {
        reviewList.value = [...reviewList.value, ...list];
      } else {
        reviewList.value = list;
      }
      console.log(reviewList.value, "reviewList.value");
      total.value = totalCount;
      hasMore.value = reviewList.value.length < totalCount;
    } else {
      uni.showToast({
        title: res.msg || "加载失败",
        icon: "none",
      });
    }
  } catch (error) {
    console.error("加载案例数据失败:", error);
    uni.showToast({
      title: "数据加载失败,请重试",
      icon: "none",
    });
  } finally {
    loading.value = false;
    uni.stopPullDownRefresh();
  }
};
// 查看详情
const viewDetail = (review) => {
  uni.navigateTo({
    url: `/pages/ethicalReview/ethicalInfo?id=${review.id}&status=${review.status}`
  })
}
    url: `/pages/ethicalReview/ethicalInfo?fcid=${
      review.fcid
    }&type=review&status=${review.expertconclusion || "pending"}&id=${
      review.id
    }`,
  });
};
const downloadReport = (review) => {
  uni.showToast({
    title: '开始下载审查报告',
    icon: 'success'
  })
}
// 下载报告
const downloadReport = async (review) => {
  if (!review.conclusionannex) {
    uni.showToast({
      title: "暂无报告可下载",
      icon: "none",
    });
    return;
  }
  try {
    const annexes = review.conclusionannex
      .split(";")
      .filter((item) => item.trim());
    if (annexes.length == 0) {
      uni.showToast({
        title: "暂无报告可下载",
        icon: "none",
      });
      return;
    }
    uni.showLoading({
      title: "下载中...",
      mask: true,
    });
    // 下载第一个附件
    const fileUrl = annexes[0];
    const downloadTask = uni.downloadFile({
      url: fileUrl,
      success: (downloadRes) => {
        if (downloadRes.statusCode == 200) {
          const tempFilePath = downloadRes.tempFilePath;
          // 保存到本地
          uni.saveFile({
            tempFilePath: tempFilePath,
            success: (saveRes) => {
              uni.hideLoading();
              uni.showToast({
                title: "下载成功",
                icon: "success",
                duration: 2000,
              });
              // 在微信小程序中可以打开文件
              if (uni.getSystemInfoSync().platform == "weixin") {
                uni.openDocument({
                  filePath: saveRes.savedFilePath,
                  showMenu: true,
                  success: () => {
                    console.log("打开文档成功");
                  },
                  fail: (err) => {
                    console.error("打开文档失败", err);
                  },
                });
              }
            },
            fail: (saveErr) => {
              uni.hideLoading();
              uni.showToast({
                title: "保存文件失败",
                icon: "error",
                duration: 2000,
              });
            },
          });
        } else {
          uni.hideLoading();
          uni.showToast({
            title: "下载失败",
            icon: "error",
            duration: 2000,
          });
        }
      },
      fail: (err) => {
        uni.hideLoading();
        uni.showToast({
          title: "下载失败",
          icon: "error",
          duration: 2000,
        });
        console.error("下载文件失败:", err);
      },
    });
    // 监听下载进度
    downloadTask.onProgressUpdate((res) => {
      console.log("下载进度:", res.progress);
    });
  } catch (error) {
    uni.hideLoading();
    console.error("下载报告失败:", error);
    uni.showToast({
      title: "下载失败",
      icon: "error",
      duration: 2000,
    });
  }
};
// 提起申诉
const submitAppeal = (review) => {
  uni.navigateTo({
    url: `/pages/ethics/appeal?id=${review.id}`
  })
}
    url: `/pages/ethics/appeal?id=${review.fcid || review.id}&caseNo=${
      review.caseNo || ""
    }&name=${review.name || ""}`,
  });
};
const restartReview = (review) => {
// 重新开始
const restartReview = async (review) => {
  uni.showModal({
    title: '重新开始审查',
    content: '确定要重新开始这个审查流程吗?',
    success: (res) => {
    title: "重新开始审查",
    content: "确定要重新开始这个审查流程吗?",
    success: async (res) => {
      if (res.confirm) {
        uni.showToast({
          title: '审查已重新开始',
          icon: 'success'
        })
        try {
          uni.showLoading({
            title: "处理中...",
            mask: true,
          });
          const response = await uni.$uapi.put(
            `/project/ethicalreviewopinions/reset/${review.fcid || review.id}`,
          );
          uni.hideLoading();
          if (response.code == 200) {
            uni.showToast({
              title: "审查已重新开始",
              icon: "success",
              duration: 2000,
            });
            // 重新加载数据
            resetAndLoad();
          } else {
            uni.showToast({
              title: response.msg || "操作失败",
              icon: "none",
              duration: 2000,
            });
          }
        } catch (error) {
          uni.hideLoading();
          console.error("重新开始审查失败:", error);
          uni.showToast({
            title: "操作失败",
            icon: "error",
            duration: 2000,
          });
        }
      }
    },
  });
};
// 导出审查数据
const exportReviews = async () => {
  try {
    uni.showLoading({
      title: "导出中...",
      mask: true,
    });
    const params = buildQueryParams();
    delete params.pageNum;
    delete params.pageSize;
    const res = await uni.$uapi.get(
      `/project/ethicalreviewopinions/export`,
      params,
    );
    uni.hideLoading();
    if (res.code == 200) {
      const fileUrl = res.data || res.url;
      if (fileUrl) {
        uni.showToast({
          title: "导出成功",
          icon: "success",
          duration: 2000,
        });
        // 在新窗口中打开下载链接
        window.open(fileUrl, "_blank");
      } else {
        uni.showToast({
          title: "导出文件获取失败",
          icon: "none",
          duration: 2000,
        });
      }
    } else {
      uni.showToast({
        title: res.msg || "导出失败",
        icon: "none",
        duration: 2000,
      });
    }
  })
}
  } catch (error) {
    uni.hideLoading();
    console.error("导出数据失败:", error);
    uni.showToast({
      title: "导出失败",
      icon: "error",
      duration: 2000,
    });
  }
};
// 生命周期
onLoad(() => {
  loadInitialData()
})
onLoad(async () => {
  // 获取字典数据
  dict.value = await useDict(
    "sys_IDType",
    "sys_user_sex",
    "sys_Nation",
    "sys_BloodType",
    "sys_Infectious",
    "sys_AgeUnit",
    "ReviewForm_status",
    "sys_ethical",
    "expert_Conclusion",
  );
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
  }
}
  // 加载数据
  await loadCaseData();
  await stateTotal();
});
onShow(() => {
  // 页面显示时刷新数据
  resetAndLoad();
});
// 监听下拉刷新
onPullDownRefresh(() => {
  onRefresh();
});
// 监听上拉触底
onReachBottom(() => {
  onLoadMore();
});
</script>
<style lang="scss" scoped>
@@ -398,7 +721,7 @@
  padding: 20rpx;
  .stats-card {
    background: linear-gradient(135deg, #747CF9, #9B7CF9);
    background: linear-gradient(135deg, #747cf9, #9b7cf9);
    border-radius: 16rpx;
    padding: 40rpx 20rpx;
    display: flex;
@@ -441,28 +764,31 @@
    .status-filter {
      display: flex;
      margin-bottom: 20rpx;
      flex-wrap: wrap;
      text {
        flex: 1;
        // min-width: 120rpx;
        text-align: center;
        font-size: 26rpx;
        color: #606266;
        padding: 16rpx 0;
        position: relative;
        margin: 0 8rpx;
        &.active {
          color: #747CF9;
          color: #747cf9;
          font-weight: 500;
          &::after {
            content: '';
            content: "";
            position: absolute;
            left: 50%;
            bottom: 0;
            transform: translateX(-50%);
            width: 40rpx;
            height: 4rpx;
            background: #747CF9;
            background: #747cf9;
            border-radius: 2rpx;
          }
        }
@@ -491,7 +817,7 @@
          align-items: center;
          .hospital-badge {
            background: linear-gradient(135deg, #747CF9, #9B7CF9);
            background: linear-gradient(135deg, #747cf9, #9b7cf9);
            width: 64rpx;
            height: 64rpx;
            border-radius: 12rpx;
@@ -508,20 +834,29 @@
              font-weight: 600;
              display: block;
              margin-bottom: 4rpx;
              max-width: 300rpx;
              overflow: hidden;
              text-overflow: ellipsis;
              white-space: nowrap;
            }
            .hospital-no {
              font-size: 26rpx;
              color: #909399;
              margin-right: 16rpx;
              background: #f5f5f5;
              padding: 4rpx 12rpx;
              border-radius: 8rpx;
            }
            .expert-type {
              font-size: 22rpx;
              color: #747CF9;
              color: #747cf9;
              background: #f0f2ff;
              padding: 4rpx 12rpx;
              border-radius: 12rpx;
              margin-top: 4rpx;
              display: inline-block;
            }
          }
        }
@@ -531,6 +866,7 @@
          border-radius: 20rpx;
          font-size: 24rpx;
          font-weight: 500;
          white-space: nowrap;
          &.approved {
            background: #f6ffed;
@@ -545,6 +881,16 @@
          &.abandoned {
            background: #f5f5f5;
            color: #8c8c8c;
          }
          &.pending {
            background: #e6f7ff;
            color: #1890ff;
          }
          &.unknown {
            background: #f5f5f5;
            color: #bfbfbf;
          }
        }
      }
@@ -569,6 +915,10 @@
              font-size: 26rpx;
              color: #303133;
              font-weight: 500;
              display: block;
              overflow: hidden;
              text-overflow: ellipsis;
              white-space: nowrap;
            }
          }
        }
@@ -588,6 +938,9 @@
          .detail-text {
            font-size: 24rpx;
            color: #606266;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
          }
        }
      }
@@ -602,6 +955,7 @@
          font-size: 24rpx;
          color: #52c41a;
          font-weight: 500;
          margin-right: 8rpx;
        }
        .conclusion-content {
@@ -610,19 +964,20 @@
        }
      }
      .abandon-reason {
        background: #f5f5f5;
      .opinion-section {
        background: #e6f7ff;
        border-radius: 8rpx;
        padding: 20rpx;
        margin-bottom: 20rpx;
        .reason-label {
        .opinion-label {
          font-size: 24rpx;
          color: #8c8c8c;
          color: #1890ff;
          font-weight: 500;
          margin-right: 8rpx;
        }
        .reason-content {
        .opinion-content {
          font-size: 24rpx;
          color: #303133;
        }
@@ -630,11 +985,13 @@
      .action-buttons {
        display: flex;
        justify-content: space-between;
        justify-content: space-between;
        gap: 16rpx;
        flex-wrap: wrap;
        .action-btn {
          flex: 1;
          min-width: 200rpx;
          height: 64rpx;
          border: none;
          border-radius: 32rpx;
@@ -644,10 +1001,11 @@
          align-items: center;
          justify-content: center;
          gap: 6rpx;
          margin-bottom: 8rpx;
          &.detail-btn {
            background: #f5f5f5;
            color: #747CF9;
            color: #747cf9;
          }
          &.download-btn {
@@ -664,21 +1022,11 @@
          &.restart-btn {
            background: #f0f2ff;
            color: #747CF9;
            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 {
@@ -699,13 +1047,20 @@
      }
      .empty-action {
        background: linear-gradient(135deg, #747CF9, #9B7CF9);
        background: linear-gradient(135deg, #747cf9, #9b7cf9);
        color: #fff;
        border: none;
        border-radius: 32rpx;
        padding: 16rpx 32rpx;
        font-size: 28rpx;
      }
    }
    .load-complete {
      text-align: center;
      padding: 32rpx;
      color: #909399;
      font-size: 24rpx;
    }
  }
}
@@ -715,14 +1070,41 @@
  .ethics-review-list {
    padding: 20rpx;
    .stats-card {
      padding: 30rpx 15rpx;
      .stat-item {
        .count {
          font-size: 30rpx;
        }
        .label {
          font-size: 22rpx;
        }
      }
    }
    .review-item .basic-info .info-row {
      grid-template-columns: 1fr;
      gap: 16rpx;
    }
    .review-header {
      flex-direction: column;
      align-items: flex-start !important;
      .status-tag {
        margin-top: 16rpx;
      }
    }
    .action-buttons {
      flex-direction: column;
      .action-btn {
        min-width: 100% !important;
      }
    }
  }
}
</style>
</style>