自己弄个对话框
2025-02-25 16:55 WEB前端小菜鸟 阅读(24) 评论(0) 收藏 举报



遇到的问题:
1.输入框一输入,然后清空,然后列表显示输入的内容,发送消息后接口返回后 对话框如何自动滚动到最新的消息位置
2.回答问题的时候如何让文字一个字一个字的输出 而不是一下子就输出完毕【定时器 100ms执行一次 每次都执行
scrollToLatestMessage 这个定位函数定到最下面
】<template>
<!-- 看板管理-->
<div class="my_chatai_box">
<div class="up">
<div class="up_btn">
<img src="@/assets/images/drive/aiimg.png" alt="" />
<div class="txt">Chat Ai</div>
</div>
</div>
<div class="down">
<div class="down_up commonBox_left_mini" ref="messagesRef">
<div class="down_up_start">
<div v-if="currentObj.repeatTalksList.length == 0">
<chat-ai-robot :isTitle="true"></chat-ai-robot>
<!-- 默认初始状态 -->
<div class="tax">请把你的问题交给我吧</div>
</div>
<!-- 对话状态 -->
<div v-else>
<div
v-for="(item, index) in currentObj.repeatTalksList"
:key="index"
>
<div
class="ask"
style="
width: 100%;
box-sizing: border-box;
display: flex;
flex-direction: column;
align-items: flex-end;
font-size: 14px;
color: #7a7687;
"
>
<div style="padding-right: 10px">{{ currentObj.name }}</div>
<div
style="
max-width: 80%;
text-align: right;
background: #fbfbfb;
padding: 10px;
margin-bottom: 10px;
border-radius: 5px;
"
>
{{ item.ask }}
</div>
</div>
<div
class="anser"
style="
width: 100%;
box-sizing: border-box;
display: flex;
flex-direction: column;
align-items: flex-start;
font-size: 14px;
color: #7a7687;
"
>
<chat-ai-robot
:isTitle="false"
:isLoading="item.isLoading"
></chat-ai-robot>
<div
style="
max-width: 80%;
background: #fbfbfb;
padding: 10px;
margin-bottom: 10px;
border-radius: 5px;
"
>
{{ item.anser }}
</div>
</div>
</div>
</div>
</div>
</div>
<div class="down_input">
<el-input
class="input-box"
v-model="currentObj.inputTalks"
placeholder="给AI发送消息"
suffix-icon="el-icon-arrow-right"
@keyup.enter="sendMessage"
>
</el-input>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { onMounted, reactive, ref, nextTick } from "vue";
import request from "@/API/request";
import serviceModule from "@/API/httpUrl";
import { ElMessage } from "element-plus";
import chatAiRobot from "./chatAiRobot.vue";
const currentObj = reactive({
inputTalks: "",
inputTalksCopy: "",
repeatTalksList: [],
inputTalksList: [],
name: "",
// 当前显示到的字符索引
currentIndex: 0,
// 定时器 ID
timer: null,
});
const getbasicUserInfo = async () => {
const { data, code, msg } = await request(serviceModule.basicUserInfo);
if (code === "0000") {
currentObj.name = data;
}
};
// 模拟发送消息的函数
const sendMessage = () => {
if (currentObj.inputTalks.trim() !== "") {
console.log("发送的消息:", currentObj.inputTalks);
currentObj.inputTalksList.push(currentObj.inputTalks);
currentObj.inputTalksCopy = currentObj.inputTalks;
currentObj.inputTalks = "";
currentObj.repeatTalksList.push({
ask: currentObj.inputTalksCopy,
anser: "",
anserFullText: "",
isLoading: true,
});
// 这里可以添加与后端交互发送消息给AI的逻辑
getAiChart(currentObj.inputTalksList);
}
};
const getAiChart = async (contents) => {
let params = {
contents,
};
const { data, code, msg } = await request(serviceModule.aiChart, params);
if (code === "0000") {
// return data;
// currentObj.repeatTalksList.push({
// ask: currentObj.inputTalksCopy,
// anser: data,
// });
currentObj.repeatTalksList[
currentObj.repeatTalksList.length - 1
].anserFullText = data;
currentObj.repeatTalksList[
currentObj.repeatTalksList.length - 1
].isLoading = false;
// console.log(currentObj.repeatTalksList, "对话内容");
startTyping(); //一个一个字的输出
scrollToLatestMessage();
} else {
ElMessage.error(msg);
}
};
const messagesRef = ref(null);
const startTyping = () => {
// 清除可能存在的旧定时器
clearInterval(currentObj.timer);
// 重置显示文本和索引
currentObj.repeatTalksList[currentObj.repeatTalksList.length - 1].anser = "";
currentObj.currentIndex = 0;
// 设置新的定时器
currentObj.timer = setInterval(() => {
if (
currentObj.currentIndex <
currentObj.repeatTalksList[currentObj.repeatTalksList.length - 1]
.anserFullText.length
) {
// 逐个添加字符到显示文本中
currentObj.repeatTalksList[currentObj.repeatTalksList.length - 1].anser +=
currentObj.repeatTalksList[
currentObj.repeatTalksList.length - 1
].anserFullText[currentObj.currentIndex];
currentObj.currentIndex++;
scrollToLatestMessage(); //这里执行一次 不然输入后对话框不会再最新的对话的位置
} else {
// 显示完所有字符后清除定时器
clearInterval(currentObj.timer);
}
}, 100); // 每个字符显示的间隔时间,单位为毫秒
};
// const scrollToLatestMessage = () => {
// if (messagesRef.value) {
// messagesRef.value.scrollTop = messagesRef.value.scrollHeight;
// }
// };
const scrollToLatestMessage = async () => {
// 等待DOM更新完成,确保列表已经渲染完毕
await nextTick();
if (messagesRef.value) {
const childElements = messagesRef.value.children;
if (childElements.length > 0) {
const lastElement = childElements[childElements.length - 1];
// 让列表容器滚动到最后一个元素的位置
messagesRef.value.scrollTop =
lastElement.offsetTop +
lastElement.offsetHeight -
messagesRef.value.offsetHeight;
}
}
};
onMounted(() => {
getbasicUserInfo();
});
</script>
<style scoped lang="less">
.my_chatai_box {
width: 100%;
height: 100%;
box-sizing: border-box;
background-color: #fff;
border-radius: 6px 6px 6px 6px;
.up {
box-sizing: border-box;
height: 64px;
background: #ffffff;
border-bottom: 1px solid #e5e4e7;
display: flex;
align-items: center;
.up_btn {
width: 92px;
height: 32px;
margin-left: 20px;
border-radius: 6px;
border: 1px solid;
border-image: linear-gradient(
94deg,
rgba(255, 129, 129, 1),
rgba(205, 111, 255, 1),
rgba(205, 111, 255, 1),
rgba(122, 146, 255, 1)
)
1 1;
display: flex;
align-items: center;
justify-content: center;
img {
width: 20px;
height: 20px;
margin-right: 3px;
}
.txt {
font-family: PingFang SC, PingFang SC;
font-size: 14px;
color: #7a7687;
}
}
}
.down {
height: calc(100% - 64px);
box-sizing: border-box;
background: #fff;
padding: 20px;
display: flex;
flex-direction: column;
justify-content: space-between;
.down_up {
height: 0;
flex: 1;
box-sizing: border-box;
margin-bottom: 10px;
.down_up_start {
.tax {
width: 100%;
height: 60px;
box-sizing: border-box;
line-height: 60px;
background: #fbfbfb;
box-shadow: 0px 0px 2px 0px rgba(219, 199, 234, 0.25);
border-radius: 12px 12px 12px 12px;
border: 1px solid #f4f0ff;
padding-left: 20px;
font-size: 14px;
color: #7a7687;
}
}
.down_up_talking {
}
}
.down_input {
height: 80px;
// border: 1px solid #f4f0ff;
.input-box {
height: 100%;
background: #fff;
border-radius: 12px;
box-sizing: border-box;
// padding: 0 20px;
border: none;
box-shadow: none; /* 去掉默认的聚焦阴影 */
outline: none; /* 去掉聚焦时的轮廓线 */
}
}
.down_input :deep(.el-input__inner) {
border: none;
box-shadow: none; /* 去掉默认的聚焦阴影 */
outline: none; /* 去掉聚焦时的轮廓线 */
}
}
}
</style>
import chatAiRobot from "./chatAiRobot.vue";
代码在下面
<template>
<div class="default" :class="isTitle ? 'title' : 'ainame'">
<img src="@/assets/images/drive/aiimg.png" alt="" />
<div class="txt">我的AI</div>
<div v-if="!isTitle">
<div class="think" v-if="isLoading">思考中...</div>
<div class="think" v-else>思考完毕</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { defineProps } from "vue";
const props = defineProps({
isTitle: { type: Boolean, default: false },
isLoading: { type: Boolean, default: false },
});
</script>
<style scoped lang="less">
.default {
display: flex;
align-items: center;
justify-content: center;
img {
width: 20px;
height: 20px;
margin-right: 3px;
}
.txt {
font-family: PingFang SC, PingFang SC;
font-size: 14px;
color: #7a7687;
margin-right: 5px;
}
.think {
border: 1px solid #f4f0ff;
padding: 5px 10px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 6px;
}
}
.title {
margin-bottom: 30px;
}
.ainame {
margin-bottom: 0px;
padding-left: 10px;
}
</style>
浙公网安备 33010602011771号