前端:

增加会话缓存
后端:
增加触发消息创建事件
增加消息创建事件触发后的处理函数
This commit is contained in:
2026-01-20 23:09:46 +08:00
parent be621e9ae2
commit bedcf97c9d
11 changed files with 158 additions and 88 deletions
-1
View File
@@ -24,7 +24,6 @@ export const useChatStore = defineStore('chat', {
this.messages = [];
//先从浏览器缓存加载一部分消息列表
const localHistory = await messagesDb.getPageMessages(sessionId, new Date().toISOString(), this.pageSize);
console.log(localHistory)
if (localHistory.length > 0) {
this.messages = localHistory;
} else {
+63
View File
@@ -0,0 +1,63 @@
import { defineStore } from "pinia";
import { messageService } from "@/services/message";
import { conversationDb } from "@/utils/db/conversationDB";
import { useMessage } from "@/components/messages/useAlert";
const message = useMessage();
export const useConversationStore = defineStore('conversation', {
state: () => ({
conversations: []
}),
actions: {
async addConversation(conversation) {
await conversationDb.save(conversation);
this.conversations.unshift(conversation)
},
/**
* 加载当前会话消息列表
*/
async loadUserConversations() {
if (this.conversations.length == 0) {
try {
const covnersationsCache = await conversationDb.getAll();
if (covnersationsCache && covnersationsCache.length > 0) {
covnersationsCache.sort((a, b) => {
return new Date(a.dateTime) - new Date(b.dateTime);
})
}
} catch (e) {
message.error('读取本地会话缓存失败...');
console.log('读取本地会话缓存失败:', e);
}
}
await this.fetchConversationsFromServier()
},
/**
* 从服务器加载新消息
* @param {*} sessionId
* @returns
*/
async fetchConversationsFromServier() {
const newConversations = (await messageService.getConversations()).data;
if (newConversations.length > 0) {
// 1. 将当前的本地数据转为 Map,方便通过 ID 快速查找 (O(1) 复杂度)
const localMap = new Map(this.conversations.map(item => [item.id, item]));
newConversations.forEach(item => {
const existingItem = localMap.get(item.id);
if (existingItem) {
// --- 局部更新 ---
// 使用 Object.assign 将新数据合并到旧对象上,保持响应式引用
Object.assign(existingItem, item);
} else {
// --- 插入新会话 ---
this.conversations.unshift(item);
}
// 同步到本地数据库
conversationDb.save(item);
});
}
}
}
})
+19
View File
@@ -0,0 +1,19 @@
import { openDB } from "idb";
const DBNAME = 'IM_DB';
const STORE_NAME = 'messages';
const CONVERSARION_STORE_NAME = 'conversations';
export const dbPromise = openDB(DBNAME, 2, {
upgrade(db) {
if (!db.objectStoreNames.contains(STORE_NAME)) {
const store = db.createObjectStore(STORE_NAME, { keyPath: 'msgId' });
store.createIndex('by-sessionId', 'sessionId');
store.createIndex('by-time', 'timeStamp');
}
if (!db.objectStoreNames.contains(CONVERSARION_STORE_NAME)) {
const store = db.createObjectStore(CONVERSARION_STORE_NAME, { keyPath: 'id' });
store.createIndex('by-id', 'id');
}
}
})
@@ -0,0 +1,18 @@
import { dbPromise } from "./baseDb";
const STORE_NAME = 'conversations';
export const conversationDb = {
async save(conversation) {
(await dbPromise).put(STORE_NAME, conversation);
},
async getById(id) {
return (await dbPromise).getFromIndex(STORE_NAME, 'by-id', id);
},
async getAll() {
return (await dbPromise).getAll(STORE_NAME);
},
async clearAll() {
(await dbPromise).clear(STORE_NAME);
}
}
+1 -12
View File
@@ -1,18 +1,7 @@
import { openDB } from "idb";
import { dbPromise } from "./baseDb";
const DBNAME = 'IM_DB';
const STORE_NAME = 'messages';
export const dbPromise = openDB(DBNAME, 1, {
upgrade(db) {
if (!db.objectStoreNames.contains(STORE_NAME)) {
const store = db.createObjectStore(STORE_NAME, { keyPath: 'msgId' });
store.createIndex('by-sessionId', 'sessionId');
store.createIndex('by-time', 'timeStamp');
}
}
})
export const messagesDb = {
async save(msg) {
return (await dbPromise).put(STORE_NAME, msg);
@@ -50,6 +50,7 @@ import { formatDate } from '@/utils/formatDate';
import { useChatStore } from '@/stores/chat';
import { generateSessionId } from '@/utils/sessionIdTools';
import { useSignalRStore } from '@/stores/signalr';
import { useConversationStore } from '@/stores/conversation';
const props = defineProps({
id:{
@@ -60,6 +61,7 @@ const props = defineProps({
const chatStore = useChatStore();
const signalRStore = useSignalRStore();
const conversationStore = useConversationStore();
const input = ref(''); // 输入框内容
const historyRef = ref(null); // 绑定 DOM 用于滚动
@@ -118,20 +120,20 @@ function toggleEmoji() {
}
async function loadConversation(conversationId) {
/*
const res = await messageService.getConversationById(conversationId);
conversationInfo.value = res.data;
console.log(res)
}
async function loadMessages(conversationId, msgId = null, pageSize = null) {
const res = await messageService.getMessages(conversationId);
messages.value = res.data;
*/
if(conversationStore.conversations.length == 0){
await conversationStore.loadUserConversations();
}
conversationInfo.value = conversationStore.conversations.find(x => x.id == Number(conversationId));
}
// 初始化时滚动到底部
onMounted(async () => {
await loadConversation(props.id);
const sessionid = generateSessionId(conversationInfo.userId, conversationInfo.targetId)
const sessionid = generateSessionId(conversationInfo.value.userId, conversationInfo.value.targetId)
await chatStore.swtichSession(sessionid,props.id);
scrollToBottom();
});
@@ -13,7 +13,7 @@
class="list-item" :class="{active: activeId === s.id}" @click="selectSession(s)">
<div class="avatar-container">
<img :src="s.targetAvatar ?? defaultAvatar" class="avatar-std" />
<span v-if="s.unread > 0" class="unread-badge">{{ s.unreadCount ?? 0 }}</span>
<span v-if="s.unreadCount > 0" class="unread-badge">{{ s.unreadCount ?? 0 }}</span>
</div>
<div class="info">
<div class="name-row">
@@ -36,26 +36,19 @@ import { ref, computed, nextTick, onMounted } from 'vue'
import { messageService } from '@/services/message'
import defaultAvatar from '@/assets/default_avatar.png'
import { formatDate } from '@/utils/formatDate'
import { useConversationStore } from '@/stores/conversation'
const conversationStore = useConversationStore();
const searchQuery = ref('')
const input = ref('')
const historyRef = ref(null)
const activeId = ref(1)
const conversations = ref([]);
const filteredSessions = computed(() => conversations.value.filter(s => s.targetName.includes(searchQuery.value)))
const currentSession = computed(() => sessions.value.find(s => s.id === activeId.value))
const filteredSessions = computed(() => conversationStore.conversations.filter(s => s.targetName.includes(searchQuery.value)))
function selectSession(s) {
activeId.value = s.id
router.push(`/messages/chat/${s.id}`)
scrollToBottom()
}
const scrollToBottom = async () => {
await nextTick()
if (historyRef.value) historyRef.value.scrollTop = historyRef.value.scrollHeight
}
async function loadConversation() {
@@ -65,7 +58,7 @@ async function loadConversation() {
}
onMounted(async () => {
await loadConversation();
await conversationStore.loadUserConversations();
})