feat(staffchat): 入口改造(StaffList/CustomerList微信行切新页)+useStaffProfile缓存+StatsPane固定列/金额格式+vitest依赖化

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
浅念
2026-08-25 19:56:22 +08:00
parent 4c3bc3ee93
commit 50635d0df8
9 changed files with 489 additions and 46 deletions
+58
View File
@@ -0,0 +1,58 @@
import { ref, watch, type Ref } from 'vue'
import { getAccounts } from '@/api'
import type { Account } from '@/api/types'
/**
* 员工资料(模块级缓存,全 app 一份请求)。avatar/nickname/phone 等。
*
* StaffChat(资料卡)与 StatsPane(员工微信列)原先各持一份相同的
* getAccounts(wechat, 100) + find 实现——提取为 composable 并加模块级缓存。
* 一次响应的 100 条整页入缓存,同页其它员工切换 owner 直接命中不再请求;
* 未命中/失败写 null(负缓存),避免重复打失败请求。
*/
const cache = new Map<string, Account | null>()
/** 命中缓存直接回调;否则请求一页 wechat 账号,整页回写缓存后回调。 */
async function fetchProfile(ownerId: string, apply: (a: Account | null) => void): Promise<void> {
if (cache.has(ownerId)) {
apply(cache.get(ownerId) ?? null)
return
}
try {
const res = await getAccounts({ channel: 'wechat', page: 1, page_size: 100 })
for (const a of res.items) cache.set(a.account_id, a)
// find 不到也写 null(负缓存)
const hit = cache.get(ownerId) ?? null
cache.set(ownerId, hit)
apply(hit)
} catch {
cache.set(ownerId, null)
apply(null)
}
}
/**
* @param ownerId 员工 account_id(wxid);响应式传入时自动随变更重载
* @returns profile 资料卡(null=未命中/失败,调用方自行兜底显示 ownerId);
* load 手动重载入口(watch immediate 已覆盖常规场景,一般无需调用)
*/
export function useStaffProfile(ownerId: string | Ref<string>): {
profile: Ref<Account | null>
load: () => void
} {
const idRef = typeof ownerId === 'string' ? ref(ownerId) : ownerId
const profile = ref<Account | null>(cache.get(idRef.value) ?? null)
// token 守卫: ownerId 快速切换时,旧 in-flight 响应不回写新 profile
let token = 0
function load(): void {
const my = ++token
const id = idRef.value
void fetchProfile(id, (a) => {
if (my === token) profile.value = a
})
}
watch(idRef, load, { immediate: true })
return { profile, load }
}