| | |
| | | <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-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 class="timeline-scroll-container"> |
| | | <div class="timeline-container"> |
| | | <div |
| | | v-for="stage in processStages" |
| | | :key="stage.key" |
| | | class="timeline-item" |
| | | :class="{ |
| | | 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'" |
| | | class="el-icon-check" |
| | | ></i> |
| | | <i |
| | | v-else-if="stage.status == 'progress'" |
| | | class="el-icon-loading" |
| | | ></i> |
| | | <i v-else class="el-icon-time"></i> |
| | | </div> |
| | | |
| | | <div class="stage-info"> |
| | | <div v-if="stage.completeTime" class="time-info"> |
| | | <span>完成时间: {{ formatTime(stage.completeTime) }}</span> |
| | | <div class="timeline-content"> |
| | | <div class="stage-header"> |
| | | <span class="stage-name">{{ stage.name }}</span> |
| | | <dict-tag |
| | | :options="dict.type[stage.dict]" |
| | | :value="stage.state" |
| | | /> |
| | | </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 class="stage-info"> |
| | | <div v-if="stage.createtime" class="time-info"> |
| | | 创建时间: {{ formatTime(stage.createtime) }} |
| | | </div> |
| | | <div v-if="stage.updateTime" class="time-info"> |
| | | 最近更新: {{ formatTime(stage.updateTime) }} |
| | | </div> |
| | | <div v-if="stage.operator" class="operator-info"> |
| | | 负责人: {{ stage.operator }} |
| | | </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> |
| | | <case-basic-info :case-id="caseId" :show-attachment="true" /> |
| | | |
| | | <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 class="stage-detail-section"> |
| | | <div class="section-header"> |
| | | <h3>{{ activeStageName }} - 阶段详情</h3> |
| | | <div class="stage-actions"> |
| | | <el-button |
| | | v-if="activeStageData.status !== 'completed'" |
| | | type="success" |
| | | size="small" |
| | | @click="handleCompleteStage" |
| | | > |
| | | 完成阶段 |
| | | </el-button> |
| | | <el-button |
| | | type="primary" |
| | | size="small" |
| | | @click="handleViewDetail" |
| | | > |
| | | 查看详情 |
| | | </el-button> |
| | | <el-button |
| | | v-if="activeStageData.status === 'completed'" |
| | | type="warning" |
| | | size="small" |
| | | @click="handleModifyStage" |
| | | > |
| | | 修改信息 |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 动态阶段内容 --> |
| | | <div class="stage-content"> |
| | | <div class="stage-content-wrapper"> |
| | | <component |
| | | :is="getStageComponent()" |
| | | :stageData="activeStageData" |
| | | :caseInfo="caseInfo" |
| | | :infoid="caseId" |
| | | /> |
| | | </div> |
| | | </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'; |
| | | // ===================== 字典 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 CaseBasicInfo from "@/components/CaseBasicInfo"; |
| | | |
| | | import DonorMaintenanceStage from "./components/DonorMaintenanceStage"; |
| | | 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 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', |
| | | name: "DonationProcessDetail", |
| | | components: { |
| | | CaseBasicInfo, |
| | | DonorMaintenanceStage, |
| | | MedicalAssessmentStage, |
| | | DeathJudgmentStage, |
| | | MedicalAssessmentStage, |
| | | DonationConfirmStage, |
| | | EthicalReviewStage, |
| | | OrganAllocationStage, |
| | | OrganProcurementStage, |
| | | 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, |
| | | 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: 'medical_assessment', |
| | | name: '医学评估', |
| | | status: 'completed', |
| | | completeTime: '2025-12-02 14:30:00', |
| | | updateTime: '2025-12-02 14:30:00', |
| | | operator: '李主任' |
| | | }, |
| | | { |
| | | key: 'death_judgment', |
| | | 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 |
| | | caseInfo: {}, |
| | | processStages: [], |
| | | activeStage: "", |
| | | activeStageName: "", |
| | | activeStageData: {} |
| | | }; |
| | | }, |
| | | computed: { |
| | | |
| | | }, |
| | | created() { |
| | | this.caseId = this.$route.query.id; |
| | | if (this.caseId) { |
| | | this.getDetail(); |
| | | } else { |
| | | this.generateMockData(); |
| | | } |
| | | this.setActiveStage(this.activeStage); |
| | | }, |
| | | methods: { |
| | | getStageComponent() { |
| | | const componentMap = { |
| | | 'donor_maintenance': 'DonorMaintenanceStage', |
| | | 'medical_assessment': 'MedicalAssessmentStage', |
| | | 'death_judgment': 'DeathJudgmentStage', |
| | | '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; |
| | | } |
| | | }, |
| | | // 处理阶段点击 |
| | | 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'); |
| | | }, |
| | | const res = await getDonatebaseinfoflow(this.caseId); |
| | | |
| | | // 获取当前阶段名称 |
| | | 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 |
| | | ); |
| | | const data = res; |
| | | |
| | | if (currentIndex !== -1) { |
| | | this.processStages[currentIndex].status = 'completed'; |
| | | this.processStages[currentIndex].completeTime = new Date().toISOString(); |
| | | this.caseInfo = data.donatebaseinfo || {}; |
| | | |
| | | // 激活下一个阶段 |
| | | 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.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]]); |
| | | |
| | | this.$message.success('阶段已完成'); |
| | | } |
| | | }); |
| | | }, |
| | | // 查看详情 |
| | | handleViewDetail() { |
| | | const routeMap = { |
| | | 'donor_maintenance': '/case/donorMaintenance/detail', |
| | | 'medical_assessment': '/case/medicalAssessment/detail', |
| | | 'death_judgment': '/case/deathJudgment/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 dictLabel = |
| | | this.dict?.type?.[STAGE_DICT_MAP[stage.apiKey]]?.find( |
| | | d => d.value == obj.state |
| | | )?.label || ""; |
| | | |
| | | const route = routeMap[this.activeStage]; |
| | | if (route) { |
| | | this.$router.push({ |
| | | path: route, |
| | | query: { id: this.caseId } |
| | | 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("获取流程详情失败"); |
| | | } |
| | | }, |
| | | // 修改阶段信息 |
| | | handleModifyStage() { |
| | | this.$message.info(`修改${this.activeStageName}信息功能`); |
| | | |
| | | 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 map = { |
| | | 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 map[this.activeStage]; |
| | | }, |
| | | |
| | | handleStageClick(stage) { |
| | | if (stage.status == "pending") { |
| | | this.$message.warning("该阶段尚未开始"); |
| | | return; |
| | | } |
| | | this.setActiveStage(stage.key); |
| | | }, |
| | | |
| | | formatTime(time) { |
| | | return time ? dayjs(time).format("YYYY-MM-DD HH:mm") : "-"; |
| | | } |
| | | } |
| | | }; |
| | |
| | | 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: 800px; |
| | | min-height: 600px; |
| | | /* 设置一个最小高度 */ |
| | | gap: 20px; |
| | | align-items: flex-start; |
| | | /* 顶部对齐 */ |
| | | } |
| | | |
| | | /* 左侧时间线样式 */ |
| | | /* 左侧时间线样式 - 固定高度,内部滚动 */ |
| | | .timeline-section { |
| | | flex: 0 0 300px; |
| | | 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 */ |
| | | } |
| | | |
| | | .section-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 20px; |
| | | padding-bottom: 15px; |
| | | border-bottom: 1px solid #e4e7ed; |
| | | .timeline-scroll-container { |
| | | flex: 1; |
| | | overflow-y: auto; |
| | | /* 内部可滚动 */ |
| | | margin-top: 20px; |
| | | padding-right: 8px; |
| | | /* 为滚动条留出空间 */ |
| | | } |
| | | |
| | | .section-header h3 { |
| | | margin: 0; |
| | | color: #303133; |
| | | font-size: 16px; |
| | | .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 { |
| | |
| | | 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.danger { |
| | | border-color: #f56c6c; |
| | | background-color: #fef0f0; |
| | | } |
| | | |
| | | .timeline-item.danger .timeline-marker { |
| | | background-color: #f56c6c; |
| | | } |
| | | .timeline-item.active { |
| | | border-color: #409EFF; |
| | | border-color: #409eff; |
| | | background-color: #f0f9ff; |
| | | } |
| | | |
| | | .timeline-item.completed { |
| | | border-color: #67C23A; |
| | | border-color: #67c23a; |
| | | background-color: #f0f9e8; |
| | | } |
| | | |
| | | .timeline-item.in-progress { |
| | | border-color: #E6A23C; |
| | | .timeline-item.progress { |
| | | border-color: #e6a23c; |
| | | background-color: #fdf6ec; |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | .timeline-item.completed .timeline-marker { |
| | | background-color: #67C23A; |
| | | background-color: #67c23a; |
| | | } |
| | | .timeline-item.active .timeline-marker { |
| | | background-color: #409eff; |
| | | } |
| | | |
| | | .timeline-item.in-progress .timeline-marker { |
| | | background-color: #E6A23C; |
| | | .timeline-item.progress .timeline-marker { |
| | | background-color: #e6a23c; |
| | | } |
| | | |
| | | .timeline-item.pending .timeline-marker { |
| | |
| | | font-weight: 600; |
| | | color: #303133; |
| | | font-size: 14px; |
| | | white-space: nowrap; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | max-width: 150px; |
| | | } |
| | | |
| | | .stage-info { |
| | |
| | | color: #606266; |
| | | } |
| | | |
| | | .time-info, .operator-info { |
| | | .time-info, |
| | | .operator-info { |
| | | margin-bottom: 4px; |
| | | } |
| | | |
| | | /* 右侧内容区域样式 */ |
| | | .content-section { |
| | | flex: 1; |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 20px; |
| | | } |
| | | |
| | | .basic-info-section, |
| | | .stage-detail-section { |
| | | background: white; |
| | | border-radius: 6px; |
| | | padding: 20px; |
| | | box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1); |
| | | white-space: nowrap; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | } |
| | | |
| | | .stage-actions { |
| | | display: flex; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .stage-content { |
| | | margin-top: 20px; |
| | | flex-wrap: wrap; |
| | | } |
| | | |
| | | /* 响应式设计 */ |
| | |
| | | |
| | | .timeline-section { |
| | | flex: none; |
| | | margin-bottom: 20px; |
| | | width: 100%; |
| | | height: auto; |
| | | max-height: 300px; |
| | | position: static; |
| | | /* 小屏幕取消 sticky */ |
| | | } |
| | | |
| | | .timeline-scroll-container { |
| | | max-height: 250px; |
| | | } |
| | | |
| | | .stage-detail-section { |
| | | max-height: 500px; |
| | | } |
| | | } |
| | | |
| | |
| | | gap: 10px; |
| | | } |
| | | |
| | | .stage-actions { |
| | | flex-wrap: wrap; |
| | | .stage-name { |
| | | max-width: 120px; |
| | | } |
| | | } |
| | | |
| | | /* 动画效果 */ |
| | | .timeline-item { |
| | | transition: all 0.3s ease; |
| | | } |
| | | .basic-info-content { |
| | | max-height: 250px; |
| | | } |
| | | |
| | | .timeline-item:hover { |
| | | transform: translateY(-2px); |
| | | } |
| | | |
| | | /* 进度条样式优化 */ |
| | | :deep(.el-progress-bar) { |
| | | padding-right: 0; |
| | | } |
| | | |
| | | :deep(.el-progress__text) { |
| | | font-size: 12px; |
| | | .stage-detail-section { |
| | | min-height: 300px; |
| | | max-height: 400px; |
| | | } |
| | | } |
| | | </style> |