Files
Yi.Framework/Yi.Ai.Vue3/src/pages/chat/layouts/chatDefaul/index.vue
2025-12-13 18:09:12 +08:00

180 lines
4.6 KiB
Vue

<!-- 默认消息列表页 -->
<script setup lang="ts">
import type { FilesCardProps } from 'vue-element-plus-x/types/FilesCard';
import { Loading } from '@element-plus/icons-vue';
import { useDebounceFn } from '@vueuse/core';
import { ElMessage } from 'element-plus';
import { nextTick, ref, watch } from 'vue';
import ModelSelect from '@/components/ModelSelect/index.vue';
import WelecomeText from '@/components/WelecomeText/index.vue';
import { useGuideTourStore, useUserStore } from '@/stores';
import { useFilesStore } from '@/stores/modules/files';
import { useSessionStore } from '@/stores/modules/session';
const userStore = useUserStore();
const sessionStore = useSessionStore();
const filesStore = useFilesStore();
const guideTourStore = useGuideTourStore();
const senderValue = ref('');
const senderRef = ref();
const isSending = ref(false); // 发送状态标志
// 防抖发送函数
const debouncedSend = useDebounceFn(async () => {
if (!senderValue.value.trim()) {
ElMessage.warning('消息内容不能为空');
return;
}
if (isSending.value) {
ElMessage.warning('请等待上一条消息发送完成');
return;
}
const content = senderValue.value;
isSending.value = true;
try {
localStorage.setItem('chatContent', content);
await sessionStore.createSessionList({
userId: userStore.userInfo?.userId as number,
sessionContent: content,
sessionTitle: content.slice(0, 10),
remark: content.slice(0, 10),
});
senderValue.value = ''; // 清空输入框
}
catch (error: any) {
console.error('发送消息失败:', error);
ElMessage.error(error);
}
finally {
isSending.value = false;
}
}, 800, { leading: true, trailing: false }); // 800ms防抖
// 处理发送事件
function handleSend() {
debouncedSend();
}
function handleDeleteCard(_item: FilesCardProps, index: number) {
filesStore.deleteFileByIndex(index);
}
watch(
() => filesStore.filesList.length,
(val) => {
if (val > 0) {
nextTick(() => {
senderRef.value?.openHeader();
});
}
else {
nextTick(() => {
senderRef.value?.closeHeader();
});
}
},
);
</script>
<template>
<div class="chat-defaul-wrap">
<WelecomeText />
<Sender
ref="senderRef"
v-model="senderValue"
class="chat-defaul-sender"
data-tour="chat-sender"
:auto-size="{
maxRows: 9,
minRows: 3,
}"
variant="updown"
clearable
allow-speech
:loading="isSending"
@submit="handleSend"
>
<template #header>
<div class="sender-header p-12px pt-6px pb-0px">
<Attachments
:items="filesStore.filesList"
:hide-upload="true"
@delete-card="handleDeleteCard"
>
<template #prev-button="{ show, onScrollLeft }">
<div
v-if="show"
class="prev-next-btn left-8px flex-center w-22px h-22px rounded-8px border-1px border-solid border-[rgba(0,0,0,0.08)] c-[rgba(0,0,0,.4)] hover:bg-#f3f4f6 bg-#fff font-size-10px"
@click="onScrollLeft"
>
<el-icon>
<ArrowLeftBold />
</el-icon>
</div>
</template>
<template #next-button="{ show, onScrollRight }">
<div
v-if="show"
class="prev-next-btn right-8px flex-center w-22px h-22px rounded-8px border-1px border-solid border-[rgba(0,0,0,0.08)] c-[rgba(0,0,0,.4)] hover:bg-#f3f4f6 bg-#fff font-size-10px"
@click="onScrollRight"
>
<el-icon>
<ArrowRightBold />
</el-icon>
</div>
</template>
</Attachments>
</div>
</template>
<template #prefix>
<div class="flex-1 flex items-center gap-8px flex-none w-fit overflow-hidden">
<FilesSelect />
<ModelSelect />
</div>
</template>
<template #suffix>
<el-icon v-if="isSending" class="is-loading">
<Loading />
</el-icon>
</template>
</Sender>
</div>
</template>
<style scoped lang="scss">
.chat-defaul-wrap {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
max-width: 800px;
min-height: 450px;
.chat-defaul-sender {
width: 100%;
}
}
:deep(.el-icon.is-loading) {
margin-left: 8px;
color: var(--el-color-primary);
animation: rotating 2s linear infinite;
}
@keyframes rotating {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>