feat(chat): 实现聊天记录本地存储功能
添加Dexie.js作为本地数据库,实现聊天记录和会话的本地存储与同步 修改消息和会话相关store方法,支持本地数据库操作 优化消息加载逻辑,优先从本地加载再同步服务器数据 添加数据库工具函数,包括消息增删改查和会话管理功能
This commit is contained in:
parent
df372ad14e
commit
4863b4c77c
@ -24,9 +24,11 @@
|
||||
"@vicons/ionicons5": "^0.13.0",
|
||||
"@vueup/vue-quill": "^1.2.0",
|
||||
"@vueuse/core": "^10.7.0",
|
||||
"@vueuse/rxjs": "^13.4.0",
|
||||
"ant-design-vue": "^4.2.6",
|
||||
"axios": "^1.6.2",
|
||||
"dayjs": "^1.11.13",
|
||||
"dexie": "^4.0.11",
|
||||
"highlight.js": "^11.5.0",
|
||||
"js-audio-recorder": "^1.0.7",
|
||||
"lodash-es": "^4.17.21",
|
||||
@ -36,6 +38,7 @@
|
||||
"quill": "^1.3.7",
|
||||
"quill-image-uploader": "^1.3.0",
|
||||
"quill-mention": "^4.1.0",
|
||||
"rxjs": "^7.8.2",
|
||||
"sortablejs": "^1.15.6",
|
||||
"viewerjs": "^1.11.7",
|
||||
"vue": "^3.3.11",
|
||||
|
@ -38,6 +38,9 @@ importers:
|
||||
'@vueuse/core':
|
||||
specifier: ^10.7.0
|
||||
version: 10.11.1(vue@3.5.17(typescript@5.2.2))
|
||||
'@vueuse/rxjs':
|
||||
specifier: ^13.4.0
|
||||
version: 13.4.0(rxjs@7.8.2)(vue@3.5.17(typescript@5.2.2))
|
||||
ant-design-vue:
|
||||
specifier: ^4.2.6
|
||||
version: 4.2.6(vue@3.5.17(typescript@5.2.2))
|
||||
@ -47,6 +50,9 @@ importers:
|
||||
dayjs:
|
||||
specifier: ^1.11.13
|
||||
version: 1.11.13
|
||||
dexie:
|
||||
specifier: ^4.0.11
|
||||
version: 4.0.11
|
||||
highlight.js:
|
||||
specifier: ^11.5.0
|
||||
version: 11.11.1
|
||||
@ -74,6 +80,9 @@ importers:
|
||||
quill-mention:
|
||||
specifier: ^4.1.0
|
||||
version: 4.1.0
|
||||
rxjs:
|
||||
specifier: ^7.8.2
|
||||
version: 7.8.2
|
||||
sortablejs:
|
||||
specifier: ^1.15.6
|
||||
version: 1.15.6
|
||||
@ -1121,9 +1130,20 @@ packages:
|
||||
'@vueuse/metadata@10.11.1':
|
||||
resolution: {integrity: sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==}
|
||||
|
||||
'@vueuse/rxjs@13.4.0':
|
||||
resolution: {integrity: sha512-KhfZ7qHlZXuV76KLpk10Ozg3Jq99GiuX+1sk/MMh3Jt8ubkZ8bhLRq2+M2buYdjlPt8O6600WV9K7fBIOLqKZQ==}
|
||||
peerDependencies:
|
||||
rxjs: '>=6.0.0'
|
||||
vue: ^3.5.0
|
||||
|
||||
'@vueuse/shared@10.11.1':
|
||||
resolution: {integrity: sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==}
|
||||
|
||||
'@vueuse/shared@13.4.0':
|
||||
resolution: {integrity: sha512-+AxuKbw8R1gYy5T21V5yhadeNM7rJqb4cPaRI9DdGnnNl3uqXh+unvQ3uCaA2DjYLbNr1+l7ht/B4qEsRegX6A==}
|
||||
peerDependencies:
|
||||
vue: ^3.5.0
|
||||
|
||||
'@webassemblyjs/ast@1.14.1':
|
||||
resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==}
|
||||
|
||||
@ -1755,6 +1775,9 @@ packages:
|
||||
engines: {node: '>=0.10'}
|
||||
hasBin: true
|
||||
|
||||
dexie@4.0.11:
|
||||
resolution: {integrity: sha512-SOKO002EqlvBYYKQSew3iymBoN2EQ4BDw/3yprjh7kAfFzjBYkaMNa/pZvcA7HSWlcKSQb9XhPe3wKyQ0x4A8A==}
|
||||
|
||||
diff@5.2.0:
|
||||
resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==}
|
||||
engines: {node: '>=0.3.1'}
|
||||
@ -4616,6 +4639,12 @@ snapshots:
|
||||
|
||||
'@vueuse/metadata@10.11.1': {}
|
||||
|
||||
'@vueuse/rxjs@13.4.0(rxjs@7.8.2)(vue@3.5.17(typescript@5.2.2))':
|
||||
dependencies:
|
||||
'@vueuse/shared': 13.4.0(vue@3.5.17(typescript@5.2.2))
|
||||
rxjs: 7.8.2
|
||||
vue: 3.5.17(typescript@5.2.2)
|
||||
|
||||
'@vueuse/shared@10.11.1(vue@3.5.17(typescript@5.2.2))':
|
||||
dependencies:
|
||||
vue-demi: 0.14.10(vue@3.5.17(typescript@5.2.2))
|
||||
@ -4623,6 +4652,10 @@ snapshots:
|
||||
- '@vue/composition-api'
|
||||
- vue
|
||||
|
||||
'@vueuse/shared@13.4.0(vue@3.5.17(typescript@5.2.2))':
|
||||
dependencies:
|
||||
vue: 3.5.17(typescript@5.2.2)
|
||||
|
||||
'@webassemblyjs/ast@1.14.1':
|
||||
dependencies:
|
||||
'@webassemblyjs/helper-numbers': 1.13.2
|
||||
@ -5329,6 +5362,8 @@ snapshots:
|
||||
detect-libc@1.0.3:
|
||||
optional: true
|
||||
|
||||
dexie@4.0.11: {}
|
||||
|
||||
diff@5.2.0: {}
|
||||
|
||||
dir-glob@2.2.2:
|
||||
|
@ -130,19 +130,19 @@ export const useTalkRecord = (uid: number) => {
|
||||
cursor: loadConfig.cursor,
|
||||
limit: 30
|
||||
}
|
||||
|
||||
loadConfig.status = 0
|
||||
// 如果不是从本地数据库加载的,则设置加载状态为0(加载中)
|
||||
if (loadConfig.status !== 2 && loadConfig.status !== 3) {
|
||||
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)
|
||||
return (loadConfig.status = (loadConfig.status === 2 || loadConfig.status === 3) ? loadConfig.status : 1) // 如果已经从本地加载了数据,保持原状态
|
||||
}
|
||||
// 防止对话切换过快,数据渲染错误
|
||||
if (
|
||||
@ -154,20 +154,77 @@ export const useTalkRecord = (uid: number) => {
|
||||
|
||||
const items = (data.items || []).map((item: ITalkRecord) => formatTalkRecord(uid, item))
|
||||
|
||||
// 同步到本地数据库
|
||||
try {
|
||||
const { batchAddOrUpdateMessages } = await import('@/utils/db')
|
||||
await batchAddOrUpdateMessages(data.items || [], params.talk_type, params.receiver_id, true, 'sequence')
|
||||
console.log('聊天记录已同步到本地数据库')
|
||||
} catch (error) {
|
||||
console.error('同步聊天记录到本地数据库失败:', error)
|
||||
}
|
||||
|
||||
// 如果是从本地数据库加载的数据,且服务器返回的数据与本地数据相同,则不需要更新UI
|
||||
if ((loadConfig.status === 2 || loadConfig.status === 3) && request.cursor === 0) {
|
||||
try {
|
||||
// 获取最新的本地数据库消息进行比较
|
||||
const { getMessages } = await import('@/utils/db')
|
||||
const localMessages = await getMessages(
|
||||
params.talk_type,
|
||||
uid,
|
||||
params.receiver_id,
|
||||
items.length || 30, // 获取与服务器返回数量相同的消息
|
||||
0 // 从第一页开始
|
||||
)
|
||||
|
||||
// 格式化本地消息,确保与服务器消息结构一致
|
||||
const formattedLocalMessages = localMessages.map((item: ITalkRecord) => formatTalkRecord(uid, item))
|
||||
|
||||
|
||||
// 改进比较逻辑:检查消息数量和所有消息的ID是否匹配
|
||||
if (formattedLocalMessages.length === items.length && formattedLocalMessages.length > 0) {
|
||||
// 创建消息ID映射,用于快速查找
|
||||
const serverMsgMap = new Map()
|
||||
items.forEach(item => serverMsgMap.set(item.msg_id, item))
|
||||
|
||||
// 检查每条本地消息是否与服务器消息匹配
|
||||
const allMatch = formattedLocalMessages.every(localMsg => {
|
||||
const serverMsg = serverMsgMap.get(localMsg.msg_id)
|
||||
// 检查消息是否存在且关键状态是否一致(考虑撤回、已读等状态变化)
|
||||
return serverMsg &&
|
||||
serverMsg.is_revoke === localMsg.is_revoke &&
|
||||
serverMsg.is_read === localMsg.is_read &&
|
||||
(serverMsg.send_status === localMsg.send_status ||
|
||||
(!serverMsg.send_status && !localMsg.send_status)) &&
|
||||
serverMsg.content === localMsg.content
|
||||
})
|
||||
|
||||
if (allMatch) {
|
||||
console.log('本地数据与服务器数据一致,无需更新UI')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 数据不一致,需要更新UI
|
||||
console.log('本地数据与服务器数据不一致,更新UI')
|
||||
} catch (error) {
|
||||
console.error('比较本地数据和服务器数据时出错:', error)
|
||||
// 出错时默认更新UI
|
||||
}
|
||||
}
|
||||
|
||||
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')
|
||||
console.log('request',request)
|
||||
if (el) {
|
||||
if (request.cursor == 0) {
|
||||
// el.scrollTop = el.scrollHeight
|
||||
@ -195,9 +252,7 @@ console.log('request',request)
|
||||
|
||||
// 获取当前消息的最小 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
|
||||
@ -206,13 +261,56 @@ console.log('request',request)
|
||||
return Math.max(...records.value.map((item) => item.sequence))
|
||||
}
|
||||
|
||||
// 从本地数据库加载聊天记录
|
||||
const loadFromLocalDB = async (params: Params) => {
|
||||
try {
|
||||
// 导入 getMessages 函数
|
||||
const { getMessages } = await import('@/utils/db')
|
||||
// 从本地数据库获取聊天记录
|
||||
const localMessages = await getMessages(
|
||||
params.talk_type,
|
||||
uid,
|
||||
params.receiver_id,
|
||||
params.limit || 30,
|
||||
0 // 从第一页开始
|
||||
// 不传入 maxSequence 参数,获取最新的消息
|
||||
)
|
||||
// 如果有本地数据
|
||||
if (localMessages && localMessages.length > 0) {
|
||||
// 清空现有记录
|
||||
dialogueStore.clearDialogueRecord()
|
||||
|
||||
// 格式化并添加记录
|
||||
const formattedMessages = localMessages.map((item: ITalkRecord) => formatTalkRecord(uid, item))
|
||||
dialogueStore.unshiftDialogueRecord(formattedMessages)
|
||||
|
||||
// 设置加载状态为完成(3表示从本地数据库加载完成)
|
||||
loadConfig.status = 3
|
||||
|
||||
// 恢复上传任务
|
||||
dialogueStore.restoreUploadTasks()
|
||||
|
||||
// 滚动到底部
|
||||
nextTick(() => {
|
||||
scrollToBottom()
|
||||
})
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
} catch (error) {
|
||||
console.error('从本地数据库加载聊天记录失败:', error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载数据主入口,支持指定消息定位模式
|
||||
* @param params 原有参数
|
||||
* @param options 可选,{ specifiedMsg } 指定消息对象
|
||||
*/
|
||||
const onLoad = (params: Params, options?: LoadOptions) => {
|
||||
// 如果会话切换,重置所有状态
|
||||
const onLoad = async (params: Params, options?: LoadOptions) => {
|
||||
if (
|
||||
params.talk_type !== loadConfig.talk_type ||
|
||||
params.receiver_id !== loadConfig.receiver_id
|
||||
@ -227,7 +325,6 @@ console.log('request',request)
|
||||
// 新增:支持指定消息定位模式,参数以传入为准合并
|
||||
if (options?.specifiedMsg?.cursor !== undefined) {
|
||||
loadConfig.specialParams = { ...options.specifiedMsg } // 记录特殊参数,供分页加载用
|
||||
console.error('options', options)
|
||||
loadConfig.status = 0 // 复用主流程 loading 状态
|
||||
// 以 params 为基础,合并 specifiedMsg 的所有字段(只要有就覆盖)
|
||||
const contextParams = {
|
||||
@ -237,6 +334,7 @@ console.log('request',request)
|
||||
//msg_id是用来做定位的,不做参数,所以这里清空
|
||||
contextParams.msg_id = ''
|
||||
ServeTalkRecords(contextParams).then(({ data, code }) => {
|
||||
console.log('data',data)
|
||||
if (code !== 200) {
|
||||
loadConfig.status = 2
|
||||
return
|
||||
@ -339,6 +437,14 @@ console.log('request',request)
|
||||
}
|
||||
|
||||
loadConfig.specialParams = undefined // 普通模式清空
|
||||
|
||||
// 设置初始加载状态为0(加载中)
|
||||
loadConfig.status = 0
|
||||
|
||||
// 先从本地数据库加载数据
|
||||
const hasLocalData = await loadFromLocalDB(params)
|
||||
|
||||
// 无论是否有本地数据,都从服务器获取最新数据
|
||||
// 原有逻辑
|
||||
console.log('onLoad()执行load')
|
||||
load(params)
|
||||
@ -347,7 +453,7 @@ console.log('request',request)
|
||||
// 向上加载更多(兼容特殊参数模式)
|
||||
const onRefreshLoad = () => {
|
||||
console.error('loadConfig.status', loadConfig.status)
|
||||
if (loadConfig.status == 1) {
|
||||
if (loadConfig.status == 1 || loadConfig.status == 3) {
|
||||
console.log('specialParams', loadConfig.specialParams)
|
||||
// 判断是否是特殊参数模式
|
||||
if (loadConfig.specialParams && typeof loadConfig.specialParams === 'object') {
|
||||
|
@ -175,10 +175,12 @@ export const useDialogueStore = defineStore('dialogue', {
|
||||
|
||||
// 数组头部压入对话记录
|
||||
unshiftDialogueRecord(records) {
|
||||
console.log('unshiftDialogueRecord')
|
||||
this.records.unshift(...records)
|
||||
},
|
||||
//数组尾部加入更多对话记录
|
||||
addDialogueRecordForLoadMore(records){
|
||||
console.log('addDialogueRecordForLoadMore')
|
||||
this.records.push(...records)
|
||||
},
|
||||
async getGroupInfo(){
|
||||
@ -190,24 +192,56 @@ export const useDialogueStore = defineStore('dialogue', {
|
||||
}
|
||||
},
|
||||
// 推送对话记录
|
||||
addDialogueRecord(record) {
|
||||
async addDialogueRecord(record) {
|
||||
// TOOD 需要通过 sequence 排序,保证消息一致性
|
||||
// this.records.splice(index, 0, record)
|
||||
|
||||
console.log('addDialogueRecord',addDialogueRecord)
|
||||
this.records.push(record)
|
||||
|
||||
// 同步到本地数据库
|
||||
try {
|
||||
const { addMessage } = await import('@/utils/db')
|
||||
await addMessage(record)
|
||||
} catch (error) {
|
||||
console.error('同步消息到本地数据库失败:', error)
|
||||
}
|
||||
},
|
||||
|
||||
// 更新对话记录
|
||||
updateDialogueRecord(params) {
|
||||
async updateDialogueRecord(params) {
|
||||
const { msg_id = '' } = params
|
||||
|
||||
const item = this.records.find((item) => item.msg_id === msg_id)
|
||||
|
||||
item && Object.assign(item, params)
|
||||
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)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 批量删除对话记录
|
||||
batchDelDialogueRecord(msgIds = []) {
|
||||
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)
|
||||
|
||||
|
@ -3,6 +3,7 @@ import { ServeGetTalkList, ServeCreateTalkList } from '@/api/chat'
|
||||
import { formatTalkItem, ttime, KEY_INDEX_NAME } from '@/utils/talk'
|
||||
import { useEditorDraftStore } from './editor-draft'
|
||||
import { ISession } from '@/types/chat'
|
||||
import { getConversations, addOrUpdateConversation, deleteConversation, getConversation } from '@/utils/db'
|
||||
|
||||
interface TalkStoreState {
|
||||
loadStatus: number
|
||||
@ -45,56 +46,126 @@ export const useTalkStore = defineStore('talk', {
|
||||
},
|
||||
|
||||
// 更新对话节点
|
||||
updateItem(params: any) {
|
||||
async updateItem(params: any) {
|
||||
const item = this.items.find((item) => item.index_name === params.index_name)
|
||||
|
||||
item && Object.assign(item, params)
|
||||
if (item) {
|
||||
Object.assign(item, params)
|
||||
|
||||
// 同步更新本地数据库
|
||||
try {
|
||||
await addOrUpdateConversation(item)
|
||||
} catch (error) {
|
||||
console.error('更新本地会话失败:', error)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 新增对话节点
|
||||
addItem(params: any) {
|
||||
async addItem(params: any) {
|
||||
this.items = [params, ...this.items]
|
||||
|
||||
// 同步添加到本地数据库
|
||||
try {
|
||||
await addOrUpdateConversation(params)
|
||||
} catch (error) {
|
||||
console.error('添加本地会话失败:', error)
|
||||
}
|
||||
},
|
||||
|
||||
// 移除对话节点
|
||||
delItem(index_name: string) {
|
||||
async delItem(index_name: string) {
|
||||
const i = this.items.findIndex((item) => item.index_name === index_name)
|
||||
|
||||
if (i >= 0) {
|
||||
const item = this.items[i]
|
||||
this.items.splice(i, 1)
|
||||
|
||||
// 同步从本地数据库删除
|
||||
try {
|
||||
// 从本地数据库中查找并删除会话
|
||||
const [talkType, receiverId] = index_name.split('_')
|
||||
const conversation = await getConversation(Number(talkType), Number(receiverId))
|
||||
|
||||
if (conversation && conversation.id) {
|
||||
await deleteConversation(conversation.id, false) // 不删除相关消息
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除本地会话失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
this.items = [...this.items]
|
||||
},
|
||||
|
||||
// 更新对话消息
|
||||
updateMessage(params: any) {
|
||||
async updateMessage(params: any) {
|
||||
const item = this.items.find((item) => item.index_name === params.index_name)
|
||||
|
||||
if (item) {
|
||||
item.unread_num++
|
||||
item.msg_text = params.msg_text
|
||||
item.updated_at = params.updated_at
|
||||
|
||||
// 同步更新本地数据库中的会话信息
|
||||
try {
|
||||
await addOrUpdateConversation(item)
|
||||
} catch (error) {
|
||||
console.error('更新本地会话消息失败:', error)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 更新联系人备注
|
||||
setRemark(params: any) {
|
||||
async setRemark(params: any) {
|
||||
const item = this.items.find((item) => item.index_name === `1_${params.user_id}`)
|
||||
|
||||
item && (item.remark = params.remark)
|
||||
if (item) {
|
||||
item.remark = params.remark
|
||||
|
||||
// 同步更新本地数据库
|
||||
try {
|
||||
await addOrUpdateConversation(item)
|
||||
} catch (error) {
|
||||
console.error('更新本地联系人备注失败:', error)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 加载会话列表
|
||||
loadTalkList() {
|
||||
async loadTalkList() {
|
||||
this.loadStatus = 2
|
||||
|
||||
const resp = ServeGetTalkList()
|
||||
try {
|
||||
// 先从本地数据库加载会话列表
|
||||
const localConversations = await getConversations()
|
||||
if (localConversations && localConversations.length > 0) {
|
||||
// 将本地会话列表转换为应用所需格式
|
||||
this.items = localConversations.map((item: any) => {
|
||||
// 确保本地存储的会话格式与应用一致
|
||||
const value = formatTalkItem(item)
|
||||
|
||||
resp.then(({ code, data }) => {
|
||||
if (code == 200) {
|
||||
const draft = useEditorDraftStore().items[value.index_name]
|
||||
if (draft) {
|
||||
value.draft_text = JSON.parse(draft).text || ''
|
||||
}
|
||||
|
||||
this.items = data.items.map((item: any) => {
|
||||
if (value.is_robot == 1) {
|
||||
value.is_online = 1
|
||||
}
|
||||
return value
|
||||
})
|
||||
|
||||
// 设置为加载完成状态,因为已从本地加载了数据,不需要等待服务器数据就可以显示
|
||||
this.loadStatus = 3
|
||||
}
|
||||
|
||||
// 从服务器获取最新会话列表
|
||||
const resp = await ServeGetTalkList()
|
||||
|
||||
if (resp.code == 200) {
|
||||
// 将服务器返回的会话列表转换为应用所需格式
|
||||
const serverItems = resp.data.items.map((item: any) => {
|
||||
const value = formatTalkItem(item)
|
||||
|
||||
const draft = useEditorDraftStore().items[value.index_name]
|
||||
@ -108,22 +179,40 @@ export const useTalkStore = defineStore('talk', {
|
||||
return value
|
||||
})
|
||||
|
||||
// 更新状态和本地数据库
|
||||
this.items = serverItems
|
||||
|
||||
// 将最新的会话列表保存到本地数据库
|
||||
for (const item of serverItems) {
|
||||
await addOrUpdateConversation(item)
|
||||
}
|
||||
|
||||
this.loadStatus = 3
|
||||
} else {
|
||||
this.loadStatus = 4
|
||||
// 如果服务器请求失败但本地有数据,保持使用本地数据
|
||||
if (this.items.length === 0) {
|
||||
this.loadStatus = 4
|
||||
} else {
|
||||
this.loadStatus = 3
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
resp.catch(() => {
|
||||
this.loadStatus = 4
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('加载会话列表失败:', error)
|
||||
|
||||
// 如果有本地数据,即使服务器请求失败也显示本地数据
|
||||
if (this.items.length === 0) {
|
||||
this.loadStatus = 4
|
||||
} else {
|
||||
this.loadStatus = 3
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
findTalkIndex(index_name: string) {
|
||||
return this.items.findIndex((item: ISession) => item.index_name === index_name)
|
||||
},
|
||||
|
||||
toTalk(talk_type: number, receiver_id: number, router: any) {
|
||||
async toTalk(talk_type: number, receiver_id: number, router: any) {
|
||||
const route = {
|
||||
path: '/message',
|
||||
query: {
|
||||
@ -136,13 +225,31 @@ export const useTalkStore = defineStore('talk', {
|
||||
return router.push(route)
|
||||
}
|
||||
|
||||
ServeCreateTalkList({
|
||||
talk_type,
|
||||
receiver_id
|
||||
}).then(({ code, data, message }) => {
|
||||
if (code == 200) {
|
||||
try {
|
||||
// 先检查本地数据库中是否有该会话
|
||||
const localConversation = await getConversation(talk_type, receiver_id)
|
||||
|
||||
if (localConversation) {
|
||||
// 如果本地有该会话,直接添加到列表中
|
||||
if (this.findTalkIndex(`${talk_type}_${receiver_id}`) === -1) {
|
||||
this.addItem(formatTalkItem(data))
|
||||
this.addItem(formatTalkItem(localConversation))
|
||||
}
|
||||
|
||||
sessionStorage.setItem(KEY_INDEX_NAME, `${talk_type}_${receiver_id}`)
|
||||
return router.push(route)
|
||||
}
|
||||
|
||||
// 如果本地没有,则从服务器创建
|
||||
const { code, data, message } = await ServeCreateTalkList({
|
||||
talk_type,
|
||||
receiver_id
|
||||
})
|
||||
|
||||
if (code == 200) {
|
||||
const formattedItem = formatTalkItem(data)
|
||||
|
||||
if (this.findTalkIndex(`${talk_type}_${receiver_id}`) === -1) {
|
||||
await this.addItem(formattedItem) // 使用 await 确保本地数据库同步更新
|
||||
}
|
||||
|
||||
sessionStorage.setItem(KEY_INDEX_NAME, `${talk_type}_${receiver_id}`)
|
||||
@ -150,7 +257,10 @@ export const useTalkStore = defineStore('talk', {
|
||||
} else {
|
||||
window['$message'].info(message)
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('创建会话失败:', error)
|
||||
window['$message'].error('创建会话失败,请稍后再试')
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
766
src/utils/db.js
Normal file
766
src/utils/db.js
Normal file
@ -0,0 +1,766 @@
|
||||
// src/db.js
|
||||
import Dexie from 'dexie';
|
||||
|
||||
// 创建数据库实例
|
||||
export const db = new Dexie('chatHistory');
|
||||
|
||||
// 定义数据库结构
|
||||
db.version(2).stores({
|
||||
// 表名: 索引字段列表
|
||||
// ++id 表示自增主键
|
||||
// name, age 表示这些字段将被索引
|
||||
// friends: '++id, name, age',
|
||||
|
||||
// 聊天记录表
|
||||
// 为常用查询字段创建索引
|
||||
messages: 'msg_id, sequence, talk_type, msg_type, user_id, receiver_id, is_read, created_at',
|
||||
|
||||
// 会话表(包含私聊和群聊)
|
||||
// 为常用查询字段创建索引
|
||||
conversations: 'id, talk_type, receiver_id, index_name, updated_at, unread_num'
|
||||
});
|
||||
|
||||
// 数据库升级处理
|
||||
db.on('ready', function() {
|
||||
// 检查是否需要从服务器同步数据
|
||||
console.log('数据库已就绪,版本:', db.verno);
|
||||
});
|
||||
|
||||
/**
|
||||
* 消息类型常量
|
||||
*/
|
||||
export const MessageType = {
|
||||
TEXT: 1, // 文本消息
|
||||
IMAGE: 2, // 图片消息
|
||||
FILE: 3, // 文件消息
|
||||
AUDIO: 4, // 语音消息
|
||||
VIDEO: 5, // 视频消息
|
||||
LOCATION: 6, // 位置消息
|
||||
CARD: 7, // 名片消息
|
||||
};
|
||||
|
||||
/**
|
||||
* 会话类型常量
|
||||
*/
|
||||
export const TalkType = {
|
||||
PRIVATE: 1, // 私聊
|
||||
GROUP: 2, // 群聊
|
||||
};
|
||||
|
||||
/**
|
||||
* 添加一条聊天记录
|
||||
* @param {Object} message - 消息对象
|
||||
* @returns {Promise<string>} - 返回消息ID
|
||||
*/
|
||||
export async function addMessage(message) {
|
||||
try {
|
||||
// 确保消息有唯一ID
|
||||
if (!message.msg_id) {
|
||||
message.msg_id = generateUUID();
|
||||
}
|
||||
|
||||
// 添加时间戳(如果没有)
|
||||
if (!message.created_at) {
|
||||
message.created_at = new Date().toISOString().replace('T', ' ').substring(0, 19);
|
||||
}
|
||||
|
||||
// 添加到数据库
|
||||
const id = await db.messages.add(message);
|
||||
return message.msg_id;
|
||||
} catch (error) {
|
||||
console.error('添加消息失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量添加或更新聊天记录
|
||||
* @param {Array} messages - 消息对象数组
|
||||
* @param {number} talkType - 会话类型(1:私聊, 2:群聊)
|
||||
* @param {number} receiverId - 接收者ID(私聊为对方用户ID,群聊为群ID)
|
||||
* @param {boolean} replaceExisting - 是否替换已存在的消息
|
||||
* @param {string} sortKey - 排序和比较的键,可选值:'sequence'或'created_at'
|
||||
* @returns {Promise<{added: number, updated: number, total: number}>} - 返回添加和更新的消息数量
|
||||
*/
|
||||
export async function batchAddOrUpdateMessages(messages, talkType, receiverId, replaceExisting = true, sortKey = 'sequence') {
|
||||
try {
|
||||
if (!Array.isArray(messages) || messages.length === 0) {
|
||||
return { added: 0, updated: 0, total: 0 };
|
||||
}
|
||||
|
||||
// 获取现有消息
|
||||
let existingMessages = [];
|
||||
if (replaceExisting) {
|
||||
// 不传入 maxSequence 参数,获取所有消息
|
||||
existingMessages = await getMessages(talkType, null, receiverId, 1000, 0);
|
||||
}
|
||||
|
||||
// 创建msg_id到消息的映射,用于快速查找
|
||||
const existingMap = new Map();
|
||||
existingMessages.forEach(msg => {
|
||||
existingMap.set(msg.msg_id, msg);
|
||||
});
|
||||
|
||||
// 创建sequence/created_at到消息的映射,用于比较和替换
|
||||
const existingKeyMap = new Map();
|
||||
existingMessages.forEach(msg => {
|
||||
if (msg[sortKey]) {
|
||||
existingKeyMap.set(msg[sortKey], msg);
|
||||
}
|
||||
});
|
||||
|
||||
let added = 0;
|
||||
let updated = 0;
|
||||
|
||||
// 批量处理事务
|
||||
await db.transaction('rw', db.messages, async () => {
|
||||
for (const message of messages) {
|
||||
// 确保消息有必要的字段
|
||||
if (!message.msg_id) {
|
||||
message.msg_id = generateUUID();
|
||||
}
|
||||
|
||||
if (!message.created_at) {
|
||||
message.created_at = new Date().toISOString().replace('T', ' ').substring(0, 19);
|
||||
}
|
||||
|
||||
// 检查消息是否已存在
|
||||
const existing = existingMap.get(message.msg_id);
|
||||
if (existing) {
|
||||
// 更新现有消息,使用 msg_id 而不是 id
|
||||
await db.messages.update(existing.msg_id, message);
|
||||
updated++;
|
||||
} else if (replaceExisting && message[sortKey] && existingKeyMap.has(message[sortKey])) {
|
||||
// 根据sortKey替换现有消息
|
||||
const existingByKey = existingKeyMap.get(message[sortKey]);
|
||||
await db.messages.update(existingByKey.msg_id, message);
|
||||
updated++;
|
||||
} else {
|
||||
console.log('message',message)
|
||||
// 添加新消息
|
||||
await db.messages.add(message);
|
||||
added++;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 如果有新消息添加,更新会话的最后一条消息
|
||||
if (added > 0 || updated > 0) {
|
||||
// 假设消息数组中的最后一条消息是最新的(保持原始顺序)
|
||||
// 如果需要找出最新消息,可以使用数组中的最后一条消息
|
||||
const latestMessage = messages[messages.length - 1];
|
||||
|
||||
// 更新会话的最后一条消息
|
||||
await updateConversationLastMessage(latestMessage);
|
||||
}
|
||||
|
||||
return { added, updated, total: added + updated };
|
||||
} catch (error) {
|
||||
console.error('批量添加或更新消息失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定会话的聊天记录
|
||||
* @param {number} talkType - 会话类型(1:私聊, 2:群聊)
|
||||
* @param {number} userId - 当前用户ID
|
||||
* @param {number} receiverId - 接收者ID(私聊为对方用户ID,群聊为群ID)
|
||||
* @param {number} limit - 限制返回的记录数量
|
||||
* @param {number} offset - 偏移量,用于分页
|
||||
* @returns {Promise<Array>} - 返回消息列表
|
||||
*/
|
||||
/**
|
||||
* 获取消息列表
|
||||
* @param {number} talkType - 会话类型(1:私聊, 2:群聊)
|
||||
* @param {number} userId - 用户ID(私聊时用于区分消息方向)
|
||||
* @param {number} receiverId - 接收者ID(私聊时为对方ID,群聊时为群ID)
|
||||
* @param {number} limit - 每页条数
|
||||
* @param {number} offset - 偏移量
|
||||
* @param {number} maxSequence - 最大sequence值,用于分页加载更早的消息
|
||||
* @returns {Promise<Array>} - 消息列表
|
||||
*/
|
||||
export async function getMessages(talkType, userId, receiverId, limit = 30, offset = 0, maxSequence = null) {
|
||||
try {
|
||||
let query;
|
||||
|
||||
if (talkType === TalkType.PRIVATE) {
|
||||
// 私聊消息(双向获取)
|
||||
query = db.messages
|
||||
.where('talk_type').equals(TalkType.PRIVATE)
|
||||
.and(item => {
|
||||
return (item.user_id === userId && item.receiver_id === receiverId) ||
|
||||
(item.user_id === receiverId && item.receiver_id === userId);
|
||||
});
|
||||
} else {
|
||||
// 群聊消息
|
||||
query = db.messages
|
||||
.where('talk_type').equals(TalkType.GROUP)
|
||||
.and(item => item.receiver_id === receiverId);
|
||||
}
|
||||
|
||||
// 如果提供了maxSequence,则只获取sequence小于该值的消息
|
||||
if (maxSequence !== null) {
|
||||
query = query.and(item => item.sequence < maxSequence);
|
||||
}
|
||||
|
||||
// 按sequence升序排序
|
||||
let messages = await query
|
||||
.sortBy('sequence');
|
||||
|
||||
// 获取尾部的limit条记录(最新的消息)
|
||||
if (messages.length > limit) {
|
||||
messages = messages.slice(Math.max(0, messages.length - limit));
|
||||
}
|
||||
|
||||
return messages;
|
||||
} catch (error) {
|
||||
console.error('获取消息失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记消息为已读
|
||||
* @param {string} msgId - 消息ID
|
||||
* @returns {Promise<boolean>} - 操作是否成功
|
||||
*/
|
||||
export async function markMessageAsRead(msgId) {
|
||||
try {
|
||||
await db.messages.update(msgId, { is_read: 1 });
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('标记消息已读失败:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量标记消息为已读
|
||||
* @param {number} talkType - 会话类型
|
||||
* @param {number} userId - 当前用户ID
|
||||
* @param {number} receiverId - 接收者ID
|
||||
* @returns {Promise<number>} - 返回更新的消息数量
|
||||
*/
|
||||
export async function markMessagesAsRead(talkType, userId, receiverId) {
|
||||
try {
|
||||
let count;
|
||||
|
||||
if (talkType === TalkType.PRIVATE) {
|
||||
// 私聊消息(只标记对方发给我的消息)
|
||||
count = await db.messages
|
||||
.where('talk_type').equals(TalkType.PRIVATE)
|
||||
.and(item => item.user_id === receiverId && item.receiver_id === userId)
|
||||
.and(item => item.is_read === 0)
|
||||
.modify({ is_read: 1 });
|
||||
} else {
|
||||
// 群聊消息(标记该群的所有未读消息)
|
||||
count = await db.messages
|
||||
.where('talk_type').equals(TalkType.GROUP)
|
||||
.and(item => item.receiver_id === receiverId)
|
||||
.and(item => item.is_read === 0 && item.user_id !== userId)
|
||||
.modify({ is_read: 1 });
|
||||
}
|
||||
|
||||
return count;
|
||||
} catch (error) {
|
||||
console.error('批量标记消息已读失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 撤回消息
|
||||
* @param {string} msgId - 消息ID
|
||||
* @returns {Promise<boolean>} - 操作是否成功
|
||||
*/
|
||||
export async function revokeMessage(msgId) {
|
||||
try {
|
||||
await db.messages.update(msgId, { is_revoke: 1 });
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('撤回消息失败:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除消息
|
||||
* @param {string} msgId - 消息ID
|
||||
* @returns {Promise<boolean>} - 操作是否成功
|
||||
*/
|
||||
export async function deleteMessage(msgId) {
|
||||
try {
|
||||
await db.messages.delete(msgId);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('删除消息失败:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取未读消息数量
|
||||
* @param {number} userId - 当前用户ID
|
||||
* @returns {Promise<Object>} - 返回未读消息统计信息
|
||||
*/
|
||||
export async function getUnreadCount(userId) {
|
||||
try {
|
||||
// 私聊未读消息数量
|
||||
const privateUnread = await db.messages
|
||||
.where('talk_type').equals(TalkType.PRIVATE)
|
||||
.and(item => item.receiver_id === userId && item.is_read === 0)
|
||||
.count();
|
||||
|
||||
// 群聊未读消息数量
|
||||
const groupUnread = await db.messages
|
||||
.where('talk_type').equals(TalkType.GROUP)
|
||||
.and(item => item.user_id !== userId && item.is_read === 0)
|
||||
.count();
|
||||
|
||||
// 按会话分组的未读消息数量
|
||||
const privateUnreadBySession = await db.messages
|
||||
.where('talk_type').equals(TalkType.PRIVATE)
|
||||
.and(item => item.receiver_id === userId && item.is_read === 0)
|
||||
.toArray()
|
||||
.then(messages => {
|
||||
const result = {};
|
||||
messages.forEach(msg => {
|
||||
if (!result[msg.user_id]) {
|
||||
result[msg.user_id] = 0;
|
||||
}
|
||||
result[msg.user_id]++;
|
||||
});
|
||||
return result;
|
||||
});
|
||||
|
||||
// 按群组分组的未读消息数量
|
||||
const groupUnreadBySession = await db.messages
|
||||
.where('talk_type').equals(TalkType.GROUP)
|
||||
.and(item => item.user_id !== userId && item.is_read === 0)
|
||||
.toArray()
|
||||
.then(messages => {
|
||||
const result = {};
|
||||
messages.forEach(msg => {
|
||||
if (!result[msg.receiver_id]) {
|
||||
result[msg.receiver_id] = 0;
|
||||
}
|
||||
result[msg.receiver_id]++;
|
||||
});
|
||||
return result;
|
||||
});
|
||||
|
||||
return {
|
||||
total: privateUnread + groupUnread,
|
||||
private: privateUnread,
|
||||
group: groupUnread,
|
||||
privateBySession: privateUnreadBySession,
|
||||
groupBySession: groupUnreadBySession
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('获取未读消息数量失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成UUID
|
||||
* @returns {string} - 返回UUID
|
||||
*/
|
||||
function generateUUID() {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||||
const r = Math.random() * 16 | 0;
|
||||
const v = c === 'x' ? r : (r & 0x3 | 0x8);
|
||||
return v.toString(16);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加或更新会话
|
||||
* @param {Object} conversation - 会话对象
|
||||
* @returns {Promise<number>} - 返回会话ID
|
||||
*/
|
||||
export async function addOrUpdateConversation(conversation) {
|
||||
try {
|
||||
// 检查会话是否已存在
|
||||
const existingConversation = await db.conversations
|
||||
.where('index_name')
|
||||
.equals(conversation.index_name)
|
||||
.first();
|
||||
|
||||
if (existingConversation) {
|
||||
// 更新现有会话
|
||||
await db.conversations.update(existingConversation.id, conversation);
|
||||
return existingConversation.id;
|
||||
} else {
|
||||
// 添加新会话
|
||||
const id = await db.conversations.add(conversation);
|
||||
return id;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('添加或更新会话失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有会话列表
|
||||
* @param {boolean} includeEmpty - 是否包含没有消息的会话
|
||||
* @returns {Promise<Array>} - 返回会话列表,按更新时间倒序排列
|
||||
*/
|
||||
export async function getConversations(includeEmpty = false) {
|
||||
try {
|
||||
let query = db.conversations;
|
||||
|
||||
if (!includeEmpty) {
|
||||
// 只获取有消息的会话
|
||||
query = query.filter(item => item.msg_text && item.msg_text.length > 0);
|
||||
}
|
||||
|
||||
// 按更新时间倒序排列
|
||||
const conversations = await query
|
||||
.toArray()
|
||||
.then(items => items.sort((a, b) => {
|
||||
// 置顶的会话优先
|
||||
if (a.is_top !== b.is_top) return b.is_top - a.is_top;
|
||||
// 然后按更新时间排序
|
||||
return new Date(b.updated_at) - new Date(a.updated_at);
|
||||
}));
|
||||
|
||||
return conversations;
|
||||
} catch (error) {
|
||||
console.error('获取会话列表失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定会话
|
||||
* @param {number} talkType - 会话类型(1:私聊, 2:群聊)
|
||||
* @param {number} receiverId - 接收者ID(私聊为对方用户ID,群聊为群ID)
|
||||
* @returns {Promise<Object|null>} - 返回会话对象,如果不存在则返回null
|
||||
*/
|
||||
export async function getConversation(talkType, receiverId) {
|
||||
try {
|
||||
const indexName = `${talkType}_${receiverId}`;
|
||||
const conversation = await db.conversations
|
||||
.where('index_name')
|
||||
.equals(indexName)
|
||||
.first();
|
||||
|
||||
return conversation || null;
|
||||
} catch (error) {
|
||||
console.error('获取会话失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新会话的未读消息数
|
||||
* @param {number} talkType - 会话类型
|
||||
* @param {number} receiverId - 接收者ID
|
||||
* @param {number} unreadNum - 未读消息数量,如果为null则增加1
|
||||
* @returns {Promise<boolean>} - 操作是否成功
|
||||
*/
|
||||
export async function updateConversationUnreadNum(talkType, receiverId, unreadNum = null) {
|
||||
try {
|
||||
const indexName = `${talkType}_${receiverId}`;
|
||||
const conversation = await db.conversations
|
||||
.where('index_name')
|
||||
.equals(indexName)
|
||||
.first();
|
||||
|
||||
if (conversation) {
|
||||
if (unreadNum === null) {
|
||||
// 增加未读消息数
|
||||
unreadNum = (conversation.unread_num || 0) + 1;
|
||||
}
|
||||
|
||||
await db.conversations.update(conversation.id, { unread_num: unreadNum });
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch (error) {
|
||||
console.error('更新会话未读消息数失败:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空会话的未读消息数
|
||||
* @param {number} talkType - 会话类型
|
||||
* @param {number} receiverId - 接收者ID
|
||||
* @returns {Promise<boolean>} - 操作是否成功
|
||||
*/
|
||||
export async function clearConversationUnreadNum(talkType, receiverId) {
|
||||
return updateConversationUnreadNum(talkType, receiverId, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除会话
|
||||
* @param {number} conversationId - 会话ID
|
||||
* @param {boolean} deleteMessages - 是否同时删除相关的消息记录
|
||||
* @returns {Promise<boolean>} - 操作是否成功
|
||||
*/
|
||||
export async function deleteConversation(conversationId, deleteMessages = false) {
|
||||
try {
|
||||
const conversation = await db.conversations.get(conversationId);
|
||||
|
||||
if (!conversation) return false;
|
||||
|
||||
// 删除会话
|
||||
await db.conversations.delete(conversationId);
|
||||
|
||||
// 如果需要,同时删除相关的消息记录
|
||||
if (deleteMessages) {
|
||||
const { talk_type, receiver_id } = conversation;
|
||||
|
||||
if (talk_type === TalkType.PRIVATE) {
|
||||
// 删除私聊消息
|
||||
await db.messages
|
||||
.where('talk_type').equals(TalkType.PRIVATE)
|
||||
.and(item => {
|
||||
return (item.user_id === receiver_id || item.receiver_id === receiver_id);
|
||||
})
|
||||
.delete();
|
||||
} else {
|
||||
// 删除群聊消息
|
||||
await db.messages
|
||||
.where('talk_type').equals(TalkType.GROUP)
|
||||
.and(item => item.receiver_id === receiver_id)
|
||||
.delete();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('删除会话失败:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新会话的最后一条消息
|
||||
* @param {Object} message - 消息对象
|
||||
* @returns {Promise<boolean>} - 操作是否成功
|
||||
*/
|
||||
export async function updateConversationLastMessage(message) {
|
||||
try {
|
||||
const { talk_type, user_id, receiver_id, msg_type } = message;
|
||||
let targetReceiverId;
|
||||
|
||||
if (talk_type === TalkType.PRIVATE) {
|
||||
// 私聊:对方ID作为会话的receiver_id
|
||||
targetReceiverId = user_id === receiver_id ? user_id : receiver_id;
|
||||
} else {
|
||||
// 群聊:群ID作为会话的receiver_id
|
||||
targetReceiverId = receiver_id;
|
||||
}
|
||||
|
||||
const indexName = `${talk_type}_${targetReceiverId}`;
|
||||
const conversation = await db.conversations
|
||||
.where('index_name')
|
||||
.equals(indexName)
|
||||
.first();
|
||||
|
||||
if (!conversation) return false;
|
||||
|
||||
// 根据消息类型生成显示文本
|
||||
let msgText = '';
|
||||
switch (msg_type) {
|
||||
case MessageType.TEXT:
|
||||
msgText = message.content || '';
|
||||
break;
|
||||
case MessageType.IMAGE:
|
||||
msgText = '[图片消息]';
|
||||
break;
|
||||
case MessageType.FILE:
|
||||
msgText = '[文件消息]';
|
||||
break;
|
||||
case MessageType.AUDIO:
|
||||
msgText = '[语音消息]';
|
||||
break;
|
||||
case MessageType.VIDEO:
|
||||
msgText = '[视频消息]';
|
||||
break;
|
||||
case MessageType.LOCATION:
|
||||
msgText = '[位置消息]';
|
||||
break;
|
||||
case MessageType.CARD:
|
||||
msgText = '[名片消息]';
|
||||
break;
|
||||
default:
|
||||
msgText = '[未知类型消息]';
|
||||
}
|
||||
|
||||
// 更新会话的最后消息和时间
|
||||
await db.conversations.update(conversation.id, {
|
||||
msg_text: msgText,
|
||||
content: message.content || '',
|
||||
updated_at: message.created_at
|
||||
});
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('更新会话最后消息失败:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例:如何使用聊天记录数据库
|
||||
*
|
||||
* // 1. 添加一条消息
|
||||
* const newMessage = {
|
||||
* msg_id: '5a266eb831594b2b8be7c9fdf34986df',
|
||||
* sequence: 184,
|
||||
* talk_type: 1, // 私聊
|
||||
* msg_type: 4, // 语音消息
|
||||
* user_id: 1496,
|
||||
* receiver_id: 1774,
|
||||
* nickname: '王一峰',
|
||||
* avatar: 'https://cdn-test.szjixun.cn/fonchain-main/dev/image/4692/oa/36b09c00-c2c3-453d-9aaf-ac41d47d564b.png',
|
||||
* is_revoke: 0,
|
||||
* is_mark: 0,
|
||||
* is_read: 0,
|
||||
* created_at: '2025-06-30 09:11:14',
|
||||
* extra: {
|
||||
* duration: 0,
|
||||
* name: '',
|
||||
* size: 15984,
|
||||
* url: 'https://cdn-test.szjixun.cn/fonchain-main/test/file/default/fonchain-chat/2b595dbc-6a2b-498e-802a-880e93d1e45a.mp3'
|
||||
* },
|
||||
* erp_user_id: 4692
|
||||
* };
|
||||
*
|
||||
* addMessage(newMessage).then(msgId => {
|
||||
* console.log('消息添加成功,ID:', msgId);
|
||||
* });
|
||||
*
|
||||
* // 2. 获取私聊消息列表
|
||||
* getMessages(TalkType.PRIVATE, 1496, 1774, 20, 0).then(messages => {
|
||||
* console.log('获取到20条私聊消息:', messages);
|
||||
* });
|
||||
*
|
||||
* // 获取特定sequence之前的消息
|
||||
* getMessages(TalkType.PRIVATE, 1496, 1774, 20, 0, 1000).then(messages => {
|
||||
* console.log('获取sequence小于1000的20条私聊消息:', messages);
|
||||
* });
|
||||
*
|
||||
* // 3. 标记消息为已读
|
||||
* markMessageAsRead('5a266eb831594b2b8be7c9fdf34986df').then(success => {
|
||||
* console.log('标记消息已读:', success ? '成功' : '失败');
|
||||
* });
|
||||
*
|
||||
* // 4. 批量标记会话消息为已读
|
||||
* markMessagesAsRead(TalkType.PRIVATE, 1496, 1774).then(count => {
|
||||
* console.log(`成功标记 ${count} 条消息为已读`);
|
||||
* });
|
||||
*
|
||||
* // 5. 撤回消息
|
||||
* revokeMessage('5a266eb831594b2b8be7c9fdf34986df').then(success => {
|
||||
* console.log('撤回消息:', success ? '成功' : '失败');
|
||||
* });
|
||||
*
|
||||
* // 6. 获取未读消息数量
|
||||
* getUnreadCount(1496).then(result => {
|
||||
* console.log('未读消息统计:', result);
|
||||
* });
|
||||
*
|
||||
* // 7. 添加或更新会话
|
||||
* const newConversation = {
|
||||
* talk_type: 1, // 私聊
|
||||
* receiver_id: 1774,
|
||||
* name: "周俊耀",
|
||||
* remark: "",
|
||||
* avatar: "https://e-cdn.fontree.cn/fonchain-main/prod/image/18248/avatar/a0b2bee7-947f-465a-986e-10a1b2b87032.png",
|
||||
* is_disturb: 0,
|
||||
* is_top: 1,
|
||||
* is_online: 0,
|
||||
* is_robot: 0,
|
||||
* is_dismiss: 0,
|
||||
* is_quit: 0,
|
||||
* unread_num: 0,
|
||||
* content: "......",
|
||||
* draft_text: "",
|
||||
* msg_text: "[语音消息]",
|
||||
* index_name: "1_1774",
|
||||
* updated_at: "2025-06-30 10:27:27",
|
||||
* atsign_num: 0,
|
||||
* erp_user_id: 3346,
|
||||
* group_member_num: 0,
|
||||
* group_type: 0
|
||||
* };
|
||||
*
|
||||
* addOrUpdateConversation(newConversation).then(id => {
|
||||
* console.log('会话添加或更新成功,ID:', id);
|
||||
* });
|
||||
*
|
||||
* // 8. 获取所有会话列表
|
||||
* getConversations().then(conversations => {
|
||||
* console.log('获取所有会话列表:', conversations);
|
||||
* });
|
||||
*
|
||||
* // 9. 获取指定会话
|
||||
* getConversation(TalkType.PRIVATE, 1774).then(conversation => {
|
||||
* console.log('获取指定会话:', conversation);
|
||||
* });
|
||||
*
|
||||
* // 10. 更新会话未读消息数
|
||||
* updateConversationUnreadNum(TalkType.PRIVATE, 1774, 5).then(success => {
|
||||
* console.log('更新会话未读消息数:', success ? '成功' : '失败');
|
||||
* });
|
||||
*
|
||||
* // 11. 清空会话未读消息数
|
||||
* clearConversationUnreadNum(TalkType.PRIVATE, 1774).then(success => {
|
||||
* console.log('清空会话未读消息数:', success ? '成功' : '失败');
|
||||
* });
|
||||
*
|
||||
* // 12. 更新会话最后一条消息
|
||||
* updateConversationLastMessage(newMessage).then(success => {
|
||||
* console.log('更新会话最后一条消息:', success ? '成功' : '失败');
|
||||
* });
|
||||
*
|
||||
* // 13. 删除会话
|
||||
* deleteConversation(5811, false).then(success => {
|
||||
* console.log('删除会话:', success ? '成功' : '失败');
|
||||
* });
|
||||
*
|
||||
* // 14. 批量添加或更新聊天记录
|
||||
* const messagesFromServer = [
|
||||
* {
|
||||
* msg_id: 'server-msg-id-1',
|
||||
* talk_type: TalkType.PRIVATE,
|
||||
* msg_type: MessageType.TEXT,
|
||||
* user_id: 1000,
|
||||
* receiver_id: 2000,
|
||||
* content: '你好,这是第一条消息',
|
||||
* is_read: 1,
|
||||
* sequence: 1,
|
||||
* created_at: '2023-05-01 10:00:00'
|
||||
* },
|
||||
* {
|
||||
* msg_id: 'server-msg-id-2',
|
||||
* talk_type: TalkType.PRIVATE,
|
||||
* msg_type: MessageType.TEXT,
|
||||
* user_id: 2000,
|
||||
* receiver_id: 1000,
|
||||
* content: '你好,这是回复消息',
|
||||
* is_read: 0,
|
||||
* sequence: 2,
|
||||
* created_at: '2023-05-01 10:01:00'
|
||||
* }
|
||||
* ];
|
||||
*
|
||||
* // 使用sequence作为排序和替换的键
|
||||
* batchAddOrUpdateMessages(
|
||||
* messagesFromServer,
|
||||
* TalkType.PRIVATE,
|
||||
* 2000, // 接收者ID(私聊为对方用户ID,群聊为群ID)
|
||||
* true, // 替换已存在的消息
|
||||
* 'sequence' // 使用sequence作为排序和替换的键
|
||||
* ).then(result => {
|
||||
* console.log(`添加了${result.added}条新消息,更新了${result.updated}条消息`);
|
||||
* });
|
||||
*/
|
@ -145,7 +145,6 @@ watch(
|
||||
if (talkParams.type !== 2) {
|
||||
ServeCheckFriend({ receiver_id: newValue.receiver_id, talk_type: 1 }).then((res) => {
|
||||
if (res?.code === 200) {
|
||||
console.log(res, 'ress')
|
||||
isFriend.value = res.data.is_friend
|
||||
}
|
||||
})
|
||||
|
@ -519,6 +519,7 @@ const items = computed((): ISession[] => {
|
||||
|
||||
return [...topItems, ...normalItems]
|
||||
})
|
||||
setTimeout(()=>{console.log('items',items)},2000)
|
||||
watch(
|
||||
() => state.addressBookSearchNickName,
|
||||
(newValue, oldValue) => {
|
||||
@ -592,6 +593,8 @@ const indexName = computed(() => dialogueStore.index_name)
|
||||
|
||||
// 切换会话
|
||||
const onTabTalk = (item: ISession, follow = false) => {
|
||||
console.log('onTabTalk')
|
||||
console.log('item.index_name === indexName.value',item.index_name === indexName.value)
|
||||
if (item.index_name === indexName.value) return
|
||||
|
||||
searchKeyword.value = ''
|
||||
|
@ -372,6 +372,7 @@ let noRefreshTimer: number | null = null
|
||||
watch(
|
||||
() => props,
|
||||
async (newProps) => {
|
||||
console.log('监听props')
|
||||
await nextTick()
|
||||
// 生成当前会话的唯一标识
|
||||
const newSessionKey = `${newProps.talk_type}_${newProps.receiver_id}`
|
||||
@ -414,7 +415,6 @@ watch(
|
||||
}, 3000)
|
||||
return
|
||||
}
|
||||
console.log('执行逻辑')
|
||||
onLoad(
|
||||
{
|
||||
receiver_id: newProps.receiver_id,
|
||||
@ -424,7 +424,7 @@ watch(
|
||||
specialParams ? { specifiedMsg: specialParams } : undefined
|
||||
)
|
||||
},
|
||||
{ deep: true }
|
||||
{ deep: true,immediate:true }
|
||||
)
|
||||
|
||||
// onMounted(() => {
|
||||
@ -609,25 +609,11 @@ const handleIntersection = (entries) => {
|
||||
watch(
|
||||
() => records.value,
|
||||
() => {
|
||||
console.log()
|
||||
nextTick(() => {
|
||||
// 断开旧的观察者
|
||||
if (observer) {
|
||||
observer.disconnect()
|
||||
}
|
||||
// 创建原数组的副本进行遍历
|
||||
// const recordsCopy = [...dialogueStore.records];
|
||||
// for (const [y, iy] of dialogueStore.globalUploadList.entries()) {
|
||||
// console.log('y',y)
|
||||
// console.log('iy',iy)
|
||||
// for (const [x, ix] of recordsCopy.entries()) {
|
||||
// if(x.msg_id === y.pre_msg){
|
||||
// // 注意:这里的ix是原数组的索引,需要考虑已插入元素的偏移
|
||||
// dialogueStore.records.splice(ix + 1 + (dialogueStore.records.length - recordsCopy.length), 0, y);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// console.log('dialogueStore.records',dialogueStore.records)
|
||||
// 重新初始化观察者
|
||||
const options = {
|
||||
root: null,
|
||||
@ -802,7 +788,7 @@ const onCustomSkipBottomEvent = () => {
|
||||
<div class="load-toolbar pointer">
|
||||
<span v-if="loadConfig.status == 0"> 正在加载数据中 ... </span>
|
||||
<span v-else-if="loadConfig.status == 1" @click="onRefreshLoad"> 查看更多消息 ... </span>
|
||||
<span v-else class="no-more"> 没有更多消息了 </span>
|
||||
<span v-else-if="loadConfig.status == 2 || loadConfig.status == 3" class="no-more"> 没有更多消息了 </span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
|
@ -119,7 +119,7 @@ const onSendVideoEvent = async ({ data }) => {
|
||||
? dialogueStore.records[dialogueStore.records.length-1].sequence
|
||||
: 0,
|
||||
sequence: Date.now(),
|
||||
talk_type: props.talk_type,
|
||||
talk_type: props.talk_type,
|
||||
msg_type: 5, // 视频消息类型
|
||||
user_id: props.uid,
|
||||
receiver_id: props.receiver_id,
|
||||
@ -154,8 +154,7 @@ const onSendVideoEvent = async ({ data }) => {
|
||||
dialogueStore.updateUploadProgress(uploadId, percentage)
|
||||
},
|
||||
async () => {
|
||||
// 上传完成后,上传任务已经被removeUploadTask方法移除
|
||||
// 不需要再次从globalUploadList中移除
|
||||
dialogueStore.batchDelDialogueRecord([uploadId])
|
||||
}
|
||||
)
|
||||
}
|
||||
@ -167,10 +166,6 @@ const onSendCodeEvent = ({ data, callBack }) => {
|
||||
|
||||
// 发送文件消息
|
||||
const onSendFileEvent = ({ data }) => {
|
||||
let maxsize = 200 * 1024 * 1024
|
||||
if (data.size > maxsize) {
|
||||
return window['$message'].warning('上传文件不能超过100M!')
|
||||
}
|
||||
const clientUploadId = `file-${Date.now()}-${Math.floor(Math.random() * 1000)}`
|
||||
const tempMessage = {
|
||||
msg_id: clientUploadId,
|
||||
|
Loading…
Reference in New Issue
Block a user