From 7f737dfd8ab10428fe79fb4d67bee9d52361f55d Mon Sep 17 00:00:00 2001 From: wangdongbo <949818794@qq.com> Date: Thu, 29 Jan 2026 18:44:34 +0800 Subject: [PATCH] IM --- .../components/ai-assistant-buttons.vue | 151 +++++++++----- .../components/medical-case-progress.vue | 195 +++++++++++++++++- 2 files changed, 289 insertions(+), 57 deletions(-) diff --git a/pages/message/components/ai-assistant-buttons.vue b/pages/message/components/ai-assistant-buttons.vue index d097a4c..00cf432 100644 --- a/pages/message/components/ai-assistant-buttons.vue +++ b/pages/message/components/ai-assistant-buttons.vue @@ -20,7 +20,11 @@ /> - + @@ -211,12 +215,10 @@ const handleCaseTypeSelect = async (type) => { try { // 打开进度弹窗 progressRef.value?.open(type.id); + progressRef.value?.updateProgress(10); - // 模拟进度更新 - progressRef.value?.updateProgress(20); - - // 调用补充病历接口 - const result = await request({ + // 调用补充病历接口(流式处理) + await requestWithStream({ url: "/getYoucanData/im", data: { type: "supplementMedicalCase", @@ -225,50 +227,22 @@ const handleCaseTypeSelect = async (type) => { corpId: props.corpId, caseType: type.id, }, - }); - - progressRef.value?.updateProgress(60); - - if (result.success && result.data) { - const { detectedInfo, formData } = result.data; - - // 显示检测到的信息 - if (detectedInfo && detectedInfo.length > 0) { - detectedInfo.forEach((info) => { - progressRef.value?.addDetectedInfo(info); + onProgress: (data) => { + // 处理流式数据 + handleStreamData(data, type.id); + }, + onComplete: (finalData) => { + // 完成后跳转 + handleComplete(finalData, type.id); + }, + onError: (error) => { + progressRef.value?.close(); + uni.showToast({ + title: error.message || "生成病历失败", + icon: "none", }); } - - progressRef.value?.updateProgress(80); - progressRef.value?.setGenerating(true); - - // 模拟生成结构化病历 - setTimeout(() => { - progressRef.value?.updateProgress(100); - - // 延迟后关闭进度弹窗并跳转 - setTimeout(() => { - progressRef.value?.close(); - - // 跳转到病历填写页面 - uni.navigateTo({ - url: `/pages/case/medical-case-form?caseType=${ - type.id - }&customerId=${ - props.customerId || props.patientAccountId - }&groupId=${props.groupId}&formData=${encodeURIComponent( - JSON.stringify(formData || {}) - )}`, - }); - }, 500); - }, 1000); - } else { - progressRef.value?.close(); - uni.showToast({ - title: result.message || "生成病历失败", - icon: "none", - }); - } + }); } catch (error) { console.error("补充病历失败:", error); progressRef.value?.close(); @@ -279,6 +253,87 @@ const handleCaseTypeSelect = async (type) => { } }; +// 流式请求处理 +const requestWithStream = async ({ url, data, onProgress, onComplete, onError }) => { + try { + // 调用接口时不显示全局 loading(第二个参数为 false) + const result = await request({ + url, + data, + }, false); + + if (result.success && result.data) { + // 模拟流式处理(如果后端返回的是完整数据) + const extractedData = result.data.extractedData || {}; + + // 逐个字段动态显示(包括空值字段) + let progressValue = 20; + const fields = Object.entries(extractedData); + const delay = 300; // 每个字段显示间隔 + + for (let i = 0; i < fields.length; i++) { + const [key, value] = fields[i]; + + // 显示所有字段,包括空值(会在组件中显示为"暂无") + await new Promise(resolve => setTimeout(resolve, delay)); + onProgress({ key, value }); + progressValue += Math.floor(60 / fields.length); + progressRef.value?.updateProgress(Math.min(progressValue, 80)); + } + + // 完成 + onComplete(result.data); + } else { + onError(new Error(result.message || "请求失败")); + } + } catch (error) { + onError(error); + } +}; + +// 处理流式数据 +const handleStreamData = (data, caseType) => { + const { key, value } = data; + + // 添加检测到的信息 + progressRef.value?.addDetectedInfo(key, value); +}; + +// 处理完成 +const handleComplete = (finalData, caseType) => { + progressRef.value?.updateProgress(90); + progressRef.value?.setGenerating(true); + + // 延迟后完成 + setTimeout(() => { + progressRef.value?.updateProgress(100); + progressRef.value?.setGenerating(false); + + // 延迟后显示操作按钮(不自动跳转) + setTimeout(() => { + progressRef.value?.setCompleted(true, finalData); + }, 500); + }, 800); +}; + +// 处理从进度弹窗点击重新生成 +const handleRegenerateFromProgress = (data) => { + const type = { id: data.caseType }; + handleCaseTypeSelect(type); +}; + +// 处理从进度弹窗点击下一步 +const handleNextFromProgress = (data) => { + // 跳转到病历填写页面 + uni.navigateTo({ + url: `/pages/case/medical-case-form?caseType=${data.caseType}&customerId=${ + props.customerId || props.patientAccountId + }&groupId=${props.groupId}&formData=${encodeURIComponent( + JSON.stringify(data.data?.extractedData || {}) + )}`, + }); +}; + // 监听重新生成事件 onMounted(() => { uni.$on("regenerateMedicalCase", handleRegenerateMedicalCase); diff --git a/pages/message/components/medical-case-progress.vue b/pages/message/components/medical-case-progress.vue index 5bd762e..c1fb044 100644 --- a/pages/message/components/medical-case-progress.vue +++ b/pages/message/components/medical-case-progress.vue @@ -15,18 +15,38 @@ {{ progress }}% - + 检测到以下{{ caseTypeName }}信息: - + - {{ item }} + + {{ item.label }}:{{ item.value }} + - 正在生成结构化{{ caseTypeName }}... + 正在生成结构化{{ caseTypeName }} + + + + + + 重新生成 + + + 下一步 + @@ -36,11 +56,15 @@ @@ -98,6 +181,8 @@ defineExpose({ border-radius: 24rpx; padding: 48rpx 40rpx; position: relative; + max-height: 80vh; + overflow-y: auto; .close-btn { position: absolute; @@ -108,6 +193,7 @@ defineExpose({ display: flex; align-items: center; justify-content: center; + z-index: 10; .close-icon { font-size: 40rpx; @@ -140,7 +226,7 @@ defineExpose({ .progress-fill { height: 100%; background: linear-gradient(90deg, #1890ff 0%, #40a9ff 100%); - transition: width 0.3s ease; + transition: width 0.5s ease; } } @@ -164,23 +250,39 @@ defineExpose({ } .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.5; + line-height: 1.6; + word-break: break-all; + + // 暂无数据的样式 + &.empty-value { + color: #999999; + } } } } @@ -191,7 +293,82 @@ defineExpose({ 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: '...'; + } +}