青鸟ai,pc版仓库
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

220 lines
5.3 KiB

<script setup lang="ts">
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'
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) // 是否停止自动滚动
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" @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>
<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="index === list.length - 1" class="reload" @click="reloadMessage">
<SvgIcon class="icon" name="again"></SvgIcon>
重新回答
</div>
</div>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
@include app('message') {
height: calc(100% - 120px);
overflow: auto;
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;
}
.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;
}
.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;
align-items: center;
.copy {
color: #4670e3;
margin-right: 20px;
cursor: pointer;
}
.reload {
color: #4670e3;
cursor: pointer;
}
}
}
}
}
</style>