323 lines
9.3 KiB
TypeScript
323 lines
9.3 KiB
TypeScript
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 }
|
||
}
|