From c4599aa279fd2f90b741608392e056fbae2282d5 Mon Sep 17 00:00:00 2001
From: WXL <wl_5969728@163.com>
Date: 星期四, 20 八月 2026 17:16:54 +0800
Subject: [PATCH] 各表单布局调整

---
 src/views/business/course/components/EthicalReviewStage.vue | 2470 +++++++++++++++++++++++++++++++++++++++++++++++++++++++---
 1 files changed, 2,311 insertions(+), 159 deletions(-)

diff --git a/src/views/business/course/components/EthicalReviewStage.vue b/src/views/business/course/components/EthicalReviewStage.vue
index 47e4877..768af02 100644
--- a/src/views/business/course/components/EthicalReviewStage.vue
+++ b/src/views/business/course/components/EthicalReviewStage.vue
@@ -1,206 +1,2358 @@
 <template>
-  <base-stage :stage-data="stageData" :case-info="caseInfo">
-    <template #header>
-      <el-alert
-        title="浼︾悊瀹℃煡闃舵"
-        :type="getAlertType()"
-        :description="getAlertDescription()"
-        show-icon
-        :closable="false"
-      />
-    </template>
+  <div class="ethics-review-detail">
+    <!-- <case-basic-info :case-id="caseId" :show-attachment="true":column="3" /> -->
 
-    <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>
+    <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-descriptions title="鍩烘湰淇℃伅" :column="descriptionColumn" border size="small">
+        <el-descriptions-item label="鍙戣捣涓婚">{{ form.initiateTheme || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="鍙戣捣浜�">{{ form.initiatePerson || '-' }}</el-descriptions-item>
+      </el-descriptions>
+
+      <el-descriptions title="瀹℃煡缁撹" :column="descriptionColumn" border size="small" style="margin-top: 16px;">
+        <el-descriptions-item label="浼︾悊瀹℃煡缁撹">{{ expertConclusionLabel }}</el-descriptions-item>
+        <el-descriptions-item label="浼︾悊瀹℃煡缁撹鏃堕棿">{{ form.expertTime || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="瀹℃煡鎰忚" :span="descriptionColumn">{{ form.expertOpinion || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="澶囨敞" :span="descriptionColumn">{{ form.remark || '-' }}</el-descriptions-item>
+      </el-descriptions>
+    </el-card>
+
+    <!-- 鐩稿叧闄勪欢 -->
+    <el-card class="attachment-card">
+      <div slot="header" class="clearfix">
+        <span class="detail-title">鐩稿叧闄勪欢</span>
+      </div>
+
+      <!-- 闄勪欢鍒楄〃 -->
+      <div
+        class="attachment-list"
+        v-if="form.annexfilesList && form.annexfilesList.length > 0"
+      >
+        <div class="list-title">
+          宸蹭笂浼犻檮浠� ({{ form.annexfilesList.length }})
+        </div>
+        <el-table :data="form.annexfilesList" style="width: 100%" size="small">
+          <el-table-column label="鏂囦欢鍚�" min-width="200">
+            <template slot-scope="scope">
+              <i
+                class="el-icon-document"
+                style="margin-right: 8px; color: #409EFF;"
+              ></i>
+              <span class="file-name">{{ scope.row.fileName }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column label="鏂囦欢绫诲瀷" width="100">
+            <template slot-scope="scope">
+              <el-tag size="small">{{
+                getFileType(scope.row.fileName)
+              }}</el-tag>
+            </template>
+          </el-table-column>
+          <el-table-column label="鍒涘缓鏃堕棿" width="160">
+            <template slot-scope="scope">
+              <span>{{ formatDateTime(scope.row.createTime) }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column label="鎿嶄綔" width="180">
+            <template slot-scope="scope">
+              <el-button
+                size="mini"
+                type="primary"
+                @click="handlePreview(scope.row)"
+              >
+                棰勮
+              </el-button>
+              <el-button
+                size="mini"
+                type="success"
+                @click="handleDownload(scope.row)"
+              >
+                涓嬭浇
+              </el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+
+      <!-- 绌虹姸鎬� -->
+      <div
+        v-if="!form.annexfilesList || form.annexfilesList.length == 0"
+        class="empty-attachment"
+      >
+        <i
+          class="el-icon-folder-opened"
+          style="font-size: 60px; color: #C0C4CC; margin-bottom: 20px;"
+        ></i>
+        <p style="color: #909399; font-size: 14px;">鏆傛棤闄勪欢</p>
+      </div>
+    </el-card>
+
+    <!-- 涓撳瀹℃煡鎯呭喌 -->
+    <el-card class="expert-card">
+      <div slot="header" class="clearfix">
+        <span class="detail-title">涓撳瀹℃煡鎯呭喌</span>
+      </div>
+
+      <!-- 涓撳缁熻淇℃伅 -->
+      <div
+        class="expert-stats"
+        style="margin-top: 20px; padding: 15px; background: #f5f7fa; border-radius: 4px;"
+      >
+        <el-row :gutter="20">
+          <el-col :span="6">
+            <div class="stat-item">
+              <span class="stat-label">鏅�氫笓瀹�:</span>
+              <span class="stat-value">{{ normalExpertsCount }}浜�</span>
+            </div>
+          </el-col>
+          <el-col :span="6">
+            <div class="stat-item">
+              <span class="stat-label">涓诲涓撳:</span>
+              <span class="stat-value">{{ chiefExpertsCount }}浜�</span>
+            </div>
+          </el-col>
+          <el-col :span="6">
+            <div class="stat-item">
+              <span class="stat-label">宸插悓鎰�:</span>
+              <span class="stat-value"
+                >{{ approvedExpertsCount }}/{{ totalExpertsCount }}</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="ethicalreviewopinionsList"
+        v-loading="expertLoading"
+        style="width: 100%"
+        height="600"
+        :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">
-            <el-tag :type="scope.row.vote === '鍚屾剰' ? 'success' : 'danger'">
-              {{ scope.row.vote }}
+            <span
+              class="expert-name-link"
+              @click="handleViewExpertHistory(scope.row)"
+            >
+              {{ scope.row.expertname }}
+            </span>
+            <el-tag
+              v-if="scope.row.expertType == '1'"
+              size="mini"
+              type="danger"
+              style="margin-left: 5px;"
+              >涓诲</el-tag
+            >
+          </template>
+        </el-table-column>
+
+        <el-table-column label="涓撳缂栧彿" width="120" align="center">
+          <template slot-scope="scope">
+            <span>{{ scope.row.expertNo || "-" }}</span>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="涓撳绫诲瀷" width="100" align="center">
+          <template slot-scope="scope">
+            <span
+              :class="
+                scope.row.expertType == '1' ? 'chief-expert' : 'normal-expert'
+              "
+            >
+              {{ getExpertTypeText(scope.row.expertType) }}
+            </span>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="瀹℃煡鐘舵��" width="100" align="center">
+          <template slot-scope="scope">
+            <el-tag
+              :type="getReviewStatusFilter(scope.row.receiveStatus)"
+              size="small"
+            >
+              {{ getReviewStatusText(scope.row.receiveStatus) }}
             </el-tag>
           </template>
         </el-table-column>
-        <el-table-column label="瀹℃煡鏃堕棿" width="160">
+
+        <el-table-column label="涓撳缁撹" width="120" align="center">
           <template slot-scope="scope">
-            {{ formatTime(scope.row.reviewTime) }}
+            <el-tag
+              v-if="scope.row.expertconclusion"
+              :type="getConclusionFilter(scope.row.expertconclusion)"
+              size="small"
+            >
+              {{ getConclusionText(scope.row.expertconclusion) }}
+            </el-tag>
+            <span v-else class="no-data">-</span>
           </template>
         </el-table-column>
+        <!-- 鍦�"瀹℃煡鏃堕棿"鍒楀悗闈㈡坊鍔�"鎵嬬闄勪欢"鍒� -->
+        <el-table-column label="鎵嬬闄勪欢" width="120" align="center">
+          <template slot-scope="scope">
+            <template v-if="scope.row.sigin">
+              <!-- 鏈夌鍚嶏紝鏄剧ず鍙偣鍑婚瑙堢殑鍥剧墖 -->
+              <el-button
+                type="text"
+                size="mini"
+                @click="handlePreviewSignature(scope.row.sigin)"
+                class="signature-preview-btn"
+              >
+                <i class="el-icon-picture" style="margin-right: 4px;"></i>
+                鏌ョ湅绛惧悕
+              </el-button>
+            </template>
+            <template v-else>
+              <!-- 鏃犵鍚嶏紝鏄剧ず鏂滄潬 -->
+              <span class="no-signature">/</span>
+            </template>
+          </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="100" align="center">
+          <template slot-scope="scope">
+            <span>{{ scope.row.conclusionorder || "-" }}</span>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="鎴鏃堕棿" width="160" align="center">
+          <template slot-scope="scope">
+            <span>{{
+              scope.row.endTime ? formatDateTime(scope.row.endTime) : "鏈缃�"
+            }}</span>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="瀹℃煡鏃堕棿" width="160" align="center">
+          <template slot-scope="scope">
+            <span>{{
+              scope.row.conclusiontime
+                ? formatDateTime(scope.row.conclusiontime)
+                : "鏈鏌�"
+            }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="鍙戦�佹椂闂�" width="160" align="center">
+          <template slot-scope="scope">
+            <span>{{
+              scope.row.startTime
+                ? formatDateTime(scope.row.startTime)
+                : "鏈彂閫�"
+            }}</span>
+          </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-dialog
+      title="涓撳鍘嗗彶瀹℃壒鎯呭喌"
+      :visible.sync="expertHistoryDialogVisible"
+      width="600px"
+    >
+      <div v-loading="expertHistoryLoading">
+        <div v-if="expertHistoryData" style="padding: 20px;">
+          <el-row :gutter="20" style="margin-bottom: 20px;">
+            <el-col :span="12">
+              <div class="history-stat-item">
+                <div class="history-stat-label">涓撳濮撳悕</div>
+                <div class="history-stat-value">
+                  {{ currentExpertInfo.expertname || "-" }}
+                </div>
+              </div>
+            </el-col>
+            <el-col :span="12">
+              <div class="history-stat-item">
+                <div class="history-stat-label">涓撳缂栧彿</div>
+                <div class="history-stat-value">
+                  {{ currentExpertInfo.expertNo || "-" }}
+                </div>
+              </div>
+            </el-col>
+          </el-row>
+
+          <el-divider></el-divider>
+
+          <el-row :gutter="20">
+            <el-col :span="12">
+              <div class="history-stat-item">
+                <div class="history-stat-label">鎬诲鎵规暟閲�</div>
+                <div
+                  class="history-stat-value"
+                  style="font-size: 24px; color: #409EFF;"
+                >
+                  {{ expertHistoryData.count || 0 }}
+                </div>
+              </div>
+            </el-col>
+            <el-col :span="12">
+              <div class="history-stat-item">
+                <div class="history-stat-label">宸叉帴鏀舵暟閲�</div>
+                <div class="history-stat-value">
+                  {{ expertHistoryData.acceptCount || 0 }}
+                </div>
+              </div>
+            </el-col>
+          </el-row>
+
+          <el-row :gutter="20" style="margin-top: 20px;">
+            <el-col :span="12">
+              <div class="history-stat-item">
+                <div class="history-stat-label">鏈帴鏀舵暟閲�</div>
+                <div class="history-stat-value">
+                  {{ expertHistoryData.notAcceptCount || 0 }}
+                </div>
+              </div>
+            </el-col>
+            <el-col :span="12">
+              <div class="history-stat-item">
+                <div class="history-stat-label">鏈夋剰瑙佹暟閲�</div>
+                <div class="history-stat-value">
+                  {{ expertHistoryData.opinionCount || 0 }}
+                </div>
+              </div>
+            </el-col>
+          </el-row>
+
+          <el-row :gutter="20" style="margin-top: 20px;">
+            <el-col :span="12">
+              <div class="history-stat-item">
+                <div class="history-stat-label">鏃犳剰瑙佹暟閲�</div>
+                <div class="history-stat-value">
+                  {{ expertHistoryData.notOpinionCount || 0 }}
+                </div>
+              </div>
+            </el-col>
+            <el-col :span="12">
+              <div class="history-stat-item">
+                <div class="history-stat-label">鏈夐檮浠舵暟閲�</div>
+                <div class="history-stat-value">
+                  {{ expertHistoryData.annexCount || 0 }}
+                </div>
+              </div>
+            </el-col>
+          </el-row>
+
+          <el-row :gutter="20" style="margin-top: 20px;">
+            <el-col :span="12">
+              <div class="history-stat-item">
+                <div class="history-stat-label">鏃犻檮浠舵暟閲�</div>
+                <div class="history-stat-value">
+                  {{ expertHistoryData.notAnnexCount || 0 }}
+                </div>
+              </div>
+            </el-col>
+          </el-row>
+        </div>
+        <div v-else style="text-align: center; padding: 40px 0;">
+          <i
+            class="el-icon-info"
+            style="font-size: 60px; color: #C0C4CC; margin-bottom: 20px;"
+          ></i>
+          <p style="color: #909399; font-size: 14px;">鏆傛棤鍘嗗彶瀹℃壒鏁版嵁</p>
         </div>
       </div>
-    </el-card>
-  </base-stage>
+      <div slot="footer">
+        <el-button @click="expertHistoryDialogVisible = false">鍏抽棴</el-button>
+      </div>
+    </el-dialog>
+
+    <!-- 鏂囦欢棰勮寮圭獥 -->
+    <FilePreviewDialog
+      :visible="previewVisible"
+      :file="currentPreviewFile"
+      @close="previewVisible = false"
+      @download="handleDownload"
+    />
+  </div>
 </template>
 
 <script>
-import BaseStage from './BaseStage.vue';
+import { getToken } from "@/utils/auth";
+import {
+  reviewinitiateBaseInfoList,
+  ethicalreviewedit,
+  ethicalreviewadd,
+  ethicalreviewInfo,
+  ethicalreExpertTotal,
+  sendNotification,
+  sendcall
+} from "@/api/businessApi";
+import { listExternalperson } from "@/api/project/externalperson";
+import CaseBasicInfo from "@/components/CaseBasicInfo";
+import FilePreviewDialog from "@/components/FilePreviewDialog";
+import dayjs from "dayjs";
+import readonlyDescriptionsMixin from "./readonlyDescriptionsMixin";
 
 export default {
-  name: 'EthicalReviewStage',
-  components: { BaseStage },
+  name: "EthicsReviewDetail",
+  components: { CaseBasicInfo, FilePreviewDialog },
+  mixins: [readonlyDescriptionsMixin],
+  dicts: ["sys_user_sex", "sys_ethical", "Review_status"],
   props: {
-    stageData: {
-      type: Object,
-      default: () => ({})
-    },
-    caseInfo: {
-      type: Object,
-      default: () => ({})
+    infoid: {
+      type: String,
+      default: true
     }
   },
   data() {
     return {
-      reviewCommittee: {
-        name: '鍖婚櫌浼︾悊瀹℃煡濮斿憳浼�',
-        meetingTime: '2023-12-03 15:20:00',
-        members: ['寮犳暀鎺�', '鏉庝富浠�', '鐜嬪尰鐢�', '璧靛鍛�', '閽变笓瀹�'],
-        conclusion: true,
-        chairman: '寮犳暀鎺�'
+      // 椤甸潰妯″紡
+      isEdit: false,
+      // 鍩烘湰淇℃伅
+      id: undefined,
+      caseId: null,
+      caseNo: "",
+
+      // 琛ㄥ崟鏁版嵁
+      form: {
+        // 鍩虹淇℃伅
+        id: undefined,
+        infoid: undefined,
+        caseNo: "",
+        initiateTheme: "",
+        initiatePerson: "",
+
+        // 鐘舵�佸拰鏃堕棿
+        status: "0", // 0:寰呭鏌�, 1:瀹℃煡涓�, 2:瀹℃煡涓, 3:瀹℃煡瀹屾垚
+        startTime: "",
+        cutOffTime: "",
+        endTime: "",
+
+        // 涓撳淇℃伅
+        expertName: "",
+        expertNo: "",
+        expertType: "0",
+        expertConclusion: "",
+        expertOpinion: "",
+        expertTime: "",
+        orderNo: 1,
+
+        // 澶囨敞
+        remark: "",
+
+        // 闄勪欢淇℃伅
+        annexfilesList: [],
+        filePatch: "",
+
+        // 涓撳瀹℃煡鎰忚鍒楄〃
+        ethicalreviewopinionsList: [],
+
+        // 绯荤粺瀛楁
+        createBy: "",
+        createTime: "",
+        updateBy: "",
+        updateTime: "",
+        delFlag: "0"
       },
-      reviewProgress: {
-        active: 4,
-        steps: [
+
+      // 琛ㄥ崟楠岃瘉瑙勫垯
+      rules: {
+        initiateTheme: [
+          { required: true, message: "鍙戣捣涓婚涓嶈兘涓虹┖", trigger: "blur" },
           {
-            title: '鏉愭枡鍒濆',
-            description: '鐢宠鏉愭枡瀹屾暣鎬у鏌�',
-            status: 'finish'
-          },
-          {
-            title: '濮斿憳璇勫',
-            description: '鍚勫鍛樼嫭绔嬪鏌�',
-            status: 'finish'
-          },
-          {
-            title: '浼氳璁ㄨ',
-            description: '濮斿憳浼氶泦浣撹璁�',
-            status: 'finish'
-          },
-          {
-            title: '褰㈡垚鍐宠',
-            description: '鎶曠エ褰㈡垚鏈�缁堝喅璁�',
-            status: 'finish'
+            min: 2,
+            max: 100,
+            message: "闀垮害鍦� 2 鍒� 100 涓瓧绗�",
+            trigger: "blur"
           }
+        ],
+        initiatePerson: [
+          { required: true, message: "鍙戣捣浜轰笉鑳戒负绌�", trigger: "blur" }
+        ],
+        status: [
+          { required: true, message: "瀹℃煡鐘舵�佷笉鑳戒负绌�", trigger: "change" }
+        ],
+        expertName: [
+          { max: 50, message: "闀垮害涓嶈兘瓒呰繃 50 涓瓧绗�", trigger: "blur" }
+        ],
+        expertNo: [
+          { max: 50, message: "闀垮害涓嶈兘瓒呰繃 50 涓瓧绗�", trigger: "blur" }
+        ],
+        expertConclusion: [
+          { max: 2, message: "闀垮害涓嶈兘瓒呰繃 2 涓瓧绗�", trigger: "change" }
+        ],
+        remark: [
+          { max: 500, message: "闀垮害涓嶈兘瓒呰繃 500 涓瓧绗�", trigger: "blur" }
         ]
       },
-      reviewComments: [
-        {
-          memberName: '寮犳暀鎺�',
-          specialty: '鍖诲浼︾悊',
-          comment: '鎹愮尞绋嬪簭绗﹀悎浼︾悊瑙勮寖锛屽悓鎰忛�氳繃',
-          vote: '鍚屾剰',
-          reviewTime: '2023-12-03 14:30:00'
-        },
-        {
-          memberName: '鏉庝富浠�',
-          specialty: '涓村簥鍖诲',
-          comment: '鍖荤枟绋嬪簭瑙勮寖锛屾棤浼︾悊闂',
-          vote: '鍚屾剰',
-          reviewTime: '2023-12-03 14:45:00'
-        },
-        {
-          memberName: '鐜嬪尰鐢�',
-          specialty: '娉曞緥鍖诲',
-          comment: '娉曞緥鏂囦欢榻愬叏锛岀▼搴忓悎娉�',
-          vote: '鍚屾剰',
-          reviewTime: '2023-12-03 15:00:00'
-        }
-      ],
-      resolutionContent: '缁忎鸡鐞嗗鏌ュ鍛樹細瀹℃煡锛岃鍣ㄥ畼鎹愮尞妗堜緥绗﹀悎鍖诲浼︾悊瑕佹眰锛屾崘鐚▼搴忚鑼冿紝瀹跺睘鎰忔効鐪熷疄鏈夋晥锛屽悓鎰忚繘琛屽櫒瀹樻崘鐚��'
+      sending: false, // 鍗曚釜鍙戦�佺姸鎬�
+      sendingAll: false, // 鍏ㄥ眬鍙戦�佺姸鎬�
+      sendingProgress: 0, // 鍙戦�佽繘搴�
+      sendingTotal: 0, // 鎬诲彂閫佹暟
+      sendingSuccessCount: 0, // 鎴愬姛鏁�
+      sendingFailCount: 0, // 澶辫触鏁�
+      sendingResults: [], // 鍙戦�佺粨鏋滃垪琛�
+      originalFormData: null, // 鍘熷琛ㄥ崟鏁版嵁
+      originalExpertList: null, // 鍘熷涓撳鍒楄〃
+      originalAttachments: null, // 鍘熷闄勪欢鍒楄〃
+      isDataLoaded: false, // 鏁版嵁鏄惁宸插姞杞�
+      // 淇濆瓨鍔犺浇鐘舵��
+      saveLoading: false,
+      completeLoading: false,
+      suspendLoading: false,
+      endLoading: false,
+      sending: false,
+
+      // 闄勪欢鐩稿叧
+      attachmentFileList: [],
+
+      // 棰勮鐩稿叧
+      previewVisible: false,
+      currentPreviewFile: null,
+
+      // 涓撳瀹℃煡鏁版嵁
+      expertReviews: [],
+      expertLoading: false,
+      attachmentLoading: false,
+
+      // 娣诲姞涓撳瀵硅瘽妗�
+      expertDialogVisible: false,
+      expertList: [],
+      expertListLoading: false,
+      expertSearchQuery: "",
+      filterExpertType: "",
+      expertPage: {
+        pageNum: 1,
+        pageSize: 50
+      },
+      expertTotal: 0,
+      selectedExperts: [],
+
+      // 鍙戦�佸璇濇
+      sendDialogVisible: false,
+      sendForm: {
+        expertType: "normal",
+        expertIds: [],
+        startTime: "",
+        endTime: "",
+        sendType: "0",
+        content: "",
+        url: ""
+      },
+
+      // 涓撳鍘嗗彶瀹℃壒鎯呭喌
+      expertHistoryDialogVisible: false,
+      expertHistoryLoading: false,
+      expertHistoryData: null,
+      currentExpertInfo: {},
+
+      // 褰撳墠鍙戦�佺殑涓撳
+      currentSendExperts: []
     };
   },
+  computed: {
+    // 璁$畻灞炴�э細鑾峰彇涓撳鍒楄〃锛堢‘淇濆搷搴斿紡锛�
+    ethicalreviewopinionsList() {
+      return this.form.ethicalreviewopinionsList || [];
+    },
+
+    // 浼︾悊瀹℃煡缁撹鏂囨湰锛堝彧璇诲睍绀猴級
+    expertConclusionLabel() {
+      const map = { 0: "涓嶅悓鎰�", 1: "鍚屾剰", 2: "瀹℃煡涓�" };
+      return map[this.form.expertConclusion] || "-";
+    },
+
+    // 璁$畻灞炴�э細鏅�氫笓瀹舵暟閲�
+    normalExpertsCount() {
+      return this.ethicalreviewopinionsList.filter(
+        expert => expert.expertType == "0"
+      ).length;
+    },
+
+    // 璁$畻灞炴�э細涓诲涓撳鏁伴噺
+    chiefExpertsCount() {
+      return this.ethicalreviewopinionsList.filter(
+        expert => expert.expertType == "1"
+      ).length;
+    },
+
+    // 璁$畻灞炴�э細鎬讳笓瀹舵暟閲�
+    totalExpertsCount() {
+      return this.ethicalreviewopinionsList.length;
+    },
+
+    // 璁$畻灞炴�э細宸插悓鎰忎笓瀹舵暟閲�
+    approvedExpertsCount() {
+      return this.ethicalreviewopinionsList.filter(
+        expert => expert.expertconclusion == "1"
+      ).length;
+    },
+
+    // 璁$畻灞炴�э細鎬讳綋缁撹
+    overallConclusionText() {
+      const total = this.totalExpertsCount;
+      const approved = this.approvedExpertsCount;
+
+      if (total == 0) return "鏈鏌�";
+      if (approved >= Math.ceil(total * 0.7)) {
+        // 瓒呰繃70%鍚屾剰
+        return "閫氳繃";
+      } else if (approved >= Math.ceil(total * 0.5)) {
+        // 瓒呰繃50%鍚屾剰
+        return "淇敼鍚庨�氳繃";
+      } else {
+        return "涓嶉�氳繃";
+      }
+    },
+
+    overallConclusionFilter() {
+      const total = this.totalExpertsCount;
+      const approved = this.approvedExpertsCount;
+
+      if (total == 0) return "info";
+      if (approved >= Math.ceil(total * 0.7)) {
+        return "success";
+      } else if (approved >= Math.ceil(total * 0.5)) {
+        return "warning";
+      } else {
+        return "danger";
+      }
+    },
+
+    // 鍙彂閫佺殑鏅�氫笓瀹�
+    availableNormalExperts() {
+      return this.ethicalreviewopinionsList.filter(
+        expert =>
+          expert.expertType == "0" &&
+          (!expert.receiveStatus ||
+            expert.receiveStatus == "0" ||
+            expert.receiveStatus == "1")
+      );
+    },
+
+    // 鍙彂閫佺殑涓诲涓撳
+    availableChiefExperts() {
+      return this.ethicalreviewopinionsList.filter(
+        expert =>
+          expert.expertType == "1" &&
+          (!expert.receiveStatus ||
+            expert.receiveStatus == "0" ||
+            expert.receiveStatus == "1")
+      );
+    },
+
+    // 鏄惁鍙互鍙戦�佺粰鏅�氫笓瀹�
+    canSendToNormalExperts() {
+      return this.availableNormalExperts.length > 0;
+    },
+
+    // 鏄惁鍙互鍙戦�佺粰涓诲涓撳锛堥渶瑕佽嚦灏�12涓櫘閫氫笓瀹跺悓鎰忥級
+    canSendToChiefExpert() {
+      const normalApprovedCount = this.ethicalreviewopinionsList.filter(
+        expert => expert.expertType == "0" && expert.expertconclusion == "1"
+      ).length;
+      return this.availableChiefExperts.length > 0 && normalApprovedCount >= 12;
+    },
+
+    // 宸插瓨鍦ㄧ殑涓撳缂栧彿鍒楄〃
+    existingExpertNos() {
+      return this.ethicalreviewopinionsList
+        .map(expert => expert.expertNo)
+        .filter(no => no);
+    },
+
+    // 宸插瓨鍦ㄧ殑涓撳濮撳悕鍒楄〃
+    existingExpertNames() {
+      return this.ethicalreviewopinionsList
+        .map(expert => expert.expertname)
+        .filter(name => name);
+    },
+
+    // 杩囨护鍚庣殑涓撳鍒楄〃锛堟帓闄ゅ凡瀛樺湪鐨勪笓瀹讹級
+    filteredExpertList() {
+      if (!this.expertList.length) return [];
+
+      return this.expertList.filter(expert => {
+        // 濡傛灉涓撳鏈夌紪鍙凤紝妫�鏌ョ紪鍙锋槸鍚﹀凡瀛樺湪
+        if (expert.userno && this.existingExpertNos.includes(expert.userno)) {
+          return false;
+        }
+        // 濡傛灉涓撳鏈夊鍚嶏紝妫�鏌ュ鍚嶆槸鍚﹀凡瀛樺湪
+        if (
+          expert.username &&
+          this.existingExpertNames.includes(expert.username)
+        ) {
+          return false;
+        }
+        return true;
+      });
+    },
+
+    // 杩囨护鍚庣殑涓撳鎬绘暟
+    filteredExpertTotal() {
+      return this.filteredExpertList.length;
+    },
+
+    // 褰撳墠鐢ㄦ埛淇℃伅
+    currentUser() {
+      return JSON.parse(sessionStorage.getItem("user") || "{}");
+    },
+
+    // 鍙戦�佸璇濇鏍囬
+    sendDialogTitle() {
+      if (this.sendForm.expertType == "chief") {
+        return "鍙戦�佷富濮斾笓瀹跺鏌�";
+      } else if (this.sendForm.expertType == "normal") {
+        return "鍙戦�佹櫘閫氫笓瀹跺鏌�";
+      } else {
+        return "鍙戦�佷笓瀹跺鏌�";
+      }
+    }
+  },
+  watch: {
+    // 鐩戝惉琛ㄥ崟涓殑涓撳鍒楄〃鍙樺寲
+    "form.ethicalreviewopinionsList": {
+      handler(newVal) {
+        console.log("涓撳鍒楄〃鍙樺寲:", newVal);
+      },
+      deep: true
+    }
+  },
+  created() {
+    this.infoid = this.infoid;
+    this.caseId = this.infoid;
+    this.getDetail(this.infoid, this.id);
+    // 鐩戝惉璺敱鍙樺寲锛岄槻姝㈢敤鎴风寮�椤甸潰
+    window.addEventListener("beforeunload", this.beforeUnloadHandler);
+  },
+  beforeDestroy() {
+    // 绉婚櫎浜嬩欢鐩戝惉鍣�
+    window.removeEventListener("beforeunload", this.beforeUnloadHandler);
+  },
   methods: {
-    getAlertType() {
-      const status = this.stageData.status;
-      return status === 'completed' ? 'success' :
-             status === 'in_progress' ? 'warning' : 'info';
+    // 鍒濆鍖栨柊澧炴暟鎹�
+    initNewData() {
+      this.form.infoid = this.infoid;
+      this.form.caseNo = this.$route.query.caseNo || "";
+      this.form.initiatePerson = this.currentUser.username || "褰撳墠鐢ㄦ埛";
+      this.form.startTime = new Date()
+        .toISOString()
+        .replace("T", " ")
+        .substring(0, 19);
+      this.form.createBy = this.currentUser.username || "admin";
     },
-    getAlertDescription() {
-      const status = this.stageData.status;
-      return status === 'completed' ? '浼︾悊瀹℃煡宸查�氳繃锛屽彲浠ヨ繘琛屽櫒瀹樺垎閰�' :
-             status === 'in_progress' ? '浼︾悊瀹℃煡娴佺▼姝e湪杩涜涓�' : '绛夊緟寮�濮嬩鸡鐞嗗鏌ユ祦绋�';
+
+    // 鑾峰彇璇︽儏
+    async getDetail(infoid, id) {
+      try {
+        this.expertLoading = true;
+        let response = {};
+        if (id) {
+          response = await ethicalreviewInfo(id);
+        } else if (infoid) {
+          response = await reviewinitiateBaseInfoList({ infoid: infoid });
+          if (response.data[0].id) {
+            this.id = response.data[0].id;
+            response = await ethicalreviewInfo(this.id);
+          }
+        }
+
+        if (
+          !response.data.status ||
+          response.data.status == 0 ||
+          response.data.status == 1
+        ) {
+          response.data.status = "2";
+        }
+        if (response.code == 200) {
+          let detailData = {};
+
+          if (response.data && this.id) {
+            this.form = response.data;
+            // 瑙f瀽 filePatch 瀛楁
+            this.parseFilePatch(this.form.filePatch);
+            this.initAttachmentFileList();
+
+            if (!this.form.ethicalreviewopinionsList) {
+              this.$set(this.form, "ethicalreviewopinionsList", []);
+            }
+          } else if (response.data && this.infoid) {
+            this.form = response.data[0];
+            this.parseFilePatch(this.form.filePatch);
+            this.initAttachmentFileList();
+
+            if (!this.form.ethicalreviewopinionsList) {
+              this.$set(this.form, "ethicalreviewopinionsList", []);
+            }
+          }
+
+          // 淇濆瓨鍘熷鏁版嵁鐢ㄤ簬姣旇緝
+          this.saveOriginalData();
+
+          this.expertReviews = this.form.ethicalreviewopinionsList;
+          this.isDataLoaded = true;
+
+          this.$message.success("鏁版嵁鍔犺浇鎴愬姛");
+        } else {
+          this.$message.error("鑾峰彇璇︽儏澶辫触锛�" + (response.msg || "鏈煡閿欒"));
+        }
+      } catch (error) {
+        console.error("鑾峰彇浼︾悊瀹℃煡璇︽儏澶辫触:", error);
+        this.$message.error("鏁版嵁鍔犺浇澶辫触");
+      } finally {
+        this.expertLoading = false;
+      }
     },
-    handleViewResolution() {
-      this.$message.info('鏌ョ湅浼︾悊瀹℃煡鍐宠鏂囦欢鍔熻兘');
+
+    // 淇濆瓨鍘熷鏁版嵁
+    saveOriginalData() {
+      // 娣辨嫹璐濊〃鍗曟暟鎹�
+      this.originalFormData = JSON.parse(JSON.stringify(this.form));
+
+      // 娣辨嫹璐濅笓瀹跺垪琛�
+      this.originalExpertList = this.form.ethicalreviewopinionsList
+        ? JSON.parse(JSON.stringify(this.form.ethicalreviewopinionsList))
+        : [];
+
+      // 娣辨嫹璐濋檮浠跺垪琛�
+      this.originalAttachments = this.form.annexfilesList
+        ? JSON.parse(JSON.stringify(this.form.annexfilesList))
+        : [];
+    },
+
+    // 瑙f瀽 filePatch 瀛楁
+    parseFilePatch(filePatch) {
+      if (!filePatch) {
+        this.form.annexfilesList = [];
+        return;
+      }
+
+      try {
+        this.form.annexfilesList = JSON.parse(filePatch);
+      } catch (error) {
+        console.error("瑙f瀽 filePatch 瀛楁澶辫触:", error);
+        this.form.annexfilesList = [];
+      }
+    },
+
+    // 鍒濆鍖栭檮浠舵枃浠跺垪琛�
+    initAttachmentFileList() {
+      if (this.form.annexfilesList && this.form.annexfilesList.length > 0) {
+        this.attachmentFileList = this.form.annexfilesList.map(item => ({
+          uid:
+            item.id ||
+            Math.random()
+              .toString(36)
+              .substr(2, 9),
+          name: item.fileName,
+          url: item.path || item.fileUrl,
+          status: "success"
+        }));
+      } else {
+        this.attachmentFileList = [];
+      }
+    },
+
+    // 鏋勫缓 filePatch 瀛楁
+    buildFilePatch() {
+      if (!this.form.annexfilesList || this.form.annexfilesList.length == 0) {
+        return "";
+      }
+      return JSON.stringify(this.form.annexfilesList);
+    },
+
+    // 闄勪欢鍙樺寲澶勭悊
+    handleAttachmentChange(fileList) {
+      this.attachmentFileList = fileList;
+    },
+
+    // 闄勪欢绉婚櫎澶勭悊
+    handleAttachmentRemove(file) {
+      if (file.url) {
+        const index = this.form.annexfilesList.findIndex(
+          item => item.path == file.url || item.fileUrl == file.url
+        );
+        if (index > -1) {
+          this.form.annexfilesList.splice(index, 1);
+        }
+      }
+    },
+
+    // 鎵嬪姩鍒犻櫎闄勪欢
+    handleRemoveAttachment(index) {
+      this.form.annexfilesList.splice(index, 1);
+      this.attachmentFileList.splice(index, 1);
+      this.$message.success("闄勪欢鍒犻櫎鎴愬姛");
+    },
+
+    // 涓婁紶鎴愬姛澶勭悊
+    handleUploadSuccess({ file, fileList, response }) {
+      if (response.code == 200) {
+        const attachmentObj = {
+          fileName: file.name,
+          path: response.data || file.url,
+          fileUrl: response.data || file.url,
+          type: this.getFileExtension(file.name),
+          createTime: dayjs().format("YYYY-MM-DD HH:mm:ss"),
+          infoid: this.infoid,
+          delFlag: 0
+        };
+
+        this.form.annexfilesList.push(attachmentObj);
+        this.$message.success("鏂囦欢涓婁紶鎴愬姛");
+      }
+    },
+
+    // 涓婁紶閿欒澶勭悊
+    handleUploadError({ file, fileList, error }) {
+      console.error("闄勪欢涓婁紶澶辫触:", error);
+      this.$message.error("鏂囦欢涓婁紶澶辫触锛岃閲嶈瘯");
+    },
+
+    // 鏂囦欢棰勮
+    handlePreview(file) {
+      console.log(file, "file");
+
+      this.currentPreviewFile = {
+        fileName: file.fileName,
+        fileUrl: file.path || file.fileUrl,
+        fileType: this.getFileType(file.fileName)
+      };
+      this.previewVisible = true;
+    },
+    // 鏂囦欢棰勮
+    handlePreviewSignature(file) {
+      console.log(file, "file");
+
+      this.currentPreviewFile = {
+        fileName: file,
+        fileUrl: file,
+        fileType: "png"
+      };
+      this.previewVisible = true;
+    },
+
+    // 鏂囦欢涓嬭浇
+    handleDownload(file) {
+      const fileUrl = file.path || file.fileUrl;
+      const fileName = file.fileName;
+
+      if (fileUrl) {
+        const link = document.createElement("a");
+        link.href = fileUrl;
+        link.download = fileName;
+        link.style.display = "none";
+        document.body.appendChild(link);
+        link.click();
+        document.body.removeChild(link);
+        this.$message.success("寮�濮嬩笅杞芥枃浠�");
+      } else {
+        this.$message.warning("鏂囦欢璺緞涓嶅瓨鍦紝鏃犳硶涓嬭浇");
+      }
+    },
+
+    // 鑾峰彇鏂囦欢绫诲瀷
+    getFileType(fileName) {
+      if (!fileName) return "other";
+
+      const extension = fileName
+        .split(".")
+        .pop()
+        .toLowerCase();
+      const imageTypes = ["jpg", "jpeg", "png", "gif", "bmp", "webp"];
+      const pdfTypes = ["pdf"];
+      const officeTypes = ["doc", "docx", "xls", "xlsx", "ppt", "pptx"];
+
+      if (imageTypes.includes(extension)) return "image";
+      if (pdfTypes.includes(extension)) return "pdf";
+      if (officeTypes.includes(extension)) return "office";
+      return "other";
+    },
+
+    // 鑾峰彇鏂囦欢鎵╁睍鍚�
+    getFileExtension(filename) {
+      return filename
+        .split(".")
+        .pop()
+        .toLowerCase();
+    },
+
+    // 鏃ユ湡鏃堕棿鏍煎紡鍖�
+    formatDateTime(dateTime) {
+      if (!dateTime) return "";
+
+      try {
+        const date = new Date(dateTime);
+        if (isNaN(date.getTime())) return dateTime;
+
+        const year = date.getFullYear();
+        const month = String(date.getMonth() + 1).padStart(2, "0");
+        const day = String(date.getDate()).padStart(2, "0");
+        const hours = String(date.getHours()).padStart(2, "0");
+        const minutes = String(date.getMinutes()).padStart(2, "0");
+        const seconds = String(date.getSeconds()).padStart(2, "0");
+
+        return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
+      } catch (error) {
+        return dateTime;
+      }
+    },
+
+    // 鍒ゆ柇鏄惁涓轰富浠诲鍛�
+    getIsChiefExpert(expert) {
+      // 鑱岀О鍖呭惈"涓讳换濮斿憳"鎴栬�卐xpertType涓�"1"
+      return (
+        (expert.title && expert.title.includes("涓讳换濮斿憳")) ||
+        expert.expertType == "1"
+      );
+    },
+
+    // 涓撳绫诲瀷鏂囨湰杞崲
+    getExpertTypeText(type) {
+      return type == "1" ? "涓诲涓撳" : "鏅�氫笓瀹�";
+    },
+
+    // 瀹℃煡鐘舵�佽繃婊ゅ櫒
+    getReviewStatusFilter(status) {
+      const statusMap = {
+        "0": "info", // 寰呮帴鏀�
+        "1": "warning", // 鏈帴鏀�
+        "2": "success", // 宸叉帴鏀�
+        "3": "danger", // 瓒呮椂
+        "4": "danger", // 涓
+        "5": "success" // 瀹屾垚
+      };
+      return statusMap[status] || "info";
+    },
+
+    getReviewStatusText(status) {
+      const statusMap = {
+        "0": "寰呮帴鏀�",
+        "1": "鏈帴鏀�",
+        "2": "宸叉帴鏀�",
+        "3": "瓒呮椂",
+        "4": "涓",
+        "5": "瀹屾垚"
+      };
+      return statusMap[status] || "鏈煡";
+    },
+
+    // 缁撹杩囨护鍣�
+    getConclusionFilter(conclusion) {
+      const conclusionMap = {
+        "1": "success", // 鍚屾剰
+        "2": "warning", // 瀹℃煡涓�
+        "0": "danger" // 涓嶅悓鎰�
+      };
+      return conclusionMap[conclusion] || "info";
+    },
+
+    getConclusionText(conclusion) {
+      const conclusionMap = {
+        "1": "鍚屾剰",
+        "2": "瀹℃煡涓�",
+        "0": "涓嶅悓鎰�"
+      };
+      return conclusionMap[conclusion] || "鏈煡";
+    },
+
+    // 涓撳琛屾牱寮�
+    getExpertRowClassName({ row }) {
+      return row.expertType == "1" ? "chief-expert-row" : "normal-expert-row";
+    },
+
+    // 鑾峰彇涓撳鍞竴鏍囪瘑
+    getExpertKey(expert) {
+      return expert.id || expert.expertNo || expert.expertname;
+    },
+
+    // 涓撳绫诲瀷鍙樻洿澶勭悊
+    handleExpertTypeChange() {
+      if (this.sendForm.expertType == "chief") {
+        // 涓诲涓撳鏃犻渶璁剧疆鎴鏃堕棿
+        this.sendForm.endTime = "";
+      } else {
+        // 鏅�氫笓瀹堕噸缃埅姝㈡椂闂翠负褰撳墠鏃堕棿
+        this.sendForm.endTime = "";
+      }
+    },
+
+    // 璁剧疆鎴鏃堕棿蹇嵎閿�
+    setEndTime(hours) {
+      const now = new Date();
+      const endTime = new Date(now.getTime() + hours * 60 * 60 * 1000);
+      this.sendForm.endTime = endTime
+        .toISOString()
+        .replace("T", " ")
+        .substring(0, 19);
+    },
+
+    // 淇濆瓨淇℃伅
+    async handleSave() {
+      this.$refs.form.validate(async valid => {
+        if (valid) {
+          this.saveLoading = true;
+          // 淇濆瓨娓呯┖id渚夸簬鍚庣鏁翠綋鍒犻櫎鏂板
+          // this.form.ethicalreviewopinionsList.forEach(item => {
+          //   item.id = null;
+          // });
+          try {
+            const submitData = {
+              ...this.form,
+              // 纭繚蹇呰瀛楁
+              infoid: this.infoid,
+              caseNo: this.caseNo,
+              // 鏋勫缓 filePatch 瀛楁
+              filePatch: this.buildFilePatch()
+            };
+
+            let response = null;
+
+            if (submitData.id) {
+              response = await ethicalreviewedit(submitData);
+            } else {
+              response = await ethicalreviewadd(submitData);
+            }
+
+            if (response.code == 200) {
+              this.$message.success("淇濆瓨鎴愬姛");
+              // 淇濆瓨鎴愬姛鍚庢洿鏂板師濮嬫暟鎹�
+              this.saveOriginalData();
+              this.isEdit = false;
+              if (!this.form.id && response.data) {
+                this.form.id = response.data;
+                this.id = this.form.id;
+                // this.$router.replace({
+                //   query: { ...this.$route.query, id: this.form.id }
+                // });
+              }
+              this.refreshPageData();
+            } else {
+              this.$message.error("淇濆瓨澶辫触锛�" + (response.msg || "鏈煡閿欒"));
+            }
+          } catch (error) {
+            console.error("淇濆瓨澶辫触:", error);
+            this.$message.error("淇濆瓨澶辫触锛岃閲嶈瘯");
+          } finally {
+            this.saveLoading = false;
+          }
+        }
+      });
+    },
+
+    // 瀹℃煡瀹屾垚
+    async handleCompleteReview() {
+      this.$confirm("纭畾瑕佸皢鏈浼︾悊瀹℃煡鐘舵�佽涓哄畬鎴愬悧锛�", "鎻愮ず", {
+        confirmButtonText: "纭畾",
+        cancelButtonText: "鍙栨秷",
+        type: "warning"
+      })
+        .then(async () => {
+          this.completeLoading = true;
+          try {
+            const updateData = {
+              ...this.form,
+              status: "3", // 瀹℃煡瀹屾垚
+              endTime: new Date()
+                .toISOString()
+                .replace("T", " ")
+                .substring(0, 19)
+            };
+
+            const response = await ethicalreviewedit(updateData);
+
+            if (response.code == 200) {
+              this.$message.success("瀹℃煡鐘舵�佸凡鏇存柊涓哄畬鎴�");
+              this.form.status = "3";
+              this.form.endTime = updateData.endTime;
+            } else {
+              this.$message.error("鎿嶄綔澶辫触锛�" + (response.msg || "鏈煡閿欒"));
+            }
+          } catch (error) {
+            console.error("鏇存柊瀹℃煡鐘舵�佸け璐�:", error);
+            this.$message.error("鏇存柊瀹℃煡鐘舵�佸け璐�");
+          } finally {
+            this.completeLoading = false;
+          }
+        })
+        .catch(() => {});
+    },
+
+    // 瀹℃煡涓
+    async handleSuspendReview() {
+      this.$confirm(
+        "纭畾瑕佷腑姝㈡湰娆′鸡鐞嗗鏌ュ悧锛熸墍鏈変笓瀹剁殑瀹℃煡鐘舵�佸皢鍙樹负涓銆�",
+        "鎻愮ず",
+        {
+          confirmButtonText: "纭畾",
+          cancelButtonText: "鍙栨秷",
+          type: "warning"
+        }
+      )
+        .then(async () => {
+          this.suspendLoading = true;
+          try {
+            // 鏇存柊鎵�鏈変笓瀹剁殑鎺ユ敹鐘舵�佷负涓
+            if (
+              this.form.ethicalreviewopinionsList &&
+              this.form.ethicalreviewopinionsList.length > 0
+            ) {
+              this.form.ethicalreviewopinionsList.forEach(expert => {
+                expert.receiveStatus = "4"; // 涓鐘舵��
+                expert.updateTime = new Date()
+                  .toISOString()
+                  .replace("T", " ")
+                  .substring(0, 19);
+              });
+            }
+
+            const updateData = {
+              ...this.form,
+              status: "2" // 瀹℃煡涓
+            };
+
+            const response = await ethicalreviewedit(updateData);
+
+            if (response.code == 200) {
+              this.$message.success("瀹℃煡宸蹭腑姝紝鎵�鏈変笓瀹剁姸鎬佸凡鏇存柊");
+              this.form.status = "2";
+            } else {
+              this.$message.error("鎿嶄綔澶辫触锛�" + (response.msg || "鏈煡閿欒"));
+            }
+          } catch (error) {
+            console.error("涓瀹℃煡澶辫触:", error);
+            this.$message.error("涓瀹℃煡澶辫触");
+          } finally {
+            this.suspendLoading = false;
+          }
+        })
+        .catch(() => {});
+    },
+
+    // 缁撴潫瀹℃煡
+    async handleEndReview() {
+      this.$confirm(
+        "纭畾瑕佺粨鏉熸湰娆′鸡鐞嗗鏌ュ悧锛熺粨鏉熷悗灏嗘棤娉曚慨鏀逛笓瀹跺鏌ョ粨鏋溿��",
+        "鎻愮ず",
+        {
+          confirmButtonText: "纭畾",
+          cancelButtonText: "鍙栨秷",
+          type: "warning"
+        }
+      )
+        .then(async () => {
+          this.endLoading = true;
+          try {
+            const updateData = {
+              ...this.form,
+              status: "4", // 瀹℃煡涓
+              endTime: new Date()
+                .toISOString()
+                .replace("T", " ")
+                .substring(0, 19)
+            };
+
+            const response = await ethicalreviewedit(updateData);
+
+            if (response.code == 200) {
+              this.$message.success("瀹℃煡宸茬粨鏉�");
+              this.form.status = "4";
+              this.form.endTime = updateData.endTime;
+            } else {
+              this.$message.error("鎿嶄綔澶辫触锛�" + (response.msg || "鏈煡閿欒"));
+            }
+          } catch (error) {
+            console.error("缁撴潫瀹℃煡澶辫触:", error);
+            this.$message.error("缁撴潫瀹℃煡澶辫触");
+          } finally {
+            this.endLoading = false;
+          }
+        })
+        .catch(() => {});
+    },
+
+    // 鎵撳紑娣诲姞涓撳瀵硅瘽妗�
+    handleAddExpert() {
+      this.expertDialogVisible = true;
+      this.loadExperts();
+    },
+    /**
+     * 鍒锋柊椤甸潰鏁版嵁
+     */
+    async refreshPageData() {
+      try {
+        // 閲嶇疆鏁版嵁鐘舵��
+        this.isDataLoaded = false;
+
+        // 娓呯┖褰撳墠鏁版嵁
+        this.form = {
+          id: undefined,
+          infoid: undefined,
+          caseNo: "",
+          initiateTheme: "",
+          initiatePerson: "",
+          status: "0",
+          startTime: "",
+          cutOffTime: "",
+          endTime: "",
+          expertName: "",
+          expertNo: "",
+          expertType: "0",
+          expertConclusion: "",
+          expertOpinion: "",
+          expertTime: "",
+          orderNo: 1,
+          remark: "",
+          annexfilesList: [],
+          filePatch: "",
+          ethicalreviewopinionsList: [],
+          createBy: "",
+          createTime: "",
+          updateBy: "",
+          updateTime: "",
+          delFlag: "0"
+        };
+
+        this.attachmentFileList = [];
+        this.originalFormData = null;
+        this.originalExpertList = null;
+        this.originalAttachments = null;
+
+        // 閲嶆柊鑾峰彇鏁版嵁
+        if (this.id) {
+          await this.getDetail(this.infoid, this.id);
+        } else if (this.infoid) {
+          await this.getDetail(this.infoid, null);
+        } else {
+          this.$message.warning("鏃犳硶鍒锋柊锛岀己灏戝繀瑕佺殑鍙傛暟");
+        }
+
+        this.$message.success("鏁版嵁鍒锋柊鎴愬姛");
+      } catch (error) {
+        console.error("鍒锋柊鏁版嵁澶辫触:", error);
+        this.$message.error("鍒锋柊鏁版嵁澶辫触锛岃閲嶈瘯");
+      }
+    },
+
+    /**
+     * 澶勭悊椤甸潰鍒锋柊
+     * 妫�鏌ユ槸鍚︽湁鏈繚瀛樻暟鎹紝纭鍚庡埛鏂伴〉闈�
+     */
+    handleRefresh() {
+      // 妫�鏌ユ槸鍚︽湁鏈繚瀛樼殑缂栬緫
+      if (this.hasUnsavedChanges()) {
+        this.$confirm(
+          "褰撳墠鏈夋湭淇濆瓨鐨勬暟鎹紝鍒锋柊椤甸潰灏嗕涪澶辫繖浜涙洿鏀广�傛槸鍚︾户缁埛鏂帮紵",
+          "璀﹀憡",
+          {
+            confirmButtonText: "缁х画鍒锋柊",
+            cancelButtonText: "鍙栨秷",
+            type: "warning",
+            distinguishCancelAndClose: true,
+            beforeClose: (action, instance, done) => {
+              if (action === "confirm") {
+                instance.confirmButtonLoading = true;
+                instance.confirmButtonText = "鍒锋柊涓�...";
+
+                // 寤惰繜鎵ц浠ョ‘淇漊I鏇存柊
+                setTimeout(() => {
+                  done();
+                  instance.confirmButtonLoading = false;
+
+                  // 鐢ㄦ埛纭鍒锋柊
+                  this.refreshPageData();
+                }, 300);
+              } else {
+                this.$message({
+                  type: "info",
+                  message: "宸插彇娑堝埛鏂�"
+                });
+                done();
+              }
+            }
+          }
+        ).catch(action => {
+          if (action === "cancel") {
+            this.$message({
+              type: "info",
+              message: "宸插彇娑堝埛鏂�"
+            });
+          }
+        });
+      } else {
+        // 娌℃湁鏈繚瀛樼殑缂栬緫锛岀洿鎺ュ埛鏂�
+        this.refreshPageData();
+      }
+    },
+    // 妫�鏌ユ槸鍚︽湁鏈繚瀛樼殑鏁版嵁鍙樺寲
+    hasUnsavedChanges() {
+      if (!this.isDataLoaded) {
+        return false; // 鏁版嵁鏈姞杞斤紝鏃犻渶妫�娴�
+      }
+
+      // 1. 妫�鏌ヨ〃鍗曞瓧娈靛彉鍖�
+      const formFieldsChanged = this.checkFormFieldsChanged();
+
+      // 2. 妫�鏌ヤ笓瀹跺垪琛ㄥ彉鍖�
+      const expertListChanged = this.checkExpertListChanged();
+
+      // 3. 妫�鏌ラ檮浠跺垪琛ㄥ彉鍖�
+      const attachmentsChanged = this.checkAttachmentsChanged();
+
+      return formFieldsChanged || expertListChanged || attachmentsChanged;
+    },
+
+    // 妫�鏌ヨ〃鍗曞瓧娈垫槸鍚︽湁鍙樺寲
+    checkFormFieldsChanged() {
+      if (!this.originalFormData) return false;
+
+      const formKeys = [
+        "initiateTheme",
+        "initiatePerson",
+        "status",
+        "expertConclusion",
+        "expertOpinion",
+        "expertTime",
+        "remark"
+      ];
+
+      for (const key of formKeys) {
+        if (this.form[key] !== this.originalFormData[key]) {
+          console.log(
+            `琛ㄥ崟瀛楁鍙樺寲: ${key}`,
+            this.form[key],
+            this.originalFormData[key]
+          );
+          return true;
+        }
+      }
+
+      return false;
+    },
+
+    // 妫�鏌ヤ笓瀹跺垪琛ㄥ彉鍖�
+    checkExpertListChanged() {
+      if (!this.originalExpertList || !this.form.ethicalreviewopinionsList) {
+        return false;
+      }
+
+      const original = this.originalExpertList;
+      const current = this.form.ethicalreviewopinionsList;
+
+      // 1. 妫�鏌ユ暟閲忓彉鍖�
+      if (original.length !== current.length) {
+        console.log("涓撳鏁伴噺鍙樺寲:", original.length, "->", current.length);
+        return true;
+      }
+
+      // 2. 妫�鏌ユ瘡涓笓瀹剁殑鍙樺寲
+      for (let i = 0; i < original.length; i++) {
+        const origExpert = original[i];
+        const currExpert = current[i];
+
+        // 妫�鏌ュ叧閿瓧娈靛彉鍖�
+        const fieldsToCheck = [
+          "expertconclusion",
+          "expertopinion",
+          "receiveStatus",
+          "conclusiontime",
+          "startTime",
+          "endTime",
+          "sendType"
+        ];
+
+        for (const field of fieldsToCheck) {
+          if (origExpert[field] !== currExpert[field]) {
+            console.log(
+              `涓撳${i}鐨�${field}瀛楁鍙樺寲:`,
+              origExpert[field],
+              "->",
+              currExpert[field]
+            );
+            return true;
+          }
+        }
+      }
+
+      return false;
+    },
+
+    // 妫�鏌ラ檮浠跺垪琛ㄥ彉鍖�
+    checkAttachmentsChanged() {
+      if (!this.originalAttachments || !this.form.annexfilesList) {
+        return false;
+      }
+
+      const original = this.originalAttachments;
+      const current = this.form.annexfilesList;
+
+      // 妫�鏌ユ暟閲忓彉鍖�
+      if (original.length !== current.length) {
+        console.log("闄勪欢鏁伴噺鍙樺寲:", original.length, "->", current.length);
+        return true;
+      }
+
+      // 妫�鏌ユ枃浠跺悕鍙樺寲锛堥�氬父闄勪欢涓嶄細淇敼锛屽彧澧炲垹锛�
+      const originalFileNames = original.map(f => f.fileName || f.name).sort();
+      const currentFileNames = current.map(f => f.fileName || f.name).sort();
+
+      for (let i = 0; i < originalFileNames.length; i++) {
+        if (originalFileNames[i] !== currentFileNames[i]) {
+          console.log(
+            "闄勪欢鏂囦欢鍚嶅彉鍖�:",
+            originalFileNames[i],
+            "->",
+            currentFileNames[i]
+          );
+          return true;
+        }
+      }
+
+      return false;
+    },
+
+    // 娴忚鍣ㄧ寮�椤甸潰妫�娴�
+    beforeUnloadHandler(event) {
+      if (this.hasUnsavedChanges()) {
+        const message = "鎮ㄦ湁鏈繚瀛樼殑鏇存敼锛岀‘瀹氳绂诲紑鍚楋紵";
+        event.returnValue = message; // 鏍囧噯鏂瑰紡
+        return message; // 鏌愪簺娴忚鍣ㄩ渶瑕佽繑鍥炲瓧绗︿覆
+      }
+    },
+    // 鍔犺浇涓撳鍒楄〃
+    async loadExperts() {
+      try {
+        this.expertListLoading = true;
+        const params = {
+          usertype: "ethical", // 浼︾悊涓撳
+          pageNum: this.expertPage.pageNum,
+          pageSize: this.expertPage.pageSize
+        };
+
+        if (this.expertSearchQuery) {
+          params.username = this.expertSearchQuery;
+        }
+
+        const response = await listExternalperson(params);
+        if (response.code == 200) {
+          this.expertList = response.rows || [];
+          this.expertTotal = response.total || 0;
+        } else {
+          this.$message.error(
+            "鍔犺浇涓撳鍒楄〃澶辫触锛�" + (response.msg || "鏈煡閿欒")
+          );
+        }
+      } catch (error) {
+        console.error("鍔犺浇涓撳鍒楄〃澶辫触:", error);
+        this.$message.error("鍔犺浇涓撳鍒楄〃澶辫触");
+      } finally {
+        this.expertListLoading = false;
+      }
+    },
+
+    // 鎼滅储涓撳
+    handleSearchExperts() {
+      this.expertPage.pageNum = 1;
+      this.loadExperts();
+    },
+
+    // 閲嶇疆鎼滅储
+    handleResetSearch() {
+      this.expertSearchQuery = "";
+      this.filterExpertType = "";
+      this.expertPage.pageNum = 1;
+      this.loadExperts();
+    },
+
+    // 涓撳閫夋嫨鍙樺寲
+    handleExpertSelectionChange(selection) {
+      this.selectedExperts = selection;
+    },
+
+    // 纭娣诲姞涓撳
+    handleConfirmAddExpert() {
+      if (this.selectedExperts.length == 0) {
+        this.$message.warning("璇烽�夋嫨瑕佹坊鍔犵殑涓撳");
+        return;
+      }
+
+      // 纭繚ethicalreviewopinionsList瀛樺湪
+      if (!this.form.ethicalreviewopinionsList) {
+        this.$set(this.form, "ethicalreviewopinionsList", []);
+      }
+
+      // 娣诲姞涓撳鍒板垪琛�
+      this.selectedExperts.forEach(expert => {
+        // 鍒ゆ柇鏄惁涓轰富浠诲鍛�
+        const isChief = this.getIsChiefExpert(expert);
+
+        const expertReview = {
+          id: undefined,
+          infoid: this.infoid,
+          nitiateId: this.form.id || undefined,
+          caseNo: this.form.caseNo,
+          expertname: expert.username,
+          expertNo: expert.userno,
+          expertType: isChief ? "1" : "0", // 涓讳换濮斿憳璁剧疆涓轰富濮斾笓瀹�
+          deptName: expert.unitname || "",
+          title: expert.title || "",
+          donorno: expert.telephone || "",
+          receiveStatus: "0", // 寰呮帴鏀�
+          expertconclusion: "",
+          expertopinion: "",
+          conclusionannex: "",
+          conclusionorder: this.form.ethicalreviewopinionsList.length + 1,
+          conclusiontime: "",
+          startTime: "",
+          endTime: "",
+          sendType: "",
+          createBy: this.currentUser.username || "admin",
+          createTime: new Date()
+            .toISOString()
+            .replace("T", " ")
+            .substring(0, 19),
+          updateBy: this.currentUser.username || "admin",
+          updateTime: new Date()
+            .toISOString()
+            .replace("T", " ")
+            .substring(0, 19),
+          delFlag: "0"
+        };
+
+        // 浣跨敤push娣诲姞锛岀‘淇濆搷搴斿紡
+        this.form.ethicalreviewopinionsList.push(expertReview);
+      });
+
+      this.$message.success(`鎴愬姛娣诲姞 ${this.selectedExperts.length} 浣嶄笓瀹禶);
+      this.expertDialogVisible = false;
+      this.selectedExperts = [];
+    },
+
+    // 娣诲姞涓撳瀵硅瘽妗嗗叧闂�
+    handleExpertDialogClose() {
+      this.selectedExperts = [];
+      this.expertSearchQuery = "";
+      this.filterExpertType = "";
+      this.expertPage.pageNum = 1;
+    },
+
+    // 椤电爜鍙樺寲
+    handlePageChange(pageNum) {
+      this.expertPage.pageNum = pageNum;
+      this.loadExperts();
+    },
+
+    // 姣忛〉鏉℃暟鍙樺寲
+    handlePageSizeChange(pageSize) {
+      this.expertPage.pageSize = pageSize;
+      this.expertPage.pageNum = 1;
+      this.loadExperts();
+    },
+
+    // 鍙戦�佺粰鏅�氫笓瀹�
+    handleSendToNormalExperts() {
+      if (!this.validateAllIds(this.ethicalreviewopinionsList)) {
+        return;
+      }
+      this.currentSendExperts = this.availableNormalExperts;
+      this.sendForm.expertType = "normal";
+      this.sendForm.endTime = ""; // 閲嶇疆鎴鏃堕棿
+      this.sendDialogVisible = true;
+    },
+
+    // 鍙戦�佺粰涓诲涓撳
+    handleSendToChiefExpert() {
+      if (!this.validateAllIds(this.ethicalreviewopinionsList)) {
+        return;
+      }
+      this.currentSendExperts = this.availableChiefExperts;
+      this.sendForm.expertType = "chief";
+      this.sendForm.endTime = ""; // 涓诲涓撳鏃犻渶鎴鏃堕棿
+      this.sendDialogVisible = true;
+    },
+
+    // 鍙戦�佺粰鍗曚釜涓撳
+    handleSendToExpert(expert) {
+      if (!this.validateAllIds(this.ethicalreviewopinionsList)) {
+        return;
+      }
+      this.currentSendExperts = [expert];
+      this.sendForm.expertType = expert.expertType == "1" ? "chief" : "normal";
+      this.sendForm.endTime = expert.expertType == "1" ? "" : ""; // 涓诲涓撳鏃犻渶鎴鏃堕棿
+      this.sendDialogVisible = true;
+    },
+
+    validateAllIds(arr, idField = "id") {
+      if (!Array.isArray(arr) || arr.length === 0) {
+        this.$message.warning("璇峰厛閫夋嫨涓撳鍒楄〃");
+        return false;
+      }
+
+      const emptyIdItems = arr.filter(item => {
+        const id = item[idField];
+        return (
+          id === undefined ||
+          id === null ||
+          id === "" ||
+          id.toString().trim() === ""
+        );
+      });
+
+      if (emptyIdItems.length > 0) {
+        this.$message.warning(
+          "褰撳墠閫変腑涓撳鍒楄〃鏈夋湭淇濆瓨鏁版嵁锛岃淇濆瓨鍒锋柊鍚庡啀鍙戦��"
+        );
+        return false;
+      }
+
+      return true;
+    },
+    // 鍙戦�佸璇濇鍏抽棴
+    handleSendDialogClose() {
+      this.sendForm = {
+        expertType: "normal",
+        expertIds: [],
+        startTime: "",
+        endTime: "",
+        sendType: "0",
+        content: "",
+        url: ""
+      };
+      this.currentSendExperts = [];
+    },
+
+    // 纭鍙戦��
+    async handleSendConfirm() {
+      if (!this.sendForm.startTime) {
+        this.$message.warning("璇烽�夋嫨鍙戦�佹椂闂�");
+        return;
+      }
+
+      // 鏅�氫笓瀹堕渶瑕佹埅姝㈡椂闂达紝涓诲涓撳涓嶉渶瑕�
+      if (this.sendForm.expertType !== "chief" && !this.sendForm.endTime) {
+        this.$message.warning("璇烽�夋嫨鎴鏃堕棿");
+        return;
+      }
+
+      // if (!this.sendForm.sendType) {
+      //   this.$message.warning("璇烽�夋嫨鍙戦�佹柟寮�");
+      //   return;
+      // }
+
+      // if (!this.sendForm.title) {
+      //   this.$message.warning("璇疯緭鍏ュ彂閫佹爣棰�");
+      //   return;
+      // }
+
+      // if (!this.sendForm.content) {
+      //   this.$message.warning("璇疯緭鍏ュ彂閫佸唴瀹�");
+      //   return;
+      // }
+
+      if (this.currentSendExperts.length == 0) {
+        this.$message.warning("娌℃湁鎵惧埌鍙彂閫佺殑涓撳");
+        return;
+      }
+
+      // 鍒濆鍖栧彂閫佺姸鎬�
+      this.sendingAll = true;
+      this.sendingProgress = 0;
+      this.sendingTotal = this.currentSendExperts.length;
+      this.sendingSuccessCount = 0;
+      this.sendingFailCount = 0;
+      this.sendingResults = [];
+
+      // 鍒涘缓涓�涓繘搴﹀璇濇
+      const progressDialog = this.$message({
+        type: "info",
+        message: `姝e湪鍙戦�侀�氱煡锛岃绋嶅��... (0/${this.sendingTotal})`,
+        duration: 0, // 涓嶄細鑷姩鍏抽棴
+        showClose: true
+      });
+
+      try {
+        // 浣跨敤Promise鏁扮粍鏉ラ『搴忔墽琛屽彂閫�
+        for (let i = 0; i < this.currentSendExperts.length; i++) {
+          const expert = this.currentSendExperts[i];
+
+          // 鏇存柊杩涘害
+          this.sendingProgress = i;
+          progressDialog.message = `姝e湪鍙戦�侀�氱煡锛岃绋嶅��... (${i}/${this.sendingTotal})`;
+
+          try {
+            // 鍙戦�佸崟涓笓瀹堕�氱煡
+            const result = await this.sendSingleExpert(expert, i);
+            this.sendingResults.push(result);
+
+            if (result.success) {
+              this.sendingSuccessCount++;
+
+              // 鏇存柊涓撳鐘舵��
+              const index = this.form.ethicalreviewopinionsList.findIndex(
+                e =>
+                  e.expertNo == expert.expertNo ||
+                  e.expertname == expert.expertname
+              );
+
+              if (index != -1) {
+                this.form.ethicalreviewopinionsList[index].receiveStatus = "1"; // 宸叉帴鏀�
+                this.form.ethicalreviewopinionsList[
+                  index
+                ].startTime = this.sendForm.startTime;
+                this.form.ethicalreviewopinionsList[
+                  index
+                ].endTime = this.sendForm.endTime;
+                this.form.ethicalreviewopinionsList[
+                  index
+                ].sendType = this.sendForm.sendType;
+                this.form.ethicalreviewopinionsList[
+                  index
+                ].updateTime = new Date()
+                  .toISOString()
+                  .replace("T", " ")
+                  .substring(0, 19);
+
+                // 浣跨敤Vue.set纭繚鍝嶅簲寮忔洿鏂�
+                this.$set(
+                  this.form.ethicalreviewopinionsList,
+                  index,
+                  this.form.ethicalreviewopinionsList[index]
+                );
+              }
+            } else {
+              this.sendingFailCount++;
+            }
+          } catch (error) {
+            console.error(`鍙戦�佺粰涓撳 ${expert.expertname} 澶辫触:`, error);
+            this.sendingResults.push({
+              success: false,
+              expert: expert.expertname,
+              error: error.message
+            });
+            this.sendingFailCount++;
+          }
+
+          // 濡傛灉涓嶆槸鏈�鍚庝竴涓紝绛夊緟100ms鍐嶅彂閫佷笅涓�涓�
+          if (i < this.currentSendExperts.length - 1) {
+            await this.sleep(100);
+          }
+        }
+
+        // 瀹屾垚杩涘害
+        this.sendingProgress = this.sendingTotal;
+        progressDialog.message = `鍙戦�佸畬鎴愶紝鎴愬姛 ${this.sendingSuccessCount} 涓紝澶辫触 ${this.sendingFailCount} 涓猔;
+
+        // 寤惰繜1绉掑悗鍏抽棴杩涘害瀵硅瘽妗�
+        await this.sleep(1000);
+        progressDialog.close();
+
+        // 鏄剧ず鏈�缁堢粨鏋�
+        if (this.sendingFailCount == 0) {
+          this.$message.success(
+            `鎴愬姛鍙戦�佺粰 ${this.sendingSuccessCount} 浣嶄笓瀹禶
+          );
+        } else if (this.sendingSuccessCount > 0) {
+          this.$message.warning(
+            `鎴愬姛鍙戦�佺粰 ${this.sendingSuccessCount} 浣嶄笓瀹讹紝澶辫触 ${this.sendingFailCount} 浣峘
+          );
+          // 濡傛灉鏈夊け璐ワ紝鍙互鏄剧ず璇︾粏澶辫触淇℃伅
+          this.showFailedDetails();
+        } else {
+          this.$message.error("鍏ㄩ儴鍙戦�佸け璐ワ紝璇风◢鍚庨噸璇�");
+        }
+
+        // 鍏抽棴鍙戦�佸璇濇
+        this.sendDialogVisible = false;
+        this.sendForm = {
+          expertType: "normal",
+          expertIds: [],
+          startTime: "",
+          endTime: "",
+          sendType: "0",
+          content: "",
+          url: ""
+        };
+        this.currentSendExperts = [];
+        // 淇濆瓨鏁翠釜鍗曟嵁
+        this.handleSave();
+      } catch (error) {
+        console.error("鍙戦�佽繃绋嬩腑鍙戠敓閿欒:", error);
+        progressDialog.close();
+        this.$message.error("鍙戦�佽繃绋嬩腑鍙戠敓閿欒锛岃閲嶈瘯");
+      } finally {
+        this.sendingAll = false;
+      }
+    },
+    // 鍙戦�佸崟涓笓瀹剁殑鏂规硶
+    async sendSingleExpert(expert, index) {
+      console.log(expert, "expert", index);
+
+      try {
+        // // 鏋勫缓鍙戦�佹暟鎹�
+        // const sendData = {
+        //   number: expert.deptname || "", // 鐢ㄦ埛鎵嬫満鍙�
+        //   title: this.sendForm.title,
+        //   url: this.sendForm.url || "",
+        //   createTime: new Date()
+        //     .toISOString()
+        //     .replace("T", " ")
+        //     .substring(0, 19)
+        // };
+        const sendDatas = {
+          expertNo: expert.donorno || "", //7.14
+          id: expert.id || "",
+          infoId: this.infoid || ""
+        };
+
+        console.log(`姝e湪鍙戦�佺 ${index + 1} 涓笓瀹�: ${expert.expertname}`);
+
+        // 璋冪敤鍙戦�侀�氱煡鎺ュ彛
+        // const response = await sendNotification(sendData);
+        // const response = await sendcall({
+        //   tel: expert.donorno ? expert.donorno : 13634195431, // 杩欓噷搴旇鏄� expert.deptname 鎴� expert.phone
+        //   messageContent:
+        //     "闈掑矝澶у闄勫睘鍖婚櫌涓婃姤娼滃湪鎹愮尞妗堜緥锛岃鐧诲綍OPO绯荤粺鏌ョ湅璇︾粏淇℃伅锛屽強鏃惰繘琛屽鎺ャ�傜櫥褰曢摼鎺�:https://brdeddd.qduhosos.cn/dklejdj/deljf/index"
+        // });
+        const response = await sendNotification(sendDatas);
+
+        if (response.code == 200) {
+          return {
+            success: true,
+            expert: expert.expertname,
+            index: index
+          };
+        } else {
+          return {
+            success: false,
+            expert: expert.expertname,
+            index: index,
+            error: response.msg
+          };
+        }
+      } catch (error) {
+        console.error(`鍙戦�佺粰涓撳 ${expert.expertname} 澶辫触:`, error);
+        return {
+          success: false,
+          expert: expert.expertname,
+          index: index,
+          error: error.message
+        };
+      }
+    },
+
+    // 鏄剧ず澶辫触璇︽儏鐨勬柟娉�
+    showFailedDetails() {
+      const failedExperts = this.sendingResults.filter(r => !r.success);
+      if (failedExperts.length > 0) {
+        this.$confirm(
+          `鏈� ${failedExperts.length} 浣嶄笓瀹跺彂閫佸け璐ワ紝鏄惁鏌ョ湅澶辫触璇︽儏锛焋,
+          "鍙戦�佺粨鏋�",
+          {
+            confirmButtonText: "鏌ョ湅璇︽儏",
+            cancelButtonText: "鍏抽棴",
+            type: "warning"
+          }
+        )
+          .then(() => {
+            let detailMessage = "鍙戦�佸け璐ョ殑涓撳锛歕n\n";
+            failedExperts.forEach((expert, index) => {
+              detailMessage += `${index + 1}. ${expert.expert}: ${
+                expert.error
+              }\n`;
+            });
+
+            this.$alert(detailMessage, "鍙戦�佸け璐ヨ鎯�", {
+              confirmButtonText: "纭畾",
+              customClass: "failed-details-dialog"
+            });
+          })
+          .catch(() => {});
+      }
+    },
+
+    // 鐫$湢鍑芥暟锛岀敤浜庨棿闅�
+    sleep(ms) {
+      return new Promise(resolve => setTimeout(resolve, ms));
+    },
+    // 鍒犻櫎涓撳瀹℃煡
+    handleDeleteExpertReview(expert, index) {
+      this.$confirm("纭畾瑕佸垹闄よ涓撳鐨勫鏌ヨ褰曞悧锛�", "鎻愮ず", {
+        confirmButtonText: "纭畾",
+        cancelButtonText: "鍙栨秷",
+        type: "warning"
+      })
+        .then(() => {
+          // 浠庢暟缁勪腑鍒犻櫎涓撳
+          this.form.ethicalreviewopinionsList.splice(index, 1);
+          this.$message.success("涓撳瀹℃煡璁板綍宸插垹闄�");
+        })
+        .catch(() => {});
+    },
+
+    // 鏌ョ湅涓撳鍘嗗彶瀹℃壒鎯呭喌
+    async handleViewExpertHistory(expert) {
+      if (!expert.expertNo) {
+        this.$message.warning("璇ヤ笓瀹舵病鏈夌紪鍙凤紝鏃犳硶鏌ヨ鍘嗗彶瀹℃壒鎯呭喌");
+        return;
+      }
+
+      this.currentExpertInfo = expert;
+      this.expertHistoryLoading = true;
+      this.expertHistoryDialogVisible = true;
+
+      try {
+        const params = {
+          expertNo: expert.expertNo
+        };
+
+        const response = await ethicalreExpertTotal(params);
+
+        if (response && response.code == 200) {
+          this.expertHistoryData = response.data || response[0] || null;
+        } else {
+          this.$message.error(
+            "鏌ヨ涓撳鍘嗗彶瀹℃壒鎯呭喌澶辫触锛�" + (response?.msg || "鏈煡閿欒")
+          );
+          this.expertHistoryData = null;
+        }
+      } catch (error) {
+        console.error("鏌ヨ涓撳鍘嗗彶瀹℃壒鎯呭喌澶辫触:", error);
+        this.$message.error("鏌ヨ涓撳鍘嗗彶瀹℃壒鎯呭喌澶辫触");
+        this.expertHistoryData = null;
+      } finally {
+        this.expertHistoryLoading = false;
+      }
+    },
+
+    // 鏃堕棿鏍煎紡鍖�
+    parseTime(time) {
+      if (!time) return "";
+      const date = new Date(time);
+      if (isNaN(date.getTime())) return 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 {
+.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;
+}
+
+.file-info {
+  display: flex;
+  align-items: center;
+}
+
+.empty-attachment {
+  text-align: center;
+  padding: 40px 0;
+  color: #909399;
+}
+
+/* 涓撳绫诲瀷鏍峰紡 */
+.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;
+}
+
+/* 涓撳濮撳悕閾炬帴鏍峰紡 */
+.expert-name-link {
+  color: #409eff;
+  cursor: pointer;
+  text-decoration: none;
+  transition: color 0.3s;
+}
+
+.expert-name-link:hover {
+  color: #66b1ff;
+  text-decoration: underline;
+}
+
+/* 鍘嗗彶缁熻鏍峰紡 */
+.history-stat-item {
+  padding: 10px;
+  border-radius: 4px;
+  background-color: #f5f7fa;
+  text-align: center;
+}
+
+.history-stat-label {
+  font-size: 12px;
+  color: #909399;
+  margin-bottom: 5px;
+}
+
+.history-stat-value {
+  font-size: 18px;
+  font-weight: bold;
+  color: #303133;
+}
+
+.form-section {
+  margin-bottom: 16px;
+}
+
+.section-header {
+  display: flex;
+  align-items: center;
+  font-weight: bold;
+  color: #303133;
+}
+
+.dialog-footer {
   text-align: right;
-  margin-top: 30px;
+  padding: 20px 0 0;
+}
+
+.attachment-section {
+  margin-bottom: 16px;
+}
+
+.attachment-header {
+  display: flex;
+  align-items: center;
+  margin-bottom: 16px;
+  padding: 8px 0;
+  border-bottom: 1px solid #ebeef5;
+}
+
+.attachment-title {
+  font-weight: bold;
+  margin: 0 8px;
+}
+
+.attachment-tip {
+  font-size: 12px;
+  color: #909399;
+}
+
+.attachment-list {
+  margin-top: 16px;
+}
+
+.list-title {
+  font-weight: bold;
+  margin-bottom: 12px;
+  color: #303133;
+}
+
+.file-name {
+  font-size: 13px;
+}
+
+/* 妗堜緥淇℃伅灞曠ず鏍峰紡 */
+.selected-case-info {
+  margin-bottom: 20px;
+}
+/* 鍙戦�佽繘搴︽牱寮� */
+.send-progress-container {
+  margin: 20px 0;
+  padding: 20px;
+  background: #f5f7fa;
+  border-radius: 8px;
+}
+
+.send-stats {
+  display: flex;
+  justify-content: center;
+  gap: 30px;
+  margin-top: 10px;
+}
+
+.stat-item {
+  font-size: 14px;
+  font-weight: 500;
+  padding: 4px 12px;
+  border-radius: 4px;
+}
+
+.stat-item.success {
+  color: #67c23a;
+  background: #f0f9eb;
+}
+
+.stat-item.fail {
+  color: #f56c6c;
+  background: #fef0f0;
+}
+
+/* 澶辫触璇︽儏瀵硅瘽妗� */
+.failed-details-dialog {
+  min-width: 400px;
+  max-width: 600px;
+}
+
+.failed-details-dialog .el-message-box__content {
+  max-height: 400px;
+  overflow-y: auto;
+  white-space: pre-wrap;
+  word-break: break-word;
+}
+.case-info-card {
+  border-left: 4px solid #67c23a;
+}
+/* 鍦–SS涓坊鍔� */
+:deep(.el-message-box) {
+  max-width: 500px;
+}
+
+/* 娣诲姞鏈繚瀛樼姸鎬佹牱寮� */
+.unsaved-hint {
+  position: absolute;
+  top: 10px;
+  right: 10px;
+  background: #e6a23c;
+  color: white;
+  padding: 4px 8px;
+  border-radius: 4px;
+  font-size: 12px;
+  animation: pulse 2s infinite;
+}
+
+@keyframes pulse {
+  0% {
+    opacity: 0.8;
+  }
+  50% {
+    opacity: 1;
+  }
+  100% {
+    opacity: 0.8;
+  }
+}
+/* 鍝嶅簲寮忚璁� */
+@media (max-width: 768px) {
+  .ethics-review-detail {
+    padding: 10px;
+  }
+
+  .expert-stats .el-col {
+    margin-bottom: 10px;
+  }
 }
 </style>

--
Gitblit v1.9.3