123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419 |
- <template>
- <div class="chat-right-header-container">
- {{selectChatUser.userName}}
- </div>
- <div class="chat-core-container " ref="chatCoreContainerRef">
- <div v-for="chatMessage in chatMessageArr"
- :class="['chat-message-card',
- chatMessage.senderId === loginUser.userId ? 'my-message' : 'other-message']">
- <!-- 自己的消息,头像在右,消息在左 -->
- <template v-if="chatMessage.senderId === loginUser.userId">
- <div class="message-content my-message-content" :style="{ backgroundColor: '#a9ea7a' }">
- {{ chatMessage.message }}
- </div>
- <div class="message-avatar my-avatar">
- <img :src="loginUser.photo" alt="avatar" />
- <div class="chat-mess-triangle-right"></div>
- </div>
- </template>
- <!-- 其他人的消息,头像在左,消息在右 -->
- <template v-else>
- <div class="message-avatar other-avatar">
- <img :src="selectChatUser.photo" alt="avatar" />
- <div class="chat-mess-triangle-left"></div>
- </div>
- <div v-html="md.render(chatMessage.message)" class="message-content other-message-content" :style="{ backgroundColor: '#f5f5f5' }"></div>
- </template>
- </div>
- </div>
- <div class="chat-editer-menu-container">
- 提示: 机器人基于大模型实现,支持对话和文生图, 可以试试输入:"数据库索引介绍" 或 "帮我画一只小猫咪"
- </div>
- <div class="chat-editer-container">
- <textarea
- ref="inputRef"
- v-model="inputValue"
- placeholder="请输入消息..."
- :auto-focus="true"
- @input="handleInput"
- @keydown="handleSendMessage"
- class="chat-input"
- ></textarea>
- </div>
- </template>
-
- <script>
- import { inject, defineComponent, ref, onMounted, onUnmounted, watch, nextTick } from 'vue';
- import { message } from 'ant-design-vue';
- import axios from 'axios';
- import MarkdownIt from 'markdown-it'
- import hljs from'highlight.js';
- export default defineComponent({
- props: {
- selectChatUserRef: Object
- },
- setup(props) {
- const inputValue = ref('');
- const socket = ref(null);
- const eventSource = ref(null);
- const messageQueue = ref([]); // 存储未发送的消息队列
- const isConnected = ref(false);
- const loginUser = ref(inject('loginUser'));
- const selectChatUser = ref(props.selectChatUserRef);
- const reconnectInterval = ref(5000); // 重连间隔时间,单位:毫秒
- const heartbeatInterval = ref(30000); // 心跳间隔时间,单位:毫秒
- const chatMessageArr = ref([]);
- const running = ref(false);
- const md = new MarkdownIt();
- const robotMessage = ref('');
- const chatCoreContainerRef = ref(null);
-
- const loadChatMessages = (chatUserId) => {
- // 先清空
- axios.get(`/client-service/chat/message/list/`+chatUserId, {})
- .then(function (response) {
- if (response.data.code === 40001) {
- // 未登录
- window.location.href = response.data.redirect_url;
- } else if (response.data.code === 200) {
- if (response.data.data.length > 0) {
- chatMessageArr.value = response.data.data;
- }
- }
- }).catch(function (error) {
- // 处理错误
- console.error(error);
- });
- }
- loadChatMessages( selectChatUser.value.userId);
- // 切换对话用户,父组件传递选中用户信息,监听选中用户变化
- watch(() => props.selectChatUserRef, (newValue) => {
- selectChatUser.value = newValue;
- loadChatMessages(selectChatUser.value.userId);
- });
-
- // SSE链接,用户机器人对话
- const connectSSE = () => {
- eventSource.value = new EventSource(`/aigc-service/event/stream?bizCode=PC_WEB_CHAT&userId=`+loginUser.value.userId);
- eventSource.value.onmessage = (event) => {
- // running.value = true;
- robotMessage.value += event.data;
- if (event.data == 'finished') {
- running.value = false;
- // saveRobotMessage(systemMessage);
- robotMessage.value = '';
- } else {
- const lastRobotMessage = chatMessageArr.value[chatMessageArr.value.length - 1];
- if (lastRobotMessage && lastRobotMessage.senderId === selectChatUser.value.userId) {
- // 使用 Vue 的响应式更新
- lastRobotMessage.message = robotMessage.value;
- }
- // 滚动条到底部
- }
- };
- eventSource.value.onerror = (error) => {
- if (eventSource.value.readyState === EventSource.CLOSED) {
- console.log('SSE 连接已关闭');
- } else {
- console.log('SSE 错误,尝试重新连接...');
- // 可以在这里实现自动重连逻辑
- connectSSE();
- }
- };
- };
- const connectWebSocket = () => {
- socket.value = new WebSocket(`/connect-service/ws/chat/PC_WEB_CHAT/` + loginUser.value.userId);
- socket.value.onopen = () => {
- console.log('WebSocket 已连接');
- isConnected.value = true;
- // 发送队列中的消息
- messageQueue.value.forEach(message => socket.value.send(message));
- messageQueue.value = [];
- startHeartbeat();
- };
- socket.value.onmessage = (event) => {
- // 在此处理收到的消息
- const socketMessage = JSON.parse(event.data);
- if(socketMessage.senderId != selectChatUser.value.userId) {
- return;
- }
- const messageType = socketMessage.type;
- if (messageType === 1) {
- // 处理对话
- chatMessageArr.value.push(socketMessage);
- }
- };
- socket.value.onerror = (error) => {
- isConnected.value = false;
- // 关闭当前连接
- socket.value.close();
- // 尝试重连
- setTimeout(connectWebSocket, reconnectInterval.value);
- };
- socket.value.onclose = () => {
- console.log('WebSocket 已关闭');
- isConnected.value = false;
- // 尝试重连
- setTimeout(connectWebSocket, reconnectInterval.value);
- };
- };
- const startHeartbeat = () => {
- const heartbeat = () => {
- if (isConnected.value) {
- socket.value.send('ping'); // 发送心跳包
- }
- };
- setInterval(heartbeat, heartbeatInterval.value);
- };
- const sendMessage = (message) => {
- if (isConnected.value) {
- socket.value.send(message);
- } else {
- messageQueue.value.push(message);
- }
- };
- const handleInput = (event) => {
- inputValue.value = event.target.value;
- };
- const handleSendMessage = () => {
- if (event.key === 'Enter') {
- event.preventDefault(); // 阻止默认的换行行为
- // 消息发送
- if (running.value) {
- message.error("对话进行中...");
- return;
- }
- if (inputValue.value.trim() !== '') {
- const meMessage = JSON.stringify({
- senderId: loginUser.value.userId,
- receiverId: selectChatUser.value.userId,
- type: 1,
- time: new Date().getTime(),
- message: inputValue.value.trim()
- })
- const robotMsg = JSON.stringify({
- senderId: selectChatUser.value.userId,
- receiverId: loginUser.value.userId,
- type: 1,
- time: new Date().getTime(),
- message: ''
- })
- running.value = true;
- robotMessage.value = '';
- // 当前用户自己发送的消息直接提交到页面渲染
- chatMessageArr.value.push(JSON.parse(meMessage));
- chatMessageArr.value.push(JSON.parse(robotMsg));
- // ws发送消息
- sendMessage(meMessage);
- // 清空输入框
- inputValue.value = '';
- }
- }
- }
- // 定义滚动到底部函数
- function scrollToBottom() {
- nextTick(() => {
- // 确保 DOM 更新完成后再执行滚动操作
- if (chatCoreContainerRef.value) {
- chatCoreContainerRef.value.scrollTop = chatCoreContainerRef.value.scrollHeight;
- }
- });
- }
- // 监听消息数组的变化,自动滚动到底部
- watch(chatMessageArr, () => {
- scrollToBottom();
- }, { deep: true });
- onMounted(() => {
-
- connectWebSocket();
- connectSSE();
- });
- onUnmounted(() => {
-
- });
-
- return {
- md,
- running,
- robotMessage,
- inputValue,
- isConnected,
- selectChatUser,
- messageQueue,
- loginUser,
- reconnectInterval,
- heartbeatInterval,
- chatMessageArr,
- chatCoreContainerRef,
- handleSendMessage,
- handleInput,
- connectWebSocket,
- connectSSE,
- scrollToBottom
- };
- }
- });
- </script>
-
- <style scoped>
- .chat-button-group {
- position: absolute;
- left: 0px;
- top: 0px;
- display: flex;
- justify-content: center;
- align-items: center;
- margin-top: 10px;
- }
- .chat-button-group button {
- padding-top: 3px;
- margin-right: 15px;
- height: 30px;
- width: 30px;
- border-radius: 50%;
- background-color: transparent;
- }
- .chat-right-header-container {
- position: absolute;
- top: 0px;
- left: 0px;
- width: 100%;
- height: 50px;
- line-height: 50px;
- padding: 0px 20px;
- border-bottom: 1px solid #e3e3e3;
- }
- .chat-editer-menu-container {
- position: absolute;
- bottom: 150px;
- left: 0px;
- width: 100%;
- height: 50px;
- line-height: 50px;
- font-size: 13px;
- text-indent: 20px;
- color: #666;
- border-top: 1px solid #e3e3e3;
- }
- .chat-editer-container {
- position: absolute;
- bottom: 0px;
- left: 0px;
- width: 100%;
- height: 150px;
- }
- .chat-core-container {
- position: absolute;
- top: 50px;
- left: 0px;
- width: 100%;
- height: calc(100% - 249px);
- padding: 10px 30px;
- overflow-y: scroll;
- overflow-x: hidden;
- }
- .chat-input {
- position: absolute;
- top: 0px;
- left: 0px;
- padding: 20px;
- width: 100%;
- height: 100%;
- line-height: normal;
- font-size: 15px;
- resize: none;
- border: none;
- outline: none;
- box-shadow: none;
- }
- .chat-message-card {
- display: flex;
- margin: 10px 0;
- }
- .my-message {
- justify-content: flex-end;
- }
- .other-message {
- justify-content: flex-start;
- }
- .my-message-content {
- margin-right: 5px;
- }
- .other-message-content {
- margin-left: 5px;
- margin-top: 0px;
- }
- .other-message-content p {
- margin-bottom: 0px;
- }
- .message-avatar {
- position: relative;
- margin-right: 10px;
- }
- .message-avatar img {
- width: 35px;
- height: 35px;
- border-radius: 5px;
- }
- .message-content {
- line-height: 35px;
- padding: 0px 10px;
- border-radius: 4px;
- max-width: 70%;
- }
- .my-avatar {
- float: right;
- margin-left: 10px;
- }
- .chat-core-container::-webkit-scrollbar {/*滚动条整体样式*/
- width: 0px; /*高宽分别对应横竖滚动条的尺寸*/
- height: 0px;
- }
- .chat-core-container::-webkit-scrollbar-thumb {/*滚动条里面小方块*/
- border-radius: 10px;
- background-color: transparent;
- background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .2) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .2) 50%, rgba(255, 255, 255, .2) 75%, transparent 75%, transparent);
- }
- .chat-core-container::-webkit-scrollbar-track {/*滚动条里面轨道*/
- -webkit-box-shadow: inset 0 0 1px rgba(0,0,0,0.2);
- /*border-radius: 10px;*/
- background: #EDEDED;
- }
- .message-content >>> img {
- width: 200px;
- margin-top: 10px;
- }
- .chat-mess-triangle-left {
- position: absolute;
- right: -20px;
- top: 10px;
- width: 0;
- height: 0;
- border-top: 8px solid transparent;
- border-right: 16px solid #f5f5f5;
- border-bottom: 8px solid transparent;
- }
- .chat-mess-triangle-right {
- position: absolute;
- left: -20px;
- top: 10px;
- width: 0;
- height: 0;
- border-top: 8px solid transparent;
- border-left: 16px solid #a9ea7a;
- border-bottom: 8px solid transparent;
- }
- </style>
|