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

---
 src/views/business/ethicalReview/ethicalReviewInfo.vue | 2196 ++++++++++++++++++++++++++++++++++++++++++++--------------
 1 files changed, 1,656 insertions(+), 540 deletions(-)

diff --git a/src/views/business/ethicalReview/ethicalReviewInfo.vue b/src/views/business/ethicalReview/ethicalReviewInfo.vue
index 9e76ff2..8e56dfa 100644
--- a/src/views/business/ethicalReview/ethicalReviewInfo.vue
+++ b/src/views/business/ethicalReview/ethicalReviewInfo.vue
@@ -12,9 +12,28 @@
           </el-button>
 
           <el-button
+            type="success"
+            @click="handleCompleteReview"
+            :disabled="form.status == '3' || form.status == '2'"
+            :loading="completeLoading"
+          >
+            瀹℃煡瀹屾垚
+          </el-button>
+
+          <el-button
             type="warning"
+            @click="handleSuspendReview"
+            :disabled="form.status == '2' || form.status == '3'"
+            :loading="suspendLoading"
+          >
+            瀹℃煡涓
+          </el-button>
+
+          <el-button
+            type="danger"
             @click="handleEndReview"
-            :disabled="form.status === '2'"
+            :disabled="form.status == '2'"
+            :loading="endLoading"
           >
             缁撴潫瀹℃煡
           </el-button>
@@ -36,10 +55,7 @@
               <el-input v-model="form.initiatePerson" />
             </el-form-item>
           </el-col>
-        </el-row>
-
-        <el-row :gutter="20">
-          <el-col :span="8">
+          <!-- <el-col :span="8">
             <el-form-item label="瀹℃煡鐘舵��" prop="status">
               <el-select v-model="form.status" style="width: 100%">
                 <el-option
@@ -50,55 +66,14 @@
                 />
               </el-select>
             </el-form-item>
-          </el-col>
-          <el-col :span="8">
-            <el-form-item label="鍙戣捣鏃堕棿" prop="startTime">
-              <el-date-picker
-                v-model="form.startTime"
-                type="datetime"
-                value-format="yyyy-MM-dd HH:mm:ss"
-                style="width: 100%"
-              />
-            </el-form-item>
-          </el-col>
-          <el-col :span="8">
-            <el-form-item label="鎴鏃堕棿" prop="cutOffTime">
-              <el-date-picker
-                v-model="form.cutOffTime"
-                type="datetime"
-                value-format="yyyy-MM-dd HH:mm:ss"
-                style="width: 100%"
-              >
-              </el-date-picker>
-            </el-form-item>
-          </el-col>
+          </el-col> -->
         </el-row>
 
         <!-- 涓撳鐩稿叧淇℃伅 -->
-        <el-row :gutter="20">
-          <el-col :span="8">
-            <el-form-item label="涓撳濮撳悕" prop="expertName">
-              <el-input v-model="form.expertName" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="8">
-            <el-form-item label="涓撳缂栧彿" prop="expertNo">
-              <el-input v-model="form.expertNo" />
-            </el-form-item>
-          </el-col>
-          <el-col :span="8">
-            <el-form-item label="涓撳绫诲瀷" prop="expertType">
-              <el-select v-model="form.expertType" style="width: 100%">
-                <el-option label="鏅�氫笓瀹�" value="normal" />
-                <el-option label="涓诲涓撳" value="chief" />
-              </el-select>
-            </el-form-item>
-          </el-col>
-        </el-row>
 
         <el-row :gutter="20">
           <el-col :span="8">
-            <el-form-item label="涓撳缁撹" prop="expertConclusion">
+            <el-form-item label="浼︾悊瀹℃煡缁撹" prop="expertConclusion">
               <el-select v-model="form.expertConclusion" style="width: 100%">
                 <el-option label="鍚屾剰" value="1" />
                 <el-option label="瀹℃煡涓�" value="2" />
@@ -107,7 +82,7 @@
             </el-form-item>
           </el-col>
           <el-col :span="8">
-            <el-form-item label="涓撳缁撹鏃堕棿" prop="expertTime">
+            <el-form-item label="浼︾悊瀹℃煡缁撹鏃堕棿" prop="expertTime">
               <el-date-picker
                 v-model="form.expertTime"
                 type="datetime"
@@ -116,26 +91,16 @@
               />
             </el-form-item>
           </el-col>
-          <el-col :span="8">
-            <el-form-item label="涓撳鎺掗槦搴忓彿" prop="orderNo">
-              <el-input-number
-                v-model="form.orderNo"
-                :min="1"
-                :max="20"
-                style="width: 100%"
-              />
-            </el-form-item>
-          </el-col>
         </el-row>
 
         <el-row :gutter="20">
           <el-col :span="24">
-            <el-form-item label="涓撳鎰忚" prop="expertOpinion">
+            <el-form-item label="瀹℃煡鎰忚" prop="expertOpinion">
               <el-input
                 type="textarea"
                 :rows="2"
                 v-model="form.expertOpinion"
-                placeholder="璇疯緭鍏ヤ笓瀹舵剰瑙�"
+                placeholder="璇疯緭鍏ユ剰瑙�"
               />
             </el-form-item>
           </el-col>
@@ -160,9 +125,6 @@
     <el-card class="attachment-card">
       <div slot="header" class="clearfix">
         <span class="detail-title">鐩稿叧闄勪欢</span>
-        <!-- <el-button type="primary" size="mini" @click="openUploadDialog">
-          涓婁紶闄勪欢
-        </el-button> -->
       </div>
 
       <!-- 浣跨敤 UploadAttachment 缁勪欢 -->
@@ -185,11 +147,7 @@
         <div class="list-title">
           宸蹭笂浼犻檮浠� ({{ form.annexfilesList.length }})
         </div>
-        <el-table
-          :data="form.annexfilesList"
-          style="width: 100%"
-          size="small"
-        >
+        <el-table :data="form.annexfilesList" style="width: 100%" size="small">
           <el-table-column label="鏂囦欢鍚�" min-width="200">
             <template slot-scope="scope">
               <i
@@ -240,8 +198,14 @@
       </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>
+      <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>
@@ -249,8 +213,19 @@
     <!-- 涓撳瀹℃煡鎯呭喌 -->
     <el-card class="expert-card">
       <div slot="header" class="clearfix">
-        <span class="detail-title">涓撳瀹℃煡鎯呭喌 (18浣嶄笓瀹� + 1浣嶄富濮斾笓瀹�)</span>
+        <span class="detail-title">涓撳瀹℃煡鎯呭喌</span>
         <div style="float: right;">
+          <el-button
+            type="warning"
+            size="mini"
+            @click="handleRefresh"
+            icon="el-icon-refresh"
+          >
+            鍒锋柊
+          </el-button>
+          <el-button size="mini" type="primary" @click="handleAddExpert">
+            娣诲姞涓撳
+          </el-button>
           <el-button
             size="mini"
             type="primary"
@@ -267,14 +242,6 @@
           >
             鍙戦�佷富濮斾笓瀹�
           </el-button>
-          <el-button
-            size="mini"
-            type="warning"
-            @click="handleBatchSend"
-            :disabled="!canBatchSend"
-          >
-            鎵归噺鍙戦��
-          </el-button>
         </div>
       </div>
 
@@ -286,20 +253,22 @@
         <el-row :gutter="20">
           <el-col :span="6">
             <div class="stat-item">
-              <span class="stat-label">涓撳宸插悓鎰�:</span>
-              <span class="stat-value">{{ approvedNormalExperts }}/18</span>
+              <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">{{ chiefExpertStatus }}</span>
+              <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">{{ completionRate }}%</span>
+              <span class="stat-label">宸插悓鎰�:</span>
+              <span class="stat-value"
+                >{{ approvedExpertsCount }}/{{ totalExpertsCount }}</span
+              >
             </div>
           </el-col>
           <el-col :span="6">
@@ -317,10 +286,10 @@
 
       <!-- 涓撳瀹℃煡琛ㄦ牸 -->
       <el-table
-        :data="expertReviews"
+        :data="ethicalreviewopinionsList"
         v-loading="expertLoading"
         style="width: 100%"
-        height="800"
+        height="600"
         :row-class-name="getExpertRowClassName"
       >
         <el-table-column label="搴忓彿" width="60" align="center" type="index" />
@@ -332,9 +301,14 @@
           fixed="left"
         >
           <template slot-scope="scope">
-            <span>{{ scope.row.expertName }}</span>
+            <span
+              class="expert-name-link"
+              @click="handleViewExpertHistory(scope.row)"
+            >
+              {{ scope.row.expertname }}
+            </span>
             <el-tag
-              v-if="scope.row.isChief"
+              v-if="scope.row.expertType == '1'"
               size="mini"
               type="danger"
               style="margin-left: 5px;"
@@ -343,18 +317,31 @@
           </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.isChief ? 'chief-expert' : 'normal-expert'">
-              {{ scope.row.isChief ? "涓诲涓撳" : "涓撳" }}
+            <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="statusFilter(scope.row.reviewStatus)" size="small">
-              {{ statusTextFilter(scope.row.reviewStatus) }}
+            <el-tag
+              :type="getReviewStatusFilter(scope.row.receiveStatus)"
+              size="small"
+            >
+              {{ getReviewStatusText(scope.row.receiveStatus) }}
             </el-tag>
           </template>
         </el-table-column>
@@ -362,106 +349,295 @@
         <el-table-column label="涓撳缁撹" width="120" align="center">
           <template slot-scope="scope">
             <el-tag
-              v-if="scope.row.expertConclusion"
-              :type="conclusionFilter(scope.row.expertConclusion)"
+              v-if="scope.row.expertconclusion"
+              :type="getConclusionFilter(scope.row.expertconclusion)"
               size="small"
             >
-              {{ conclusionTextFilter(scope.row.expertConclusion) }}
+              {{ 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 :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.reviewTime ? parseTime(scope.row.reviewTime) : "鏈鏌�"
+              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.sendTime ? parseTime(scope.row.sendTime) : "鏈彂閫�"
+              scope.row.startTime
+                ? formatDateTime(scope.row.startTime)
+                : "鏈彂閫�"
             }}</span>
           </template>
         </el-table-column>
 
-        <el-table-column label="鎿嶄綔" width="280" align="center" fixed="right">
+        <el-table-column label="鎿嶄綔" width="180" align="center" fixed="right">
           <template slot-scope="scope">
             <el-button
               size="mini"
               type="text"
               icon="el-icon-s-promotion"
               @click="handleSendToExpert(scope.row)"
-              :disabled="scope.row.reviewStatus === 'submitted'"
-              :class="{ 'sent-button': scope.row.reviewStatus === 'submitted' }"
+              :disabled="
+                scope.row.receiveStatus == '2' ||
+                  scope.row.receiveStatus == '3' ||
+                  scope.row.receiveStatus == '4'
+              "
+              :class="{
+                'sent-button':
+                  scope.row.receiveStatus == '2' ||
+                  scope.row.receiveStatus == '3'
+              }"
             >
-              {{ scope.row.reviewStatus === "submitted" ? "宸插彂閫�" : "鍙戦��" }}
+              {{
+                scope.row.receiveStatus == "2" || scope.row.receiveStatus == "3"
+                  ? "宸插彂閫�"
+                  : "鍙戦��"
+              }}
             </el-button>
             <el-button
+              v-if="scope.row.receiveStatus == 0"
               size="mini"
               type="text"
-              icon="el-icon-edit"
-              @click="handleEditExpertReview(scope.row)"
-              :disabled="scope.row.reviewStatus !== 'submitted'"
+              icon="el-icon-delete"
+              @click="handleDeleteExpertReview(scope.row, scope.$index)"
+              style="color: #f56c6c;"
             >
-              缂栬緫
-            </el-button>
-            <el-button
-              size="mini"
-              type="text"
-              icon="el-icon-view"
-              @click="handleViewExpertReview(scope.row)"
-            >
-              璇︽儏
+              鍒犻櫎
             </el-button>
           </template>
         </el-table-column>
       </el-table>
     </el-card>
 
+    <!-- 娣诲姞涓撳瀵硅瘽妗� -->
+    <el-dialog
+      title="娣诲姞涓撳"
+      :visible.sync="expertDialogVisible"
+      width="900px"
+      @close="handleExpertDialogClose"
+    >
+      <div style="margin-bottom: 20px;">
+        <el-input
+          v-model="expertSearchQuery"
+          placeholder="璇疯緭鍏ヤ笓瀹跺鍚嶆垨缂栧彿鎼滅储"
+          style="width: 300px; margin-right: 10px;"
+          @keyup.enter.native="handleSearchExperts"
+        >
+          <el-button
+            slot="append"
+            icon="el-icon-search"
+            @click="handleSearchExperts"
+          ></el-button>
+        </el-input>
+        <el-select
+          v-model="filterExpertType"
+          placeholder="涓撳绫诲瀷"
+          style="width: 150px; margin-right: 10px;"
+          @change="handleSearchExperts"
+        >
+          <el-option label="鍏ㄩ儴" value=""></el-option>
+          <el-option label="鏅�氫笓瀹�" value="0"></el-option>
+          <el-option label="涓讳换濮斿憳" value="1"></el-option>
+        </el-select>
+        <el-button type="primary" @click="handleSearchExperts">鎼滅储</el-button>
+        <el-button @click="handleResetSearch">閲嶇疆</el-button>
+      </div>
+
+      <el-table
+        :data="filteredExpertList"
+        v-loading="expertListLoading"
+        style="width: 100%"
+        max-height="600"
+        @selection-change="handleExpertSelectionChange"
+      >
+        <el-table-column type="selection" width="55"></el-table-column>
+        <el-table-column
+          label="涓撳濮撳悕"
+          prop="username"
+          width="120"
+        ></el-table-column>
+        <el-table-column
+          label="涓撳缂栧彿"
+          prop="userno"
+          width="120"
+        ></el-table-column>
+        <el-table-column label="涓撳绫诲瀷" width="100">
+          <template slot-scope="scope">
+            <el-tag
+              size="small"
+              :type="getIsChiefExpert(scope.row) ? 'danger' : ''"
+            >
+              {{ getIsChiefExpert(scope.row) ? "涓讳换濮斿憳" : "鏅�氫笓瀹�" }}
+            </el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column
+          label="鍗曚綅鍚嶇О"
+          prop="unitname"
+          show-overflow-tooltip
+        ></el-table-column>
+        <el-table-column
+          label="鑱岀О"
+          prop="title"
+          width="100"
+        ></el-table-column>
+        <el-table-column
+          label="鑱旂郴鐢佃瘽"
+          prop="donorno"
+          width="120"
+        ></el-table-column>
+      </el-table>
+
+      <div style="margin-top: 20px; text-align: center;">
+        <el-pagination
+          @size-change="handlePageSizeChange"
+          @current-change="handlePageChange"
+          :current-page="expertPage.pageNum"
+          :page-sizes="[10, 20, 50, 100]"
+          :page-size="expertPage.pageSize"
+          layout="total, sizes, prev, pager, next, jumper"
+          :total="filteredExpertTotal"
+        ></el-pagination>
+      </div>
+
+      <div slot="footer">
+        <el-button @click="expertDialogVisible = false">鍙栨秷</el-button>
+        <el-button
+          type="primary"
+          @click="handleConfirmAddExpert"
+          :disabled="selectedExperts.length == 0"
+          >纭畾娣诲姞</el-button
+        >
+      </div>
+    </el-dialog>
+
     <!-- 鍙戦�佷笓瀹跺璇濇 -->
     <el-dialog
-      title="鍙戦�佷笓瀹跺鏌�"
+      :title="sendDialogTitle"
       :visible.sync="sendDialogVisible"
       width="500px"
+      @close="handleSendDialogClose"
     >
       <el-form :model="sendForm" ref="sendForm" label-width="100px">
         <el-form-item label="涓撳绫诲瀷" prop="expertType">
-          <el-radio-group v-model="sendForm.expertType">
-            <el-radio label="normal">涓撳</el-radio>
+          <el-radio-group
+            v-model="sendForm.expertType"
+            @change="handleExpertTypeChange"
+          >
+            <el-radio label="normal">鏅�氫笓瀹�</el-radio>
             <el-radio label="chief">涓诲涓撳</el-radio>
           </el-radio-group>
         </el-form-item>
+
+        <el-form-item label="鍙戦�佹椂闂�" prop="startTime" required>
+          <el-date-picker
+            v-model="sendForm.startTime"
+            type="datetime"
+            placeholder="璇烽�夋嫨鍙戦�佹椂闂�"
+            value-format="yyyy-MM-dd HH:mm:ss"
+            style="width: 100%"
+          />
+        </el-form-item>
+
         <el-form-item
-          label="閫夋嫨涓撳"
-          prop="expertIds"
-          v-if="sendForm.expertType === 'normal'"
+          label="鎴鏃堕棿"
+          prop="endTime"
+          :required="sendForm.expertType !== 'chief'"
         >
+          <el-date-picker
+            v-model="sendForm.endTime"
+            type="datetime"
+            placeholder="璇烽�夋嫨鎴鏃堕棿"
+            value-format="yyyy-MM-dd HH:mm:ss"
+            style="width: 100%"
+            :disabled="sendForm.expertType == 'chief'"
+          />
+          <div v-if="sendForm.expertType !== 'chief'" style="margin-top: 5px;">
+            <el-button-group>
+              <el-button size="mini" @click="setEndTime(0.5)"
+                >鍗婂皬鏃跺悗</el-button
+              >
+              <el-button size="mini" @click="setEndTime(1)">涓�灏忔椂鍚�</el-button>
+              <el-button size="mini" @click="setEndTime(2)">涓ゅ皬鏃跺悗</el-button>
+              <el-button size="mini" @click="setEndTime(24)">涓�澶╁悗</el-button>
+            </el-button-group>
+          </div>
+          <div
+            v-if="sendForm.expertType == 'chief'"
+            style="font-size: 12px; color: #999; margin-top: 5px;"
+          >
+            涓诲涓撳鏃犻渶璁剧疆鎴鏃堕棿
+          </div>
+        </el-form-item>
+
+        <el-form-item label="鍙戦�佹柟寮�" prop="sendType" required>
           <el-select
-            v-model="sendForm.expertIds"
-            multiple
-            placeholder="璇烽�夋嫨涓撳"
+            v-model="sendForm.sendType"
+            placeholder="璇烽�夋嫨鍙戦�佹柟寮�"
             style="width: 100%"
           >
-            <el-option
-              v-for="expert in availableExperts"
-              :key="expert.id"
-              :label="expert.name"
-              :value="expert.id"
-            />
+            <el-option label="绯荤粺鍙戦��" value="0"></el-option>
+            <el-option label="閭欢鍙戦��" value="1"></el-option>
+            <el-option label="鐭俊鍙戦��" value="2"></el-option>
+            <el-option label="鍏朵粬鏂瑰紡" value="3"></el-option>
           </el-select>
         </el-form-item>
-        <el-form-item label="鍙戦�佸唴瀹�" prop="content">
+
+        <el-form-item label="鍙戦�佹爣棰�" prop="title" required>
+          <el-input v-model="sendForm.title" placeholder="璇疯緭鍏ュ彂閫佹爣棰�" />
+        </el-form-item>
+
+        <el-form-item label="鍙戦�佸唴瀹�" prop="content" required>
           <el-input
             type="textarea"
             :rows="4"
@@ -469,12 +645,155 @@
             placeholder="璇疯緭鍏ュ彂閫佺粰涓撳鐨勫鏌ュ唴瀹硅鏄�"
           />
         </el-form-item>
+
+        <el-form-item label="璺宠浆閾炬帴" prop="url">
+          <el-input
+            v-model="sendForm.url"
+            placeholder="璇疯緭鍏ヨ烦杞摼鎺ワ紙鍙�夛級"
+          />
+        </el-form-item>
       </el-form>
       <div slot="footer">
         <el-button @click="sendDialogVisible = false">鍙栨秷</el-button>
-        <el-button type="primary" @click="handleSendConfirm"
-          >纭鍙戦��</el-button
+        <el-button
+          type="primary"
+          @click="handleSendConfirm"
+          :loading="sendingAll"
+          :disabled="sendingAll"
         >
+          {{
+            sendingAll
+              ? `鍙戦�佷腑 (${sendingProgress}/${sendingTotal})`
+              : "纭鍙戦��"
+          }}
+        </el-button>
+      </div>
+    </el-dialog>
+    <!-- 鎴栬�呭湪椤甸潰涓坊鍔犺繘搴︽潯 -->
+    <div v-if="sendingAll" class="send-progress-container">
+      <el-progress
+        :percentage="Math.round((sendingProgress / sendingTotal) * 100)"
+        :text-inside="true"
+        :stroke-width="20"
+        status="success"
+      >
+        <span>宸插彂閫� {{ sendingProgress }} / {{ sendingTotal }}</span>
+      </el-progress>
+      <div class="send-stats">
+        <span class="stat-item success">鎴愬姛: {{ sendingSuccessCount }}</span>
+        <span class="stat-item fail">澶辫触: {{ sendingFailCount }}</span>
+      </div>
+    </div>
+    <!-- 涓撳鍘嗗彶瀹℃壒鎯呭喌瀵硅瘽妗� -->
+    <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>
+      <div slot="footer">
+        <el-button @click="expertHistoryDialogVisible = false">鍏抽棴</el-button>
       </div>
     </el-dialog>
 
@@ -494,8 +813,12 @@
   reviewinitiateBaseInfoList,
   ethicalreviewedit,
   ethicalreviewadd,
-  ethicalreviewInfo
+  ethicalreviewInfo,
+  ethicalreExpertTotal,
+  sendNotification,
+  sendcall
 } from "@/api/businessApi";
+import { listExternalperson } from "@/api/project/externalperson";
 import CaseBasicInfo from "@/components/CaseBasicInfo";
 import UploadAttachment from "@/components/UploadAttachment";
 import FilePreviewDialog from "@/components/FilePreviewDialog";
@@ -504,7 +827,7 @@
 export default {
   name: "EthicsReviewDetail",
   components: { CaseBasicInfo, UploadAttachment, FilePreviewDialog },
-  dicts: ["sys_user_sex", "sys_ethical"],
+  dicts: ["sys_user_sex", "sys_ethical", "Review_status"],
 
   data() {
     return {
@@ -526,7 +849,7 @@
         initiatePerson: "",
 
         // 鐘舵�佸拰鏃堕棿
-        status: "0", // 0:鏂板缓, 1:瀹℃煡涓�, 2:缁撴潫
+        status: "0", // 0:寰呭鏌�, 1:瀹℃煡涓�, 2:瀹℃煡涓, 3:瀹℃煡瀹屾垚
         startTime: "",
         cutOffTime: "",
         endTime: "",
@@ -534,7 +857,7 @@
         // 涓撳淇℃伅
         expertName: "",
         expertNo: "",
-        expertType: "normal",
+        expertType: "0",
         expertConclusion: "",
         expertOpinion: "",
         expertTime: "",
@@ -547,6 +870,9 @@
         annexfilesList: [],
         filePatch: "",
 
+        // 涓撳瀹℃煡鎰忚鍒楄〃
+        ethicalreviewopinionsList: [],
+
         // 绯荤粺瀛楁
         createBy: "",
         createTime: "",
@@ -554,6 +880,7 @@
         updateTime: "",
         delFlag: "0"
       },
+
       // 琛ㄥ崟楠岃瘉瑙勫垯
       rules: {
         initiateTheme: [
@@ -571,12 +898,6 @@
         status: [
           { required: true, message: "瀹℃煡鐘舵�佷笉鑳戒负绌�", trigger: "change" }
         ],
-        startTime: [
-          { required: true, message: "鍙戣捣鏃堕棿涓嶈兘涓虹┖", trigger: "change" }
-        ],
-        cutOffTime: [
-          { required: true, message: "鎴鏃堕棿涓嶈兘涓虹┖", trigger: "change" }
-        ],
         expertName: [
           { max: 50, message: "闀垮害涓嶈兘瓒呰繃 50 涓瓧绗�", trigger: "blur" }
         ],
@@ -590,8 +911,23 @@
           { max: 500, message: "闀垮害涓嶈兘瓒呰繃 500 涓瓧绗�", trigger: "blur" }
         ]
       },
+      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: [],
@@ -601,305 +937,206 @@
       currentPreviewFile: null,
 
       // 涓撳瀹℃煡鏁版嵁
-      expertReviews: [
-        // 涓撳锛�18浣嶏級- 鍒濆鐘舵�佷负鐢宠涓�
-        {
-          id: 1,
-          expertName: "闄舵槉",
-          isChief: false,
-          reviewStatus: "applying",
-          expertConclusion: "",
-          expertOpinion: "",
-          reviewTime: "",
-          sendTime: ""
-        },
-        {
-          id: 2,
-          expertName: "鍒樻枌",
-          isChief: false,
-          reviewStatus: "applying",
-          expertConclusion: "",
-          expertOpinion: "",
-          reviewTime: "",
-          sendTime: ""
-        },
-        {
-          id: 3,
-          expertName: "浜庢捣鍒� ",
-          isChief: false,
-          reviewStatus: "applying",
-          expertConclusion: "",
-          expertOpinion: "",
-          reviewTime: "",
-          sendTime: ""
-        },
-        {
-          id: 4,
-          expertName: "鐜嬬孩姊�",
-          isChief: false,
-          reviewStatus: "applying",
-          expertConclusion: "",
-          expertOpinion: "",
-          reviewTime: "",
-          sendTime: ""
-        },
-        {
-          id: 5,
-          expertName: "鐜嬫槬鍏�",
-          isChief: false,
-          reviewStatus: "applying",
-          expertConclusion: "",
-          expertOpinion: "",
-          reviewTime: "",
-          sendTime: ""
-        },
-        {
-          id: 6,
-          expertName: "鐜嬮潤",
-          isChief: false,
-          reviewStatus: "applying",
-          expertConclusion: "",
-          expertOpinion: "",
-          reviewTime: "",
-          sendTime: ""
-        },
-        {
-          id: 7,
-          expertName: "杈规枃瓒�",
-          isChief: false,
-          reviewStatus: "applying",
-          expertConclusion: "",
-          expertOpinion: "",
-          reviewTime: "",
-          sendTime: ""
-        },
-        {
-          id: 8,
-          expertName: "闂織鍕�",
-          isChief: false,
-          reviewStatus: "applying",
-          expertConclusion: "",
-          expertOpinion: "",
-          reviewTime: "",
-          sendTime: ""
-        },
-        {
-          id: 9,
-          expertName: "璁稿嚖",
-          isChief: false,
-          reviewStatus: "applying",
-          expertConclusion: "",
-          expertOpinion: "",
-          reviewTime: "",
-          sendTime: ""
-        },
-        {
-          id: 10,
-          expertName: "璁镐紶灞�",
-          isChief: false,
-          reviewStatus: "applying",
-          expertConclusion: "",
-          expertOpinion: "",
-          reviewTime: "",
-          sendTime: ""
-        },
-        {
-          id: 11,
-          expertName: "寮犵孩宀�",
-          isChief: false,
-          reviewStatus: "applying",
-          expertConclusion: "",
-          expertOpinion: "",
-          reviewTime: "",
-          sendTime: ""
-        },
-        {
-          id: 12,
-          expertName: "鏉ㄨ嫃姘�",
-          isChief: false,
-          reviewStatus: "applying",
-          expertConclusion: "",
-          expertOpinion: "",
-          reviewTime: "",
-          sendTime: ""
-        },
-        {
-          id: 13,
-          expertName: "瀹嬬帀寮�",
-          isChief: false,
-          reviewStatus: "applying",
-          expertConclusion: "",
-          expertOpinion: "",
-          reviewTime: "",
-          sendTime: ""
-        },
-        {
-          id: 14,
-          expertName: "鍛ㄤ紶鍒�",
-          isChief: false,
-          reviewStatus: "applying",
-          expertConclusion: "",
-          expertOpinion: "",
-          reviewTime: "",
-          sendTime: ""
-        },
-        {
-          id: 15,
-          expertName: "鑽嗗嚒娉�",
-          isChief: false,
-          reviewStatus: "applying",
-          expertConclusion: "",
-          expertOpinion: "",
-          reviewTime: "",
-          sendTime: ""
-        },
-        {
-          id: 16,
-          expertName: "鐭枃鎹�",
-          isChief: false,
-          reviewStatus: "applying",
-          expertConclusion: "",
-          expertOpinion: "",
-          reviewTime: "",
-          sendTime: ""
-        },
-        {
-          id: 17,
-          expertName: "钁i渿",
-          isChief: false,
-          reviewStatus: "applying",
-          expertConclusion: "",
-          expertOpinion: "",
-          reviewTime: "",
-          sendTime: ""
-        },
-        {
-          id: 18,
-          expertName: "钄¢噾璐�",
-          isChief: false,
-          reviewStatus: "applying",
-          expertConclusion: "",
-          expertOpinion: "",
-          reviewTime: "",
-          sendTime: ""
-        },
-        // 涓诲涓撳锛�1浣嶏級
-        {
-          id: 19,
-          expertName: "瀛斿績娑�",
-          isChief: true,
-          reviewStatus: "applying",
-          expertConclusion: "",
-          expertOpinion: "",
-          reviewTime: "",
-          sendTime: ""
-        }
-      ],
+      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: [],
-        content: ""
+        startTime: "",
+        endTime: "",
+        sendType: "0",
+        title: "浼︾悊瀹℃煡浠诲姟閫氱煡",
+        content: "",
+        url: ""
       },
 
-      // 鍙敤涓撳鍒楄〃
-      availableExperts: [
-        { id: 1, name: "闄舵槉", type: "normal" },
-        { id: 2, name: "鍒樻枌", type: "normal" },
-        { id: 3, name: "浜庢捣鍒�", type: "normal" },
-        { id: 4, name: "鐜嬬孩姊�", type: "normal" },
-        { id: 5, name: "鐜嬫槬鍏�", type: "normal" },
-        { id: 6, name: "鐜嬮潤", type: "normal" },
-        { id: 7, name: "杈规枃瓒�", type: "normal" },
-        { id: 8, name: "闂織鍕�", type: "normal" },
-        { id: 9, name: "璁稿嚖", type: "normal" },
-        { id: 10, name: "璁镐紶灞�", type: "normal" },
-        { id: 11, name: "寮犵孩宀�", type: "normal" },
-        { id: 12, name: "鏉ㄨ嫃姘�", type: "normal" },
-        { id: 13, name: "瀹嬬帀寮�", type: "normal" },
-        { id: 14, name: "鍛ㄤ紶鍒�", type: "normal" },
-        { id: 15, name: "鑽嗗嚒娉�", type: "normal" },
-        { id: 16, name: "鐭枃鎹�", type: "normal" },
-        { id: 17, name: "钁i渿", type: "normal" },
-        { id: 18, name: "钄¢噾璐�", type: "normal" },
-        { id: 19, name: "瀛斿績娑�", type: "chief" }
-      ]
+      // 涓撳鍘嗗彶瀹℃壒鎯呭喌
+      expertHistoryDialogVisible: false,
+      expertHistoryLoading: false,
+      expertHistoryData: null,
+      currentExpertInfo: {},
+
+      // 褰撳墠鍙戦�佺殑涓撳
+      currentSendExperts: []
     };
   },
   computed: {
-    // 璁$畻灞炴�э細涓撳鍚屾剰鏁伴噺
-    approvedNormalExperts() {
-      return this.expertReviews.filter(
-        expert => !expert.isChief && expert.expertConclusion === "approved"
+    // 璁$畻灞炴�э細鑾峰彇涓撳鍒楄〃锛堢‘淇濆搷搴斿紡锛�
+    ethicalreviewopinionsList() {
+      return this.form.ethicalreviewopinionsList || [];
+    },
+
+    // 璁$畻灞炴�э細鏅�氫笓瀹舵暟閲�
+    normalExpertsCount() {
+      return this.ethicalreviewopinionsList.filter(
+        expert => expert.expertType == "0"
       ).length;
     },
-    // 璁$畻灞炴�э細涓诲涓撳鐘舵��
-    chiefExpertStatus() {
-      const chiefExpert = this.expertReviews.find(expert => expert.isChief);
-      return chiefExpert
-        ? this.statusTextFilter(chiefExpert.reviewStatus)
-        : "鏈垎閰�";
-    },
-    // 璁$畻灞炴�э細瀹屾垚杩涘害
-    completionRate() {
-      const totalExperts = this.expertReviews.length;
-      const completedExperts = this.expertReviews.filter(
-        expert => expert.reviewStatus === "submitted"
+
+    // 璁$畻灞炴�э細涓诲涓撳鏁伴噺
+    chiefExpertsCount() {
+      return this.ethicalreviewopinionsList.filter(
+        expert => expert.expertType == "1"
       ).length;
-      return totalExperts > 0
-        ? Math.round((completedExperts / totalExperts) * 100)
-        : 0;
     },
+
+    // 璁$畻灞炴�э細鎬讳笓瀹舵暟閲�
+    totalExpertsCount() {
+      return this.ethicalreviewopinionsList.length;
+    },
+
+    // 璁$畻灞炴�э細宸插悓鎰忎笓瀹舵暟閲�
+    approvedExpertsCount() {
+      return this.ethicalreviewopinionsList.filter(
+        expert => expert.expertconclusion == "1"
+      ).length;
+    },
+
     // 璁$畻灞炴�э細鎬讳綋缁撹
     overallConclusionText() {
-      if (this.approvedNormalExperts >= 12) {
+      const total = this.totalExpertsCount;
+      const approved = this.approvedExpertsCount;
+
+      if (total == 0) return "鏈鏌�";
+      if (approved >= Math.ceil(total * 0.7)) {
+        // 瓒呰繃70%鍚屾剰
         return "閫氳繃";
-      } else if (this.approvedNormalExperts >= 9) {
+      } else if (approved >= Math.ceil(total * 0.5)) {
+        // 瓒呰繃50%鍚屾剰
         return "淇敼鍚庨�氳繃";
       } else {
         return "涓嶉�氳繃";
       }
     },
+
     overallConclusionFilter() {
-      if (this.approvedNormalExperts >= 12) {
+      const total = this.totalExpertsCount;
+      const approved = this.approvedExpertsCount;
+
+      if (total == 0) return "info";
+      if (approved >= Math.ceil(total * 0.7)) {
         return "success";
-      } else if (this.approvedNormalExperts >= 9) {
+      } 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.expertReviews.filter(
-          expert => !expert.isChief && expert.reviewStatus === "applying"
-        ).length > 0
-      );
+      return this.availableNormalExperts.length > 0;
     },
-    // 鏄惁鍙互鍙戦�佺粰涓诲涓撳锛堥渶瑕佽嚦灏�12涓笓瀹跺悓鎰忥級
+
+    // 鏄惁鍙互鍙戦�佺粰涓诲涓撳锛堥渶瑕佽嚦灏�12涓櫘閫氫笓瀹跺悓鎰忥級
     canSendToChiefExpert() {
-      return (
-        this.approvedNormalExperts >= 12 &&
-        this.expertReviews.filter(
-          expert => expert.isChief && expert.reviewStatus === "applying"
-        ).length > 0
-      );
+      const normalApprovedCount = this.ethicalreviewopinionsList.filter(
+        expert => expert.expertType == "0" && expert.expertconclusion == "1"
+      ).length;
+      return this.availableChiefExperts.length > 0 && normalApprovedCount >= 12;
     },
-    // 鏄惁鍙互鎵归噺鍙戦��
-    canBatchSend() {
-      return (
-        this.expertReviews.filter(expert => expert.reviewStatus === "applying")
-          .length > 0
-      );
+
+    // 宸插瓨鍦ㄧ殑涓撳缂栧彿鍒楄〃
+    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() {
@@ -907,6 +1144,12 @@
     this.id = this.$route.query.id;
     this.caseId = this.$route.query.infoid;
     this.getDetail(this.infoid, this.id);
+    // 鐩戝惉璺敱鍙樺寲锛岄槻姝㈢敤鎴风寮�椤甸潰
+    window.addEventListener("beforeunload", this.beforeUnloadHandler);
+  },
+  beforeDestroy() {
+    // 绉婚櫎浜嬩欢鐩戝惉鍣�
+    window.removeEventListener("beforeunload", this.beforeUnloadHandler);
   },
   methods: {
     // 鍒濆鍖栨柊澧炴暟鎹�
@@ -932,7 +1175,7 @@
           response = await reviewinitiateBaseInfoList({ infoid: infoid });
         }
 
-        if (response.code === 200) {
+        if (response.code == 200) {
           let detailData = {};
 
           if (response.data && id) {
@@ -940,16 +1183,25 @@
             // 瑙f瀽 filePatch 瀛楁
             this.parseFilePatch(this.form.filePatch);
             this.initAttachmentFileList();
+
+            if (!this.form.ethicalreviewopinionsList) {
+              this.$set(this.form, "ethicalreviewopinionsList", []);
+            }
           } else if (response.data && infoid) {
             this.form = response.data[0];
-            // 瑙f瀽 filePatch 瀛楁
             this.parseFilePatch(this.form.filePatch);
             this.initAttachmentFileList();
-          }
-          console.log(this.form, "this.form ");
 
-          this.infoid = detailData.infoid || this.infoid;
-          this.caseNo = detailData.caseNo || "";
+            if (!this.form.ethicalreviewopinionsList) {
+              this.$set(this.form, "ethicalreviewopinionsList", []);
+            }
+          }
+
+          // 淇濆瓨鍘熷鏁版嵁鐢ㄤ簬姣旇緝
+          this.saveOriginalData();
+
+          this.expertReviews = this.form.ethicalreviewopinionsList;
+          this.isDataLoaded = true;
 
           this.$message.success("鏁版嵁鍔犺浇鎴愬姛");
         } else {
@@ -961,6 +1213,22 @@
       } finally {
         this.expertLoading = false;
       }
+    },
+
+    // 淇濆瓨鍘熷鏁版嵁
+    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 瀛楁
@@ -982,7 +1250,11 @@
     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),
+          uid:
+            item.id ||
+            Math.random()
+              .toString(36)
+              .substr(2, 9),
           name: item.fileName,
           url: item.path || item.fileUrl,
           status: "success"
@@ -994,7 +1266,7 @@
 
     // 鏋勫缓 filePatch 瀛楁
     buildFilePatch() {
-      if (!this.form.annexfilesList || this.form.annexfilesList.length === 0) {
+      if (!this.form.annexfilesList || this.form.annexfilesList.length == 0) {
         return "";
       }
       return JSON.stringify(this.form.annexfilesList);
@@ -1009,7 +1281,7 @@
     handleAttachmentRemove(file) {
       if (file.url) {
         const index = this.form.annexfilesList.findIndex(
-          item => item.path === file.url || item.fileUrl === file.url
+          item => item.path == file.url || item.fileUrl == file.url
         );
         if (index > -1) {
           this.form.annexfilesList.splice(index, 1);
@@ -1026,7 +1298,7 @@
 
     // 涓婁紶鎴愬姛澶勭悊
     handleUploadSuccess({ file, fileList, response }) {
-      if (response.code === 200) {
+      if (response.code == 200) {
         const attachmentObj = {
           fileName: file.name,
           path: response.data || file.url,
@@ -1048,17 +1320,25 @@
       this.$message.error("鏂囦欢涓婁紶澶辫触锛岃閲嶈瘯");
     },
 
-    // 鎵撳紑涓婁紶瀵硅瘽妗�
-    openUploadDialog() {
-      this.$refs.uploadAttachment.openUpload();
-    },
-
     // 鏂囦欢棰勮
     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;
     },
@@ -1129,53 +1409,93 @@
       }
     },
 
-    // 鑾峰彇涓撳瀹℃煡鍒楄〃
-    getExpertReviews(ethicsReviewId) {
-      this.expertLoading = true;
-      setTimeout(() => {
-        this.expertLoading = false;
-      }, 500);
+    // 鍒ゆ柇鏄惁涓轰富浠诲鍛�
+    getIsChiefExpert(expert) {
+      // 鑱岀О鍖呭惈"涓讳换濮斿憳"鎴栬�卐xpertType涓�"1"
+      return (
+        (expert.title && expert.title.includes("涓讳换濮斿憳")) ||
+        expert.expertType == "1"
+      );
     },
 
-    // 涓撳琛屾牱寮�
-    getExpertRowClassName({ row }) {
-      return row.isChief ? "chief-expert-row" : "normal-expert-row";
+    // 涓撳绫诲瀷鏂囨湰杞崲
+    getExpertTypeText(type) {
+      return type == "1" ? "涓诲涓撳" : "鏅�氫笓瀹�";
     },
 
-    // 鐘舵�佽繃婊ゅ櫒
-    statusFilter(status) {
+    // 瀹℃煡鐘舵�佽繃婊ゅ櫒
+    getReviewStatusFilter(status) {
       const statusMap = {
-        applying: "info",
-        submitted: "success"
+        "0": "info", // 寰呮帴鏀�
+        "1": "warning", // 鏈帴鏀�
+        "2": "success", // 宸叉帴鏀�
+        "3": "danger", // 瓒呮椂
+        "4": "danger", // 涓
+        "5": "success" // 瀹屾垚
       };
       return statusMap[status] || "info";
     },
 
-    statusTextFilter(status) {
+    getReviewStatusText(status) {
       const statusMap = {
-        applying: "鐢宠涓�",
-        submitted: "宸叉彁浜�"
+        "0": "寰呮帴鏀�",
+        "1": "鏈帴鏀�",
+        "2": "宸叉帴鏀�",
+        "3": "瓒呮椂",
+        "4": "涓",
+        "5": "瀹屾垚"
       };
       return statusMap[status] || "鏈煡";
     },
 
     // 缁撹杩囨护鍣�
-    conclusionFilter(conclusion) {
+    getConclusionFilter(conclusion) {
       const conclusionMap = {
-        approved: "success",
-        approved_with_modifications: "warning",
-        disapproved: "danger"
+        "1": "success", // 鍚屾剰
+        "2": "warning", // 瀹℃煡涓�
+        "0": "danger" // 涓嶅悓鎰�
       };
       return conclusionMap[conclusion] || "info";
     },
 
-    conclusionTextFilter(conclusion) {
+    getConclusionText(conclusion) {
       const conclusionMap = {
-        approved: "鍚屾剰",
-        approved_with_modifications: "淇敼鍚庡悓鎰�",
-        disapproved: "涓嶅悓鎰�"
+        "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);
     },
 
     // 淇濆瓨淇℃伅
@@ -1183,6 +1503,10 @@
       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,
@@ -1201,8 +1525,10 @@
               response = await ethicalreviewadd(submitData);
             }
 
-            if (response.code === 200) {
+            if (response.code == 200) {
               this.$message.success("淇濆瓨鎴愬姛");
+              // 淇濆瓨鎴愬姛鍚庢洿鏂板師濮嬫暟鎹�
+              this.saveOriginalData();
               this.isEdit = false;
               if (!this.form.id && response.data && response.data.id) {
                 this.form.id = response.data.id;
@@ -1223,6 +1549,95 @@
       });
     },
 
+    // 瀹℃煡瀹屾垚
+    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(
@@ -1235,10 +1650,11 @@
         }
       )
         .then(async () => {
+          this.endLoading = true;
           try {
             const updateData = {
               ...this.form,
-              status: "2",
+              status: "4", // 瀹℃煡涓
               endTime: new Date()
                 .toISOString()
                 .replace("T", " ")
@@ -1247,9 +1663,9 @@
 
             const response = await ethicalreviewedit(updateData);
 
-            if (response.code === 200) {
+            if (response.code == 200) {
               this.$message.success("瀹℃煡宸茬粨鏉�");
-              this.form.status = "2";
+              this.form.status = "4";
               this.form.endTime = updateData.endTime;
             } else {
               this.$message.error("鎿嶄綔澶辫触锛�" + (response.msg || "鏈煡閿欒"));
@@ -1257,136 +1673,732 @@
           } 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() {
-      const normalExperts = this.expertReviews.filter(
-        expert => !expert.isChief && expert.reviewStatus === "applying"
-      );
-      this.sendForm.expertIds = normalExperts.map(expert => expert.id);
+      this.currentSendExperts = this.availableNormalExperts;
       this.sendForm.expertType = "normal";
+      this.sendForm.endTime = ""; // 閲嶇疆鎴鏃堕棿
       this.sendDialogVisible = true;
     },
 
     // 鍙戦�佺粰涓诲涓撳
     handleSendToChiefExpert() {
-      const chiefExpert = this.expertReviews.find(
-        expert => expert.isChief && expert.reviewStatus === "applying"
-      );
-      if (chiefExpert) {
-        this.sendForm.expertIds = [chiefExpert.id];
-        this.sendForm.expertType = "chief";
-        this.sendDialogVisible = true;
-      }
-    },
-
-    // 鎵归噺鍙戦��
-    handleBatchSend() {
-      const applyingExperts = this.expertReviews.filter(
-        expert => expert.reviewStatus === "applying"
-      );
-      this.sendForm.expertIds = applyingExperts.map(expert => expert.id);
-      this.sendForm.expertType = "batch";
+      this.currentSendExperts = this.availableChiefExperts;
+      this.sendForm.expertType = "chief";
+      this.sendForm.endTime = ""; // 涓诲涓撳鏃犻渶鎴鏃堕棿
       this.sendDialogVisible = true;
     },
 
     // 鍙戦�佺粰鍗曚釜涓撳
     handleSendToExpert(expert) {
-      this.sendForm.expertIds = [expert.id];
-      this.sendForm.expertType = expert.isChief ? "chief" : "normal";
+      this.currentSendExperts = [expert];
+      this.sendForm.expertType = expert.expertType == "1" ? "chief" : "normal";
+      this.sendForm.endTime = expert.expertType == "1" ? "" : ""; // 涓诲涓撳鏃犻渶鎴鏃堕棿
       this.sendDialogVisible = true;
     },
 
-    // 纭鍙戦��
-    handleSendConfirm() {
-      if (this.sendForm.expertIds.length === 0) {
-        this.$message.warning("璇烽�夋嫨瑕佸彂閫佺殑涓撳");
-        return;
-      }
-
-      this.$message.success("鍙戦�佹垚鍔�");
-      this.sendDialogVisible = false;
-
-      this.sendForm.expertIds.forEach(expertId => {
-        const index = this.expertReviews.findIndex(
-          expert => expert.id === expertId
-        );
-        if (index !== -1) {
-          this.expertReviews[index].reviewStatus = "submitted";
-          this.expertReviews[index].sendTime = new Date()
-            .toISOString()
-            .replace("T", " ")
-            .substring(0, 19);
-        }
-      });
-
+    // 鍙戦�佸璇濇鍏抽棴
+    handleSendDialogClose() {
       this.sendForm = {
         expertType: "normal",
         expertIds: [],
-        content: ""
+        startTime: "",
+        endTime: "",
+        sendType: "0",
+        title: "浼︾悊瀹℃煡浠诲姟閫氱煡",
+        content: "",
+        url: ""
       };
+      this.currentSendExperts = [];
     },
 
-    // 缂栬緫涓撳瀹℃煡
-    handleEditExpertReview(expert) {
-      this.$prompt("璇疯緭鍏ュ鏌ユ剰瑙�", "缂栬緫涓撳瀹℃煡", {
+    // 纭鍙戦��
+    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",
+          title: "浼︾悊瀹℃煡浠诲姟閫氱煡",
+          content: "",
+          url: ""
+        };
+        this.currentSendExperts = [];
+        // 淇濆瓨鏁翠釜鍗曟嵁
+        this.handleSave();
+      } catch (error) {
+        console.error("鍙戦�佽繃绋嬩腑鍙戠敓閿欒:", error);
+        progressDialog.close();
+        this.$message.error("鍙戦�佽繃绋嬩腑鍙戠敓閿欒锛岃閲嶈瘯");
+      } finally {
+        this.sendingAll = false;
+      }
+    },
+    // 鍙戦�佸崟涓笓瀹剁殑鏂规硶
+    async sendSingleExpert(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)
+        };
+
+        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"
+        });
+
+        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: "鍙栨秷",
-        inputValue: expert.expertOpinion || "",
-        inputValidator: value => {
-          if (!value || value.trim() === "") {
-            return "瀹℃煡鎰忚涓嶈兘涓虹┖";
-          }
-          return true;
-        }
+        type: "warning"
       })
-        .then(({ value }) => {
-          const index = this.expertReviews.findIndex(e => e.id === expert.id);
-          if (index !== -1) {
-            this.expertReviews[index].expertOpinion = value;
-            this.$message.success("瀹℃煡鎰忚宸叉洿鏂�");
-          }
+        .then(() => {
+          // 浠庢暟缁勪腑鍒犻櫎涓撳
+          this.form.ethicalreviewopinionsList.splice(index, 1);
+          this.$message.success("涓撳瀹℃煡璁板綍宸插垹闄�");
         })
         .catch(() => {});
     },
 
-    // 鏌ョ湅涓撳瀹℃煡璇︽儏
-    handleViewExpertReview(expert) {
-      this.$alert(
-        `
-        <div>
-          <p><strong>涓撳濮撳悕锛�</strong>${expert.expertName}</p>
-          <p><strong>涓撳绫诲瀷锛�</strong>${
-            expert.isChief ? "涓诲涓撳" : "涓撳"
-          }</p>
-          <p><strong>瀹℃煡鐘舵�侊細</strong>${this.statusTextFilter(
-            expert.reviewStatus
-          )}</p>
-          <p><strong>涓撳缁撹锛�</strong>${
-            expert.expertConclusion
-              ? this.conclusionTextFilter(expert.expertConclusion)
-              : "鏈彁浜�"
-          }</p>
-          <p><strong>瀹℃煡鎰忚锛�</strong>${expert.expertOpinion || "鏃�"}</p>
-          <p><strong>瀹℃煡鏃堕棿锛�</strong>${expert.reviewTime || "鏈鏌�"}</p>
-        </div>
-      `,
-        "涓撳瀹℃煡璇︽儏",
-        {
-          dangerouslyUseHTMLString: true,
-          customClass: "expert-review-detail-dialog"
+    // 鏌ョ湅涓撳鍘嗗彶瀹℃壒鎯呭喌
+    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
@@ -1403,8 +2415,6 @@
   }
 };
 </script>
-
-
 
 <style scoped>
 .ethics-review-detail {
@@ -1516,6 +2526,40 @@
 .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;
 }
@@ -1572,10 +2616,82 @@
 .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 {

--
Gitblit v1.9.3