From 4d9da000fbe74d344e0e4580b138e79d4ad98ede Mon Sep 17 00:00:00 2001
From: WXL <wl_5969728@163.com>
Date: 星期一, 01 六月 2026 11:07:14 +0800
Subject: [PATCH] 维护

---
 pages/ethicalReview/index.vue |  866 +++++++++++++++++++++++++++++++++++++++++----------------
 1 files changed, 624 insertions(+), 242 deletions(-)

diff --git a/pages/ethicalReview/index.vue b/pages/ethicalReview/index.vue
index ecc7182..139dc6f 100644
--- a/pages/ethicalReview/index.vue
+++ b/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>
\ No newline at end of file
+</style>

--
Gitblit v1.9.3