<template>
|
<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">
|
<div class="timeline-container">
|
<div
|
v-for="stage in processStages"
|
: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'
|
}"
|
@click="handleStageClick(stage)"
|
>
|
<div class="timeline-marker">
|
<i
|
v-if="stage.status === 'completed'"
|
class="el-icon-check"
|
></i>
|
<i
|
v-else-if="stage.status === 'in_progress'"
|
class="el-icon-loading"
|
></i>
|
<i v-else class="el-icon-time"></i>
|
</div>
|
|
<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>
|
</div>
|
|
<div class="stage-info">
|
<div v-if="stage.completeTime" class="time-info">
|
<span
|
>完成时间: {{ formatTime(stage.completeTime) }}</span
|
>
|
</div>
|
<div v-if="stage.updateTime" class="time-info">
|
<span>最近更新: {{ formatTime(stage.updateTime) }}</span>
|
</div>
|
<div v-if="stage.operator" class="operator-info">
|
<span>负责人: {{ stage.operator }}</span>
|
</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
|
<!-- 右侧内容区域 - 独立滚动 -->
|
<div class="content-section">
|
<!-- 案例基本信息 - 固定高度,可滚动 -->
|
<div class="basic-info-section">
|
<div class="section-header">
|
<h3>案例基本信息</h3>
|
<el-button
|
type="primary"
|
size="small"
|
@click="handleEditBasicInfo"
|
>
|
编辑信息
|
</el-button>
|
</div>
|
|
<div class="basic-info-content">
|
<el-descriptions :column="2" border>
|
<el-descriptions-item label="住院号">
|
{{ caseInfo.caseNo }}
|
</el-descriptions-item>
|
<el-descriptions-item label="住院号">
|
{{ caseInfo.hospitalNo }}
|
</el-descriptions-item>
|
<el-descriptions-item label="捐献者姓名">
|
{{ caseInfo.donorName }}
|
</el-descriptions-item>
|
<el-descriptions-item label="性别">
|
<dict-tag
|
:options="dict.type.sys_user_sex"
|
:value="parseInt(caseInfo.gender)"
|
/>
|
</el-descriptions-item>
|
<el-descriptions-item label="年龄">
|
{{ caseInfo.age }} 岁
|
</el-descriptions-item>
|
<el-descriptions-item label="血型">
|
<dict-tag
|
:options="dict.type.sys_BloodType"
|
:value="caseInfo.bloodType"
|
/>
|
</el-descriptions-item>
|
<el-descriptions-item label="疾病诊断">
|
{{ caseInfo.diagnosis }}
|
</el-descriptions-item>
|
<el-descriptions-item label="案例状态">
|
<el-tag :type="getOverallStatusTag(caseInfo.status)">
|
{{ getStatusText(caseInfo.status) }}
|
</el-tag>
|
</el-descriptions-item>
|
<el-descriptions-item label="创建时间">
|
{{ formatTime(caseInfo.createTime) }}
|
</el-descriptions-item>
|
<el-descriptions-item label="登记人">
|
{{ caseInfo.registrant }}
|
</el-descriptions-item>
|
<el-descriptions-item label="当前阶段">
|
{{ getCurrentStageName() }}
|
</el-descriptions-item>
|
</el-descriptions>
|
</div>
|
</div>
|
|
<!-- 阶段详情内容 - 自适应高度,可滚动 -->
|
<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()"
|
:stageData="activeStageData"
|
:caseInfo="caseInfo"
|
:infoid="caseId"
|
/>
|
</div>
|
</div>
|
</div>
|
</div>
|
</el-card>
|
</div>
|
</template>
|
|
<script>
|
import { getDonationProcessDetail } from "./donationProcess";
|
import DonorMaintenanceStage from "./components/DonorMaintenanceStage";
|
import MedicalAssessmentStage from "./components/MedicalAssessmentStage";
|
import DeathJudgmentStage from "./components/DeathJudgmentStage";
|
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 dayjs from "dayjs";
|
|
export default {
|
name: "DonationProcessDetail",
|
components: {
|
DonorMaintenanceStage,
|
MedicalAssessmentStage,
|
DeathJudgmentStage,
|
DonationConfirmStage,
|
EthicalReviewStage,
|
OrganAllocationStage,
|
OrganProcurementStage,
|
OrganUtilizationStage
|
},
|
dicts: ["sys_user_sex", "sys_BloodType", "sys_0_1"],
|
data() {
|
return {
|
caseId: 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: "pending",
|
operator: "待分配"
|
},
|
{
|
key: "organ_utilization",
|
name: "器官利用",
|
status: "pending",
|
operator: "待分配"
|
}
|
],
|
activeStage: "organ_allocation",
|
activeStageName: "器官分配",
|
activeStageData: {},
|
loading: false
|
};
|
},
|
computed: {},
|
created() {
|
this.caseId = this.$route.query.id;
|
console.log(this.caseId, "this.caseId");
|
|
if (this.caseId) {
|
this.getDetail();
|
} else {
|
this.generateMockData();
|
}
|
this.setActiveStage(this.activeStage);
|
},
|
methods: {
|
getStageComponent() {
|
const componentMap = {
|
donor_maintenance: "DonorMaintenanceStage",
|
death_judgment: "DeathJudgmentStage",
|
medical_assessment: "MedicalAssessmentStage",
|
donation_confirm: "DonationConfirmStage",
|
ethical_review: "EthicalReviewStage",
|
organ_allocation: "OrganAllocationStage",
|
organ_procurement: "OrganProcurementStage",
|
organ_utilization: "OrganUtilizationStage"
|
};
|
return componentMap[this.activeStage];
|
},
|
// 获取详情数据
|
async getDetail() {
|
this.loading = true;
|
try {
|
const response = await getDonationProcessDetail(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("该阶段尚未开始,无法查看详情");
|
}
|
},
|
// 获取阶段状态标签类型
|
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}信息功能`);
|
}
|
}
|
};
|
</script>
|
|
<style scoped>
|
.donation-process-detail {
|
padding: 20px;
|
background-color: #f5f7fa;
|
min-height: 100vh;
|
box-sizing: border-box;
|
}
|
|
.process-card {
|
border-radius: 8px;
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
margin-bottom: 20px;
|
}
|
|
.process-container {
|
display: flex;
|
min-height: 600px; /* 设置一个最小高度 */
|
gap: 20px;
|
align-items: flex-start; /* 顶部对齐 */
|
}
|
|
/* 左侧时间线样式 - 固定高度,内部滚动 */
|
.timeline-section {
|
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 */
|
}
|
|
.timeline-scroll-container {
|
flex: 1;
|
overflow-y: auto; /* 内部可滚动 */
|
margin-top: 20px;
|
padding-right: 8px; /* 为滚动条留出空间 */
|
}
|
|
.timeline-scroll-container::-webkit-scrollbar {
|
width: 6px;
|
}
|
|
.timeline-scroll-container::-webkit-scrollbar-track {
|
background: #f1f1f1;
|
border-radius: 3px;
|
}
|
|
.timeline-scroll-container::-webkit-scrollbar-thumb {
|
background: #c1c1c1;
|
border-radius: 3px;
|
}
|
|
.timeline-scroll-container::-webkit-scrollbar-thumb:hover {
|
background: #a8a8a8;
|
}
|
|
.timeline-container {
|
display: flex;
|
flex-direction: column;
|
gap: 15px;
|
padding-bottom: 10px;
|
}
|
|
/* 右侧内容区域样式 - 自适应高度 */
|
.content-section {
|
flex: 1;
|
display: flex;
|
flex-direction: column;
|
gap: 20px;
|
min-height: 0; /* 重要:允许flex子项压缩 */
|
}
|
|
.basic-info-section {
|
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: 0; /* 重要 */
|
}
|
|
.basic-info-content {
|
flex: 1;
|
max-height: 300px; /* 基本信息区域最大高度 */
|
overflow-y: auto; /* 基本信息内部可滚动 */
|
margin-top: 20px;
|
padding-right: 8px;
|
}
|
|
.basic-info-content::-webkit-scrollbar {
|
width: 6px;
|
}
|
|
.basic-info-content::-webkit-scrollbar-track {
|
background: #f1f1f1;
|
border-radius: 3px;
|
}
|
|
.basic-info-content::-webkit-scrollbar-thumb {
|
background: #c1c1c1;
|
border-radius: 3px;
|
}
|
|
.basic-info-content::-webkit-scrollbar-thumb:hover {
|
background: #a8a8a8;
|
}
|
|
.basic-info-content .el-descriptions {
|
width: 100%;
|
}
|
|
.stage-detail-section {
|
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; /* 隐藏外层溢出 */
|
}
|
|
.stage-content-wrapper {
|
flex: 1;
|
overflow-y: auto; /* 阶段详情内部可滚动 */
|
margin-top: 20px;
|
padding-right: 8px;
|
min-height: 0; /* 重要 */
|
}
|
|
.stage-content-wrapper::-webkit-scrollbar {
|
width: 6px;
|
}
|
|
.stage-content-wrapper::-webkit-scrollbar-track {
|
background: #f1f1f1;
|
border-radius: 3px;
|
}
|
|
.stage-content-wrapper::-webkit-scrollbar-thumb {
|
background: #c1c1c1;
|
border-radius: 3px;
|
}
|
|
.stage-content-wrapper::-webkit-scrollbar-thumb:hover {
|
background: #a8a8a8;
|
}
|
|
/* 原有样式保持不变 */
|
.section-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
flex-shrink: 0; /* 防止被压缩 */
|
}
|
|
.section-header h3 {
|
margin: 0;
|
color: #303133;
|
font-size: 16px;
|
white-space: nowrap;
|
}
|
|
.timeline-item {
|
display: flex;
|
align-items: flex-start;
|
padding: 15px;
|
border-radius: 6px;
|
cursor: pointer;
|
transition: all 0.3s ease;
|
border: 1px solid #e4e7ed;
|
flex-shrink: 0; /* 防止被压缩 */
|
}
|
|
.timeline-item:hover {
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
transform: translateY(-1px);
|
}
|
|
.timeline-item.active {
|
border-color: #409eff;
|
background-color: #f0f9ff;
|
}
|
|
.timeline-item.completed {
|
border-color: #67c23a;
|
background-color: #f0f9e8;
|
}
|
|
.timeline-item.in-progress {
|
border-color: #e6a23c;
|
background-color: #fdf6ec;
|
}
|
|
.timeline-item.pending {
|
border-color: #909399;
|
background-color: #f4f4f5;
|
}
|
|
.timeline-marker {
|
flex: 0 0 40px;
|
height: 40px;
|
border-radius: 50%;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
margin-right: 15px;
|
font-size: 18px;
|
color: white;
|
}
|
|
.timeline-item.completed .timeline-marker {
|
background-color: #67c23a;
|
}
|
|
.timeline-item.in-progress .timeline-marker {
|
background-color: #e6a23c;
|
}
|
|
.timeline-item.pending .timeline-marker {
|
background-color: #909399;
|
}
|
|
.timeline-content {
|
flex: 1;
|
}
|
|
.stage-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
margin-bottom: 8px;
|
}
|
|
.stage-name {
|
font-weight: 600;
|
color: #303133;
|
font-size: 14px;
|
white-space: nowrap;
|
overflow: hidden;
|
text-overflow: ellipsis;
|
max-width: 150px;
|
}
|
|
.stage-info {
|
font-size: 12px;
|
color: #606266;
|
}
|
|
.time-info,
|
.operator-info {
|
margin-bottom: 4px;
|
white-space: nowrap;
|
overflow: hidden;
|
text-overflow: ellipsis;
|
}
|
|
.stage-actions {
|
display: flex;
|
gap: 10px;
|
flex-wrap: wrap;
|
}
|
|
/* 响应式设计 */
|
@media (max-width: 1200px) {
|
.process-container {
|
flex-direction: column;
|
}
|
|
.timeline-section {
|
flex: none;
|
width: 100%;
|
height: auto;
|
max-height: 300px;
|
position: static; /* 小屏幕取消 sticky */
|
}
|
|
.timeline-scroll-container {
|
max-height: 250px;
|
}
|
|
.stage-detail-section {
|
max-height: 500px;
|
}
|
}
|
|
@media (max-width: 768px) {
|
.donation-process-detail {
|
padding: 10px;
|
}
|
|
.process-container {
|
gap: 15px;
|
}
|
|
.timeline-section,
|
.basic-info-section,
|
.stage-detail-section {
|
padding: 15px;
|
}
|
|
.section-header {
|
flex-direction: column;
|
align-items: flex-start;
|
gap: 10px;
|
}
|
|
.stage-name {
|
max-width: 120px;
|
}
|
|
.basic-info-content {
|
max-height: 250px;
|
}
|
|
.stage-detail-section {
|
min-height: 300px;
|
max-height: 400px;
|
}
|
}
|
</style>
|