WXL
2026-06-01 dc082351978a1e9f75d7a1471a0ca7ebeac552a5
opo维护
已删除1个文件
已修改41个文件
已添加5个文件
5771 ■■■■■ 文件已修改
src/api/businessApi/ethicalReview.js 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/project/donatebaseinfo.js 83 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/CaseBasicInfo/index.vue 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/CaseStageAttachmentsDialog/index.vue 535 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/MaintainComponents/BloodRoutinePanel.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/MaintainComponents/LiverKidneyPanel.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/MaintainComponents/UrineRoutinePanel.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/request.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/business/GetWitness/GetWitnessInfo.vue 32 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/business/OrganUtilization/OrganUtilizationInfo.vue 57 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/business/OrganUtilization/index.vue 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/business/affirm/affirmInfo.vue 231 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/business/allocation/allocationInfo.vue 860 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/business/appear/caseDetail.vue 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/business/appear/index.vue 33 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/business/course/components/DeathJudgmentStage.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/business/course/components/DonationConfirmStage.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/business/course/components/EthicalReviewStage.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/business/course/components/OrganAllocationStage.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/business/course/components/components/BloodRoutinePanel.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/business/course/components/components/LiverKidneyPanel.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/business/course/components/components/UrineRoutinePanel.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/business/course/index.vue 522 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/business/decide/DecideInfo.vue 29 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/business/decide/index.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/business/ethicalReview/ethicalReviewInfo copy.vue 1536 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/business/ethicalReview/ethicalReviewInfo.vue 724 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/business/maintain/maintainInfo.vue 120 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/business/transfer/TransportEdit.vue 45 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/business/transfer/index.vue 63 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/login.vue 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/DonationProcess/index.vue 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/distributedetail/index.vue 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/donatebaseinfo/Archivedpage.vue 322 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/donatebaseinfo/EditCaseModal.vue 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/donatebaseinfo/index.vue 119 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/donatereview/index.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/donationdetails/index.vue 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/externalperson/index.vue 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/funddetail/index.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/reimbursementpayee/index.vue 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/relativesconfirmation/index.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/travelexpensedeal/index.vue 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vue.config.js 132 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
管理端 (2).zip 补丁 | 查看 | 原始文档 | blame | 历史
管理端 (3).zip 补丁 | 查看 | 原始文档 | blame | 历史
管理端.zip 补丁 | 查看 | 原始文档 | blame | 历史
src/api/businessApi/ethicalReview.js
@@ -1,57 +1,72 @@
import request from '@/utils/request'
import request from "@/utils/request";
// æ¡ˆä¾‹åˆ—表及详情
export function reviewinitiateBaseInfoList(data) {
  return request({
    url: '/project/ethicalreviewinitiate/reviewinitiateBaseInfoList',
    method: 'post',
    url: "/project/ethicalreviewinitiate/reviewinitiateBaseInfoList",
    method: "post",
    data: data
  })
  });
}
// ä¼¦ç†å®¡æŸ¥ä¿¡æ¯ä¿®æ”¹
export function ethicalreviewadd(data) {
  return request({
    url: '/project/ethicalreviewinitiate/add',
    method: 'post',
    url: "/project/ethicalreviewinitiate/add",
    method: "post",
    data: data
  })
  });
}
// ä¼¦ç†å®¡æŸ¥ä¿¡æ¯ä¿®æ”¹
export function ethicalreviewedit(data) {
  return request({
    url: '/project/ethicalreviewinitiate/edit',
    method: 'post',
    url: "/project/ethicalreviewinitiate/edit",
    method: "post",
    data: data
  })
  });
}
// å®¡æŸ¥ä¸“家统计
export function ethicalreExpertTotal(query) {
  return request({
    url: '/project/ethicalreviewopinions/expertTotal',
   method: 'get',
    url: "/project/ethicalreviewopinions/expertTotal",
    method: "get",
    params: query
  })
  });
}
// ä¼¦ç†å®¡æŸ¥ä¿¡æ¯è¯¦æƒ…
export function ethicalreviewInfo(id) {
  return request({
    url: '/project/ethicalreviewinitiate/getInfo/' + id,
    method: 'get'
  })
    url: "/project/ethicalreviewinitiate/getInfo/" + id,
    method: "get"
  });
}
// ä¼¦ç†å®¡æŸ¥ä¿¡æ¯infoid查询详情
export function ethicalreviewgetInfoID(query) {
  return request({
    url: "/project/ethicalreviewinitiate/getInfoID",
    method: "get",
    params: query
  });
}
// å®¡æŸ¥å•状态变更
export function ethicalreviewreceiveStatus(id) {
  return request({
    url: '/project/ethicalreviewopinions/receiveStatus',
    method: 'get'
  })
    url: "/project/ethicalreviewopinions/receiveStatus",
    method: "get"
  });
}
// ä¸“家消息推送
export function sendNotification(data) {
  return request({
    url: '/system/dingtalk/sendNotification',
     method: 'post',
    url: "/system/dingtalk/sendNotification",
    method: "post",
    data: data
  })
  });
}
// çŸ­ä¿¡
export function sendcall(data) {
  return request({
    url: "/sms/send",
    method: "post",
    data: data
  });
}
src/api/project/donatebaseinfo.js
@@ -1,101 +1,110 @@
import request from '@/utils/request'
import request from "@/utils/request";
// æŸ¥è¯¢æçŒ®åŸºç¡€åˆ—表
export function listDonatebaseinfo(data) {
  return request({
    url: '/project/donatebaseinfo/list',
    method: 'post',
    url: "/project/donatebaseinfo/list",
    method: "post",
    data: data
  })
  });
}
export function listDonationProcess(query) {
  return request({
    url: '/VDonationworkflow/donationworkflow/list',
    method: 'get',
    url: "/VDonationworkflow/donationworkflow/list",
    method: "get",
    params: query
  })
  });
}
// æŸ¥è¯¢æçŒ®åŸºç¡€è¯¦ç»†
export function getDonatebaseinfo(id) {
  return request({
    url: '/project/donatebaseinfo/' + id,
    method: 'get'
  })
    url: "/project/donatebaseinfo/" + id,
    method: "get"
  });
}
// èŽ·å–æçŒ®ç¼–å·
export function getfileList(data) {
  return request({
    url: "/project/donatebaseinfo/fileList",
    method: "get",
    params: data
  });
}
// èŽ·å–æçŒ®ç¼–å·
export function getDonationNumber(data) {
  return request({
    url: '/project/donatebaseinfo/donatenumber',
    method: 'post',
    url: "/project/donatebaseinfo/donatenumber",
    method: "post",
    data: data
  })
  });
}
// æŸ¥è¯¢æçŒ®å·¥ä½œæµ
export function getDonatebaseinfoflow(id) {
  return request({
    url: '/project/donatebaseinfo/getWorkFlow/' + id,
    method: 'get'
  })
    url: "/project/donatebaseinfo/getWorkFlow/" + id,
    method: "get"
  });
}
// æ–°å¢žæçŒ®åŸºç¡€
export function addDonatebaseinfo(data) {
  return request({
    url: '/project/donatebaseinfo/add',
    method: 'post',
    url: "/project/donatebaseinfo/add",
    method: "post",
    data: data
  })
  });
}
// ä¿®æ”¹æçŒ®åŸºç¡€
export function updateDonatebaseinfo(data) {
  return request({
    url: '/project/donatebaseinfo/edit',
    method: 'post',
    url: "/project/donatebaseinfo/edit",
    method: "post",
    data: data
  })
  });
}
// åˆ é™¤æçŒ®åŸºç¡€
export function delDonatebaseinfo(id) {
  return request({
    url: '/project/donatebaseinfo/remove/' + id,
    method: 'get',
  })
    url: "/project/donatebaseinfo/remove/" + id,
    method: "get"
  });
}
// å¯¼å‡ºæçŒ®åŸºç¡€
export function exportDonatebaseinfo(query) {
  return request({
    url: '/project/donatebaseinfo/export',
    method: 'get',
    url: "/project/donatebaseinfo/export",
    method: "get",
    params: query
  })
  });
}
// èŽ·å–æçŒ®ç¼–å·
export function getdonatorno(data) {
  // console.log("获取捐献编号:入参:" + JSON.stringify(data));
  return request({
    url: '/project/donatebaseinfo/donatenumber',
    method: 'get',
    url: "/project/donatebaseinfo/donatenumber",
    method: "get",
    params: data
  })
  });
}
// ä¸‹è½½äººä½“器官潜在捐献者登记表
export function downloadbaseinfo(id) {
  return request({
    url: '/project/donatebaseinfo/download/' + id,
    method: 'get'
  })
    url: "/project/donatebaseinfo/download/" + id,
    method: "get"
  });
}
// ä¸‹è½½äººä½“器官潜在捐献者登记表
export function fileCase(data) {
  return request({
    url: '/project/pdfmerge/merge',
    method: 'get',
    url: "/project/pdfmerge/merge",
    method: "get",
    params: data
  })
  });
}
src/components/CaseBasicInfo/index.vue
@@ -2,6 +2,16 @@
  <el-card class="basic-info-card" v-loading="loading">
    <div slot="header" class="clearfix">
      <span>案例基本信息</span>
      <el-button
        v-if="showAttachment"
        type="text"
        style="float: right; margin-left: 12px"
        @click="openStageAttachments"
      >
        <i class="el-icon-folder"></i> æŸ¥çœ‹å„阶段附件
      </el-button>
      <el-button
        v-if="showAttachment && hasAttachments"
        style="float: right; padding: 3px 0"
@@ -100,14 +110,24 @@
    <div v-else class="empty-state">
      <el-empty description="暂无案例信息" :image-size="100"></el-empty>
    </div>
    <!-- å„阶段附件弹框 -->
    <case-stage-attachments-dialog
      ref="stageDialog"
      :case-id="caseId"
      :selected-ids="selectedAttachmentIds"
      @confirm="onAttachmentsConfirm"
    />
  </el-card>
</template>
<script>
import { getDonatebaseinfo } from "@/api/project/donatebaseinfo";
import { getDonatebaseinfo, getfileList } from "@/api/project/donatebaseinfo";
import CaseStageAttachmentsDialog from "@/components/CaseStageAttachmentsDialog";
export default {
  name: "CaseBasicInfoSimple",
  components: { CaseStageAttachmentsDialog },
  props: {
    // æ¡ˆä¾‹ID
    caseId: {
@@ -173,7 +193,9 @@
      basicData: null,
      // å­—典选项
      sexOptions: [],
      bloodTypeOptions: []
      bloodTypeOptions: [],
      selectedAttachmentIds: ["123", "456"],
      finalAttachments: []
    };
  },
  computed: {
@@ -234,7 +256,16 @@
        console.warn("加载字典失败:", error);
      }
    },
    // æ‰“开各阶段附件弹框
    openStageAttachments() {
      this.$refs.stageDialog.open();
    },
    onAttachmentsConfirm(list) {
      this.finalAttachments = list;
      console.log("选中的阶段附件:", list);
      // å¯ç›´æŽ¥æäº¤ç»™æŽ¥å£
    },
    // åŠ è½½åŸºæœ¬ä¿¡æ¯
    async loadBasicInfo() {
      if (!this.caseId) return;
@@ -245,7 +276,7 @@
        if (response.code === 200) {
          this.basicData = this.mapApiData(response.data);
          console.log(this.basicData );
          console.log(this.basicData);
          this.loading = false;
        } else {
src/components/CaseStageAttachmentsDialog/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,535 @@
<template>
  <el-dialog
    title="案例各阶段附件"
    :visible.sync="visible"
    width="1200px"
    top="4vh"
    append-to-body
    :close-on-click-modal="false"
    @closed="handleClosed"
  >
    <!-- é˜¶æ®µ Tabs -->
    <el-tabs v-model="activeStage" type="card" @tab-click="handleStageChange">
      <el-tab-pane
        v-for="stage in stageTypes"
        :key="stage.value"
        :label="stage.label"
        :name="stage.value"
      >
        <!-- é˜¶æ®µå†…的附件类型 Tabs -->
        <div class="stage-content">
          <el-tabs v-model="activeType" type="border-card">
            <el-tab-pane
              v-for="type in getTypesByStage(stage.value)"
              :key="type.value"
              :label="type.label"
              :name="type.value"
            >
              <!-- é™„件列表 -->
              <div class="attachment-list">
                <el-table
                  :data="getAttachmentsByStageAndType(stage.value, type.value)"
                  size="small"
                  v-loading="loading"
                  style="width: 100%;"
                  @row-click="handleRowClick"
                  :row-class-name="tableRowClassName"
                >
                  <el-table-column label="文件名" min-width="220">
                    <template slot-scope="scope">
                      <i
                        class="el-icon-document"
                        style="color: #409EFF; margin-right: 8px;"
                      ></i>
                      <span class="file-name">{{ scope.row.fileName }}</span>
                    </template>
                  </el-table-column>
                  <el-table-column label="文件类型" width="100" align="center">
                    <template slot-scope="scope">
                      <el-tag size="small">{{
                        getFileType(scope.row.fileName)
                      }}</el-tag>
                    </template>
                  </el-table-column>
                  <el-table-column label="文件大小" width="100" align="center">
                    <template slot-scope="scope">
                      <span>{{ formatFileSize(scope.row.fileSize) }}</span>
                    </template>
                  </el-table-column>
                  <el-table-column label="上传时间" width="160" align="center">
                    <template slot-scope="scope">
                      <span>{{ formatDateTime(scope.row.uploadTime) }}</span>
                    </template>
                  </el-table-column>
                  <el-table-column label="上传人" width="120" align="center">
                    <template slot-scope="scope">
                      <span>{{ scope.row.uploader || "-" }}</span>
                    </template>
                  </el-table-column>
                  <el-table-column label="操作" width="180" align="center">
                    <template slot-scope="scope">
                      <el-button
                        size="mini"
                        type="primary"
                        @click="handlePreview(scope.row)"
                      >
                        é¢„览
                      </el-button>
                      <el-button
                        size="mini"
                        type="danger"
                        @click="handleDownload(scope.row)"
                      >
                        ä¸‹è½½
                      </el-button>
                    </template>
                  </el-table-column>
                </el-table>
                <!-- ç©ºçŠ¶æ€ -->
                <div
                  v-if="
                    getAttachmentsByStageAndType(stage.value, type.value)
                      .length === 0
                  "
                  class="empty-attachment"
                >
                  <el-empty
                    :description="`暂无${type.label}附件`"
                    :image-size="80"
                  ></el-empty>
                </div>
              </div>
            </el-tab-pane>
          </el-tabs>
        </div>
      </el-tab-pane>
    </el-tabs>
    <span slot="footer" class="dialog-footer">
      <el-button @click="visible = false">关闭</el-button>
      <!-- <el-button type="primary" @click="handleConfirm">
        ç¡®è®¤é€‰æ‹© ({{ selectedCount }})
      </el-button> -->
    </span>
    <!-- æ–‡ä»¶é¢„览弹窗 -->
    <FilePreviewDialog
      :visible="previewVisible"
      :file="currentPreviewFile"
      @close="previewVisible = false"
      @download="handleDownload"
    />
  </el-dialog>
</template>
<script>
import { getfileList } from "@/api/project/donatebaseinfo";
import FilePreviewDialog from "@/components/FilePreviewDialog";
import { parseTime } from "@/utils/ruoyi";
export default {
  name: "StageAttachmentsDialog",
  components: {
    FilePreviewDialog
  },
  props: {
    caseId: {
      type: [String, Number],
      required: true
    },
    // å·²é€‰ä¸­çš„附件ID集合(用于回显)
    selectedIds: {
      type: Array,
      default: () => []
    }
  },
  data() {
    return {
      visible: false,
      loading: false,
      // é˜¶æ®µå®šä¹‰
      stageTypes: [
        { value: "death", label: "死亡判定" },
        { value: "confirm", label: "捐献确认" },
        { value: "maintain", label: "供者维护" },
        { value: "witness", label: "器官获取见证" },
        { value: "ethics", label: "伦理审查" }
      ],
      // å„阶段附件类型定义
      typeDefinitions: {
        death: [
          { value: "brain", label: "脑死亡判定" },
          { value: "heart", label: "心死亡判定" }
        ],
        confirm: [
          { value: "1", label: "人体器官潜在捐献者登记表" },
          { value: "2", label: "人体器官捐献亲属确认登记表" },
          { value: "3", label: "捐献者及直系亲属身份证、户口簿相关证明" },
          { value: "4", label: "公民身故后人体器官(角膜)遗体捐献告知书" },
          { value: "5", label: "脑死亡判定知情同意书" },
          { value: "6", label: "心死亡判定知情同意书" }
        ],
        maintain: [
          { value: "urine", label: "尿常规" },
          { value: "blood", label: "血常规" },
          { value: "liver", label: "肝肾功能" },
          { value: "culture", label: "培养结果" },
          { value: "nurse", label: "护理记录" }
        ],
        witness: [{ value: "deathCert", label: "死亡证明" }],
        ethics: [{ value: "review", label: "伦理审查材料" }]
      },
      // å½“前激活的Tab
      activeStage: "death",
      activeType: "",
      // åŽŸå§‹æŽ¥å£æ•°æ®
      rawData: null,
      // å¤„理后的附件数据 stage -> type -> list
      attachmentData: {},
      // é€‰ä¸­çš„附件集合
      selectedAttachments: new Map(),
      // é¢„览相关
      previewVisible: false,
      currentPreviewFile: null
    };
  },
  computed: {
    // å½“前阶段下的附件类型
    currentStageTypes() {
      return this.typeDefinitions[this.activeStage] || [];
    },
    // å·²é€‰æ•°é‡
    selectedCount() {
      return this.selectedAttachments.size;
    },
      selectedCount() {
    return this.selectedAttachments?.size || 0;
  }
  },
  watch: {
    selectedIds: {
      handler(newVal) {
        this.initSelectedState(newVal);
      },
      immediate: true
    }
  },
  methods: {
    open() {
      this.visible = true;
      this.loadData();
    },
    // åˆå§‹åŒ–选中状态
    initSelectedState(ids) {
      this.selectedAttachments.clear();
      ids.forEach(id => {
        this.selectedAttachments.set(id, true);
      });
    },
    // åŠ è½½æ•°æ®
    async loadData() {
      this.loading = true;
      try {
        const res = await getfileList({ infoid: this.caseId });
        if (res.code === 200) {
          this.rawData = res.data || {};
          this.processData();
        }
      } catch (e) {
        console.error(e);
      } finally {
        this.loading = false;
      }
    },
    // å¤„理接口数据
    processData() {
      const d = this.rawData;
      const result = {};
      // åˆå§‹åŒ–结构
      this.stageTypes.forEach(stage => {
        result[stage.value] = {};
        (this.typeDefinitions[stage.value] || []).forEach(type => {
          result[stage.value][type.value] = [];
        });
      });
      // ===== 1. æ­»äº¡åˆ¤å®š =====
      (d.deathinfo || []).forEach(item => {
        this.pushAttachment(
          result.death,
          "brain",
          this.parseAttachments(item.deathjudgeannex)
        );
        this.pushAttachment(
          result.death,
          "heart",
          this.parseAttachments(item.heartdeathjudgeannex)
        );
      });
      // ===== 2. æçŒ®ç¡®è®¤ =====
      (d.relativesconfirmation || []).forEach(item => {
        this.pushAttachment(
          result.confirm,
          "1",
          this.parseAttachments(item.assessannex)
        );
      });
      // ===== 3. ä¾›è€…维护 =====
      (d.donatemaintenance || []).forEach(item => {
        try {
          const itemDesc = JSON.parse(item.itemDesc || "{}");
          this.pushAttachment(
            result.maintain,
            "urine",
            itemDesc.urineRoutine?.attachments || []
          );
          this.pushAttachment(
            result.maintain,
            "blood",
            itemDesc.bloodRoutine?.attachments || []
          );
          this.pushAttachment(
            result.maintain,
            "liver",
            itemDesc.liverKidney?.attachments || []
          );
          this.pushAttachment(
            result.maintain,
            "culture",
            itemDesc.cultureResults?.flatMap(c => c.attachments || []) || []
          );
          this.pushAttachment(
            result.maintain,
            "nurse",
            itemDesc.nursingRecords?.flatMap(n => n.attachments || []) || []
          );
        } catch {}
      });
      // ===== 4. å™¨å®˜èŽ·å–è§è¯ =====
      (d.donationwitness || []).forEach(item => {
        this.pushAttachment(
          result.witness,
          "deathCert",
          this.parseAttachments(item.deathjudgeannex)
        );
      });
      // ===== 5. ä¼¦ç†å®¡æŸ¥ =====
      (d.donateflowcharts || []).forEach(item => {
        this.pushAttachment(
          result.ethics,
          "review",
          this.parseAttachments(item.filePatch)
        );
      });
      this.attachmentData = result;
      this.$nextTick(() => {
        if (this.currentStageTypes.length > 0) {
          this.activeType = this.currentStageTypes[0].value;
        }
      });
    },
    // è¾…助:解析附件字段
    parseAttachments(val) {
      if (!val) return [];
      if (Array.isArray(val)) return val;
      try {
        const arr = JSON.parse(val);
        return Array.isArray(arr) ? arr : [];
      } catch {
        return val
          .split(";")
          .filter(Boolean)
          .map(p => ({ fileName: p.split("/").pop(), fileUrl: p }));
      }
    },
    // è¾…助:推送附件并补充字段
    pushAttachment(result, type, files) {
      files.forEach(file => {
        const attachment = {
          id: file.id || `${Date.now()}_${Math.random()}`,
          fileName: file.fileName,
          fileUrl: file.path || file.fileUrl,
          fileSize: file.fileSize,
          uploadTime: file.uploadTime,
          uploader: file.uploader,
          type: type,
          stage: this.activeStage
        };
        result[type].push(attachment);
        // // å›žæ˜¾é€‰ä¸­çŠ¶æ€
        // if (this.selectedAttachments.has(attachment.id)) {
        //   attachment._selected = true;
        // }
      });
    },
    // èŽ·å–æŸé˜¶æ®µä¸‹çš„é™„ä»¶ç±»åž‹
    getTypesByStage(stage) {
      return this.typeDefinitions[stage] || [];
    },
    // èŽ·å–é™„ä»¶åˆ—è¡¨
    getAttachmentsByStageAndType(stage, type) {
      return this.attachmentData[stage]?.[type] || [];
    },
    // é˜¶æ®µåˆ‡æ¢
    handleStageChange() {
      this.$nextTick(() => {
        if (this.currentStageTypes.length > 0) {
          this.activeType = this.currentStageTypes[0].value;
        }
      });
    },
    // ç‚¹å‡»è¡Œé€‰æ‹©/取消
    handleRowClick(row) {
  if (this.selectedAttachments.has(row.id)) {
    this.selectedAttachments.delete(row.id);
  } else {
    this.selectedAttachments.set(row.id, row);
  }
},
    tableRowClassName({ row }) {
  return this.selectedAttachments.has(row.id)
    ? "selected-row"
    : "";
},
    // é¢„览
    handlePreview(row) {
      this.currentPreviewFile = {
        fileName: row.fileName,
        fileUrl: row.fileUrl,
        fileType: this.getFileType(row.fileName)
      };
      this.previewVisible = true;
    },
    // ä¸‹è½½
    handleDownload(file) {
      const link = document.createElement("a");
      link.href = file.fileUrl || file.path;
      link.download = file.fileName;
      link.click();
    },
    // èŽ·å–æ–‡ä»¶ç±»åž‹
    getFileType(fileName) {
      const ext = fileName
        .split(".")
        .pop()
        .toLowerCase();
      if (["jpg", "jpeg", "png"].includes(ext)) return "image";
      if (ext === "pdf") return "pdf";
      if (["doc", "docx"].includes(ext)) return "office";
      return "other";
    },
    // æ–‡ä»¶å¤§å°æ ¼å¼åŒ–
    formatFileSize(size) {
      if (!size) return "0 B";
      const k = 1024;
      const sizes = ["B", "KB", "MB", "GB"];
      const i = Math.floor(Math.log(size) / Math.log(k));
      return parseFloat((size / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
    },
    // æ—¥æœŸæ—¶é—´æ ¼å¼åŒ–
    formatDateTime(dateTime) {
      return dateTime ? parseTime(dateTime) : "-";
    },
    // ç¡®è®¤é€‰æ‹©
    handleConfirm() {
      const result = [];
      this.selectedAttachments.forEach(attachment => {
        result.push({
          id: attachment.id,
          fileName: attachment.fileName,
          fileUrl: attachment.fileUrl,
          fileSize: attachment.fileSize,
          uploadTime: attachment.uploadTime,
          uploader: attachment.uploader,
          stage: attachment.stage,
          stageName:
            this.stageTypes.find(s => s.value === attachment.stage)?.label ||
            "",
          type: attachment.type,
          typeName:
            this.typeDefinitions[attachment.stage]?.find(
              t => t.value === attachment.type
            )?.label || ""
        });
      });
      this.$emit("confirm", result);
      this.visible = false;
    },
    // å¼¹æ¡†å…³é—­
    handleClosed() {
      this.attachmentData = {};
      this.selectedAttachments.clear();
    }
  }
};
</script>
<style scoped>
.stage-content {
  padding: 15px 0;
}
.attachment-list {
  margin-top: 15px;
}
.empty-attachment {
  text-align: center;
  padding: 40px 0;
  color: #909399;
  border: 1px dashed #dcdfe6;
  border-radius: 4px;
  margin-top: 15px;
}
.file-name {
  font-size: 13px;
  color: #606266;
}
/* é€‰ä¸­è¡Œé«˜äº® */
::v-deep .el-table__row:hover {
  cursor: pointer;
}
::v-deep .el-table__row.selected-row {
  background-color: #ecf5ff !important;
}
</style>
src/components/MaintainComponents/BloodRoutinePanel.vue
@@ -29,7 +29,6 @@
      border
      style="width: 100%"
      class="medical-table"
      v-fit-columns
      :key="tableKey"
      @header-dragend="handleHeaderDragEnd"
      v-loading="tableLoading"
src/components/MaintainComponents/LiverKidneyPanel.vue
@@ -19,7 +19,7 @@
      style="width: 100%"
      class="medical-table"
      :key="tableKey"
      v-fit-columns
      @header-dragend="handleHeaderDragEnd"
    >
      <el-table-column
src/components/MaintainComponents/UrineRoutinePanel.vue
@@ -29,7 +29,7 @@
      border
      style="width: 100%"
      class="medical-table"
      v-fit-columns
      :key="tableKey"
      @header-dragend="handleHeaderDragEnd"
      v-loading="tableLoading"
src/utils/request.js
@@ -61,7 +61,7 @@
      const s_url = sessionObj.url;                  // è¯·æ±‚地址
      const s_data = sessionObj.data;                // è¯·æ±‚数据
      const s_time = sessionObj.time;                // è¯·æ±‚æ—¶é—´
      const interval = 500;                         // é—´é𔿗¶é—´(ms),小于此时间视为重复提交
      const interval = 50;                         // é—´é𔿗¶é—´(ms),小于此时间视为重复提交
      if (s_data === requestObj.data && requestObj.time - s_time < interval && s_url === requestObj.url) {
        const message = '数据正在处理,请勿重复提交';
        console.warn(`[${s_url}]: ` + message)
src/views/business/GetWitness/GetWitnessInfo.vue
@@ -136,19 +136,41 @@
        </el-row>
        <el-row :gutter="20">
          <el-col :span="8">
          <!-- <el-col :span="8">
            <el-form-item label="协调员签字" prop="coordinatorSign">
              <el-input v-model="form.coordinatorSign" />
            </el-form-item>
          </el-col>
          </el-col> -->
          <el-col :span="8">
            <el-form-item label="签字时间" prop="coordinatorSignTime">
            <el-form-item label="获取时间" prop="coordinatorSignTime">
              <el-date-picker
                v-model="form.coordinatorSignTime"
                type="datetime"
                value-format="yyyy-MM-dd HH:mm:ss"
                style="width: 100%"
              />
            </el-form-item>
          </el-col>
           <el-col :span="8">
            <el-form-item label="是否默哀缅怀" prop="isspendremember">
              <el-select
                v-model="form.isspendremember"
                style="width: 100%"
              >
                <el-option label="是" :value="1" />
                <el-option label="否" :value="0" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label="恢复遗体仪容" prop="isrestoreremains">
              <el-select
                v-model="form.isrestoreremains"
                style="width: 100%"
              >
                <el-option label="是" :value="1" />
                <el-option label="否" :value="0" />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
@@ -163,12 +185,12 @@
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label="联络人一" prop="coordinatedusernameo">
            <el-form-item label="协调员一" prop="coordinatedusernameo">
              <el-input v-model="form.coordinatedusernameo" />
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label="联络人二" prop="coordinatedusernamet">
            <el-form-item label="协调员二" prop="coordinatedusernamet">
              <el-input v-model="form.coordinatedusernamet" />
            </el-form-item>
          </el-col>
src/views/business/OrganUtilization/OrganUtilizationInfo.vue
@@ -85,7 +85,7 @@
        </div>
        <el-row :gutter="20">
          <el-col :span="6">
            <el-form-item align="left" label="遗体捐献" prop="isbodydonation">
            <el-form-item align="left" label="遗体接收" prop="isbodydonation">
              <el-radio-group v-model="form.isbodydonation">
                <el-radio
                  v-for="dict in dict.type.sys_0_1 || []"
@@ -96,24 +96,28 @@
              </el-radio-group>
            </el-form-item>
          </el-col>
          <el-col :span="18" v-if="form.isbodydonation == 1">
            <el-form-item
              align="left"
              label="接收单位"
              prop="receivingunitname"
            >
              <el-input
                v-model="form.receivingunitname"
                placeholder="请输入接收单位"
              />
            </el-form-item>
          </el-col>
          <el-col :span="8" v-else>
          <el-col :span="8">
            <el-form-item align="left" label="接收家属" prop="relationname">
              <el-input
                v-model="form.relationname"
                placeholder="请输入接收家属"
              />
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="与捐献者关系" prop="signfamilyrelations">
              <el-select
                v-model="form.signfamilyrelations"
                placeholder="请选择与捐献者关系"
              >
                <el-option
                  v-for="dict in dict.type.sys_FamilyRelation || []"
                  :key="dict.value"
                  :label="dict.label"
                  :value="dict.value"
                ></el-option>
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
@@ -597,7 +601,13 @@
    FilePreviewDialog,
    CaseBasicInfo
  },
  dicts: ["sys_BloodType", "sys_Organ", "sys_0_1", "utilize_statue"],
  dicts: [
    "sys_BloodType",
    "sys_Organ",
    "sys_FamilyRelation",
    "sys_0_1",
    "utilize_statue"
  ],
  data() {
    return {
      caseId: null,
@@ -627,7 +637,7 @@
        coordinatedusernamet: "",
        assessannex: "",
        donateorgan: "",
        isbodydonation: "0",
        isbodydonation: "1",
        receivingunitname: "",
        createBy: "",
        createTime: "",
@@ -756,17 +766,8 @@
        .replace("T", " ")
        .substring(0, 19);
      this.generateDonorNo();
      this.getDetail();
      this.getHospitalData();
    },
    // ç”ŸæˆæçŒ®è€…编号
    generateDonorNo() {
      const timestamp = Date.now().toString();
      this.form.donorno = "D" + timestamp.slice(-8);
      this.form.caseNo = "CASE" + timestamp.slice(-6);
      this.form.inpatientno = "IP" + timestamp.slice(-6);
    },
    // èŽ·å–è¯¦æƒ…
@@ -783,9 +784,10 @@
          if (!data.completeState || data.completeState == 1) {
            data.completeState = "2";
          }
          this.form = data;
          // å¡«å……表单数据
          Object.assign(this.form, data);
          // Object.assign(this.form, data);
          this.form.signfamilyrelations = this.form.signfamilyrelations || "";
          // å¤„理捐献器官字段
          if (data.donateorgan) {
            const organArray = Array.isArray(data.donateorgan)
@@ -1180,7 +1182,6 @@
          return false;
        }
      );
      if (incompleteRecords.length > 0) {
        this.$message.warning("请先完善所有利用记录的信息");
src/views/business/OrganUtilization/index.vue
@@ -26,17 +26,17 @@
            @keyup.enter.native="handleQuery"
          />
        </el-form-item>
        <el-form-item label="记录状态" prop="recordstate">
        <el-form-item label="利用状态" prop="completeState">
          <el-select
            v-model="queryParams.recordstate"
            placeholder="请选择记录状态"
            v-model="queryParams.completeState"
            placeholder="请选择利用状态"
            clearable
            style="width: 200px"
          >
            <el-option label="已完成" value="completed" />
            <el-option label="进行中" value="processing" />
            <el-option label="待处理" value="pending" />
            <el-option label="已关闭" value="closed" />
            <el-option label="待利用" value="1" />
            <el-option label="进行中" value="2" />
            <el-option label="已完成" value="3" />
            <el-option label="放弃" value="4" />
          </el-select>
        </el-form-item>
        <el-form-item>
@@ -113,7 +113,12 @@
          </template>
        </el-table-column>
        <el-table-column label="年龄" align="center" prop="age" width="80" />
        <el-table-column label="血型" align="center" prop="bloodtype" width="80">
        <el-table-column
          label="血型"
          align="center"
          prop="bloodtype"
          width="80"
        >
          <template slot-scope="scope">
            <dict-tag
              v-if="scope.row.bloodtype"
@@ -121,6 +126,19 @@
              :value="scope.row.bloodtype"
            />
            <span v-else>-</span>
          </template>
        </el-table-column>
           <el-table-column
          label="利用状态"
          align="center"
          prop="recordstate"
          width="100"
        >
          <template slot-scope="scope">
            <dict-tag
              :options="dict.type.utilize_statue"
              :value="scope.row.completeState"
            />
          </template>
        </el-table-column>
        <el-table-column
@@ -154,19 +172,7 @@
            <span>{{ scope.row.responsibleusername || "-" }}</span>
          </template>
        </el-table-column>
        <el-table-column
          label="记录状态"
          align="center"
          prop="recordstate"
          width="100"
        >
          <template slot-scope="scope">
               <dict-tag
              :options="dict.type.utilize_statue"
              :value="scope.row.completeState"
            />
          </template>
        </el-table-column>
        <el-table-column
          label="操作"
          fixed="right"
@@ -214,13 +220,17 @@
</template>
<script>
import { completionList, completionadd, completionedit } from "@/api/businessApi";
import {
  completionList,
  completionadd,
  completionedit
} from "@/api/businessApi";
import Pagination from "@/components/Pagination";
export default {
  name: "OrganUtilizationList",
  components: { Pagination },
  dicts: ["sys_user_sex", "sys_BloodType",'utilize_statue'],
  dicts: ["sys_user_sex", "sys_BloodType", "utilize_statue"],
  data() {
    return {
      // é®ç½©å±‚
@@ -262,13 +272,13 @@
          let data = response.data;
          if (Array.isArray(data)) {
            this.organUtilizationList = data;
            this.total = data.length;
            this.total = response.total;
          } else if (data && data.rows) {
            this.organUtilizationList = data.rows;
            this.total = data.total || data.rows.length;
            this.total = response.total;
          } else if (data && data.list) {
            this.organUtilizationList = data.list;
            this.total = data.total || data.list.length;
            this.total = response.total;
          } else {
            this.organUtilizationList = [];
            this.total = 0;
src/views/business/affirm/affirmInfo.vue
@@ -126,7 +126,48 @@
            </el-form-item>
          </el-col>
        </el-row>
        <el-row style="margin-bottom: 10px;">
          <el-button
            type="primary"
            size="mini"
            icon="el-icon-plus"
            @click="openOtherFamilyDialog()"
          >
            æ·»åŠ å…¶ä»–å®¶å±ž
          </el-button>
        </el-row>
        <el-table :data="otherFamilyList" size="small" border>
          <el-table-column label="姓名" prop="name" />
          <el-table-column label="与捐赠者关系" prop="relation">
            <template slot-scope="scope">
              <dict-tag
                :options="dict.type.sys_FamilyRelation"
                :value="scope.row.relation"
              />
            </template>
          </el-table-column>
          <el-table-column label="联系电话" prop="phone" />
          <el-table-column label="操作" width="120" align="center">
            <template slot-scope="scope">
              <el-button
                size="mini"
                type="text"
                @click="editOtherFamily(scope.$index)"
              >
                ç¼–辑
              </el-button>
              <el-button
                size="mini"
                type="text"
                style="color:red"
                @click="deleteOtherFamily(scope.$index)"
              >
                åˆ é™¤
              </el-button>
            </template>
          </el-table-column>
        </el-table>
        <el-row>
          <el-form-item label-width="100px" label="捐献决定">
            <el-checkbox-group v-model="organdecision">
@@ -259,7 +300,38 @@
        </el-tab-pane>
      </el-tabs>
    </el-card>
    <!-- å…¶ä»–家属弹窗 -->
    <el-dialog
      :title="isEditOtherFamily ? '编辑其他家属' : '添加其他家属'"
      :visible.sync="otherFamilyDialogVisible"
      width="400px"
    >
      <el-form :model="currentOtherFamily" label-width="100px">
        <el-form-item label="姓名" prop="name">
          <el-input v-model="currentOtherFamily.name" />
        </el-form-item>
        <el-form-item label="关系" prop="relation">
          <el-select v-model="currentOtherFamily.relation" style="width:100%">
            <el-option
              v-for="dict in dict.type.sys_FamilyRelation"
              :key="dict.value"
              :label="dict.label"
              :value="dict.value"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="电话" prop="phone">
          <el-input v-model="currentOtherFamily.phone" />
        </el-form-item>
      </el-form>
      <span slot="footer">
        <el-button @click="otherFamilyDialogVisible = false">取消</el-button>
        <el-button type="primary" @click="saveOtherFamily">确定</el-button>
      </span>
    </el-dialog>
    <!-- ä¸Šä¼ å¯¹è¯æ¡† -->
    <el-dialog
      :title="`上传${getCurrentTypeLabel}附件`"
@@ -271,14 +343,15 @@
        ref="uploadRef"
        class="upload-demo"
        drag
        action="#"
        :action="uploadAction"
        :headers="headers"
        multiple
        :file-list="tempFileList"
        :before-upload="beforeUpload"
        :on-change="handleFileChange"
        :on-remove="handleTempRemove"
        :on-success="handleUploadSuccess"
        :auto-upload="false"
        :http-request="handleHttpRequest"
      >
        <i class="el-icon-upload"></i>
        <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
@@ -314,6 +387,8 @@
import { relativesList, relativesEdit, relativesAdd } from "@/api/businessApi";
import FilePreviewDialog from "@/components/FilePreviewDialog";
import CaseBasicInfo from "@/components/CaseBasicInfo";
import { getToken } from "@/utils/auth";
export default {
  name: "ConfirmationDetail",
  components: {
@@ -347,21 +422,39 @@
        relativeidcardno: "",
        relativephone: "",
        relativeRemark: "",
        assessannex: "" // JSON字符串存储所有附件
        assessannex: "", // JSON字符串存储所有附件
        otherFamilyMembers: "" // âœ… æ–°å¢ž
      },
      uploadAction: process.env.VUE_APP_BASE_API + "/common/upload",
      headers: {
        Authorization: "Bearer " + getToken()
      },
      // å…¶ä»–家属表格数据
      otherFamilyList: [],
      // å½“前编辑的其他家属(弹窗用)
      currentOtherFamily: {
        name: "",
        relation: "",
        phone: ""
      },
      // æ˜¯å¦ç¼–辑模式(其他家属)
      isEditOtherFamily: false,
      editOtherFamilyIndex: -1,
      // å…¶ä»–家属弹窗
      otherFamilyDialogVisible: false,
      organdecision: [],
      organdecisionOther: "",
      organselection: [
        "肝脏",
        "双肾",
        "左肾",
        "右肾",
        "肾脏",
        "心脏",
        "肺脏",
        "胰腺",
        "小肠",
        "双眼组织",
        "遗体",
        "眼角膜",
        "其他"
      ],
      // åŠ è½½çŠ¶æ€
@@ -454,7 +547,17 @@
      } else {
        detailData = response;
      }
      // å¤„理其他家属数据
      if (detailData.otherFamilyMembers) {
        try {
          this.otherFamilyList =
            typeof detailData.otherFamilyMembers === "string"
              ? JSON.parse(detailData.otherFamilyMembers)
              : detailData.otherFamilyMembers || [];
        } catch (e) {
          this.otherFamilyList = [];
        }
      }
      // æ˜ å°„字段到表单
      this.form = {
        ...this.form,
@@ -586,55 +689,40 @@
      this.tempFileList = fileList;
    },
    // è‡ªå®šä¹‰ä¸Šä¼ è¯·æ±‚
    handleHttpRequest(options) {
      return new Promise((resolve, reject) => {
        this.uploadLoading = true;
    handleUploadSuccess(response, file) {
      if (response.code !== 200) {
        this.$message.error(response.msg || "上传失败");
        return;
      }
        // æ¨¡æ‹Ÿä¸Šä¼ è¿‡ç¨‹
        setTimeout(() => {
          const newAttachment = {
            id: Date.now(),
            fileName: options.file.name,
            fileUrl: URL.createObjectURL(options.file),
            fileSize: options.file.size,
            fileType: this.getFileExtension(options.file.name),
            type: this.currentUploadType, // è®°å½•附件类型
            uploadTime: new Date().toISOString(),
            uploader: "当前用户"
          };
      const newAttachment = {
        id: Date.now(),
        fileName: file.name,
        fileUrl: response.url,
        fileSize: file.size,
        fileType: this.getFileExtension(file.name),
        type: this.currentUploadType,
        uploadTime: this.getCurrentTime(),
        uploader: "当前用户"
      };
          // æ·»åŠ åˆ°å¯¹åº”ç±»åž‹çš„é™„ä»¶åˆ—è¡¨
          if (this.attachmentData[this.currentUploadType]) {
            this.attachmentData[this.currentUploadType].push(newAttachment);
          }
      this.attachmentData[this.currentUploadType].push(newAttachment);
      this.updateAssessannexField();
          this.uploadLoading = false;
          this.updateAssessannexField(); // æ›´æ–°å­˜å‚¨å­—段
          resolve({ code: 200, data: newAttachment });
        }, 1500);
      });
      this.$message.success("上传成功");
      this.uploadLoading = false;
      this.uploadDialogVisible = false;
      this.tempFileList = [];
    },
    // æäº¤ä¸Šä¼ 
    async submitUpload() {
    submitUpload() {
      if (this.tempFileList.length === 0) {
        this.$message.warning("请先选择要上传的文件");
        return;
      }
      try {
        for (const file of this.tempFileList) {
          await this.$refs.uploadRef.submit();
        }
        this.$message.success("文件上传成功");
        this.uploadDialogVisible = false;
        this.tempFileList = [];
      } catch (error) {
        this.$message.error("文件上传失败");
        console.error("上传失败:", error);
      }
      this.uploadLoading = true;
      this.$refs.uploadRef.submit(); // âœ… åªè°ƒç”¨ä¸€æ¬¡
    },
    // åˆ é™¤é™„ä»¶
@@ -653,7 +741,51 @@
        })
        .catch(() => {});
    },
    openOtherFamilyDialog(index) {
      this.isEditOtherFamily = typeof index === "number";
      this.editOtherFamilyIndex = index || -1;
      this.currentOtherFamily = this.isEditOtherFamily
        ? { ...this.otherFamilyList[index] }
        : { name: "", relation: "", phone: "" };
      this.otherFamilyDialogVisible = true;
    },
    editOtherFamily(index) {
      this.openOtherFamilyDialog(index);
    },
    deleteOtherFamily(index) {
      this.$confirm("确认删除该家属?", "提示", { type: "warning" }).then(
        () => {
          this.otherFamilyList.splice(index, 1);
          this.updateOtherFamilyField();
        }
      );
    },
    saveOtherFamily() {
      if (
        !this.currentOtherFamily.name ||
        !this.currentOtherFamily.relation ||
        !this.currentOtherFamily.phone
      ) {
        this.$message.warning("请填写完整信息");
        return;
      }
      if (this.isEditOtherFamily) {
        this.otherFamilyList.splice(this.editOtherFamilyIndex, 1, {
          ...this.currentOtherFamily
        });
      } else {
        this.otherFamilyList.push({ ...this.currentOtherFamily });
      }
      this.updateOtherFamilyField();
      this.otherFamilyDialogVisible = false;
    },
    updateOtherFamilyField() {
      this.form.otherFamilyMembers = JSON.stringify(this.otherFamilyList);
    },
    // æ›´æ–°assessannex存储字段
    updateAssessannexField() {
      // å°†æ‰€æœ‰ç±»åž‹çš„附件合并为一个数组
@@ -784,9 +916,8 @@
        await this.$refs.form.validate();
        this.saveLoading = true;
        // ç¡®ä¿é™„件数据是最新的
        this.updateOtherFamilyField();
        this.updateAssessannexField();
        const saveData = {
          ...this.form,
          infoid: this.infoid,
src/views/business/allocation/allocationInfo.vue
@@ -52,7 +52,7 @@
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="登记人" prop="registrationName">
            <el-form-item label="负责人" prop="registrationName">
              <el-input v-model="form.registrationName" />
            </el-form-item>
          </el-col>
@@ -122,6 +122,7 @@
                v-model="otherOrganInput"
                placeholder="请输入其他器官名称"
                style="margin-top: 10px; width: 300px;"
                :disabled="form.allocationStatus == '1'"
              />
            </el-form-item>
          </el-col>
@@ -137,6 +138,33 @@
                style="width: 100%"
                :row-class-name="getOrganRowClassName"
              >
                <el-table-column
                  label="序号"
                  type="index"
                  width="60"
                  align="center"
                  fixed
                ></el-table-column>
                <el-table-column
                  label="分配状态"
                  align="center"
                  width="100"
                  prop="allocationstatus"
                  fixed
                >
                  <template slot-scope="scope">
                    <el-tag
                      :type="
                        getAllocationStatusTagType(scope.row.allocationstatus)
                      "
                      size="small"
                    >
                      {{ getAllocationStatusText(scope.row.allocationstatus) }}
                    </el-tag>
                  </template>
                </el-table-column>
                <el-table-column
                  label="器官名称"
                  align="center"
@@ -162,13 +190,16 @@
                    <el-input
                      v-model="scope.row.caseno"
                      placeholder="分配系统编号"
                      :disabled="form.allocationStatus == '1'"
                      :disabled="
                        form.allocationStatus == '1' ||
                          scope.row.allocationstatus == '3'
                      "
                    />
                  </template>
                </el-table-column>
                <el-table-column
                  label="分配接收时间"
                  label="分配开始时间"
                  align="center"
                  width="180"
                  prop="applicanttime"
@@ -181,8 +212,33 @@
                      v-model="scope.row.applicanttime"
                      type="datetime"
                      value-format="yyyy-MM-dd HH:mm:ss"
                      placeholder="选择开始接收时间"
                      :disabled="
                        form.allocationStatus == '1' ||
                          scope.row.allocationstatus == '3'
                      "
                    />
                  </template>
                </el-table-column>
                <el-table-column
                  label="分配接收时间"
                  align="center"
                  width="180"
                  prop="organgettime"
                >
                  <template slot-scope="scope">
                    <el-date-picker
                      clearable
                      size="small"
                      style="width: 100%"
                      v-model="scope.row.organgettime"
                      type="datetime"
                      value-format="yyyy-MM-dd HH:mm:ss"
                      placeholder="选择分配接收时间"
                      :disabled="form.allocationStatus == '1'"
                      :disabled="
                        form.allocationStatus == '1' ||
                          scope.row.allocationstatus == '3'
                      "
                    />
                  </template>
                </el-table-column>
@@ -197,7 +253,10 @@
                    <el-input
                      v-model="scope.row.name"
                      placeholder="受体姓氏"
                      :disabled="form.allocationStatus == '1'"
                      :disabled="
                        form.allocationStatus == '1' ||
                          scope.row.allocationstatus == '3'
                      "
                    />
                  </template>
                </el-table-column>
@@ -216,11 +275,16 @@
                        :dataList="dataList"
                        v-model="scope.row.transplanthospitalno"
                        style="width: 100%"
                        :disabled="
                          form.allocationStatus == '1' ||
                            scope.row.allocationstatus == '3'
                        "
                      />
                    </div>
                  </template>
                </el-table-column>
                <!-- ä¿®æ”¹template中的说明列 -->
                <el-table-column
                  label="说明"
                  align="center"
@@ -228,13 +292,51 @@
                  min-width="200"
                >
                  <template slot-scope="scope">
                    <el-input
                      type="textarea"
                      clearable
                      v-model="scope.row.reallocationreason"
                      placeholder="请输入说明"
                      :disabled="form.allocationStatus == '1'"
                    />
                    <div v-if="scope.row.allocationstatus != '3'">
                      <el-input
                        type="textarea"
                        clearable
                        v-model="scope.row.reallocationreason"
                        placeholder="请输入说明"
                        :disabled="
                          form.allocationStatus == '1' ||
                            scope.row.allocationstatus == '3'
                        "
                      />
                    </div>
                    <div v-else>
                      <!-- é‡åˆ†é…è®°å½•:显示详细查看按钮 -->
                      <el-button
                        v-if="scope.row.reallocationreason"
                        type="text"
                        size="small"
                        @click="handleViewRedistributionDetail(scope.row)"
                        style="color: #e6a23c;"
                      >
                        <i class="el-icon-document"></i>
                        æŸ¥çœ‹é‡åˆ†é…è¯¦æƒ…
                      </el-button>
                      <span v-else class="no-data">-</span>
                      <!-- ä¿ç•™åŽŸæœ‰çš„å·¥å…·æç¤ºï¼ˆå¯ç§»é™¤æˆ–ä¿ç•™ï¼‰ -->
                      <el-tooltip
                        v-if="scope.row.redistributionInfo"
                        :content="
                          formatRedistributionTooltip(
                            scope.row.redistributionInfo
                          )
                        "
                        placement="top"
                      >
                        <el-button
                          type="text"
                          size="small"
                          style="margin-left: 5px;"
                        >
                          <i class="el-icon-info"></i>
                        </el-button>
                      </el-tooltip>
                    </div>
                  </template>
                </el-table-column>
@@ -244,6 +346,7 @@
                  width="120"
                  class-name="small-padding fixed-width"
                  v-if="form.allocationStatus !== '1'"
                  fixed="right"
                >
                  <template slot-scope="scope">
                    <el-button
@@ -251,9 +354,16 @@
                      type="text"
                      icon="el-icon-copy-document"
                      @click="handleRedistribution(scope.row)"
                      :disabled="!scope.row.caseno"
                      :disabled="
                        scope.row.allocationstatus == '3' || !scope.row.caseno
                      "
                      style="color: #e6a23c;"
                    >
                      é‡åˆ†é…
                      {{
                        scope.row.allocationstatus == "3"
                          ? "已重分配"
                          : "重分配"
                      }}
                    </el-button>
                  </template>
                </el-table-column>
@@ -281,8 +391,8 @@
            </el-col>
            <el-col :span="6">
              <div class="stat-item">
                <span class="stat-label">待完善信息:</span>
                <span class="stat-value">{{ incompleteRecords }} ä¸ª</span>
                <span class="stat-label">待审核:</span>
                <span class="stat-value">{{ pendingReviewCount }} ä¸ª</span>
              </div>
            </el-col>
            <el-col :span="6">
@@ -293,26 +403,8 @@
            </el-col>
            <el-col :span="6">
              <div class="stat-item">
                <span class="stat-label">分配状态:</span>
                <span class="stat-value">
                  <el-tag
                    :type="
                      form.allocationStatus == '1'
                        ? 'success'
                        : form.allocationStatus == '2'
                        ? 'danger'
                        : 'warning'
                    "
                  >
                    {{
                      form.allocationStatus == "1"
                        ? "已分配"
                        : form.allocationStatus == "2"
                        ? "作废"
                        : "未分配"
                    }}
                  </el-tag>
                </span>
                <span class="stat-label">重分配:</span>
                <span class="stat-value">{{ redistributedCount }} ä¸ª</span>
              </div>
            </el-col>
          </el-row>
@@ -324,28 +416,6 @@
          </el-empty>
        </div>
      </el-form>
      <!-- <div class="dialog-footer" v-if="form.allocationStatus !== '1'">
        <el-button
          type="primary"
          @click="handleSaveAllocation"
          :loading="saveLoading"
          :disabled="
            !allocationData.serviceDonateorganList ||
              allocationData.serviceDonateorganList.length == 0
          "
        >
          ä¿å­˜åˆ†é…è®°å½•
        </el-button>
        <el-button
          type="success"
          @click="handleConfirmAllocation"
          :loading="confirmLoading"
          :disabled="incompleteRecords > 0"
        >
          ç¡®è®¤å®Œæˆåˆ†é…
        </el-button>
      </div> -->
    </el-card>
    <!-- é™„件管理部分优化 -->
@@ -436,12 +506,18 @@
      title="器官重分配"
      :visible.sync="redistributionDialogVisible"
      width="500px"
      @close="handleRedistributionDialogClose"
    >
      <el-form :model="redistributionForm" label-width="100px">
      <el-form
        :model="redistributionForm"
        :rules="redistributionRules"
        ref="redistributionFormRef"
        label-width="100px"
      >
        <el-form-item label="原器官信息">
          <el-input v-model="redistributionForm.organname" readonly />
        </el-form-item>
        <el-form-item label="重分配原因" prop="reason">
        <el-form-item label="重分配原因" prop="reason" required>
          <el-input
            type="textarea"
            :rows="4"
@@ -449,15 +525,170 @@
            placeholder="请输入重分配原因"
          />
        </el-form-item>
        <el-form-item label="重分配时间" prop="redistributionTime" required>
          <el-date-picker
            v-model="redistributionForm.redistributionTime"
            type="datetime"
            placeholder="请选择重分配时间"
            value-format="yyyy-MM-dd HH:mm:ss"
            style="width: 100%"
          />
        </el-form-item>
        <el-form-item label="重分配附件">
          <UploadAttachment
            ref="redistributionAttachmentUpload"
            :file-list="redistributionAttachmentList"
            :limit="5"
            :accept="attachmentAccept"
            :multiple="true"
            @change="handleRedistributionChange"
            @upload-success="handleRedistributionUploadSuccess"
            @upload-error="handleRedistributionUploadError"
            @remove="handleRedistributionAttachmentRemove"
          />
          <div style="margin-top: 5px; font-size: 12px; color: #999;">
            æ”¯æŒä¸Šä¼ é‡åˆ†é…ç›¸å…³æ–‡ä»¶ (最多5个)
          </div>
        </el-form-item>
      </el-form>
      <div slot="footer">
        <el-button @click="redistributionDialogVisible = false">取消</el-button>
        <el-button type="primary" @click="handleRedistributionConfirm"
          >确认重分配</el-button
        <el-button
          type="primary"
          @click="handleRedistributionConfirm"
          :loading="redistributionLoading"
        >
          ç¡®è®¤é‡åˆ†é…
        </el-button>
      </div>
    </el-dialog>
    <!-- åœ¨template中添加重分配详情弹窗 -->
    <el-dialog
      title="重分配详情"
      :visible.sync="redistributionDetailDialogVisible"
      width="600px"
    >
      <div v-loading="redistributionDetailLoading">
        <div v-if="currentRedistributionDetail" style="padding: 20px;">
          <!-- åŸºæœ¬ä¿¡æ¯ -->
          <el-descriptions title="重分配信息" :column="2" border>
            <el-descriptions-item label="器官名称">
              {{ currentRedistributionDetail.organname || "-" }}
            </el-descriptions-item>
            <el-descriptions-item label="操作人">
              {{ currentRedistributionDetail.operator || "-" }}
            </el-descriptions-item>
            <el-descriptions-item label="操作时间">
              {{
                currentRedistributionDetail.operatorTime
                  ? formatDateTime(currentRedistributionDetail.operatorTime)
                  : "-"
              }}
            </el-descriptions-item>
            <el-descriptions-item label="重分配时间">
              {{
                currentRedistributionDetail.redistributionTime
                  ? formatDateTime(
                      currentRedistributionDetail.redistributionTime
                    )
                  : "-"
              }}
            </el-descriptions-item>
          </el-descriptions>
          <!-- é‡åˆ†é…åŽŸå›  -->
          <div style="margin-top: 20px;">
            <div class="section-title">重分配原因</div>
            <div
              class="section-content"
              style="padding: 10px; background: #f5f7fa; border-radius: 4px;"
            >
              {{ currentRedistributionDetail.reason || "无" }}
            </div>
          </div>
          <!-- é‡åˆ†é…é™„ä»¶ -->
          <div
            style="margin-top: 20px;"
            v-if="
              currentRedistributionDetail.attachments &&
                currentRedistributionDetail.attachments.length > 0
            "
          >
            <div class="section-title">
              é‡åˆ†é…é™„ä»¶ ({{ currentRedistributionDetail.attachments.length }})
            </div>
            <div class="redistribution-attachments">
              <el-table
                :data="currentRedistributionDetail.attachments"
                size="small"
                style="width: 100%"
              >
                <el-table-column label="文件名" min-width="200">
                  <template slot-scope="scope">
                    <i
                      class="el-icon-document"
                      :style="{ color: getFileIconColor(scope.row.fileName) }"
                    ></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
                      :type="getFileTagType(scope.row.fileName)"
                      size="small"
                    >
                      {{ getFileTypeText(scope.row.fileName) }}
                    </el-tag>
                  </template>
                </el-table-column>
                <el-table-column label="上传时间" width="150">
                  <template slot-scope="scope">
                    <span>{{
                      scope.row.uploadTime
                        ? formatDateTime(scope.row.uploadTime)
                        : "-"
                    }}</span>
                  </template>
                </el-table-column>
                <el-table-column label="操作" width="150" fixed="right">
                  <template slot-scope="scope">
                    <el-button
                      size="mini"
                      type="primary"
                      @click="handleRedistributionAttachmentPreview(scope.row)"
                      :disabled="!isPreviewable(scope.row.fileName)"
                    >
                      é¢„览
                    </el-button>
                    <el-button
                      size="mini"
                      type="success"
                      @click="handleRedistributionAttachmentDownload(scope.row)"
                    >
                      ä¸‹è½½
                    </el-button>
                  </template>
                </el-table-column>
              </el-table>
            </div>
          </div>
          <div
            v-else
            style="margin-top: 20px; text-align: center; color: #909399;"
          >
            æ— é‡åˆ†é…é™„ä»¶
          </div>
        </div>
      </div>
      <div slot="footer">
        <el-button @click="redistributionDetailDialogVisible = false"
          >关闭</el-button
        >
      </div>
    </el-dialog>
    <!-- é™„件预览对话框 -->
    <FilePreviewDialog
      :visible="filePreviewVisible"
@@ -499,7 +730,10 @@
  data() {
    return {
      caseId: null,
      // é‡åˆ†é…è¯¦æƒ…相关
      redistributionDetailDialogVisible: false,
      redistributionDetailLoading: false,
      currentRedistributionDetail: null,
      // è¡¨å•数据
      form: {
        id: undefined,
@@ -538,9 +772,19 @@
      },
      // åˆ†é…è®°å½•验证规则
      allocationRules: {},
      // é‡åˆ†é…éªŒè¯è§„则
      redistributionRules: {
        reason: [
          { required: true, message: "重分配原因不能为空", trigger: "blur" }
        ],
        redistributionTime: [
          { required: true, message: "重分配时间不能为空", trigger: "blur" }
        ]
      },
      // ä¿å­˜åŠ è½½çŠ¶æ€
      saveLoading: false,
      confirmLoading: false,
      redistributionLoading: false,
      // åŠ è½½çŠ¶æ€
      loading: false,
      // é€‰ä¸­çš„器官(存储字典value)
@@ -564,8 +808,11 @@
      redistributionDialogVisible: false,
      redistributionForm: {
        organname: "",
        reason: ""
        reason: "",
        redistributionTime: ""
      },
      redistributionAttachmentList: [],
      redistributionFormRef: null,
      currentRedistributeRecord: null,
      // æ–‡ä»¶é¢„览相关
      filePreviewVisible: false,
@@ -585,8 +832,23 @@
        record =>
          !record.caseno ||
          !record.applicanttime ||
          !record.organgettime ||
          !record.name ||
          !record.transplanthospitalno
      ).length;
    },
    // å¾…审核记录数量
    pendingReviewCount() {
      if (!this.allocationData.serviceDonateorganList) return 0;
      return this.allocationData.serviceDonateorganList.filter(
        record => record.allocationstatus == "0"
      ).length;
    },
    // é‡åˆ†é…è®°å½•数量
    redistributedCount() {
      if (!this.allocationData.serviceDonateorganList) return 0;
      return this.allocationData.serviceDonateorganList.filter(
        record => record.allocationstatus == "3"
      ).length;
    },
    // å”¯ä¸€åŒ»é™¢æ•°é‡
@@ -604,6 +866,15 @@
    // åˆ¤æ–­æ˜¯å¦éœ€è¦æ˜¾ç¤ºå…¶ä»–输入框
    showOtherInput() {
      return this.selectedOrgans.includes("C01"); // å‡è®¾"其他"的字典值是C01
    },
    // åˆ†é…çŠ¶æ€å­—å…¸æ˜ å°„
    allocationStatusDict() {
      return {
        "0": { label: "提交分配", type: "info" },
        "1": { label: "审核通过", type: "success" },
        "2": { label: "审核拒绝", type: "danger" },
        "3": { label: "重分配", type: "warning" }
      };
    }
  },
  watch: {
@@ -639,6 +910,100 @@
    this.initData();
  },
  methods: {
    // èŽ·å–åˆ†é…çŠ¶æ€æ ‡ç­¾ç±»åž‹
    getAllocationStatusTagType(status) {
      const statusMap = this.allocationStatusDict;
      return statusMap[status] ? statusMap[status].type : "info";
    },
    // èŽ·å–åˆ†é…çŠ¶æ€æ–‡æœ¬
    getAllocationStatusText(status) {
      const statusMap = this.allocationStatusDict;
      return statusMap[status] ? statusMap[status].label : "未知状态";
    },
    // æŸ¥çœ‹é‡åˆ†é…è¯¦æƒ…
    handleViewRedistributionDetail(row) {
      this.redistributionDetailLoading = true;
      this.redistributionDetailDialogVisible = true;
      try {
        if (row.redistributionInfo) {
          // è§£æžé‡åˆ†é…ä¿¡æ¯
          const redistributionInfo = JSON.parse(row.redistributionInfo);
          this.currentRedistributionDetail = {
            organname: row.organname || "-",
            ...redistributionInfo
          };
        } else {
          this.currentRedistributionDetail = {
            organname: row.organname || "-",
            reason: "无重分配原因",
            attachments: []
          };
        }
      } catch (error) {
        console.error("解析重分配信息失败:", error);
        this.$message.error("重分配信息解析失败");
        this.currentRedistributionDetail = {
          organname: row.organname || "-",
          reason: "重分配信息格式错误",
          attachments: []
        };
      } finally {
        this.redistributionDetailLoading = false;
      }
    },
    // é¢„览重分配附件
    handleRedistributionAttachmentPreview(file) {
      this.currentPreviewFile = {
        fileName: file.fileName,
        fileUrl: file.path || file.fileUrl,
        fileType: this.getFileType(file.fileName)
      };
      this.filePreviewVisible = true;
    },
    // ä¸‹è½½é‡åˆ†é…é™„ä»¶
    handleRedistributionAttachmentDownload(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("文件路径不存在,无法下载");
      }
    },
    // å¢žå¼ºæ ¼å¼åŒ–工具提示方法
    formatRedistributionTooltip(redistributionInfo) {
      if (!redistributionInfo) return "";
      try {
        const info = JSON.parse(redistributionInfo);
        let tooltip = `重分配原因: ${info.reason}\n重分配时间: ${info.redistributionTime}`;
        // æ·»åŠ é™„ä»¶ä¿¡æ¯
        if (info.attachments && info.attachments.length > 0) {
          tooltip += `\n\n附件(${info.attachments.length}个):`;
          info.attachments.forEach((att, index) => {
            tooltip += `\n${index + 1}. ${att.fileName}`;
          });
        }
        return tooltip;
      } catch (error) {
        return redistributionInfo;
      }
    },
    // æ ¹æ®å­—å…¸value获取label
    getOrganLabel(organValue) {
      const dictItem = this.organDict.find(item => item.value == organValue);
@@ -671,6 +1036,7 @@
      this.getHospitalData();
    },
    // ç”ŸæˆæçŒ®è€…编号
    generateDonorNo() {
      const timestamp = Date.now().toString();
@@ -692,10 +1058,10 @@
        this.form.attachments = [];
      }
    },
    // èŽ·å–è¯¦æƒ…
    async getDetail(infoid, id) {
      this.loading = true;
      donateorganBaseinfoInfo(id);
      try {
        const response = await allocationList({ infoid });
        if (response.code == 200 && response.data && response.data.length > 0) {
@@ -719,7 +1085,30 @@
            this.allocationData.serviceDonateorganList = Array.isArray(
              data.serviceDonateorganList
            )
              ? data.serviceDonateorganList
              ? data.serviceDonateorganList.map(item => {
                  // ç¡®ä¿æ¯æ¡è®°å½•都有分配状态字段,默认值为0
                  const allocationstatus = item.allocationstatus || "0";
                  let redistributionInfo = null;
                  // è§£æžé‡åˆ†é…ä¿¡æ¯
                  if (allocationstatus == "3" && item.reallocationreason) {
                    try {
                      redistributionInfo = JSON.parse(item.reallocationreason);
                      console.log(redistributionInfo);
                    } catch (error) {
                      console.warn("解析重分配信息失败:", error);
                      redistributionInfo = item.reallocationreason;
                    }
                  }
                  return {
                    ...item,
                    allocationstatus,
                    redistributionInfo: redistributionInfo
                      ? JSON.stringify(redistributionInfo)
                      : null
                  };
                })
              : [];
            // æ›´æ–°é€‰ä¸­çš„器官
@@ -767,6 +1156,7 @@
        this.loading = false;
      }
    },
    // èŽ·å–åŒ»é™¢æ•°æ®
    async getHospitalData() {
      try {
@@ -784,6 +1174,7 @@
        this.$message.error("获取医院数据失败");
      }
    },
    // å™¨å®˜é€‰æ‹©çŠ¶æ€å˜åŒ–
    handleOrganSelectionChange(selectedValues) {
      if (!this.allocationData.serviceDonateorganList) {
@@ -884,10 +1275,13 @@
        organno: organValue,
        caseno: "",
        applicanttime: "",
        organgettime: "",
        name: "",
        transplanthospitalno: "",
        transplantHospitalName: "",
        reallocationreason: "",
        allocationstatus: "0", // é»˜è®¤æäº¤åˆ†é…çŠ¶æ€
        redistributionInfo: null,
        organState: 1
      });
    },
@@ -901,38 +1295,184 @@
        row.transplantHospitalName = hospital.hospitalName;
      }
    },
    // é‡åˆ†é…æ“ä½œ
    handleRedistribution(row) {
      this.currentRedistributeRecord = row;
      this.redistributionForm.organname = row.organname;
      this.redistributionForm.reason = row.reallocationreason || "";
      this.redistributionForm.reason = "";
      this.redistributionForm.redistributionTime = new Date()
        .toISOString()
        .replace("T", " ")
        .substring(0, 19);
      this.redistributionAttachmentList = [];
      this.redistributionDialogVisible = true;
    },
    // ç¡®è®¤é‡åˆ†é…
    handleRedistributionConfirm() {
      if (!this.redistributionForm.reason) {
        this.$message.warning("请输入重分配原因");
        return;
      }
      if (this.currentRedistributeRecord) {
        this.currentRedistributeRecord.reallocationreason = this.redistributionForm.reason;
        this.$message.success("重分配原因已更新");
        this.redistributionDialogVisible = false;
    // é‡åˆ†é…å¯¹è¯æ¡†å…³é—­
    handleRedistributionDialogClose() {
      this.redistributionForm = {
        organname: "",
        reason: "",
        redistributionTime: ""
      };
      this.redistributionAttachmentList = [];
      this.currentRedistributeRecord = null;
    },
    // é‡åˆ†é…é™„件上传成功
    handleRedistributionUploadSuccess({ file, fileList, response }) {
      if (response.code == 200) {
        const attachmentObj = {
          fileName: file.name,
          path: response.fileUrl || file.url,
          fileUrl: response.fileUrl || file.url,
          fileType: this.getFileExtension(file.name),
          fileSize: file.size,
          uploadTime: dayjs().format("YYYY-MM-DD HH:mm:ss")
        };
        console.log(11);
        this.redistributionAttachmentList = fileList;
        this.$message.success("重分配附件上传成功");
      }
    },
    // é‡åˆ†é…é™„件上传错误
    handleRedistributionUploadError({ file, fileList, error }) {
      console.error("重分配附件上传失败:", error);
      this.$message.error("重分配附件上传失败,请重试");
    },
    // é‡åˆ†é…é™„件移除
    handleRedistributionAttachmentRemove(file) {
      if (file.url) {
        const index = this.redistributionAttachmentList.findIndex(
          item => item.path == file.url || item.fileUrl == file.url
        );
        if (index > -1) {
          this.redistributionAttachmentList.splice(index, 1);
        }
      }
    },
    // ç¡®è®¤é‡åˆ†é…
    async handleRedistributionConfirm() {
      this.$refs.redistributionFormRef.validate(async valid => {
        if (!valid) {
          this.$message.warning("请完善重分配信息");
          return;
        }
        console.log(1, this.redistributionAttachmentList);
        this.redistributionLoading = true;
        try {
          // æž„建重分配信息对象
          const redistributionInfo = {
            reason: this.redistributionForm.reason,
            redistributionTime: this.redistributionForm.redistributionTime,
            attachments: this.redistributionAttachmentList.map(att => ({
              // fileName: att.fileName,
              // path: att.path || att.fileUrl,
              // fileUrl: att.path || att.fileUrl,
              // fileType: this.getFileExtension(att.name),
              // fileSize: att.fileSize,
              // uploadTime: att.uploadTime
              fileName: att.name,
              path: att.url,
              fileUrl: att.url,
              fileType: this.getFileExtension(att.name),
              fileSize: att.size,
              uploadTime: dayjs().format("YYYY-MM-DD HH:mm:ss")
            })),
            operator: this.currentUser.username || this.currentUser.nickName,
            operatorTime: new Date()
              .toISOString()
              .replace("T", " ")
              .substring(0, 19)
          };
          console.log(2, this.redistributionAttachmentList);
          // æ‰¾åˆ°å½“前记录的索引
          const currentIndex = this.allocationData.serviceDonateorganList.findIndex(
            item => item == this.currentRedistributeRecord
          );
          if (currentIndex !== -1) {
            // 1. æ›´æ–°åŽŸè®°å½•çš„çŠ¶æ€ä¸º3(重分配)并保存重分配信息
            const originalRecord = { ...this.currentRedistributeRecord };
            originalRecord.allocationstatus = "3";
            originalRecord.reallocationreason = JSON.stringify(
              redistributionInfo
            );
            originalRecord.redistributionInfo = JSON.stringify(
              redistributionInfo
            );
            // 2. åˆ›å»ºæ–°çš„记录
            const newRecord = {
              ...this.currentRedistributeRecord,
              id: null, // æ–°è®°å½•ID为空
              allocationstatus: "0", // æ–°è®°å½•状态为提交分配
              reallocationreason: "", // æ¸…空重分配原因
              redistributionInfo: null,
              // é‡ç½®ç›¸å…³å­—段,但保留器官信息
              caseno: "",
              applicanttime: "",
              organgettime: "",
              name: "",
              transplanthospitalno: "",
              transplantHospitalName: ""
            };
            // 3. åœ¨æ•°ç»„中插入新记录(在旧记录之后)
            this.allocationData.serviceDonateorganList.splice(
              currentIndex + 1,
              0,
              newRecord
            );
            // 4. æ›´æ–°åŽŸè®°å½•
            this.allocationData.serviceDonateorganList[
              currentIndex
            ] = originalRecord;
            // 5. æ›´æ–°é€‰ä¸­çš„器官列表
            this.selectedOrgans.push(this.currentRedistributeRecord.organno);
            this.$message.success("重分配操作成功完成");
            this.redistributionDialogVisible = false;
          } else {
            this.$message.error("未找到对应的记录");
          }
        } catch (error) {
          console.error("重分配操作失败:", error);
          this.$message.error("重分配操作失败");
        } finally {
          this.redistributionLoading = false;
        }
      });
    },
    // å™¨å®˜è¡Œæ ·å¼
    getOrganRowClassName({ row }) {
      if (
      if (row.allocationstatus == "3") {
        return "redistributed-row"; // é‡åˆ†é…è®°å½•样式
      } else if (
        !row.caseno ||
        !row.applicanttime ||
        !row.organgettime ||
        !row.name ||
        !row.transplanthospitalno
      ) {
        return "warning-row";
        return "warning-row"; // ä¿¡æ¯ä¸å®Œæ•´æ ·å¼
      } else if (row.allocationstatus == "0") {
        return "pending-row"; // å¾…审核样式
      }
      return "";
    },
    // æž„建 filePatch å­—段
    buildFilePatch() {
      if (!this.attachments || this.attachments.length == 0) {
@@ -940,6 +1480,7 @@
      }
      return JSON.stringify(this.attachments);
    },
    // ä¿å­˜åŸºæœ¬ä¿¡æ¯
    async handleSave() {
      this.$refs.form.validate(async valid => {
@@ -955,17 +1496,26 @@
            serviceDonateorganList:
              this.allocationData.serviceDonateorganList || []
          };
          if (
            submitData.allocationStatus == 1 ||
            !submitData.allocationStatus
          ) {
            submitData.allocationStatus = 2;
          }
          saveData.fileName = this.buildFilePatch();
          // ç¡®ä¿æ¯æ¡è®°å½•都有分配状态
          saveData.serviceDonateorganList.forEach(item => {
            item.baseid = this.form.id;
            item.infoid = this.form.infoid;
            // ç¡®ä¿æœ‰åˆ†é…çŠ¶æ€å­—æ®µ
            if (!item.allocationstatus) {
              item.allocationstatus = "0";
            }
            // å¦‚果是重分配记录,确保有重分配信息
            if (item.allocationstatus == "3" && !item.reallocationreason) {
              item.reallocationreason = item.redistributionInfo || "";
            }
          });
          if (saveData.allocationStatus == 1 || !saveData.allocationStatus) {
            saveData.allocationStatus = 2;
          }
          saveData.fileName = this.buildFilePatch();
          const apiMethod = this.form.id ? allocationedit : allocationadd;
          const response = await apiMethod(saveData);
@@ -973,9 +1523,6 @@
            this.$message.success("保存成功");
            if (!this.form.id && response.data) {
              this.form.id = response.data;
              // this.$router.replace({
              //   query: { ...this.$route.query, id: this.form.id }
              // });
            }
          } else {
            this.$message.error("保存失败:" + (response.msg || "未知错误"));
@@ -988,38 +1535,7 @@
        }
      });
    },
    // ä¿å­˜åˆ†é…è®°å½•
    async handleSaveAllocation() {
      if (!this.form.id) {
        this.$message.warning("请先保存基本信息");
        return;
      }
      this.saveLoading = true;
      try {
        const saveData = {
          ...this.form,
          attachments: this.attachments,
          serviceDonateorganList:
            this.allocationData.serviceDonateorganList || []
        };
        const response = await allocationedit(saveData);
        if (response.code == 200) {
          this.$message.success("分配记录保存成功");
        } else {
          this.$message.error(
            "保存分配记录失败:" + (response.msg || "未知错误")
          );
        }
      } catch (error) {
        console.error("保存分配记录失败:", error);
        this.$message.error("保存分配记录失败");
      } finally {
        this.saveLoading = false;
      }
    },
    // ç¡®è®¤å®Œæˆåˆ†é…
    async handleConfirmAllocation() {
      if (this.incompleteRecords > 0) {
@@ -1075,6 +1591,9 @@
    /** é™„件变化处理 */
    handleAttachmentChange(fileList) {
      this.attachmentFileList = fileList;
    },
    handleRedistributionChange(fileList) {
      this.redistributionAttachmentList = fileList;
    },
    /** é™„件移除处理 */
@@ -1132,9 +1651,9 @@
        fileUrl: file.path || file.fileUrl,
        fileType: this.getFileType(file.fileName)
      };
      // this.filePreviewTitle = file.fileName;
      this.filePreviewVisible = true;
    },
    handleDownloadAttachment(file) {
      const fileUrl = file.path || file.fileUrl;
      const fileName = file.fileName;
@@ -1213,6 +1732,8 @@
    /** èŽ·å–æ–‡ä»¶æ‰©å±•å */
    getFileExtension(filename) {
      console.log(filename, "filename");
      return filename
        .split(".")
        .pop()
@@ -1342,7 +1863,46 @@
  font-size: 20px;
  font-weight: bold;
}
/* åœ¨style部分添加 */
.section-title {
  font-weight: 600;
  color: #303133;
  margin-bottom: 8px;
  font-size: 14px;
}
.section-content {
  background: #f5f7fa;
  padding: 12px;
  border-radius: 4px;
  border-left: 3px solid #e6a23c;
  line-height: 1.6;
}
.redistribution-attachments {
  margin-top: 10px;
  border: 1px solid #ebeef5;
  border-radius: 4px;
  overflow: hidden;
}
/* é‡åˆ†é…è¯¦æƒ…表格样式 */
:deep(.redistribution-attachments .el-table) {
  border: none;
}
:deep(.redistribution-attachments .el-table th) {
  background-color: #f5f7fa;
  color: #606266;
  font-weight: 600;
}
/* å“åº”式调整 */
@media (max-width: 768px) {
  .redistribution-attachments {
    overflow-x: auto;
  }
}
/* ç©ºçŠ¶æ€æ ·å¼ */
.empty-allocation {
  text-align: center;
@@ -1350,15 +1910,23 @@
  color: #909399;
}
/* å¯¹è¯æ¡†åº•部按钮 */
.dialog-footer {
  margin-top: 20px;
  text-align: center;
  padding-top: 20px;
  border-top: 1px solid #e4e7ed;
/* æ— æ•°æ®æ ·å¼ */
.no-data {
  color: #909399;
  font-style: italic;
}
/* è¡¨æ ¼è¡Œæ ·å¼ */
:deep(.redistributed-row) {
  background-color: #fdf6ec;
  opacity: 0.8;
}
:deep(.redistributed-row:hover) {
  background-color: #faecd8;
  opacity: 0.9;
}
:deep(.warning-row) {
  background-color: #fff7e6;
}
@@ -1367,6 +1935,42 @@
  background-color: #ffecc2;
}
:deep(.pending-row) {
  background-color: #f0f9ff;
}
:deep(.pending-row:hover) {
  background-color: #e0f2ff;
}
/* ç¦ç”¨çŠ¶æ€çš„è¾“å…¥æ¡†æ ·å¼ */
:deep(.el-input.is-disabled .el-input__inner) {
  background-color: #f5f7fa;
  border-color: #e4e7ed;
  color: #c0c4cc;
  cursor: not-allowed;
}
:deep(.el-textarea.is-disabled .el-textarea__inner) {
  background-color: #f5f7fa;
  border-color: #e4e7ed;
  color: #c0c4cc;
  cursor: not-allowed;
}
/* é‡åˆ†é…æŒ‰é’®æ ·å¼ */
:deep(.el-button--text.is-disabled) {
  color: #c0c4cc !important;
  cursor: not-allowed;
}
/* é‡åˆ†é…ä¿¡æ¯æç¤ºæ¡†æ ·å¼ */
:deep(.el-tooltip__popper) {
  max-width: 300px;
  white-space: pre-line;
  line-height: 1.6;
}
/* å“åº”式设计 */
@media (max-width: 768px) {
  .organ-allocation-detail {
src/views/business/appear/caseDetail.vue
@@ -117,7 +117,13 @@
        <span class="section-title">医院信息</span>
      </div>
      <el-descriptions :column="2" border>
        <el-descriptions-item label="治疗医院名称">{{
        <el-descriptions-item label="ICU评估医生">{{
          caseData.icuDoctor || "-"
        }}</el-descriptions-item>
        <el-descriptions-item label="ICU医生电话">{{
          caseData.icuDoctorPhone || "-"
        }}</el-descriptions-item
        ><el-descriptions-item label="治疗医院名称">{{
          caseData.treatmenthospitalname || "-"
        }}</el-descriptions-item>
        <!-- <el-descriptions-item label="治疗科室名称">{{
src/views/business/appear/index.vue
@@ -715,7 +715,24 @@
            ></i>
            <span>医院信息</span>
          </div>
          <el-row :gutter="20">
            <el-col :span="12">
              <el-form-item label="ICU评估医生" prop="icuDoctor">
                <el-input
                  v-model="editForm.icuDoctor"
                  placeholder="请输入ICU评估医生"
                />
              </el-form-item>
            </el-col>
            <el-col :span="12">
              <el-form-item label="ICU医生电话" prop="icuDoctorPhone">
                <el-input
                  v-model="editForm.icuDoctorPhone"
                  placeholder="请输入ICU医生手机号"
                />
              </el-form-item>
            </el-col>
          </el-row>
          <el-row :gutter="20">
            <el-col :span="12">
              <el-form-item label="治疗医院" prop="treatmenthospitalname">
@@ -998,7 +1015,15 @@
    FilePreviewDialog
  },
  dicts: ["sys_user_sex", "sys_BloodType", "sys_Infectious", "sys_IDType"],
  data() {
    const validateConfirmResult = (rule, value, callback) => {
      if (this.approveForm.approveResult === "4" && !value) {
        callback(new Error("驳回时必须填写驳回意见"));
      } else {
        callback();
      }
    };
    return {
      // é®ç½©å±‚
      loading: false,
@@ -1048,9 +1073,7 @@
        approveResult: [
          { required: true, message: "请选择确认结果", trigger: "change" }
        ],
        confirmResult: [
          { required: true, message: "请输入确认意见", trigger: "blur" }
        ],
        confirmResult: [{ validator: validateConfirmResult, trigger: "blur" }],
        rejectType: {
          required: false,
          validator: (rule, value, callback) => {
@@ -1219,7 +1242,7 @@
        }
        return "有转运单";
      }
      return "需转运";
      return "未知";
    },
    /** è·³è½¬åˆ°åˆ›å»ºè½¬è¿å•页面 */
src/views/business/course/components/DeathJudgmentStage.vue
@@ -23,7 +23,7 @@
        </el-tab-pane>
      </el-tabs>
    </el-card>
    <el-card class="detail-card common-info-card">
    <!-- <el-card class="detail-card common-info-card">
      <div slot="header" class="clearfix">
        <span class="detail-title">公共信息</span>
      </div>
@@ -71,7 +71,7 @@
          </el-col>
        </el-row>
      </el-form>
    </el-card>
    </el-card> -->
    <!-- è„‘死亡判定模块 -->
    <el-card v-if="activeJudgmentType === 'brain'" class="detail-card">
      <div slot="header" class="clearfix">
src/views/business/course/components/DonationConfirmStage.vue
@@ -79,10 +79,10 @@
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="与捐赠者关系" prop="signfamilyrelations">
            <el-form-item label="与捐献者关系" prop="signfamilyrelations">
              <el-select
                v-model="form.signfamilyrelations"
                placeholder="请选择与捐赠者关系"
                placeholder="请选择与捐献者关系"
              >
                <el-option
                  v-for="dict in dict.type.sys_FamilyRelation || []"
src/views/business/course/components/EthicalReviewStage.vue
@@ -758,7 +758,7 @@
<script>
import { getToken } from "@/utils/auth";
import {
  reviewinitiateBaseInfoList,
  ethicalreviewgetInfoID,
  ethicalreviewedit,
  ethicalreviewadd,
  ethicalreviewInfo,
@@ -1106,7 +1106,7 @@
        if (id) {
          response = await ethicalreviewInfo(id);
        } else if (infoid) {
          response = await reviewinitiateBaseInfoList({ infoid: infoid });
          response = await ethicalreviewgetInfoID({ InfoId: infoid });
        }
        if (response.code === 200) {
@@ -1123,7 +1123,7 @@
              this.$set(this.form, "ethicalreviewopinionsList", []);
            }
          } else if (response.data && infoid) {
            this.form = response.data[0];
            this.form = response.data;
            // è§£æž filePatch å­—段
            this.parseFilePatch(this.form.filePatch);
            this.initAttachmentFileList();
src/views/business/course/components/OrganAllocationStage.vue
@@ -688,7 +688,7 @@
    // èŽ·å–è¯¦æƒ…
    async getDetail(infoid, id) {
      this.loading = true;
      donateorganBaseinfoInfo(id);
      try {
        const response = await allocationList({ infoid });
        if (response.code == 200 && response.data && response.data.length > 0) {
src/views/business/course/components/components/BloodRoutinePanel.vue
@@ -29,7 +29,7 @@
      border
      style="width: 100%"
      class="medical-table"
      v-fit-columns
      :key="tableKey"
      @header-dragend="handleHeaderDragEnd"
      v-loading="tableLoading"
src/views/business/course/components/components/LiverKidneyPanel.vue
@@ -19,7 +19,7 @@
      style="width: 100%"
      class="medical-table"
      :key="tableKey"
      v-fit-columns
      @header-dragend="handleHeaderDragEnd"
    >
      <el-table-column
src/views/business/course/components/components/UrineRoutinePanel.vue
@@ -29,7 +29,7 @@
      border
      style="width: 100%"
      class="medical-table"
      v-fit-columns
      :key="tableKey"
      @header-dragend="handleHeaderDragEnd"
      v-loading="tableLoading"
src/views/business/course/index.vue
@@ -2,13 +2,10 @@
  <div class="donation-process-detail">
    <el-card class="process-card">
      <div class="process-container">
        <!-- å·¦ä¾§æ—¶é—´çº¿ - ç‹¬ç«‹å›ºå®šï¼Œå†…部可滚动 -->
        <!-- å·¦ä¾§æ—¶é—´çº¿ -->
        <div class="timeline-section">
          <div class="section-header">
            <h3>捐献进程时间线</h3>
            <el-tag :type="getOverallStatusTag(caseInfo.status)">
              {{ getStatusText(caseInfo.status) }}
            </el-tag>
          </div>
          <div class="timeline-scroll-container">
@@ -18,20 +15,20 @@
                :key="stage.key"
                class="timeline-item"
                :class="{
                  active: activeStage === stage.key,
                  completed: stage.status === 'completed',
                  'in-progress': stage.status === 'in_progress',
                  pending: stage.status === 'pending'
                  active: stage.status == 'active',
                  completed: stage.status == 'completed',
                  progress: stage.status == 'progress',
                  pending: stage.status == 'pending'
                }"
                @click="handleStageClick(stage)"
              >
                <div class="timeline-marker">
                  <i
                    v-if="stage.status === 'completed'"
                    v-if="stage.status == 'completed'"
                    class="el-icon-check"
                  ></i>
                  <i
                    v-else-if="stage.status === 'in_progress'"
                    v-else-if="stage.status == 'progress'"
                    class="el-icon-loading"
                  ></i>
                  <i v-else class="el-icon-time"></i>
@@ -40,25 +37,21 @@
                <div class="timeline-content">
                  <div class="stage-header">
                    <span class="stage-name">{{ stage.name }}</span>
                    <el-tag
                      size="small"
                      :type="getStageStatusTag(stage.status)"
                    >
                      {{ getStageStatusText(stage.status) }}
                    </el-tag>
                    <dict-tag
                      :options="dict.type[stage.dict]"
                      :value="stage.state"
                    />
                  </div>
                  <div class="stage-info">
                    <div v-if="stage.completeTime" class="time-info">
                      <span
                        >完成时间: {{ formatTime(stage.completeTime) }}</span
                      >
                    <div v-if="stage.createtime" class="time-info">
                      åˆ›å»ºæ—¶é—´: {{ formatTime(stage.createtime) }}
                    </div>
                    <div v-if="stage.updateTime" class="time-info">
                      <span>最近更新: {{ formatTime(stage.updateTime) }}</span>
                      æœ€è¿‘æ›´æ–°: {{ formatTime(stage.updateTime) }}
                    </div>
                    <div v-if="stage.operator" class="operator-info">
                      <span>负责人: {{ stage.operator }}</span>
                      è´Ÿè´£äºº: {{ stage.operator }}
                    </div>
                  </div>
                </div>
@@ -67,19 +60,15 @@
          </div>
        </div>
        <!-- å³ä¾§å†…容区域 - ç‹¬ç«‹æ»šåЍ -->
        <!-- å³ä¾§å†…容 -->
        <div class="content-section">
          <!-- æ¡ˆä¾‹åŸºæœ¬ä¿¡æ¯ - å›ºå®šé«˜åº¦ï¼Œå¯æ»šåЍ -->
          <case-basic-info :case-id="caseId" :show-attachment="true" />
          <!-- é˜¶æ®µè¯¦æƒ…内容 - è‡ªé€‚应高度,可滚动 -->
          <div class="stage-detail-section">
            <div class="section-header">
              <h3>{{ activeStageName }} - é˜¶æ®µè¯¦æƒ…</h3>
              <div class="stage-actions"></div>
            </div>
            <!-- åŠ¨æ€é˜¶æ®µå†…å®¹ -->
            <div class="stage-content-wrapper">
              <component
                :is="getStageComponent()"
@@ -96,135 +85,178 @@
</template>
<script>
// ===================== å­—å…¸ Label â†’ UI ä¸»é¢˜ =====================
function mapDictLabelToTheme(label) {
  if (!label) return "pending";
  const l = label.trim();
  // å¾… XX
  if (/^待/.test(l)) {
    return "progress";
  }
  // XX中
  if (/中$/.test(l)) {
    return "active";
  }
  // å®Œæˆ / å®ŒæˆXX
  if (/完成/.test(l)) {
    return "completed";
  }
  // å¼ƒç”¨ / æ”¾å¼ƒ
  if (/弃用|放弃/.test(l)) {
    return "danger";
  }
  return "pending";
}
import { getDonatebaseinfoflow } from "@/api/project/donatebaseinfo";
import { getDonationProcessDetail } from "./donationProcess";
import CaseBasicInfo from "@/components/CaseBasicInfo";
import DonorMaintenanceStage from "./components/DonorMaintenanceStage";
import MedicalAssessmentStage from "./components/MedicalAssessmentStage";
import DeathJudgmentStage from "./components/DeathJudgmentStage";
import MedicalAssessmentStage from "./components/MedicalAssessmentStage";
import DonationConfirmStage from "./components/DonationConfirmStage";
import EthicalReviewStage from "./components/EthicalReviewStage";
import OrganAllocationStage from "./components/OrganAllocationStage";
import OrganProcurementStage from "./components/OrganProcurementStage";
import OrganUtilizationStage from "./components/OrganUtilizationStage";
import CaseBasicInfo from "@/components/CaseBasicInfo";
import dayjs from "dayjs";
// ============== å­—典映射(你后面自己改) ==============
const STAGE_DICT_MAP = {
  donatemaintenance: "maintain_type",
  deathinfo: "decide_type",
  medicalevaluation: "state_Evaluation",
  relativesconfirmation: "affirm_type",
  donateflowcharts: "sys_ethical",
  donateorgansService: "allocation_Status",
  donationwitness: "Obtain_status",
  donatecompletioninfo: "utilize_statue"
};
// state -> æµç¨‹çŠ¶æ€
const STATE_MAP = {
  0: "pending",
  1: "progress",
  2: "completed",
  3: "terminated"
};
// é˜¶æ®µé…ç½®
const STAGE_CONFIG = [
  { key: "donor_maintenance", name: "供者维护", apiKey: "donatemaintenance" },
  { key: "death_judgment", name: "死亡判定", apiKey: "deathinfo" },
  { key: "medical_assessment", name: "医学评估", apiKey: "medicalevaluation" },
  {
    key: "donation_confirm",
    name: "捐献确认",
    apiKey: "relativesconfirmation"
  },
  { key: "ethical_review", name: "伦理审查", apiKey: "donateflowcharts" },
  { key: "organ_allocation", name: "器官分配", apiKey: "donateorgansService" },
  { key: "organ_procurement", name: "器官获取", apiKey: "donationwitness" },
  { key: "organ_utilization", name: "器官利用", apiKey: "donatecompletioninfo" }
];
export default {
  name: "DonationProcessDetail",
  components: {
    CaseBasicInfo,
    DonorMaintenanceStage,
    MedicalAssessmentStage,
    DeathJudgmentStage,
    MedicalAssessmentStage,
    DonationConfirmStage,
    EthicalReviewStage,
    OrganAllocationStage,
    OrganProcurementStage,
    OrganUtilizationStage,
    CaseBasicInfo
    OrganUtilizationStage
  },
  dicts: ["sys_user_sex", "sys_BloodType", "sys_0_1"],
  dicts: [
    "decide_type",
    "maintain_type",
    "state_Evaluation",
    "affirm_type",
    "sys_ethical",
    "allocation_Status",
    "Obtain_status",
    "utilize_statue"
  ], // è¿™é‡Œåªå£°æ˜Žä¸€ä¸ªå³å¯ï¼Œå…¶ä½™é€šè¿‡ dict.type[xxx] ä½¿ç”¨
  data() {
    return {
      caseId: null,
      infoid: null,
      caseInfo: {
        id: "",
        caseNo: "",
        hospitalNo: "",
        donorName: "",
        gender: "",
        age: "",
        bloodType: "",
        diagnosis: "",
        status: "in_progress",
        createTime: "",
        registrant: "",
        currentStage: "donor_maintenance"
      },
      processStages: [
        {
          key: "donor_maintenance",
          name: "供者维护",
          status: "completed",
          completeTime: "2025-12-01 10:00:00",
          updateTime: "2025-12-01 10:00:00",
          operator: "张医生"
        },
        {
          key: "death_judgment",
          name: "死亡判定",
          status: "completed",
          completeTime: "2025-12-02 14:30:00",
          updateTime: "2025-12-02 14:30:00",
          operator: "王医生"
        },
        {
          key: "medical_assessment",
          name: "医学评估",
          status: "completed",
          completeTime: "2025-12-03 09:15:00",
          updateTime: "2025-12-03 09:15:00",
          operator: "李主任"
        },
        {
          key: "donation_confirm",
          name: "捐献确认",
          status: "completed",
          completeTime: "2025-12-03 11:00:00",
          updateTime: "2025-12-03 11:00:00",
          operator: "赵协调员"
        },
        {
          key: "ethical_review",
          name: "伦理审查",
          status: "completed",
          completeTime: "2025-12-03 15:20:00",
          updateTime: "2025-12-03 15:20:00",
          operator: "伦理委员会"
        },
        {
          key: "organ_allocation",
          name: "器官分配",
          status: "in_progress",
          updateTime: "2025-12-04 10:00:00",
          operator: "分配系统"
        },
        {
          key: "organ_procurement",
          name: "器官获取",
          status: "in_progress",
          operator: "待分配"
        },
        {
          key: "organ_utilization",
          name: "器官利用",
          status: "in_progress",
          operator: "待分配"
        }
      ],
      activeStage: "organ_allocation",
      activeStageName: "器官分配",
      activeStageData: {},
      loading: false
      caseInfo: {},
      processStages: [],
      activeStage: "",
      activeStageName: "",
      activeStageData: {}
    };
  },
  computed: {},
  created() {
    this.caseId = this.$route.query.id;
    this.infoid = this.$route.query.id;
    if (this.caseId) {
      this.getDetail();
    } else {
      this.generateMockData();
    }
    this.setActiveStage(this.activeStage);
  },
  methods: {
    async getDetail() {
      try {
        const res = await getDonatebaseinfoflow(this.caseId);
        const data = res;
        this.caseInfo = data.donatebaseinfo || {};
        this.processStages = STAGE_CONFIG.map(stage => {
          const obj = data[stage.apiKey] || {};
          console.log(stage.apiKey, "stage.apiKey");
          console.log(this.dict?.type?.[STAGE_DICT_MAP[stage.apiKey]]);
          const dictLabel =
            this.dict?.type?.[STAGE_DICT_MAP[stage.apiKey]]?.find(
              d => d.value == obj.state
            )?.label || "";
          const theme = mapDictLabelToTheme(dictLabel);
          return {
            key: stage.key,
            name: stage.name,
            dict: STAGE_DICT_MAP[stage.apiKey],
            state: obj.state,
            dictLabel,
            status: theme, // âœ… æ ¸å¿ƒï¼šUI ä¸»é¢˜ç”± dictLabel å†³å®š
            createtime: obj.createtime,
            updateTime: obj.updatetime,
            operator: obj.updateperson || obj.createperson
          };
        });
        const active =
          this.processStages.find(s => s.status == "progress") ||
          [...this.processStages].reverse().find(s => s.status == "completed");
        this.setActiveStage(active?.key || STAGE_CONFIG[0].key);
      } catch (e) {
        console.error(e);
        this.$message.error("获取流程详情失败");
      }
    },
    setActiveStage(key) {
      const stage = this.processStages.find(s => s.key == key);
      if (!stage) return;
      this.activeStage = key;
      this.activeStageName = stage.name;
      this.activeStageData = stage;
    },
    getStageComponent() {
      const componentMap = {
      const map = {
        donor_maintenance: "DonorMaintenanceStage",
        death_judgment: "DeathJudgmentStage",
        medical_assessment: "MedicalAssessmentStage",
@@ -234,169 +266,19 @@
        organ_procurement: "OrganProcurementStage",
        organ_utilization: "OrganUtilizationStage"
      };
      return componentMap[this.activeStage];
      return map[this.activeStage];
    },
    // èŽ·å–è¯¦æƒ…æ•°æ®
    async getDetail() {
      this.loading = true;
      try {
        const response = await getDonatebaseinfoflow(this.caseId);
        if (response.code === 200) {
          this.caseInfo = response.data.caseInfo;
          this.processStages = response.data.processStages;
          this.setActiveStage(response.data.currentStage);
        }
      } catch (error) {
        console.error("获取捐献进程详情失败:", error);
        this.$message.error("获取详情失败");
      } finally {
        this.loading = false;
      }
    },
    // ç”Ÿæˆæ¨¡æ‹Ÿæ•°æ®
    generateMockData() {
      this.caseInfo = {
        id: "202512001",
        caseNo: "C202512001",
        hospitalNo: "D202512001",
        donorName: "张三",
        gender: "0",
        age: 45,
        bloodType: "A",
        diagnosis: "脑外伤",
        status: "in_progress",
        createTime: "2025-12-01 08:00:00",
        registrant: "李协调员",
        currentStage: "organ_allocation"
      };
    },
    // è®¾ç½®å½“前激活阶段
    setActiveStage(stageKey) {
      this.activeStage = stageKey;
      const stage = this.processStages.find(s => s.key === stageKey);
      if (stage) {
        this.activeStageName = stage.name;
        this.activeStageData = stage;
        console.log(this.activeStageData, "this.activeStageData");
      }
    },
    // å¤„理阶段点击
    handleStageClick(stage) {
      if (stage.status !== "pending") {
        this.setActiveStage(stage.key);
      } else {
        this.$message.warning("该阶段尚未开始,无法查看详情");
      if (stage.status == "pending") {
        this.$message.warning("该阶段尚未开始");
        return;
      }
      this.setActiveStage(stage.key);
    },
    // èŽ·å–é˜¶æ®µçŠ¶æ€æ ‡ç­¾ç±»åž‹
    getStageStatusTag(status) {
      const map = {
        completed: "success",
        in_progress: "warning",
        pending: "info"
      };
      return map[status] || "info";
    },
    // èŽ·å–é˜¶æ®µçŠ¶æ€æ–‡æœ¬
    getStageStatusText(status) {
      const map = {
        completed: "已完成",
        in_progress: "进行中",
        pending: "未开始"
      };
      return map[status] || "未知";
    },
    // èŽ·å–æ•´ä½“çŠ¶æ€æ ‡ç­¾ç±»åž‹
    getOverallStatusTag(status) {
      const map = {
        completed: "success",
        in_progress: "warning",
        pending: "info",
        terminated: "danger"
      };
      return map[status] || "info";
    },
    // èŽ·å–æ•´ä½“çŠ¶æ€æ–‡æœ¬
    getStatusText(status) {
      const map = {
        completed: "已完成",
        in_progress: "进行中",
        pending: "未开始",
        terminated: "已终止"
      };
      return map[status] || "未知";
    },
    // æ—¶é—´æ ¼å¼åŒ–
    formatTime(time) {
      if (!time) return "-";
      return dayjs(time).format("YYYY-MM-DD HH:mm");
    },
    // èŽ·å–å½“å‰é˜¶æ®µåç§°
    getCurrentStageName() {
      const currentStage = this.processStages.find(
        stage => stage.status === "in_progress"
      );
      return currentStage ? currentStage.name : "已完成";
    },
    // ç¼–辑基本信息
    handleEditBasicInfo() {
      this.$message.info("编辑基本信息功能");
    },
    // å®Œæˆé˜¶æ®µ
    handleCompleteStage() {
      this.$confirm(`确定要完成【${this.activeStageName}】阶段吗?`, "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning"
      }).then(() => {
        // æ›´æ–°å½“前阶段状态
        const currentIndex = this.processStages.findIndex(
          stage => stage.key === this.activeStage
        );
        if (currentIndex !== -1) {
          this.processStages[currentIndex].status = "completed";
          this.processStages[
            currentIndex
          ].completeTime = new Date().toISOString();
          // æ¿€æ´»ä¸‹ä¸€ä¸ªé˜¶æ®µ
          if (currentIndex < this.processStages.length - 1) {
            this.processStages[currentIndex + 1].status = "in_progress";
            this.setActiveStage(this.processStages[currentIndex + 1].key);
          } else {
            this.caseInfo.status = "completed";
          }
          this.$message.success("阶段已完成");
        }
      });
    },
    // æŸ¥çœ‹è¯¦æƒ…
    handleViewDetail() {
      const routeMap = {
        donor_maintenance: "/case/donorMaintenance/detail",
        death_judgment: "/case/deathJudgment/detail",
        medical_assessment: "/case/medicalAssessment/detail",
        donation_confirm: "/case/donationConfirm/detail",
        ethical_review: "/case/ethicalReview/detail",
        organ_allocation: "/case/organAllocation/detail",
        organ_procurement: "/case/organProcurement/detail",
        organ_utilization: "/case/organUtilization/detail"
      };
      const route = routeMap[this.activeStage];
      if (route) {
        this.$router.push({
          path: route,
          query: { id: this.caseId }
        });
      }
    },
    // ä¿®æ”¹é˜¶æ®µä¿¡æ¯
    handleModifyStage() {
      this.$message.info(`修改${this.activeStageName}信息功能`);
      return time ? dayjs(time).format("YYYY-MM-DD HH:mm") : "-";
    }
  }
};
@@ -418,31 +300,40 @@
.process-container {
  display: flex;
  min-height: 600px; /* è®¾ç½®ä¸€ä¸ªæœ€å°é«˜åº¦ */
  min-height: 600px;
  /* è®¾ç½®ä¸€ä¸ªæœ€å°é«˜åº¦ */
  gap: 20px;
  align-items: flex-start; /* é¡¶éƒ¨å¯¹é½ */
  align-items: flex-start;
  /* é¡¶éƒ¨å¯¹é½ */
}
/* å·¦ä¾§æ—¶é—´çº¿æ ·å¼ - å›ºå®šé«˜åº¦ï¼Œå†…部滚动 */
.timeline-section {
  flex: 0 0 320px; /* å›ºå®šå®½åº¦ */
  flex: 0 0 320px;
  /* å›ºå®šå®½åº¦ */
  display: flex;
  flex-direction: column;
  background: white;
  border-radius: 6px;
  padding: 20px;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
  height: calc(120vh - 120px); /* æ ¹æ®è§†å£é«˜åº¦è‡ªé€‚应 */
  max-height: 1200px; /* è®¾ç½®æœ€å¤§é«˜åº¦ */
  position: sticky; /* ä½¿ç”¨ sticky å®šä½ */
  top: 20px; /* è·ç¦»é¡¶éƒ¨ 20px */
  height: calc(120vh - 120px);
  /* æ ¹æ®è§†å£é«˜åº¦è‡ªé€‚应 */
  max-height: 1200px;
  /* è®¾ç½®æœ€å¤§é«˜åº¦ */
  position: sticky;
  /* ä½¿ç”¨ sticky å®šä½ */
  top: 20px;
  /* è·ç¦»é¡¶éƒ¨ 20px */
}
.timeline-scroll-container {
  flex: 1;
  overflow-y: auto; /* å†…部可滚动 */
  overflow-y: auto;
  /* å†…部可滚动 */
  margin-top: 20px;
  padding-right: 8px; /* ä¸ºæ»šåŠ¨æ¡ç•™å‡ºç©ºé—´ */
  padding-right: 8px;
  /* ä¸ºæ»šåŠ¨æ¡ç•™å‡ºç©ºé—´ */
}
.timeline-scroll-container::-webkit-scrollbar {
@@ -476,7 +367,8 @@
  display: flex;
  flex-direction: column;
  gap: 20px;
  min-height: 0; /* é‡è¦ï¼šå…è®¸flex子项压缩 */
  min-height: 0;
  /* é‡è¦ï¼šå…è®¸flex子项压缩 */
}
.basic-info-section {
@@ -486,13 +378,16 @@
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
  display: flex;
  flex-direction: column;
  min-height: 0; /* é‡è¦ */
  min-height: 0;
  /* é‡è¦ */
}
.basic-info-content {
  flex: 1;
  max-height: 300px; /* åŸºæœ¬ä¿¡æ¯åŒºåŸŸæœ€å¤§é«˜åº¦ */
  overflow-y: auto; /* åŸºæœ¬ä¿¡æ¯å†…部可滚动 */
  max-height: 300px;
  /* åŸºæœ¬ä¿¡æ¯åŒºåŸŸæœ€å¤§é«˜åº¦ */
  overflow-y: auto;
  /* åŸºæœ¬ä¿¡æ¯å†…部可滚动 */
  margin-top: 20px;
  padding-right: 8px;
}
@@ -520,24 +415,30 @@
}
.stage-detail-section {
  flex: 1; /* å æ®å‰©ä½™ç©ºé—´ */
  flex: 1;
  /* å æ®å‰©ä½™ç©ºé—´ */
  background: white;
  border-radius: 6px;
  padding: 20px;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
  display: flex;
  flex-direction: column;
  min-height: 400px; /* æœ€å°é«˜åº¦ */
  max-height: 800px; /* æœ€å¤§é«˜åº¦ï¼Œå¯æ ¹æ®éœ€è¦è°ƒæ•´ */
  overflow: hidden; /* éšè—å¤–层溢出 */
  min-height: 400px;
  /* æœ€å°é«˜åº¦ */
  max-height: 800px;
  /* æœ€å¤§é«˜åº¦ï¼Œå¯æ ¹æ®éœ€è¦è°ƒæ•´ */
  overflow: hidden;
  /* éšè—å¤–层溢出 */
}
.stage-content-wrapper {
  flex: 1;
  overflow-y: auto; /* é˜¶æ®µè¯¦æƒ…内部可滚动 */
  overflow-y: auto;
  /* é˜¶æ®µè¯¦æƒ…内部可滚动 */
  margin-top: 20px;
  padding-right: 8px;
  min-height: 0; /* é‡è¦ */
  min-height: 0;
  /* é‡è¦ */
}
.stage-content-wrapper::-webkit-scrollbar {
@@ -563,7 +464,8 @@
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-shrink: 0; /* é˜²æ­¢è¢«åŽ‹ç¼© */
  flex-shrink: 0;
  /* é˜²æ­¢è¢«åŽ‹ç¼© */
}
.section-header h3 {
@@ -581,14 +483,22 @@
  cursor: pointer;
  transition: all 0.3s ease;
  border: 1px solid #e4e7ed;
  flex-shrink: 0; /* é˜²æ­¢è¢«åŽ‹ç¼© */
  flex-shrink: 0;
  /* é˜²æ­¢è¢«åŽ‹ç¼© */
}
.timeline-item:hover {
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  transform: translateY(-1px);
}
.timeline-item.danger {
  border-color: #f56c6c;
  background-color: #fef0f0;
}
.timeline-item.danger .timeline-marker {
  background-color: #f56c6c;
}
.timeline-item.active {
  border-color: #409eff;
  background-color: #f0f9ff;
@@ -599,7 +509,7 @@
  background-color: #f0f9e8;
}
.timeline-item.in-progress {
.timeline-item.progress {
  border-color: #e6a23c;
  background-color: #fdf6ec;
}
@@ -624,8 +534,11 @@
.timeline-item.completed .timeline-marker {
  background-color: #67c23a;
}
.timeline-item.active .timeline-marker {
  background-color: #409eff;
}
.timeline-item.in-progress .timeline-marker {
.timeline-item.progress .timeline-marker {
  background-color: #e6a23c;
}
@@ -684,7 +597,8 @@
    width: 100%;
    height: auto;
    max-height: 300px;
    position: static; /* å°å±å¹•取消 sticky */
    position: static;
    /* å°å±å¹•取消 sticky */
  }
  .timeline-scroll-container {
src/views/business/decide/DecideInfo.vue
@@ -24,7 +24,7 @@
        </el-tab-pane>
      </el-tabs>
    </el-card>
    <el-card class="detail-card common-info-card">
    <!-- <el-card class="detail-card common-info-card">
      <div slot="header" class="clearfix">
        <span class="detail-title">公共信息</span>
      </div>
@@ -46,33 +46,10 @@
              <el-input v-model="form.gainhospitalname" :readonly="!isEdit" />
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label="是否默哀缅怀" prop="isspendremember">
              <el-select
                v-model="form.isspendremember"
                :disabled="!isEdit"
                style="width: 100%"
              >
                <el-option label="是" :value="1" />
                <el-option label="否" :value="0" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label="恢复遗体仪容" prop="isrestoreremains">
              <el-select
                v-model="form.isrestoreremains"
                :disabled="!isEdit"
                style="width: 100%"
              >
                <el-option label="是" :value="1" />
                <el-option label="否" :value="0" />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
    </el-card>
    </el-card> -->
    <!-- è„‘死亡判定模块 -->
    <el-card v-if="activeJudgmentType === 'brain'" class="detail-card">
      <div slot="header" class="clearfix">
src/views/business/decide/index.vue
@@ -175,9 +175,9 @@
            <template slot-scope="scope">
              <el-tag
                v-if="scope.row.deathreason"
                :type="getDeathReasonTagType(scope.row.deathreason)"
                type="danger"
              >
                {{ getDeathReasonText(scope.row.deathreason) }}
                {{ scope.row.deathreason }}
              </el-tag>
              <span v-else>-</span>
            </template>
@@ -226,7 +226,7 @@
          >
            <template slot-scope="scope">
              <el-tag v-if="scope.row.heartdeathreason" type="danger">
                {{ getHeartDeathReasonText(scope.row.heartdeathreason) }}
                {{ scope.row.heartdeathreason }}
              </el-tag>
              <span v-else>-</span>
            </template>
src/views/business/ethicalReview/ethicalReviewInfo copy.vue
ÎļþÒÑɾ³ý
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 {
src/views/business/maintain/maintainInfo.vue
@@ -55,8 +55,17 @@
                  <el-tag
                    :type="scope.row.result === '阴性' ? 'success' : 'danger'"
                    effect="plain"
                    @click="handleResultClick(scope.row)"
                    style="cursor: pointer;"
                  >
                    {{ scope.row.result }}
                    <i
                      v-if="
                        scope.row.result === '阳性' && scope.row.positiveDetails
                      "
                      class="el-icon-info"
                      style="margin-left: 4px;"
                    ></i>
                  </el-tag>
                </template>
              </el-table-column>
@@ -263,6 +272,7 @@
                v-model="cultureForm.result"
                placeholder="请选择培养结果"
                style="width: 100%"
                @change="handleResultChange"
              >
                <el-option label="阴性" value="阴性" />
                <el-option label="阳性" value="阳性" />
@@ -270,7 +280,19 @@
            </el-form-item>
          </el-col>
        </el-row>
        <el-form-item
          v-if="cultureForm.result === '阳性'"
          label="阳性详情"
          prop="positiveDetails"
        >
          <el-input
            type="textarea"
            :rows="2"
            v-model="cultureForm.positiveDetails"
            placeholder="请输入阳性结果的详细信息"
            clearable
          />
        </el-form-item>
        <el-form-item label="附件">
          <UploadAttachment
            ref="cultureUploadAttachment"
@@ -502,6 +524,7 @@
        cultureType: "",
        sampleTime: "",
        result: "阴性",
        positiveDetails: "", // æ–°å¢žï¼šé˜³æ€§è¯¦æƒ…
        attachments: []
      },
      cultureFileList: [],
@@ -514,7 +537,8 @@
        ],
        result: [
          { required: true, message: "请选择培养结果", trigger: "change" }
        ]
        ],
        positiveDetails: [] // åŠ¨æ€éªŒè¯è§„åˆ™
      },
      cultureTypeOptions: [
        { value: "1", label: "血培养" },
@@ -661,7 +685,46 @@
        this.recordLoading = false;
      }
    },
    // å¤„理培养结果选择变化
    handleResultChange(value) {
      this.$nextTick(() => {
        if (value === "阳性") {
          this.cultureRules.positiveDetails = [
            { required: true, message: "请输入阳性详情", trigger: "blur" }
          ];
        } else {
          this.cultureRules.positiveDetails = [];
          this.cultureForm.positiveDetails = "";
        }
        // æ¸…除验证
        if (this.$refs.cultureForm) {
          this.$refs.cultureForm.clearValidate("positiveDetails");
        }
      });
    },
    // å¤„理点击培养结果标签
    handleResultClick(row) {
      if (row.result === "阳性" && row.positiveDetails) {
        this.$alert(
          `<div style="padding: 10px;">
        <h4 style="margin-bottom: 10px; color: #f56c6c;">阳性详情:</h4>
        <div style="background: #fef0f0; padding: 15px; border-radius: 4px; border-left: 4px solid #f56c6c;">
          <p style="margin: 0; white-space: pre-wrap; line-height: 1.5;">${row.positiveDetails}</p>
        </div>
      </div>`,
          "阳性结果详情",
          {
            dangerouslyUseHTMLString: true,
            confirmButtonText: "关闭",
            customClass: "result-details-dialog",
            showClose: false
          }
        );
      } else if (row.result === "阳性") {
        this.$message.warning("该阳性记录暂无详情信息");
      }
    },
    // ä¿å­˜æ‰€æœ‰æ•°æ®
    async handleSave() {
      try {
@@ -747,9 +810,13 @@
      });
    },
    // 5. ä¿®æ”¹ç¼–辑培养记录方法
    handleEditCulture(row) {
      this.cultureDialogTitle = "编辑培养记录";
      this.cultureForm = { ...row };
      this.cultureForm = {
        ...row,
        positiveDetails: row.positiveDetails || "" // ç¡®ä¿æœ‰positiveDetails字段
      };
      this.cultureFileList = row.attachments
        ? row.attachments.map(item => ({
            uid: item.id || Math.random(),
@@ -763,27 +830,44 @@
      this.cultureDialogVisible = true;
      this.$nextTick(() => {
        this.$refs.cultureForm && this.$refs.cultureForm.clearValidate();
        // å¦‚果编辑时是阳性结果,设置验证规则
        if (row.result === "阳性") {
          this.cultureRules.positiveDetails = [
            { required: true, message: "请输入阳性详情", trigger: "blur" }
          ];
        }
      });
    },
    // 6. ä¿®æ”¹ä¿å­˜åŸ¹å…»è®°å½•方法
    handleSaveCulture() {
      this.$refs.cultureForm.validate(valid => {
        if (valid) {
          this.cultureSaveLoading = true;
          if (this.cultureForm.id) {
          // æž„建保存数据
          const saveData = {
            ...this.cultureForm,
            // å¦‚果是阴性,清空阳性详情
            positiveDetails:
              this.cultureForm.result === "阳性"
                ? this.cultureForm.positiveDetails || ""
                : ""
          };
          if (saveData.id) {
            const index = this.cultureList.findIndex(
              item => item.id === this.cultureForm.id
              item => item.id === saveData.id
            );
            if (index !== -1) {
              this.cultureList.splice(index, 1, { ...this.cultureForm });
              this.cultureList.splice(index, 1, { ...saveData });
            }
          } else {
            this.cultureForm.id = Date.now();
            this.cultureList.push({ ...this.cultureForm });
            saveData.id = Date.now();
            this.cultureList.push({ ...saveData });
          }
          this.$message.success(this.cultureForm.id ? "修改成功" : "新增成功");
          this.$message.success(saveData.id ? "修改成功" : "新增成功");
          this.cultureDialogVisible = false;
          this.cultureSaveLoading = false;
        }
@@ -1171,4 +1255,22 @@
  font-size: 13px;
  margin-left: 8px;
}
/* 7. æ·»åŠ ä¸€äº›æ ·å¼ */
.result-details-dialog {
  width: 500px;
}
.result-details-dialog .el-message-box__content {
  padding: 0;
}
.result-details-dialog .el-message-box__header {
  background: #fef0f0;
  border-bottom: 1px solid #fde2e2;
}
.result-details-dialog .el-message-box__title {
  color: #f56c6c;
  font-weight: bold;
}
</style>
src/views/business/transfer/TransportEdit.vue
@@ -149,6 +149,24 @@
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="ICU评估医生" prop="icuDoctor">
              <el-input
                v-model="formData.icuDoctor"
                placeholder="请输入ICU评估医生"
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="ICU医生电话" prop="icuDoctorPhone">
              <el-input
                v-model="formData.icuDoctorPhone"
                placeholder="请输入ICU医生手机号"
              />
            </el-form-item>
          </el-col>
        </el-row>
      </el-card>
      <!-- è½¬è¿ä¿¡æ¯ -->
@@ -222,10 +240,10 @@
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="急诊科医生" prop="doctor">
            <el-form-item label="转运医生" prop="doctor">
              <el-input
                v-model="formData.doctor"
                placeholder="请输入急诊科医生"
                placeholder="请输入转运医生"
              />
            </el-form-item>
          </el-col>
@@ -241,7 +259,7 @@
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="护士" prop="nurse">
            <el-form-item label="转运护士" prop="nurse">
              <el-input v-model="formData.nurse" placeholder="请输入护士姓名" />
            </el-form-item>
          </el-col>
@@ -257,7 +275,7 @@
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="驾驶员" prop="driver">
            <el-form-item label="转运驾驶员" prop="driver">
              <el-input
                v-model="formData.driver"
                placeholder="请输入驾驶员姓名"
@@ -274,24 +292,7 @@
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="ICU评估医生" prop="icuDoctor">
              <el-input
                v-model="formData.icuDoctor"
                placeholder="请输入ICU评估医生"
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="ICU医生电话" prop="icuDoctorPhone">
              <el-input
                v-model="formData.icuDoctorPhone"
                placeholder="请输入ICU医生手机号"
              />
            </el-form-item>
          </el-col>
        </el-row>
      </el-card>
      <!-- é™„件信息 -->
src/views/business/transfer/index.vue
@@ -217,34 +217,49 @@
      >
        <template slot-scope="scope">
          <el-button
            size="mini"
            type="text"
            icon="el-icon-view"
            size="small"
            type="primary"
            @click="handleDetail(scope.row)"
            >详情</el-button
          >
          <el-button
            size="mini"
            type="text"
            icon="el-icon-edit"
            size="small"
            type="primary"
            style="margin-bottom: 10px;"
            plain
            @click="handleUpdate(scope.row)"
            >编辑</el-button
          >
          <el-button
            size="mini"
            type="text"
            icon="el-icon-video-play"
            size="small"
            type="primary"
            plain
            @click="handleStartTransport(scope.row)"
            v-if="scope.row.transitStatus === 1"
            >开始转运</el-button
          >
          <el-button
            size="mini"
            type="text"
            icon="el-icon-check"
            size="small"
            type="success"
            plain
            @click="handleCompleteTransport(scope.row)"
            v-if="scope.row.transitStatus === 2"
            >完成转运</el-button
          ><el-button
            size="small"
            type="danger"
            plain
            @click="cancelTransport(scope.row)"
            v-if="scope.row.transitStatus === 2"
            >取消转运</el-button
          >
          <el-button
            size="small"
            type="warning"
            plain
            @click="recoverTransport(scope.row)"
            v-if="scope.row.transitStatus === 4"
            >恢复转运</el-button
          >
        </template>
      </el-table-column>
@@ -664,6 +679,8 @@
        // å…¶ä»–字段可以根据需要从案例中获取
        transportStartPlace: caseData.treatmenthospitalname || "",
        contactPerson: caseData.coordinatorName || "",
        icuDoctor: caseData.icuDoctor,
        icuDoctorPhone: caseData.icuDoctorPhone,
        transitStatus: 1, // é»˜è®¤å¾…转运
        // æ¸…空其他字段
        id: undefined,
@@ -793,6 +810,18 @@
      this.actionTitle = "完成转运";
      this.actionText = "完成";
      this.actionOpen = true;
    } /** å®Œæˆè½¬è¿æ“ä½œ */,
    async cancelTransport(row) {
      this.currentTransport = row;
      this.actionTitle = "取消转运";
      this.actionText = "取消";
      this.actionOpen = true;
    } /** å®Œæˆè½¬è¿æ“ä½œ */,
    async recoverTransport(row) {
      this.currentTransport = row;
      this.actionTitle = "恢复转运";
      this.actionText = "恢复";
      this.actionOpen = true;
    },
    /** ç¡®è®¤æ“ä½œ */
@@ -806,13 +835,19 @@
          requestData.transitStatus = 2; // è®¾ç½®ä¸ºè½¬è¿ä¸­
        } else if (this.actionText === "完成") {
          requestData.transitStatus = 3; // è®¾ç½®ä¸ºè½¬è¿å®Œæˆ
        } else if (this.actionText === "取消") {
          requestData.transitStatus = 4; // è®¾ç½®ä¸ºè½¬è¿å–消
        } else if (this.actionText === "恢复") {
          requestData.transitStatus = 2; // è®¾ç½®ä¸ºè½¬è¿ä¸­
        }
        requestData.annexfilesList.forEach(item => {
          item.id = null;
        });
        const response = await transportEdit(requestData);
        if (response.code == 200) {
          this.$modal.msgSuccess(`${this.actionText}转运成功`);
          if (requestData.transitStatus==3) {
          if (requestData.transitStatus == 3) {
            const resappear = await donateInfo(requestData.reportId);
            if (resappear.code) {
              let obj = resappear.data;
src/views/login.vue
@@ -139,7 +139,8 @@
    this.getCode();
    this.getCookie();
    this.getAuthCode();
    this.loginForm.password=this.generatePassword();
    this.loginForm.password='';
    // this.loginForm.password=this.generatePassword();
    // this.avoidLogin();
  },
  methods: {
src/views/project/DonationProcess/index.vue
@@ -195,12 +195,13 @@
          />
        </template>
      </el-table-column>
      <el-table-column
        label="传染病"
        align="center"
        prop="infectious"
        width="180"
      />
       <el-table-column label="传染病" align="center" prop="infectious">
        <template slot-scope="scope">
          <span v-for="item in scope.row.infectious.split(',')"
            ><dict-tag :options="dict.type.sys_Infectious" :value="item" />
          </span>
        </template>
      </el-table-column>
      <el-table-column
        label="首诊医院"
@@ -405,6 +406,7 @@
    "sys_donornode",
    "sys_BloodType",
    "sys_EthicalReview",
    'sys_Infectious',
    "sys_BaseAssessConclusion"
  ],
  data() {
src/views/project/distributedetail/index.vue
@@ -188,7 +188,7 @@
          <dict-tag :options="dict.type.sys_user_sex" :value="scope.row.sex"/>
        </template>
      </el-table-column>
      <el-table-column label="与捐赠者关系 æ ¹æ®å­—å…¸sys_FamilyRelation" align="center" prop="familyrelations">
      <el-table-column label="与捐献者关系 æ ¹æ®å­—å…¸sys_FamilyRelation" align="center" prop="familyrelations">
        <template slot-scope="scope">
          <dict-tag :options="dict.type.sys_FamilyRelation" :value="scope.row.familyrelations"/>
        </template>
@@ -240,7 +240,7 @@
        </template>
      </el-table-column>
    </el-table>
    <pagination
      v-show="total>0"
      :total="total"
@@ -296,8 +296,8 @@
            ></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="与捐赠者关系 æ ¹æ®å­—å…¸sys_FamilyRelation" prop="familyrelations">
          <el-input v-model="form.familyrelations" placeholder="请输入与捐赠者关系 æ ¹æ®å­—å…¸sys_FamilyRelation" />
        <el-form-item label="与捐献者关系 æ ¹æ®å­—å…¸sys_FamilyRelation" prop="familyrelations">
          <el-input v-model="form.familyrelations" placeholder="请输入与捐献者关系 æ ¹æ®å­—å…¸sys_FamilyRelation" />
        </el-form-item>
        <el-form-item label="联系电话" prop="phone">
          <el-input v-model="form.phone" placeholder="请输入联系电话" />
src/views/project/donatebaseinfo/Archivedpage.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,322 @@
<template>
  <div class="app-container archive-container">
    <div class="archive-header">
  <span class="archive-title">捐献案例归档管理</span>
  <span class="archive-tag">仅展示已归档案例(不可编辑)</span>
</div>
    <!-- æœç´¢ -->
     <el-card class="search-card">
       <el-form
         :model="queryParams"
         ref="queryForm"
         :inline="true"
         v-show="showSearch"
         label-width="70px"
       >
         <el-row :gutter="8">
           <el-col :span="5">
             <el-form-item label="姓名" prop="name">
               <el-input
                 v-model="queryParams.name"
                 placeholder="请输入姓名"
                 clearable
                 size="small"
               />
             </el-form-item>
           </el-col>
           <el-col :span="5">
             <el-form-item label="报告医院" prop="treatmenthospitalno">
               <org-selecter
                 ref="orgSelecter"
                 :org-type="'3'"
                 v-model="queryParams.treatmenthospitalno"
               />
             </el-form-item>
           </el-col>
           <el-col :span="9">
             <el-form-item label="归档时间">
               <el-date-picker
                 v-model="selecttime"
                 type="daterange"
                 range-separator="至"
                 start-placeholder="开始日期"
                 end-placeholder="结束日期"
                 value-format="yyyy-MM-dd"
                 @change="getTimeList"
               />
             </el-form-item>
           </el-col>
         </el-row>
         <el-row>
           <el-col :span="4">
             <el-form-item>
               <el-button
                 type="primary"
                 icon="el-icon-search"
                 size="mini"
                 @click="handleQuery"
               >
                 æœç´¢
               </el-button>
               <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">
                 é‡ç½®
               </el-button>
             </el-form-item>
           </el-col>
         </el-row>
       </el-form>
     </el-card>
    <!-- æ“ä½œ -->
    <el-row :gutter="10" class="mb8">
      <el-col :span="1.5">
        <el-button
          type="warning"
          plain
          icon="el-icon-download"
          size="mini"
          :loading="exportLoading"
          @click="handleExport"
        >
          å¯¼å‡º
        </el-button>
      </el-col>
    </el-row>
    <el-card class="table-card">
      <el-table
        v-loading="loading"
        :data="tableList"
         class="archive-table"
        border
      >
        <el-table-column label="姓名" align="center" prop="name" />
        <el-table-column label="住院号" align="center" prop="inpatientno" />
        <el-table-column label="性别" align="center" prop="sex">
          <template slot-scope="scope">
            <dict-tag
              :options="dict.type.sys_user_sex"
              :value="scope.row.sex"
            />
          </template>
        </el-table-column>
        <el-table-column label="年龄" align="center" prop="age" />
        <el-table-column label="血型" align="center" prop="bloodtype">
          <template slot-scope="scope">
            <dict-tag
              :options="dict.type.sys_BloodType"
              :value="scope.row.bloodtype"
            />
          </template>
        </el-table-column>
        <el-table-column label="归档时间" align="center" prop="filingtime" />
        <el-table-column
          label="操作"
          width="120"
          align="center"
          fixed="right"
        >
          <template slot-scope="scope">
            <el-button
              size="mini"
              type="text"
              style="color:#67C23A;"
              @click="handleCancelArchive(scope.row)"
            >
              å–消归档
            </el-button>
          </template>
        </el-table-column>
      </el-table>
    </el-card>
    <!-- è¡¨æ ¼ -->
    <pagination
      v-show="total > 0"
      :total="total"
      :page.sync="queryParams.pageNum"
      :limit.sync="queryParams.pageSize"
      @pagination="getList"
    />
  </div>
</template>
<script>
import {
  listDonatebaseinfo,
  updateDonatebaseinfo,
  exportDonatebaseinfo
} from "@/api/project/donatebaseinfo";
import OrgSelecter from "@/views/project/components/orgselect";
export default {
  name: "DonateArchive",
  components: { OrgSelecter },
  dicts: ["sys_user_sex", "sys_BloodType"],
  data() {
    return {
      loading: true,
      exportLoading: false,
      tableList: [],
      total: 0,
      selecttime: [],
      queryParams: {
        pageNum: 1,
        pageSize: 10,
        name: null,
        treatmenthospitalno: null,
        starttime: null,
        endtime: null,
        recordstate: "4" // âœ… åªæŸ¥å½’档案例
      }
    };
  },
  created() {
    this.getList();
  },
  methods: {
    getTimeList() {
      if (!this.selecttime) return;
      const [start, end] = this.selecttime;
      this.queryParams.starttime = start;
      this.queryParams.endtime = end;
    },
    getList() {
      this.loading = true;
      listDonatebaseinfo(this.queryParams).then(res => {
        this.tableList = res.data;
        this.total = res.total;
        this.loading = false;
      });
    },
    handleQuery() {
      this.queryParams.pageNum = 1;
      this.getList();
    },
    resetQuery() {
      this.queryParams = {
        pageNum: 1,
        pageSize: 10,
        name: null,
        treatmenthospitalno: null,
        starttime: null,
        endtime: null,
        recordstate: "4"
      };
      this.selecttime = [];
      this.resetForm("queryForm");
      this.getList();
    },
    /** å–消归档 */
    handleCancelArchive(row) {
      this.$confirm("确认取消归档?取消后案例将恢复为正常状态。", "提示", {
        type: "warning"
      }).then(async () => {
        const res = await updateDonatebaseinfo({
          id: row.id,
          recordstate: "2"
        });
        if (res.code === 200) {
          this.$modal.msgSuccess("已取消归档");
          this.getList();
        } else {
          this.$modal.msgError(res.msg || "操作失败");
        }
      }).catch(() => {});
    },
    /** å¯¼å‡º */
    async handleExport() {
      this.$modal.confirm("是否导出归档案例数据?").then(async () => {
        this.exportLoading = true;
        const res = await exportDonatebaseinfo(this.queryParams);
        this.$download.name(res.msg);
        this.exportLoading = false;
      }).catch(() => {});
    }
  }
};
</script>
<style scoped>
/* ===== å½’档页面整体风格 ===== */
.archive-container {
  background: #f5f7fa;
  min-height: calc(100vh - 84px);
  padding: 20px;
}
/* ===== é¡µé¢æ ‡é¢˜åŒº ===== */
.archive-header {
  display: flex;
  align-items: center;
  margin-bottom: 16px;
}
.archive-title {
  font-size: 18px;
  font-weight: bold;
  color: #606266;
}
.archive-tag {
  margin-left: 12px;
  background: #fdf6ec;
  color: #e6a23c;
  border: 1px solid #faecd8;
  padding: 4px 10px;
  border-radius: 4px;
  font-size: 12px;
}
/* ===== æœç´¢åŒºåŸŸ ===== */
.search-card {
  background: #ffffff;
  padding: 18px 20px 0;
  border-radius: 6px;
  margin-bottom: 16px;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04);
}
/* ===== è¡¨æ ¼åŒºåŸŸ ===== */
.table-card {
  background: #ffffff;
  padding: 16px;
  border-radius: 6px;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04);
}
/* ===== å½’档表格行样式(重点) ===== */
::v-deep .archive-table .el-table__row {
  background: #fdfbf5;
}
::v-deep .archive-table .el-table__row:hover > td {
  background: #f9f3e3 !important;
}
/* ===== æ“ä½œæŒ‰é’® ===== */
.archive-action-btn {
  color: #67c23a;
  font-weight: 500;
}
/* ===== ç©ºçŠ¶æ€ ===== */
.empty-archive {
  text-align: center;
  padding: 60px 0;
  color: #909399;
}
</style>
src/views/project/donatebaseinfo/EditCaseModal.vue
@@ -181,18 +181,12 @@
          <el-row :gutter="20">
            <el-col :span="8">
              <el-form-item label="入院时间" prop="Reporttothehospital">
                <el-input
                  v-model="formData.Reporttothehospital"
                  placeholder="请输入入院时间"
                />
              </el-form-item>
            </el-col>
            <el-col :span="8">
              <el-form-item label="入院科室" prop="treatmentdeptname">
                <el-input
                  v-model="formData.treatmentdeptname"
                  placeholder="请输入科室"
                <el-date-picker
                  v-model="formData.entryTime"
                  type="datetime"
                  placeholder="选择入院时间"
                  value-format="yyyy-MM-dd HH:mm:ss"
                  style="width: 100%"
                />
              </el-form-item>
            </el-col>
@@ -370,14 +364,7 @@
                />
              </el-form-item>
            </el-col>
            <el-col :span="8">
              <el-form-item label="协调员编号" prop="coordinatorNo">
                <el-input
                  v-model="formData.coordinatorNo"
                  placeholder="请输入协调员编号"
                />
              </el-form-item>
            </el-col>
            <el-col :span="8">
              <el-form-item label="协调员姓名" prop="coordinatorName">
                <el-input
@@ -496,10 +483,10 @@
              </el-form-item>
            </el-col>
            <el-col :span="8">
              <el-form-item label="与捐赠者关系" prop="familyrelations">
              <el-form-item label="与捐献者关系" prop="familyrelations">
                <el-select
                  v-model="formData.familyrelations"
                  placeholder="请选择与捐赠者关系"
                  placeholder="请选择与捐献者关系"
                >
                  <el-option
                    v-for="dict in dict.type.sys_FamilyRelation || []"
@@ -1013,19 +1000,21 @@
        // è®¾ç½®åœ°å€ä¿¡æ¯
        if (data.residenceprovince) {
          this.residenceAddress = {
            sheng: data.residenceprovincename,
            shi: data.residencecityname,
            qu: data.residencetownname
            sheng: data.residenceprovince,
            shi: data.residencecity,
            qu: data.residencetown
          };
        }
        if (data.registerprovince) {
          this.registerAddress = {
            sheng: data.registerprovincename,
            shi: data.registercityname,
            qu: data.registertownname
            sheng: data.registerprovince,
            shi: data.registercity,
            qu: data.registertown
          };
        }
      console.log(this.registerAddress,'registerAddress12');
        this.calculateAge(data.birthday);
      } catch (error) {
@@ -1137,6 +1126,8 @@
    },
    handleRegisterAddressChange(address) {
      console.log(this.registerAddress,'registerAddress11');
      this.formData.registerprovince = address.sheng;
      this.formData.registercity = address.shi;
      this.formData.registertown = address.qu;
src/views/project/donatebaseinfo/index.vue
@@ -101,6 +101,12 @@
        </template>
      </el-table-column>
      <el-table-column
        label="住院号"
        align="center"
        prop="inpatientno"
        width="200"
      />
      <el-table-column
        label="捐献进度"
        align="center"
        prop="workflow"
@@ -129,12 +135,7 @@
          />
        </template>
      </el-table-column>
      <el-table-column
        label="案例编号"
        align="center"
        prop="caseNo"
        width="200"
      />
      <el-table-column label="性别" align="center" prop="sex" width="100">
        <template slot-scope="scope">
          <dict-tag
@@ -158,16 +159,12 @@
          }}
        </template>
      </el-table-column>
<<<<<<< HEAD
      <el-table-column label="GCS评分" align="center" prop="gcsScore" width="100"/>
=======
      <el-table-column
        label="上报医院"
        label="GCS评分"
        align="center"
        prop="treatmenthospitalname"
        prop="gcsScore"
        width="100"
      />
      <el-table-column label="GCS评分" align="center" prop="gcsScore" />
>>>>>>> 059398ad3ad81ea49dfb75ac09f268bc0b0f6145
      <el-table-column label="血型" align="center" prop="bloodtype" width="100">
        <template slot-scope="scope">
          <dict-tag
@@ -176,20 +173,24 @@
          />
        </template>
      </el-table-column>
      <el-table-column label="传染病" align="center" prop="infectious">
        <template slot-scope="scope">
          <span v-for="item in scope.row.infectious.split(',')"
            ><dict-tag :options="dict.type.sys_Infectious" :value="item" />
          </span>
        </template>
      </el-table-column>
      <el-table-column label="疾病诊断" align="center" prop="diagnosisname" />
      <el-table-column
        label="报告医院"
        label="协调员"
        align="center"
        prop="treatmenthospitalname"
      />
      <el-table-column
        label="报告人"
        align="center"
        prop="reportername"
        prop="coordinatorName"
        width="100"
      />
      <el-table-column
        label="操作"
        width="190"
        width="220"
        align="center"
        class-name="small-padding fixed-width"
        fixed="right"
@@ -204,14 +205,29 @@
          >
            è¯¦æƒ…
          </el-button>
          <el-button
            size="mini"
            type="text"
            icon="el-icon-edit"
            @click="handleOpenEdit(scope.row)"
            v-hasPermi="['project:donatebaseinfo:edit']"
            >编辑</el-button
            v-if="scope.row.recordstate !== '4'"
          >
            ç¼–辑
          </el-button>
          <!-- âœ… æ–°å¢žï¼šå½’档按钮 -->
          <el-button
            size="mini"
            type="text"
            style="color:#E6A23C;"
            @click="handleArchive(scope.row)"
            v-if="scope.row.recordstate !== '4'"
          >
            å½’æ¡£
          </el-button>
          <el-button
            size="mini"
            type="text"
@@ -223,6 +239,7 @@
          >
            ç»ˆæ­¢
          </el-button>
          <el-button
            size="mini"
            type="text"
@@ -270,7 +287,8 @@
import {
  listDonatebaseinfo,
  addDonatebaseinfo,
  exportDonatebaseinfo
  exportDonatebaseinfo,
  updateDonatebaseinfo
} from "@/api/project/donatebaseinfo";
import Li_area_select from "@/components/Address";
import OrgSelecter from "@/views/project/components/orgselect";
@@ -287,6 +305,7 @@
  dicts: [
    "sys_user_sex",
    "sys_BloodType",
    "sys_Infectious",
    "sys_DonationCategory",
    "sys_donornode"
  ],
@@ -455,7 +474,52 @@
      this.currentRecord = { ...row };
      this.modalVisible = { ...this.modalVisible, restore: true };
    },
    /** å½’æ¡£ */
    handleArchive(row) {
      this.$confirm("确认将该案例归档?归档后将不可编辑。", "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning"
      })
        .then(async () => {
          try {
            const res = await updateDonatebaseinfo({
              id: row.id,
              recordstate: "4",
              filingtime: this.getCurrentTime()
            });
            if (res.code === 200) {
              this.$modal.msgSuccess("归档成功");
              this.getList();
            } else {
              this.$modal.msgError(res.msg || "归档失败");
            }
          } catch (err) {
            this.$modal.msgError("操作失败");
          }
        })
        .catch(() => {});
    },
    // èŽ·å–å½“å‰æ—¶é—´
    getCurrentTime() {
      const now = new Date();
      return `${now.getFullYear()}-${(now.getMonth() + 1)
        .toString()
        .padStart(2, "0")}-${now
        .getDate()
        .toString()
        .padStart(2, "0")} ${now
        .getHours()
        .toString()
        .padStart(2, "0")}:${now
        .getMinutes()
        .toString()
        .padStart(2, "0")}:${now
        .getSeconds()
        .toString()
        .padStart(2, "0")}`;
    },
    getTimeList() {
      if (!this.selecttime) {
        // this.queryParams.starttime = "1998-01-01 00:00:00";
@@ -541,12 +605,13 @@
        this.loading = false;
      }
    },
    /** æ‰“开编辑弹窗 */
    handleOpenEdit(row) {
      // ç¡®ä¿åœ¨æ‰“开弹框前重置currentEditData
      this.currentEditData = {};
      if (row.recordstate === "4") {
        this.$modal.msgWarning("归档案例不可编辑");
        return;
      }
      // ä½¿ç”¨$nextTick确保DOM更新完成
      this.currentEditData = {};
      this.$nextTick(() => {
        this.currentEditData = { ...row };
        this.editModalVisible = true;
src/views/project/donatereview/index.vue
@@ -629,10 +629,10 @@
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label="与捐赠者关系" prop="familyrelations">
            <el-form-item label="与捐献者关系" prop="familyrelations">
              <el-select
                v-model="form.familyrelations"
                placeholder="请选择与捐赠者关系"
                placeholder="请选择与捐献者关系"
              >
                <el-option
                  v-for="dict in dict.type.sys_FamilyRelation"
src/views/project/donationdetails/index.vue
@@ -664,10 +664,10 @@
              </el-form-item>
            </el-col>
            <el-col :span="8">
              <el-form-item label="与捐赠者关系" prop="familyrelations">
              <el-form-item label="与捐献者关系" prop="familyrelations">
                <el-select
                  v-model="form.familyrelations"
                  placeholder="请选择与捐赠者关系"
                  placeholder="请选择与捐献者关系"
                >
                  <el-option
                    v-for="dict in dict.type.sys_FamilyRelation || []"
@@ -1023,7 +1023,7 @@
        >
          <el-row>
            <el-col :span="6">
              <el-form-item label="捐赠者民族" prop="nation">
              <el-form-item label="捐献者民族" prop="nation">
                <el-select
                  filterable
                  v-model="affirmform.nation"
@@ -1039,7 +1039,7 @@
              </el-form-item>
            </el-col>
            <el-col :span="6">
              <el-form-item label="捐赠者学历" prop="education">
              <el-form-item label="捐献者学历" prop="education">
                <el-select
                  v-model="affirmform.education"
                  placeholder="请选择学历"
@@ -1054,7 +1054,7 @@
              </el-form-item>
            </el-col>
            <el-col :span="6">
              <el-form-item label="捐赠者职业" prop="occupation">
              <el-form-item label="捐献者职业" prop="occupation">
                <el-select
                  filterable
                  v-model="affirmform.occupation"
@@ -1079,12 +1079,12 @@
            <el-col :span="6">
              <el-form-item
                align="left"
                label="与捐赠者关系"
                label="与捐献者关系"
                prop="familyrelations"
              >
                <el-select
                  v-model="affirmform.familyrelations"
                  placeholder="请选择与捐赠者关系"
                  placeholder="请选择与捐献者关系"
                >
                  <el-option
                    v-for="dict in dict.type.sys_FamilyRelation || []"
src/views/project/externalperson/index.vue
@@ -460,12 +460,7 @@
            }
          }
        ],
        bankcardno: [
          { required: true, message: "请输入银行账号", trigger: "blur" }
        ],
        depositbank: [
          { required: true, message: "请输入开户银行", trigger: "blur" }
        ]
      },
      //是否是专家费的OPO审批人员
      ismanager: false
src/views/project/funddetail/index.vue
@@ -133,8 +133,8 @@
                <el-option label="请选择字典生成" value="" />
              </el-select>
            </el-form-item></el-col>
          <el-col :span="5"><el-form-item label="与捐赠者关系 æ ¹æ®å­—å…¸sys_FamilyRelation" prop="familyrelations">
              <el-input v-model="form.familyrelations" placeholder="请输入与捐赠者关系 æ ¹æ®å­—å…¸sys_FamilyRelation" />
          <el-col :span="5"><el-form-item label="与捐献者关系 æ ¹æ®å­—å…¸sys_FamilyRelation" prop="familyrelations">
              <el-input v-model="form.familyrelations" placeholder="请输入与捐献者关系 æ ¹æ®å­—å…¸sys_FamilyRelation" />
            </el-form-item></el-col>
          <el-col :span="5"><el-form-item label="联系电话" prop="phone">
              <el-input v-model="form.phone" placeholder="请输入联系电话" />
src/views/project/reimbursementpayee/index.vue
@@ -69,10 +69,10 @@
          @keyup.enter.native="handleQuery"
        />
      </el-form-item>
      <el-form-item label="与捐赠者关系 æ ¹æ®å­—å…¸sys_FamilyRelation" prop="familyrelations">
      <el-form-item label="与捐献者关系 æ ¹æ®å­—å…¸sys_FamilyRelation" prop="familyrelations">
        <el-input
          v-model="queryParams.familyrelations"
          placeholder="请输入与捐赠者关系 æ ¹æ®å­—å…¸sys_FamilyRelation"
          placeholder="请输入与捐献者关系 æ ¹æ®å­—å…¸sys_FamilyRelation"
          clearable
          size="small"
          @keyup.enter.native="handleQuery"
@@ -167,7 +167,7 @@
      <el-table-column label="卡号" align="center" prop="bankcardno" />
      <el-table-column label="申请金额" align="center" prop="amount" />
      <el-table-column label="联系电话" align="center" prop="phone" />
      <el-table-column label="与捐赠者关系 æ ¹æ®å­—å…¸sys_FamilyRelation" align="center" prop="familyrelations" />
      <el-table-column label="与捐献者关系 æ ¹æ®å­—å…¸sys_FamilyRelation" align="center" prop="familyrelations" />
      <el-table-column label="付款日期" align="center" prop="paiddate" width="180">
        <template slot-scope="scope">
          <span>{{ parseTime(scope.row.paiddate, '{y}-{m}-{d}') }}</span>
@@ -199,7 +199,7 @@
        </template>
      </el-table-column>
    </el-table>
    <pagination
      v-show="total>0"
      :total="total"
@@ -237,8 +237,8 @@
        <el-form-item label="联系电话" prop="phone">
          <el-input v-model="form.phone" placeholder="请输入联系电话" />
        </el-form-item>
        <el-form-item label="与捐赠者关系 æ ¹æ®å­—å…¸sys_FamilyRelation" prop="familyrelations">
          <el-input v-model="form.familyrelations" placeholder="请输入与捐赠者关系 æ ¹æ®å­—å…¸sys_FamilyRelation" />
        <el-form-item label="与捐献者关系 æ ¹æ®å­—å…¸sys_FamilyRelation" prop="familyrelations">
          <el-input v-model="form.familyrelations" placeholder="请输入与捐献者关系 æ ¹æ®å­—å…¸sys_FamilyRelation" />
        </el-form-item>
        <el-form-item label="付款日期" prop="paiddate">
          <el-date-picker clearable size="small"
src/views/project/relativesconfirmation/index.vue
@@ -449,12 +449,12 @@
            <el-col :span="6">
              <el-form-item
                align="left"
                label="与捐赠者关系"
                label="与捐献者关系"
                prop="familyrelations"
              >
                <el-select
                  v-model="form.familyrelations"
                  placeholder="请选择与捐赠者关系"
                  placeholder="请选择与捐献者关系"
                >
                  <el-option
                    v-for="dict in dict.type.sys_FamilyRelation"
src/views/project/travelexpensedeal/index.vue
@@ -227,7 +227,7 @@
    <!-- æ·»åŠ æˆ–ä¿®æ”¹è´¹ç”¨ç”³è¯·æ˜Žç»†å¯¹è¯æ¡† -->
    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
        <el-form-item label="收益人" prop="beneficiaryname">
          <el-input
            v-model="form.beneficiaryname"
@@ -260,12 +260,12 @@
          </el-select>
        </el-form-item>
        <el-form-item
          label="与捐赠者关系 æ ¹æ®å­—å…¸sys_FamilyRelation"
          label="与捐献者关系 æ ¹æ®å­—å…¸sys_FamilyRelation"
          prop="familyrelations"
        >
          <el-input
            v-model="form.familyrelations"
            placeholder="请输入与捐赠者关系 æ ¹æ®å­—å…¸sys_FamilyRelation"
            placeholder="请输入与捐献者关系 æ ¹æ®å­—å…¸sys_FamilyRelation"
          />
        </el-form-item>
        <el-form-item label="联系电话" prop="phone">
@@ -574,4 +574,4 @@
    },
  },
};
</script>
</script>
vue.config.js
@@ -1,13 +1,13 @@
'use strict'
const path = require('path')
"use strict";
const path = require("path");
function resolve(dir) {
  return path.join(__dirname, dir)
  return path.join(__dirname, dir);
}
const name = process.env.VUE_APP_TITLE || '青附院OPO管理平台' // ç½‘页标题
const name = process.env.VUE_APP_TITLE || "青附院OPO管理平台"; // ç½‘页标题
const port = process.env.port || process.env.npm_config_port || 80 // ç«¯å£
const port = process.env.port || process.env.npm_config_port || 80; // ç«¯å£
// vue.config.js é…ç½®è¯´æ˜Ž
//官方vue.config.js å‚考文档 https://cli.vuejs.org/zh/config/#css-loaderoptions
@@ -18,36 +18,37 @@
  // ä¾‹å¦‚ https://www.ruoyi.vip/。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.ruoyi.vip/admin/,则设置 baseUrl ä¸º /admin/。
  publicPath: process.env.NODE_ENV === "production" ? "/" : "/",
  // åœ¨npm run build æˆ– yarn build æ—¶ ï¼Œç”Ÿæˆæ–‡ä»¶çš„目录名称(要和baseUrl的生产环境路径一致)(默认dist)
  outputDir: 'dist',
  outputDir: "dist",
  // ç”¨äºŽæ”¾ç½®ç”Ÿæˆçš„静态资源 (js、css、img、fonts) çš„;(项目打包之后,静态资源会放在这个文件夹下)
  assetsDir: 'static',
  assetsDir: "static",
  // æ˜¯å¦å¼€å¯eslint保存检测,有效值:ture | false | 'error'
  lintOnSave: process.env.NODE_ENV === 'development',
  lintOnSave: process.env.NODE_ENV === "development",
  // å¦‚果你不需要生产环境的 source map,可以将其设置为 false ä»¥åŠ é€Ÿç”Ÿäº§çŽ¯å¢ƒæž„å»ºã€‚
  productionSourceMap: false,
  // webpack-dev-server ç›¸å…³é…ç½®
  devServer: {
    host: '0.0.0.0',
    host: "0.0.0.0",
    port: port,
    open: true,
    proxy: {
      // detail: https://cli.vuejs.org/config/#devserver-proxy
      [process.env.VUE_APP_BASE_API]: {
        target:`http://localhost:8080`,
        target: `http://www.qdopo.com:9095`,
        // target:`http://192.168.76.25:9095`,//打包地址
        // target:`http://192.168.100.10:8080`,
        // target:`http://192.168.100.137:8080`,
        // target: `https://slb.hospitalstar.com:9093`,
        changeOrigin: true,
        pathRewrite: {
          ['^' + process.env.VUE_APP_BASE_API]: ''
          ["^" + process.env.VUE_APP_BASE_API]: ""
        }
      },
      //当pdf和数据接口不在同一个请求地址下时,为pdf预览追加一个代理
      '/pdf': {
        target: 'http://192.168.1.4/pdf/data',
      "/pdf": {
        target: "http://192.168.1.4/pdf/data",
        changOrigin: true,
        pathRewrite: {
            '^/pdf': ''
          "^/pdf": ""
        }
      }
    },
@@ -57,73 +58,70 @@
    name: name,
    resolve: {
      alias: {
        '@': resolve('src')
        "@": resolve("src")
      }
    }
  },
  chainWebpack(config) {
    config.plugins.delete('preload') // TODO: need test
    config.plugins.delete('prefetch') // TODO: need test
    config.plugins.delete("preload"); // TODO: need test
    config.plugins.delete("prefetch"); // TODO: need test
    // set svg-sprite-loader
    config.module
      .rule('svg')
      .exclude.add(resolve('src/assets/icons'))
      .end()
      .rule("svg")
      .exclude.add(resolve("src/assets/icons"))
      .end();
    config.module
      .rule('icons')
      .rule("icons")
      .test(/\.svg$/)
      .include.add(resolve('src/assets/icons'))
      .include.add(resolve("src/assets/icons"))
      .end()
      .use('svg-sprite-loader')
      .loader('svg-sprite-loader')
      .use("svg-sprite-loader")
      .loader("svg-sprite-loader")
      .options({
        symbolId: 'icon-[name]'
        symbolId: "icon-[name]"
      })
      .end()
      .end();
    config
      .when(process.env.NODE_ENV !== 'development',
        config => {
          config
            .plugin('ScriptExtHtmlWebpackPlugin')
            .after('html')
            .use('script-ext-html-webpack-plugin', [{
              // `runtime` must same as runtimeChunk name. default is `runtime`
              inline: /runtime\..*\.js$/
            }])
            .end()
          config
            .optimization.splitChunks({
              chunks: 'all',
              cacheGroups: {
                libs: {
                  name: 'chunk-libs',
                  test: /[\\/]node_modules[\\/]/,
                  priority: 10,
                  chunks: 'initial' // only package third parties that are initially dependent
                },
                elementUI: {
                  name: 'chunk-elementUI', // split elementUI into a single package
                  priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app
                  test: /[\\/]node_modules[\\/]_?element-ui(.*)/ // in order to adapt to cnpm
                },
                commons: {
                  name: 'chunk-commons',
                  test: resolve('src/components'), // can customize your rules
                  minChunks: 3, //  minimum common number
                  priority: 5,
                  reuseExistingChunk: true
                }
              }
            })
          config.optimization.runtimeChunk('single'),
    config.when(process.env.NODE_ENV !== "development", config => {
      config
        .plugin("ScriptExtHtmlWebpackPlugin")
        .after("html")
        .use("script-ext-html-webpack-plugin", [
          {
            from: path.resolve(__dirname, './public/robots.txt'), //防爬虫文件
            to: './' //到根目录下
            // `runtime` must same as runtimeChunk name. default is `runtime`
            inline: /runtime\..*\.js$/
          }
        ])
        .end();
      config.optimization.splitChunks({
        chunks: "all",
        cacheGroups: {
          libs: {
            name: "chunk-libs",
            test: /[\\/]node_modules[\\/]/,
            priority: 10,
            chunks: "initial" // only package third parties that are initially dependent
          },
          elementUI: {
            name: "chunk-elementUI", // split elementUI into a single package
            priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app
            test: /[\\/]node_modules[\\/]_?element-ui(.*)/ // in order to adapt to cnpm
          },
          commons: {
            name: "chunk-commons",
            test: resolve("src/components"), // can customize your rules
            minChunks: 3, //  minimum common number
            priority: 5,
            reuseExistingChunk: true
          }
        }
      )
      });
      config.optimization.runtimeChunk("single"),
        {
          from: path.resolve(__dirname, "./public/robots.txt"), //防爬虫文件
          to: "./" //到根目录下
        };
    });
  }
}
};
¹ÜÀí¶Ë (2).zip
Binary files differ
¹ÜÀí¶Ë (3).zip
Binary files differ
¹ÜÀí¶Ë.zip
Binary files differ