Files
taimed/pages/home/index.vue
liuyuan 060344610a 提交
2025-05-21 16:35:35 +08:00

807 lines
19 KiB
Vue
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.
<template>
<view class="content">
<view class="home_top">
<view class="home_top_icon">
<image src='../../static/icon/icon_bars.png' class="home_top_left" @click="openDrawer"></image>
<image src='../../static/icon/icon_dialog.png' class="home_top_right" @click="showMode"></image>
</view>
<text>智慧医疗</text>
</view>
<view class="home_wrap" v-if="!showMessages">
<view class="home_logo">
<image src='../../static/logo.png'></image>
<text class="logo_main">我是太湖云医的智慧医疗<br/>很高兴见到您</text>
<text class="logo_con">我是您的诊断小助手您可以把情况发给我我会结合太湖学堂知识数据库为您进行分析解答</text>
</view>
<view class="home_form">
<template>
<view class="form_item">
<text>西医诊断</text>
<input type="text" v-model="formData.diagnosis" placeholder="请输入西医诊断" placeholder-class="custom-placeholder" />
</view>
<view class="form_item">
<text>详细病情</text>
<textarea v-model="formData.illness" maxlength="-1"
auto-height
placeholder="请输入详细病情"
placeholder-class="custom-placeholder" />
</view>
<view class="form_item">
<text>主要症状</text>
<textarea auto-height v-model="formData.symptoms" maxlength="-1"
placeholder="请输入主要症状" placeholder-class="custom-placeholder" />
</view>
<view class="form_item form_item_genetic">
<text>基因检测阳性</text>
<input type="text" v-model="formData.genetic" placeholder="选填" placeholder-class="custom-placeholder" />
</view>
<view class="form_item">
<text>患者姓名</text>
<input type="text" v-model="formData.name" placeholder="如果您是医生,建议填写" placeholder-class="custom-placeholder" />
</view>
</template>
</view>
</view>
<view class="submit_form" v-if="!showMessages">
<view class="assistants_list">
<view v-for="(item,index) in chatAssistants" :key="index"
@click="clickAssistants(item,index)"
class="assistants_item"
:class="activeIndex==index?'active':''"
>
<image class="assistants_img_1" :src="item.icon_1" mode="widthFix" ></image>
<image class="assistants_img_2" :src="item.icon_2" mode="widthFix" ></image>
<text>{{item.name}}</text>
</view>
</view>
<button class="submit_btn" @click="submit">
<image src='../../static/icon/icon_submit.png'></image>
</button>
</view>
<view class="message_wrap" :style="{ top: 82 + 'px' }" v-if="showMessages">
<text class="message_title" v-if="tishi">好的结合您的情况下面是分析结果</text>
<!-- 显示聊天记录 -->
<view class="message-container-block" ref="messageContainerBlock">
<view class="message-container" v-for="(item, index) in messages" :key="index"
:class="{'message-left': item.type === 'answer', 'message-right': item.type === 'question'}">
<text v-html="item.content"></text>
</view>
<view class="loading-spinner" v-if="loading"></view>
</view>
<view class="submit_form submit_form_2">
<view class="assistants_list">
<textarea auto-height v-model="question_send" placeholder="给智慧医疗发送消息" placeholder-class="custom-placeholder" />
</view>
<button class="submit_btn" @click="sendAgain">
<image src='../../static/icon/icon_submit.png' v-if="!pauseStatus"></image>
<image src='../../static/icon/icon_submit_pause.png' v-if="pauseStatus"></image>
</button>
</view>
</view>
<!-- 左侧弹窗 -->
<uni-drawer ref="drawer" mode="left" :width="230">
<view class="drawer-content">
<scroll-view scroll-y="true" class="list_content" v-if="record_list.length>0">
<view v-for="(item, index) in record_list" :key="index" class="list_item"
:class="activeRecord == index?'active_item':''"
@click="clickRecord(item, index)">
<text class="text_item">{{ item.chatName }}</text>
</view>
</scroll-view>
<view class="null_text" v-else>{{ null_text }}</view>
</view>
</uni-drawer>
<z-navigation></z-navigation>
</view>
</template>
<script>
import $http from "@/config/requestConfig.js";
import { mapState, mapMutations } from "vuex";
export default {
data() {
return {
containerHeight: null,
formData: {
diagnosis: '',
illness: '',
symptoms: '',
genetic: '',
name: '',
},
chatId: null, //选择助手的id
chatName: '',
chatAssistants: [],
activeIndex: null,
sessionId: null, //对话id
eventSource: null,
showMessages: false,
loading: false,
question: '', //传递的问题
messages: [],
previousAnswer: null, //存储上一条回答内容
question_send: '',
pauseStatus: false, //暂停操作
record_list: [], //对话列表
null_text: '',
activeRecord: null,
tishi: false, //提示语
}
},
computed: {
...mapState(["userInfo"]),
},
mounted() {
},
onLoad() {
uni.hideTabBar();
uni.removeStorageSync('homeParams');
//获取设备信息
const systemInfo = uni.getSystemInfoSync();
this.containerHeight = systemInfo.windowHeight; //获取设备的窗口高度
this.getChatAssistants();
},
onShow(){
this.showMessages = false;
this.activeRecord = null;
this.getRecordsData();
//我的-会话记录跳转来的
let data = uni.getStorageSync('homeParams').data;
let index = uni.getStorageSync('homeParams').index;
if(data){
this.clickRecord(data, index);
}
},
methods: {
//获取病症种类数据
getChatAssistants() {
uni.showLoading({
title: '加载中'
})
this.$http.request({
url: 'common/ragFlowApi/getChatAssistants',
method: "POST",
data: {},
header: {
"Content-Type": "application/json",
},
})
.then(res=> {
if (res.list&&res.list.length>0) {
uni.hideLoading();
res.list = res.list.filter(item => {
return !item.name.includes('心理助手') && !item.name.includes('太湖云翳');
});
res.list.forEach(item => {
if (item.name === "呼吸专科") {
item.icon_1 = '../../static/icon/iocn_zs_2.png';
item.icon_2 = '../../static/icon/iocn_zs_2_a.png';
} else if (item.name === "妇科专科") {
item.icon_1 = '../../static/icon/iocn_zs_5.png';
item.icon_2 = '../../static/icon/iocn_zs_5_a.png';
} else if (item.name === "风湿免疫专科") {
item.icon_1 = '../../static/icon/iocn_zs_3.png';
item.icon_2 = '../../static/icon/iocn_zs_3_a.png';
} else if (item.name === "消化专科") {
item.icon_1 = '../../static/icon/iocn_zs_1.png';
item.icon_2 = '../../static/icon/iocn_zs_1_a.png';
} else if (item.name === "肿瘤专科") {
item.icon_1 = '../../static/icon/iocn_zs_4.png';
item.icon_2 = '../../static/icon/iocn_zs_4_a.png';
} else if (item.name === "全科医生") {
item.icon_1 = '../../static/icon/iocn_zs_6.png';
item.icon_2 = '../../static/icon/iocn_zs_6_a.png';
}
});
this.chatAssistants = res.list;
}
})
.catch(e=>{
uni.setStorageSync("guidePages", 2);
});
},
//获取对话记录数据
getRecordsData() {
this.$http.request({
url: 'common/ragFlowApi/getChats',
method: "POST",
data: {
chatId: '',
sessionId: '',
page: 1,
pageSize: 300
},
header: {
"Content-Type": "application/json",
},
})
.then(res=> {
if(res.code==0){
if(res.list&&res.list.length>0){
this.record_list = res.list;
}else{
this.null_text = '暂无数据';
}
}
})
.catch(e=>{
uni.setStorageSync("guidePages", 2);
uni.redirectTo({
url: "/pages/user/login",
});
});
},
//点击每个记录
clickRecord(item,index){
//重新定义id
this.chatId = item.chatAssistantId;
this.sessionId = item.chatId;
this.activeRecord = index;
this.messages = [];
this.showMessages = true;
uni.showLoading({
title: '加载中'
})
this.$http.request({
url: 'common/ragFlowApi/getChats',
method: "POST",
data: {
chatId: item.chatAssistantId,
sessionId: item.chatId,
page: 1,
pageSize: 300
},
header: {
"Content-Type": "application/json",
},
})
.then(res=> {
if(res.code==0){
uni.hideLoading();
if(res.list&&res.list.length>0){
this.messages = res.list.map(item => {
let content = item.content.replace(/##\d+$$/g, '');
content = content.replace(/<\/?think>/g, '');
content = content.replace(/\*\*(.*?)\*\*/g, '<b class="bold-text">$1</b>');
content = content.replace(/\*(.*?)\*/g, '<span class="red-text">$1</span>');
content = content.replace(/\n\n/g, '\n');
content = content.replace(/\n/g, '<br>');
content = content.replace(/^#{3,4}.*(\r?\n)?/gm, '');
return {
...item,
type: item.type === 1 ? 'answer' : 'question',
content: content
};
});
}
this.tishi = false; //提示语不用展示
this.$refs.drawer.close();
}
});
},
//
//选中科目类别
clickAssistants(item, index){
this.activeIndex = index;
this.chatId = item.id;
this.chatName = item.name;
},
//提交
submit(){
if(!this.formData.diagnosis){
this.$commonJS.showToast("请输入西医诊断");
return
}
if(!this.formData.illness){
this.$commonJS.showToast("请输入详细病情");
return
}
if(!this.formData.symptoms){
this.$commonJS.showToast("请输入主要症状");
return
}
if(!this.chatId){
this.$commonJS.showToast("请选择助手分类");
return
}
let question = '';
if(this.formData.name){
question += '患者'+this.formData.name+'';
}
if(this.formData.diagnosis){
question += '诊断:'+this.formData.diagnosis+'';
}
question += '病情为:'+this.formData.illness+''+this.formData.symptoms+',请根据这位患者的情况出一个中医治疗方案';
this.question = question;
//创建对话 获取sessionId
this.createChat();
},
//创建新对话
createChat(){
let data = {
chatId: this.chatId,
name: this.question.slice(0, 30)
}
this.$http.request({
url: 'common/ragFlowApi/createChat',
method: "POST",
data: data,
header: {
"Content-Type": "application/json",
},
})
.then(res=> {
if (res.code==0) {
this.sessionId = res.id;
//获取回答
this.sendQuestion();
}
});
},
//交谈请求,获取回答
sendQuestion(){
//清空消息记录
this.messages = [];
this.showMessages = true;
this.pauseStatus = true;
this.loading = true;
//展示提示语
this.tishi = true;
const params = {
chatId: this.chatId,
chatName: this.chatName,
question: this.question,
sessionId: this.sessionId,
sessionName: this.question.slice(0, 30),
patientName: this.formData.name
};
//调用后端 SSE 接口,发送问题并接收实时回答
this.startSSE(params);
},
//开始监听 SSE 数据
startSSE(params){
// 拼接查询字符串
const query = new URLSearchParams({
...params,
token: uni.getStorageSync('token')
}).toString();
// 创建 SSE 连接
this.eventSource = new EventSource(this.$baseUrl+`common/ragFlowApi/chatToAssistantStream?${query}`);
// 监听服务器发送的消息
this.eventSource.onmessage = (event) => {
try {
const message = JSON.parse(event.data);
if (message.data === true) {
console.log("回答已结束");
this.pauseStatus = false;
this.eventSource.close();
this.loading = false;
return;
}
const answer = message.data.answer.replace(/##\d+$$/g, '');
let newAnswer = ''; //初始化 newAnswer 变量
if(this.previousAnswer === null){
console.log("第一次进来")
//如果没有包含上一条的内容,则直接显示当前 answer
newAnswer = answer;
this.messages.push({ content: newAnswer, type: 'answer' });
this.previousAnswer = newAnswer;
}else{
//只显示新增的部分
newAnswer = answer.replace(this.previousAnswer, '');
newAnswer = newAnswer.replace(/\*\*(.*?)\*\*/g, '<b class="bold-text">$1</b>');
newAnswer = newAnswer.replace(/\*(.*?)\*/g, '<span class="red-text">$1</span>');
newAnswer = newAnswer.replace(/\n\n/g, '\n');
newAnswer = newAnswer.replace(/\n/g, '<br>');
newAnswer = newAnswer.replace(/####([\s\S]*?)$/gm, '<h4>$1</h4>')
.replace(/###([\s\S]*?)$/gm, '<h3>$1</h3>');
this.messages.push({ content: newAnswer, type: 'answer' });
this.previousAnswer = answer;
}
} catch (error) {
this.$commonJS.showToast("处理消息时发生错误");
}
};
//监听 SSE 连接关闭
this.eventSource.onclose = () => {
console.log("SSE 连接已关闭");
this.loading = false;
};
//监听 SSE 错误
this.eventSource.onerror = (error) => {
console.error("SSE 连接发生错误", error);
this.loading = false;
};
//获取最近的会话记录数据
this.getRecordsData();
},
//回答界面的提交
sendAgain(){
console.log('这是再一次提问')
if(!this.question_send){
this.$commonJS.showToast("请输入发送内容");
return
}
const params = {
chatId: this.chatId,
chatName: this.chatName,
question: this.question_send,
sessionId: this.sessionId,
sessionName: this.question_send.slice(0, 15),
patientName: this.formData.name
};
this.messages.push({
content: `${this.question_send}`,
type: 'question',
});
this.loading = true;
this.question_send = '';
this.pauseStatus = true;
this.previousAnswer = null;
//调用后端 SSE 接口,发送问题并接收实时回答
this.startSSE(params);
},
//点击左侧弹窗
openDrawer() {
this.$refs.drawer.open();
},
//点击新会话
showMode(){
this.showMessages = false;
this.messages = [];
this.formData = {
diagnosis: '',
illness: '',
symptoms: '',
genetic: '',
name: ''
}
this.chatId = null;
this.activeIndex = null;
this.activeRecord = null;
if(this.eventSource){
this.eventSource.close();
}
this.previousAnswer = null;
this.pauseStatus = false;
}
},
beforeDestroy() {
}
}
</script>
<style lang="scss" scoped>
@import '@/static/mixin.scss';
.content{
background: linear-gradient(to bottom, #d8e6ff 20%, #ffffff 100%);
}
.home_top{
width: 100%;
background: #fff;
position: fixed;
top: 0;
left: 0;
padding: 90rpx 50rpx 30rpx;
text-align: center;
box-sizing: border-box;
z-index: 999;
.home_top_icon{
position: absolute;
left: 50rpx;
top: 90rpx;
display: flex;
align-items: center;
.home_top_left,.home_top_right{
width: 40rpx;
height: 40rpx;
}
.home_top_right{
margin-left: 40rpx;
}
}
text{
font-size: 43rpx;
font-weight: bold;
color: $themeColor;
}
}
.home_wrap{
margin: 170rpx 50rpx 0;
.home_logo{
padding-top: 40rpx;
image{
display: block;
width: 130rpx;
height: 130rpx;
margin: 0 auto;
}
.logo_main{
text-align: center;
display: block;
padding-top: 30rpx;
font-size: 35rpx;
color: $themeColor;
line-height: 46rpx;
}
.logo_con{
display: block;
width: 490rpx;
margin: 30rpx auto;
font-size: 28rpx;
color: #303030;
line-height: 40rpx;
text-indent: 2em;
}
}
}
.home_form{
margin-top: 60rpx;
.form_item{
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 30rpx;
text{
line-height: 65rpx;
font-size: 30rpx;
color: #fff;
background: $themeBgColor;
text-align: center;
border-radius: 20rpx;
padding: 0 25rpx;
}
textarea,input{
display: inline-block;
width:calc(100% - 255rpx);
height: 70rpx;
line-height: 36rpx;
padding: 17rpx 30rpx;
font-size: 28rpx;
color: #303030;
border-radius: 50rpx;
border: 1rpx solid #777778;
box-sizing: border-box;
resize: none;
max-height: 200rpx;
overflow-y: scroll;
}
}
}
.custom-placeholder{
font-size: 25rpx;
color: #8b8c90 !important;
}
.submit_form{
width: 100%;
height: 90px;
background: #fff;
position: fixed;
left: 0;
bottom: 120rpx;
padding: 0 50rpx;
z-index: 999;
}
.submit_form_2{
height: 145rpx;
padding: 30rpx;
.assistants_list{
textarea,input{
width: calc(100% - 65px);
font-size: 26rpx;
line-height: 34rpx;
border-radius: 50rpx;
padding: 22rpx 30rpx;
color: #303030;
background: #f3f4f6;
max-height: 72rpx;
overflow: hidden;
}
}
.submit_btn{
top: 42rpx;
}
}
.assistants_list{
display: flex;
align-items: center;
flex-wrap: wrap;;
.assistants_item{
display: flex;
align-items: center;
width: 217rpx;
margin-top: 30rpx;
image{
width: 38rpx;
height: 38rpx;
}
text{
padding-left: 10rpx;
display: inline-block;
font-size: 28rpx;
color: #606061;
}
.assistants_img_1{
display: block;
}
.assistants_img_2{
display: none;
}
}
}
.submit_btn{
position: absolute;
right: 50rpx;
top: 90rpx;
image{
width: 55rpx;
height: 55rpx;
}
}
.active{
text{
color: $themeBgColor;
}
.assistants_img_1{
display: none !important;
}
.assistants_img_2{
display: block !important;
}
}
.message_wrap{
position: relative;
width: 100%;
background: #fff;
z-index: 99;
padding: 0 50rpx;
overflow: scroll;
}
.message_title{
text-align: center;
font-size: 34rpx;
color: #333;
line-height: 50rpx;
display: block;
font-weight: bold;
padding: 10rpx 0;
}
.message-container-block{
padding-top: 10rpx;
padding-bottom: 150px;
font-size: 30rpx;
}
.message-container {
display: inline;
}
.message-item {
}
/* 自定义的loading效果 */
.loading-spinner {
margin-top: 10rpx;
border: 2px solid #f3f3f3; /* 灰色背景 */
border-top: 2px solid #3498db; /* 蓝色顶部 */
border-radius: 50%;
width: 16px;
height: 16px;
animation: spin 1s linear infinite; /* 旋转动画 */
}
/* 旋转动画 */
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.bold-text {
display: block;
font-weight: bold;
font-size: 28rpx; /* 增大字号 */
}
h3{
font-size: 36rpx;
font-weight: bold;
}
h3{
font-size: 32rpx;
}
.red-text {
color: red;
font-size: 28rpx; /* 增大字号 */
}
.message-right{
display: block;
text-align: right;
margin: 30rpx 0;
text{
display: inline-block;
padding: 20rpx;
line-height: 34rpx;
background-color: rgba(81, 136, 229, 0.2);
border-radius: 15rpx;
color: #333;
font-size: 28rpx;
text-align: left;
}
}
.drawer-content {
height: 100vh;
display: flex;
flex-direction: column;
}
.list_content{
padding: 30rpx 20rpx;
overflow-y: auto;
}
.list_item{
padding: 20rpx 10rpx;
}
.text_item{
display: block;
width: 100%;
font-size: 30rpx;
line-height: 40rpx;
padding: 0 10rpx;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.null_text{
display: block;
text-align: center;
font-size: 30rpx;
color: #999;
padding-top: 150rpx;
}
.active_item{
background: #d8e6ff;
border-radius: 15rpx;
text{
color: #5188e5;
}
}
</style>