如何让我们的会话界面自动回到底部
一、前言
上一篇给大家讲解了如何将消息发送给机器人,本篇将为大家讲解,在实际开发过程中,不难发现,我们的机器人在流式输出文字的过程中,由于篇幅长度的变化,界面不但要控制高度,同时要做到滚动条的自动滚动,那么本节将以源码加实战的方式给大家讲解
二、源码链接
前端源码地址:https://gitee.com/wanyushu/chat-ai
后端源码链接:https://gitee.com/wanyushu/springboot-demo.git
二、实现思路
1、在聊天记录的底层设置锚点,每次发送消息调用一次锚点
2、发送消息在请求后端成功响应后,清空消息框
3、对于机器人回复的消息每次消息的追加,进行一次链接锚点的调用
三、重难点讲解
1、聊天记录锚点的设置
<div id="msg_end" style="height: 1px;width: 1px"></div>
注意该锚点设置在 v-for 标签的下面:
<div class="chat-main-content" v-for="(item,index) in msgList" :key="index">
<li>
<span>{{item.createTime}}</span>
<span style="width: 100%">{{item.question}}</span>
</li>
<li>
<span style="display: flex;text-align: center">
<img src="../asserts/image/gpt.png" width="36" height="36">
<span style="padding-left: 10px">{{item.createTime}}</span>
</span>
<span>
<markdown-it-vue :content="item.answer" class="markdown-body" :options="options"></markdown-it-vue>
</span>
</li>
</div>
以下是 chat-main组件截图
<template xmlns="http://www.w3.org/1999/html">
<div class="chat-main">
<!-- -->
<div class="chat-main-content" v-for="(item,index) in msgList" :key="index">
<li>
<span>{{item.createTime}}</span>
<span style="width: 100%">{{item.question}}</span>
</li>
<li>
<span style="display: flex;text-align: center">
<img src="../../asserts/image/gpt.png" width="36" height="36">
<span style="padding-left: 10px">{{item.createTime}}</span>
</span>
<span>
<markdown-it-vue :content="item.answer" class="markdown-body" :options="options"></markdown-it-vue>
</span>
</li>
</div>
<div id="msg_end" style="height: 1px;width: 1px"></div>
</div>
</template>
<script>
import MarkdownItVue from 'markdown-it-vue'
import 'markdown-it-vue/dist/markdown-it-vue.css'
import {formatDate} from "@/utils/date";
import {setChatInfo} from "@/utils/storage";
export default {
name: "chat-main",
components:{MarkdownItVue},
data(){
return{
content:"",
msgIndex:0,
msgList:[],
options: {
markdownIt: {
linkify: true,
highlight:true
},
linkAttributes: {
attrs: {
target: '_blank',
rel: 'noopener'
}
}
}
}
},
methods:{
showMsg(msgId){
},
appendQ(question){
let param = {id:2,groupId:"148",question:question,answer:"",createTime:this.formatDate(new Date())}
this.msgList.push(param);
this.msgIndex = this.msgList.length -1
},
appendMsg(data){
if(data.content){
this.msgList[this.msgIndex].answer+=data.content
this.toBottom()
}else{
//表示空需要持久化一次
setChatInfo(this.msgList)
}
},
formatDate(time){
return formatDate(time, 'yyyy-MM-dd hh:mm:ss')
},
toBottom() {
this.$nextTick(() => {
document.querySelector('#msg_end').scrollIntoView({behavior:"smooth"})
})
},
}
}
</script>
<style scoped>
.chat-main{
height: calc(90vh);
text-align: center;
width: 100%;
overflow-y: auto;
}
.markdown-body{
color: #0c0c0c !important;
margin-left: 10px;
border-radius: 5px;
padding-top: 10px;
}
.chat-main-content li{
list-style-type: none;
line-height: 36px;
/*border:solid 1px white;*/
margin: auto;
}
li:nth-child(odd) {
/* 设置奇数项样式 */
margin-right: 1.5vw;
text-align: right;
display: flex;
flex-direction: column;
}
li:nth-child(even) {
/* 设置偶数项样式 */
text-align: left;
margin-left: 1.5vw;
}
</style>
2、在发送完成消息后通过toBottom方法让问题到最底的同时,需要将消息清空,这里用到子组件的的方法调用方式
在组件子组件上第一链接 对象,通过在组件上 ref=“send”
在对应的组件上调用echoQuestion
echoQuestion(){
this.textarea="";
}
一下是index组件源码
<template>
<div class="container">
<div class="aside">
<div class="logo">logo区域</div>
<div>聊天选择区域</div>
</div>
<div class="main-right">
<div class="header">标题区域</div>
<chat-main ref="main"></chat-main>
<chat-send @sendMsg="sendMsg" ref="send" @keydown.enter.native="sendText($event)"></chat-send>
</div>
</div>
</template>
<script>
import EventSource from 'eventsource';
import {setDeviceInfo} from "@/utils/storage";
import ChatMain from "@/components/chat-main";
import ChatSend from "@/components/chat-send";
import {chat} from "@/api/sse";
export default {
name: "index",
components: {ChatSend, ChatMain},
data(){
return{
deviceType:1, //1pc 2h5
uid:123456
}
},
mounted() {
this.clientWidth()
this.initSSE();
},
methods:{
initSSE(){
const url = `${process.env.VUE_APP_API_BASE_URL}/createSse`; // 替换为实际的SSE端点URL
this.eventSource = new EventSource(url,{headers:{uid:this.uid}});
this.eventSource.onmessage = (event) => {
// 处理接收到的SSE数据
let payload;
try{
payload =JSON.parse(event.data);
}catch (error) {
// console.error("内容报异常")
}
if(undefined!=payload){
this.$refs.main.appendMsg(payload)
this.$refs.main.toBottom()
}
};
this.eventSource.onerror = (error) => {
// 处理连接错误
console.error(error);
};
},
sendMsg(textarea){
if(null==this.eventSource){
this.initSSE()
}
chat({question:textarea,uid:this.uid}).then(()=>{
this.$refs.main.appendQ(textarea)
this.$refs.send.echoQuestion()
})
},
sendText(event){
if (event.keyCode === 13) {
this.$refs.send.sendMessages()
this.$refs.main.toBottom()
return false
}
},
clientWidth(){
//监听宽度,判断是否关闭对应的菜单
let deviceInfo = 'pc';
this.deviceType = 1
if(document.body.clientWidth>document.body.clientHeight){
setDeviceInfo(deviceInfo);
}else{
deviceInfo = 'h5'
this.deviceType = 2
setDeviceInfo(deviceInfo);
}
},
},
}
</script>
<style scoped>
.container{
text-align: center;
display: flex;
justify-content: flex-start;
align-items: center;
}
.aside{
width: 13vw;
height: calc(100vh);
background: #f1f0f0;
}
.logo{
width: 13vw;
height: 12vh;
}
.main-right{
width: 87vw;
height: calc(100vh);
}
.header{
width: 100%;
height:7vh;
background: white;
}
</style>
</style>
四、实测效果
五、总结
本编 如何让我们的会话界面自动回到底部的案例给大家分享到这里,请帮忙点赞收藏,下一篇将为大家分享 如何将消息保存到历史会话中
前端源码地址:https://gitee.com/wanyushu/chat-ai
后端源码链接:https://gitee.com/wanyushu/springboot-demo.git
顺便给大家推荐聊天项目:AI聊天