Files
medical-mall/pages/mall/merchant/ai-consultation.uvue

782 lines
18 KiB
Plaintext
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.
<!-- 机构端 - AI问诊页面 -->
<template>
<view class="ai-page">
<!-- #ifdef MP-WEIXIN -->
<view class="detail-navbar">
<view class="detail-navbar-back" @click="uni.navigateBack()">
<text class="back-arrow"></text>
<text class="back-text">返回</text>
</view>
<text class="detail-navbar-title">AI问诊</text>
<view style="width: 120rpx;"></view>
</view>
<!-- #endif -->
<!-- 高风险提示横幅(始终显示) -->
<view class="risk-banner">
<text class="risk-banner-icon">⚠️</text>
<text class="risk-banner-text">AI问诊仅供参考不能代替专业医生诊断紧急情况请立即拨打120</text>
</view>
<!-- 症状快捷选择 -->
<view v-if="!hasConversation" class="quick-select-area">
<text class="qs-title">请选择主要症状(可多选)</text>
<view class="qs-tags">
<view
v-for="sym in symptomOptions"
:key="sym"
class="qs-tag"
:class="selectedSymptoms.includes(sym) ? 'qs-tag-selected' : ''"
@click="toggleSymptom(sym)"
>
{{ sym }}
</view>
</view>
<view v-if="selectedSymptoms.length > 0" class="qs-confirm-row">
<view class="qs-confirm-btn" @click="startWithSymptoms">开始问诊</view>
</view>
</view>
<!-- 对话区域 -->
<scroll-view
class="chat-scroll"
direction="vertical"
:scroll-into-view="lastMsgId"
:scroll-with-animation="true"
>
<!-- 欢迎提示 -->
<view class="welcome-msg" v-if="messages.length === 0 && !hasConversation">
<view class="welcome-avatar">🤖</view>
<view class="welcome-bubble">
<text class="welcome-title">您好我是医养AI助手</text>
<text class="welcome-desc">我可以帮助您初步了解用户症状、进行健康风险评估,以及提供护理建议。请选择上方症状或直接输入描述。</text>
<text class="welcome-disclaimer">⚠ 本工具仅供辅助参考,最终诊疗请遵循医嘱。</text>
</view>
</view>
<!-- 消息列表 -->
<view
v-for="(msg, idx) in messages"
:key="idx"
:id="'msg-' + idx"
class="msg-row"
:class="msg.role === 'user' ? 'msg-row-right' : 'msg-row-left'"
>
<!-- AI头像 -->
<view v-if="msg.role === 'ai'" class="msg-avatar ai-avatar">🤖</view>
<view class="msg-bubble-wrap" :class="msg.role === 'user' ? 'mbw-right' : 'mbw-left'">
<!-- 高风险提醒卡片 -->
<view v-if="msg.riskLevel === 'high'" class="risk-card">
<view class="risk-card-header">
<text class="risk-card-icon">🚨</text>
<text class="risk-card-title">高风险提示</text>
</view>
<text class="risk-card-body">{{ msg.content }}</text>
<view class="risk-card-actions">
<view class="risk-action-btn rca-call" @click="callEmergency">一键拨打120</view>
<view class="risk-action-btn rca-notify" @click="notifyFamily">通知家属</view>
</view>
</view>
<!-- 普通气泡 -->
<view v-else class="msg-bubble" :class="msg.role === 'user' ? 'bubble-user' : 'bubble-ai'">
<text class="msg-text">{{ msg.content }}</text>
</view>
<!-- 建议操作列表AI回复带操作 -->
<view v-if="msg.suggestions && msg.suggestions.length > 0" class="suggestion-list">
<view
v-for="sug in msg.suggestions"
:key="sug"
class="suggestion-item"
@click="sendMessage(sug)"
>
{{ sug }}
</view>
</view>
</view>
<!-- 用户头像 -->
<view v-if="msg.role === 'user'" class="msg-avatar user-avatar">👩‍⚕️</view>
</view>
<!-- AI加载中 -->
<view v-if="isLoading" class="msg-row msg-row-left">
<view class="msg-avatar ai-avatar">🤖</view>
<view class="loading-bubble">
<view class="loading-dot"></view>
<view class="loading-dot"></view>
<view class="loading-dot"></view>
</view>
</view>
<view id="msg-bottom" style="height: 20rpx;"></view>
</scroll-view>
<!-- 快捷问题(有对话后显示) -->
<view v-if="hasConversation && quickReplies.length > 0" class="quick-replies">
<scroll-view direction="horizontal" class="qr-scroll">
<view
v-for="qr in quickReplies"
:key="qr"
class="qr-chip"
@click="sendMessage(qr)"
>
{{ qr }}
</view>
</scroll-view>
</view>
<!-- 输入区 -->
<view class="input-bar">
<view class="input-bar-inner">
<input
class="chat-input"
v-model="inputText"
placeholder="描述症状或询问护理建议..."
:confirm-type="'send'"
@confirm="onSend"
:maxlength="200"
/>
<view class="send-btn" :class="inputText.trim() ? 'send-btn-active' : ''" @click="onSend">
发送
</view>
</view>
<view class="input-actions">
<view class="ia-btn" @click="clearConversation">清空对话</view>
<view class="ia-btn" @click="exportRecord">导出记录</view>
<view class="ia-btn ia-btn-primary" @click="referToDoctor">转诊医生</view>
</view>
</view>
</view>
</template>
<script lang="uts">
type MessageType = {
role: string
content: string
riskLevel: string
suggestions: string[]
}
// 模拟AI回复知识库实际接入大模型API
const AI_RESPONSES : UTSJSONObject = {
'头晕': {
content: '头晕可能与血压波动、低血糖、颈椎问题等有关。\n\n建议\n1. 立即测量血压\n2. 询问是否有进食\n3. 让老人平躺休息\n\n如出现突发剧烈头晕伴言语不清请立即就医',
riskLevel: 'medium',
suggestions: ['血压偏高怎么办', '低血糖症状有哪些', '需要叫救护车吗']
},
'胸痛': {
content: '胸痛是高度危险的症状!\n\n可能原因心绞痛、心肌梗死、气胸等。\n\n⚠ 建议立即评估:\n· 是否伴随出汗、恶心\n· 是否放射至左臂或下颌\n· 疼痛是否持续超过5分钟\n\n如有以上情况属于急危症请立即拨打120',
riskLevel: 'high',
suggestions: ['心肌梗死急救步骤', '如何拨打120']
},
'发烧': {
content: '发热(体温>37.3°C在老年人中需要特别关注因为老年人感染反应可能不典型。\n\n建议\n1. 测量实际体温\n2. 补充水分\n3. 体温>38.5°C 给予退热处理\n4. 持续高热或伴随寒战,建议就医检查血常规',
riskLevel: 'medium',
suggestions: ['退烧药怎么用', '老年人发烧危险信号']
},
'default': {
content: '感谢您的描述。作为AI助手我可以提供初步护理建议但无法替代医生的专业诊断。\n\n请详细描述\n· 症状持续时间\n· 症状严重程度1-10分\n· 是否有用药变化\n\n如情况紧急请立即联系医生或拨打120。',
riskLevel: 'low',
suggestions: ['当前用药有哪些', '如何联系值班医生', '需要上门服务吗']
}
}
export default {
data() {
return {
inputText: '' as string,
messages: [] as MessageType[],
isLoading: false as boolean,
hasConversation: false as boolean,
lastMsgId: '' as string,
selectedSymptoms: [] as string[],
symptomOptions: ['头晕', '胸痛', '发烧', '呼吸困难', '腹痛', '跌倒', '意识模糊', '血压异常', '血糖异常', '情绪异常'] as string[],
quickReplies: ['症状加重了', '需要上门服务', '联系家属', '转诊医生', '今日用药'] as string[]
}
},
methods: {
toggleSymptom(sym: string) {
const idx = this.selectedSymptoms.indexOf(sym)
if (idx >= 0) {
this.selectedSymptoms.splice(idx, 1)
} else {
this.selectedSymptoms.push(sym)
}
},
startWithSymptoms() {
const text = this.selectedSymptoms.join('、')
this.selectedSymptoms = []
this.hasConversation = true
this.sendMessage(`用户主诉:${text}`)
},
onSend() {
const text = this.inputText.trim()
if (!text) return
this.inputText = ''
this.hasConversation = true
this.sendMessage(text)
},
sendMessage(text: string) {
this.messages.push({
role: 'user',
content: text,
riskLevel: 'none',
suggestions: []
} as MessageType)
this.lastMsgId = 'msg-' + (this.messages.length - 1)
this.isLoading = true
// 模拟AI响应延迟实际需接入大模型API
setTimeout(() => {
this.generateAIResponse(text)
}, 1200)
},
generateAIResponse(userText: string) {
let responseData : UTSJSONObject | null = null
// 关键词匹配
if (userText.includes('头晕')) {
responseData = AI_RESPONSES['头晕'] as UTSJSONObject
} else if (userText.includes('胸痛') || userText.includes('心脏')) {
responseData = AI_RESPONSES['胸痛'] as UTSJSONObject
} else if (userText.includes('发烧') || userText.includes('发热')) {
responseData = AI_RESPONSES['发烧'] as UTSJSONObject
} else {
responseData = AI_RESPONSES['default'] as UTSJSONObject
}
const content = String(responseData['content'] ?? '') || ''
const riskLevel = String(responseData['riskLevel'] ?? 'low') || 'low'
const sugsRaw = responseData.getArray('suggestions')
const sugs : string[] = []
if (sugsRaw != null) {
for (let i = 0; i < sugsRaw.length; i++) {
sugs.push(String(sugsRaw[i]) || '')
}
}
this.isLoading = false
this.messages.push({
role: 'ai',
content: content,
riskLevel: riskLevel,
suggestions: sugs
} as MessageType)
this.lastMsgId = 'msg-bottom'
},
clearConversation() {
uni.showModal({
title: '清空对话',
content: '确定要清空当前对话记录吗?',
success: (res) => {
if (res.confirm) {
this.messages = []
this.hasConversation = false
this.lastMsgId = ''
}
}
})
},
exportRecord() {
uni.showToast({ title: '导出功能开发中', icon: 'none' })
},
referToDoctor() {
uni.showToast({ title: '转诊功能开发中', icon: 'none' })
},
callEmergency() {
uni.makePhoneCall({ phoneNumber: '120' })
},
notifyFamily() {
uni.showToast({ title: '通知家属功能开发中', icon: 'none' })
}
}
}
</script>
<style>
.ai-page {
background-color: #f0f2f7;
min-height: 100vh;
display: flex;
flex-direction: column;
}
/* ===== 导航栏 ===== */
.detail-navbar {
display: flex;
flex-direction: row;
align-items: flex-end;
background-color: #ffffff;
border-bottom-width: 1rpx;
border-bottom-style: solid;
border-bottom-color: #eeeeee;
box-sizing: border-box;
padding-top: var(--status-bar-height);
height: calc(88rpx + var(--status-bar-height));
}
.detail-navbar-back {
display: flex;
flex-direction: row;
align-items: center;
padding: 0 30rpx;
height: 88rpx;
width: 120rpx;
}
.back-arrow {
font-size: 44rpx;
color: #333333;
line-height: 1;
margin-right: 4rpx;
}
.back-text {
font-size: 28rpx;
color: #333333;
}
.detail-navbar-title {
flex: 1;
text-align: center;
font-size: 32rpx;
font-weight: 600;
color: #1a1a1a;
height: 88rpx;
line-height: 88rpx;
}
/* ===== 高风险横幅 ===== */
.risk-banner {
background-color: #E64A19;
display: flex;
flex-direction: row;
align-items: center;
padding: 16rpx 24rpx;
}
.risk-banner-icon {
font-size: 28rpx;
margin-right: 12rpx;
flex-shrink: 0;
}
.risk-banner-text {
font-size: 22rpx;
color: #ffffff;
line-height: 1.5;
flex: 1;
}
/* ===== 症状快选 ===== */
.quick-select-area {
background-color: #ffffff;
padding: 24rpx 30rpx 20rpx 30rpx;
border-bottom-width: 8rpx;
border-bottom-style: solid;
border-bottom-color: #f0f2f7;
}
.qs-title {
font-size: 26rpx;
font-weight: 600;
color: #333;
margin-bottom: 16rpx;
display: block;
}
.qs-tags {
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
.qs-tag {
font-size: 24rpx;
color: #555;
background-color: #f5f5f5;
border-radius: 30rpx;
padding: 10rpx 24rpx;
margin-right: 14rpx;
margin-bottom: 14rpx;
border-width: 1rpx;
border-style: solid;
border-color: #e5e5e5;
}
.qs-tag-selected {
background-color: #eef2fe;
border-color: rgb(66,121,240);
color: rgb(66,121,240);
}
.qs-confirm-row {
display: flex;
flex-direction: row;
justify-content: flex-end;
margin-top: 8rpx;
}
.qs-confirm-btn {
background-color: rgb(66,121,240);
color: #ffffff;
font-size: 28rpx;
font-weight: 600;
height: 72rpx;
line-height: 72rpx;
padding: 0 40rpx;
border-radius: 36rpx;
}
/* ===== 聊天滚动区 ===== */
.chat-scroll {
flex: 1;
padding: 20rpx 0;
}
/* ===== 欢迎消息 ===== */
.welcome-msg {
display: flex;
flex-direction: row;
align-items: flex-start;
padding: 20rpx 24rpx;
}
.welcome-avatar {
width: 72rpx;
height: 72rpx;
border-radius: 36rpx;
background-color: #eef2fe;
font-size: 36rpx;
line-height: 72rpx;
text-align: center;
margin-right: 16rpx;
flex-shrink: 0;
}
.welcome-bubble {
flex: 1;
background-color: #ffffff;
border-radius: 4rpx 16rpx 16rpx 16rpx;
padding: 20rpx 24rpx;
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.06);
}
.welcome-title {
font-size: 28rpx;
font-weight: 600;
color: #1a1a1a;
display: block;
margin-bottom: 10rpx;
}
.welcome-desc {
font-size: 24rpx;
color: #555;
line-height: 1.6;
display: block;
margin-bottom: 12rpx;
}
.welcome-disclaimer {
font-size: 20rpx;
color: #E64A19;
background-color: #fff5f0;
padding: 8rpx 14rpx;
border-radius: 4rpx;
display: block;
}
/* ===== 消息行 ===== */
.msg-row {
display: flex;
flex-direction: row;
align-items: flex-start;
padding: 12rpx 24rpx;
}
.msg-row-left {
flex-direction: row;
}
.msg-row-right {
flex-direction: row-reverse;
}
.msg-avatar {
width: 64rpx;
height: 64rpx;
border-radius: 32rpx;
font-size: 32rpx;
line-height: 64rpx;
text-align: center;
flex-shrink: 0;
}
.ai-avatar {
background-color: #eef2fe;
}
.user-avatar {
background-color: #e8f5e9;
}
.msg-bubble-wrap {
max-width: 75%;
display: flex;
flex-direction: column;
}
.mbw-left {
margin-left: 12rpx;
align-items: flex-start;
}
.mbw-right {
margin-right: 12rpx;
align-items: flex-end;
}
.msg-bubble {
border-radius: 16rpx;
padding: 18rpx 22rpx;
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.06);
}
.bubble-ai {
background-color: #ffffff;
border-top-left-radius: 4rpx;
}
.bubble-user {
background-color: rgb(66,121,240);
border-top-right-radius: 4rpx;
}
.msg-text {
font-size: 28rpx;
line-height: 1.6;
}
.bubble-ai .msg-text {
color: #333;
}
.bubble-user .msg-text {
color: #ffffff;
}
/* ===== 高风险卡片 ===== */
.risk-card {
background-color: #ffffff;
border-radius: 16rpx;
border-top-left-radius: 4rpx;
border-top-width: 4rpx;
border-top-style: solid;
border-top-color: #E64A19;
padding: 20rpx 24rpx;
box-shadow: 0 2rpx 12rpx rgba(230,74,25,0.15);
}
.risk-card-header {
display: flex;
flex-direction: row;
align-items: center;
margin-bottom: 12rpx;
}
.risk-card-icon {
font-size: 30rpx;
margin-right: 10rpx;
}
.risk-card-title {
font-size: 28rpx;
font-weight: 700;
color: #E64A19;
}
.risk-card-body {
font-size: 26rpx;
color: #333;
line-height: 1.7;
display: block;
margin-bottom: 16rpx;
}
.risk-card-actions {
display: flex;
flex-direction: row;
}
.risk-action-btn {
height: 64rpx;
line-height: 64rpx;
padding: 0 28rpx;
border-radius: 32rpx;
font-size: 26rpx;
font-weight: 600;
margin-right: 16rpx;
}
.rca-call {
background-color: #E64A19;
color: #ffffff;
}
.rca-notify {
background-color: #fff5f0;
color: #E64A19;
border-width: 1rpx;
border-style: solid;
border-color: #E64A19;
}
/* ===== 建议操作列表 ===== */
.suggestion-list {
display: flex;
flex-direction: row;
flex-wrap: wrap;
margin-top: 10rpx;
}
.suggestion-item {
font-size: 22rpx;
color: rgb(66,121,240);
border-width: 1rpx;
border-style: solid;
border-color: rgb(66,121,240);
border-radius: 20rpx;
padding: 6rpx 18rpx;
margin-right: 10rpx;
margin-bottom: 10rpx;
background-color: #eef2fe;
}
/* ===== 加载中气泡 ===== */
.loading-bubble {
background-color: #ffffff;
border-radius: 4rpx 16rpx 16rpx 16rpx;
padding: 18rpx 28rpx;
margin-left: 12rpx;
display: flex;
flex-direction: row;
align-items: center;
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.06);
}
.loading-dot {
width: 12rpx;
height: 12rpx;
border-radius: 6rpx;
background-color: #bbb;
margin: 0 4rpx;
}
/* ===== 快捷回复 ===== */
.quick-replies {
background-color: #ffffff;
border-top-width: 1rpx;
border-top-style: solid;
border-top-color: #f0f0f0;
}
.qr-scroll {
white-space: nowrap;
}
.qr-chip {
display: inline-flex;
font-size: 24rpx;
color: rgb(66,121,240);
background-color: #eef2fe;
border-radius: 20rpx;
padding: 10rpx 24rpx;
margin: 12rpx 8rpx;
}
/* ===== 输入区 ===== */
.input-bar {
background-color: #ffffff;
border-top-width: 1rpx;
border-top-style: solid;
border-top-color: #eeeeee;
padding-bottom: env(safe-area-inset-bottom);
}
.input-bar-inner {
display: flex;
flex-direction: row;
align-items: center;
padding: 16rpx 20rpx 12rpx 20rpx;
}
.chat-input {
flex: 1;
height: 72rpx;
border-width: 1rpx;
border-style: solid;
border-color: #e5e5e5;
border-radius: 36rpx;
padding: 0 24rpx;
font-size: 28rpx;
background-color: #f9f9f9;
}
.send-btn {
height: 72rpx;
line-height: 72rpx;
padding: 0 28rpx;
border-radius: 36rpx;
font-size: 28rpx;
font-weight: 600;
margin-left: 16rpx;
color: #bbb;
background-color: #f0f0f0;
flex-shrink: 0;
}
.send-btn-active {
background-color: rgb(66,121,240);
color: #ffffff;
}
.input-actions {
display: flex;
flex-direction: row;
justify-content: flex-end;
padding: 0 20rpx 12rpx 20rpx;
}
.ia-btn {
font-size: 22rpx;
color: #666;
border-width: 1rpx;
border-style: solid;
border-color: #ddd;
border-radius: 20rpx;
padding: 8rpx 20rpx;
margin-left: 12rpx;
background-color: #ffffff;
}
.ia-btn-primary {
color: rgb(66,121,240);
border-color: rgb(66,121,240);
background-color: #eef2fe;
}
</style>