fix: 对话创建防抖

This commit is contained in:
Gsh
2025-07-12 00:36:11 +08:00
parent 57fae7fe4b
commit 5162f9ce3b
4 changed files with 83 additions and 12 deletions

View File

@@ -40,6 +40,7 @@
"element-plus": "^2.9.11", "element-plus": "^2.9.11",
"fingerprintjs": "^0.5.3", "fingerprintjs": "^0.5.3",
"hook-fetch": "^2.0.4-beta.1", "hook-fetch": "^2.0.4-beta.1",
"lodash-es": "^4.17.21",
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"pinia": "^3.0.3", "pinia": "^3.0.3",
"pinia-plugin-persistedstate": "^4.3.0", "pinia-plugin-persistedstate": "^4.3.0",

View File

@@ -41,6 +41,9 @@ importers:
hook-fetch: hook-fetch:
specifier: ^2.0.4-beta.1 specifier: ^2.0.4-beta.1
version: 2.0.4-beta.1(react@19.1.0)(typescript-api-pro@0.0.7)(vue@3.5.16(typescript@5.8.3)) version: 2.0.4-beta.1(react@19.1.0)(typescript-api-pro@0.0.7)(vue@3.5.16(typescript@5.8.3))
lodash-es:
specifier: ^4.17.21
version: 4.17.21
nprogress: nprogress:
specifier: ^0.2.0 specifier: ^0.2.0
version: 0.2.0 version: 0.2.0

View File

@@ -1,10 +1,15 @@
<!-- 默认消息列表页 --> <!-- 默认消息列表页 -->
<script setup lang="ts"> <script setup lang="ts">
import type { FilesCardProps } from 'vue-element-plus-x/types/FilesCard'; 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 ModelSelect from '@/components/ModelSelect/index.vue';
import WelecomeText from '@/components/WelecomeText/index.vue'; import WelecomeText from '@/components/WelecomeText/index.vue';
import { useUserStore } from '@/stores'; import { useUserStore } from '@/stores';
import { useFilesStore } from '@/stores/modules/files'; import { useFilesStore } from '@/stores/modules/files';
import { useSessionStore } from '@/stores/modules/session'; import { useSessionStore } from '@/stores/modules/session';
const userStore = useUserStore(); const userStore = useUserStore();
@@ -13,15 +18,45 @@ const filesStore = useFilesStore();
const senderValue = ref(''); const senderValue = ref('');
const senderRef = ref(); const senderRef = ref();
const isSending = ref(false); // 发送状态标志
async function handleSend() { // 防抖发送函数
localStorage.setItem('chatContent', senderValue.value); const debouncedSend = useDebounceFn(async () => {
await sessionStore.createSessionList({ if (!senderValue.value.trim()) {
userId: userStore.userInfo?.userId as number, ElMessage.warning('消息内容不能为空');
sessionContent: senderValue.value, return;
sessionTitle: senderValue.value.slice(0, 10), }
remark: senderValue.value.slice(0, 10),
}); 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) {
console.error('发送消息失败:', error);
ElMessage.error('发送消息失败,请重试');
}
finally {
isSending.value = false;
}
}, 800, { leading: true, trailing: false }); // 800ms防抖
// 处理发送事件
function handleSend() {
debouncedSend();
} }
function handleDeleteCard(_item: FilesCardProps, index: number) { function handleDeleteCard(_item: FilesCardProps, index: number) {
@@ -33,12 +68,12 @@ watch(
(val) => { (val) => {
if (val > 0) { if (val > 0) {
nextTick(() => { nextTick(() => {
senderRef.value.openHeader(); senderRef.value?.openHeader();
}); });
} }
else { else {
nextTick(() => { nextTick(() => {
senderRef.value.closeHeader(); senderRef.value?.closeHeader();
}); });
} }
}, },
@@ -59,6 +94,7 @@ watch(
variant="updown" variant="updown"
clearable clearable
allow-speech allow-speech
:loading="isSending"
@submit="handleSend" @submit="handleSend"
> >
<template #header> <template #header>
@@ -96,10 +132,14 @@ watch(
</template> </template>
<template #prefix> <template #prefix>
<div class="flex-1 flex items-center gap-8px flex-none w-fit overflow-hidden"> <div class="flex-1 flex items-center gap-8px flex-none w-fit overflow-hidden">
<!-- <FilesSelect /> -->
<ModelSelect /> <ModelSelect />
</div> </div>
</template> </template>
<template #suffix>
<el-icon v-if="isSending" class="is-loading">
<Loading />
</el-icon>
</template>
</Sender> </Sender>
</div> </div>
</template> </template>
@@ -117,4 +157,19 @@ watch(
width: 100%; 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> </style>

View File

@@ -6,7 +6,7 @@ import type { BubbleProps } from 'vue-element-plus-x/types/Bubble';
import type { BubbleListInstance } from 'vue-element-plus-x/types/BubbleList'; import type { BubbleListInstance } from 'vue-element-plus-x/types/BubbleList';
import type { FilesCardProps } from 'vue-element-plus-x/types/FilesCard'; import type { FilesCardProps } from 'vue-element-plus-x/types/FilesCard';
import type { ThinkingStatus } from 'vue-element-plus-x/types/Thinking'; import type { ThinkingStatus } from 'vue-element-plus-x/types/Thinking';
import { ArrowLeftBold, ArrowRightBold } from '@element-plus/icons-vue'; import { ArrowLeftBold, ArrowRightBold, Loading } from '@element-plus/icons-vue';
import { ElIcon, ElMessage } from 'element-plus'; import { ElIcon, ElMessage } from 'element-plus';
import { useHookFetch } from 'hook-fetch/vue'; import { useHookFetch } from 'hook-fetch/vue';
import { computed, nextTick, ref, watch } from 'vue'; import { computed, nextTick, ref, watch } from 'vue';
@@ -164,8 +164,12 @@ function handleDataChunk(chunk: AnyObject) {
function handleError(err: any) { function handleError(err: any) {
console.error('Fetch error:', err); console.error('Fetch error:', err);
} }
const isSending = ref(false);
async function startSSE(chatContent: string) { async function startSSE(chatContent: string) {
if (isSending.value)
return;
try { try {
// 清空输入框 // 清空输入框
inputValue.value = ''; inputValue.value = '';
@@ -199,6 +203,8 @@ async function startSSE(chatContent: string) {
} }
} }
finally { finally {
isSending.value = false;
// 停止打字器状态 // 停止打字器状态
if (bubbleItems.value.length) { if (bubbleItems.value.length) {
bubbleItems.value[bubbleItems.value.length - 1].typing = false; bubbleItems.value[bubbleItems.value.length - 1].typing = false;
@@ -348,6 +354,12 @@ function copy(item: any) {
<ModelSelect /> <ModelSelect />
</div> </div>
</template> </template>
<!-- 新增的 #suffix 模板 -->
<template #suffix>
<ElIcon v-if="isSending" class="is-loading">
<Loading />
</ElIcon>
</template>
</Sender> </Sender>
</div> </div>
</div> </div>