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' import { addClass, removeClass } from '@/utils/dom' interface Params { receiver_id: number talk_type: number limit: number } interface SpecialParams extends Params { msg_id?: string cursor?: number direction?: 'up' | 'down', sort_sequence?: string } interface LoadOptions { specifiedMsg?: SpecialParams middleMsgCreatedAt?: string } 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, cursor: 0, specialParams: undefined as SpecialParams | undefined }) // 重置 loadConfig const resetLoadConfig = () => { loadConfig.receiver_id = 0 loadConfig.talk_type = 0 loadConfig.status = 0 loadConfig.cursor = 0 loadConfig.specialParams = undefined } 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 element?.scrollIntoView({ behavior: 'smooth' }) 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 } loadConfig.status = 0 let scrollHeight = 0 console.log('加载数据列表load') const el = document.getElementById('imChatPanel') if (el) { scrollHeight = el.scrollHeight } const { data, code } = await ServeTalkRecords(request) if (code != 200) { return (loadConfig.status = 1) } // 防止对话切换过快,数据渲染错误 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)) if (request.cursor == 0) { // 判断是否是初次加载 dialogueStore.clearDialogueRecord() } dialogueStore.unshiftDialogueRecord(items.reverse()) loadConfig.status = items.length >= request.limit ? 1 : 2 loadConfig.cursor = data.cursor nextTick(() => { const el = document.getElementById('imChatPanel') if (el) { if (request.cursor == 0) { el.scrollTop = el.scrollHeight setTimeout(() => { console.log('el.scrollHeight',el.scrollHeight) console.log('request.cursor == 0') el.scrollTop = el.scrollHeight + 1000 }, 500) } else { console.log('request.cursor !== 0') el.scrollTop = el.scrollHeight - scrollHeight } } if (location.msgid) { onJumpMessage(location.msgid) } }) } // 获取当前消息的最小 sequence const getMinSequence = () => { console.error('records.value', records.value) if (!records.value.length) return 0 console.error(Math.min(...records.value.map(item => item.sequence))) return Math.min(...records.value.map(item => item.sequence)) } // 获取当前消息的最大 sequence const getMaxSequence = () => { if (!records.value.length) return 0 return Math.max(...records.value.map(item => item.sequence)) } /** * 加载数据主入口,支持指定消息定位模式 * @param params 原有参数 * @param options 可选,{ specifiedMsg } 指定消息对象 */ const onLoad = (params: Params, options?: LoadOptions) => { // 如果会话切换,重置所有状态 if (params.talk_type !== loadConfig.talk_type || params.receiver_id !== loadConfig.receiver_id) { resetLoadConfig() } loadConfig.cursor = 0 loadConfig.receiver_id = params.receiver_id loadConfig.talk_type = params.talk_type console.error('onLoad', params, options) // 新增:支持指定消息定位模式,参数以传入为准合并 if (options?.specifiedMsg?.cursor !== undefined) { loadConfig.specialParams = { ...options.specifiedMsg } // 记录特殊参数,供分页加载用 console.error('options', options) loadConfig.status = 0 // 复用主流程 loading 状态 // 以 params 为基础,合并 specifiedMsg 的所有字段(只要有就覆盖) const contextParams = { ...params, ...options.specifiedMsg } ServeTalkRecords(contextParams).then(({ data, code }) => { if (code !== 200) { loadConfig.status = 2 return } // dialogueStore.clearDialogueRecord() const items = (data.items || []).map((item: ITalkRecord) => formatTalkRecord(uid, item)) dialogueStore.unshiftDialogueRecord(contextParams.direction === 'down' ? items : items.reverse()) loadConfig.status = items.length >= contextParams.limit ? 1 : 2 loadConfig.cursor = data.cursor nextTick(() => { setTimeout(() => { const el = document.getElementById('imChatPanel') const target = document.getElementById(options.specifiedMsg?.msg_id || '') if (el && target) { const containerRect = el.getBoundingClientRect() const targetRect = target.getBoundingClientRect() const offset = targetRect.top - containerRect.top // 居中 const scrollTo = el.scrollTop + offset - el.clientHeight / 2 + target.clientHeight / 2 el.scrollTo({ top: scrollTo, behavior: 'smooth' }) addClass(target, 'border') setTimeout(() => removeClass(target, 'border'), 3000) } else if (el) { el.scrollTop = el.scrollHeight } }, 50) }) }) return } loadConfig.specialParams = undefined // 普通模式清空 // 原有逻辑 load(params) } // 向上加载更多(兼容特殊参数模式) const onRefreshLoad = () => { console.error('loadConfig.status', loadConfig.status) if (loadConfig.status == 1) { console.log('specialParams', loadConfig.specialParams) // 判断是否是特殊参数模式 if (loadConfig.specialParams && typeof loadConfig.specialParams === 'object') { // 检查特殊参数是否与当前会话匹配 if (loadConfig.specialParams.talk_type === loadConfig.talk_type && loadConfig.specialParams.receiver_id === loadConfig.receiver_id) { // 特殊参数模式下,direction: 'up',cursor: 当前最小 sequence onLoad( { receiver_id: loadConfig.receiver_id, talk_type: loadConfig.talk_type, limit: 30 }, { specifiedMsg: { ...loadConfig.specialParams, direction: 'up', sort_sequence: '', cursor: getMinSequence() } } ) } else { // 如果不匹配,重置为普通模式 resetLoadConfig() load({ receiver_id: loadConfig.receiver_id, talk_type: loadConfig.talk_type, limit: 30 }) } } else { // 原有逻辑 load({ receiver_id: loadConfig.receiver_id, talk_type: loadConfig.talk_type, limit: 30 }) } } } // 向下加载更多(兼容特殊参数模式) const onLoadMoreDown = () => { // 判断是否是特殊参数模式 if (loadConfig.specialParams && typeof loadConfig.specialParams === 'object') { // 检查特殊参数是否与当前会话匹配 if (loadConfig.specialParams.talk_type === loadConfig.talk_type && loadConfig.specialParams.receiver_id === loadConfig.receiver_id) { onLoad( { receiver_id: loadConfig.receiver_id, talk_type: loadConfig.talk_type, limit: 30 }, { specifiedMsg: { ...loadConfig.specialParams, direction: 'down', cursor: getMaxSequence() } } ) } else { // 如果不匹配,重置为普通模式 resetLoadConfig() load({ receiver_id: loadConfig.receiver_id, talk_type: loadConfig.talk_type, limit: 30 }) } } else { load({ receiver_id: loadConfig.receiver_id, talk_type: loadConfig.talk_type, limit: 30 }) } } return { loadConfig, records, onLoad, onRefreshLoad, onLoadMoreDown, onJumpMessage, resetLoadConfig } }