<template>
|
<view class="ai-consultation">
|
<!-- 聊天内容区域 -->
|
<scroll-view
|
class="chat-content"
|
scroll-y
|
:scroll-top="scrollTop"
|
:scroll-with-animation="true"
|
@scrolltoupper="loadMoreHistory"
|
>
|
<!-- 默认问候语 -->
|
<view class="welcome-message">
|
<text class="title">AI智能问诊助手</text>
|
<text class="desc">您好,我是您的AI医疗助手,请详细描述您的症状。</text>
|
<view class="quick-questions">
|
<text
|
v-for="(q, index) in quickQuestions"
|
:key="index"
|
@tap="askQuestion(q)"
|
>{{ q }}</text>
|
</view>
|
</view>
|
|
<!-- 聊天记录 -->
|
<view class="message-list">
|
<view
|
v-for="(msg, index) in messages"
|
:key="index"
|
:class="['message-item', msg.type]"
|
>
|
<!-- 用户消息 -->
|
<template v-if="msg.type === 'user'">
|
<view class="message-content">{{ msg.content }}</view>
|
<image :src="userAvatar" class="avatar" mode="aspectFill" />
|
</template>
|
|
<!-- AI消息 -->
|
<template v-else>
|
<image src="/static/avatar/ai.png" class="avatar" mode="aspectFill" />
|
<view class="message-content">
|
{{ msg.content }}
|
<view class="quick-replies" v-if="msg.quickReplies">
|
<text
|
v-for="(reply, idx) in msg.quickReplies"
|
:key="idx"
|
@tap="askQuestion(reply)"
|
>{{ reply }}</text>
|
</view>
|
</view>
|
</template>
|
</view>
|
</view>
|
</scroll-view>
|
|
<!-- 输入区域 -->
|
<view class="input-area">
|
<view class="input-box">
|
<textarea
|
v-model="inputContent"
|
:adjust-position="false"
|
:show-confirm-bar="false"
|
:cursor-spacing="20"
|
:maxlength="-1"
|
placeholder="请输入问题"
|
@confirm="sendMessage"
|
/>
|
<text
|
class="send-btn"
|
:class="{ active: inputContent && inputContent.trim() }"
|
@tap="sendMessage"
|
>发送</text>
|
</view>
|
</view>
|
|
<!-- 病历选择弹窗 -->
|
<uni-popup ref="recordSelector" type="bottom">
|
<view class="record-selector">
|
<view class="selector-header">
|
<text class="title">选择要分析的病历</text>
|
<text class="close" @tap="closeRecordSelector">×</text>
|
</view>
|
<view class="record-list">
|
<view
|
class="record-item"
|
v-for="(record, index) in medicalRecords"
|
:key="index"
|
@tap="selectRecord(record)"
|
>
|
<view class="info">
|
<text class="hospital">{{ record.hospital }}</text>
|
<text class="department">{{ record.department }}</text>
|
<text class="date">就诊时间:{{ record.date }}</text>
|
</view>
|
<text class="diagnosis">{{ record.diagnosis }}</text>
|
</view>
|
</view>
|
</view>
|
</uni-popup>
|
</view>
|
</template>
|
|
<script setup>
|
import { ref, nextTick, onMounted } from 'vue'
|
import { onLoad as onPageLoad } from '@dcloudio/uni-app'
|
|
// 用户头像
|
const userAvatar = ref(uni.getStorageSync('userInfo')?.avatar || '/static/avatar/default.png')
|
const inputContent = ref('')
|
const messages = ref([])
|
const scrollTop = ref(0)
|
const record = ref(null)
|
|
// 加载更多历史记录
|
const loadMoreHistory = () => {
|
uni.showLoading({
|
title: '加载中...'
|
})
|
|
// 模拟加载历史记录
|
setTimeout(() => {
|
const historicalMessages = [
|
{
|
type: 'user',
|
content: '之前的咨询记录1'
|
},
|
{
|
type: 'ai',
|
content: '历史回复1',
|
quickReplies: ['追问1', '追问2']
|
}
|
]
|
|
messages.value = [...historicalMessages, ...messages.value]
|
uni.hideLoading()
|
}, 1000)
|
}
|
|
// 预览图片
|
const previewImage = (url) => {
|
uni.previewImage({
|
urls: [url],
|
current: url
|
})
|
}
|
|
// 快速问题
|
const quickQuestions = ref([
|
'最近感冒了,该怎么办?',
|
'头痛是什么原因?',
|
'如何缓解失眠?'
|
])
|
|
// AI分析模板
|
const analyzeTemplates = {
|
// 病历分析模板
|
record: {
|
content: (data) => `
|
根据您的病历,我的分析如下:
|
|
1. 基本情况
|
医院:${data.hospital}
|
科室:${data.department}
|
诊断:${data.diagnosis}
|
|
2. 病情分析
|
- 这是一个${data.diagnosis}的诊断
|
- 这类疾病通常属于${data.department}常见病症
|
- 需要重点关注的症状包括...
|
|
3. 建议
|
- 建议按医嘱完成治疗疗程
|
- 注意观察以下症状...
|
- 如有以下情况需及时就医...
|
|
4. 预防措施
|
- 保持良好的生活习惯
|
- 避免接触可能的诱发因素
|
- 定期进行相关检查
|
|
需要了解更多详细信息吗?
|
`,
|
quickReplies: [
|
'这个病严重吗?',
|
'需要注意什么?',
|
'多久能康复?',
|
'是否需要复查?'
|
]
|
},
|
|
// 检查报告分析模板
|
report: {
|
content: (data) => `
|
根据您的检查报告,我的分析如下:
|
|
1. 检查概况
|
项目:${data.name}
|
时间:${data.time}
|
结果:${data.status === 'normal' ? '正常' : '异常'}
|
|
2. 详细分析
|
${data.status === 'normal' ? `
|
• 您的检查结果在正常范围内
|
• 各项指标符合健康标准
|
• 建议继续保持良好的生活习惯
|
` : `
|
• 检测到部分指标异常
|
• 建议及时就医进行进一步检查
|
• 需要注意观察相关症状变化
|
`}
|
|
3. 建议
|
- ${data.status === 'normal' ?
|
'定期进行健康检查,保持健康的生活方式。' :
|
'建议您尽快预约相关科室进行就医。'
|
}
|
- 保持良好的作息和饮食习惯
|
- 适量运动,增强体质
|
|
4. 后续跟进
|
${data.status === 'normal' ?
|
'建议您按照常规体检周期进行复查。' :
|
'建议您在就医后根据医生建议安排复查时间。'
|
}
|
|
需要了解更多信息或预约就医吗?`,
|
quickReplies: [
|
'如何预约复查?',
|
'需要挂什么科室?',
|
'这个结果代表什么?',
|
'建议就医吗?'
|
]
|
}
|
}
|
|
// 快速提问
|
const askQuestion = (question) => {
|
inputContent.value = question
|
sendMessage()
|
}
|
|
// 快速回复
|
const quickReply = (reply) => {
|
inputContent.value = reply
|
sendMessage()
|
}
|
|
// 模拟回答模板
|
const aiResponses = {
|
common: [
|
{
|
keywords: ['头痛', '头晕'],
|
content: '根据您描述的症状,可能的原因包括:\n1. 紧张性头痛\n2. 偏头痛\n3. 颈椎问题\n4. 眼疲劳\n\n建议:\n1. 保持良好的作息习惯\n2. 避免长时间用眼\n3. 适当运动放松\n4. 如果症状持续,建议到神经内科就诊',
|
medicines: ['布洛芬缓释胶囊', '头痛宁片'],
|
quickReplies: ['症状多久需要就医?', '如何预防头痛?', '需要做什么检查?']
|
},
|
{
|
keywords: ['感冒', '发烧', '咳嗽'],
|
content: '您的症状疑似上呼吸道感染,建议:\n1. 多休息,保持充足睡眠\n2. 多饮温水\n3. 可以服用一些退烧止咳药物\n4. 如果发烧超过38.5℃或持续3天以上,建议及时就医',
|
medicines: ['布洛芬缓释胶囊', '感冒灵颗粒', '止咳糖浆'],
|
quickReplies: ['需要吃什么药?', '多久能好?', '如何预防传染?']
|
}
|
]
|
}
|
|
// 模拟药品数据库
|
const medicineDatabase = {
|
'布洛芬缓释胶囊': {
|
id: 1,
|
name: '布洛芬缓释胶囊',
|
image: '/static/medicines/buluofen.jpg',
|
price: '39.8',
|
desc: '用于缓解轻至中度疼痛,如头痛、关节痛、发热等',
|
usage: '口服,一次1片,必要时每4-6小时重复一次',
|
spec: '0.3g*12片/盒'
|
},
|
'感冒灵颗粒': {
|
id: 2,
|
name: '感冒灵颗粒',
|
image: '/static/medicines/ganmaoling.jpg',
|
price: '28.5',
|
desc: '用于感冒引起的头痛、发热、鼻塞、流涕、咽痛等',
|
usage: '口服,一次1袋,一日3次',
|
spec: '10g*10袋/盒'
|
},
|
'止咳糖浆': {
|
id: 3,
|
name: '止咳糖浆',
|
image: '/static/medicines/zhike.jpg',
|
price: '35.6',
|
desc: '用于各种原因引起的咳嗽症状',
|
usage: '口服,一次10ml,一日3次',
|
spec: '100ml/瓶'
|
}
|
}
|
|
// 发送消息
|
const sendMessage = async () => {
|
if (!inputContent.value || !inputContent.value.trim()) return
|
|
const userMessage = inputContent.value.trim()
|
|
// 添加用户消息
|
messages.value.push({
|
type: 'user',
|
content: userMessage,
|
avatar: userAvatar.value
|
})
|
|
// 清空输入框
|
inputContent.value = ''
|
|
// 滚动到底部
|
await nextTick()
|
scrollToBottom()
|
|
// 查找匹配的回答
|
let aiResponse = null
|
for (const response of aiResponses.common) {
|
if (response.keywords.some(keyword => userMessage.includes(keyword))) {
|
aiResponse = response
|
break
|
}
|
}
|
|
// 如果没有匹配的回答,使用默认回答
|
if (!aiResponse) {
|
aiResponse = {
|
content: '我理解您的问题。建议您:\n1. 注意观察症状变化\n2. 保持良好的作息习惯\n3. 如果症状持续,建议及时就医',
|
medicines: [],
|
quickReplies: ['需要挂什么科?', '如何预约就医?', '还有其他建议吗?']
|
}
|
}
|
|
// 获取推荐药品详情
|
const recommendedMedicines = aiResponse.medicines.map(name => medicineDatabase[name]).filter(Boolean)
|
|
// 模拟AI回复
|
setTimeout(() => {
|
messages.value.push({
|
type: 'ai',
|
content: aiResponse.content + (recommendedMedicines.length ? '\n\n推荐药品:' +
|
recommendedMedicines.map(med => `\n• ${med.name}\n ${med.desc}\n 规格:${med.spec}\n 用法:${med.usage}`).join('\n')
|
: ''),
|
medicines: recommendedMedicines,
|
quickReplies: aiResponse.quickReplies
|
})
|
scrollToBottom()
|
}, 1000)
|
}
|
|
// 滚动到底部
|
const scrollToBottom = () => {
|
nextTick(() => {
|
const query = uni.createSelectorQuery()
|
query.select('.chat-content').boundingClientRect()
|
query.exec((res) => {
|
if (res[0]) {
|
scrollTop.value = res[0].height
|
}
|
})
|
})
|
}
|
|
// 监听初始消息
|
onMounted(() => {
|
const pages = getCurrentPages()
|
const currentPage = pages[pages.length - 1]
|
const eventChannel = currentPage.$getOpenerEventChannel?.()
|
|
eventChannel?.on('initMessage', (data) => {
|
if (data.message) {
|
// 发送初始消息
|
messages.value.push({
|
type: 'user',
|
content: data.message
|
})
|
|
// 模拟AI回复
|
setTimeout(() => {
|
// 判断是病历还是报告
|
const isRecord = data.message.includes('病历')
|
const template = isRecord ? analyzeTemplates.record : analyzeTemplates.report
|
|
// 解析数据
|
const dataMatch = data.message.match(/医院: (.+)\n科室: (.+)\n诊断: (.+)/) ||
|
data.message.match(/检查项目: (.+)\n检查时间: (.+)\n检查结果: (.+)/)
|
|
if (dataMatch) {
|
const templateData = isRecord ? {
|
hospital: dataMatch[1],
|
department: dataMatch[2],
|
diagnosis: dataMatch[3]
|
} : {
|
name: dataMatch[1],
|
time: dataMatch[2],
|
status: dataMatch[3]
|
}
|
|
// 使用模板生成回复
|
messages.value.push({
|
type: 'ai',
|
content: template.content(templateData),
|
quickReplies: template.quickReplies
|
})
|
} else {
|
messages.value.push({
|
type: 'ai',
|
content: '抱歉,我无法解析这些信息。请提供更多详细信息。',
|
quickReplies: ['重新描述', '人工咨询']
|
})
|
}
|
scrollToBottom()
|
}, 1000)
|
}
|
})
|
|
// 从病历进入时自动分析
|
const recordId = uni.getStorageSync('selectedRecordId')
|
if(recordId) {
|
getRecordDetail(recordId).then(res => {
|
record.value = res
|
analyzeRecord()
|
})
|
}
|
})
|
|
// 分析病历
|
const analyzeRecord = () => {
|
if(!record.value) return
|
|
// 添加AI消息
|
messages.value.push({
|
type: 'ai',
|
content: '我已收到您的病历,正在为您分析...',
|
avatar: '/static/avatar/ai.png'
|
})
|
|
// 模拟分析延迟
|
setTimeout(() => {
|
messages.value.push({
|
type: 'ai',
|
content: `根据您的病历显示:
|
1. 主要症状: ${record.value.symptoms}
|
2. 初步诊断: ${record.value.diagnosis}
|
3. 建议治疗方案: ${record.value.treatment}
|
|
请问您还有什么想要了解的吗?`,
|
avatar: '/static/avatar/ai.png'
|
})
|
}, 1000)
|
}
|
|
// 病历记录
|
const medicalRecords = ref([
|
{
|
id: 1,
|
hospital: '青岛中央医院',
|
department: '呼吸内科',
|
diagnosis: '上呼吸道感染',
|
date: '2024-03-15',
|
details: '发热38.5℃,咽痛,咳嗽...'
|
},
|
{
|
id: 2,
|
hospital: '青岛仁伯爵综合医院',
|
department: '消化内科',
|
diagnosis: '慢性胃炎',
|
date: '2024-02-20',
|
details: '上腹部不适,嗳气...'
|
}
|
])
|
|
// 推荐药品
|
const recommendMedicines = {
|
'上呼吸道感染': [
|
{
|
id: 1,
|
name: '布洛芬缓释胶囊',
|
image: '/static/medicines/buluofen.jpg',
|
price: '39.8',
|
desc: '用于缓解感冒发热、咽喉疼痛'
|
},
|
{
|
id: 2,
|
name: '板蓝根颗粒',
|
image: '/static/medicines/banlangen.jpg',
|
price: '28.5',
|
desc: '清热解毒,凉血利咽'
|
}
|
],
|
'慢性胃炎': [
|
{
|
id: 3,
|
name: '奥美拉唑肠溶胶囊',
|
image: '/static/medicines/amzl.jpg',
|
price: '45.6',
|
desc: '用于胃酸过多、胃痛'
|
}
|
]
|
}
|
|
// 病历选择器相关方法
|
const recordSelector = ref(null)
|
|
// 显示病历选择器
|
const showRecordSelector = () => {
|
recordSelector.value.open()
|
}
|
|
// 关闭病历选择器
|
const closeRecordSelector = () => {
|
recordSelector.value.close()
|
}
|
|
// 从病历进入时的自动分析
|
const analyzeFromRecord = (record) => {
|
// 添加用户请求分析的消息
|
messages.value.push({
|
type: 'user',
|
content: `请分析我的病历:\n医院:${record.hospital}\n科室:${record.department}\n诊断:${record.diagnosis}`,
|
avatar: userAvatar.value
|
})
|
|
// 模拟AI分析回复
|
setTimeout(() => {
|
const analysis = analyzeTemplates.record.content({
|
hospital: record.hospital,
|
department: record.department,
|
diagnosis: record.diagnosis,
|
details: record.details
|
})
|
|
// 获取推荐药品
|
const medicines = recommendMedicines[record.diagnosis] || []
|
|
messages.value.push({
|
type: 'ai',
|
content: analysis,
|
medicines: medicines,
|
quickReplies: [
|
'需要复查吗?',
|
'如何预防复发?',
|
'建议就医吗?',
|
'查看其他病历'
|
]
|
})
|
|
scrollToBottom()
|
}, 1000)
|
}
|
|
// 选择病历进行分析
|
const selectRecord = (record) => {
|
closeRecordSelector()
|
analyzeFromRecord(record)
|
}
|
|
// 监听页面参数
|
onPageLoad((options) => {
|
if (options.type === 'record') {
|
if (medicalRecords.value.length > 1) {
|
// 多条病历,显示选择器
|
showRecordSelector()
|
} else if (medicalRecords.value.length === 1) {
|
// 只有一条病历,直接分析
|
analyzeFromRecord(medicalRecords.value[0])
|
}
|
}
|
})
|
</script>
|
|
<style lang="scss">
|
.ai-consultation {
|
display: flex;
|
flex-direction: column;
|
height: 100vh;
|
background: $bg-color;
|
|
.chat-content {
|
flex: 1;
|
padding: 20rpx;
|
overflow-y: auto;
|
|
.welcome-message {
|
background: #fff;
|
border-radius: $radius-lg;
|
padding: 20rpx;
|
margin-bottom: 20rpx;
|
|
.title {
|
font-size: 30rpx;
|
color: $text-primary;
|
font-weight: bold;
|
margin-bottom: 8rpx;
|
display: block;
|
}
|
|
.desc {
|
font-size: 26rpx;
|
color: $text-regular;
|
margin-bottom: 16rpx;
|
display: block;
|
}
|
|
.quick-questions {
|
display: flex;
|
flex-wrap: wrap;
|
gap: 12rpx;
|
|
text {
|
font-size: 24rpx;
|
color: $primary-color;
|
background: $primary-light;
|
padding: 8rpx 20rpx;
|
border-radius: $radius-xl;
|
|
&:active {
|
opacity: 0.8;
|
}
|
}
|
}
|
}
|
|
.message-list {
|
padding: 20rpx 0;
|
|
.message-item {
|
display: flex;
|
align-items: flex-start;
|
margin-bottom: 30rpx;
|
padding: 0 30rpx;
|
|
.avatar {
|
width: 80rpx;
|
height: 80rpx;
|
border-radius: 50%;
|
flex-shrink: 0;
|
border: 2rpx solid rgba(255,255,255,0.2);
|
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.1);
|
}
|
|
.message-content {
|
max-width: 70%;
|
padding: 20rpx;
|
border-radius: 20rpx;
|
font-size: 28rpx;
|
line-height: 1.5;
|
word-break: break-all;
|
}
|
|
// AI消息样式
|
&.ai {
|
.avatar {
|
margin-right: 20rpx;
|
}
|
|
.message-content {
|
background: #fff;
|
color: $text-primary;
|
border-top-left-radius: 4rpx;
|
}
|
}
|
|
// 用户消息样式
|
&.user {
|
justify-content: flex-end; // 整体靠右对齐
|
|
.message-content {
|
margin-right: 20rpx;
|
background: $primary-light;
|
color: $primary-color;
|
border-top-right-radius: 4rpx;
|
}
|
}
|
|
// 快速回复按钮样式
|
.quick-replies {
|
margin-top: 16rpx;
|
display: flex;
|
flex-wrap: wrap;
|
gap: 12rpx;
|
|
text {
|
font-size: 24rpx;
|
color: $primary-color;
|
background: rgba($primary-color, 0.1);
|
padding: 8rpx 20rpx;
|
border-radius: 30rpx;
|
|
&:active {
|
opacity: 0.8;
|
}
|
}
|
}
|
}
|
}
|
}
|
|
.input-area {
|
background: #fff;
|
padding: 16rpx;
|
border-top: 1rpx solid $border-color;
|
|
.input-box {
|
display: flex;
|
align-items: flex-end;
|
gap: 16rpx;
|
|
textarea {
|
flex: 1;
|
height: 72rpx;
|
background: $bg-color;
|
border-radius: $radius-lg;
|
padding: 16rpx;
|
font-size: 26rpx;
|
line-height: 36rpx;
|
max-height: 144rpx;
|
}
|
|
.send-btn {
|
width: 100rpx;
|
height: 72rpx;
|
line-height: 72rpx;
|
text-align: center;
|
font-size: 26rpx;
|
color: #fff;
|
background: $text-secondary;
|
border-radius: $radius-lg;
|
|
&.active {
|
background: $primary-gradient;
|
}
|
|
&:active {
|
transform: scale(0.98);
|
}
|
}
|
}
|
}
|
|
.record-selector {
|
background: #fff;
|
border-radius: $radius-lg $radius-lg 0 0;
|
|
.selector-header {
|
padding: 30rpx;
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
border-bottom: 1rpx solid $border-color;
|
|
.title {
|
font-size: 32rpx;
|
font-weight: bold;
|
color: $text-primary;
|
}
|
|
.close {
|
font-size: 40rpx;
|
color: $text-secondary;
|
padding: 0 20rpx;
|
}
|
}
|
|
.record-list {
|
max-height: 60vh;
|
overflow-y: auto;
|
padding: 20rpx;
|
|
.record-item {
|
background: $bg-color;
|
padding: 20rpx;
|
border-radius: $radius-lg;
|
margin-bottom: 20rpx;
|
|
.info {
|
margin-bottom: 12rpx;
|
|
.hospital {
|
font-size: 28rpx;
|
color: $text-primary;
|
margin-right: 16rpx;
|
}
|
|
.department {
|
font-size: 26rpx;
|
color: $primary-color;
|
margin-right: 16rpx;
|
}
|
|
.date {
|
font-size: 24rpx;
|
color: $text-secondary;
|
}
|
}
|
|
.diagnosis {
|
font-size: 30rpx;
|
color: $text-primary;
|
font-weight: bold;
|
}
|
|
&:active {
|
transform: scale(0.98);
|
}
|
}
|
}
|
}
|
|
.message-content {
|
.medicine-list {
|
margin-top: 20rpx;
|
display: flex;
|
flex-wrap: wrap;
|
gap: 20rpx;
|
|
.medicine-item {
|
width: calc(50% - 10rpx);
|
background: #fff;
|
border-radius: $radius-lg;
|
overflow: hidden;
|
|
&:active {
|
transform: scale(0.98);
|
}
|
|
image {
|
width: 100%;
|
height: 200rpx;
|
background: #f5f5f5;
|
}
|
|
.info {
|
padding: 16rpx;
|
|
.name {
|
font-size: 28rpx;
|
color: $text-primary;
|
margin-bottom: 8rpx;
|
}
|
|
.price {
|
font-size: 32rpx;
|
color: $danger;
|
font-weight: bold;
|
|
&::before {
|
content: '¥';
|
font-size: 24rpx;
|
}
|
}
|
}
|
}
|
}
|
}
|
}
|
</style>
|