Skip to content

Commit 612383f

Browse files
author
Kerwin
committed
perf: 无限加载聊天记录
fix: 左侧会话无法滚动
1 parent fc35035 commit 612383f

File tree

8 files changed

+97
-24
lines changed

8 files changed

+97
-24
lines changed

service/src/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,14 +93,14 @@ router.post('/room-delete', auth, async (req, res) => {
9393
router.get('/chat-hisroty', auth, async (req, res) => {
9494
try {
9595
const userId = req.headers.userId as string
96-
const roomId = +req.query.roomid
97-
const lastTime = req.query.lasttime as string
96+
const roomId = +req.query.roomId
97+
const lastId = req.query.lastId as string
9898
if (!roomId || !await existsChatRoom(userId, roomId)) {
9999
res.send({ status: 'Success', message: null, data: [] })
100100
// res.send({ status: 'Fail', message: 'Unknow room', data: null })
101101
return
102102
}
103-
const chats = await getChats(roomId, !lastTime ? null : parseInt(lastTime))
103+
const chats = await getChats(roomId, !isNotEmptyString(lastId) ? null : parseInt(lastId))
104104

105105
const result = []
106106
chats.forEach((c) => {

service/src/storage/mongo.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,12 +73,12 @@ export async function deleteAllChatRooms(userId: string) {
7373
await chatCol.updateMany({ userId, status: Status.Normal }, { $set: { status: Status.Deleted } })
7474
}
7575

76-
export async function getChats(roomId: number, lastTime?: number) {
77-
if (!lastTime)
78-
lastTime = new Date().getTime()
79-
const query = { roomId, dateTime: { $lt: lastTime }, status: { $ne: Status.Deleted } }
76+
export async function getChats(roomId: number, lastId?: number) {
77+
if (!lastId)
78+
lastId = new Date().getTime()
79+
const query = { roomId, uuid: { $lt: lastId }, status: { $ne: Status.Deleted } }
8080
const sort = { dateTime: -1 }
81-
const limit = 200
81+
const limit = 20
8282
const cursor = await chatCol.find(query).sort(sort).limit(limit)
8383
const chats = []
8484
await cursor.forEach(doc => chats.push(doc))

src/api/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,9 @@ export function fetchDeleteChatRoom<T = any>(roomId: number) {
102102
})
103103
}
104104

105-
export function fetchGetChatHistory<T = any>(roomId: number) {
105+
export function fetchGetChatHistory<T = any>(roomId: number, lastId?: number) {
106106
return get<T>({
107-
url: `/chat-hisroty?roomid=${roomId}`,
107+
url: `/chat-hisroty?roomId=${roomId}&lastId=${lastId}`,
108108
})
109109
}
110110

src/store/modules/chat/index.ts

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,18 +48,42 @@ export const useChatStore = defineStore('chat-store', {
4848
callback && callback()
4949
},
5050

51-
async syncChat(h: Chat.History, callback: () => void) {
51+
async syncChat(h: Chat.History, lastId?: number, callback?: () => void,
52+
callbackForStartRequest?: () => void,
53+
callbackForEmptyMessage?: () => void) {
5254
if (!h.uuid) {
5355
callback && callback()
5456
return
5557
}
58+
const hisroty = this.history.filter(item => item.uuid === h.uuid)[0]
59+
if (hisroty === undefined || hisroty.loading || hisroty.all) {
60+
// callback && callback()
61+
if (hisroty?.all ?? false)
62+
callbackForEmptyMessage && callbackForEmptyMessage()
63+
return
64+
}
65+
try {
66+
hisroty.loading = true
67+
const chatIndex = this.chat.findIndex(item => item.uuid === h.uuid)
68+
if (chatIndex <= -1 || this.chat[chatIndex].data.length <= 0 || lastId !== undefined) {
69+
callbackForStartRequest && callbackForStartRequest()
70+
const chatData = (await fetchGetChatHistory(h.uuid, lastId)).data
71+
if (chatData.length <= 0)
72+
hisroty.all = true
5673

57-
const chatIndex = this.chat.findIndex(item => item.uuid === h.uuid)
58-
if (chatIndex <= -1 || this.chat[chatIndex].data.length <= 0) {
59-
const chatData = (await fetchGetChatHistory(h.uuid)).data
60-
this.chat.unshift({ uuid: h.uuid, data: chatData })
74+
if (chatIndex <= -1)
75+
this.chat.unshift({ uuid: h.uuid, data: chatData })
76+
else
77+
this.chat[chatIndex].data.unshift(...chatData)
78+
}
79+
}
80+
finally {
81+
hisroty.loading = false
82+
if (hisroty.all)
83+
callbackForEmptyMessage && callbackForEmptyMessage()
84+
this.recordState()
85+
callback && callback()
6186
}
62-
callback && callback()
6387
},
6488

6589
setUsingContext(context: boolean) {

src/typings/chat.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ declare namespace Chat {
1515
title: string
1616
isEdit: boolean
1717
uuid: number
18+
loading?: boolean
19+
all?: boolean
1820
}
1921

2022
interface ChatState {

src/views/chat/hooks/useScroll.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ type ScrollElement = HTMLDivElement | null
55

66
interface ScrollReturn {
77
scrollRef: Ref<ScrollElement>
8+
scrollTo: (top: number) => Promise<void>
89
scrollToBottom: () => Promise<void>
910
scrollToTop: () => Promise<void>
1011
scrollToBottomIfAtBottom: () => Promise<void>
@@ -13,6 +14,12 @@ interface ScrollReturn {
1314
export function useScroll(): ScrollReturn {
1415
const scrollRef = ref<ScrollElement>(null)
1516

17+
const scrollTo = async (top: number) => {
18+
await nextTick()
19+
if (scrollRef.value)
20+
scrollRef.value.scrollTop = top
21+
}
22+
1623
const scrollToBottom = async () => {
1724
await nextTick()
1825
if (scrollRef.value)
@@ -37,6 +44,7 @@ export function useScroll(): ScrollReturn {
3744

3845
return {
3946
scrollRef,
47+
scrollTo,
4048
scrollToBottom,
4149
scrollToTop,
4250
scrollToBottomIfAtBottom,

src/views/chat/index.vue

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
<script setup lang='ts'>
22
import type { Ref } from 'vue'
3-
import { computed, onMounted, onUnmounted, ref } from 'vue'
3+
import { computed, nextTick, onMounted, onUnmounted, ref } from 'vue'
44
import { useRoute } from 'vue-router'
55
import { storeToRefs } from 'pinia'
6+
import type { MessageReactive } from 'naive-ui'
67
import { NAutoComplete, NButton, NInput, NSpin, useDialog, useMessage } from 'naive-ui'
78
import html2canvas from 'html2canvas'
89
import { Message } from './components'
@@ -32,7 +33,7 @@ useCopyCode()
3233
3334
const { isMobile } = useBasicLayout()
3435
const { addChat, updateChat, updateChatSome, getChatByUuidAndIndex } = useChat()
35-
const { scrollRef, scrollToBottom, scrollToBottomIfAtBottom } = useScroll()
36+
const { scrollRef, scrollToBottom, scrollToBottomIfAtBottom, scrollTo } = useScroll()
3637
const { usingContext, toggleUsingContext } = useUsingContext()
3738
3839
const { uuid } = route.params as { uuid: string }
@@ -45,6 +46,10 @@ const firstLoading = ref<boolean>(false)
4546
const loading = ref<boolean>(false)
4647
const inputRef = ref<Ref | null>(null)
4748
49+
let loadingms: MessageReactive
50+
let allmsg: MessageReactive
51+
let prevScrollTop: number
52+
4853
// 添加PromptStore
4954
const promptStore = usePromptStore()
5055
@@ -421,6 +426,40 @@ function handleStop() {
421426
}
422427
}
423428
429+
async function loadMoreMessage(event: any) {
430+
const chatIndex = chatStore.chat.findIndex(d => d.uuid === +uuid)
431+
if (chatIndex <= -1)
432+
return
433+
434+
const scrollPosition = event.target.scrollHeight - event.target.scrollTop
435+
436+
const lastId = chatStore.chat[chatIndex].data[0].uuid
437+
await chatStore.syncChat({ uuid: +uuid } as Chat.History, lastId, () => {
438+
loadingms && loadingms.destroy()
439+
nextTick(() => scrollTo(event.target.scrollHeight - scrollPosition))
440+
}, () => {
441+
loadingms = ms.loading(
442+
'加载中...', {
443+
duration: 0,
444+
},
445+
)
446+
}, () => {
447+
allmsg && allmsg.destroy()
448+
allmsg = ms.warning('没有更多了', {
449+
duration: 1000,
450+
})
451+
})
452+
}
453+
454+
const handleLoadMoreMessage = debounce(loadMoreMessage, 300)
455+
456+
async function handleScroll(event: any) {
457+
const scrollTop = event.target.scrollTop
458+
if (scrollTop < 50 && (scrollTop < prevScrollTop || prevScrollTop === undefined))
459+
handleLoadMoreMessage(event)
460+
prevScrollTop = scrollTop
461+
}
462+
424463
// 可优化部分
425464
// 搜索选项计算,这里使用value作为索引项,所以当出现重复value时渲染异常(多项同时出现选中效果)
426465
// 理想状态下其实应该是key作为索引项,但官方的renderOption会出现问题,所以就需要value反renderLabel实现
@@ -468,7 +507,7 @@ onMounted(() => {
468507
firstLoading.value = true
469508
debounce(() => {
470509
// 直接刷 极小概率不请求
471-
chatStore.syncChat({ uuid: Number(uuid) } as Chat.History, () => {
510+
chatStore.syncChat({ uuid: Number(uuid) } as Chat.History, undefined, () => {
472511
firstLoading.value = false
473512
scrollToBottom()
474513
if (inputRef.value && !isMobile.value)
@@ -492,7 +531,7 @@ onUnmounted(() => {
492531
@toggle-using-context="toggleUsingContext"
493532
/>
494533
<main class="flex-1 overflow-hidden">
495-
<div id="scrollRef" ref="scrollRef" class="h-full overflow-hidden overflow-y-auto">
534+
<div id="scrollRef" ref="scrollRef" class="h-full overflow-hidden overflow-y-auto" @scroll="handleScroll">
496535
<div
497536
id="image-wrapper"
498537
class="w-full max-w-screen-xl m-auto dark:bg-[#101014]"

src/views/chat/layout/sider/List.vue

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,8 @@ function isActive(uuid: number) {
7070
</script>
7171

7272
<template>
73-
<NSpin :show="loadingRoom">
74-
<NScrollbar class="px-4">
73+
<NScrollbar class="px-4">
74+
<NSpin :show="loadingRoom">
7575
<div class="flex flex-col gap-2 text-sm">
7676
<template v-if="!dataSources.length">
7777
<div class="flex flex-col items-center mt-4 text-center text-neutral-300">
@@ -121,6 +121,6 @@ function isActive(uuid: number) {
121121
</div>
122122
</template>
123123
</div>
124-
</NScrollbar>
125-
</NSpin>
124+
</NSpin>
125+
</NScrollbar>
126126
</template>

0 commit comments

Comments
 (0)