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
+61 -36
View File
@@ -1,13 +1,8 @@
<template>
<HomePage
v-if="showHomePage"
@open-settings="returnToSettings"
@open-profile="openProfile"
/>
<view v-else class="config-page">
<view class="config-page">
<view class="phone-frame">
<view class="hero-section">
<image class="hospital-image" src="/static/config-hospital.png" mode="aspectFill"></image>
<image class="hospital-image" :src="hospitalBannerUrl" mode="aspectFill" @error="handleBannerError"></image>
<view class="hero-overlay"></view>
</view>
@@ -99,8 +94,8 @@
<script setup lang="ts">
import { computed, onMounted, onUnmounted, reactive, ref } from 'vue'
import HomePage from '../home/home.vue'
import { createProfileOpener } from '../../api/navigation'
import { fetchInstitutionInfo, fetchMyDepartments, type DepartmentRecord } from '../../api/auth'
import {
MOCK_CONFIG_OPTIONS,
fetchConfigOptions,
@@ -114,12 +109,7 @@ type PickerType = '' | 'department' | 'title' | 'experience'
type OptionGroup = keyof ConfigOptions
type SaveState = 'idle' | 'saved'
type LoginUser = {
id?: string | number
phone?: string
institutionId?: string
institution?: string | null
}
const DEFAULT_HOSPITAL_BANNER = '/static/config-hospital.png'
const form = reactive({
department: 'im',
@@ -137,7 +127,7 @@ const saving = ref(false)
const saveState = ref<SaveState>('idle')
const toastMessage = ref('')
const toastVisible = ref(false)
const showHomePage = ref(false)
const hospitalBannerUrl = ref('')
const emit = defineEmits<{
(event: 'open-profile'): void
@@ -189,6 +179,39 @@ function loadConfigOptions() {
})
}
function normalizeDepartment(item: DepartmentRecord): ConfigOption {
return {
value: String(item.id),
label: item.name,
desc: item.category
}
}
async function loadDepartments() {
try {
const departments = await fetchMyDepartments()
if (departments.length === 0) return
const departmentOptions = departments.map(normalizeDepartment)
options.value = {
...options.value,
departments: departmentOptions
}
form.department = departmentOptions[0].value
} catch {
// Keep mock departments when the user-specific list cannot be loaded.
}
}
async function loadInstitutionInfo() {
try {
const institution = await fetchInstitutionInfo()
hospitalBannerUrl.value = institution.banner_url || DEFAULT_HOSPITAL_BANNER
} catch {
hospitalBannerUrl.value = DEFAULT_HOSPITAL_BANNER
}
}
function findOption(group: OptionGroup, value: string) {
const groupOptions = options.value[group] || []
return groupOptions.find(item => item.value === value) || groupOptions[0] || { label: '请选择', value: '' }
@@ -231,17 +254,16 @@ function chooseOption(option: ConfigOption) {
}
function handleSubmit() {
const user = (uni.getStorageSync('clinical-thinking-user') || {}) as LoginUser
const departmentId = Number(form.department)
if (!Number.isInteger(departmentId) || departmentId <= 0) {
showToast('请先选择有效执业科室')
return
}
const payload: ClinicalConfigPayload = {
userId: user.id ? String(user.id) : 'mock-user-guest',
phone: user.phone || '',
institutionId: user.institutionId || user.institution || '',
department: form.department,
title: form.title,
experience: form.experience,
departmentName: selectedDepartment.value.label,
titleName: selectedTitle.value.label,
experienceName: selectedExperience.value.label
department: departmentId,
title_name: selectedTitle.value.label,
practice_years: selectedExperience.value.label
}
saving.value = true
@@ -253,33 +275,36 @@ function handleSubmit() {
showToast('配置已保存')
setTimeout(() => {
showHomePage.value = true
uni.reLaunch({
url: '/pages/home/home'
})
}, 500)
}).catch(() => {
}).catch(error => {
saving.value = false
showToast('保存失败,请稍后重试')
showToast(error instanceof Error ? error.message : '保存失败,请稍后重试')
})
}
function returnToSettings() {
showHomePage.value = false
saveState.value = 'idle'
function handleBannerError() {
if (hospitalBannerUrl.value !== DEFAULT_HOSPITAL_BANNER) {
hospitalBannerUrl.value = DEFAULT_HOSPITAL_BANNER
}
}
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(loadConfigOptions)
onMounted(() => {
loadConfigOptions()
void loadDepartments()
void loadInstitutionInfo()
})
onUnmounted(() => {
if (typingTimer) clearTimeout(typingTimer)