2024-12-24 08:14:21 +00:00
|
|
|
|
import { reactive, computed, nextTick } from 'vue'
|
|
|
|
|
import { ServeTalkRecords } from '@/api/chat'
|
|
|
|
|
import { useDialogueStore } from '@/store'
|
|
|
|
|
import { ITalkRecord } from '@/types/chat'
|
|
|
|
|
import { formatTalkRecord } from '@/utils/talk'
|
2025-05-28 03:29:13 +00:00
|
|
|
|
import { addClass, removeClass, scrollToBottom, isScrollAtBottom } from '@/utils/dom'
|
2024-12-24 08:14:21 +00:00
|
|
|
|
|
|
|
|
|
interface Params {
|
|
|
|
|
receiver_id: number
|
|
|
|
|
talk_type: number
|
|
|
|
|
limit: number
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-22 07:24:13 +00:00
|
|
|
|
interface SpecialParams extends Params {
|
|
|
|
|
msg_id?: string
|
|
|
|
|
cursor?: number
|
2025-05-29 10:04:16 +00:00
|
|
|
|
direction?: 'up' | 'down'
|
2025-05-26 10:57:02 +00:00
|
|
|
|
sort_sequence?: string
|
2025-05-29 10:04:16 +00:00
|
|
|
|
type?: 'loadMore'
|
2025-05-22 07:24:13 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface LoadOptions {
|
|
|
|
|
specifiedMsg?: SpecialParams
|
|
|
|
|
middleMsgCreatedAt?: string
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-24 08:14:21 +00:00
|
|
|
|
export const useTalkRecord = (uid: number) => {
|
|
|
|
|
const dialogueStore = useDialogueStore()
|
|
|
|
|
|
|
|
|
|
const records = computed((): ITalkRecord[] => dialogueStore.records)
|
|
|
|
|
|
|
|
|
|
const location = reactive({
|
|
|
|
|
msgid: '',
|
|
|
|
|
num: 0
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const loadConfig = reactive({
|
|
|
|
|
receiver_id: 0,
|
|
|
|
|
talk_type: 0,
|
|
|
|
|
status: 0,
|
2025-05-22 07:24:13 +00:00
|
|
|
|
cursor: 0,
|
2025-05-29 10:04:16 +00:00
|
|
|
|
specialParams: undefined as SpecialParams | undefined,
|
|
|
|
|
isLocatingMessage: false,
|
|
|
|
|
isLoadingMore: false,
|
|
|
|
|
lastLoadMoreTime: 0,
|
|
|
|
|
targetMessagePosition: 0
|
2024-12-24 08:14:21 +00:00
|
|
|
|
})
|
|
|
|
|
|
2025-05-22 07:24:13 +00:00
|
|
|
|
// 重置 loadConfig
|
|
|
|
|
const resetLoadConfig = () => {
|
|
|
|
|
loadConfig.receiver_id = 0
|
|
|
|
|
loadConfig.talk_type = 0
|
|
|
|
|
loadConfig.status = 0
|
|
|
|
|
loadConfig.cursor = 0
|
|
|
|
|
loadConfig.specialParams = undefined
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-24 08:14:21 +00:00
|
|
|
|
const onJumpMessage = (msgid: string) => {
|
|
|
|
|
const element = document.getElementById(msgid)
|
|
|
|
|
if (!element) {
|
|
|
|
|
if (location.msgid == '') {
|
|
|
|
|
location.msgid = msgid
|
|
|
|
|
location.num = 3
|
|
|
|
|
} else {
|
|
|
|
|
location.num--
|
|
|
|
|
|
|
|
|
|
if (location.num === 0) {
|
|
|
|
|
location.msgid = ''
|
|
|
|
|
location.num = 0
|
|
|
|
|
window['$message'].info('仅支持查看最近300条的记录')
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const el = document.getElementById('imChatPanel')
|
|
|
|
|
|
|
|
|
|
return el?.scrollTo({
|
|
|
|
|
top: 0,
|
|
|
|
|
behavior: 'smooth'
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
location.msgid = ''
|
|
|
|
|
location.num = 0
|
|
|
|
|
|
2025-06-18 09:11:30 +00:00
|
|
|
|
// 使用更精确的滚动定位方式
|
|
|
|
|
const el = document.getElementById('imChatPanel')
|
|
|
|
|
if (el && element) {
|
|
|
|
|
// 计算目标元素相对于容器的偏移量
|
|
|
|
|
const targetOffsetTop = element.offsetTop
|
|
|
|
|
const containerHeight = el.clientHeight
|
|
|
|
|
const targetHeight = element.offsetHeight
|
|
|
|
|
|
|
|
|
|
// 计算理想的滚动位置:让目标元素居中显示
|
|
|
|
|
let scrollTo = targetOffsetTop - containerHeight / 2 + targetHeight / 2
|
|
|
|
|
|
|
|
|
|
// 边界检查:确保目标元素完全在可视区域内
|
|
|
|
|
const minScrollTop = 0
|
|
|
|
|
const maxScrollTop = el.scrollHeight - containerHeight
|
|
|
|
|
|
|
|
|
|
// 如果计算出的位置超出边界,调整到边界内
|
|
|
|
|
if (scrollTo < minScrollTop) {
|
|
|
|
|
scrollTo = minScrollTop
|
|
|
|
|
} else if (scrollTo > maxScrollTop) {
|
|
|
|
|
scrollTo = maxScrollTop
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 执行滚动
|
|
|
|
|
el.scrollTo({ top: scrollTo, behavior: 'smooth' })
|
|
|
|
|
} else {
|
|
|
|
|
// 降级方案:使用 scrollIntoView
|
|
|
|
|
element?.scrollIntoView({
|
|
|
|
|
behavior: 'smooth',
|
|
|
|
|
block: 'center'
|
|
|
|
|
})
|
|
|
|
|
}
|
2024-12-24 08:14:21 +00:00
|
|
|
|
|
|
|
|
|
addClass(element, 'border')
|
|
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
element && removeClass(element, 'border')
|
|
|
|
|
}, 3000)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 加载数据列表
|
|
|
|
|
const load = async (params: Params) => {
|
|
|
|
|
const request = {
|
|
|
|
|
talk_type: params.talk_type,
|
|
|
|
|
receiver_id: params.receiver_id,
|
|
|
|
|
cursor: loadConfig.cursor,
|
|
|
|
|
limit: 30
|
|
|
|
|
}
|
2025-06-30 08:00:06 +00:00
|
|
|
|
// 如果不是从本地数据库加载的,则设置加载状态为0(加载中)
|
|
|
|
|
if (loadConfig.status !== 2 && loadConfig.status !== 3) {
|
|
|
|
|
loadConfig.status = 0
|
|
|
|
|
}
|
2024-12-24 08:14:21 +00:00
|
|
|
|
|
|
|
|
|
let scrollHeight = 0
|
|
|
|
|
const el = document.getElementById('imChatPanel')
|
|
|
|
|
if (el) {
|
|
|
|
|
scrollHeight = el.scrollHeight
|
|
|
|
|
}
|
|
|
|
|
const { data, code } = await ServeTalkRecords(request)
|
|
|
|
|
if (code != 200) {
|
2025-06-30 08:00:06 +00:00
|
|
|
|
return (loadConfig.status = (loadConfig.status === 2 || loadConfig.status === 3) ? loadConfig.status : 1) // 如果已经从本地加载了数据,保持原状态
|
2024-12-24 08:14:21 +00:00
|
|
|
|
}
|
|
|
|
|
// 防止对话切换过快,数据渲染错误
|
|
|
|
|
if (
|
|
|
|
|
request.talk_type != loadConfig.talk_type ||
|
|
|
|
|
request.receiver_id != loadConfig.receiver_id
|
|
|
|
|
) {
|
|
|
|
|
return (location.msgid = '')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const items = (data.items || []).map((item: ITalkRecord) => formatTalkRecord(uid, item))
|
|
|
|
|
|
2025-06-30 08:00:06 +00:00
|
|
|
|
// 同步到本地数据库
|
|
|
|
|
try {
|
|
|
|
|
const { batchAddOrUpdateMessages } = await import('@/utils/db')
|
|
|
|
|
await batchAddOrUpdateMessages(data.items || [], params.talk_type, params.receiver_id, true, 'sequence')
|
|
|
|
|
console.log('聊天记录已同步到本地数据库')
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('同步聊天记录到本地数据库失败:', error)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 如果是从本地数据库加载的数据,且服务器返回的数据与本地数据相同,则不需要更新UI
|
|
|
|
|
if ((loadConfig.status === 2 || loadConfig.status === 3) && request.cursor === 0) {
|
|
|
|
|
try {
|
|
|
|
|
// 获取最新的本地数据库消息进行比较
|
|
|
|
|
const { getMessages } = await import('@/utils/db')
|
|
|
|
|
const localMessages = await getMessages(
|
|
|
|
|
params.talk_type,
|
|
|
|
|
uid,
|
|
|
|
|
params.receiver_id,
|
|
|
|
|
items.length || 30, // 获取与服务器返回数量相同的消息
|
|
|
|
|
0 // 从第一页开始
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// 格式化本地消息,确保与服务器消息结构一致
|
|
|
|
|
const formattedLocalMessages = localMessages.map((item: ITalkRecord) => formatTalkRecord(uid, item))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 改进比较逻辑:检查消息数量和所有消息的ID是否匹配
|
|
|
|
|
if (formattedLocalMessages.length === items.length && formattedLocalMessages.length > 0) {
|
|
|
|
|
// 创建消息ID映射,用于快速查找
|
|
|
|
|
const serverMsgMap = new Map()
|
|
|
|
|
items.forEach(item => serverMsgMap.set(item.msg_id, item))
|
|
|
|
|
|
|
|
|
|
// 检查每条本地消息是否与服务器消息匹配
|
|
|
|
|
const allMatch = formattedLocalMessages.every(localMsg => {
|
|
|
|
|
const serverMsg = serverMsgMap.get(localMsg.msg_id)
|
|
|
|
|
// 检查消息是否存在且关键状态是否一致(考虑撤回、已读等状态变化)
|
|
|
|
|
return serverMsg &&
|
|
|
|
|
serverMsg.is_revoke === localMsg.is_revoke &&
|
|
|
|
|
serverMsg.is_read === localMsg.is_read &&
|
|
|
|
|
(serverMsg.send_status === localMsg.send_status ||
|
|
|
|
|
(!serverMsg.send_status && !localMsg.send_status)) &&
|
|
|
|
|
serverMsg.content === localMsg.content
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if (allMatch) {
|
|
|
|
|
console.log('本地数据与服务器数据一致,无需更新UI')
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 数据不一致,需要更新UI
|
|
|
|
|
console.log('本地数据与服务器数据不一致,更新UI')
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('比较本地数据和服务器数据时出错:', error)
|
|
|
|
|
// 出错时默认更新UI
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-24 08:14:21 +00:00
|
|
|
|
if (request.cursor == 0) {
|
|
|
|
|
// 判断是否是初次加载
|
|
|
|
|
dialogueStore.clearDialogueRecord()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dialogueStore.unshiftDialogueRecord(items.reverse())
|
2025-06-30 08:00:06 +00:00
|
|
|
|
|
2024-12-24 08:14:21 +00:00
|
|
|
|
loadConfig.status = items.length >= request.limit ? 1 : 2
|
|
|
|
|
|
|
|
|
|
loadConfig.cursor = data.cursor
|
|
|
|
|
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
const el = document.getElementById('imChatPanel')
|
|
|
|
|
if (el) {
|
|
|
|
|
if (request.cursor == 0) {
|
2025-05-29 10:28:12 +00:00
|
|
|
|
// el.scrollTop = el.scrollHeight
|
2025-06-18 09:11:30 +00:00
|
|
|
|
|
2025-05-29 10:28:12 +00:00
|
|
|
|
// setTimeout(() => {
|
|
|
|
|
// el.scrollTop = el.scrollHeight + 1000
|
|
|
|
|
// }, 500)
|
2025-06-27 08:26:02 +00:00
|
|
|
|
console.log('滚动到底部')
|
|
|
|
|
|
|
|
|
|
// 在初次加载完成后恢复上传任务
|
|
|
|
|
// 确保在所有聊天记录加载完成后再恢复上传任务
|
|
|
|
|
dialogueStore.restoreUploadTasks()
|
|
|
|
|
|
2025-05-28 03:29:13 +00:00
|
|
|
|
scrollToBottom()
|
2024-12-24 08:14:21 +00:00
|
|
|
|
} else {
|
|
|
|
|
el.scrollTop = el.scrollHeight - scrollHeight
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (location.msgid) {
|
|
|
|
|
onJumpMessage(location.msgid)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-22 07:24:13 +00:00
|
|
|
|
// 获取当前消息的最小 sequence
|
|
|
|
|
const getMinSequence = () => {
|
|
|
|
|
if (!records.value.length) return 0
|
2025-05-29 10:04:16 +00:00
|
|
|
|
return Math.min(...records.value.map((item) => item.sequence))
|
2025-05-22 07:24:13 +00:00
|
|
|
|
}
|
|
|
|
|
// 获取当前消息的最大 sequence
|
|
|
|
|
const getMaxSequence = () => {
|
|
|
|
|
if (!records.value.length) return 0
|
2025-05-29 10:04:16 +00:00
|
|
|
|
return Math.max(...records.value.map((item) => item.sequence))
|
2025-05-22 07:24:13 +00:00
|
|
|
|
}
|
|
|
|
|
|
2025-06-30 08:00:06 +00:00
|
|
|
|
// 从本地数据库加载聊天记录
|
|
|
|
|
const loadFromLocalDB = async (params: Params) => {
|
|
|
|
|
try {
|
|
|
|
|
// 导入 getMessages 函数
|
|
|
|
|
const { getMessages } = await import('@/utils/db')
|
|
|
|
|
// 从本地数据库获取聊天记录
|
|
|
|
|
const localMessages = await getMessages(
|
|
|
|
|
params.talk_type,
|
|
|
|
|
uid,
|
|
|
|
|
params.receiver_id,
|
|
|
|
|
params.limit || 30,
|
|
|
|
|
0 // 从第一页开始
|
|
|
|
|
// 不传入 maxSequence 参数,获取最新的消息
|
|
|
|
|
)
|
|
|
|
|
// 如果有本地数据
|
|
|
|
|
if (localMessages && localMessages.length > 0) {
|
|
|
|
|
// 清空现有记录
|
|
|
|
|
dialogueStore.clearDialogueRecord()
|
|
|
|
|
|
|
|
|
|
// 格式化并添加记录
|
|
|
|
|
const formattedMessages = localMessages.map((item: ITalkRecord) => formatTalkRecord(uid, item))
|
|
|
|
|
dialogueStore.unshiftDialogueRecord(formattedMessages)
|
|
|
|
|
|
|
|
|
|
// 设置加载状态为完成(3表示从本地数据库加载完成)
|
|
|
|
|
loadConfig.status = 3
|
|
|
|
|
|
|
|
|
|
// 恢复上传任务
|
|
|
|
|
dialogueStore.restoreUploadTasks()
|
|
|
|
|
|
|
|
|
|
// 滚动到底部
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
scrollToBottom()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('从本地数据库加载聊天记录失败:', error)
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-22 07:24:13 +00:00
|
|
|
|
/**
|
|
|
|
|
* 加载数据主入口,支持指定消息定位模式
|
|
|
|
|
* @param params 原有参数
|
|
|
|
|
* @param options 可选,{ specifiedMsg } 指定消息对象
|
|
|
|
|
*/
|
2025-06-30 08:00:06 +00:00
|
|
|
|
const onLoad = async (params: Params, options?: LoadOptions) => {
|
2025-05-29 10:04:16 +00:00
|
|
|
|
if (
|
|
|
|
|
params.talk_type !== loadConfig.talk_type ||
|
|
|
|
|
params.receiver_id !== loadConfig.receiver_id
|
|
|
|
|
) {
|
2025-05-22 07:24:13 +00:00
|
|
|
|
resetLoadConfig()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
loadConfig.cursor = 0
|
|
|
|
|
loadConfig.receiver_id = params.receiver_id
|
|
|
|
|
loadConfig.talk_type = params.talk_type
|
|
|
|
|
|
|
|
|
|
// 新增:支持指定消息定位模式,参数以传入为准合并
|
|
|
|
|
if (options?.specifiedMsg?.cursor !== undefined) {
|
|
|
|
|
loadConfig.specialParams = { ...options.specifiedMsg } // 记录特殊参数,供分页加载用
|
|
|
|
|
loadConfig.status = 0 // 复用主流程 loading 状态
|
|
|
|
|
// 以 params 为基础,合并 specifiedMsg 的所有字段(只要有就覆盖)
|
|
|
|
|
const contextParams = {
|
|
|
|
|
...params,
|
|
|
|
|
...options.specifiedMsg
|
|
|
|
|
}
|
2025-05-29 10:04:16 +00:00
|
|
|
|
//msg_id是用来做定位的,不做参数,所以这里清空
|
|
|
|
|
contextParams.msg_id = ''
|
2025-05-22 07:24:13 +00:00
|
|
|
|
ServeTalkRecords(contextParams).then(({ data, code }) => {
|
2025-06-30 08:00:06 +00:00
|
|
|
|
console.log('data',data)
|
2025-05-22 07:24:13 +00:00
|
|
|
|
if (code !== 200) {
|
|
|
|
|
loadConfig.status = 2
|
|
|
|
|
return
|
|
|
|
|
}
|
2025-05-29 10:04:16 +00:00
|
|
|
|
// 记录当前滚动高度
|
|
|
|
|
const el = document.getElementById('imChatPanel')
|
|
|
|
|
const scrollHeight = el?.scrollHeight || 0
|
|
|
|
|
|
|
|
|
|
if (contextParams.direction === 'down' && !contextParams.type) {
|
|
|
|
|
dialogueStore.clearDialogueRecord()
|
|
|
|
|
}
|
2025-05-22 07:24:13 +00:00
|
|
|
|
const items = (data.items || []).map((item: ITalkRecord) => formatTalkRecord(uid, item))
|
2025-05-29 10:04:16 +00:00
|
|
|
|
if (contextParams.type && contextParams.type === 'loadMore') {
|
|
|
|
|
dialogueStore.addDialogueRecordForLoadMore(items)
|
|
|
|
|
} else {
|
|
|
|
|
dialogueStore.unshiftDialogueRecord(
|
|
|
|
|
contextParams.direction === 'down' ? items : items.reverse()
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
if (
|
|
|
|
|
contextParams.direction === 'up' ||
|
|
|
|
|
(contextParams.direction === 'down' && !contextParams.type)
|
|
|
|
|
) {
|
|
|
|
|
loadConfig.status = items[0].sequence == 1 || data.length === 0 ? 2 : 1
|
|
|
|
|
}
|
2025-05-22 07:24:13 +00:00
|
|
|
|
loadConfig.cursor = data.cursor
|
2025-05-29 10:04:16 +00:00
|
|
|
|
|
|
|
|
|
// 使用 requestAnimationFrame 来确保在下一帧渲染前设置滚动位置
|
|
|
|
|
requestAnimationFrame(() => {
|
|
|
|
|
const el = document.getElementById('imChatPanel')
|
2025-06-18 09:11:30 +00:00
|
|
|
|
const msgId = options.specifiedMsg?.msg_id
|
|
|
|
|
const target = msgId ? document.getElementById(msgId) : null
|
|
|
|
|
|
|
|
|
|
if (el) {
|
2025-05-29 10:04:16 +00:00
|
|
|
|
// 如果是向上加载更多,保持原有内容位置
|
|
|
|
|
if (contextParams.direction === 'up') {
|
|
|
|
|
el.scrollTop = el.scrollHeight - scrollHeight
|
|
|
|
|
} else if (contextParams.type && contextParams.type === 'loadMore') {
|
|
|
|
|
// 如果是向下加载更多,保持目标消息在可视区域底部
|
|
|
|
|
// 使用可视区域高度来调整,而不是新内容的总高度
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
if (el) {
|
|
|
|
|
el.scrollTop = scrollHeight - el.clientHeight
|
|
|
|
|
}
|
|
|
|
|
})
|
2025-06-18 09:11:30 +00:00
|
|
|
|
} else if (target && msgId) {
|
|
|
|
|
// 只有在有目标元素且有 msg_id 时才执行定位逻辑
|
2025-05-29 10:04:16 +00:00
|
|
|
|
// 如果是定位到特定消息,计算并滚动到目标位置
|
2025-06-18 09:11:30 +00:00
|
|
|
|
// 使用 nextTick 确保 DOM 完全渲染后再计算位置
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
const el = document.getElementById('imChatPanel')
|
|
|
|
|
const target = document.getElementById(msgId)
|
|
|
|
|
|
|
|
|
|
if (el && target) {
|
|
|
|
|
loadConfig.isLocatingMessage = true
|
|
|
|
|
|
|
|
|
|
// 计算目标元素相对于容器的偏移量
|
|
|
|
|
const targetOffsetTop = target.offsetTop
|
|
|
|
|
const containerHeight = el.clientHeight
|
|
|
|
|
const targetHeight = target.offsetHeight
|
|
|
|
|
|
|
|
|
|
// 计算理想的滚动位置:让目标元素居中显示
|
|
|
|
|
let scrollTo = targetOffsetTop - containerHeight / 2 + targetHeight / 2
|
|
|
|
|
|
|
|
|
|
// 边界检查:确保目标元素完全在可视区域内
|
|
|
|
|
const minScrollTop = 0
|
|
|
|
|
const maxScrollTop = el.scrollHeight - containerHeight
|
|
|
|
|
|
|
|
|
|
// 如果计算出的位置超出边界,调整到边界内
|
|
|
|
|
if (scrollTo < minScrollTop) {
|
|
|
|
|
scrollTo = minScrollTop
|
|
|
|
|
} else if (scrollTo > maxScrollTop) {
|
|
|
|
|
scrollTo = maxScrollTop
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 执行滚动
|
|
|
|
|
el.scrollTo({ top: scrollTo, behavior: 'smooth' })
|
|
|
|
|
|
|
|
|
|
// 添加高亮边框
|
|
|
|
|
addClass(target, 'border')
|
|
|
|
|
setTimeout(() => removeClass(target, 'border'), 3000)
|
|
|
|
|
|
|
|
|
|
// 清除 dialogueStore 中的 specifiedMsg,避免重复使用
|
|
|
|
|
if (dialogueStore.specifiedMsg) {
|
|
|
|
|
dialogueStore.specifiedMsg = ''
|
|
|
|
|
dialogueStore.noRefreshRecords = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
} else {
|
|
|
|
|
// 其他情况滚动到底部
|
2025-06-27 08:26:02 +00:00
|
|
|
|
// 在特殊参数模式下也需要恢复上传任务
|
|
|
|
|
dialogueStore.restoreUploadTasks()
|
2025-06-18 09:11:30 +00:00
|
|
|
|
scrollToBottom()
|
2025-05-22 07:24:13 +00:00
|
|
|
|
}
|
2025-05-29 10:04:16 +00:00
|
|
|
|
}
|
2025-05-22 07:24:13 +00:00
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
loadConfig.specialParams = undefined // 普通模式清空
|
2025-06-30 08:00:06 +00:00
|
|
|
|
|
|
|
|
|
// 设置初始加载状态为0(加载中)
|
|
|
|
|
loadConfig.status = 0
|
|
|
|
|
|
|
|
|
|
// 先从本地数据库加载数据
|
|
|
|
|
const hasLocalData = await loadFromLocalDB(params)
|
|
|
|
|
|
|
|
|
|
// 无论是否有本地数据,都从服务器获取最新数据
|
2025-05-22 07:24:13 +00:00
|
|
|
|
// 原有逻辑
|
2025-06-27 08:26:02 +00:00
|
|
|
|
console.log('onLoad()执行load')
|
2025-05-22 07:24:13 +00:00
|
|
|
|
load(params)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 向上加载更多(兼容特殊参数模式)
|
2024-12-24 08:14:21 +00:00
|
|
|
|
const onRefreshLoad = () => {
|
2025-05-22 07:24:13 +00:00
|
|
|
|
console.error('loadConfig.status', loadConfig.status)
|
2025-06-30 08:00:06 +00:00
|
|
|
|
if (loadConfig.status == 1 || loadConfig.status == 3) {
|
2025-05-22 07:24:13 +00:00
|
|
|
|
console.log('specialParams', loadConfig.specialParams)
|
|
|
|
|
// 判断是否是特殊参数模式
|
|
|
|
|
if (loadConfig.specialParams && typeof loadConfig.specialParams === 'object') {
|
|
|
|
|
// 检查特殊参数是否与当前会话匹配
|
2025-05-29 10:04:16 +00:00
|
|
|
|
if (
|
|
|
|
|
loadConfig.specialParams.talk_type === loadConfig.talk_type &&
|
|
|
|
|
loadConfig.specialParams.receiver_id === loadConfig.receiver_id
|
|
|
|
|
) {
|
2025-05-22 07:24:13 +00:00
|
|
|
|
// 特殊参数模式下,direction: 'up',cursor: 当前最小 sequence
|
2025-06-18 09:11:30 +00:00
|
|
|
|
// 注意:向上加载更多时,不传递 msg_id,避免触发定位逻辑
|
2025-05-22 07:24:13 +00:00
|
|
|
|
onLoad(
|
|
|
|
|
{
|
|
|
|
|
receiver_id: loadConfig.receiver_id,
|
|
|
|
|
talk_type: loadConfig.talk_type,
|
|
|
|
|
limit: 30
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
specifiedMsg: {
|
|
|
|
|
...loadConfig.specialParams,
|
|
|
|
|
direction: 'up',
|
2025-05-26 10:57:02 +00:00
|
|
|
|
sort_sequence: '',
|
2025-05-29 10:04:16 +00:00
|
|
|
|
cursor: getMinSequence(),
|
2025-06-18 09:11:30 +00:00
|
|
|
|
// 向上加载更多时不传递 msg_id,保持原有滚动位置
|
|
|
|
|
msg_id: undefined
|
2025-05-22 07:24:13 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
// 如果不匹配,重置为普通模式
|
|
|
|
|
resetLoadConfig()
|
2025-06-27 08:26:02 +00:00
|
|
|
|
console.log('load执行2')
|
2025-05-22 07:24:13 +00:00
|
|
|
|
load({
|
|
|
|
|
receiver_id: loadConfig.receiver_id,
|
|
|
|
|
talk_type: loadConfig.talk_type,
|
|
|
|
|
limit: 30
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// 原有逻辑
|
2025-06-27 08:26:02 +00:00
|
|
|
|
console.log('load执行3')
|
2025-05-22 07:24:13 +00:00
|
|
|
|
load({
|
|
|
|
|
receiver_id: loadConfig.receiver_id,
|
|
|
|
|
talk_type: loadConfig.talk_type,
|
|
|
|
|
limit: 30
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-29 10:04:16 +00:00
|
|
|
|
// 向下加载更多(特殊参数模式才生效,普通模式无效,因为普通模式的数据就是从最新开始加载历史的,所以不需要加载更新的数据)
|
2025-05-22 07:24:13 +00:00
|
|
|
|
const onLoadMoreDown = () => {
|
|
|
|
|
// 判断是否是特殊参数模式
|
|
|
|
|
if (loadConfig.specialParams && typeof loadConfig.specialParams === 'object') {
|
2025-05-29 10:04:16 +00:00
|
|
|
|
if (loadConfig.isLocatingMessage) {
|
|
|
|
|
loadConfig.isLocatingMessage = false
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
// 添加时间间隔限制,至少间隔 500ms 才能触发下一次加载
|
|
|
|
|
const now = Date.now()
|
|
|
|
|
if (now - loadConfig.lastLoadMoreTime < 500) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
loadConfig.lastLoadMoreTime = now
|
|
|
|
|
|
|
|
|
|
// 记录当前目标消息的位置
|
|
|
|
|
const el = document.getElementById('imChatPanel')
|
|
|
|
|
if (el) {
|
|
|
|
|
loadConfig.targetMessagePosition = el.scrollHeight - el.scrollTop
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log('onLoadMoreDown 特殊模式触底了')
|
2025-05-22 07:24:13 +00:00
|
|
|
|
// 检查特殊参数是否与当前会话匹配
|
2025-05-29 10:04:16 +00:00
|
|
|
|
if (
|
|
|
|
|
loadConfig.specialParams.talk_type === loadConfig.talk_type &&
|
|
|
|
|
loadConfig.specialParams.receiver_id === loadConfig.receiver_id
|
|
|
|
|
) {
|
2025-05-22 07:24:13 +00:00
|
|
|
|
onLoad(
|
|
|
|
|
{
|
|
|
|
|
receiver_id: loadConfig.receiver_id,
|
|
|
|
|
talk_type: loadConfig.talk_type,
|
|
|
|
|
limit: 30
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
specifiedMsg: {
|
|
|
|
|
...loadConfig.specialParams,
|
|
|
|
|
direction: 'down',
|
2025-05-29 10:04:16 +00:00
|
|
|
|
sort_sequence: 'asc',
|
|
|
|
|
cursor: getMaxSequence(),
|
|
|
|
|
type: 'loadMore'
|
2025-05-22 07:24:13 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
}
|
2024-12-24 08:14:21 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-29 10:04:16 +00:00
|
|
|
|
return {
|
|
|
|
|
loadConfig,
|
|
|
|
|
records,
|
|
|
|
|
onLoad,
|
|
|
|
|
onRefreshLoad,
|
|
|
|
|
onLoadMoreDown,
|
|
|
|
|
onJumpMessage,
|
|
|
|
|
resetLoadConfig
|
|
|
|
|
}
|
2024-12-24 08:14:21 +00:00
|
|
|
|
}
|