出发前保存
This commit is contained in:
@@ -3,7 +3,8 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<!-- <meta name="viewport" content="width=device-width, initial-scale=1.0"> -->
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
|
||||
<title>Vite App</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
32
package-lock.json
generated
32
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
131
src/App.vue
131
src/App.vue
@@ -1,11 +1,140 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<router-view /> <!-- 这里会根据路由显示不同页面 -->
|
||||
<!-- 页面内容 -->
|
||||
<router-view class="page-content" />
|
||||
|
||||
<!-- 底部导航栏 -->
|
||||
<van-tabbar v-if="!$route.meta.hideTabbar" v-model="active" fixed>
|
||||
<van-tabbar-item icon="chat" :badge="unreadMessages" @click="$router.push('/')">
|
||||
消息
|
||||
</van-tabbar-item>
|
||||
|
||||
<van-tabbar-item icon="friends" :badge="unreadContacts" @click="$router.push('/contacts')">
|
||||
联系人
|
||||
</van-tabbar-item>
|
||||
|
||||
<van-tabbar-item icon="user" @click="$router.push('/profile')">
|
||||
我的
|
||||
</van-tabbar-item>
|
||||
</van-tabbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import { emitter } from '@/network/signalr'
|
||||
import { useMessagesStore } from '@/stores/messages'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { useFriendStore } from '@/stores/friend'
|
||||
|
||||
const usefriendStore = useFriendStore()
|
||||
const userStore = useUserStore()
|
||||
const currentUserId = userStore.Id
|
||||
const currentChatId = usefriendStore.currentFriend.userid
|
||||
|
||||
const active = ref(1)
|
||||
const unreadMessages = ref(5) // 消息未读数
|
||||
const unreadContacts = ref(2) // 联系人未读数
|
||||
const messagesStore = useMessagesStore()
|
||||
|
||||
onMounted(() => {
|
||||
console.log('App.vue mounted, currentUserId:', currentUserId, 'currentChatId:', currentChatId)
|
||||
const accessToken = localStorage.getItem('accessToken')
|
||||
if (!accessToken) {
|
||||
if (window.location.pathname !== '/login') {
|
||||
// 没有访问令牌,跳转到登录页面
|
||||
window.location.href = '/login'
|
||||
}
|
||||
}
|
||||
|
||||
emitter.on('UserStatusChanged', ({ userId, isOnline }) => {
|
||||
console.log('用户状态变化:', userId, isOnline)
|
||||
// 更新消息列表中的用户状态
|
||||
//messagesStore.updateUserStatus(userId, isOnline)
|
||||
const friend = usefriendStore.friends.find(f => f.userid === Number(userId))
|
||||
if (friend) {
|
||||
friend.isonline = isOnline
|
||||
}
|
||||
})
|
||||
|
||||
emitter.on("SendPrivateMessage", (resp) => {
|
||||
// 根据 senderId / receiverId 判断属于哪个会话
|
||||
const friendId = resp.SenderId === currentUserId ? resp.ReceiverId : resp.SenderId
|
||||
|
||||
console.log('收到消息:', resp.Message, '来自用户ID:', resp.SenderId, "friendId:", friendId)
|
||||
if (resp.IsMine) {
|
||||
messagesStore.addMessage(friendId, {
|
||||
id: resp.MessageId,
|
||||
from: 'me',
|
||||
text: resp.Message,
|
||||
isRead: false,
|
||||
avatarurl: resp.AvatarUrl,
|
||||
nickname: resp.Nickname
|
||||
})
|
||||
} else {
|
||||
messagesStore.addMessage(friendId, {
|
||||
id: resp.MessageId,
|
||||
from: 'other',
|
||||
text: resp.Message,
|
||||
isRead: false,
|
||||
avatarurl: resp.AvatarUrl,
|
||||
nickname: resp.Nickname
|
||||
})
|
||||
}
|
||||
|
||||
// // 已读回执
|
||||
// if (friendId === currentChatId) {
|
||||
// connection.send("SendReadReceipt", resp.messageId)
|
||||
// }
|
||||
})
|
||||
|
||||
// 监听已读回执事件
|
||||
emitter.on("SendReadReceipt", (messageId) => {
|
||||
messagesStore.markRead(currentChatId, messageId)
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
console.log('App.vue unmounted')
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<style>
|
||||
html,
|
||||
body,
|
||||
#app {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.page-content {
|
||||
min-height: calc(100vh - 50px);
|
||||
/* 扣掉底部导航栏高度 */
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
|
||||
/* 调整图标大小 */
|
||||
.van-tabbar-item__icon .van-icon {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
/* 修改背景颜色 */
|
||||
.van-tabbar {
|
||||
background-color: #f5f5f5 !important;
|
||||
border-top: 1px solid #e0e0e0;
|
||||
/* 在最上方加一条浅灰色边框 */
|
||||
}
|
||||
|
||||
.van-tabbar-item--active {
|
||||
background-color: #f5f5f5 !important;
|
||||
/* 改成你想要的浅灰色或其他颜色 */
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
16
src/main.js
16
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')
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
],
|
||||
})
|
||||
|
||||
|
||||
27
src/stores/friend.js
Normal file
27
src/stores/friend.js
Normal file
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
57
src/stores/messages.js
Normal file
57
src/stores/messages.js
Normal file
@@ -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
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
})
|
||||
46
src/stores/user.js
Normal file
46
src/stores/user.js
Normal file
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
@@ -1,44 +0,0 @@
|
||||
<script setup>
|
||||
defineProps({
|
||||
msg: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="greetings">
|
||||
<h1 class="green">{{ msg }}</h1>
|
||||
<h3>
|
||||
You’ve successfully created a project with
|
||||
<a href="https://vite.dev/" target="_blank" rel="noopener">Vite</a> +
|
||||
<a href="https://vuejs.org/" target="_blank" rel="noopener">Vue 3</a>.
|
||||
</h3>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
h1 {
|
||||
font-weight: 500;
|
||||
font-size: 2.6rem;
|
||||
position: relative;
|
||||
top: -10px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.greetings h1,
|
||||
.greetings h3 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.greetings h1,
|
||||
.greetings h3 {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
98
src/views/IContacts.vue
Normal file
98
src/views/IContacts.vue
Normal file
@@ -0,0 +1,98 @@
|
||||
<template>
|
||||
<div class="contacts-page">
|
||||
<!-- 顶部导航栏 -->
|
||||
<van-nav-bar title="联系人" left-text="返回" left-arrow @click-left="$router.back()">
|
||||
<template #right>
|
||||
<van-icon name="search" size="18" @click="goSearch" />
|
||||
</template>
|
||||
</van-nav-bar>
|
||||
|
||||
<!-- 子路由出口 -->
|
||||
<router-view />
|
||||
<!-- 搜索框 -->
|
||||
<van-search v-model="searchValue" placeholder="搜索联系人" @search="searchContacts" />
|
||||
|
||||
<!-- 联系人列表 -->
|
||||
|
||||
<van-cell-group>
|
||||
|
||||
<van-cell v-for="contact in contacts" :key="contact.userid" :title="contact.nickname" :label="contact.signature"
|
||||
:description="contact.signature" :is-online="contact.isonline" is-link @click="openContact(contact)">
|
||||
<template #icon>
|
||||
|
||||
<van-image :src="contact.avatarurl" width="40" height="40" fit="cover" />
|
||||
|
||||
</template>
|
||||
<van-tag v-if="contact.isonline" type="success">在线</van-tag>
|
||||
<van-tag v-else type="danger">离线</van-tag>
|
||||
</van-cell>
|
||||
</van-cell-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { connection } from '@/network/signalr'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { useFriendStore } from '@/stores/friend'
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
const friendStore = useFriendStore()
|
||||
|
||||
onMounted( async () => {
|
||||
console.log('用户ID:', userStore.Id)
|
||||
await getContacts(userStore.Id);
|
||||
})
|
||||
|
||||
|
||||
async function getContacts(userid)
|
||||
{
|
||||
await connection.invoke("GetFriendsAsync", userid)
|
||||
.then(result => {
|
||||
console.log('获取联系人列表成功:', result)
|
||||
contacts.value.push(...result); // 将获取到的联系人列表赋值给contacts
|
||||
friendStore.friends = result
|
||||
})
|
||||
.catch(err => console.error("获取联系人列表失败:", err));
|
||||
}
|
||||
|
||||
function goSearch() {
|
||||
router.push('/searchcontacts')
|
||||
}
|
||||
|
||||
const searchValue = ref('')
|
||||
const contacts = ref([])
|
||||
|
||||
async function searchContacts() {
|
||||
// 这里可以添加搜索逻辑,例如调用API获取搜索结果
|
||||
console.log('搜索联系人:', searchValue.value)
|
||||
await connection.invoke("SearchFriends", searchValue.value)
|
||||
.then(result => {
|
||||
console.log('搜索结果:', result)
|
||||
contacts.value.push(result);
|
||||
// 这里可以将result赋值给contacts来更新联系人列表
|
||||
})
|
||||
.catch(err => console.error("搜索联系人失败:", err));
|
||||
|
||||
}
|
||||
|
||||
function openContact(contact) {
|
||||
console.log('进入联系人详情:', contact)
|
||||
// 这里可以用 $router.push('/contact/' + contact.id) 跳转详情页
|
||||
friendStore.setFriend(contact)
|
||||
router.push(`/frienddetail`)
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.contacts-page {
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
background-color: #f5f5f5;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
</style>
|
||||
@@ -5,8 +5,8 @@
|
||||
|
||||
<form @submit.prevent="handleSubmit">
|
||||
<div v-if="isLogin" class="form-group">
|
||||
<label>邮箱</label>
|
||||
<input v-model="email" type="email" required />
|
||||
<label>用户名或邮箱</label>
|
||||
<input v-model="username" type="text" required />
|
||||
</div>
|
||||
|
||||
<div v-if="isLogin" class="form-group">
|
||||
@@ -37,51 +37,50 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from "vue";
|
||||
import { connection, reconnectWithToken, emitter } from '@/network/signalr'
|
||||
import { ref } from "vue";
|
||||
import { connection, reconnectWithToken } from '@/network/signalr'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
|
||||
|
||||
const isLogin = ref(true);
|
||||
const email = ref("");
|
||||
const username = ref("");
|
||||
const password = ref("");
|
||||
const confirmPassword = ref("");
|
||||
const router = useRouter()
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
onMounted(async () => {
|
||||
// 启动SignalR连接
|
||||
//await startConnection();
|
||||
emitter.on("RegisterResult", ({ success, message }) => {
|
||||
alert(message);
|
||||
if (success) {
|
||||
isLogin.value = true; // 注册成功后切换到登录界面
|
||||
console.log("注册成功:", message);
|
||||
return
|
||||
}
|
||||
console.error("注册失败:", message);
|
||||
});
|
||||
emitter.on("LoginResult", async ({ success, accessToken, refreshToken }) => {
|
||||
if (success) {
|
||||
localStorage.setItem("accessToken", accessToken);
|
||||
localStorage.setItem("refreshToken", refreshToken);
|
||||
console.log("登录成功:", accessToken, "=========", refreshToken);
|
||||
await reconnectWithToken();
|
||||
router.push("/chattest"); // 登录成功后跳转到主界面 隐藏登录注册界面
|
||||
return
|
||||
}
|
||||
console.error("登录失败:", accessToken); // 这里accessToken实际上是错误信息
|
||||
});
|
||||
});
|
||||
|
||||
function RegisterRequest() {
|
||||
connection.send("Register")
|
||||
async function RegisterRequest() {
|
||||
const result = await connection.invoke("Register")
|
||||
.catch(err => console.error("注册请求失败:", err));
|
||||
if (result.Success) {
|
||||
isLogin.value = true; // 注册成功后切换到登录界面
|
||||
console.log("注册成功:", result.Username); // 这里result.message实际上是成功信息
|
||||
await LoginRequest(result.Username, result.Password); // 注册成功后自动登录
|
||||
return
|
||||
}
|
||||
console.error("注册失败:", result.Success); // 这里result.message实际上是错误信息
|
||||
}
|
||||
|
||||
function LoginRequest(email, password) {
|
||||
connection.send("LoginAuthentication", email, password)
|
||||
async function LoginRequest(username, password) {
|
||||
// 登录请求,获取访问令牌和刷新令牌
|
||||
const ua = navigator.userAgent // 获取用户代理字符串
|
||||
|
||||
const platform = navigator.platform // 获取平台信息
|
||||
console.log("ua:", ua, "platform:", platform)
|
||||
const result = await connection.invoke("LoginAuthentication", username, password, ua + platform)
|
||||
.catch(err => console.error("登录请求失败:", err));
|
||||
if (result.Success) {
|
||||
localStorage.setItem("accessToken", result.AccessToken);
|
||||
localStorage.setItem("refreshToken", result.RefreshToken);
|
||||
console.log("登录成功:", result.AccessToken, "=========", result.RefreshToken);
|
||||
userStore.setUser(result);
|
||||
await reconnectWithToken();
|
||||
router.push("/"); // 登录成功后跳转到主界面 隐藏登录注册界面
|
||||
return
|
||||
}
|
||||
console.error("登录失败:", "用户名或密码错误!"); // 这里result.message实际上是错误信息
|
||||
}
|
||||
|
||||
function handleSubmit() {
|
||||
@@ -90,9 +89,9 @@ function handleSubmit() {
|
||||
return;
|
||||
}
|
||||
if (isLogin.value) {
|
||||
LoginRequest(email.value, password.value);
|
||||
LoginRequest(username.value, password.value);
|
||||
} else {
|
||||
RegisterRequest(email.value, password.value);
|
||||
RegisterRequest(username.value, password.value);
|
||||
}
|
||||
}
|
||||
|
||||
92
src/views/IMessage.vue
Normal file
92
src/views/IMessage.vue
Normal file
@@ -0,0 +1,92 @@
|
||||
<template>
|
||||
<div class="message-page">
|
||||
<!-- 顶部导航栏 -->
|
||||
<van-nav-bar title="消息" />
|
||||
<h1>消息</h1>
|
||||
<!-- 消息列表 -->
|
||||
<van-list>
|
||||
<van-cell v-for="chat in messagesStore.chatList" :key="chat.userid" clickable @click="openChat(chat)">
|
||||
<!-- 左侧头像 -->
|
||||
<template #icon>
|
||||
<van-image :src="chat.avatarurl" width="40" height="40" />
|
||||
</template>
|
||||
|
||||
<!-- 中间昵称 + 最新消息 -->
|
||||
<template #title>
|
||||
<div class="nickname">{{ chat.nickname }}</div>
|
||||
<div class="last-message">{{ chat.lastMessage }}</div>
|
||||
</template>
|
||||
|
||||
<!-- 右侧未读数 -->
|
||||
<template #right-icon v-if="chat.unreadCount > 0">
|
||||
<van-badge :content="chat.unreadCount" class="unread-badge" />
|
||||
</template>
|
||||
|
||||
</van-cell>
|
||||
</van-list>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
import { useMessagesStore } from '@/stores/messages'
|
||||
import { useFriendStore } from '@/stores/friend'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const friendStore = useFriendStore()
|
||||
|
||||
const messagesStore = useMessagesStore()
|
||||
|
||||
|
||||
|
||||
function openChat(chat) {
|
||||
// 跳转到聊天详情页
|
||||
console.log('打开聊天:', chat.userid, chat)
|
||||
const friend = friendStore.friends.find(f => f.userid === Number(chat.userid)) || {}
|
||||
friendStore.setFriend(friend)
|
||||
router.push(`/friendmessage`)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.message-page {
|
||||
height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
/* 昵称加粗 */
|
||||
.nickname {
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* 最新消息灰色小字 */
|
||||
.last-message {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.unread-badge {
|
||||
/* 放大角标 */
|
||||
font-size: 16px;
|
||||
/* 字体更大 */
|
||||
min-width: 28px;
|
||||
/* 宽度更大 */
|
||||
height: 28px;
|
||||
/* 高度更大 */
|
||||
line-height: 28px;
|
||||
/* 垂直居中 */
|
||||
|
||||
/* 调整位置到右侧中间 */
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
/* 距离右边 */
|
||||
top: 50%;
|
||||
/* 垂直居中 */
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
</style>
|
||||
116
src/views/IProfile.vue
Normal file
116
src/views/IProfile.vue
Normal file
@@ -0,0 +1,116 @@
|
||||
<template>
|
||||
<div class="my-page">
|
||||
<!-- 顶部个人信息 -->
|
||||
<div class="profile-box">
|
||||
<van-row>
|
||||
<van-col span="6">
|
||||
<van-image
|
||||
:src="userStore.AvatarUrl"
|
||||
width="80"
|
||||
height="80"
|
||||
fit="cover"
|
||||
@click="showAvatar = true"
|
||||
/>
|
||||
</van-col>
|
||||
<van-col span="18" class="profile-info">
|
||||
<div class="nickname" @click="goDetail">{{ userStore.Nickname }}</div>
|
||||
<div class="userid" @click="goDetail">UID: {{ userStore.Id }}</div>
|
||||
<van-tag v-if="isOnline" type="success">在线</van-tag>
|
||||
<van-tag v-else type="danger">离线</van-tag>
|
||||
</van-col>
|
||||
</van-row>
|
||||
<van-button @click="isOnline = !isOnline">切换状态</van-button>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 功能列表 -->
|
||||
<van-cell-group>
|
||||
<van-cell title="个人资料" is-link />
|
||||
<van-cell title="我的订单" is-link />
|
||||
<van-cell title="消息通知" is-link />
|
||||
<van-cell title="设置" is-link />
|
||||
</van-cell-group>
|
||||
|
||||
<!-- 底部退出按钮 -->
|
||||
<div class="logout-box">
|
||||
<van-button type="danger" block @click="logout">退出登录</van-button>
|
||||
</div>
|
||||
|
||||
<!-- 全屏头像预览 -->
|
||||
<van-image-preview v-model:show="showAvatar" :images="[userStore.AvatarUrl]" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
|
||||
const userStore = useUserStore()
|
||||
const showAvatar = ref(false)
|
||||
const isOnline = ref(true)
|
||||
|
||||
onMounted(() => {
|
||||
console.log('isOnline:', userStore.IsOnline)
|
||||
// 初始化时检查用户是否在线
|
||||
isOnline.value = userStore.IsOnline
|
||||
})
|
||||
|
||||
function goDetail() {
|
||||
// 跳转详情页逻辑(功能为空)
|
||||
console.log('进入详情页')
|
||||
}
|
||||
|
||||
// 这里可以添加退出登录的逻辑,例如清除用户信息和访问令牌
|
||||
function logout() {
|
||||
// 清除用户信息和访问令牌
|
||||
userStore.clearUser()
|
||||
localStorage.removeItem('accessToken')
|
||||
localStorage.removeItem('refreshToken')
|
||||
// 跳转到登录页面
|
||||
window.location.href = '/login'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 外层容器铺满全屏 */
|
||||
.my-page {
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #f5f5f5;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 顶部信息卡片 */
|
||||
.profile-box {
|
||||
padding: 30px;
|
||||
background-color: #ffffff;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
/* 右侧文字布局 */
|
||||
.profile-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding-left: 12px;
|
||||
}
|
||||
|
||||
.nickname {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.userid {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.logout-box {
|
||||
margin: 20px;
|
||||
margin-top: auto 1; /* 保证按钮在底部 */
|
||||
}
|
||||
</style>
|
||||
198
src/views/Message/FriendMessage.vue
Normal file
198
src/views/Message/FriendMessage.vue
Normal file
@@ -0,0 +1,198 @@
|
||||
<template>
|
||||
<div class="send-message-page">
|
||||
<!-- 顶部导航栏 -->
|
||||
<van-nav-bar :title="friend.nickname" left-text="返回" left-arrow @click-left="$router.back()">
|
||||
<template #right>
|
||||
<van-tag type="success" v-if="friend.isonline">在线</van-tag>
|
||||
<van-tag type="danger" v-else>离线</van-tag>
|
||||
</template>
|
||||
</van-nav-bar>
|
||||
<!-- 消息窗口 -->
|
||||
<div class="messages" ref="messagesBox">
|
||||
<div v-for="msg in messages" :key="msg.id" :class="['message', msg.from === 'me' ? 'me' : 'other']">
|
||||
<div class="bubble">
|
||||
{{ msg.text }}
|
||||
</div>
|
||||
<!-- 已读回执 -->
|
||||
<div v-if="msg.from === 'me'" class="receipt">
|
||||
<van-icon :name="msg.isRead ? 'success' : 'clock-o'" :color="msg.isRead ? '#4a90e2' : '#999'" size="14" />
|
||||
<span :class="msg.isRead ? 'read' : 'unread'">
|
||||
{{ msg.isRead ? '已读' : '未读' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 输入框和发送按钮 -->
|
||||
<div class="input-box">
|
||||
<van-field v-model="newMessage" placeholder="输入消息..." @keyup.enter="sendMessage" />
|
||||
<van-button type="primary" @click="sendMessage">发送</van-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, nextTick, computed, watch } from 'vue'
|
||||
import { useFriendStore } from '@/stores/friend'
|
||||
import { connection } from '@/network/signalr'
|
||||
import { useMessagesStore } from '@/stores/messages'
|
||||
|
||||
// import { route } from '@/router'
|
||||
|
||||
|
||||
|
||||
const friendStore = useFriendStore()
|
||||
const friend = ref({})
|
||||
const newMessage = ref('')
|
||||
const messagesBox = ref(null)
|
||||
const messagesStore = useMessagesStore()
|
||||
|
||||
//const friend = computed(() => friendStore.currentFriend)
|
||||
|
||||
onMounted(() => {
|
||||
friend.value = friendStore.currentFriend
|
||||
getChatRecord(Number(friend.value.userid))
|
||||
// 进入聊天详情页时,把该好友的未读消息全部标记为已读
|
||||
const msgs = messagesStore.chats[friend.value.userid] || []
|
||||
msgs.forEach(msg => {
|
||||
if (!msg.isRead && msg.from === 'other') {
|
||||
msg.isRead = true
|
||||
connection.send("SendReadReceipt", msg.id)
|
||||
}
|
||||
})
|
||||
|
||||
})
|
||||
// 只取当前好友的消息
|
||||
const messages = computed(() => messagesStore.chats[friend.value.userid] || [])
|
||||
|
||||
watch(
|
||||
() => messages.value.length,
|
||||
(newLen, oldLen) => {
|
||||
if (newLen > oldLen) {
|
||||
const lastMsg = messages.value[newLen - 1]
|
||||
if (lastMsg && lastMsg.from === 'other' && !lastMsg.isRead) {
|
||||
lastMsg.isRead = true
|
||||
connection.send("SendReadReceipt", lastMsg.id)
|
||||
}
|
||||
scrollToBottom()
|
||||
}
|
||||
}
|
||||
)
|
||||
//
|
||||
async function getChatRecord(friendid) {
|
||||
try {
|
||||
const res = await connection.invoke("GetChatRecord", friendid)
|
||||
console.log('获取聊天记录:', res)
|
||||
|
||||
// 一次性转换并存储
|
||||
const msgs = res.map(msg => ({
|
||||
id: msg.MessageId,
|
||||
from: msg.IsMine ? 'me' : 'other',
|
||||
text: msg.Message,
|
||||
isRead: msg.IsRead
|
||||
}))
|
||||
messagesStore.setMessages(friendid, msgs)
|
||||
} catch (err) {
|
||||
console.error('获取聊天记录失败:', err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function sendMessage() {
|
||||
if (!newMessage.value.trim()) return
|
||||
connection.send("SendPrivateMessage", friend.value.userid.toString(), newMessage.value)
|
||||
//messages.value.push({ from: 'me', text: newMessage.value, id: Date.now(), isRead: false })
|
||||
console.log('发送消息:', newMessage.value)
|
||||
newMessage.value = ''
|
||||
scrollToBottom()
|
||||
}
|
||||
|
||||
function scrollToBottom() {
|
||||
nextTick(() => {
|
||||
if (messagesBox.value) {
|
||||
messagesBox.value.scrollTop = messagesBox.value.scrollHeight
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.send-message-page {
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
/* 消息窗口 */
|
||||
.messages {
|
||||
flex: 1;
|
||||
padding: 10px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.message {
|
||||
margin: 8px 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.message.me {
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.message.other {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.bubble {
|
||||
max-width: 70%;
|
||||
padding: 8px 12px;
|
||||
border-radius: 16px;
|
||||
font-size: 14px;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.message.me .bubble {
|
||||
background-color: #4a90e2;
|
||||
/* 蓝色气泡 */
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.message.other .bubble {
|
||||
background-color: #e5e5ea;
|
||||
/* 灰色气泡 */
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.receipt {
|
||||
font-size: 12px;
|
||||
margin-top: 2px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.read {
|
||||
color: #4a90e2;
|
||||
/* 蓝色已读 */
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.unread {
|
||||
color: #999;
|
||||
/* 灰色未读 */
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.input-box {
|
||||
display: flex;
|
||||
padding: 10px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.van-field {
|
||||
flex: 1;
|
||||
margin-right: 10px;
|
||||
}
|
||||
</style>
|
||||
115
src/views/Users/FriendDetail.vue
Normal file
115
src/views/Users/FriendDetail.vue
Normal file
@@ -0,0 +1,115 @@
|
||||
<template>
|
||||
<div class="friend-detail-page">
|
||||
<!-- 顶部导航栏 -->
|
||||
<van-nav-bar title="好友详情" left-text="返回" left-arrow @click-left="$router.back()" />
|
||||
|
||||
<!-- 好友信息卡片 -->
|
||||
<div class="profile-box">
|
||||
<van-image :src="friend.avatarurl" width="100" height="100" round fit="cover" />
|
||||
<div class="info">
|
||||
<div class="nickname">{{ friend.nickname }}</div>
|
||||
<div class="username">@{{ friend.username }}</div>
|
||||
<div class="signature">{{ friend.signature }}</div>
|
||||
<div class="userid">UID: {{ friend.userid }}</div>
|
||||
<div class="status">状态: {{ friend.isonline ? '在线' : '离线' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="actions">
|
||||
<van-button type="primary" block @click="sendMessage">发送消息</van-button>
|
||||
<div class="top"></div>
|
||||
<van-button type="success" block @click="addFriend">加为好友</van-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useFriendStore } from '@/stores/friend'
|
||||
import { connection } from '@/network/signalr'
|
||||
import { useRouter } from 'vue-router'
|
||||
const router = useRouter()
|
||||
|
||||
const friendStore = useFriendStore()
|
||||
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
console.log('好友详情页加载,好友信息:', friendStore.currentFriend)
|
||||
friend.value = friendStore.currentFriend
|
||||
})
|
||||
// 模拟好友数据(实际应从后端接口获取)
|
||||
const friend = ref({
|
||||
userid: 3, username: '王五', nickname: '13800000003', avatarurl: 'https://auth.zotv.ru/webapp/9666.webp',
|
||||
signature: '这是王五的个性签名', isonline: false
|
||||
})
|
||||
|
||||
function sendMessage() {
|
||||
console.log('跳转到聊天页面:', friend)
|
||||
// $router.push('/chat/' + friend.value.id)
|
||||
//friendStore.setFriend(friend.value)
|
||||
router.push(`/friendmessage`)
|
||||
}
|
||||
|
||||
async function addFriend() {
|
||||
console.log('添加好友:', friend.value)
|
||||
// 这里可以添加添加好友的逻辑,例如调用API发送好友请求
|
||||
await connection.invoke("AddFriend", friend.value.userid)
|
||||
.then(result => {
|
||||
console.log('添加好友结果:', result)
|
||||
// 这里可以根据result来更新UI,例如显示添加成功的提示
|
||||
})
|
||||
.catch(err => console.error("添加好友失败:", err));
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.friend-detail-page {
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
background-color: #f5f5f5;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.profile-box {
|
||||
background-color: #fff;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.info {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.nickname {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.username {
|
||||
font-size: 16px;
|
||||
color: #888;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.signature {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.userid {
|
||||
font-size: 12px;
|
||||
color: #aaa;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
margin: 30px;
|
||||
}
|
||||
.top {
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
||||
101
src/views/Users/SearchContacts.vue
Normal file
101
src/views/Users/SearchContacts.vue
Normal file
@@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<div class="add-friend-page">
|
||||
<!-- 顶部导航栏 -->
|
||||
<van-nav-bar title="添加好友" left-text="返回" left-arrow @click-left="$router.back()" />
|
||||
|
||||
<!-- 搜索框 -->
|
||||
<van-search v-model="searchValue" placeholder="输入昵称或ID搜索" @search="onSearch" />
|
||||
|
||||
<!-- 搜索结果列表 -->
|
||||
<van-empty v-if="results.length === 0" description="暂无搜索结果" />
|
||||
|
||||
<van-cell-group v-else>
|
||||
<van-cell v-for="user in results" :key="user.id" is-link @click="openUser(user)">
|
||||
<template #title>
|
||||
<div class="nickname">{{ user.nickname }}</div>
|
||||
<div class="username">@{{ user.username }}</div>
|
||||
</template>
|
||||
<template #label>
|
||||
<div class="signature">{{ user.signature }}</div>
|
||||
<div class="userid">UID: {{ user.userid }}</div>
|
||||
</template>
|
||||
<template #icon>
|
||||
<van-image :src="user.avatarurl" width="40" height="40" round fit="cover" />
|
||||
</template>
|
||||
</van-cell>
|
||||
</van-cell-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { connection } from '@/network/signalr'
|
||||
import { useFriendStore } from '@/stores/friend'
|
||||
import { useRouter } from 'vue-router'
|
||||
const router = useRouter()
|
||||
|
||||
const friendStore = useFriendStore()
|
||||
const searchValue = ref('')
|
||||
const results = ref([])
|
||||
|
||||
// 模拟搜索
|
||||
async function onSearch() {
|
||||
// 这里可以添加搜索逻辑,例如调用API获取搜索结果
|
||||
console.log('搜索联系人:', searchValue.value)
|
||||
await connection.invoke("SearchFriends", searchValue.value)
|
||||
.then(result => {
|
||||
console.log('搜索结果:', result)
|
||||
if (result.success) {
|
||||
results.value = []
|
||||
results.value.push(result)
|
||||
}else{
|
||||
results.value = []
|
||||
}
|
||||
// 这里可以将result赋值给results来更新搜索结果列表
|
||||
})
|
||||
.catch(err => console.error("搜索联系人失败:", err));
|
||||
}
|
||||
|
||||
function openUser(user) {
|
||||
console.log('进入用户详情:', user)
|
||||
// 这里可以用 $router.push('/user/' + user.id) 跳转详情页
|
||||
friendStore.setFriend(user)
|
||||
router.push(`/frienddetail`)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.add-friend-page {
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
background-color: #f5f5f5;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.nickname {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.username {
|
||||
font-size: 14px;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.signature {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.addfriend {
|
||||
margin-left: 70%;
|
||||
margin-top: -10%;
|
||||
/* margin-right: 100px; */
|
||||
}
|
||||
|
||||
.userid {
|
||||
font-size: 12px;
|
||||
color: #aaa;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user