Files
taimed/pages/home/index.vue
liuyuan 56ee2fee9e 提交
2025-05-23 17:23:44 +08:00

866 lines
20 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: 80 + '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";
import qs from 'qs'
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"]),
},
onLoad() {
uni.hideTabBar();
uni.removeStorageSync('homeParams');
//获取设备信息
const systemInfo = uni.getSystemInfoSync();
this.containerHeight = systemInfo.windowHeight; //获取设备的窗口高度
this.getChatAssistants();
//重置
this.$nextTick(() => {
this.showMode();
});
},
onShow(){
console.log('进入到onShow方法')
this.activeRecord = null;
this.getRecordsData();
//我的-会话记录跳转来的
let data = uni.getStorageSync('homeParams').data;
let index = uni.getStorageSync('homeParams').index;
if(data){
this.clickRecord(data, index);
}else{
uni.removeStorageSync('homeParams');
this.$nextTick(() => {
this.showMessages = false;
});
}
},
methods: {
//获取病症种类数据
getChatAssistants() {
uni.showLoading({
title: '加载中'
})
this.$http.request({
url: 'common/ragFlowApi/getChatAssistants',
method: "POST",
data: {},
header: {
"Content-Type": "application/json",
},
})
.then(res=> {
uni.hideLoading();
if (res.list&&res.list.length>0) {
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.hideLoading();
uni.setStorageSync("guidePages", 2);
});
},
//获取对话记录数据
getRecordsData() {
this.record_list = [];
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){
console.log('请求最新列表')
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;
//如果正在回答的时候切换需要中断回答
this.closeWebSocket();
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;
//获取回答
console.log('sessionId', this.sessionId)
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 queryString = qs.stringify(params);
var data = {};
this.eventSource = uni.connectSocket({
url: this.$baseUrl + `websocket`,
success: () => {
console.log('WebSocket连接中...');
$http.request({
url: `common/ragFlowApi/chatToAssistantStream?${queryString}`,
method: "GET",
data,
header: {
"Content-Type": "application/json",
},
})
.then(res=> {
console.log('请求成功')
})
.catch(e=>{
console.log('失败')
});
},
fail: (err) => {
console.error('连接失败', err);
uni.showToast({ title: '连接失败', icon: 'error' });
}
});
// 监听服务器发送的消息
uni.onSocketMessage((event) => {
try {
const message = JSON.parse(event.data);
if (message.data === true) {
console.log("回答已结束1111");
this.pauseStatus = false;
this.loading = false;
//获取最近的会话记录数据
this.getRecordsData();
setTimeout(() => {
this.closeWebSocket();
}, 200);
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.loading = false;
this.closeWebSocket();
}
});
//监听WebSocket连接打开
uni.onSocketOpen(() => {
console.log('WebSocket已连接');
});
//监听WebSocket错误
uni.onSocketError((err) => {
console.error('WebSocket连接错误', err);
});
//监听WebSocket关闭
uni.onSocketClose((res) => {
console.log('WebSocket 已关闭', res);
});
},
//回答界面的提交
sendAgain(){
console.log('这是再一次提问')
if(!this.pauseStatus){
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);
}else{
console.log('不能点击了')
}
},
//点击左侧弹窗
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;
//中断
this.closeWebSocket();
this.previousAnswer = null;
this.pauseStatus = false;
//把缓存清除
uni.removeStorageSync('homeParams');
},
//关闭进程和监听
closeWebSocket() {
if (this.eventSource) {
// 关闭连接并移除监听
this.eventSource.close({
success: () => {
console.log('WebSocket 已关闭-closeWebSocket');
uni.offSocketMessage(); //移除消息监听
}
});
this.eventSource = null;
this.loading = false;
this.pauseStatus = false;
}
}
},
onHide() {
this.closeWebSocket();
},
onUnload() {
this.closeWebSocket();
}
}
</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;
line-height: 48rpx;
}
.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: 38rpx;
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: 60rpx 20rpx 30rpx;
overflow-y: auto;
}
.list_item{
padding: 15rpx 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>