feat(chat): 实现聊天记录本地存储功能

添加Dexie.js作为本地数据库,实现聊天记录和会话的本地存储与同步
修改消息和会话相关store方法,支持本地数据库操作
优化消息加载逻辑,优先从本地加载再同步服务器数据
添加数据库工具函数,包括消息增删改查和会话管理功能
This commit is contained in:
Phoenix 2025-06-30 16:00:06 +08:00
parent df372ad14e
commit 4863b4c77c
10 changed files with 1106 additions and 69 deletions

View File

@ -24,9 +24,11 @@
"@vicons/ionicons5": "^0.13.0", "@vicons/ionicons5": "^0.13.0",
"@vueup/vue-quill": "^1.2.0", "@vueup/vue-quill": "^1.2.0",
"@vueuse/core": "^10.7.0", "@vueuse/core": "^10.7.0",
"@vueuse/rxjs": "^13.4.0",
"ant-design-vue": "^4.2.6", "ant-design-vue": "^4.2.6",
"axios": "^1.6.2", "axios": "^1.6.2",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"dexie": "^4.0.11",
"highlight.js": "^11.5.0", "highlight.js": "^11.5.0",
"js-audio-recorder": "^1.0.7", "js-audio-recorder": "^1.0.7",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
@ -36,6 +38,7 @@
"quill": "^1.3.7", "quill": "^1.3.7",
"quill-image-uploader": "^1.3.0", "quill-image-uploader": "^1.3.0",
"quill-mention": "^4.1.0", "quill-mention": "^4.1.0",
"rxjs": "^7.8.2",
"sortablejs": "^1.15.6", "sortablejs": "^1.15.6",
"viewerjs": "^1.11.7", "viewerjs": "^1.11.7",
"vue": "^3.3.11", "vue": "^3.3.11",

View File

@ -38,6 +38,9 @@ importers:
'@vueuse/core': '@vueuse/core':
specifier: ^10.7.0 specifier: ^10.7.0
version: 10.11.1(vue@3.5.17(typescript@5.2.2)) 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: ant-design-vue:
specifier: ^4.2.6 specifier: ^4.2.6
version: 4.2.6(vue@3.5.17(typescript@5.2.2)) version: 4.2.6(vue@3.5.17(typescript@5.2.2))
@ -47,6 +50,9 @@ importers:
dayjs: dayjs:
specifier: ^1.11.13 specifier: ^1.11.13
version: 1.11.13 version: 1.11.13
dexie:
specifier: ^4.0.11
version: 4.0.11
highlight.js: highlight.js:
specifier: ^11.5.0 specifier: ^11.5.0
version: 11.11.1 version: 11.11.1
@ -74,6 +80,9 @@ importers:
quill-mention: quill-mention:
specifier: ^4.1.0 specifier: ^4.1.0
version: 4.1.0 version: 4.1.0
rxjs:
specifier: ^7.8.2
version: 7.8.2
sortablejs: sortablejs:
specifier: ^1.15.6 specifier: ^1.15.6
version: 1.15.6 version: 1.15.6
@ -1121,9 +1130,20 @@ packages:
'@vueuse/metadata@10.11.1': '@vueuse/metadata@10.11.1':
resolution: {integrity: sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==} 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': '@vueuse/shared@10.11.1':
resolution: {integrity: sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==} 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': '@webassemblyjs/ast@1.14.1':
resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==} resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==}
@ -1755,6 +1775,9 @@ packages:
engines: {node: '>=0.10'} engines: {node: '>=0.10'}
hasBin: true hasBin: true
dexie@4.0.11:
resolution: {integrity: sha512-SOKO002EqlvBYYKQSew3iymBoN2EQ4BDw/3yprjh7kAfFzjBYkaMNa/pZvcA7HSWlcKSQb9XhPe3wKyQ0x4A8A==}
diff@5.2.0: diff@5.2.0:
resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==} resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==}
engines: {node: '>=0.3.1'} engines: {node: '>=0.3.1'}
@ -4616,6 +4639,12 @@ snapshots:
'@vueuse/metadata@10.11.1': {} '@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))': '@vueuse/shared@10.11.1(vue@3.5.17(typescript@5.2.2))':
dependencies: dependencies:
vue-demi: 0.14.10(vue@3.5.17(typescript@5.2.2)) vue-demi: 0.14.10(vue@3.5.17(typescript@5.2.2))
@ -4623,6 +4652,10 @@ snapshots:
- '@vue/composition-api' - '@vue/composition-api'
- vue - 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': '@webassemblyjs/ast@1.14.1':
dependencies: dependencies:
'@webassemblyjs/helper-numbers': 1.13.2 '@webassemblyjs/helper-numbers': 1.13.2
@ -5329,6 +5362,8 @@ snapshots:
detect-libc@1.0.3: detect-libc@1.0.3:
optional: true optional: true
dexie@4.0.11: {}
diff@5.2.0: {} diff@5.2.0: {}
dir-glob@2.2.2: dir-glob@2.2.2:

View File

@ -130,19 +130,19 @@ export const useTalkRecord = (uid: number) => {
cursor: loadConfig.cursor, cursor: loadConfig.cursor,
limit: 30 limit: 30
} }
// 如果不是从本地数据库加载的则设置加载状态为0加载中
loadConfig.status = 0 if (loadConfig.status !== 2 && loadConfig.status !== 3) {
loadConfig.status = 0
}
let scrollHeight = 0 let scrollHeight = 0
console.log('加载数据列表load')
const el = document.getElementById('imChatPanel') const el = document.getElementById('imChatPanel')
if (el) { if (el) {
scrollHeight = el.scrollHeight scrollHeight = el.scrollHeight
} }
const { data, code } = await ServeTalkRecords(request) const { data, code } = await ServeTalkRecords(request)
if (code != 200) { if (code != 200) {
return (loadConfig.status = 1) return (loadConfig.status = (loadConfig.status === 2 || loadConfig.status === 3) ? loadConfig.status : 1) // 如果已经从本地加载了数据,保持原状态
} }
// 防止对话切换过快,数据渲染错误 // 防止对话切换过快,数据渲染错误
if ( if (
@ -154,6 +154,64 @@ export const useTalkRecord = (uid: number) => {
const items = (data.items || []).map((item: ITalkRecord) => formatTalkRecord(uid, item)) 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) { if (request.cursor == 0) {
// 判断是否是初次加载 // 判断是否是初次加载
dialogueStore.clearDialogueRecord() dialogueStore.clearDialogueRecord()
@ -167,7 +225,6 @@ export const useTalkRecord = (uid: number) => {
nextTick(() => { nextTick(() => {
const el = document.getElementById('imChatPanel') const el = document.getElementById('imChatPanel')
console.log('request',request)
if (el) { if (el) {
if (request.cursor == 0) { if (request.cursor == 0) {
// el.scrollTop = el.scrollHeight // el.scrollTop = el.scrollHeight
@ -195,9 +252,7 @@ console.log('request',request)
// 获取当前消息的最小 sequence // 获取当前消息的最小 sequence
const getMinSequence = () => { const getMinSequence = () => {
console.error('records.value', records.value)
if (!records.value.length) return 0 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)) return Math.min(...records.value.map((item) => item.sequence))
} }
// 获取当前消息的最大 sequence // 获取当前消息的最大 sequence
@ -206,13 +261,56 @@ console.log('request',request)
return Math.max(...records.value.map((item) => item.sequence)) 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 params
* @param options { specifiedMsg } * @param options { specifiedMsg }
*/ */
const onLoad = (params: Params, options?: LoadOptions) => { const onLoad = async (params: Params, options?: LoadOptions) => {
// 如果会话切换,重置所有状态
if ( if (
params.talk_type !== loadConfig.talk_type || params.talk_type !== loadConfig.talk_type ||
params.receiver_id !== loadConfig.receiver_id params.receiver_id !== loadConfig.receiver_id
@ -227,7 +325,6 @@ console.log('request',request)
// 新增:支持指定消息定位模式,参数以传入为准合并 // 新增:支持指定消息定位模式,参数以传入为准合并
if (options?.specifiedMsg?.cursor !== undefined) { if (options?.specifiedMsg?.cursor !== undefined) {
loadConfig.specialParams = { ...options.specifiedMsg } // 记录特殊参数,供分页加载用 loadConfig.specialParams = { ...options.specifiedMsg } // 记录特殊参数,供分页加载用
console.error('options', options)
loadConfig.status = 0 // 复用主流程 loading 状态 loadConfig.status = 0 // 复用主流程 loading 状态
// 以 params 为基础,合并 specifiedMsg 的所有字段(只要有就覆盖) // 以 params 为基础,合并 specifiedMsg 的所有字段(只要有就覆盖)
const contextParams = { const contextParams = {
@ -237,6 +334,7 @@ console.log('request',request)
//msg_id是用来做定位的不做参数所以这里清空 //msg_id是用来做定位的不做参数所以这里清空
contextParams.msg_id = '' contextParams.msg_id = ''
ServeTalkRecords(contextParams).then(({ data, code }) => { ServeTalkRecords(contextParams).then(({ data, code }) => {
console.log('data',data)
if (code !== 200) { if (code !== 200) {
loadConfig.status = 2 loadConfig.status = 2
return return
@ -339,6 +437,14 @@ console.log('request',request)
} }
loadConfig.specialParams = undefined // 普通模式清空 loadConfig.specialParams = undefined // 普通模式清空
// 设置初始加载状态为0加载中
loadConfig.status = 0
// 先从本地数据库加载数据
const hasLocalData = await loadFromLocalDB(params)
// 无论是否有本地数据,都从服务器获取最新数据
// 原有逻辑 // 原有逻辑
console.log('onLoad()执行load') console.log('onLoad()执行load')
load(params) load(params)
@ -347,7 +453,7 @@ console.log('request',request)
// 向上加载更多(兼容特殊参数模式) // 向上加载更多(兼容特殊参数模式)
const onRefreshLoad = () => { const onRefreshLoad = () => {
console.error('loadConfig.status', loadConfig.status) console.error('loadConfig.status', loadConfig.status)
if (loadConfig.status == 1) { if (loadConfig.status == 1 || loadConfig.status == 3) {
console.log('specialParams', loadConfig.specialParams) console.log('specialParams', loadConfig.specialParams)
// 判断是否是特殊参数模式 // 判断是否是特殊参数模式
if (loadConfig.specialParams && typeof loadConfig.specialParams === 'object') { if (loadConfig.specialParams && typeof loadConfig.specialParams === 'object') {

View File

@ -175,10 +175,12 @@ export const useDialogueStore = defineStore('dialogue', {
// 数组头部压入对话记录 // 数组头部压入对话记录
unshiftDialogueRecord(records) { unshiftDialogueRecord(records) {
console.log('unshiftDialogueRecord')
this.records.unshift(...records) this.records.unshift(...records)
}, },
//数组尾部加入更多对话记录 //数组尾部加入更多对话记录
addDialogueRecordForLoadMore(records){ addDialogueRecordForLoadMore(records){
console.log('addDialogueRecordForLoadMore')
this.records.push(...records) this.records.push(...records)
}, },
async getGroupInfo(){ async getGroupInfo(){
@ -190,24 +192,56 @@ export const useDialogueStore = defineStore('dialogue', {
} }
}, },
// 推送对话记录 // 推送对话记录
addDialogueRecord(record) { async addDialogueRecord(record) {
// TOOD 需要通过 sequence 排序,保证消息一致性 // TOOD 需要通过 sequence 排序,保证消息一致性
// this.records.splice(index, 0, record) // this.records.splice(index, 0, record)
console.log('addDialogueRecord',addDialogueRecord)
this.records.push(record) 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 { msg_id = '' } = params
const item = this.records.find((item) => item.msg_id === msg_id) 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) => { msgIds.forEach((msgid) => {
const index = this.records.findIndex((item) => item.msg_id === msgid) const index = this.records.findIndex((item) => item.msg_id === msgid)

View File

@ -3,6 +3,7 @@ import { ServeGetTalkList, ServeCreateTalkList } from '@/api/chat'
import { formatTalkItem, ttime, KEY_INDEX_NAME } from '@/utils/talk' import { formatTalkItem, ttime, KEY_INDEX_NAME } from '@/utils/talk'
import { useEditorDraftStore } from './editor-draft' import { useEditorDraftStore } from './editor-draft'
import { ISession } from '@/types/chat' import { ISession } from '@/types/chat'
import { getConversations, addOrUpdateConversation, deleteConversation, getConversation } from '@/utils/db'
interface TalkStoreState { interface TalkStoreState {
loadStatus: number loadStatus: number
@ -45,56 +46,103 @@ export const useTalkStore = defineStore('talk', {
}, },
// 更新对话节点 // 更新对话节点
updateItem(params: any) { async updateItem(params: any) {
const item = this.items.find((item) => item.index_name === params.index_name) 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] 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) const i = this.items.findIndex((item) => item.index_name === index_name)
if (i >= 0) { if (i >= 0) {
const item = this.items[i]
this.items.splice(i, 1) 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] this.items = [...this.items]
}, },
// 更新对话消息 // 更新对话消息
updateMessage(params: any) { async updateMessage(params: any) {
const item = this.items.find((item) => item.index_name === params.index_name) const item = this.items.find((item) => item.index_name === params.index_name)
if (item) { if (item) {
item.unread_num++ item.unread_num++
item.msg_text = params.msg_text item.msg_text = params.msg_text
item.updated_at = params.updated_at 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}`) 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 this.loadStatus = 2
const resp = ServeGetTalkList() try {
// 先从本地数据库加载会话列表
resp.then(({ code, data }) => { const localConversations = await getConversations()
if (code == 200) { if (localConversations && localConversations.length > 0) {
// 将本地会话列表转换为应用所需格式
this.items = data.items.map((item: any) => { this.items = localConversations.map((item: any) => {
// 确保本地存储的会话格式与应用一致
const value = formatTalkItem(item) const value = formatTalkItem(item)
const draft = useEditorDraftStore().items[value.index_name] const draft = useEditorDraftStore().items[value.index_name]
@ -108,22 +156,63 @@ export const useTalkStore = defineStore('talk', {
return value 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]
if (draft) {
value.draft_text = JSON.parse(draft).text || ''
}
if (value.is_robot == 1) {
value.is_online = 1
}
return value
})
// 更新状态和本地数据库
this.items = serverItems
// 将最新的会话列表保存到本地数据库
for (const item of serverItems) {
await addOrUpdateConversation(item)
}
this.loadStatus = 3 this.loadStatus = 3
} else { } else {
this.loadStatus = 4 // 如果服务器请求失败但本地有数据,保持使用本地数据
if (this.items.length === 0) {
this.loadStatus = 4
} else {
this.loadStatus = 3
}
} }
}) } catch (error) {
console.error('加载会话列表失败:', error)
resp.catch(() => { // 如果有本地数据,即使服务器请求失败也显示本地数据
this.loadStatus = 4 if (this.items.length === 0) {
}) this.loadStatus = 4
} else {
this.loadStatus = 3
}
}
}, },
findTalkIndex(index_name: string) { findTalkIndex(index_name: string) {
return this.items.findIndex((item: ISession) => item.index_name === index_name) 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 = { const route = {
path: '/message', path: '/message',
query: { query: {
@ -136,13 +225,31 @@ export const useTalkStore = defineStore('talk', {
return router.push(route) return router.push(route)
} }
ServeCreateTalkList({ try {
talk_type, // 先检查本地数据库中是否有该会话
receiver_id const localConversation = await getConversation(talk_type, receiver_id)
}).then(({ code, data, message }) => {
if (code == 200) { if (localConversation) {
// 如果本地有该会话,直接添加到列表中
if (this.findTalkIndex(`${talk_type}_${receiver_id}`) === -1) { 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}`) sessionStorage.setItem(KEY_INDEX_NAME, `${talk_type}_${receiver_id}`)
@ -150,7 +257,10 @@ export const useTalkStore = defineStore('talk', {
} else { } else {
window['$message'].info(message) window['$message'].info(message)
} }
}) } catch (error) {
console.error('创建会话失败:', error)
window['$message'].error('创建会话失败,请稍后再试')
}
} }
} }
}) })

766
src/utils/db.js Normal file
View 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}条消息`);
* });
*/

View File

@ -145,7 +145,6 @@ watch(
if (talkParams.type !== 2) { if (talkParams.type !== 2) {
ServeCheckFriend({ receiver_id: newValue.receiver_id, talk_type: 1 }).then((res) => { ServeCheckFriend({ receiver_id: newValue.receiver_id, talk_type: 1 }).then((res) => {
if (res?.code === 200) { if (res?.code === 200) {
console.log(res, 'ress')
isFriend.value = res.data.is_friend isFriend.value = res.data.is_friend
} }
}) })

View File

@ -519,6 +519,7 @@ const items = computed((): ISession[] => {
return [...topItems, ...normalItems] return [...topItems, ...normalItems]
}) })
setTimeout(()=>{console.log('items',items)},2000)
watch( watch(
() => state.addressBookSearchNickName, () => state.addressBookSearchNickName,
(newValue, oldValue) => { (newValue, oldValue) => {
@ -592,6 +593,8 @@ const indexName = computed(() => dialogueStore.index_name)
// //
const onTabTalk = (item: ISession, follow = false) => { 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 if (item.index_name === indexName.value) return
searchKeyword.value = '' searchKeyword.value = ''

View File

@ -372,6 +372,7 @@ let noRefreshTimer: number | null = null
watch( watch(
() => props, () => props,
async (newProps) => { async (newProps) => {
console.log('监听props')
await nextTick() await nextTick()
// //
const newSessionKey = `${newProps.talk_type}_${newProps.receiver_id}` const newSessionKey = `${newProps.talk_type}_${newProps.receiver_id}`
@ -414,7 +415,6 @@ watch(
}, 3000) }, 3000)
return return
} }
console.log('执行逻辑')
onLoad( onLoad(
{ {
receiver_id: newProps.receiver_id, receiver_id: newProps.receiver_id,
@ -424,7 +424,7 @@ watch(
specialParams ? { specifiedMsg: specialParams } : undefined specialParams ? { specifiedMsg: specialParams } : undefined
) )
}, },
{ deep: true } { deep: true,immediate:true }
) )
// onMounted(() => { // onMounted(() => {
@ -609,25 +609,11 @@ const handleIntersection = (entries) => {
watch( watch(
() => records.value, () => records.value,
() => { () => {
console.log()
nextTick(() => { nextTick(() => {
// //
if (observer) { if (observer) {
observer.disconnect() 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 = { const options = {
root: null, root: null,
@ -802,7 +788,7 @@ const onCustomSkipBottomEvent = () => {
<div class="load-toolbar pointer"> <div class="load-toolbar pointer">
<span v-if="loadConfig.status == 0"> 正在加载数据中 ... </span> <span v-if="loadConfig.status == 0"> 正在加载数据中 ... </span>
<span v-else-if="loadConfig.status == 1" @click="onRefreshLoad"> 查看更多消息 ... </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>
<div <div

View File

@ -154,8 +154,7 @@ const onSendVideoEvent = async ({ data }) => {
dialogueStore.updateUploadProgress(uploadId, percentage) dialogueStore.updateUploadProgress(uploadId, percentage)
}, },
async () => { async () => {
// removeUploadTask dialogueStore.batchDelDialogueRecord([uploadId])
// globalUploadList
} }
) )
} }
@ -167,10 +166,6 @@ const onSendCodeEvent = ({ data, callBack }) => {
// //
const onSendFileEvent = ({ data }) => { 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 clientUploadId = `file-${Date.now()}-${Math.floor(Math.random() * 1000)}`
const tempMessage = { const tempMessage = {
msg_id: clientUploadId, msg_id: clientUploadId,