chat-pc/src/store/modules/dialogue.js

540 lines
15 KiB
JavaScript
Raw Normal View History

2024-12-24 08:14:21 +00:00
import { defineStore } from 'pinia'
import {
ServeRemoveRecords,
ServeRevokeRecords,
ServePublishMessage,
ServeCollectEmoticon
} from '@/api/chat'
import { ServeGetGroupMembers,ServeGroupDetail } from '@/api/group.js'
2024-12-24 08:14:21 +00:00
import { useEditorStore } from './editor'
// 键盘消息事件定时器
let keyboardTimeout = null
export const useDialogueStore = defineStore('dialogue', {
state: () => {
return {
// 对话索引(聊天对话的唯一索引)
index_name: '',
globalUploadList:[],
// 添加一个映射,用于快速查找每个会话的上传任务
uploadTaskMap: {}, // 格式: { "talk_type_receiver_id": [task1, task2, ...] }
2024-12-24 08:14:21 +00:00
// 对话节点
talk: {
avatar:'',
2024-12-24 08:14:21 +00:00
username: '',
talk_type: 0, // 对话来源[1:私聊;2:群聊]
receiver_id: 0,
group_type:0
2024-12-24 08:14:21 +00:00
},
// 好友是否正在输入文字
keyboard: false,
// 对方是否在线
online: false,
// 聊天记录
records: [],
// 查询指定消息上下文的消息信息
specifiedMsg: '',
// 是否不刷新聊天记录
noRefreshRecords: false,
// 是否是手动切换会话
isManualSwitch: false,
2024-12-24 08:14:21 +00:00
// 新消息提示
unreadBubble: 0,
// 是否开启多选操作模式
isOpenMultiSelect: false,
// 是否显示编辑器
isShowEditor: false,
//是否已被解散
isDismiss: false,
//是否退群/移出群
isQuit: false,
2024-12-24 08:14:21 +00:00
// 是否显示会话列表
isShowSessionList: true,
groupInfo: {} ,
2024-12-24 08:14:21 +00:00
// 群成员列表
members: [],
// 群成员列表按字母分组
membersByAlphabet: {},
2024-12-24 08:14:21 +00:00
// 对话记录
items: {
'1_1': {
talk_type: 1, // 对话类型
receiver_id: 0, // 接收者ID
read_sequence: 0, // 当前已读的最后一条记录
records: []
}
}
}
},
getters: {
// 多选列表
selectItems: (state) => state.records.filter((item) => item.isCheck),
// 当前对话是否是群聊
isGroupTalk: (state) => state.talk.talk_type === 2
},
actions: {
// 更新在线状态
setOnlineStatus(status) {
this.online = status
},
// 更新群解散状态
updateDismiss(isDismiss) {
this.isDismiss = isDismiss
},
// 更新群成员退出状态
updateQuit(isQuit) {
this.isQuit = isQuit
},
2024-12-24 08:14:21 +00:00
// 更新对话信息
setDialogue(data = {}) {
this.online = data.is_online == 1
this.talk = {
username: data.remark || data.name,
talk_type: data.talk_type,
receiver_id: data.receiver_id,
avatar:data.avatar,
group_type:data.group_type
2024-12-24 08:14:21 +00:00
}
this.index_name = `${data.talk_type}_${data.receiver_id}`
this.records = []
this.unreadBubble = 0
this.isShowEditor = data?.is_robot === 0
this.isDismiss = data?.is_dismiss === 1 ? true : false
this.isQuit = data?.is_quit === 1 ? true : false
// 只在手动切换会话时清空 specifiedMsg
// if (this.isManualSwitch) {
// this.specifiedMsg = ''
// this.isManualSwitch = false
// }
2024-12-24 08:14:21 +00:00
this.members = []
this.membersByAlphabet = []
2024-12-24 08:14:21 +00:00
if (data.talk_type == 2) {
this.updateGroupMembers()
this.getGroupInfo()
2024-12-24 08:14:21 +00:00
}
// 注意:上传任务的恢复将在聊天记录加载完成后进行
// 在useTalkRecord.ts的onLoad方法中会在加载完聊天记录后调用restoreUploadTasks方法
2024-12-24 08:14:21 +00:00
},
// 更新提及列表
async updateGroupMembers() {
let { code, data } = await ServeGetGroupMembers({
group_id: this.talk.receiver_id
})
if (code != 200) return
this.members = data.items.map((o) => ({
id: o.user_id,
nickname: o.nickname,
avatar: o.avatar,
gender: o.gender,
leader: o.leader,
remark: o.remark,
online: false,
value: o.nickname
}))
const groupMap = {};
data.sortItems.forEach(member => {
const alpha = (member.key || member.nickname?.[0] || '#').toUpperCase();
if (!groupMap[alpha]) groupMap[alpha] = [];
groupMap[alpha].push(member);
});
const alphabets = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
this.membersByAlphabet = alphabets.map(alpha => ({
alphabet: alpha,
members: groupMap[alpha] || []
})).filter(group => group.members.length > 0);
2024-12-24 08:14:21 +00:00
},
// 清空对话记录
clearDialogueRecord() {
this.records = []
},
// 数组头部压入对话记录
unshiftDialogueRecord(records) {
console.log('unshiftDialogueRecord')
2024-12-24 08:14:21 +00:00
this.records.unshift(...records)
},
//数组尾部加入更多对话记录
addDialogueRecordForLoadMore(records){
console.log('addDialogueRecordForLoadMore')
this.records.push(...records)
},
async getGroupInfo(){
const { code, data } = await ServeGroupDetail({
group_id: this.talk.receiver_id
})
if(code == 200){
this.groupInfo = data
}
},
2024-12-24 08:14:21 +00:00
// 推送对话记录
async addDialogueRecord(record) {
2024-12-24 08:14:21 +00:00
// TOOD 需要通过 sequence 排序,保证消息一致性
// this.records.splice(index, 0, record)
this.records.push(record)
// 同步到本地数据库
try {
const { addMessage } = await import('@/utils/db')
await addMessage(record)
} catch (error) {
console.error('同步消息到本地数据库失败:', error)
}
2024-12-24 08:14:21 +00:00
},
// 更新对话记录
async updateDialogueRecord(params) {
2024-12-24 08:14:21 +00:00
const { msg_id = '' } = params
const item = this.records.find((item) => item.msg_id === msg_id)
if (item) {
Object.assign(item, params)
// 同步到本地数据库
try {
// 如果是撤回消息
if (params.is_revoke === 1) {
const { revokeMessage } = await import('@/utils/db')
await revokeMessage(msg_id)
}
} catch (error) {
console.error('同步消息更新到本地数据库失败:', error)
}
}
2024-12-24 08:14:21 +00:00
},
// 批量删除对话记录
async batchDelDialogueRecord(msgIds = []) {
// 同步到本地数据库
try {
const { deleteMessage } = await import('@/utils/db')
for (const msgid of msgIds) {
await deleteMessage(msgid)
}
} catch (error) {
console.error('同步消息删除到本地数据库失败:', error)
}
// 从内存中删除
2024-12-24 08:14:21 +00:00
msgIds.forEach((msgid) => {
const index = this.records.findIndex((item) => item.msg_id === msgid)
if (index >= 0) this.records.splice(index, 1)
})
},
// 自增好友键盘输入事件
triggerKeyboard() {
this.keyboard = true
clearTimeout(keyboardTimeout)
keyboardTimeout = setTimeout(() => (this.keyboard = false), 2000)
},
setUnreadBubble(value) {
if (value === 0) {
this.unreadBubble = 0
} else {
this.unreadBubble++
}
},
// 关闭多选模式
closeMultiSelect() {
this.isOpenMultiSelect = false
for (const item of this.selectItems) {
if (item.isCheck) {
item.isCheck = false
}
}
},
// 删除聊天记录
ApiDeleteRecord(msgIds = []) {
ServeRemoveRecords({
talk_type: this.talk.talk_type,
receiver_id: this.talk.receiver_id,
msg_ids: msgIds
}).then((res) => {
if (res.code == 200) {
this.batchDelDialogueRecord(msgIds)
}
})
},
// 撤销聊天记录
ApiRevokeRecord(msg_id = '') {
ServeRevokeRecords({ msg_id }).then((res) => {
if (res.code == 200) {
this.updateDialogueRecord({ msg_id, is_revoke: 1 })
} else {
window['$message'].warning(res.message)
}
})
},
// 转发聊天记录
ApiForwardRecord(options) {
let data = {
type: 'forward',
receiver: {
talk_type: this.talk.talk_type,
receiver_id: this.talk.receiver_id
},
...options
}
ServePublishMessage(data).then((res) => {
if (res.code == 200) {
this.closeMultiSelect()
}
})
},
ApiCollectImage(options) {
const { msg_id } = options
ServeCollectEmoticon({ msg_id }).then(() => {
useEditorStore().loadUserEmoticon()
window['$message'] && window['$message'].success('收藏成功')
})
},
// 更新视频上传进度
updateUploadProgress(uploadId, percentage) {
// 更新全局列表中的进度
const globalTask = this.globalUploadList.find(item =>
item.extra && item.extra.is_uploading && item.extra.upload_id === uploadId
)
if (globalTask) {
globalTask.extra.percentage = percentage
}
// 更新当前会话记录中的进度
const record = this.records.find(item =>
item.extra && item.extra.is_uploading && item.extra.upload_id === uploadId
)
if (record) {
record.extra.percentage = percentage
}
},
// 添加上传任务
addUploadTask(task) {
// 添加到全局列表
this.globalUploadList.push(task)
// 添加到会话映射
const sessionKey = `${task.talk_type}_${task.receiver_id}`
if (!this.uploadTaskMap[sessionKey]) {
this.uploadTaskMap[sessionKey] = []
}
this.uploadTaskMap[sessionKey].push(task)
// 同时添加到当前会话记录
this.addDialogueRecord(task)
},
// 上传完成后移除任务
removeUploadTask(uploadId) {
// 从全局列表中找到任务
const taskIndex = this.globalUploadList.findIndex(item => item.msg_id === uploadId)
if (taskIndex >= 0) {
const task = this.globalUploadList[taskIndex]
const sessionKey = `${task.talk_type}_${task.receiver_id}`
// 从会话映射中移除
if (this.uploadTaskMap[sessionKey]) {
const mapIndex = this.uploadTaskMap[sessionKey].findIndex(item => item.msg_id === uploadId)
if (mapIndex >= 0) {
this.uploadTaskMap[sessionKey].splice(mapIndex, 1)
}
}
// 从全局列表中移除
this.globalUploadList.splice(taskIndex, 1)
2025-07-08 03:07:37 +00:00
// 移除消息记录
this.batchDelDialogueRecord([uploadId])
}
},
// 视频上传完成后更新消息
completeUpload(uploadId, videoInfo) {
const record = this.records.find(item =>
item.extra && item.extra.is_uploading && item.extra.upload_id === uploadId
)
if (record) {
record.extra.is_uploading = false
record.extra.url = videoInfo.url
// record.extra.cover = videoInfo.cover
}
},
// 更新会话信息
updateDialogueTalk(params){
Object.assign(this.talk, params)
},
// 根据 insert_sequence 将任务插入到 records 数组的正确位置(使用优化的二分查找)
insertTaskAtCorrectPosition(task) {
const len = this.records.length
// 快速路径:如果数组为空或任务应该插入到末尾
if (len === 0) {
this.records.push(task)
return
}
// 快速路径:检查是否应该插入到开头或末尾(避免二分查找的开销)
if (task.insert_sequence < this.records[0].sequence) {
this.records.unshift(task)
return
}
if (task.insert_sequence >= this.records[len - 1].sequence) {
this.records.push(task)
return
}
// 使用优化的二分查找算法找到插入位置
let low = 0
let high = len - 1
// 二分查找优化:使用位运算加速计算中点
while (low <= high) {
const mid = (low + high) >>> 1 // 无符号右移代替 Math.floor((low + high) / 2)
if (this.records[mid].sequence <= task.insert_sequence) {
low = mid + 1
} else {
high = mid - 1
}
}
// 在找到的位置插入任务
this.records.splice(low, 0, task)
},
// 恢复当前会话的上传任务
restoreUploadTasks() {
// 获取当前会话的sessionKey
const sessionKey = `${this.talk.talk_type}_${this.talk.receiver_id}`
// 检查是否有需要恢复的上传任务
if (!this.uploadTaskMap[sessionKey] || this.uploadTaskMap[sessionKey].length === 0) {
return
}
// 性能优化:缓存数组长度和本地变量,减少属性查找
const tasks = this.uploadTaskMap[sessionKey]
const tasksLength = tasks.length
// 如果只有一个任务,直接处理
if (tasksLength === 1) {
this.insertTaskAtCorrectPosition(tasks[0])
return
}
// 性能优化:对于少量任务,避免创建新数组和排序开销
if (tasksLength <= 10) {
// 找出最小的 insert_sequence
let minIndex = 0
for (let i = 1; i < tasksLength; i++) {
if (tasks[i].insert_sequence < tasks[minIndex].insert_sequence) {
minIndex = i
}
}
// 按顺序插入任务
let inserted = 0
let currentMin = tasks[minIndex]
this.insertTaskAtCorrectPosition(currentMin)
inserted++
while (inserted < tasksLength) {
minIndex = -1
let minSequence = Infinity
// 找出剩余任务中 insert_sequence 最小的
for (let i = 0; i < tasksLength; i++) {
const task = tasks[i]
if (task !== currentMin && task.insert_sequence < minSequence) {
minIndex = i
minSequence = task.insert_sequence
}
}
if (minIndex !== -1) {
currentMin = tasks[minIndex]
this.insertTaskAtCorrectPosition(currentMin)
inserted++
}
}
} else {
// 对于大量任务,使用排序后批量处理
// 创建一个新数组并排序,避免修改原数组
const sortedTasks = [...tasks].sort((a, b) => a.insert_sequence - b.insert_sequence)
// 性能优化:使用 requestAnimationFrame 进行批处理,更好地配合浏览器渲染周期
const batchSize = 50 // 每批处理的任务数量
const totalBatches = Math.ceil(sortedTasks.length / batchSize)
const processBatch = (batchIndex) => {
const startIndex = batchIndex * batchSize
const endIndex = Math.min(startIndex + batchSize, sortedTasks.length)
// 处理当前批次的任务
for (let i = startIndex; i < endIndex; i++) {
this.insertTaskAtCorrectPosition(sortedTasks[i])
}
// 如果还有更多批次,安排下一个批次
if (batchIndex < totalBatches - 1) {
// 使用 requestAnimationFrame 配合浏览器渲染周期
// 如果不支持,回退到 setTimeout
if (typeof requestAnimationFrame !== 'undefined') {
requestAnimationFrame(() => processBatch(batchIndex + 1))
} else {
setTimeout(() => processBatch(batchIndex + 1), 0)
}
}
}
// 开始处理第一批
processBatch(0)
}
2024-12-24 08:14:21 +00:00
}
}
})