admin-vue3/src/views/im/home/store/friendStore.ts

203 lines
7.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import { defineStore, acceptHMRUpdate } from 'pinia'
import { store } from '@/store'
import { CommonStatusEnum } from '@/utils/constants'
import {
getMyFriendList as apiGetMyFriendList,
getFriend as apiGetFriend,
addFriend as apiAddFriend,
deleteFriend as apiDeleteFriend,
updateFriend as apiUpdateFriend,
type ImFriendRespVO
} from '@/api/im/friend'
import { useConversationStore } from './conversationStore'
import { ImConversationType } from '../../utils/constants'
import { getFriendDisplayName } from '../../utils/user'
import type { Friend } from '../types'
/**
* IM 好友 Store
*
* 负责:
* - 拉取 / 缓存当前登录用户的好友列表
* - 加好友 / 删好友(走后端 API + 本地乐观同步)
* - 被 ConversationItem / FriendPage / MessageInput 等多处消费
*/
export const useFriendStore = defineStore('imFriendStore', {
state: () => ({
friends: [] as Friend[],
loaded: false
}),
getters: {
/** 按 friendUserId 找好友(含已软删的 DISABLE 记录,调用方自行判定) */
getFriend:
(state) =>
(friendUserId: number): Friend | undefined => {
return state.friends.find((f) => f.friendUserId === friendUserId)
},
/** 当前生效的好友列表(过滤掉 DISABLE 软删记录) */
getActiveFriends: (state): Friend[] => {
return state.friends.filter((f) => f.status !== CommonStatusEnum.DISABLE)
},
/** 判断对方是否是当前用户的有效好友(存在 + 非 DISABLE */
isFriend() {
return (friendUserId: number): boolean => {
const entry = this.getFriend(friendUserId)
return !!entry && entry.status !== CommonStatusEnum.DISABLE
}
}
},
actions: {
/** 从后端拉取并覆盖本地列表;同步刷新对应私聊会话的展示名 / 头像 */
async loadFriends(force = false) {
if (this.loaded && !force) {
return
}
const list = await apiGetMyFriendList()
this.friends = (list || []).map(convertFriend)
this.loaded = true
// 同步 conversationStore 私聊会话的展示名 / 头像 / 免打扰
const conversationStore = useConversationStore()
for (const f of this.friends) {
conversationStore.updateConversation(ImConversationType.PRIVATE, f.friendUserId, {
name: getFriendDisplayName(f),
avatar: f.avatar,
muted: f.muted
})
}
},
/** 按 friendUserId 获取详情并合并到本地(保证 nickname / avatar 最新) */
async loadFriendInfo(friendUserId: number) {
try {
const data = await apiGetFriend(friendUserId)
if (!data) {
return
}
this.upsertFriend(convertFriend(data))
} catch (e) {
console.warn('[IM friendStore] loadFriendInfo 失败', e)
}
},
/** 添加好友:后端双向建立关系后,本地占位插入(服务端返回后可 loadFriends 刷新) */
async addFriend(friendUserId: number, preview?: Partial<Friend>) {
await apiAddFriend(friendUserId)
if (preview) {
this.upsertFriend({
friendUserId,
nickname: preview.nickname || String(friendUserId),
avatar: preview.avatar,
status: CommonStatusEnum.ENABLE
})
} else {
await this.loadFriendInfo(friendUserId)
}
},
/** 删除好友(软删,保留记录但置 DISABLE同时级联清理本地私聊会话 */
async deleteFriend(friendUserId: number) {
await apiDeleteFriend(friendUserId)
this.removeFriend(friendUserId)
},
/** 本地合并 / 新增某个好友WebSocket 事件 & 手动刷新都用) */
upsertFriend(friend: Friend) {
// 按 friendUserId 查已有记录下标:>=0 命中则覆盖合并,<0 则追加
const index = this.friends.findIndex((f) => f.friendUserId === friend.friendUserId)
if (index >= 0) {
this.friends[index] = {
...this.friends[index],
...friend,
status: friend.status ?? CommonStatusEnum.ENABLE
}
} else {
this.friends.push({
...friend,
status: friend.status ?? CommonStatusEnum.ENABLE
})
}
// 同步对应私聊会话的展示
const conversationStore = useConversationStore()
const merged = this.getFriend(friend.friendUserId)
conversationStore.updateConversation(ImConversationType.PRIVATE, friend.friendUserId, {
name: merged ? getFriendDisplayName(merged) : friend.nickname,
avatar: friend.avatar,
muted: friend.muted
})
},
/** 本地标记删除WebSocket FRIEND_DEL 事件触发;同时级联清私聊会话) */
removeFriend(friendUserId: number) {
// 软删:保留记录但置为 DISABLE避免后续误判"陌生人"
const friend = this.getFriend(friendUserId)
if (friend) {
friend.status = CommonStatusEnum.DISABLE
friend.deleteTime = Date.now()
}
// 级联清理:把对应的私聊会话也软删,避免会话列表里留着已删好友
const conversationStore = useConversationStore()
conversationStore.removePrivateConversation(friendUserId)
},
/** 切换免打扰 */
async setMuted(friendUserId: number, muted: boolean) {
await apiUpdateFriend({ friendUserId, muted })
const friend = this.getFriend(friendUserId)
if (friend) {
friend.muted = muted
}
},
/**
* 修改好友展示备注(仅自己可见)
*
* 走后端 /im/friend/update 接口;保存成功后再同步本地 friend + 会话列表 name
* 失败就直接抛给上层,让 UI 决定是否回滚 / 提示用户
*/
async setDisplayName(friendUserId: number, displayName: string) {
const value = displayName.trim()
// 后端的 displayName 语义null/undefined = 不改,"" = 清空,所以这里直接传 value可能是空串
await apiUpdateFriend({ friendUserId, displayName: value })
const friend = this.getFriend(friendUserId)
if (friend) {
friend.displayName = value
const conversationStore = useConversationStore()
conversationStore.updateConversation(ImConversationType.PRIVATE, friendUserId, {
name: getFriendDisplayName(friend)
})
}
},
/** 切换用户时清空 */
clear() {
this.friends = []
this.loaded = false
}
}
})
function convertFriend(vo: ImFriendRespVO): Friend {
return {
id: vo.id,
friendUserId: vo.friendUserId,
nickname: vo.nickname || String(vo.friendUserId),
avatar: vo.avatar,
muted: !!vo.muted,
displayName: vo.displayName || '',
status: vo.status,
addTime: vo.addTime ? new Date(vo.addTime).getTime() : undefined,
deleteTime: vo.deleteTime ? new Date(vo.deleteTime).getTime() : undefined
}
}
export const useFriendStoreWithOut = () => useFriendStore(store)
// dev: 让 Pinia 的 actions / state 改动支持 HMR避免每次改 store 都得硬刷
// 否则 Vite 把新模块推下来后,老 store 实例的 action 闭包仍指向旧函数体
if (import.meta.hot) {
import.meta.hot.accept(acceptHMRUpdate(useFriendStore, import.meta.hot))
}