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 |  724 +++++++++++++++++++++++++++++++++++++++++++++++--------
 1 files changed, 620 insertions(+), 104 deletions(-)

diff --git a/src/views/business/ethicalReview/ethicalReviewInfo.vue b/src/views/business/ethicalReview/ethicalReviewInfo.vue
index c783e46..8e56dfa 100644
--- a/src/views/business/ethicalReview/ethicalReviewInfo.vue
+++ b/src/views/business/ethicalReview/ethicalReviewInfo.vue
@@ -55,7 +55,7 @@
               <el-input v-model="form.initiatePerson" />
             </el-form-item>
           </el-col>
-          <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
@@ -66,7 +66,7 @@
                 />
               </el-select>
             </el-form-item>
-          </el-col>
+          </el-col> -->
         </el-row>
 
         <!-- 涓撳鐩稿叧淇℃伅 -->
@@ -215,6 +215,14 @@
       <div slot="header" class="clearfix">
         <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>
@@ -350,7 +358,27 @@
             <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 }">
@@ -435,7 +463,7 @@
     <el-dialog
       title="娣诲姞涓撳"
       :visible.sync="expertDialogVisible"
-      width="800px"
+      width="900px"
       @close="handleExpertDialogClose"
     >
       <div style="margin-bottom: 20px;">
@@ -469,7 +497,7 @@
         :data="filteredExpertList"
         v-loading="expertListLoading"
         style="width: 100%"
-        max-height="400"
+        max-height="600"
         @selection-change="handleExpertSelectionChange"
       >
         <el-table-column type="selection" width="55"></el-table-column>
@@ -505,7 +533,7 @@
         ></el-table-column>
         <el-table-column
           label="鑱旂郴鐢佃瘽"
-          prop="telephone"
+          prop="donorno"
           width="120"
         ></el-table-column>
       </el-table>
@@ -572,7 +600,7 @@
             placeholder="璇烽�夋嫨鎴鏃堕棿"
             value-format="yyyy-MM-dd HH:mm:ss"
             style="width: 100%"
-            :disabled="sendForm.expertType === 'chief'"
+            :disabled="sendForm.expertType == 'chief'"
           />
           <div v-if="sendForm.expertType !== 'chief'" style="margin-top: 5px;">
             <el-button-group>
@@ -585,7 +613,7 @@
             </el-button-group>
           </div>
           <div
-            v-if="sendForm.expertType === 'chief'"
+            v-if="sendForm.expertType == 'chief'"
             style="font-size: 12px; color: #999; margin-top: 5px;"
           >
             涓诲涓撳鏃犻渶璁剧疆鎴鏃堕棿
@@ -627,12 +655,35 @@
       </el-form>
       <div slot="footer">
         <el-button @click="sendDialogVisible = false">鍙栨秷</el-button>
-        <el-button type="primary" @click="handleSendConfirm" :loading="sending"
-          >纭鍙戦��</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="涓撳鍘嗗彶瀹℃壒鎯呭喌"
@@ -764,7 +815,8 @@
   ethicalreviewadd,
   ethicalreviewInfo,
   ethicalreExpertTotal,
-  sendNotification
+  sendNotification,
+  sendcall
 } from "@/api/businessApi";
 import { listExternalperson } from "@/api/project/externalperson";
 import CaseBasicInfo from "@/components/CaseBasicInfo";
@@ -859,7 +911,17 @@
           { 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,
@@ -924,14 +986,14 @@
     // 璁$畻灞炴�э細鏅�氫笓瀹舵暟閲�
     normalExpertsCount() {
       return this.ethicalreviewopinionsList.filter(
-        expert => expert.expertType === "0"
+        expert => expert.expertType == "0"
       ).length;
     },
 
     // 璁$畻灞炴�э細涓诲涓撳鏁伴噺
     chiefExpertsCount() {
       return this.ethicalreviewopinionsList.filter(
-        expert => expert.expertType === "1"
+        expert => expert.expertType == "1"
       ).length;
     },
 
@@ -943,7 +1005,7 @@
     // 璁$畻灞炴�э細宸插悓鎰忎笓瀹舵暟閲�
     approvedExpertsCount() {
       return this.ethicalreviewopinionsList.filter(
-        expert => expert.expertconclusion === "1"
+        expert => expert.expertconclusion == "1"
       ).length;
     },
 
@@ -952,7 +1014,7 @@
       const total = this.totalExpertsCount;
       const approved = this.approvedExpertsCount;
 
-      if (total === 0) return "鏈鏌�";
+      if (total == 0) return "鏈鏌�";
       if (approved >= Math.ceil(total * 0.7)) {
         // 瓒呰繃70%鍚屾剰
         return "閫氳繃";
@@ -968,7 +1030,7 @@
       const total = this.totalExpertsCount;
       const approved = this.approvedExpertsCount;
 
-      if (total === 0) return "info";
+      if (total == 0) return "info";
       if (approved >= Math.ceil(total * 0.7)) {
         return "success";
       } else if (approved >= Math.ceil(total * 0.5)) {
@@ -982,10 +1044,10 @@
     availableNormalExperts() {
       return this.ethicalreviewopinionsList.filter(
         expert =>
-          expert.expertType === "0" &&
+          expert.expertType == "0" &&
           (!expert.receiveStatus ||
-            expert.receiveStatus === "0" ||
-            expert.receiveStatus === "1")
+            expert.receiveStatus == "0" ||
+            expert.receiveStatus == "1")
       );
     },
 
@@ -993,10 +1055,10 @@
     availableChiefExperts() {
       return this.ethicalreviewopinionsList.filter(
         expert =>
-          expert.expertType === "1" &&
+          expert.expertType == "1" &&
           (!expert.receiveStatus ||
-            expert.receiveStatus === "0" ||
-            expert.receiveStatus === "1")
+            expert.receiveStatus == "0" ||
+            expert.receiveStatus == "1")
       );
     },
 
@@ -1008,7 +1070,7 @@
     // 鏄惁鍙互鍙戦�佺粰涓诲涓撳锛堥渶瑕佽嚦灏�12涓櫘閫氫笓瀹跺悓鎰忥級
     canSendToChiefExpert() {
       const normalApprovedCount = this.ethicalreviewopinionsList.filter(
-        expert => expert.expertType === "0" && expert.expertconclusion === "1"
+        expert => expert.expertType == "0" && expert.expertconclusion == "1"
       ).length;
       return this.availableChiefExperts.length > 0 && normalApprovedCount >= 12;
     },
@@ -1059,9 +1121,9 @@
 
     // 鍙戦�佸璇濇鏍囬
     sendDialogTitle() {
-      if (this.sendForm.expertType === "chief") {
+      if (this.sendForm.expertType == "chief") {
         return "鍙戦�佷富濮斾笓瀹跺鏌�";
-      } else if (this.sendForm.expertType === "normal") {
+      } else if (this.sendForm.expertType == "normal") {
         return "鍙戦�佹櫘閫氫笓瀹跺鏌�";
       } else {
         return "鍙戦�佷笓瀹跺鏌�";
@@ -1082,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: {
     // 鍒濆鍖栨柊澧炴暟鎹�
@@ -1107,7 +1175,7 @@
           response = await reviewinitiateBaseInfoList({ infoid: infoid });
         }
 
-        if (response.code === 200) {
+        if (response.code == 200) {
           let detailData = {};
 
           if (response.data && id) {
@@ -1116,24 +1184,24 @@
             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();
 
-            // 濡傛灉涓撳瀹℃煡鎰忚鍒楄〃涓嶅瓨鍦紝鍒濆鍖栦负绌烘暟缁�
             if (!this.form.ethicalreviewopinionsList) {
               this.$set(this.form, "ethicalreviewopinionsList", []);
             }
           }
 
-          // 璁剧疆 expertReviews 鐢ㄤ簬琛ㄦ牸鏄剧ず
+          // 淇濆瓨鍘熷鏁版嵁鐢ㄤ簬姣旇緝
+          this.saveOriginalData();
+
           this.expertReviews = this.form.ethicalreviewopinionsList;
+          this.isDataLoaded = true;
 
           this.$message.success("鏁版嵁鍔犺浇鎴愬姛");
         } else {
@@ -1145,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 瀛楁
@@ -1182,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);
@@ -1197,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);
@@ -1214,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,
@@ -1238,10 +1322,23 @@
 
     // 鏂囦欢棰勮
     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;
     },
@@ -1317,13 +1414,13 @@
       // 鑱岀О鍖呭惈"涓讳换濮斿憳"鎴栬�卐xpertType涓�"1"
       return (
         (expert.title && expert.title.includes("涓讳换濮斿憳")) ||
-        expert.expertType === "1"
+        expert.expertType == "1"
       );
     },
 
     // 涓撳绫诲瀷鏂囨湰杞崲
     getExpertTypeText(type) {
-      return type === "1" ? "涓诲涓撳" : "鏅�氫笓瀹�";
+      return type == "1" ? "涓诲涓撳" : "鏅�氫笓瀹�";
     },
 
     // 瀹℃煡鐘舵�佽繃婊ゅ櫒
@@ -1372,7 +1469,7 @@
 
     // 涓撳琛屾牱寮�
     getExpertRowClassName({ row }) {
-      return row.expertType === "1" ? "chief-expert-row" : "normal-expert-row";
+      return row.expertType == "1" ? "chief-expert-row" : "normal-expert-row";
     },
 
     // 鑾峰彇涓撳鍞竴鏍囪瘑
@@ -1382,7 +1479,7 @@
 
     // 涓撳绫诲瀷鍙樻洿澶勭悊
     handleExpertTypeChange() {
-      if (this.sendForm.expertType === "chief") {
+      if (this.sendForm.expertType == "chief") {
         // 涓诲涓撳鏃犻渶璁剧疆鎴鏃堕棿
         this.sendForm.endTime = "";
       } else {
@@ -1407,9 +1504,9 @@
         if (valid) {
           this.saveLoading = true;
           // 淇濆瓨娓呯┖id渚夸簬鍚庣鏁翠綋鍒犻櫎鏂板
-          this.form.ethicalreviewopinionsList.forEach(item=>{
-            item.id=null
-          })
+          this.form.ethicalreviewopinionsList.forEach(item => {
+            item.id = null;
+          });
           try {
             const submitData = {
               ...this.form,
@@ -1428,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;
@@ -1471,7 +1570,7 @@
 
             const response = await ethicalreviewedit(updateData);
 
-            if (response.code === 200) {
+            if (response.code == 200) {
               this.$message.success("瀹℃煡鐘舵�佸凡鏇存柊涓哄畬鎴�");
               this.form.status = "3";
               this.form.endTime = updateData.endTime;
@@ -1523,7 +1622,7 @@
 
             const response = await ethicalreviewedit(updateData);
 
-            if (response.code === 200) {
+            if (response.code == 200) {
               this.$message.success("瀹℃煡宸蹭腑姝紝鎵�鏈変笓瀹剁姸鎬佸凡鏇存柊");
               this.form.status = "2";
             } else {
@@ -1555,7 +1654,7 @@
           try {
             const updateData = {
               ...this.form,
-              status: "2", // 瀹℃煡涓
+              status: "4", // 瀹℃煡涓
               endTime: new Date()
                 .toISOString()
                 .replace("T", " ")
@@ -1564,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 || "鏈煡閿欒"));
@@ -1586,13 +1685,255 @@
       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: "浼︾悊涓撳", // 浼︾悊涓撳
+          usertype: "ethical", // 浼︾悊涓撳
           pageNum: this.expertPage.pageNum,
           pageSize: this.expertPage.pageSize
         };
@@ -1602,7 +1943,7 @@
         }
 
         const response = await listExternalperson(params);
-        if (response.code === 200) {
+        if (response.code == 200) {
           this.expertList = response.rows || [];
           this.expertTotal = response.total || 0;
         } else {
@@ -1639,7 +1980,7 @@
 
     // 纭娣诲姞涓撳
     handleConfirmAddExpert() {
-      if (this.selectedExperts.length === 0) {
+      if (this.selectedExperts.length == 0) {
         this.$message.warning("璇烽�夋嫨瑕佹坊鍔犵殑涓撳");
         return;
       }
@@ -1664,7 +2005,7 @@
           expertType: isChief ? "1" : "0", // 涓讳换濮斿憳璁剧疆涓轰富濮斾笓瀹�
           deptName: expert.unitname || "",
           title: expert.title || "",
-          deptname: expert.telephone || "",
+          donorno: expert.telephone || "",
           receiveStatus: "0", // 寰呮帴鏀�
           expertconclusion: "",
           expertopinion: "",
@@ -1736,8 +2077,8 @@
     // 鍙戦�佺粰鍗曚釜涓撳
     handleSendToExpert(expert) {
       this.currentSendExperts = [expert];
-      this.sendForm.expertType = expert.expertType === "1" ? "chief" : "normal";
-      this.sendForm.endTime = expert.expertType === "1" ? "" : ""; // 涓诲涓撳鏃犻渶鎴鏃堕棿
+      this.sendForm.expertType = expert.expertType == "1" ? "chief" : "normal";
+      this.sendForm.endTime = expert.expertType == "1" ? "" : ""; // 涓诲涓撳鏃犻渶鎴鏃堕棿
       this.sendDialogVisible = true;
     },
 
@@ -1784,40 +2125,52 @@
         return;
       }
 
-      if (this.currentSendExperts.length === 0) {
+      if (this.currentSendExperts.length == 0) {
         this.$message.warning("娌℃湁鎵惧埌鍙彂閫佺殑涓撳");
         return;
       }
 
-      this.sending = true;
+      // 鍒濆鍖栧彂閫佺姸鎬�
+      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 {
-        // 鍙戦�佺粰姣忎釜涓撳
-        const sendPromises = this.currentSendExperts.map(async expert => {
+        // 浣跨敤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 sendData = {
-              number: expert.deptname || "", // 鐢ㄦ埛鎵嬫満鍙�
-              title: this.sendForm.title,
-              url: this.sendForm.url || "",
+            // 鍙戦�佸崟涓笓瀹堕�氱煡
+            const result = await this.sendSingleExpert(expert, i);
+            this.sendingResults.push(result);
 
-              createTime: new Date()
-                .toISOString()
-                .replace("T", " ")
-                .substring(0, 19)
-            };
+            if (result.success) {
+              this.sendingSuccessCount++;
 
-            // 璋冪敤鍙戦�侀�氱煡鎺ュ彛
-            const response = await sendNotification(sendData);
-
-            if (response.code === 200) {
               // 鏇存柊涓撳鐘舵��
               const index = this.form.ethicalreviewopinionsList.findIndex(
                 e =>
-                  e.expertNo === expert.expertNo ||
-                  e.expertname === expert.expertname
+                  e.expertNo == expert.expertNo ||
+                  e.expertname == expert.expertname
               );
 
-              if (index !== -1) {
+              if (index != -1) {
                 this.form.ethicalreviewopinionsList[index].receiveStatus = "1"; // 宸叉帴鏀�
                 this.form.ethicalreviewopinionsList[
                   index
@@ -1842,42 +2195,49 @@
                   this.form.ethicalreviewopinionsList[index]
                 );
               }
-
-              return { success: true, expert: expert.expertname };
             } else {
-              return {
-                success: false,
-                expert: expert.expertname,
-                error: response.msg
-              };
+              this.sendingFailCount++;
             }
           } catch (error) {
             console.error(`鍙戦�佺粰涓撳 ${expert.expertname} 澶辫触:`, error);
-            return {
+            this.sendingResults.push({
               success: false,
               expert: expert.expertname,
               error: error.message
-            };
+            });
+            this.sendingFailCount++;
           }
-        });
 
-        // 绛夊緟鎵�鏈夊彂閫佸畬鎴�
-        const results = await Promise.all(sendPromises);
-
-        // 缁熻鍙戦�佺粨鏋�
-        const successCount = results.filter(r => r.success).length;
-        const failCount = results.filter(r => !r.success).length;
-
-        if (failCount === 0) {
-          this.$message.success(`鎴愬姛鍙戦�佺粰 ${successCount} 浣嶄笓瀹禶);
-        } else if (successCount > 0) {
-          this.$message.warning(
-            `鎴愬姛鍙戦�佺粰 ${successCount} 浣嶄笓瀹讹紝澶辫触 ${failCount} 浣峘
-          );
-        } else {
-          this.$message.error("鍙戦�佸け璐ワ紝璇风◢鍚庨噸璇�");
+          // 濡傛灉涓嶆槸鏈�鍚庝竴涓紝绛夊緟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",
@@ -1890,14 +2250,99 @@
           url: ""
         };
         this.currentSendExperts = [];
+        // 淇濆瓨鏁翠釜鍗曟嵁
+        this.handleSave();
       } catch (error) {
-        console.error("鍙戦�佸け璐�:", error);
-        this.$message.error("鍙戦�佸け璐ワ紝璇烽噸璇�");
+        console.error("鍙戦�佽繃绋嬩腑鍙戠敓閿欒:", error);
+        progressDialog.close();
+        this.$message.error("鍙戦�佽繃绋嬩腑鍙戠敓閿欒锛岃閲嶈瘯");
       } finally {
-        this.sending = false;
+        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("纭畾瑕佸垹闄よ涓撳鐨勫鏌ヨ褰曞悧锛�", "鎻愮ず", {
@@ -1931,7 +2376,7 @@
 
         const response = await ethicalreExpertTotal(params);
 
-        if (response && response.code === 200) {
+        if (response && response.code == 200) {
           this.expertHistoryData = response.data || response[0] || null;
         } else {
           this.$message.error(
@@ -2171,11 +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