From 0509104ed11f92dd06971c5cc97e95ed45c8980d Mon Sep 17 00:00:00 2001 From: heiye111 Date: Wed, 27 May 2026 13:36:35 +0800 Subject: [PATCH] =?UTF-8?q?=E5=87=BA=E5=8F=91=E5=89=8D=E4=BF=9D=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.html | 3 +- package-lock.json | 32 ++++ package.json | 1 + src/App.vue | 131 +++++++++++++++- src/components/ChatTest.vue | 30 ++-- src/main.js | 16 +- src/network/signalr.js | 19 +-- src/router/index.js | 39 ++++- src/stores/friend.js | 27 ++++ src/stores/messages.js | 57 +++++++ src/stores/user.js | 46 ++++++ src/views/HomeView.vue | 44 ------ src/views/IContacts.vue | 98 ++++++++++++ src/views/{LoginTest.vue => ILogin.vue} | 71 +++++---- src/views/IMessage.vue | 92 +++++++++++ src/views/IProfile.vue | 116 ++++++++++++++ src/views/Message/FriendMessage.vue | 198 ++++++++++++++++++++++++ src/views/Users/FriendDetail.vue | 115 ++++++++++++++ src/views/Users/SearchContacts.vue | 101 ++++++++++++ 19 files changed, 1115 insertions(+), 121 deletions(-) create mode 100644 src/stores/friend.js create mode 100644 src/stores/messages.js create mode 100644 src/stores/user.js delete mode 100644 src/views/HomeView.vue create mode 100644 src/views/IContacts.vue rename src/views/{LoginTest.vue => ILogin.vue} (58%) create mode 100644 src/views/IMessage.vue create mode 100644 src/views/IProfile.vue create mode 100644 src/views/Message/FriendMessage.vue create mode 100644 src/views/Users/FriendDetail.vue create mode 100644 src/views/Users/SearchContacts.vue diff --git a/index.html b/index.html index b19040a..eccafa1 100644 --- a/index.html +++ b/index.html @@ -3,7 +3,8 @@ - + + Vite App diff --git a/package-lock.json b/package-lock.json index adbada9..a0ef2aa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@microsoft/signalr-protocol-msgpack": "^10.0.0", "mitt": "^3.0.1", "pinia": "^3.0.4", + "pinia-plugin-persistedstate": "^4.7.1", "update": "^0.7.4", "vant": "^4.9.24", "vue": "beta", @@ -6095,6 +6096,12 @@ "node": ">=0.10.0" } }, + "node_modules/defu": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", + "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", + "license": "MIT" + }, "node_modules/delimiter-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/delimiter-regex/-/delimiter-regex-2.0.0.tgz", @@ -11400,6 +11407,31 @@ } } }, + "node_modules/pinia-plugin-persistedstate": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/pinia-plugin-persistedstate/-/pinia-plugin-persistedstate-4.7.1.tgz", + "integrity": "sha512-WHOqh2esDlR3eAaknPbqXrkkj0D24h8shrDPqysgCFR6ghqP/fpFfJmMPJp0gETHsvrh9YNNg6dQfo2OEtDnIQ==", + "license": "MIT", + "dependencies": { + "defu": "^6.1.4" + }, + "peerDependencies": { + "@nuxt/kit": ">=3.0.0", + "@pinia/nuxt": ">=0.10.0", + "pinia": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@nuxt/kit": { + "optional": true + }, + "@pinia/nuxt": { + "optional": true + }, + "pinia": { + "optional": true + } + } + }, "node_modules/pkg-store": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/pkg-store/-/pkg-store-0.2.2.tgz", diff --git a/package.json b/package.json index 48e297a..7f2400c 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "@microsoft/signalr-protocol-msgpack": "^10.0.0", "mitt": "^3.0.1", "pinia": "^3.0.4", + "pinia-plugin-persistedstate": "^4.7.1", "update": "^0.7.4", "vant": "^4.9.24", "vue": "beta", diff --git a/src/App.vue b/src/App.vue index 96a1a4c..74bc9c9 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,11 +1,140 @@ + + diff --git a/src/components/ChatTest.vue b/src/components/ChatTest.vue index 21f5f38..6311b73 100644 --- a/src/components/ChatTest.vue +++ b/src/components/ChatTest.vue @@ -73,30 +73,30 @@ const messages = computed(() => chatMessages.value[currentChat.value.id] || []) const newMessage = ref("") onMounted(() => { - emitter.on('SendPrivateMessage', ({ success, message, userid, messageId }) => { - if (success) { - if (!chatMessages.value[userid]) { - chatMessages.value[userid] = [] + emitter.on('SendPrivateMessage', (ResultResponse) => { + if (ResultResponse.success) { + if (!chatMessages.value[ResultResponse.senderId]) { + chatMessages.value[ResultResponse.senderId] = [] } - chatMessages.value[userid].push({ - id: messageId, - from: chats.value.find(c => c.id === userid)?.name || "未知用户", - text: message, + chatMessages.value[ResultResponse.senderId].push({ + id: ResultResponse.messageId, + from: chats.value.find(c => c.id === ResultResponse.senderId)?.name || "未知用户", + text: ResultResponse.message, isRead: false }) - currentChat.value.lastMessage = message - if (currentChat.value.id === userid) { - sendIsRead(messageId) - console.log("收到消息,已发送已读回执:", messageId) + currentChat.value.lastMessage = ResultResponse.message + if (currentChat.value.id === ResultResponse.senderId) { + sendIsRead(ResultResponse.messageId) + console.log("收到消息,已发送已读回执:", ResultResponse.messageId) } } else { chatMessages.value[currentChat.value.id].push({ - id: messageId, + id: ResultResponse.messageId, from: "我", - text: message, + text: ResultResponse.message, isRead: false }) - currentChat.value.lastMessage = message + currentChat.value.lastMessage = ResultResponse.message } }) emitter.on('SendReadReceipt', (messageId) => { diff --git a/src/main.js b/src/main.js index 75163b0..b693419 100644 --- a/src/main.js +++ b/src/main.js @@ -5,23 +5,21 @@ import { createPinia } from 'pinia' import App from './App.vue' import router from './router' +import piniaPluginPersistedstate from 'pinia-plugin-persistedstate' // 引入 Vant 组件 -import { Search, Cell, List, NavBar, Card, Field, Button } from 'vant' +import Vant from 'vant' import 'vant/lib/index.css' const app = createApp(App) +const pinia = createPinia() +pinia.use(piniaPluginPersistedstate) // 注册 Vant 组件 -app.use(Search) -app.use(Cell) -app.use(List) -app.use(NavBar) -app.use(Card) -app.use(Field) -app.use(Button) +app.use(Vant) + +app.use(pinia) -app.use(createPinia()) app.use(router) app.mount('#app') diff --git a/src/network/signalr.js b/src/network/signalr.js index 114fd9c..de6579b 100644 --- a/src/network/signalr.js +++ b/src/network/signalr.js @@ -20,20 +20,17 @@ function registerHandlers(conn) { emitter.emit('GroupCount', { groupName, count }) }) - conn.on('RegisterResult', (success, message) => { - emitter.emit('RegisterResult', { success, message }) - }) - - conn.on('LoginResult', async (success, accessToken, refreshToken) => { - emitter.emit('LoginResult', { success, accessToken, refreshToken }) - }) - - conn.on('SendPrivateMessage', (success, message, userid, messageId) => { - emitter.emit('SendPrivateMessage', { success, message, userid, messageId }) + conn.on('SendPrivateMessage', (ResultResponse) => { + emitter.emit('SendPrivateMessage', ResultResponse) }) conn.on('SendReadReceipt', (messageId) => { emitter.emit('SendReadReceipt', messageId) }) + + // 监听用户状态变化 + connection.on("UserStatusChanged", (userId, isOnline) => { + emitter.emit('UserStatusChanged', { userId, isOnline }) + }) } // 初始连接(不带 token) @@ -99,4 +96,4 @@ async function reconnectWithToken() { await RefreshAccessToken() // 尝试刷新 token 并重连 })() -export { connection, reconnectWithToken } +export { connection, reconnectWithToken, createConnection } diff --git a/src/router/index.js b/src/router/index.js index 36e7410..9855131 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -4,23 +4,54 @@ const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes: [ { - path: '/', + path: '/', // 消息页面 name: 'home', - component: () => import('@/views/LoginTest.vue'), + component: () => import('@/views/IMessage.vue'), }, { - path: '/chat', + path: '/chat', //旧的测试聊天页面 name: 'chat', component: () => import('@/components/HelloWorld.vue'), }, { - path: '/chattest', + path: '/chattest', //新的测试聊天页面 name: 'chattest', // route level code-splitting // this generates a separate chunk (About.[hash].js) for this route // which is lazy-loaded when the route is visited. component: () => import('@/components/ChatTest.vue'), }, + { + path: '/contacts', //联系人页面 + name: 'contacts', + component: () => import('@/views/IContacts.vue'), + }, + { + path: '/frienddetail', //好友详情页面 + name: 'friendDetail', + component: () => import('@/views/Users/FriendDetail.vue'), + }, + { + path: '/searchcontacts', //搜索联系人页面 + name: 'searchContacts', + component: () => import('@/views/Users/SearchContacts.vue'), + }, + { + path: '/profile', //个人中心页面 + name: 'profile', + component: () => import('@/views/IProfile.vue'), + }, + { + path: '/login', //登录页面 + name: 'login', + component: () => import('@/views/ILogin.vue'), + }, + { + path: '/friendmessage', //好友消息页面 + name: 'friendMessage', + component: () => import('@/views/Message/FriendMessage.vue'), + meta: { hideTabbar: true } + } ], }) diff --git a/src/stores/friend.js b/src/stores/friend.js new file mode 100644 index 0000000..fadbdd1 --- /dev/null +++ b/src/stores/friend.js @@ -0,0 +1,27 @@ +// stores/friend.js +import { defineStore } from 'pinia' + +export const useFriendStore = defineStore('friend', { + state: () => ({ + currentFriend: {}, + friends: [] + }), + actions: { + setFriend(friend) { + this.currentFriend = friend + }, + clearFriend() { + this.currentFriend = null + } + }, + persist: { + enabled: true, + strategies: [ + { + key: 'friend', + storage: localStorage + } + ] + } +}) + diff --git a/src/stores/messages.js b/src/stores/messages.js new file mode 100644 index 0000000..7fadaf5 --- /dev/null +++ b/src/stores/messages.js @@ -0,0 +1,57 @@ +// stores/messages.js +import { defineStore } from 'pinia' +import { useFriendStore } from './friend' + +export const useMessagesStore = defineStore('messages', { + state: () => ({ + chats: {}, // { friendId: [消息数组] } + }), + actions: { + addMessage(friendId, msg) { + if (!this.chats[friendId]) this.chats[friendId] = [] + const exists = this.chats[friendId].some((m) => m.id === msg.id) + if (!exists) { + this.chats[friendId].push(msg) + } + }, + + setMessages(friendId, msgs) { + const id = Number(friendId) + const existing = this.chats[id] || [] + const merged = [...existing] + + msgs.forEach((msg) => { + if (!merged.some((m) => m.id === msg.id)) { + merged.push(msg) + } + }) + + // 按消息 id 或时间排序,保证顺序正确 + this.chats[id] = merged.sort((a, b) => a.id - b.id) + }, + + markRead(friendId, messageId) { + const msg = this.chats[friendId]?.find((m) => m.id === messageId) + if (msg) msg.isRead = true + }, + }, + getters: { + chatList: (state) => { + const friendStore = useFriendStore() + return Object.keys(state.chats).map((friendId) => { + const msgs = state.chats[friendId] + const lastMsg = msgs[msgs.length - 1] + const unreadCount = msgs.filter((m) => !m.isRead && m.from === 'other').length + + const friend = friendStore.friends.find(f => f.userid === Number(friendId)) || {} + + // 直接返回引用 + 补充字段 + return { + ...friend, // ✅ 保持响应式引用 + lastMessage: lastMsg ? lastMsg.text : '', + unreadCount + } + }) + }, + }, +}) diff --git a/src/stores/user.js b/src/stores/user.js new file mode 100644 index 0000000..89fb836 --- /dev/null +++ b/src/stores/user.js @@ -0,0 +1,46 @@ +// stores/user.js +import { defineStore } from 'pinia' + +export const useUserStore = defineStore('user', { + state: () => ({ + AccessToken: '', + RefreshToken: '', + Nickname: '', + Id: 0, + AvatarUrl: '', + Signature: '', + IsOnline: false, + Success: false + }), + actions: { + setUser(data) { + this.AccessToken = data.AccessToken + this.RefreshToken = data.RefreshToken + this.Nickname = data.Nickname + this.Id = data.Id + this.AvatarUrl = data.AvatarUrl + this.Signature = data.Signature + this.IsOnline = data.IsOnline + this.Success = data.Success + }, + clearUser() { + this.AccessToken = '' + this.RefreshToken = '' + this.Nickname = '' + this.Id = 0 + this.AvatarUrl = '' + this.Signature = '' + this.IsOnline = false + this.Success = false + } + }, + persist: { + enabled: true, // 开启持久化 + strategies: [ + { + key: 'user', // localStorage 的 key + storage: localStorage // 默认就是 localStorage + } + ] + } +}) diff --git a/src/views/HomeView.vue b/src/views/HomeView.vue deleted file mode 100644 index eff59f1..0000000 --- a/src/views/HomeView.vue +++ /dev/null @@ -1,44 +0,0 @@ - - - - - diff --git a/src/views/IContacts.vue b/src/views/IContacts.vue new file mode 100644 index 0000000..220caa2 --- /dev/null +++ b/src/views/IContacts.vue @@ -0,0 +1,98 @@ + + + + + diff --git a/src/views/LoginTest.vue b/src/views/ILogin.vue similarity index 58% rename from src/views/LoginTest.vue rename to src/views/ILogin.vue index 35bb96a..ad7c7a9 100644 --- a/src/views/LoginTest.vue +++ b/src/views/ILogin.vue @@ -5,8 +5,8 @@
- - + +
@@ -37,51 +37,50 @@ + + diff --git a/src/views/IProfile.vue b/src/views/IProfile.vue new file mode 100644 index 0000000..df9cb37 --- /dev/null +++ b/src/views/IProfile.vue @@ -0,0 +1,116 @@ + + + + + diff --git a/src/views/Message/FriendMessage.vue b/src/views/Message/FriendMessage.vue new file mode 100644 index 0000000..cecc162 --- /dev/null +++ b/src/views/Message/FriendMessage.vue @@ -0,0 +1,198 @@ + + + + + diff --git a/src/views/Users/FriendDetail.vue b/src/views/Users/FriendDetail.vue new file mode 100644 index 0000000..fbec4ea --- /dev/null +++ b/src/views/Users/FriendDetail.vue @@ -0,0 +1,115 @@ + + + + + diff --git a/src/views/Users/SearchContacts.vue b/src/views/Users/SearchContacts.vue new file mode 100644 index 0000000..8c4cb7a --- /dev/null +++ b/src/views/Users/SearchContacts.vue @@ -0,0 +1,101 @@ + + + + +