ykt-wxapp/pages/message/components/medical-case-progress.vue
2026-02-09 15:11:36 +08:00

380 lines
8.1 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<uni-popup ref="popup" type="center" :mask-click="false">
<view class="progress-modal">
<view class="close-btn" @click="close">
<text class="close-icon"></text>
</view>
<view class="progress-content">
<view class="progress-title">{{ progressTitle }}</view>
<view class="progress-bar-wrapper">
<view class="progress-bar">
<view
class="progress-fill"
:style="{ width: progress + '%' }"
></view>
</view>
<text class="progress-text">{{ progress }}%</text>
</view>
<view class="detected-info">
<text class="detected-title">检测到以下{{ caseTypeName }}信息</text>
<view class="info-list">
<view
v-for="(item, index) in detectedInfo"
:key="index"
class="info-item"
:class="{ 'fade-in': item.animated }"
>
<text class="check-icon"></text>
<text
class="info-text"
:class="{ 'empty-value': item.value === '暂无' }"
>
{{ item.label }}{{ item.value }}
</text>
</view>
</view>
</view>
<view v-if="isGenerating" class="generating-text">
<text class="dot-animation">正在生成结构化{{ caseTypeName }}</text>
</view>
<!-- 完成后的操作按钮 -->
<view v-if="isCompleted" class="action-buttons">
<view class="action-button secondary" @click="handleRegenerate">
<text class="button-text">重新生成</text>
</view>
<view class="action-button primary" @click="handleNext">
<text class="button-text">下一步</text>
</view>
</view>
</view>
</view>
</uni-popup>
</template>
<script setup>
import { ref, computed } from "vue";
const emit = defineEmits(["regenerate", "next"]);
const popup = ref(null);
const progress = ref(0);
const detectedInfo = ref([]);
const isGenerating = ref(false);
const isCompleted = ref(false);
const caseType = ref("");
const finalData = ref(null);
const CASE_TYPE_NAMES = {
outpatient: "门诊病历",
inhospital: "住院病历",
physicalExaminationTemplate: "体检记录",
preConsultation: "预问诊记录",
};
const FIELD_LABELS = {
// 门诊病历
visitTime: "就诊日期",
chiefComplaint: "主诉",
medicalHistorySummary: "病史概要",
examination: "检查",
diagnosisName: "门诊诊断",
// 住院病历
inhosDate: "入院日期",
operation: "手术名称",
operationDate: "手术日期",
treatmentPlan: "治疗方案",
// 体检记录
inspectTime: "体检日期",
inspectSummary: "体检小结",
positiveFind: "阳性发现及处理意见",
// 预问诊记录
consultationDate: "问诊日期",
presentIllnessHistory: "现病史",
pastMedicalHistory: "既往史",
};
const caseTypeName = computed(() => CASE_TYPE_NAMES[caseType.value] || "病历");
const progressTitle = computed(() => {
if (progress.value < 100) {
return `正在智能整理${caseTypeName.value}...`;
}
return `${caseTypeName.value}生成完成`;
});
const open = (type) => {
caseType.value = type;
progress.value = 0;
detectedInfo.value = [];
isGenerating.value = false;
isCompleted.value = false;
finalData.value = null;
popup.value?.open();
};
const close = () => {
popup.value?.close();
};
const updateProgress = (value) => {
progress.value = value;
};
const addDetectedInfo = (fieldKey, fieldValue) => {
const label = FIELD_LABELS[fieldKey] || fieldKey;
// 如果字段值为空,显示"暂无"
const displayValue = fieldValue && fieldValue.trim() ? fieldValue : "暂无";
detectedInfo.value.push({
label,
value: displayValue,
animated: true,
});
};
const setGenerating = (value) => {
isGenerating.value = value;
};
const setCompleted = (value, data = null) => {
isCompleted.value = value;
finalData.value = data;
};
const reset = () => {
progress.value = 0;
detectedInfo.value = [];
isGenerating.value = false;
isCompleted.value = false;
finalData.value = null;
};
const handleRegenerate = () => {
emit("regenerate", { caseType: caseType.value });
close();
};
const handleNext = () => {
emit("next", {
caseType: caseType.value,
data: finalData.value,
});
close();
};
defineExpose({
open,
close,
updateProgress,
addDetectedInfo,
setGenerating,
setCompleted,
reset,
});
</script>
<style scoped lang="scss">
.progress-modal {
width: 600rpx;
background-color: #ffffff;
border-radius: 24rpx;
padding: 48rpx 40rpx;
position: relative;
max-height: 80vh;
overflow-y: auto;
.close-btn {
position: absolute;
top: 20rpx;
right: 20rpx;
width: 48rpx;
height: 48rpx;
display: flex;
align-items: center;
justify-content: center;
z-index: 10;
.close-icon {
font-size: 40rpx;
color: #999999;
}
}
.progress-content {
.progress-title {
font-size: 32rpx;
font-weight: 600;
color: #333333;
margin-bottom: 32rpx;
text-align: center;
}
.progress-bar-wrapper {
display: flex;
align-items: center;
gap: 16rpx;
margin-bottom: 32rpx;
.progress-bar {
flex: 1;
height: 16rpx;
background-color: #e5e5e5;
border-radius: 8rpx;
overflow: hidden;
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #1890ff 0%, #40a9ff 100%);
transition: width 0.5s ease;
}
}
.progress-text {
font-size: 28rpx;
color: #1890ff;
font-weight: 600;
min-width: 80rpx;
text-align: right;
}
}
.detected-info {
margin-bottom: 24rpx;
.detected-title {
font-size: 28rpx;
color: #666666;
display: block;
margin-bottom: 16rpx;
}
.info-list {
max-height: 400rpx;
overflow-y: auto;
.info-item {
display: flex;
align-items: flex-start;
gap: 12rpx;
margin-bottom: 12rpx;
opacity: 0;
transform: translateX(-20rpx);
&.fade-in {
animation: fadeInSlide 0.4s ease forwards;
}
.check-icon {
font-size: 28rpx;
color: #52c41a;
font-weight: bold;
margin-top: 2rpx;
}
.info-text {
flex: 1;
font-size: 26rpx;
color: #333333;
line-height: 1.6;
word-break: break-all;
// 暂无数据的样式
&.empty-value {
color: #999999;
}
}
}
}
}
.generating-text {
font-size: 28rpx;
color: #1890ff;
text-align: center;
padding: 16rpx 0;
.dot-animation::after {
content: "...";
animation: dots 1.5s steps(4, end) infinite;
}
}
.action-buttons {
display: flex;
gap: 16rpx;
margin-top: 32rpx;
.action-button {
flex: 1;
height: 80rpx;
border-radius: 40rpx;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
&.primary {
background: linear-gradient(90deg, #1890ff 0%, #40a9ff 100%);
.button-text {
color: #ffffff;
}
&:active {
opacity: 0.8;
transform: scale(0.98);
}
}
&.secondary {
background-color: #ffffff;
border: 2rpx solid #d9d9d9;
.button-text {
color: #666666;
}
&:active {
background-color: #f5f5f5;
transform: scale(0.98);
}
}
.button-text {
font-size: 30rpx;
font-weight: 500;
}
}
}
}
}
@keyframes fadeInSlide {
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes dots {
0%,
20% {
content: "";
}
40% {
content: ".";
}
60% {
content: "..";
}
80%,
100% {
content: "...";
}
}
</style>