WXL
3 天以前 dc082351978a1e9f75d7a1471a0ca7ebeac552a5
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 {