WXL
3 天以前 dc082351978a1e9f75d7a1471a0ca7ebeac552a5
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];
            // 解析 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))
        : [];
    },
    // 解析 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 @@
      // 职称包含"主任委员"或者expertType为"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 = "刷新中...";
                // 延迟执行以确保UI更新
                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: `正在发送通知,请稍候... (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 = `正在发送通知,请稍候... (${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(`正在发送第 ${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;
}
/* 在CSS中添加 */
: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 {