chat-pc/src/store/modules/dialogue.js
2025-07-08 11:07:37 +08:00

540 lines
15 KiB
JavaScript
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 { defineStore } from 'pinia'
import {
ServeRemoveRecords,
ServeRevokeRecords,
ServePublishMessage,
ServeCollectEmoticon
} from '@/api/chat'
import { ServeGetGroupMembers,ServeGroupDetail } from '@/api/group.js'
import { useEditorStore } from './editor'
// 键盘消息事件定时器
let keyboardTimeout = null
export const useDialogueStore = defineStore('dialogue', {
state: () => {
return {
// 对话索引(聊天对话的唯一索引)
index_name: '',
globalUploadList:[],
// 添加一个映射,用于快速查找每个会话的上传任务
uploadTaskMap: {}, // 格式: { "talk_type_receiver_id": [task1, task2, ...] }
// 对话节点
talk: {
avatar:'',
username: '',
talk_type: 0, // 对话来源[1:私聊;2:群聊]
receiver_id: 0,
group_type:0
},
// 好友是否正在输入文字
keyboard: false,
// 对方是否在线
online: false,
// 聊天记录
records: [],
// 查询指定消息上下文的消息信息
specifiedMsg: '',
// 是否不刷新聊天记录
noRefreshRecords: false,
// 是否是手动切换会话
isManualSwitch: false,
// 新消息提示
unreadBubble: 0,
// 是否开启多选操作模式
isOpenMultiSelect: false,
// 是否显示编辑器
isShowEditor: false,
//是否已被解散
isDismiss: false,
//是否退群/移出群
isQuit: false,
// 是否显示会话列表
isShowSessionList: true,
groupInfo: {} ,
// 群成员列表
members: [],
// 群成员列表按字母分组
membersByAlphabet: {},
// 对话记录
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
},
// 更新对话信息
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
}
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
// }
this.members = []
this.membersByAlphabet = []
if (data.talk_type == 2) {
this.updateGroupMembers()
this.getGroupInfo()
}
// 注意:上传任务的恢复将在聊天记录加载完成后进行
// 在useTalkRecord.ts的onLoad方法中会在加载完聊天记录后调用restoreUploadTasks方法
},
// 更新提及列表
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);
},
// 清空对话记录
clearDialogueRecord() {
this.records = []
},
// 数组头部压入对话记录
unshiftDialogueRecord(records) {
console.log('unshiftDialogueRecord')
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
}
},
// 推送对话记录
async addDialogueRecord(record) {
// 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)
}
},
// 更新对话记录
async updateDialogueRecord(params) {
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)
}
}
},
// 批量删除对话记录
async batchDelDialogueRecord(msgIds = []) {
// 同步到本地数据库
try {
const { deleteMessage } = await import('@/utils/db')
for (const msgid of msgIds) {
await deleteMessage(msgid)
}
} catch (error) {
console.error('同步消息删除到本地数据库失败:', error)
}
// 从内存中删除
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)
// 移除消息记录
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)
}
}
}
})