Browse Source

feat:基本完成会话功能;

dxj
李朋徽 1 year ago
parent
commit
3bead79c63
  1. 5
      src/api/base/message.ts
  2. 22
      src/assets/svg/again.svg
  3. 16
      src/assets/svg/copy.svg
  4. 20
      src/assets/svg/stop.svg
  5. 283
      src/components/AppMessage/index.vue
  6. 11
      src/design/public.scss
  7. 2
      src/enums/messageEnum.ts
  8. 63
      src/hooks/useMqtt.ts
  9. 6
      src/store/moules/messageStore/index.ts
  10. 38
      src/utils/copyTextToClipboard.ts
  11. 12
      src/utils/mqtt.ts
  12. 113
      src/views/conversation/index.vue

5
src/api/base/message.ts

@ -12,14 +12,14 @@ export async function conversationList() {
/**
* @description
*/
export async function historyMessage(data: {
export async function historyMessage(params: {
conversationId: string
current: number
size: number
}) {
return defHttp.get({
url: `/open-chat/chat/chatMessageLog/page`,
data,
params,
})
}
@ -34,6 +34,7 @@ export async function sendMessage(data: {
return defHttp.post({
url: `/open-chat/chat/session`,
data,
timeout: 30 * 1000,
})
}

22
src/assets/svg/again.svg

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="22px" height="22px" viewBox="0 0 22 22" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>编组 6</title>
<defs>
<rect id="path-1" x="0" y="0" width="22" height="22"></rect>
</defs>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="回答结束" transform="translate(-639.000000, -692.000000)">
<g id="编组-6" transform="translate(639.000000, 692.000000)">
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<g id="矩形"></g>
<g id="tizhibianbiezhongxinceshi" mask="url(#mask-2)" fill="#4670E3" fill-rule="nonzero">
<g transform="translate(0.000000, 0.004246)" id="路径">
<path d="M3.18539434,11.6316828 L6.37084384,8.24859031 L4.50456772,8.24859031 C4.52177325,8.19915198 4.53895121,8.14979637 4.55756296,8.10107495 C5.6647278,5.12189882 8.39897328,3.01358719 11.5972718,3.01358719 C15.7752653,3.01358719 19.162604,6.61218997 19.162604,11.050169 C19.162604,15.4887822 15.7752653,19.0874952 11.5972718,19.0874952 C8.52354796,19.0874952 5.87811497,17.1409266 4.69506935,14.3451659 C4.68359899,14.3186407 4.67499624,14.2899924 4.66498724,14.2627503 L2.63545158,16.4047836 C2.64832815,16.4298199 2.65833714,16.4585509 2.67267509,16.483532 C2.67554268,16.4892948 2.67841027,16.4950024 2.68125028,16.5007376 C4.47161804,19.7856979 7.81602567,21.9957537 11.6473719,21.9957537 C17.3651225,21.9957537 22,17.0715531 22,10.9971186 C22,4.92351133 17.3651225,6.8598109e-16 11.6473719,6.8598109e-16 C6.86784656,6.8598109e-16 2.84453738,3.44038912 1.65286142,8.11679155 C1.64139106,8.15980538 1.63852347,8.20488716 1.62848692,8.24856275 L-6.61481765e-16,8.24856275 L3.18539434,11.6316828 Z"></path>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

16
src/assets/svg/copy.svg

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="22px" height="24px" viewBox="0 0 22 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>fuzhi</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="回答结束" transform="translate(-849.000000, -691.000000)">
<g id="fuzhi" transform="translate(849.000000, 692.000000)">
<rect id="矩形" x="0" y="0" width="22" height="22"></rect>
<g id="编组-5" transform="translate(1.000000, 0.000000)" fill="#4670E3" fill-rule="nonzero" stroke="#4670E3" stroke-width="0.4">
<path d="M16.8235294,19.4117647 L0,19.4117647 L0,0 L16.8235294,0 L16.8235294,19.4117647 Z M1.52941176,18.0252101 L15.2941176,18.0252101 L15.2941176,1.38655462 L1.52941176,1.38655462 L1.52941176,18.0252101 Z" id="形状"></path>
<path d="M3.4,7.6 L13.4,7.6 L13.4,9 L3.4,9 L3.4,7.6 Z M3.4,10.6 L13.4,10.6 L13.4,12 L3.4,12 L3.4,10.6 Z M3.4,13.6 L13.4,13.6 L13.4,15 L3.4,15 L3.4,13.6 Z M3.4,4.6 L8.4,4.6 L8.4,6 L3.4,6 L3.4,4.6 Z" id="形状"></path>
<polygon id="路径" points="19.4117647 22 6.47058824 22 6.47058824 20.6134454 18.1792717 20.6134454 18.1792717 3.97478992 16.977591 3.97478992 16.977591 2.58823529 19.4117647 2.58823529"></polygon>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

20
src/assets/svg/stop.svg

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="22px" height="22px" viewBox="0 0 22 22" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>编组 6</title>
<defs>
<rect id="path-1" x="0" y="0" width="22" height="22"></rect>
</defs>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="回答中" transform="translate(-641.000000, -668.000000)">
<g id="编组-6" transform="translate(641.000000, 668.000000)">
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<g id="矩形"></g>
<g id="tingzhi" mask="url(#mask-2)" fill="#4670E3" fill-rule="nonzero">
<path d="M8.27047258,8.27376029 L13.7620168,8.27376029 L13.7620168,13.7653042 L8.27047258,13.7653042 L8.27047258,8.27376029 Z M20.5028874,16.5385339 L18.4572872,14.4970525 C20.1853307,10.8423285 19.0340567,6.47431111 15.7291045,4.14612872 C12.4241523,1.81794634 7.92352899,2.20446963 5.06394361,5.06207587 C2.20435824,7.91968212 1.8147193,12.4200365 4.1406133,15.7265995 C6.46650729,19.0331625 10.8337269,20.1874601 14.4896465,18.4619472 L16.5352467,20.5048015 C11.758152,23.2868757 5.65835199,22.09116 2.28515453,17.7114216 C-1.08804293,13.3316833 -0.686533687,7.12877414 3.22314327,3.22045025 C7.13282022,-0.687873643 13.3358683,-1.08723612 17.7144393,2.2874767 C22.0930102,5.66218951 23.2866148,11.7624026 20.5028874,16.5385339 Z" id="形状"></path>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

283
src/components/AppMessage/index.vue

@ -1,50 +1,134 @@
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { nextTick, onMounted, ref, watch } from 'vue'
import type { MessageItem } from './index.d'
import { SvgIcon } from '@/components/SvgIcon'
import { MessageStatusEnum, MessageTypeEnum } from '@/enums/messageEnum'
import { copyText } from '@/utils/copyTextToClipboard'
defineProps<{
const props = defineProps<{
list: MessageItem[]
}>()
const emit = defineEmits(['onScrollTop', 'reloadMessage'])
defineExpose({ getScrollTopDistance, seamlessScrollToTop })
const messageRef = ref<Element | null>(null)
const elHeight = ref(0) //
const defaultScrollTop = ref(0) //
const isStop = ref(false) //
onMounted(async () => {
watch(
() => props.list[props.list.length - 1],
() => {
isStop.value && scrollToBottom()
},
{ immediate: true },
)
/**
* @description: 滚动到底部
*/
async function scrollToBottom() {
if (!messageRef.value) {
return
}
await nextTick()
defaultScrollTop.value = messageRef.value.scrollTop
messageRef.value.scrollTo(0, messageRef.value.scrollHeight)
}
/**
* @description: 监听滚动
*/
function onScroll() {
if (!messageRef.value) {
return
}
// 20px
if (messageRef.value.scrollTop - defaultScrollTop.value > 20) {
isStop.value = true
}
else {
isStop.value = false
}
emit('onScrollTop', messageRef.value.scrollTop)
}
/**
* @description: 上滑加载无缝滚动
*/
async function seamlessScrollToTop() {
if (!messageRef.value) {
return
}
await getScrollTopDistance()
messageRef.value.scrollTo(0, elHeight.value)
}
/**
* @description: 获取前10个元素的总高度用于无缝滚动顶部的距离
* @param size 获取的元素个数默认为10
*/
async function getScrollTopDistance(size = 10) {
if (!messageRef.value || messageRef.value.children.length < size) {
return
}
await nextTick()
elHeight.value = 0
Array.from(messageRef.value.children).slice(0, size).forEach((item) => {
elHeight.value = elHeight.value + item.clientHeight
})
// +130
elHeight.value += 130
}
/**
* @description: 重新回答
*/
function reloadMessage() {
emit('reloadMessage')
}
onMounted(async () => {
scrollToBottom()
})
</script>
<template>
<div ref="messageRef" class="app-message">
<div class="content-wrap">
<div v-for="(item, index) in list" :key="item.time + index" class="item">
<div v-if="item.messageType === MessageTypeEnum.USER" class="user">
<div class="content">
{{ item.content }}
</div>
<img class="icon-user" src="@/assets/images/conversation/user.png" alt="">
<div ref="messageRef" class="app-message" @scroll="onScroll">
<div v-for="(item, index) in list" :key="item.time + index" class="item">
<div v-if="item.messageType === MessageTypeEnum.USER" class="user">
<div class="content">
{{ item.content }}
</div>
<div v-if="item.messageType === MessageTypeEnum.AI" class="ai">
<img class="icon-ai" src="@/assets/images/conversation/logo.png" alt="">
<div class="content">
{{ item.content }}
</div>
<div v-if="item.messageStatus === MessageStatusEnum.ERROR" class="error">
哎呀出问题了...
</div>
<div
v-if="item.messageStatus === MessageStatusEnum.ACTICON"
class="loading"
>
正在回答中...
<img class="icon-user" src="@/assets/images/conversation/user.png" alt="">
</div>
<div v-if="item.messageType === MessageTypeEnum.AI" class="ai">
<img class="icon-ai" src="@/assets/images/conversation/logo.png" alt="">
<div class="content">
{{ item.content }}
</div>
<div v-if="item.messageStatus === MessageStatusEnum.ERROR" class="error">
哎呀出问题了...
</div>
<div
v-if="item.messageStatus === MessageStatusEnum.ACTICON"
class="stop"
>
<SvgIcon class="icon" name="stop"></SvgIcon>
停止回答
</div>
<div v-if="item.messageStatus === MessageStatusEnum.END" class="btns">
<div class="copy" @click="copyText(item.content)">
<SvgIcon class="icon" name="copy"></SvgIcon>
复制
</div>
<div v-if="item.messageStatus === MessageStatusEnum.END" class="btns">
<div class="copy" @click="copyText(item.content)">
复制
</div>
<div v-if="index === 0" class="reload" @click="reloadMessage">
重新回答
</div>
<div v-if="index === list.length - 1" class="reload" @click="reloadMessage">
<SvgIcon class="icon" name="again"></SvgIcon>
重新回答
</div>
</div>
</div>
@ -56,80 +140,77 @@ onMounted(async () => {
@include app('message') {
height: calc(100% - 120px);
overflow: auto;
.content-wrap {
box-sizing: border-box;
transform: rotate(180deg);
.item {
padding: 0 15px;
margin-top: 15px;
transform: rotate(180deg);
.user,
.ai {
display: flex;
box-sizing: border-box;
.item {
padding: 0 15px;
margin-top: 15px;
.user,
.ai {
display: flex;
}
.user {
justify-content: flex-end;
.content {
max-width: calc(100% - 110px);
color: #ffffff;
padding: 10px 15px;
background: linear-gradient(131deg, #009bfc 0%, #00eadb 100%);
border-radius: 15px 15px 0px 15px;
}
.user {
justify-content: flex-end;
.content {
color: #ffffff;
padding: 10px 15px;
background: linear-gradient(131deg, #009bfc 0%, #00eadb 100%);
border-radius: 15px 15px 0px 15px;
}
.icon-user {
width: 40px;
height: 40px;
margin-left: 15px;
}
.icon-user {
width: 40px;
height: 40px;
margin-left: 15px;
}
}
.ai {
position: relative;
margin-bottom: 15px;
display: flex;
flex-wrap: wrap;
.icon-ai {
width: 40px;
height: 40px;
margin-right: 15px;
}
.icon {
margin-right: 5px;
}
.content {
width: calc(100% - 110px);
padding: 10px 15px;
background: #ffffff;
border-radius: 0px 15px 15px 15px;
border: 1px solid #e7edef;
}
.ai {
position: relative;
margin-bottom: 15px;
.error {
color: red;
height: 30px;
padding-left: 60px;
margin-top: 10px;
}
.stop {
height: 30px;
color: #4670e3;
padding-left: 60px;
margin-top: 10px;
cursor: pointer;
}
.btns {
width: 100%;
height: 30px;
padding-left: 60px;
margin: 10px auto 0 auto;
display: flex;
flex-wrap: wrap;
.icon-ai {
width: 40px;
height: 40px;
margin-right: 15px;
}
.content {
width: calc(100% - 55px);
padding: 10px 15px;
background: #ffffff;
border-radius: 0rpx 15px 15px 15px;
border: 1px solid #e7edef;
}
.error {
color: red;
height: 30px;
padding-left: 60px;
margin: 10px auto 0 auto;
}
.loading {
height: 30px;
color: #009dfb;
padding-left: 60px;
margin: 20rpx auto 0 auto;
align-items: center;
.copy {
color: #4670e3;
margin-right: 20px;
cursor: pointer;
}
.btns {
width: 100%;
height: 30px;
padding-left: 60px;
margin: 10px auto 0 auto;
display: flex;
justify-content: space-between;
align-items: center;
.copy {
color: #009dfb;
padding: 5px 15px;
background: #c1effb;
border-radius: 15px;
text-align: center;
margin-right: 20px;
}
.reload {
color: #009dfb;
}
.reload {
color: #4670e3;
cursor: pointer;
}
}
}

11
src/design/public.scss

@ -12,19 +12,14 @@
height: 8px;
}
// ::-webkit-scrollbar-track {
// background: transparent;
// }
::-webkit-scrollbar-track {
background-color: rgba($color: #000000, $alpha: 0.1);
background-color: rgba($color: #eeeeee, $alpha: 0.6);
}
::-webkit-scrollbar-thumb {
background-color: rgba($color: #bdc1c94d, $alpha: 0.2);
background-color: rgba($color: #cccccc, $alpha: 0.4);
border-radius: 2px;
box-shadow: inset 0 0 6px rgba($color: #000000, $alpha: 0.2);
cursor: pointer;
// box-shadow: inset 0 0 6px rgba($color: #000000, $alpha: 0.2);
}
::-webkit-scrollbar-thumb:hover {

2
src/enums/messageEnum.ts

@ -2,7 +2,7 @@
* @description: ai/user
*/
export enum MessageTypeEnum {
AI = 'ai',
AI = 'bot',
USER = 'user',
}

63
src/hooks/useMqtt.ts

@ -3,6 +3,7 @@ import { MqttService } from '@/utils/mqtt'
import { useUserStore } from '@/store/moules/userStore/index'
import { useMessageStore } from '@/store/moules/messageStore/index'
import { MessageStatusEnum, MessageTypeEnum } from '@/enums/messageEnum'
import { sendMessage as sendMessageApi } from '@/api/base/message'
export function useMqtt() {
const userStore = useUserStore()
@ -30,11 +31,9 @@ export function useMqtt() {
.subscribe(topicKey)
.then(() => {
mqttService.onMessage(topicKey, (messageData: any) => {
console.log(messageData)
if (messageData.message_type === MessageStatusEnum.ACTICON) {
messageStore.setMessageStatus(MessageStatusEnum.ACTICON)
messageStore.setMessageFirstItem({
messageStore.setMessageLastItem({
messageType: MessageTypeEnum.AI,
content: messageData.message_content,
time: String(new Date().getTime()),
@ -44,7 +43,7 @@ export function useMqtt() {
}
if (messageData.message_type === MessageStatusEnum.END) {
messageStore.setMessageStatus(MessageStatusEnum.END)
messageStore.setMessageFirstItem({
messageStore.setMessageLastItem({
messageType: MessageTypeEnum.AI,
content: messageData.message_content,
time: String(new Date().getTime()),
@ -54,9 +53,6 @@ export function useMqtt() {
}
})
})
.catch(() => {
// uni.hideLoading()
})
}
/**
@ -79,8 +75,61 @@ export function useMqtt() {
})
}
/**
* @description:
*/
const unsubscribe = (topicKey: string) => {
mqttService.unsubscribe(topicKey)
}
/**
* @description:
*/
const end = () => {
console.log('123123123123')
mqttService.end()
}
/**
* @description: hook
*/
const sendMessage = async (roleId: number, conversationId: string, question: string): Promise<void> => {
messageStore.setMessageStatus(MessageStatusEnum.LOADING)
messageStore.setMessagePushItem({
messageType: MessageTypeEnum.USER,
content: question,
time: String(new Date().getTime()),
avatar: '',
})
messageStore.setMessagePushItem({
messageType: MessageTypeEnum.AI,
content: '正在思考中...',
time: String(new Date().getTime()),
avatar: '',
messageStatus: MessageStatusEnum.LOADING,
})
if (!messageStore.getConversationData) {
return
}
try {
await sendMessageApi({
roleId,
conversationId,
question,
})
}
catch (error: any) {
messageStore.setMessageStatus(MessageStatusEnum.END)
}
}
return {
connect,
subscribe,
unsubscribe,
end,
sendMessage,
}
}

6
src/store/moules/messageStore/index.ts

@ -39,10 +39,14 @@ export const useMessageStore = defineStore('useMessageStore', {
this.messageList.push(item)
},
setMessageFirstItem(item: MessageItem) {
setMessageUnshiftItem(item: MessageItem) {
this.messageList.unshift(item)
},
setMessageLastItem(item: MessageItem) {
this.messageList[this.messageList.length - 1] = item
},
setTopicKey(key: string) {
this.topicKey = key
},

38
src/utils/copyTextToClipboard.ts

@ -0,0 +1,38 @@
import { message } from 'ant-design-vue'
export function copyText(text: string, prompt: string | null = '已成功复制到剪切板!') {
// 浏览器禁用了非安全域的 navigator.clipboard 对象
// 在线上环境会报错 TypeError: Cannot read properties of undefined (reading 'writeText')
if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard.writeText(text).then(
() => {
prompt && message.success(prompt)
},
(error: Error) => {
message.error(`复制失败!${error.message}`)
},
)
}
else {
const textarea = document.createElement('textarea')
textarea.value = text
document.body.appendChild(textarea)
textarea.select()
try {
// 尝试执行复制操作
const success = document.execCommand('copy')
if (success) {
message.success('复制成功')
}
else {
message.error('复制失败')
}
}
catch (error) {
message.error(`复制失败:${error}`)
}
document.body.removeChild(textarea)
}
}

12
src/utils/mqtt.ts

@ -1,4 +1,5 @@
import mqtt from 'mqtt'
import { message } from 'ant-design-vue'
export interface Options {
host: string
@ -24,13 +25,11 @@ export class MqttService {
const { protocol, host, port } = this.options
const connectUrl = `${protocol}://${host}:${port}/mqtt`
this.client = mqtt.connect(connectUrl, this.options)
console.log(this.client)
this.client.on('connect', () => {
console.log('连接成功')
resolve(true)
})
this.client.on('error', (error: any) => {
console.log('连接失败!', error)
message.error('连接失败!')
reject(error)
})
})
@ -42,13 +41,12 @@ export class MqttService {
}
return new Promise((resolve, reject) => {
this.client.subscribe(topic, (err: any) => {
this.client?.subscribe(topic, (err: any) => {
if (err) {
console.log('订阅失败!', err)
message.error('订阅失败!请稍后重试')
reject(err)
}
else {
console.log('订阅成功!', topic)
resolve(true)
}
})
@ -76,7 +74,7 @@ export class MqttService {
this.client.unsubscribe(topic, (err: any) => {
if (err) {
console.log('取消订阅失败!', err)
message.error('取消订阅失败!!请联系管理员')
}
else {
console.log('取消订阅成功!', topic)

113
src/views/conversation/index.vue

@ -1,6 +1,7 @@
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import { Button } from 'ant-design-vue'
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
import { onBeforeRouteLeave } from 'vue-router'
import { Button, message } from 'ant-design-vue'
import { AppContainerBox } from '@/components/AppContainerBox'
import { AppSubMenuTitle } from '@/components/AppSubMenuTitle'
import { AppSubMenuList } from '@/components/AppSubMenuList'
@ -11,7 +12,7 @@ import { AppMessage } from '@/components/AppMessage'
import type { MessageItem } from '@/components/AppMessage/index.d'
import { useMessageStore } from '@/store/moules/messageStore/index'
import { MessageStatusEnum, MessageTypeEnum } from '@/enums/messageEnum'
import { conversationList, historyMessage, sendMessage } from '@/api/base/message'
import { conversationList, historyMessage } from '@/api/base/message'
import { useMqtt } from '@/hooks/useMqtt'
const messageStore = useMessageStore()
@ -19,10 +20,12 @@ const messageStore = useMessageStore()
const sendBtnLoading = ref(false)
const subMenuActive = ref(0)
const subMenuList = ref<SubMenuItem[]>([])
const appMessage = ref()
const messageList = computed(() => messageStore.getMessageList)
const messageStatus = computed(() => messageStore.getMessageStatus)
const conversationData = computed(() => messageStore.getConversationData)
const historyMessageParams = ref({
conversationId: conversationData.value ? conversationData.value.id : '',
conversationId: '',
current: 1,
size: 10,
total: 0,
@ -30,40 +33,33 @@ const historyMessageParams = ref({
const conversationDefaultShow = ref(true)
watch(
() => messageStatus.value,
(val) => {
if (val === MessageStatusEnum.END) {
sendBtnLoading.value = false
}
},
)
function handleSubMenuChange(index: number) {
if (messageStatus.value !== MessageStatusEnum.END) {
message.warn('请先结束对话')
return
}
subMenuActive.value = index
}
/**
* @description: 发送消息
*/
function handleSend(value: string) {
console.log('handleSend', value)
sendBtnLoading.value = true
conversationDefaultShow.value = false
messageStore.setMessageStatus(MessageStatusEnum.LOADING)
messageStore.setMessageFirstItem({
messageType: MessageTypeEnum.USER,
content: value,
time: String(new Date().getTime()),
avatar: '',
})
messageStore.setMessageFirstItem({
messageType: MessageTypeEnum.AI,
content: '正在思考中...',
time: String(new Date().getTime()),
avatar: '',
messageStatus: MessageStatusEnum.LOADING,
})
if (!messageStore.getConversationData) {
return
}
sendMessage({
roleId: messageStore.getConversationData.roleId,
conversationId: messageStore.getConversationData.id,
question: value,
}).then(() => {
}).catch(() => {
messageStore.setMessageStatus(MessageStatusEnum.END)
})
useMqtt().sendMessage(messageStore.getConversationData.roleId, messageStore.getConversationData.id, value)
}
/**
@ -86,6 +82,10 @@ async function getConversationList() {
* @description: 获取历史对话记录
*/
async function getHistoryMessage() {
if (!conversationData.value) {
return
}
historyMessageParams.value.conversationId = conversationData.value.id
const res = await historyMessage(historyMessageParams.value)
if (!res || !res.records || !res.records.length) {
return
@ -99,14 +99,59 @@ async function getHistoryMessage() {
messageStatus: MessageStatusEnum.END,
}
historyMessageParams.value.total = res.total
messageStore.setMessagePushItem(itemData)
messageStore.setMessageUnshiftItem(itemData)
})
conversationDefaultShow.value = false
}
/**
* @description: 滚动监听
*/
async function onScrollTop(scrollTop: number) {
if (scrollTop !== 0) {
return
}
if (historyMessageParams.value.current * historyMessageParams.value.size > historyMessageParams.value.total) {
return
}
if (historyMessageParams.value.current < historyMessageParams.value.total) {
historyMessageParams.value.current++
await getHistoryMessage()
//
appMessage.value.seamlessScrollToTop()
}
}
/**
* @description: 重新回答
*/
function reloadMessage() {
if (!messageStore.getConversationData) {
return
}
const question = messageList.value[messageList.value.length - 2]?.content
useMqtt().sendMessage(messageStore.getConversationData.roleId, messageStore.getConversationData.id, question)
}
onMounted(() => {
getConversationList()
})
onUnmounted(() => {
useMqtt().end()
})
//
onBeforeRouteLeave(() => {
if (messageStatus.value !== MessageStatusEnum.END) {
message.warn('请先结束对话')
return false
}
})
</script>
<template>
@ -139,7 +184,15 @@ onMounted(() => {
</AppConversationDefault>
<!-- 消息列表 -->
<AppMessage v-else :list="messageList"></AppMessage>
<AppMessage
v-else
ref="appMessage"
class="pl-27 pr-5"
:list="messageList"
@on-scroll-top="onScrollTop"
@reload-message="reloadMessage"
>
</AppMessage>
<!-- 发送框 -->
<AppTextarea

Loading…
Cancel
Save