WXL
4 天以前 c4599aa279fd2f90b741608392e056fbae2282d5
src/views/business/course/components/EthicalReviewStage.vue
@@ -1,206 +1,2358 @@
<template>
  <base-stage :stage-data="stageData" :case-info="caseInfo">
    <template #header>
      <el-alert
        title="伦理审查阶段"
        :type="getAlertType()"
        :description="getAlertDescription()"
        show-icon
        :closable="false"
      />
    </template>
  <div class="ethics-review-detail">
    <!-- <case-basic-info :case-id="caseId" :show-attachment="true":column="3" /> -->
    <el-row :gutter="20" style="margin-top: 20px;">
      <el-col :span="12">
        <el-card>
          <div slot="header" class="card-header">
            <span>审查委员会信息</span>
          </div>
          <el-descriptions :column="1" border>
            <el-descriptions-item label="委员会名称">
              {{ reviewCommittee.name }}
            </el-descriptions-item>
            <el-descriptions-item label="审查会议时间">
              {{ formatTime(reviewCommittee.meetingTime) }}
            </el-descriptions-item>
            <el-descriptions-item label="参会委员">
              {{ reviewCommittee.members.length }} 人
            </el-descriptions-item>
            <el-descriptions-item label="审查结论">
              <el-tag :type="reviewCommittee.conclusion ? 'success' : 'warning'">
                {{ reviewCommittee.conclusion ? '审查通过' : '审查中' }}
              </el-tag>
            </el-descriptions-item>
            <el-descriptions-item label="主席签字">
              {{ reviewCommittee.chairman }}
            </el-descriptions-item>
          </el-descriptions>
        </el-card>
      </el-col>
      <el-col :span="12">
        <el-card>
          <div slot="header" class="card-header">
            <span>审查流程进度</span>
          </div>
          <el-steps direction="vertical" :active="reviewProgress.active" space="80px">
            <el-step
              v-for="step in reviewProgress.steps"
              :key="step.title"
              :title="step.title"
              :description="step.description"
              :status="step.status"
            />
          </el-steps>
        </el-card>
      </el-col>
    </el-row>
    <el-card style="margin-top: 20px;">
      <div slot="header" class="card-header">
        <span>委员审查意见</span>
    <el-card class="detail-card">
      <!-- 伦理审查基本信息 -->
      <div slot="header" class="clearfix">
        <span class="detail-title">伦理审查基本信息</span>
      </div>
      <el-table :data="reviewComments" border>
        <el-table-column label="委员姓名" prop="memberName" width="120" />
        <el-table-column label="专业领域" prop="specialty" width="120" />
        <el-table-column label="审查意见" prop="comment" min-width="200" />
        <el-table-column label="投票结果" width="100">
      <el-descriptions title="基本信息" :column="descriptionColumn" border size="small">
        <el-descriptions-item label="发起主题">{{ form.initiateTheme || '-' }}</el-descriptions-item>
        <el-descriptions-item label="发起人">{{ form.initiatePerson || '-' }}</el-descriptions-item>
      </el-descriptions>
      <el-descriptions title="审查结论" :column="descriptionColumn" border size="small" style="margin-top: 16px;">
        <el-descriptions-item label="伦理审查结论">{{ expertConclusionLabel }}</el-descriptions-item>
        <el-descriptions-item label="伦理审查结论时间">{{ form.expertTime || '-' }}</el-descriptions-item>
        <el-descriptions-item label="审查意见" :span="descriptionColumn">{{ form.expertOpinion || '-' }}</el-descriptions-item>
        <el-descriptions-item label="备注" :span="descriptionColumn">{{ form.remark || '-' }}</el-descriptions-item>
      </el-descriptions>
    </el-card>
    <!-- 相关附件 -->
    <el-card class="attachment-card">
      <div slot="header" class="clearfix">
        <span class="detail-title">相关附件</span>
      </div>
      <!-- 附件列表 -->
      <div
        class="attachment-list"
        v-if="form.annexfilesList && form.annexfilesList.length > 0"
      >
        <div class="list-title">
          已上传附件 ({{ form.annexfilesList.length }})
        </div>
        <el-table :data="form.annexfilesList" style="width: 100%" size="small">
          <el-table-column label="文件名" min-width="200">
            <template slot-scope="scope">
              <i
                class="el-icon-document"
                style="margin-right: 8px; color: #409EFF;"
              ></i>
              <span class="file-name">{{ scope.row.fileName }}</span>
            </template>
          </el-table-column>
          <el-table-column label="文件类型" width="100">
            <template slot-scope="scope">
              <el-tag size="small">{{
                getFileType(scope.row.fileName)
              }}</el-tag>
            </template>
          </el-table-column>
          <el-table-column label="创建时间" width="160">
            <template slot-scope="scope">
              <span>{{ formatDateTime(scope.row.createTime) }}</span>
            </template>
          </el-table-column>
          <el-table-column label="操作" width="180">
            <template slot-scope="scope">
              <el-button
                size="mini"
                type="primary"
                @click="handlePreview(scope.row)"
              >
                预览
              </el-button>
              <el-button
                size="mini"
                type="success"
                @click="handleDownload(scope.row)"
              >
                下载
              </el-button>
            </template>
          </el-table-column>
        </el-table>
      </div>
      <!-- 空状态 -->
      <div
        v-if="!form.annexfilesList || form.annexfilesList.length == 0"
        class="empty-attachment"
      >
        <i
          class="el-icon-folder-opened"
          style="font-size: 60px; color: #C0C4CC; margin-bottom: 20px;"
        ></i>
        <p style="color: #909399; font-size: 14px;">暂无附件</p>
      </div>
    </el-card>
    <!-- 专家审查情况 -->
    <el-card class="expert-card">
      <div slot="header" class="clearfix">
        <span class="detail-title">专家审查情况</span>
      </div>
      <!-- 专家统计信息 -->
      <div
        class="expert-stats"
        style="margin-top: 20px; padding: 15px; background: #f5f7fa; border-radius: 4px;"
      >
        <el-row :gutter="20">
          <el-col :span="6">
            <div class="stat-item">
              <span class="stat-label">普通专家:</span>
              <span class="stat-value">{{ normalExpertsCount }}人</span>
            </div>
          </el-col>
          <el-col :span="6">
            <div class="stat-item">
              <span class="stat-label">主委专家:</span>
              <span class="stat-value">{{ chiefExpertsCount }}人</span>
            </div>
          </el-col>
          <el-col :span="6">
            <div class="stat-item">
              <span class="stat-label">已同意:</span>
              <span class="stat-value"
                >{{ approvedExpertsCount }}/{{ totalExpertsCount }}</span
              >
            </div>
          </el-col>
          <el-col :span="6">
            <div class="stat-item">
              <span class="stat-label">审查结果:</span>
              <span class="stat-value">
                <el-tag :type="overallConclusionFilter">
                  {{ overallConclusionText }}
                </el-tag>
              </span>
            </div>
          </el-col>
        </el-row>
      </div>
      <!-- 专家审查表格 -->
      <el-table
        :data="ethicalreviewopinionsList"
        v-loading="expertLoading"
        style="width: 100%"
        height="600"
        :row-class-name="getExpertRowClassName"
      >
        <el-table-column label="序号" width="60" align="center" type="index" />
        <el-table-column
          label="专家姓名"
          width="120"
          align="center"
          fixed="left"
        >
          <template slot-scope="scope">
            <el-tag :type="scope.row.vote === '同意' ? 'success' : 'danger'">
              {{ scope.row.vote }}
            <span
              class="expert-name-link"
              @click="handleViewExpertHistory(scope.row)"
            >
              {{ scope.row.expertname }}
            </span>
            <el-tag
              v-if="scope.row.expertType == '1'"
              size="mini"
              type="danger"
              style="margin-left: 5px;"
              >主委</el-tag
            >
          </template>
        </el-table-column>
        <el-table-column label="专家编号" width="120" align="center">
          <template slot-scope="scope">
            <span>{{ scope.row.expertNo || "-" }}</span>
          </template>
        </el-table-column>
        <el-table-column label="专家类型" width="100" align="center">
          <template slot-scope="scope">
            <span
              :class="
                scope.row.expertType == '1' ? 'chief-expert' : 'normal-expert'
              "
            >
              {{ getExpertTypeText(scope.row.expertType) }}
            </span>
          </template>
        </el-table-column>
        <el-table-column label="审查状态" width="100" align="center">
          <template slot-scope="scope">
            <el-tag
              :type="getReviewStatusFilter(scope.row.receiveStatus)"
              size="small"
            >
              {{ getReviewStatusText(scope.row.receiveStatus) }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column label="审查时间" width="160">
        <el-table-column label="专家结论" width="120" align="center">
          <template slot-scope="scope">
            {{ formatTime(scope.row.reviewTime) }}
            <el-tag
              v-if="scope.row.expertconclusion"
              :type="getConclusionFilter(scope.row.expertconclusion)"
              size="small"
            >
              {{ getConclusionText(scope.row.expertconclusion) }}
            </el-tag>
            <span v-else class="no-data">-</span>
          </template>
        </el-table-column>
        <!-- 在"审查时间"列后面添加"手签附件"列 -->
        <el-table-column label="手签附件" width="120" align="center">
          <template slot-scope="scope">
            <template v-if="scope.row.sigin">
              <!-- 有签名,显示可点击预览的图片 -->
              <el-button
                type="text"
                size="mini"
                @click="handlePreviewSignature(scope.row.sigin)"
                class="signature-preview-btn"
              >
                <i class="el-icon-picture" style="margin-right: 4px;"></i>
                查看签名
              </el-button>
            </template>
            <template v-else>
              <!-- 无签名,显示斜杠 -->
              <span class="no-signature">/</span>
            </template>
          </template>
        </el-table-column>
        <el-table-column label="审查意见" min-width="200" show-overflow-tooltip>
          <template slot-scope="scope">
            <span :class="{ 'expert-opinion': scope.row.expertopinion }">
              {{ scope.row.expertopinion || "暂无意见" }}
            </span>
          </template>
        </el-table-column>
        <el-table-column label="结论顺序" width="100" align="center">
          <template slot-scope="scope">
            <span>{{ scope.row.conclusionorder || "-" }}</span>
          </template>
        </el-table-column>
        <el-table-column label="截止时间" width="160" align="center">
          <template slot-scope="scope">
            <span>{{
              scope.row.endTime ? formatDateTime(scope.row.endTime) : "未设置"
            }}</span>
          </template>
        </el-table-column>
        <el-table-column label="审查时间" width="160" align="center">
          <template slot-scope="scope">
            <span>{{
              scope.row.conclusiontime
                ? formatDateTime(scope.row.conclusiontime)
                : "未审查"
            }}</span>
          </template>
        </el-table-column>
        <el-table-column label="发送时间" width="160" align="center">
          <template slot-scope="scope">
            <span>{{
              scope.row.startTime
                ? formatDateTime(scope.row.startTime)
                : "未发送"
            }}</span>
          </template>
        </el-table-column>
      </el-table>
    </el-card>
    <el-card style="margin-top: 20px;">
      <div slot="header" class="card-header">
        <span>审查决议文件</span>
        <el-button type="primary" size="small" @click="handleViewResolution">
          查看决议文件
        </el-button>
      </div>
      <div class="resolution-content">
        <p><strong>伦理审查决议:</strong></p>
        <p>{{ resolutionContent }}</p>
        <el-divider />
        <div class="signature-area">
          <p>伦理委员会主席:{{ reviewCommittee.chairman }}</p>
          <p>日期:{{ formatTime(reviewCommittee.meetingTime) }}</p>
    <!-- 专家历史审批情况对话框 -->
    <el-dialog
      title="专家历史审批情况"
      :visible.sync="expertHistoryDialogVisible"
      width="600px"
    >
      <div v-loading="expertHistoryLoading">
        <div v-if="expertHistoryData" style="padding: 20px;">
          <el-row :gutter="20" style="margin-bottom: 20px;">
            <el-col :span="12">
              <div class="history-stat-item">
                <div class="history-stat-label">专家姓名</div>
                <div class="history-stat-value">
                  {{ currentExpertInfo.expertname || "-" }}
                </div>
              </div>
            </el-col>
            <el-col :span="12">
              <div class="history-stat-item">
                <div class="history-stat-label">专家编号</div>
                <div class="history-stat-value">
                  {{ currentExpertInfo.expertNo || "-" }}
                </div>
              </div>
            </el-col>
          </el-row>
          <el-divider></el-divider>
          <el-row :gutter="20">
            <el-col :span="12">
              <div class="history-stat-item">
                <div class="history-stat-label">总审批数量</div>
                <div
                  class="history-stat-value"
                  style="font-size: 24px; color: #409EFF;"
                >
                  {{ expertHistoryData.count || 0 }}
                </div>
              </div>
            </el-col>
            <el-col :span="12">
              <div class="history-stat-item">
                <div class="history-stat-label">已接收数量</div>
                <div class="history-stat-value">
                  {{ expertHistoryData.acceptCount || 0 }}
                </div>
              </div>
            </el-col>
          </el-row>
          <el-row :gutter="20" style="margin-top: 20px;">
            <el-col :span="12">
              <div class="history-stat-item">
                <div class="history-stat-label">未接收数量</div>
                <div class="history-stat-value">
                  {{ expertHistoryData.notAcceptCount || 0 }}
                </div>
              </div>
            </el-col>
            <el-col :span="12">
              <div class="history-stat-item">
                <div class="history-stat-label">有意见数量</div>
                <div class="history-stat-value">
                  {{ expertHistoryData.opinionCount || 0 }}
                </div>
              </div>
            </el-col>
          </el-row>
          <el-row :gutter="20" style="margin-top: 20px;">
            <el-col :span="12">
              <div class="history-stat-item">
                <div class="history-stat-label">无意见数量</div>
                <div class="history-stat-value">
                  {{ expertHistoryData.notOpinionCount || 0 }}
                </div>
              </div>
            </el-col>
            <el-col :span="12">
              <div class="history-stat-item">
                <div class="history-stat-label">有附件数量</div>
                <div class="history-stat-value">
                  {{ expertHistoryData.annexCount || 0 }}
                </div>
              </div>
            </el-col>
          </el-row>
          <el-row :gutter="20" style="margin-top: 20px;">
            <el-col :span="12">
              <div class="history-stat-item">
                <div class="history-stat-label">无附件数量</div>
                <div class="history-stat-value">
                  {{ expertHistoryData.notAnnexCount || 0 }}
                </div>
              </div>
            </el-col>
          </el-row>
        </div>
        <div v-else style="text-align: center; padding: 40px 0;">
          <i
            class="el-icon-info"
            style="font-size: 60px; color: #C0C4CC; margin-bottom: 20px;"
          ></i>
          <p style="color: #909399; font-size: 14px;">暂无历史审批数据</p>
        </div>
      </div>
    </el-card>
  </base-stage>
      <div slot="footer">
        <el-button @click="expertHistoryDialogVisible = false">关闭</el-button>
      </div>
    </el-dialog>
    <!-- 文件预览弹窗 -->
    <FilePreviewDialog
      :visible="previewVisible"
      :file="currentPreviewFile"
      @close="previewVisible = false"
      @download="handleDownload"
    />
  </div>
</template>
<script>
import BaseStage from './BaseStage.vue';
import { getToken } from "@/utils/auth";
import {
  reviewinitiateBaseInfoList,
  ethicalreviewedit,
  ethicalreviewadd,
  ethicalreviewInfo,
  ethicalreExpertTotal,
  sendNotification,
  sendcall
} from "@/api/businessApi";
import { listExternalperson } from "@/api/project/externalperson";
import CaseBasicInfo from "@/components/CaseBasicInfo";
import FilePreviewDialog from "@/components/FilePreviewDialog";
import dayjs from "dayjs";
import readonlyDescriptionsMixin from "./readonlyDescriptionsMixin";
export default {
  name: 'EthicalReviewStage',
  components: { BaseStage },
  name: "EthicsReviewDetail",
  components: { CaseBasicInfo, FilePreviewDialog },
  mixins: [readonlyDescriptionsMixin],
  dicts: ["sys_user_sex", "sys_ethical", "Review_status"],
  props: {
    stageData: {
      type: Object,
      default: () => ({})
    },
    caseInfo: {
      type: Object,
      default: () => ({})
    infoid: {
      type: String,
      default: true
    }
  },
  data() {
    return {
      reviewCommittee: {
        name: '医院伦理审查委员会',
        meetingTime: '2023-12-03 15:20:00',
        members: ['张教授', '李主任', '王医生', '赵委员', '钱专家'],
        conclusion: true,
        chairman: '张教授'
      // 页面模式
      isEdit: false,
      // 基本信息
      id: undefined,
      caseId: null,
      caseNo: "",
      // 表单数据
      form: {
        // 基础信息
        id: undefined,
        infoid: undefined,
        caseNo: "",
        initiateTheme: "",
        initiatePerson: "",
        // 状态和时间
        status: "0", // 0:待审查, 1:审查中, 2:审查中止, 3:审查完成
        startTime: "",
        cutOffTime: "",
        endTime: "",
        // 专家信息
        expertName: "",
        expertNo: "",
        expertType: "0",
        expertConclusion: "",
        expertOpinion: "",
        expertTime: "",
        orderNo: 1,
        // 备注
        remark: "",
        // 附件信息
        annexfilesList: [],
        filePatch: "",
        // 专家审查意见列表
        ethicalreviewopinionsList: [],
        // 系统字段
        createBy: "",
        createTime: "",
        updateBy: "",
        updateTime: "",
        delFlag: "0"
      },
      reviewProgress: {
        active: 4,
        steps: [
      // 表单验证规则
      rules: {
        initiateTheme: [
          { required: true, message: "发起主题不能为空", trigger: "blur" },
          {
            title: '材料初审',
            description: '申请材料完整性审查',
            status: 'finish'
          },
          {
            title: '委员评审',
            description: '各委员独立审查',
            status: 'finish'
          },
          {
            title: '会议讨论',
            description: '委员会集体讨论',
            status: 'finish'
          },
          {
            title: '形成决议',
            description: '投票形成最终决议',
            status: 'finish'
            min: 2,
            max: 100,
            message: "长度在 2 到 100 个字符",
            trigger: "blur"
          }
        ],
        initiatePerson: [
          { required: true, message: "发起人不能为空", trigger: "blur" }
        ],
        status: [
          { required: true, message: "审查状态不能为空", trigger: "change" }
        ],
        expertName: [
          { max: 50, message: "长度不能超过 50 个字符", trigger: "blur" }
        ],
        expertNo: [
          { max: 50, message: "长度不能超过 50 个字符", trigger: "blur" }
        ],
        expertConclusion: [
          { max: 2, message: "长度不能超过 2 个字符", trigger: "change" }
        ],
        remark: [
          { max: 500, message: "长度不能超过 500 个字符", trigger: "blur" }
        ]
      },
      reviewComments: [
        {
          memberName: '张教授',
          specialty: '医学伦理',
          comment: '捐献程序符合伦理规范,同意通过',
          vote: '同意',
          reviewTime: '2023-12-03 14:30:00'
        },
        {
          memberName: '李主任',
          specialty: '临床医学',
          comment: '医疗程序规范,无伦理问题',
          vote: '同意',
          reviewTime: '2023-12-03 14:45:00'
        },
        {
          memberName: '王医生',
          specialty: '法律医学',
          comment: '法律文件齐全,程序合法',
          vote: '同意',
          reviewTime: '2023-12-03 15:00:00'
        }
      ],
      resolutionContent: '经伦理审查委员会审查,该器官捐献案例符合医学伦理要求,捐献程序规范,家属意愿真实有效,同意进行器官捐献。'
      sending: false, // 单个发送状态
      sendingAll: false, // 全局发送状态
      sendingProgress: 0, // 发送进度
      sendingTotal: 0, // 总发送数
      sendingSuccessCount: 0, // 成功数
      sendingFailCount: 0, // 失败数
      sendingResults: [], // 发送结果列表
      originalFormData: null, // 原始表单数据
      originalExpertList: null, // 原始专家列表
      originalAttachments: null, // 原始附件列表
      isDataLoaded: false, // 数据是否已加载
      // 保存加载状态
      saveLoading: false,
      completeLoading: false,
      suspendLoading: false,
      endLoading: false,
      sending: false,
      // 附件相关
      attachmentFileList: [],
      // 预览相关
      previewVisible: false,
      currentPreviewFile: null,
      // 专家审查数据
      expertReviews: [],
      expertLoading: false,
      attachmentLoading: false,
      // 添加专家对话框
      expertDialogVisible: false,
      expertList: [],
      expertListLoading: false,
      expertSearchQuery: "",
      filterExpertType: "",
      expertPage: {
        pageNum: 1,
        pageSize: 50
      },
      expertTotal: 0,
      selectedExperts: [],
      // 发送对话框
      sendDialogVisible: false,
      sendForm: {
        expertType: "normal",
        expertIds: [],
        startTime: "",
        endTime: "",
        sendType: "0",
        content: "",
        url: ""
      },
      // 专家历史审批情况
      expertHistoryDialogVisible: false,
      expertHistoryLoading: false,
      expertHistoryData: null,
      currentExpertInfo: {},
      // 当前发送的专家
      currentSendExperts: []
    };
  },
  computed: {
    // 计算属性:获取专家列表(确保响应式)
    ethicalreviewopinionsList() {
      return this.form.ethicalreviewopinionsList || [];
    },
    // 伦理审查结论文本(只读展示)
    expertConclusionLabel() {
      const map = { 0: "不同意", 1: "同意", 2: "审查中" };
      return map[this.form.expertConclusion] || "-";
    },
    // 计算属性:普通专家数量
    normalExpertsCount() {
      return this.ethicalreviewopinionsList.filter(
        expert => expert.expertType == "0"
      ).length;
    },
    // 计算属性:主委专家数量
    chiefExpertsCount() {
      return this.ethicalreviewopinionsList.filter(
        expert => expert.expertType == "1"
      ).length;
    },
    // 计算属性:总专家数量
    totalExpertsCount() {
      return this.ethicalreviewopinionsList.length;
    },
    // 计算属性:已同意专家数量
    approvedExpertsCount() {
      return this.ethicalreviewopinionsList.filter(
        expert => expert.expertconclusion == "1"
      ).length;
    },
    // 计算属性:总体结论
    overallConclusionText() {
      const total = this.totalExpertsCount;
      const approved = this.approvedExpertsCount;
      if (total == 0) return "未审查";
      if (approved >= Math.ceil(total * 0.7)) {
        // 超过70%同意
        return "通过";
      } else if (approved >= Math.ceil(total * 0.5)) {
        // 超过50%同意
        return "修改后通过";
      } else {
        return "不通过";
      }
    },
    overallConclusionFilter() {
      const total = this.totalExpertsCount;
      const approved = this.approvedExpertsCount;
      if (total == 0) return "info";
      if (approved >= Math.ceil(total * 0.7)) {
        return "success";
      } else if (approved >= Math.ceil(total * 0.5)) {
        return "warning";
      } else {
        return "danger";
      }
    },
    // 可发送的普通专家
    availableNormalExperts() {
      return this.ethicalreviewopinionsList.filter(
        expert =>
          expert.expertType == "0" &&
          (!expert.receiveStatus ||
            expert.receiveStatus == "0" ||
            expert.receiveStatus == "1")
      );
    },
    // 可发送的主委专家
    availableChiefExperts() {
      return this.ethicalreviewopinionsList.filter(
        expert =>
          expert.expertType == "1" &&
          (!expert.receiveStatus ||
            expert.receiveStatus == "0" ||
            expert.receiveStatus == "1")
      );
    },
    // 是否可以发送给普通专家
    canSendToNormalExperts() {
      return this.availableNormalExperts.length > 0;
    },
    // 是否可以发送给主委专家(需要至少12个普通专家同意)
    canSendToChiefExpert() {
      const normalApprovedCount = this.ethicalreviewopinionsList.filter(
        expert => expert.expertType == "0" && expert.expertconclusion == "1"
      ).length;
      return this.availableChiefExperts.length > 0 && normalApprovedCount >= 12;
    },
    // 已存在的专家编号列表
    existingExpertNos() {
      return this.ethicalreviewopinionsList
        .map(expert => expert.expertNo)
        .filter(no => no);
    },
    // 已存在的专家姓名列表
    existingExpertNames() {
      return this.ethicalreviewopinionsList
        .map(expert => expert.expertname)
        .filter(name => name);
    },
    // 过滤后的专家列表(排除已存在的专家)
    filteredExpertList() {
      if (!this.expertList.length) return [];
      return this.expertList.filter(expert => {
        // 如果专家有编号,检查编号是否已存在
        if (expert.userno && this.existingExpertNos.includes(expert.userno)) {
          return false;
        }
        // 如果专家有姓名,检查姓名是否已存在
        if (
          expert.username &&
          this.existingExpertNames.includes(expert.username)
        ) {
          return false;
        }
        return true;
      });
    },
    // 过滤后的专家总数
    filteredExpertTotal() {
      return this.filteredExpertList.length;
    },
    // 当前用户信息
    currentUser() {
      return JSON.parse(sessionStorage.getItem("user") || "{}");
    },
    // 发送对话框标题
    sendDialogTitle() {
      if (this.sendForm.expertType == "chief") {
        return "发送主委专家审查";
      } else if (this.sendForm.expertType == "normal") {
        return "发送普通专家审查";
      } else {
        return "发送专家审查";
      }
    }
  },
  watch: {
    // 监听表单中的专家列表变化
    "form.ethicalreviewopinionsList": {
      handler(newVal) {
        console.log("专家列表变化:", newVal);
      },
      deep: true
    }
  },
  created() {
    this.infoid = this.infoid;
    this.caseId = this.infoid;
    this.getDetail(this.infoid, this.id);
    // 监听路由变化,防止用户离开页面
    window.addEventListener("beforeunload", this.beforeUnloadHandler);
  },
  beforeDestroy() {
    // 移除事件监听器
    window.removeEventListener("beforeunload", this.beforeUnloadHandler);
  },
  methods: {
    getAlertType() {
      const status = this.stageData.status;
      return status === 'completed' ? 'success' :
             status === 'in_progress' ? 'warning' : 'info';
    // 初始化新增数据
    initNewData() {
      this.form.infoid = this.infoid;
      this.form.caseNo = this.$route.query.caseNo || "";
      this.form.initiatePerson = this.currentUser.username || "当前用户";
      this.form.startTime = new Date()
        .toISOString()
        .replace("T", " ")
        .substring(0, 19);
      this.form.createBy = this.currentUser.username || "admin";
    },
    getAlertDescription() {
      const status = this.stageData.status;
      return status === 'completed' ? '伦理审查已通过,可以进行器官分配' :
             status === 'in_progress' ? '伦理审查流程正在进行中' : '等待开始伦理审查流程';
    // 获取详情
    async getDetail(infoid, id) {
      try {
        this.expertLoading = true;
        let response = {};
        if (id) {
          response = await ethicalreviewInfo(id);
        } else if (infoid) {
          response = await reviewinitiateBaseInfoList({ infoid: infoid });
          if (response.data[0].id) {
            this.id = response.data[0].id;
            response = await ethicalreviewInfo(this.id);
          }
        }
        if (
          !response.data.status ||
          response.data.status == 0 ||
          response.data.status == 1
        ) {
          response.data.status = "2";
        }
        if (response.code == 200) {
          let detailData = {};
          if (response.data && this.id) {
            this.form = response.data;
            // 解析 filePatch 字段
            this.parseFilePatch(this.form.filePatch);
            this.initAttachmentFileList();
            if (!this.form.ethicalreviewopinionsList) {
              this.$set(this.form, "ethicalreviewopinionsList", []);
            }
          } else if (response.data && this.infoid) {
            this.form = response.data[0];
            this.parseFilePatch(this.form.filePatch);
            this.initAttachmentFileList();
            if (!this.form.ethicalreviewopinionsList) {
              this.$set(this.form, "ethicalreviewopinionsList", []);
            }
          }
          // 保存原始数据用于比较
          this.saveOriginalData();
          this.expertReviews = this.form.ethicalreviewopinionsList;
          this.isDataLoaded = true;
          this.$message.success("数据加载成功");
        } else {
          this.$message.error("获取详情失败:" + (response.msg || "未知错误"));
        }
      } catch (error) {
        console.error("获取伦理审查详情失败:", error);
        this.$message.error("数据加载失败");
      } finally {
        this.expertLoading = false;
      }
    },
    handleViewResolution() {
      this.$message.info('查看伦理审查决议文件功能');
    // 保存原始数据
    saveOriginalData() {
      // 深拷贝表单数据
      this.originalFormData = JSON.parse(JSON.stringify(this.form));
      // 深拷贝专家列表
      this.originalExpertList = this.form.ethicalreviewopinionsList
        ? JSON.parse(JSON.stringify(this.form.ethicalreviewopinionsList))
        : [];
      // 深拷贝附件列表
      this.originalAttachments = this.form.annexfilesList
        ? JSON.parse(JSON.stringify(this.form.annexfilesList))
        : [];
    },
    // 解析 filePatch 字段
    parseFilePatch(filePatch) {
      if (!filePatch) {
        this.form.annexfilesList = [];
        return;
      }
      try {
        this.form.annexfilesList = JSON.parse(filePatch);
      } catch (error) {
        console.error("解析 filePatch 字段失败:", error);
        this.form.annexfilesList = [];
      }
    },
    // 初始化附件文件列表
    initAttachmentFileList() {
      if (this.form.annexfilesList && this.form.annexfilesList.length > 0) {
        this.attachmentFileList = this.form.annexfilesList.map(item => ({
          uid:
            item.id ||
            Math.random()
              .toString(36)
              .substr(2, 9),
          name: item.fileName,
          url: item.path || item.fileUrl,
          status: "success"
        }));
      } else {
        this.attachmentFileList = [];
      }
    },
    // 构建 filePatch 字段
    buildFilePatch() {
      if (!this.form.annexfilesList || this.form.annexfilesList.length == 0) {
        return "";
      }
      return JSON.stringify(this.form.annexfilesList);
    },
    // 附件变化处理
    handleAttachmentChange(fileList) {
      this.attachmentFileList = fileList;
    },
    // 附件移除处理
    handleAttachmentRemove(file) {
      if (file.url) {
        const index = this.form.annexfilesList.findIndex(
          item => item.path == file.url || item.fileUrl == file.url
        );
        if (index > -1) {
          this.form.annexfilesList.splice(index, 1);
        }
      }
    },
    // 手动删除附件
    handleRemoveAttachment(index) {
      this.form.annexfilesList.splice(index, 1);
      this.attachmentFileList.splice(index, 1);
      this.$message.success("附件删除成功");
    },
    // 上传成功处理
    handleUploadSuccess({ file, fileList, response }) {
      if (response.code == 200) {
        const attachmentObj = {
          fileName: file.name,
          path: response.data || file.url,
          fileUrl: response.data || file.url,
          type: this.getFileExtension(file.name),
          createTime: dayjs().format("YYYY-MM-DD HH:mm:ss"),
          infoid: this.infoid,
          delFlag: 0
        };
        this.form.annexfilesList.push(attachmentObj);
        this.$message.success("文件上传成功");
      }
    },
    // 上传错误处理
    handleUploadError({ file, fileList, error }) {
      console.error("附件上传失败:", error);
      this.$message.error("文件上传失败,请重试");
    },
    // 文件预览
    handlePreview(file) {
      console.log(file, "file");
      this.currentPreviewFile = {
        fileName: file.fileName,
        fileUrl: file.path || file.fileUrl,
        fileType: this.getFileType(file.fileName)
      };
      this.previewVisible = true;
    },
    // 文件预览
    handlePreviewSignature(file) {
      console.log(file, "file");
      this.currentPreviewFile = {
        fileName: file,
        fileUrl: file,
        fileType: "png"
      };
      this.previewVisible = true;
    },
    // 文件下载
    handleDownload(file) {
      const fileUrl = file.path || file.fileUrl;
      const fileName = file.fileName;
      if (fileUrl) {
        const link = document.createElement("a");
        link.href = fileUrl;
        link.download = fileName;
        link.style.display = "none";
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
        this.$message.success("开始下载文件");
      } else {
        this.$message.warning("文件路径不存在,无法下载");
      }
    },
    // 获取文件类型
    getFileType(fileName) {
      if (!fileName) return "other";
      const extension = fileName
        .split(".")
        .pop()
        .toLowerCase();
      const imageTypes = ["jpg", "jpeg", "png", "gif", "bmp", "webp"];
      const pdfTypes = ["pdf"];
      const officeTypes = ["doc", "docx", "xls", "xlsx", "ppt", "pptx"];
      if (imageTypes.includes(extension)) return "image";
      if (pdfTypes.includes(extension)) return "pdf";
      if (officeTypes.includes(extension)) return "office";
      return "other";
    },
    // 获取文件扩展名
    getFileExtension(filename) {
      return filename
        .split(".")
        .pop()
        .toLowerCase();
    },
    // 日期时间格式化
    formatDateTime(dateTime) {
      if (!dateTime) return "";
      try {
        const date = new Date(dateTime);
        if (isNaN(date.getTime())) return dateTime;
        const year = date.getFullYear();
        const month = String(date.getMonth() + 1).padStart(2, "0");
        const day = String(date.getDate()).padStart(2, "0");
        const hours = String(date.getHours()).padStart(2, "0");
        const minutes = String(date.getMinutes()).padStart(2, "0");
        const seconds = String(date.getSeconds()).padStart(2, "0");
        return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
      } catch (error) {
        return dateTime;
      }
    },
    // 判断是否为主任委员
    getIsChiefExpert(expert) {
      // 职称包含"主任委员"或者expertType为"1"
      return (
        (expert.title && expert.title.includes("主任委员")) ||
        expert.expertType == "1"
      );
    },
    // 专家类型文本转换
    getExpertTypeText(type) {
      return type == "1" ? "主委专家" : "普通专家";
    },
    // 审查状态过滤器
    getReviewStatusFilter(status) {
      const statusMap = {
        "0": "info", // 待接收
        "1": "warning", // 未接收
        "2": "success", // 已接收
        "3": "danger", // 超时
        "4": "danger", // 中止
        "5": "success" // 完成
      };
      return statusMap[status] || "info";
    },
    getReviewStatusText(status) {
      const statusMap = {
        "0": "待接收",
        "1": "未接收",
        "2": "已接收",
        "3": "超时",
        "4": "中止",
        "5": "完成"
      };
      return statusMap[status] || "未知";
    },
    // 结论过滤器
    getConclusionFilter(conclusion) {
      const conclusionMap = {
        "1": "success", // 同意
        "2": "warning", // 审查中
        "0": "danger" // 不同意
      };
      return conclusionMap[conclusion] || "info";
    },
    getConclusionText(conclusion) {
      const conclusionMap = {
        "1": "同意",
        "2": "审查中",
        "0": "不同意"
      };
      return conclusionMap[conclusion] || "未知";
    },
    // 专家行样式
    getExpertRowClassName({ row }) {
      return row.expertType == "1" ? "chief-expert-row" : "normal-expert-row";
    },
    // 获取专家唯一标识
    getExpertKey(expert) {
      return expert.id || expert.expertNo || expert.expertname;
    },
    // 专家类型变更处理
    handleExpertTypeChange() {
      if (this.sendForm.expertType == "chief") {
        // 主委专家无需设置截止时间
        this.sendForm.endTime = "";
      } else {
        // 普通专家重置截止时间为当前时间
        this.sendForm.endTime = "";
      }
    },
    // 设置截止时间快捷键
    setEndTime(hours) {
      const now = new Date();
      const endTime = new Date(now.getTime() + hours * 60 * 60 * 1000);
      this.sendForm.endTime = endTime
        .toISOString()
        .replace("T", " ")
        .substring(0, 19);
    },
    // 保存信息
    async handleSave() {
      this.$refs.form.validate(async valid => {
        if (valid) {
          this.saveLoading = true;
          // 保存清空id便于后端整体删除新增
          // this.form.ethicalreviewopinionsList.forEach(item => {
          //   item.id = null;
          // });
          try {
            const submitData = {
              ...this.form,
              // 确保必要字段
              infoid: this.infoid,
              caseNo: this.caseNo,
              // 构建 filePatch 字段
              filePatch: this.buildFilePatch()
            };
            let response = null;
            if (submitData.id) {
              response = await ethicalreviewedit(submitData);
            } else {
              response = await ethicalreviewadd(submitData);
            }
            if (response.code == 200) {
              this.$message.success("保存成功");
              // 保存成功后更新原始数据
              this.saveOriginalData();
              this.isEdit = false;
              if (!this.form.id && response.data) {
                this.form.id = response.data;
                this.id = this.form.id;
                // this.$router.replace({
                //   query: { ...this.$route.query, id: this.form.id }
                // });
              }
              this.refreshPageData();
            } else {
              this.$message.error("保存失败:" + (response.msg || "未知错误"));
            }
          } catch (error) {
            console.error("保存失败:", error);
            this.$message.error("保存失败,请重试");
          } finally {
            this.saveLoading = false;
          }
        }
      });
    },
    // 审查完成
    async handleCompleteReview() {
      this.$confirm("确定要将本次伦理审查状态设为完成吗?", "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning"
      })
        .then(async () => {
          this.completeLoading = true;
          try {
            const updateData = {
              ...this.form,
              status: "3", // 审查完成
              endTime: new Date()
                .toISOString()
                .replace("T", " ")
                .substring(0, 19)
            };
            const response = await ethicalreviewedit(updateData);
            if (response.code == 200) {
              this.$message.success("审查状态已更新为完成");
              this.form.status = "3";
              this.form.endTime = updateData.endTime;
            } else {
              this.$message.error("操作失败:" + (response.msg || "未知错误"));
            }
          } catch (error) {
            console.error("更新审查状态失败:", error);
            this.$message.error("更新审查状态失败");
          } finally {
            this.completeLoading = false;
          }
        })
        .catch(() => {});
    },
    // 审查中止
    async handleSuspendReview() {
      this.$confirm(
        "确定要中止本次伦理审查吗?所有专家的审查状态将变为中止。",
        "提示",
        {
          confirmButtonText: "确定",
          cancelButtonText: "取消",
          type: "warning"
        }
      )
        .then(async () => {
          this.suspendLoading = true;
          try {
            // 更新所有专家的接收状态为中止
            if (
              this.form.ethicalreviewopinionsList &&
              this.form.ethicalreviewopinionsList.length > 0
            ) {
              this.form.ethicalreviewopinionsList.forEach(expert => {
                expert.receiveStatus = "4"; // 中止状态
                expert.updateTime = new Date()
                  .toISOString()
                  .replace("T", " ")
                  .substring(0, 19);
              });
            }
            const updateData = {
              ...this.form,
              status: "2" // 审查中止
            };
            const response = await ethicalreviewedit(updateData);
            if (response.code == 200) {
              this.$message.success("审查已中止,所有专家状态已更新");
              this.form.status = "2";
            } else {
              this.$message.error("操作失败:" + (response.msg || "未知错误"));
            }
          } catch (error) {
            console.error("中止审查失败:", error);
            this.$message.error("中止审查失败");
          } finally {
            this.suspendLoading = false;
          }
        })
        .catch(() => {});
    },
    // 结束审查
    async handleEndReview() {
      this.$confirm(
        "确定要结束本次伦理审查吗?结束后将无法修改专家审查结果。",
        "提示",
        {
          confirmButtonText: "确定",
          cancelButtonText: "取消",
          type: "warning"
        }
      )
        .then(async () => {
          this.endLoading = true;
          try {
            const updateData = {
              ...this.form,
              status: "4", // 审查中止
              endTime: new Date()
                .toISOString()
                .replace("T", " ")
                .substring(0, 19)
            };
            const response = await ethicalreviewedit(updateData);
            if (response.code == 200) {
              this.$message.success("审查已结束");
              this.form.status = "4";
              this.form.endTime = updateData.endTime;
            } else {
              this.$message.error("操作失败:" + (response.msg || "未知错误"));
            }
          } catch (error) {
            console.error("结束审查失败:", error);
            this.$message.error("结束审查失败");
          } finally {
            this.endLoading = false;
          }
        })
        .catch(() => {});
    },
    // 打开添加专家对话框
    handleAddExpert() {
      this.expertDialogVisible = true;
      this.loadExperts();
    },
    /**
     * 刷新页面数据
     */
    async refreshPageData() {
      try {
        // 重置数据状态
        this.isDataLoaded = false;
        // 清空当前数据
        this.form = {
          id: undefined,
          infoid: undefined,
          caseNo: "",
          initiateTheme: "",
          initiatePerson: "",
          status: "0",
          startTime: "",
          cutOffTime: "",
          endTime: "",
          expertName: "",
          expertNo: "",
          expertType: "0",
          expertConclusion: "",
          expertOpinion: "",
          expertTime: "",
          orderNo: 1,
          remark: "",
          annexfilesList: [],
          filePatch: "",
          ethicalreviewopinionsList: [],
          createBy: "",
          createTime: "",
          updateBy: "",
          updateTime: "",
          delFlag: "0"
        };
        this.attachmentFileList = [];
        this.originalFormData = null;
        this.originalExpertList = null;
        this.originalAttachments = null;
        // 重新获取数据
        if (this.id) {
          await this.getDetail(this.infoid, this.id);
        } else if (this.infoid) {
          await this.getDetail(this.infoid, null);
        } else {
          this.$message.warning("无法刷新,缺少必要的参数");
        }
        this.$message.success("数据刷新成功");
      } catch (error) {
        console.error("刷新数据失败:", error);
        this.$message.error("刷新数据失败,请重试");
      }
    },
    /**
     * 处理页面刷新
     * 检查是否有未保存数据,确认后刷新页面
     */
    handleRefresh() {
      // 检查是否有未保存的编辑
      if (this.hasUnsavedChanges()) {
        this.$confirm(
          "当前有未保存的数据,刷新页面将丢失这些更改。是否继续刷新?",
          "警告",
          {
            confirmButtonText: "继续刷新",
            cancelButtonText: "取消",
            type: "warning",
            distinguishCancelAndClose: true,
            beforeClose: (action, instance, done) => {
              if (action === "confirm") {
                instance.confirmButtonLoading = true;
                instance.confirmButtonText = "刷新中...";
                // 延迟执行以确保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: "ethical", // 伦理专家
          pageNum: this.expertPage.pageNum,
          pageSize: this.expertPage.pageSize
        };
        if (this.expertSearchQuery) {
          params.username = this.expertSearchQuery;
        }
        const response = await listExternalperson(params);
        if (response.code == 200) {
          this.expertList = response.rows || [];
          this.expertTotal = response.total || 0;
        } else {
          this.$message.error(
            "加载专家列表失败:" + (response.msg || "未知错误")
          );
        }
      } catch (error) {
        console.error("加载专家列表失败:", error);
        this.$message.error("加载专家列表失败");
      } finally {
        this.expertListLoading = false;
      }
    },
    // 搜索专家
    handleSearchExperts() {
      this.expertPage.pageNum = 1;
      this.loadExperts();
    },
    // 重置搜索
    handleResetSearch() {
      this.expertSearchQuery = "";
      this.filterExpertType = "";
      this.expertPage.pageNum = 1;
      this.loadExperts();
    },
    // 专家选择变化
    handleExpertSelectionChange(selection) {
      this.selectedExperts = selection;
    },
    // 确认添加专家
    handleConfirmAddExpert() {
      if (this.selectedExperts.length == 0) {
        this.$message.warning("请选择要添加的专家");
        return;
      }
      // 确保ethicalreviewopinionsList存在
      if (!this.form.ethicalreviewopinionsList) {
        this.$set(this.form, "ethicalreviewopinionsList", []);
      }
      // 添加专家到列表
      this.selectedExperts.forEach(expert => {
        // 判断是否为主任委员
        const isChief = this.getIsChiefExpert(expert);
        const expertReview = {
          id: undefined,
          infoid: this.infoid,
          nitiateId: this.form.id || undefined,
          caseNo: this.form.caseNo,
          expertname: expert.username,
          expertNo: expert.userno,
          expertType: isChief ? "1" : "0", // 主任委员设置为主委专家
          deptName: expert.unitname || "",
          title: expert.title || "",
          donorno: expert.telephone || "",
          receiveStatus: "0", // 待接收
          expertconclusion: "",
          expertopinion: "",
          conclusionannex: "",
          conclusionorder: this.form.ethicalreviewopinionsList.length + 1,
          conclusiontime: "",
          startTime: "",
          endTime: "",
          sendType: "",
          createBy: this.currentUser.username || "admin",
          createTime: new Date()
            .toISOString()
            .replace("T", " ")
            .substring(0, 19),
          updateBy: this.currentUser.username || "admin",
          updateTime: new Date()
            .toISOString()
            .replace("T", " ")
            .substring(0, 19),
          delFlag: "0"
        };
        // 使用push添加,确保响应式
        this.form.ethicalreviewopinionsList.push(expertReview);
      });
      this.$message.success(`成功添加 ${this.selectedExperts.length} 位专家`);
      this.expertDialogVisible = false;
      this.selectedExperts = [];
    },
    // 添加专家对话框关闭
    handleExpertDialogClose() {
      this.selectedExperts = [];
      this.expertSearchQuery = "";
      this.filterExpertType = "";
      this.expertPage.pageNum = 1;
    },
    // 页码变化
    handlePageChange(pageNum) {
      this.expertPage.pageNum = pageNum;
      this.loadExperts();
    },
    // 每页条数变化
    handlePageSizeChange(pageSize) {
      this.expertPage.pageSize = pageSize;
      this.expertPage.pageNum = 1;
      this.loadExperts();
    },
    // 发送给普通专家
    handleSendToNormalExperts() {
      if (!this.validateAllIds(this.ethicalreviewopinionsList)) {
        return;
      }
      this.currentSendExperts = this.availableNormalExperts;
      this.sendForm.expertType = "normal";
      this.sendForm.endTime = ""; // 重置截止时间
      this.sendDialogVisible = true;
    },
    // 发送给主委专家
    handleSendToChiefExpert() {
      if (!this.validateAllIds(this.ethicalreviewopinionsList)) {
        return;
      }
      this.currentSendExperts = this.availableChiefExperts;
      this.sendForm.expertType = "chief";
      this.sendForm.endTime = ""; // 主委专家无需截止时间
      this.sendDialogVisible = true;
    },
    // 发送给单个专家
    handleSendToExpert(expert) {
      if (!this.validateAllIds(this.ethicalreviewopinionsList)) {
        return;
      }
      this.currentSendExperts = [expert];
      this.sendForm.expertType = expert.expertType == "1" ? "chief" : "normal";
      this.sendForm.endTime = expert.expertType == "1" ? "" : ""; // 主委专家无需截止时间
      this.sendDialogVisible = true;
    },
    validateAllIds(arr, idField = "id") {
      if (!Array.isArray(arr) || arr.length === 0) {
        this.$message.warning("请先选择专家列表");
        return false;
      }
      const emptyIdItems = arr.filter(item => {
        const id = item[idField];
        return (
          id === undefined ||
          id === null ||
          id === "" ||
          id.toString().trim() === ""
        );
      });
      if (emptyIdItems.length > 0) {
        this.$message.warning(
          "当前选中专家列表有未保存数据,请保存刷新后再发送"
        );
        return false;
      }
      return true;
    },
    // 发送对话框关闭
    handleSendDialogClose() {
      this.sendForm = {
        expertType: "normal",
        expertIds: [],
        startTime: "",
        endTime: "",
        sendType: "0",
        content: "",
        url: ""
      };
      this.currentSendExperts = [];
    },
    // 确认发送
    async handleSendConfirm() {
      if (!this.sendForm.startTime) {
        this.$message.warning("请选择发送时间");
        return;
      }
      // 普通专家需要截止时间,主委专家不需要
      if (this.sendForm.expertType !== "chief" && !this.sendForm.endTime) {
        this.$message.warning("请选择截止时间");
        return;
      }
      // if (!this.sendForm.sendType) {
      //   this.$message.warning("请选择发送方式");
      //   return;
      // }
      // if (!this.sendForm.title) {
      //   this.$message.warning("请输入发送标题");
      //   return;
      // }
      // if (!this.sendForm.content) {
      //   this.$message.warning("请输入发送内容");
      //   return;
      // }
      if (this.currentSendExperts.length == 0) {
        this.$message.warning("没有找到可发送的专家");
        return;
      }
      // 初始化发送状态
      this.sendingAll = true;
      this.sendingProgress = 0;
      this.sendingTotal = this.currentSendExperts.length;
      this.sendingSuccessCount = 0;
      this.sendingFailCount = 0;
      this.sendingResults = [];
      // 创建一个进度对话框
      const progressDialog = this.$message({
        type: "info",
        message: `正在发送通知,请稍候... (0/${this.sendingTotal})`,
        duration: 0, // 不会自动关闭
        showClose: true
      });
      try {
        // 使用Promise数组来顺序执行发送
        for (let i = 0; i < this.currentSendExperts.length; i++) {
          const expert = this.currentSendExperts[i];
          // 更新进度
          this.sendingProgress = i;
          progressDialog.message = `正在发送通知,请稍候... (${i}/${this.sendingTotal})`;
          try {
            // 发送单个专家通知
            const result = await this.sendSingleExpert(expert, i);
            this.sendingResults.push(result);
            if (result.success) {
              this.sendingSuccessCount++;
              // 更新专家状态
              const index = this.form.ethicalreviewopinionsList.findIndex(
                e =>
                  e.expertNo == expert.expertNo ||
                  e.expertname == expert.expertname
              );
              if (index != -1) {
                this.form.ethicalreviewopinionsList[index].receiveStatus = "1"; // 已接收
                this.form.ethicalreviewopinionsList[
                  index
                ].startTime = this.sendForm.startTime;
                this.form.ethicalreviewopinionsList[
                  index
                ].endTime = this.sendForm.endTime;
                this.form.ethicalreviewopinionsList[
                  index
                ].sendType = this.sendForm.sendType;
                this.form.ethicalreviewopinionsList[
                  index
                ].updateTime = new Date()
                  .toISOString()
                  .replace("T", " ")
                  .substring(0, 19);
                // 使用Vue.set确保响应式更新
                this.$set(
                  this.form.ethicalreviewopinionsList,
                  index,
                  this.form.ethicalreviewopinionsList[index]
                );
              }
            } else {
              this.sendingFailCount++;
            }
          } catch (error) {
            console.error(`发送给专家 ${expert.expertname} 失败:`, error);
            this.sendingResults.push({
              success: false,
              expert: expert.expertname,
              error: error.message
            });
            this.sendingFailCount++;
          }
          // 如果不是最后一个,等待100ms再发送下一个
          if (i < this.currentSendExperts.length - 1) {
            await this.sleep(100);
          }
        }
        // 完成进度
        this.sendingProgress = this.sendingTotal;
        progressDialog.message = `发送完成,成功 ${this.sendingSuccessCount} 个,失败 ${this.sendingFailCount} 个`;
        // 延迟1秒后关闭进度对话框
        await this.sleep(1000);
        progressDialog.close();
        // 显示最终结果
        if (this.sendingFailCount == 0) {
          this.$message.success(
            `成功发送给 ${this.sendingSuccessCount} 位专家`
          );
        } else if (this.sendingSuccessCount > 0) {
          this.$message.warning(
            `成功发送给 ${this.sendingSuccessCount} 位专家,失败 ${this.sendingFailCount} 位`
          );
          // 如果有失败,可以显示详细失败信息
          this.showFailedDetails();
        } else {
          this.$message.error("全部发送失败,请稍后重试");
        }
        // 关闭发送对话框
        this.sendDialogVisible = false;
        this.sendForm = {
          expertType: "normal",
          expertIds: [],
          startTime: "",
          endTime: "",
          sendType: "0",
          content: "",
          url: ""
        };
        this.currentSendExperts = [];
        // 保存整个单据
        this.handleSave();
      } catch (error) {
        console.error("发送过程中发生错误:", error);
        progressDialog.close();
        this.$message.error("发送过程中发生错误,请重试");
      } finally {
        this.sendingAll = false;
      }
    },
    // 发送单个专家的方法
    async sendSingleExpert(expert, index) {
      console.log(expert, "expert", index);
      try {
        // // 构建发送数据
        // const sendData = {
        //   number: expert.deptname || "", // 用户手机号
        //   title: this.sendForm.title,
        //   url: this.sendForm.url || "",
        //   createTime: new Date()
        //     .toISOString()
        //     .replace("T", " ")
        //     .substring(0, 19)
        // };
        const sendDatas = {
          expertNo: expert.donorno || "", //7.14
          id: expert.id || "",
          infoId: this.infoid || ""
        };
        console.log(`正在发送第 ${index + 1} 个专家: ${expert.expertname}`);
        // 调用发送通知接口
        // const response = await sendNotification(sendData);
        // const response = await sendcall({
        //   tel: expert.donorno ? expert.donorno : 13634195431, // 这里应该是 expert.deptname 或 expert.phone
        //   messageContent:
        //     "青岛大学附属医院上报潜在捐献案例,请登录OPO系统查看详细信息,及时进行对接。登录链接:https://brdeddd.qduhosos.cn/dklejdj/deljf/index"
        // });
        const response = await sendNotification(sendDatas);
        if (response.code == 200) {
          return {
            success: true,
            expert: expert.expertname,
            index: index
          };
        } else {
          return {
            success: false,
            expert: expert.expertname,
            index: index,
            error: response.msg
          };
        }
      } catch (error) {
        console.error(`发送给专家 ${expert.expertname} 失败:`, error);
        return {
          success: false,
          expert: expert.expertname,
          index: index,
          error: error.message
        };
      }
    },
    // 显示失败详情的方法
    showFailedDetails() {
      const failedExperts = this.sendingResults.filter(r => !r.success);
      if (failedExperts.length > 0) {
        this.$confirm(
          `有 ${failedExperts.length} 位专家发送失败,是否查看失败详情?`,
          "发送结果",
          {
            confirmButtonText: "查看详情",
            cancelButtonText: "关闭",
            type: "warning"
          }
        )
          .then(() => {
            let detailMessage = "发送失败的专家:\n\n";
            failedExperts.forEach((expert, index) => {
              detailMessage += `${index + 1}. ${expert.expert}: ${
                expert.error
              }\n`;
            });
            this.$alert(detailMessage, "发送失败详情", {
              confirmButtonText: "确定",
              customClass: "failed-details-dialog"
            });
          })
          .catch(() => {});
      }
    },
    // 睡眠函数,用于间隔
    sleep(ms) {
      return new Promise(resolve => setTimeout(resolve, ms));
    },
    // 删除专家审查
    handleDeleteExpertReview(expert, index) {
      this.$confirm("确定要删除该专家的审查记录吗?", "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning"
      })
        .then(() => {
          // 从数组中删除专家
          this.form.ethicalreviewopinionsList.splice(index, 1);
          this.$message.success("专家审查记录已删除");
        })
        .catch(() => {});
    },
    // 查看专家历史审批情况
    async handleViewExpertHistory(expert) {
      if (!expert.expertNo) {
        this.$message.warning("该专家没有编号,无法查询历史审批情况");
        return;
      }
      this.currentExpertInfo = expert;
      this.expertHistoryLoading = true;
      this.expertHistoryDialogVisible = true;
      try {
        const params = {
          expertNo: expert.expertNo
        };
        const response = await ethicalreExpertTotal(params);
        if (response && response.code == 200) {
          this.expertHistoryData = response.data || response[0] || null;
        } else {
          this.$message.error(
            "查询专家历史审批情况失败:" + (response?.msg || "未知错误")
          );
          this.expertHistoryData = null;
        }
      } catch (error) {
        console.error("查询专家历史审批情况失败:", error);
        this.$message.error("查询专家历史审批情况失败");
        this.expertHistoryData = null;
      } finally {
        this.expertHistoryLoading = false;
      }
    },
    // 时间格式化
    parseTime(time) {
      if (!time) return "";
      const date = new Date(time);
      if (isNaN(date.getTime())) return time;
      return `${date.getFullYear()}-${(date.getMonth() + 1)
        .toString()
        .padStart(2, "0")}-${date
        .getDate()
        .toString()
        .padStart(2, "0")} ${date
        .getHours()
        .toString()
        .padStart(2, "0")}:${date
        .getMinutes()
        .toString()
        .padStart(2, "0")}`;
    }
  }
};
</script>
<style scoped>
.resolution-content {
.ethics-review-detail {
  padding: 20px;
  line-height: 1.8;
  background-color: #f5f7fa;
}
.signature-area {
.detail-card {
  margin-bottom: 20px;
  border-radius: 8px;
  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.expert-card {
  margin-bottom: 20px;
  border-radius: 8px;
  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.attachment-card {
  margin-bottom: 20px;
  border-radius: 8px;
  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.detail-title {
  font-size: 18px;
  font-weight: 600;
  color: #303133;
}
.expert-stats {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: rgb(43, 181, 245);
  border-radius: 8px;
  margin-bottom: 20px;
}
.stat-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 10px;
}
.stat-label {
  font-size: 12px;
  opacity: 0.9;
  margin-bottom: 5px;
}
.stat-value {
  font-size: 18px;
  font-weight: bold;
}
.file-info {
  display: flex;
  align-items: center;
}
.empty-attachment {
  text-align: center;
  padding: 40px 0;
  color: #909399;
}
/* 专家类型样式 */
.normal-expert {
  color: #409eff;
  font-weight: 500;
}
.chief-expert {
  color: #f56c6c;
  font-weight: 600;
}
/* 专家行样式 */
:deep(.normal-expert-row) {
  background-color: #fafafa;
}
:deep(.chief-expert-row) {
  background-color: #fff7e6;
}
:deep(.normal-expert-row:hover) {
  background-color: #f0f7ff;
}
:deep(.chief-expert-row:hover) {
  background-color: #ffecc2;
}
/* 无数据样式 */
.no-data {
  color: #909399;
  font-style: italic;
}
/* 专家意见样式 */
.expert-opinion {
  color: #303133;
  line-height: 1.5;
}
/* 已发送按钮样式 */
.sent-button {
  color: #67c23a !important;
}
/* 专家姓名链接样式 */
.expert-name-link {
  color: #409eff;
  cursor: pointer;
  text-decoration: none;
  transition: color 0.3s;
}
.expert-name-link:hover {
  color: #66b1ff;
  text-decoration: underline;
}
/* 历史统计样式 */
.history-stat-item {
  padding: 10px;
  border-radius: 4px;
  background-color: #f5f7fa;
  text-align: center;
}
.history-stat-label {
  font-size: 12px;
  color: #909399;
  margin-bottom: 5px;
}
.history-stat-value {
  font-size: 18px;
  font-weight: bold;
  color: #303133;
}
.form-section {
  margin-bottom: 16px;
}
.section-header {
  display: flex;
  align-items: center;
  font-weight: bold;
  color: #303133;
}
.dialog-footer {
  text-align: right;
  margin-top: 30px;
  padding: 20px 0 0;
}
.attachment-section {
  margin-bottom: 16px;
}
.attachment-header {
  display: flex;
  align-items: center;
  margin-bottom: 16px;
  padding: 8px 0;
  border-bottom: 1px solid #ebeef5;
}
.attachment-title {
  font-weight: bold;
  margin: 0 8px;
}
.attachment-tip {
  font-size: 12px;
  color: #909399;
}
.attachment-list {
  margin-top: 16px;
}
.list-title {
  font-weight: bold;
  margin-bottom: 12px;
  color: #303133;
}
.file-name {
  font-size: 13px;
}
/* 案例信息展示样式 */
.selected-case-info {
  margin-bottom: 20px;
}
/* 发送进度样式 */
.send-progress-container {
  margin: 20px 0;
  padding: 20px;
  background: #f5f7fa;
  border-radius: 8px;
}
.send-stats {
  display: flex;
  justify-content: center;
  gap: 30px;
  margin-top: 10px;
}
.stat-item {
  font-size: 14px;
  font-weight: 500;
  padding: 4px 12px;
  border-radius: 4px;
}
.stat-item.success {
  color: #67c23a;
  background: #f0f9eb;
}
.stat-item.fail {
  color: #f56c6c;
  background: #fef0f0;
}
/* 失败详情对话框 */
.failed-details-dialog {
  min-width: 400px;
  max-width: 600px;
}
.failed-details-dialog .el-message-box__content {
  max-height: 400px;
  overflow-y: auto;
  white-space: pre-wrap;
  word-break: break-word;
}
.case-info-card {
  border-left: 4px solid #67c23a;
}
/* 在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 {
    padding: 10px;
  }
  .expert-stats .el-col {
    margin-bottom: 10px;
  }
}
</style>