fix: 对话创建防抖
This commit is contained in:
@@ -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",
|
||||||
|
|||||||
3
Yi.Ai.Vue3/pnpm-lock.yaml
generated
3
Yi.Ai.Vue3/pnpm-lock.yaml
generated
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user