From 68daed8ac76b42542e6ed3fbcac19a057c1dedf0 Mon Sep 17 00:00:00 2001
From: WXL <wl_5969728@163.com>
Date: 星期五, 15 五月 2026 09:22:17 +0800
Subject: [PATCH] 青岛opo维护

---
 src/views/business/ethicalReview/ethicalReviewInfo.vue | 1543 +++++++++++++++++++++++++++++++++++++++++-----------------
 1 files changed, 1,086 insertions(+), 457 deletions(-)

diff --git a/src/views/business/ethicalReview/ethicalReviewInfo.vue b/src/views/business/ethicalReview/ethicalReviewInfo.vue
index 9e76ff2..1c4b908 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,9 +55,6 @@
               <el-input v-model="form.initiatePerson" />
             </el-form-item>
           </el-col>
-        </el-row>
-
-        <el-row :gutter="20">
           <el-col :span="8">
             <el-form-item label="瀹℃煡鐘舵��" prop="status">
               <el-select v-model="form.status" style="width: 100%">
@@ -51,54 +67,13 @@
               </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-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,11 @@
     <!-- 涓撳瀹℃煡鎯呭喌 -->
     <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 size="mini" type="primary" @click="handleAddExpert">
+            娣诲姞涓撳
+          </el-button>
           <el-button
             size="mini"
             type="primary"
@@ -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,11 +349,11 @@
         <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>
@@ -374,23 +361,33 @@
 
         <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.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>
@@ -402,17 +399,29 @@
               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
               size="mini"
               type="text"
               icon="el-icon-edit"
               @click="handleEditExpertReview(scope.row)"
-              :disabled="scope.row.reviewStatus !== 'submitted'"
+              :disabled="!['2', '3'].includes(scope.row.receiveStatus)"
             >
               缂栬緫
             </el-button>
@@ -424,28 +433,187 @@
             >
               璇︽儏
             </el-button>
+            <el-button
+              size="mini"
+              type="text"
+              icon="el-icon-delete"
+              @click="handleDeleteExpertReview(scope.$index)"
+              style="color: #f56c6c;"
+            >
+              鍒犻櫎
+            </el-button>
           </template>
         </el-table-column>
       </el-table>
     </el-card>
 
+    <!-- 娣诲姞涓撳瀵硅瘽妗� -->
+    <el-dialog
+      title="娣诲姞涓撳"
+      :visible.sync="expertDialogVisible"
+      width="800px"
+      @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="expertList"
+        v-loading="expertListLoading"
+        style="width: 100%"
+        max-height="400"
+        @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="telephone"
+          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="expertTotal"
+        ></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"
     >
       <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="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="font-size: 12px; color: #999; margin-top: 5px;"
+          >
+            涓诲涓撳鏃犻渶璁剧疆鎴鏃堕棿
+          </div>
+        </el-form-item>
+
+        <el-form-item label="鍙戦�佹柟寮�" prop="sendType" required>
+          <el-select
+            v-model="sendForm.sendType"
+            placeholder="璇烽�夋嫨鍙戦�佹柟寮�"
+            style="width: 100%"
+          >
+            <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="expertIds"
-          v-if="sendForm.expertType === 'normal'"
+          v-if="sendForm.expertType == 'normal'"
         >
           <el-select
             v-model="sendForm.expertIds"
@@ -454,13 +622,18 @@
             style="width: 100%"
           >
             <el-option
-              v-for="expert in availableExperts"
-              :key="expert.id"
-              :label="expert.name"
-              :value="expert.id"
+              v-for="expert in availableNormalExperts"
+              :key="getExpertKey(expert)"
+              :label="
+                `${expert.expertname}${
+                  expert.expertNo ? '(' + expert.expertNo + ')' : ''
+                }`
+              "
+              :value="getExpertKey(expert)"
             />
           </el-select>
         </el-form-item>
+
         <el-form-item label="鍙戦�佸唴瀹�" prop="content">
           <el-input
             type="textarea"
@@ -472,9 +645,122 @@
       </el-form>
       <div slot="footer">
         <el-button @click="sendDialogVisible = false">鍙栨秷</el-button>
-        <el-button type="primary" @click="handleSendConfirm"
+        <el-button type="primary" @click="handleSendConfirm" :loading="sending"
           >纭鍙戦��</el-button
         >
+      </div>
+    </el-dialog>
+
+    <!-- 涓撳鍘嗗彶瀹℃壒鎯呭喌瀵硅瘽妗� -->
+    <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 +780,10 @@
   reviewinitiateBaseInfoList,
   ethicalreviewedit,
   ethicalreviewadd,
-  ethicalreviewInfo
+  ethicalreviewInfo,
+  ethicalreExpertTotal
 } 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 +792,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 +814,7 @@
         initiatePerson: "",
 
         // 鐘舵�佸拰鏃堕棿
-        status: "0", // 0:鏂板缓, 1:瀹℃煡涓�, 2:缁撴潫
+        status: "0", // 0:寰呭鏌�, 1:瀹℃煡涓�, 2:瀹℃煡涓, 3:瀹℃煡瀹屾垚
         startTime: "",
         cutOffTime: "",
         endTime: "",
@@ -534,7 +822,7 @@
         // 涓撳淇℃伅
         expertName: "",
         expertNo: "",
-        expertType: "normal",
+        expertType: "0",
         expertConclusion: "",
         expertOpinion: "",
         expertTime: "",
@@ -547,6 +835,9 @@
         annexfilesList: [],
         filePatch: "",
 
+        // 涓撳瀹℃煡鎰忚鍒楄〃
+        ethicalreviewopinionsList: [],
+
         // 绯荤粺瀛楁
         createBy: "",
         createTime: "",
@@ -554,6 +845,7 @@
         updateTime: "",
         delFlag: "0"
       },
+
       // 琛ㄥ崟楠岃瘉瑙勫垯
       rules: {
         initiateTheme: [
@@ -571,12 +863,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 +876,13 @@
           { max: 500, message: "闀垮害涓嶈兘瓒呰繃 500 涓瓧绗�", trigger: "blur" }
         ]
       },
+
       // 淇濆瓨鍔犺浇鐘舵��
       saveLoading: false,
+      completeLoading: false,
+      suspendLoading: false,
+      endLoading: false,
+      sending: false,
 
       // 闄勪欢鐩稿叧
       attachmentFileList: [],
@@ -601,305 +892,175 @@
       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: [],
+        startTime: "",
+        endTime: "",
+        sendType: "0",
         content: ""
       },
 
-      // 鍙敤涓撳鍒楄〃
-      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: {},
+
+      // 鍐呴儴鐘舵�佽窡韪�
+      internalExpertList: []
     };
   },
   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
+        this.availableNormalExperts.length > 0 ||
+        this.availableChiefExperts.length > 0
       );
     },
+
     // 褰撳墠鐢ㄦ埛淇℃伅
     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);
+        // 寮哄埗瑙﹀彂璁$畻灞炴�ф洿鏂�
+        this.$forceUpdate();
+      },
+      deep: true
     }
   },
   created() {
@@ -940,16 +1101,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", []);
+            }
+          }
+
+          // 璁剧疆 expertReviews 鐢ㄤ簬琛ㄦ牸鏄剧ず
+          this.expertReviews = this.form.ethicalreviewopinionsList;
 
           this.$message.success("鏁版嵁鍔犺浇鎴愬姛");
         } else {
@@ -982,7 +1152,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"
@@ -1046,11 +1220,6 @@
     handleUploadError({ file, fileList, error }) {
       console.error("闄勪欢涓婁紶澶辫触:", error);
       this.$message.error("鏂囦欢涓婁紶澶辫触锛岃閲嶈瘯");
-    },
-
-    // 鎵撳紑涓婁紶瀵硅瘽妗�
-    openUploadDialog() {
-      this.$refs.uploadAttachment.openUpload();
     },
 
     // 鏂囦欢棰勮
@@ -1129,53 +1298,80 @@
       }
     },
 
-    // 鑾峰彇涓撳瀹℃煡鍒楄〃
-    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 = "";
+      }
     },
 
     // 淇濆瓨淇℃伅
@@ -1223,6 +1419,98 @@
       });
     },
 
+    // 瀹℃煡瀹屾垚
+    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";
+
+              // 瑙﹀彂璁$畻灞炴�ф洿鏂�
+              this.$forceUpdate();
+            } 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 +1523,11 @@
         }
       )
         .then(async () => {
+          this.endLoading = true;
           try {
             const updateData = {
               ...this.form,
-              status: "2",
+              status: "2", // 瀹℃煡涓
               endTime: new Date()
                 .toISOString()
                 .replace("T", " ")
@@ -1257,78 +1546,292 @@
           } catch (error) {
             console.error("缁撴潫瀹℃煡澶辫触:", error);
             this.$message.error("缁撴潫瀹℃煡澶辫触");
+          } finally {
+            this.endLoading = false;
           }
         })
         .catch(() => {});
     },
 
-    // 鍙戦�佺粰涓撳
-    handleSendToNormalExperts() {
-      const normalExperts = this.expertReviews.filter(
-        expert => !expert.isChief && expert.reviewStatus === "applying"
+    // 鎵撳紑娣诲姞涓撳瀵硅瘽妗�
+    handleAddExpert() {
+      this.expertDialogVisible = true;
+      this.loadExperts();
+    },
+
+    // 鍔犺浇涓撳鍒楄〃
+    async loadExperts() {
+      try {
+        this.expertListLoading = true;
+        const params = {
+          usertype: "浼︾悊涓撳", // 浼︾悊涓撳
+          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;
+        } 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", []);
+      }
+
+      // 杩囨护宸插瓨鍦ㄧ殑涓撳
+      const existingExpertIds = this.form.ethicalreviewopinionsList.map(
+        expert => expert.expertNo || expert.expertname
       );
-      this.sendForm.expertIds = normalExperts.map(expert => expert.id);
+      const newExperts = this.selectedExperts.filter(expert => {
+        return !existingExpertIds.includes(expert.userno || expert.username);
+      });
+
+      if (newExperts.length === 0) {
+        this.$message.warning("閫夋嫨鐨勪笓瀹跺凡瀛樺湪");
+        return;
+      }
+
+      // 娣诲姞涓撳鍒板垪琛�
+      newExperts.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 || "",
+          telephone: 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"
+        };
+
+        // 浣跨敤Vue.set纭繚鍝嶅簲寮�
+        this.form.ethicalreviewopinionsList.push(expertReview);
+      });
+
+      // 瑙﹀彂璁$畻灞炴�ф洿鏂�
+      this.$forceUpdate();
+
+      console.log(
+        "娣诲姞涓撳鍚庯紝褰撳墠涓撳鍒楄〃:",
+        this.form.ethicalreviewopinionsList
+      );
+      console.log("鏅�氫笓瀹舵暟閲�:", this.normalExpertsCount);
+      console.log("涓诲涓撳鏁伴噺:", this.chiefExpertsCount);
+      console.log("鍙彂閫佹櫘閫氫笓瀹�:", this.availableNormalExperts);
+
+      this.$message.success(`鎴愬姛娣诲姞 ${newExperts.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() {
+      this.sendForm.expertIds = this.availableNormalExperts.map(expert =>
+        this.getExpertKey(expert)
+      );
       this.sendForm.expertType = "normal";
+      this.sendForm.endTime = ""; // 閲嶇疆鎴鏃堕棿
       this.sendDialogVisible = true;
     },
 
     // 鍙戦�佺粰涓诲涓撳
     handleSendToChiefExpert() {
-      const chiefExpert = this.expertReviews.find(
-        expert => expert.isChief && expert.reviewStatus === "applying"
+      this.sendForm.expertIds = this.availableChiefExperts.map(expert =>
+        this.getExpertKey(expert)
       );
-      if (chiefExpert) {
-        this.sendForm.expertIds = [chiefExpert.id];
-        this.sendForm.expertType = "chief";
-        this.sendDialogVisible = true;
-      }
+      this.sendForm.expertType = "chief";
+      this.sendForm.endTime = ""; // 涓诲涓撳鏃犻渶鎴鏃堕棿
+      this.sendDialogVisible = true;
     },
 
     // 鎵归噺鍙戦��
     handleBatchSend() {
-      const applyingExperts = this.expertReviews.filter(
-        expert => expert.reviewStatus === "applying"
+      const allAvailableExperts = [
+        ...this.availableNormalExperts,
+        ...this.availableChiefExperts
+      ];
+      this.sendForm.expertIds = allAvailableExperts.map(expert =>
+        this.getExpertKey(expert)
       );
-      this.sendForm.expertIds = applyingExperts.map(expert => expert.id);
       this.sendForm.expertType = "batch";
+      this.sendForm.endTime = ""; // 閲嶇疆鎴鏃堕棿
       this.sendDialogVisible = true;
     },
 
     // 鍙戦�佺粰鍗曚釜涓撳
     handleSendToExpert(expert) {
-      this.sendForm.expertIds = [expert.id];
-      this.sendForm.expertType = expert.isChief ? "chief" : "normal";
+      this.sendForm.expertIds = [this.getExpertKey(expert)];
+      this.sendForm.expertType = expert.expertType === "1" ? "chief" : "normal";
+      this.sendForm.endTime = expert.expertType === "1" ? "" : ""; // 涓诲涓撳鏃犻渶鎴鏃堕棿
       this.sendDialogVisible = true;
     },
 
     // 纭鍙戦��
-    handleSendConfirm() {
+    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.expertIds.length === 0) {
         this.$message.warning("璇烽�夋嫨瑕佸彂閫佺殑涓撳");
         return;
       }
 
-      this.$message.success("鍙戦�佹垚鍔�");
-      this.sendDialogVisible = false;
+      this.sending = true;
+      try {
+        // 妯℃嫙鍙戦�佽繃绋�
+        await new Promise(resolve => setTimeout(resolve, 1000));
 
-      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);
-        }
-      });
+        // 鏇存柊涓撳鐘舵��
+        this.sendForm.expertIds.forEach(expertKey => {
+          const index = this.form.ethicalreviewopinionsList.findIndex(
+            expert => this.getExpertKey(expert) === expertKey
+          );
+          if (index !== -1) {
+            this.form.ethicalreviewopinionsList[index].receiveStatus = "2"; // 宸叉帴鏀�
+            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);
 
-      this.sendForm = {
-        expertType: "normal",
-        expertIds: [],
-        content: ""
-      };
+            // 浣跨敤Vue.set纭繚鍝嶅簲寮�
+            this.$set(
+              this.form.ethicalreviewopinionsList,
+              index,
+              this.form.ethicalreviewopinionsList[index]
+            );
+          }
+        });
+
+        this.$message.success("鍙戦�佹垚鍔�");
+        this.sendDialogVisible = false;
+        this.sendForm = {
+          expertType: "normal",
+          expertIds: [],
+          startTime: "",
+          endTime: "",
+          sendType: "0",
+          content: ""
+        };
+
+        // 瑙﹀彂璁$畻灞炴�ф洿鏂�
+        this.$forceUpdate();
+      } catch (error) {
+        console.error("鍙戦�佸け璐�:", error);
+        this.$message.error("鍙戦�佸け璐ワ紝璇烽噸璇�");
+      } finally {
+        this.sending = false;
+      }
     },
 
     // 缂栬緫涓撳瀹℃煡
@@ -1336,7 +1839,8 @@
       this.$prompt("璇疯緭鍏ュ鏌ユ剰瑙�", "缂栬緫涓撳瀹℃煡", {
         confirmButtonText: "纭畾",
         cancelButtonText: "鍙栨秷",
-        inputValue: expert.expertOpinion || "",
+        inputValue: expert.expertopinion || "",
+        inputPlaceholder: "璇疯緭鍏ュ鏌ユ剰瑙�",
         inputValidator: value => {
           if (!value || value.trim() === "") {
             return "瀹℃煡鎰忚涓嶈兘涓虹┖";
@@ -1345,42 +1849,134 @@
         }
       })
         .then(({ value }) => {
-          const index = this.expertReviews.findIndex(e => e.id === expert.id);
+          const index = this.form.ethicalreviewopinionsList.findIndex(
+            e => e.id === expert.id || e.expertNo === expert.expertNo
+          );
           if (index !== -1) {
-            this.expertReviews[index].expertOpinion = value;
+            this.form.ethicalreviewopinionsList[index].expertopinion = value;
+            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]
+            );
+
             this.$message.success("瀹℃煡鎰忚宸叉洿鏂�");
           }
         })
         .catch(() => {});
     },
 
+    // 鏌ョ湅涓撳鍘嗗彶瀹℃壒鎯呭喌
+    async handleViewExpertHistory(expert) {
+      console.log(12);
+
+      if (!expert.expertNo) {
+        this.$message.warning("璇ヤ笓瀹舵病鏈夌紪鍙凤紝鏃犳硶鏌ヨ鍘嗗彶瀹℃壒鎯呭喌");
+        return;
+      }
+
+      this.currentExpertInfo = expert;
+      this.expertHistoryLoading = true;
+      this.expertHistoryDialogVisible = true;
+
+      try {
+        const params = {
+          expertNo: expert.expertNo
+        };
+        console.log(11);
+
+        const response = await ethicalreExpertTotal(params);
+        console.log(response);
+
+        if (response) {
+          this.expertHistoryData = response[0];
+        } else {
+          this.$message.error(
+            "鏌ヨ涓撳鍘嗗彶瀹℃壒鎯呭喌澶辫触锛�" + (response.msg || "鏈煡閿欒")
+          );
+          this.expertHistoryData = null;
+        }
+      } catch (error) {
+        console.error("鏌ヨ涓撳鍘嗗彶瀹℃壒鎯呭喌澶辫触:", error);
+        this.$message.error("鏌ヨ涓撳鍘嗗彶瀹℃壒鎯呭喌澶辫触");
+        this.expertHistoryData = null;
+      } finally {
+        this.expertHistoryLoading = false;
+      }
+    },
+
     // 鏌ョ湅涓撳瀹℃煡璇︽儏
     handleViewExpertReview(expert) {
       this.$alert(
         `
-        <div>
-          <p><strong>涓撳濮撳悕锛�</strong>${expert.expertName}</p>
-          <p><strong>涓撳绫诲瀷锛�</strong>${
-            expert.isChief ? "涓诲涓撳" : "涓撳"
-          }</p>
-          <p><strong>瀹℃煡鐘舵�侊細</strong>${this.statusTextFilter(
-            expert.reviewStatus
+        <div style="line-height: 1.6;">
+          <p><strong>涓撳濮撳悕锛�</strong>${expert.expertname}</p>
+          <p><strong>涓撳缂栧彿锛�</strong>${expert.expertNo || "-"}</p>
+          <p><strong>涓撳绫诲瀷锛�</strong>${this.getExpertTypeText(
+            expert.expertType
+          )}</p>
+          <p><strong>绉戝鍚嶇О锛�</strong>${expert.deptName || "-"}</p>
+          <p><strong>鑱岀О锛�</strong>${expert.title || "-"}</p>
+          <p><strong>鑱旂郴鐢佃瘽锛�</strong>${expert.telephone || "-"}</p>
+          <p><strong>瀹℃煡鐘舵�侊細</strong>${this.getReviewStatusText(
+            expert.receiveStatus
           )}</p>
           <p><strong>涓撳缁撹锛�</strong>${
-            expert.expertConclusion
-              ? this.conclusionTextFilter(expert.expertConclusion)
+            expert.expertconclusion
+              ? this.getConclusionText(expert.expertconclusion)
               : "鏈彁浜�"
           }</p>
-          <p><strong>瀹℃煡鎰忚锛�</strong>${expert.expertOpinion || "鏃�"}</p>
-          <p><strong>瀹℃煡鏃堕棿锛�</strong>${expert.reviewTime || "鏈鏌�"}</p>
+          <p><strong>瀹℃煡鎰忚锛�</strong>${expert.expertopinion || "鏃�"}</p>
+          <p><strong>缁撹椤哄簭锛�</strong>${expert.conclusionorder || "-"}</p>
+          <p><strong>瀹℃煡鏃堕棿锛�</strong>${expert.conclusiontime || "鏈鏌�"}</p>
+          <p><strong>鍙戦�佹椂闂达細</strong>${expert.startTime || "鏈彂閫�"}</p>
+          <p><strong>鎴鏃堕棿锛�</strong>${expert.endTime || "-"}</p>
+          <p><strong>鍙戦�佹柟寮忥細</strong>${
+            expert.sendType
+              ? expert.sendType === "0"
+                ? "绯荤粺鍙戦��"
+                : expert.sendType === "1"
+                ? "閭欢鍙戦��"
+                : expert.sendType === "2"
+                ? "鐭俊鍙戦��"
+                : "鍏朵粬鏂瑰紡"
+              : "-"
+          }</p>
         </div>
       `,
         "涓撳瀹℃煡璇︽儏",
         {
           dangerouslyUseHTMLString: true,
-          customClass: "expert-review-detail-dialog"
+          customClass: "expert-review-detail-dialog",
+          showConfirmButton: false,
+          showCancelButton: true,
+          cancelButtonText: "鍏抽棴"
         }
       );
+    },
+
+    // 鍒犻櫎涓撳瀹℃煡
+    handleDeleteExpertReview(index) {
+      this.$confirm("纭畾瑕佸垹闄よ涓撳鐨勫鏌ヨ褰曞悧锛�", "鎻愮ず", {
+        confirmButtonText: "纭畾",
+        cancelButtonText: "鍙栨秷",
+        type: "warning"
+      })
+        .then(() => {
+          this.form.ethicalreviewopinionsList.splice(index, 1);
+
+          // 瑙﹀彂璁$畻灞炴�ф洿鏂�
+          this.$forceUpdate();
+
+          this.$message.success("涓撳瀹℃煡璁板綍宸插垹闄�");
+        })
+        .catch(() => {});
     },
 
     // 鏃堕棿鏍煎紡鍖�
@@ -1403,8 +1999,6 @@
   }
 };
 </script>
-
-
 
 <style scoped>
 .ethics-review-detail {
@@ -1516,6 +2110,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;
 }
@@ -1576,6 +2204,7 @@
 .case-info-card {
   border-left: 4px solid #67c23a;
 }
+
 /* 鍝嶅簲寮忚璁� */
 @media (max-width: 768px) {
   .ethics-review-detail {

--
Gitblit v1.9.3