feat: 联调对话功能

This commit is contained in:
王天骄
2026-06-09 17:00:23 +08:00
parent 3414d0662c
commit 2192b855a1
77 changed files with 1082 additions and 487 deletions
+54 -35
View File
@@ -1,18 +1,11 @@
<template>
<DiagnosisPage
v-if="showDiagnosisPage"
:case-item="caseItem"
@open-settings="emit('open-settings')"
@open-profile="openProfile"
@go-home="emit('go-home')"
/>
<view v-else class="chat-page">
<view class="chat-page">
<view class="chat-shell">
<view class="top-nav">
<button class="icon-button" aria-label="设置" @click="emit('open-settings')">
<button class="icon-button" aria-label="设置" @click="openSettings">
<view class="settings-icon"></view>
</button>
<button class="icon-button home-button" aria-label="首页" @click="emit('go-home')">
<button class="icon-button home-button" aria-label="首页" @click="goHome">
<view class="home-icon"></view>
</button>
<view class="nav-spacer"></view>
@@ -24,9 +17,9 @@
<view class="case-header">
<view class="case-title-row">
<text class="case-heading">患者{{ session.patient.name }} ({{ complaintShort }})</text>
<button class="finish-button" @click="showDiagnosisPage = true">
<button class="finish-button" :disabled="completingInquiry" @click="handleCompleteInquiry">
<view class="check-icon"></view>
<text>完成采集</text>
<text>{{ completingInquiry ? '提交中' : '完成采集' }}</text>
</button>
</view>
<view class="patient-meta">
@@ -193,14 +186,19 @@
<script setup lang="ts">
import { computed, onMounted, onUnmounted, reactive, ref } from 'vue'
import type { ClinicalCase } from '../../api/cases'
import { readStoredClinicalCase, type ClinicalCase } from '../../api/cases'
import { createMockChatSession, sendMockChatMessage, type ChatMessage, type ChatSession } from '../../api/chat'
import { createProfileOpener } from '../../api/navigation'
import { streamSessionChat, streamSessionHint, type TrainingSession } from '../../api/session'
import DiagnosisPage from '../diagnosis/diagnosis.vue'
import { createHomeNavigator, createProfileOpener, createSettingsOpener } from '../../api/navigation'
import {
completeInquiry,
readStoredTrainingScenario,
streamSessionChat,
streamSessionHint,
updateStoredSessionStatus
} from '../../api/session'
const props = defineProps<{
caseItem: ClinicalCase | null
caseItem?: ClinicalCase | null
}>()
const emit = defineEmits<{
@@ -210,6 +208,8 @@ const emit = defineEmits<{
}>()
const openProfile = createProfileOpener(emit)
const openSettings = createSettingsOpener(emit)
const goHome = createHomeNavigator(emit)
const session = reactive<ChatSession>({
patient: {
@@ -230,22 +230,19 @@ const session = reactive<ChatSession>({
const draft = ref('')
const sending = ref(false)
const hinting = ref(false)
const completingInquiry = ref(false)
const scrollTop = ref(0)
const toastMessage = ref('')
const toastVisible = ref(false)
const showDiagnosisPage = ref(false)
const examPanelVisible = ref(false)
const physicalPanelVisible = ref(false)
const activeSessionId = ref<number | null>(null)
const storedCase = ref<ClinicalCase | null>(null)
let toastTimer: ReturnType<typeof setTimeout> | null = null
let activeStreamController: AbortController | null = null
let activeHintController: AbortController | null = null
type StoredScenario = {
session?: TrainingSession
}
type AuxiliaryExam = {
name: string
result: string
@@ -281,16 +278,18 @@ const physicalForm = reactive({
otherFinding: ''
})
const activeCase = computed(() => props.caseItem || storedCase.value)
const complaintShort = computed(() => {
if (session.patient.chiefComplaint.includes('胸痛')) return '胸痛'
return session.patient.chiefComplaint.slice(0, 6)
})
function loadSession() {
createMockChatSession(props.caseItem).then(result => {
createMockChatSession(activeCase.value).then(result => {
Object.assign(session.patient, result.patient)
session.stages = result.stages
const scenario = readStoredScenario()
const scenario = readStoredTrainingScenario()
if (scenario?.session?.session_id) {
activeSessionId.value = scenario.session.session_id
session.messages = scenario.session.patient_opening ? [
@@ -311,17 +310,38 @@ function loadSession() {
})
}
function readStoredScenario() {
const value = uni.getStorageSync('clinical-thinking-scenario')
if (value && typeof value === 'object') return value as StoredScenario
return null
}
function sendQuickAction(content: string) {
draft.value = content
handleSend()
}
async function handleCompleteInquiry() {
if (completingInquiry.value) return
if (sending.value || hinting.value) {
showToast('请等待当前回复完成')
return
}
const sessionId = activeSessionId.value
if (!sessionId) {
showToast('未找到当前会话,请先生成模拟场景')
return
}
completingInquiry.value = true
try {
const result = await completeInquiry(sessionId)
updateStoredSessionStatus(result.status)
uni.navigateTo({
url: '/pages/diagnosis/diagnosis'
})
} catch (error) {
showToast(error instanceof Error ? error.message : '完成采集失败')
} finally {
completingInquiry.value = false
}
}
function openExamPanel() {
physicalPanelVisible.value = false
examPanelVisible.value = true
@@ -550,16 +570,15 @@ function showToast(message: string) {
if (toastTimer) clearTimeout(toastTimer)
toastMessage.value = message
toastVisible.value = true
uni.showToast({
title: message,
icon: 'none'
})
toastTimer = setTimeout(() => {
toastVisible.value = false
}, 2200)
}
onMounted(loadSession)
onMounted(() => {
storedCase.value = readStoredClinicalCase()
loadSession()
})
onUnmounted(() => {
activeStreamController?.abort()