158 lines
5.1 KiB
TypeScript
158 lines
5.1 KiB
TypeScript
import { defineStore, acceptHMRUpdate } from 'pinia'
|
||
import { ref } from 'vue'
|
||
import { store } from '@/store'
|
||
|
||
import {
|
||
getFacePackList as apiGetFacePackList,
|
||
type ImFacePackUserVO
|
||
} from '@/api/im/face/pack'
|
||
import {
|
||
getFaceUserItemList as apiGetFaceUserItemList,
|
||
createFaceUserItem as apiCreateFaceUserItem,
|
||
deleteFaceUserItem as apiDeleteFaceUserItem,
|
||
type ImFaceUserItemVO,
|
||
type ImFaceUserItemSaveReqVO
|
||
} from '@/api/im/face/useritem'
|
||
|
||
/**
|
||
* IM 表情面板数据 store(系统表情包 + 个人表情)
|
||
*
|
||
* 不持久化:数据小、低频;每次进 IM 第一次打开表情面板时按需拉,关 tab 即丢弃
|
||
* - 系统包:IM 主页 onMounted 后台预拉(不阻塞首屏),消除面板首次展开的白屏
|
||
* - 个人表情:切到「收藏」tab / 长按消息「添加到表情」时按需拉
|
||
*/
|
||
export const useFaceStore = defineStore('imFace', () => {
|
||
|
||
/** 系统表情包列表(含每个包的 items);运营管理后台维护 */
|
||
const facePacks = ref<ImFacePackUserVO[]>([])
|
||
/** 个人表情包列表(用户长按「添加到表情」/ 上传产生) */
|
||
const faceUserItems = ref<ImFaceUserItemVO[]>([])
|
||
|
||
/** reset() 时递增;旧账号那次还没返回的请求 resolve 后比对一致才写 store,防跨账号数据泄漏 */
|
||
let storeEpoch = 0
|
||
|
||
/**
|
||
* 系统表情包拉取 promise;ensureFacePacks 内 cache:
|
||
* - null = 还没拉过,下次调用真发请求
|
||
* - resolve 后保留对象 = 后续调用 await 立即返回,不再发请求
|
||
* - reject 后置回 null,让调用方下次重试
|
||
*/
|
||
let facePacksPromise: Promise<void> | null = null
|
||
/** 按需拉取系统表情包(已拉过则直接复用 cached promise) */
|
||
async function ensureFacePacks(): Promise<void> {
|
||
if (!facePacksPromise) {
|
||
const requestEpoch = storeEpoch
|
||
facePacksPromise = apiGetFacePackList()
|
||
.then((data) => {
|
||
if (requestEpoch !== storeEpoch) {
|
||
return
|
||
}
|
||
facePacks.value = data || []
|
||
})
|
||
.catch((e) => {
|
||
console.warn('[IM] 拉取表情包失败', e)
|
||
if (requestEpoch === storeEpoch) {
|
||
facePacksPromise = null
|
||
}
|
||
throw e
|
||
})
|
||
}
|
||
return facePacksPromise
|
||
}
|
||
|
||
/** 个人表情拉取 promise;语义同上 */
|
||
let faceUserItemsPromise: Promise<void> | null = null
|
||
/** 按需拉取个人表情(已拉过则直接复用 cached promise) */
|
||
async function ensureFaceUserItems(): Promise<void> {
|
||
if (!faceUserItemsPromise) {
|
||
const requestEpoch = storeEpoch
|
||
faceUserItemsPromise = apiGetFaceUserItemList()
|
||
.then((data) => {
|
||
if (requestEpoch !== storeEpoch) {
|
||
return
|
||
}
|
||
faceUserItems.value = data || []
|
||
})
|
||
.catch((e) => {
|
||
console.warn('[IM] 拉取个人表情失败', e)
|
||
if (requestEpoch === storeEpoch) {
|
||
faceUserItemsPromise = null
|
||
}
|
||
throw e
|
||
})
|
||
}
|
||
return faceUserItemsPromise
|
||
}
|
||
|
||
/**
|
||
* 添加个人表情;服务端对同 URL 抛 FACE_USER_ITEM_DUPLICATED 错误
|
||
*
|
||
* 来源:1. 用户在表情面板「+」上传图片 2. 长按消息「添加到表情」
|
||
*/
|
||
async function addFaceUserItem(reqVO: ImFaceUserItemSaveReqVO): Promise<boolean> {
|
||
const requestEpoch = storeEpoch
|
||
const id = await apiCreateFaceUserItem(reqVO)
|
||
if (!id) {
|
||
return false
|
||
}
|
||
// reset 已切账号:旧请求拿到的 id 不能再 unshift 进新账号内存
|
||
if (requestEpoch !== storeEpoch) {
|
||
return false
|
||
}
|
||
// id 不在缓存里才插入;服务端唯一约束兜底了 race,本地理论上不会拿到重复 id
|
||
if (!faceUserItems.value.some((item) => item.id === id)) {
|
||
faceUserItems.value.unshift({
|
||
id,
|
||
url: reqVO.url,
|
||
name: reqVO.name,
|
||
width: reqVO.width,
|
||
height: reqVO.height
|
||
})
|
||
}
|
||
return true
|
||
}
|
||
|
||
/** 删除个人表情;本地立即移除 */
|
||
async function removeFaceUserItem(id: number): Promise<boolean> {
|
||
const requestEpoch = storeEpoch
|
||
try {
|
||
await apiDeleteFaceUserItem(id)
|
||
// reset 已切账号:不要再 filter 新账号列表
|
||
if (requestEpoch !== storeEpoch) {
|
||
return false
|
||
}
|
||
faceUserItems.value = faceUserItems.value.filter((item) => item.id !== id)
|
||
return true
|
||
} catch (e) {
|
||
console.warn('[IM] 删除个人表情失败', { id }, e)
|
||
return false
|
||
}
|
||
}
|
||
|
||
/** 切账号 / 退出 IM 时清空缓存,避免下个用户看到上一用户的个人表情 */
|
||
function reset(): void {
|
||
facePacks.value = []
|
||
faceUserItems.value = []
|
||
facePacksPromise = null
|
||
faceUserItemsPromise = null
|
||
storeEpoch++
|
||
}
|
||
|
||
return {
|
||
facePacks,
|
||
faceUserItems,
|
||
ensureFacePacks,
|
||
ensureFaceUserItems,
|
||
addFaceUserItem,
|
||
removeFaceUserItem,
|
||
reset
|
||
}
|
||
})
|
||
|
||
/** 在 setup 外(路由守卫等)取 store 实例的工具方法 */
|
||
export const useFaceStoreWithOut = () => useFaceStore(store)
|
||
|
||
if (import.meta.hot) {
|
||
import.meta.hot.accept(acceptHMRUpdate(useFaceStore, import.meta.hot))
|
||
}
|