chat-pc/src/hooks/useTalkRecord.ts

323 lines
9.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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'
}
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(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',
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 }
}