import type { ImFacePackApi } from '#/api/im/face/pack'
|
import type { ImFaceUserItemApi } from '#/api/im/face/userItem'
|
|
import { ref } from 'vue'
|
|
import { acceptHMRUpdate, defineStore } from 'pinia'
|
|
import { getFacePackList as apiGetFacePackList } from '#/api/im/face/pack'
|
import { createFaceUserItem as apiCreateFaceUserItem, deleteFaceUserItem as apiDeleteFaceUserItem, getFaceUserItemList as apiGetFaceUserItemList } from '#/api/im/face/userItem'
|
|
/**
|
* IM 表情面板数据 store(系统表情包 + 个人表情)
|
*
|
* 不持久化:数据小、低频;每次进 IM 第一次打开表情面板时按需拉,关 tab 即丢弃
|
* - 系统包:IM 主页 onMounted 后台预拉(不阻塞首屏),消除面板首次展开的白屏
|
* - 个人表情:切到「收藏」tab / 长按消息「添加到表情」时按需拉
|
*/
|
export const useFaceStore = defineStore('imFace', () => {
|
|
/** 系统表情包列表(含每个包的 items);运营管理后台维护 */
|
const facePacks = ref<ImFacePackApi.FacePackUser[]>([])
|
/** 个人表情包列表(用户长按「添加到表情」/ 上传产生) */
|
const faceUserItems = ref<ImFaceUserItemApi.FaceUserItem[]>([])
|
|
/** clear() 时递增;旧账号请求返回后不写入新账号内存 */
|
let storeEpoch = 0
|
|
/**
|
* 系统表情包拉取 promise;ensureFacePackList 内 cache:
|
* - null = 还没拉过,下次调用真发请求
|
* - resolve 后保留对象 = 后续调用 await 立即返回,不再发请求
|
* - reject 后置回 null,让调用方下次重试
|
*/
|
let facePacksPromise: null | Promise<void> = null
|
/** 按需拉取系统表情包(已拉过则直接复用 cached promise) */
|
async function ensureFacePackList(): Promise<void> {
|
if (!facePacksPromise) {
|
const requestEpoch = storeEpoch
|
facePacksPromise = apiGetFacePackList()
|
.then((data) => {
|
if (requestEpoch !== storeEpoch) {
|
return
|
}
|
facePacks.value = data || []
|
})
|
.catch((error) => {
|
console.warn('[IM] 拉取表情包失败', error)
|
if (requestEpoch === storeEpoch) {
|
facePacksPromise = null
|
}
|
throw error
|
})
|
}
|
return facePacksPromise
|
}
|
|
/** 个人表情拉取 promise;语义同上 */
|
let faceUserItemsPromise: null | Promise<void> = null
|
/** 按需拉取个人表情(已拉过则直接复用 cached promise) */
|
async function ensureFaceUserItemList(): Promise<void> {
|
if (!faceUserItemsPromise) {
|
const requestEpoch = storeEpoch
|
faceUserItemsPromise = apiGetFaceUserItemList()
|
.then((data) => {
|
if (requestEpoch !== storeEpoch) {
|
return
|
}
|
faceUserItems.value = data || []
|
})
|
.catch((error) => {
|
console.warn('[IM] 拉取个人表情失败', error)
|
if (requestEpoch === storeEpoch) {
|
faceUserItemsPromise = null
|
}
|
throw error
|
})
|
}
|
return faceUserItemsPromise
|
}
|
|
/**
|
* 添加个人表情;服务端对同 URL 抛 FACE_USER_ITEM_DUPLICATED 错误
|
*
|
* 来源:1. 用户在表情面板「+」上传图片 2. 长按消息「添加到表情」
|
*/
|
async function addFaceUserItem(reqVO: ImFaceUserItemApi.FaceUserItemSaveReqVO): Promise<boolean> {
|
const requestEpoch = storeEpoch
|
const id = await apiCreateFaceUserItem(reqVO)
|
if (!id) {
|
return false
|
}
|
// 已切账号时跳过旧请求结果
|
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)
|
// 已切账号时跳过旧请求结果
|
if (requestEpoch !== storeEpoch) {
|
return false
|
}
|
faceUserItems.value = faceUserItems.value.filter((item) => item.id !== id)
|
return true
|
} catch (error) {
|
console.warn('[IM] 删除个人表情失败', { id }, error)
|
return false
|
}
|
}
|
|
/** 清空表情缓存 */
|
function clear(): void {
|
facePacks.value = []
|
faceUserItems.value = []
|
facePacksPromise = null
|
faceUserItemsPromise = null
|
storeEpoch++
|
}
|
|
return {
|
facePacks,
|
faceUserItems,
|
ensureFacePackList,
|
ensureFaceUserItemList,
|
addFaceUserItem,
|
removeFaceUserItem,
|
clear
|
}
|
})
|
|
/** 在 setup 外(路由守卫等)取 store 实例的工具方法 */
|
export const useFaceStoreWithOut = () => useFaceStore()
|
|
if (import.meta.hot) {
|
import.meta.hot.accept(acceptHMRUpdate(useFaceStore, import.meta.hot))
|
}
|