topfans/frontend/composables/useDashboardData.js
zheng020 3676fc977e refactor(dashboard): useDashboardData 提取 SECTION_FETCHERS 模块级单例
代码评审建议:loadAll 与 refresh 各有一份相同的 7 项 fetcher 映射,
且 refresh 每次调用都重建对象。提取为模块级单例后:
- 消除重复(单一信源)
- 节省每次 refresh 的对象分配
- loadAll 用 Object.entries 驱动而非手写 7 行
2026-06-03 01:20:48 +08:00

120 lines
3.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { ref, computed } from 'vue'
import { dashboardApi } from '@/utils/api'
// section 名 → fetcher 映射模块级单例loadAll 与 refresh 复用
const SECTION_FETCHERS = {
today: dashboardApi.getTodayOverview,
curve: dashboardApi.get7DayIncomeCurve,
exhibition: dashboardApi.getExhibitionSummary,
likeIncome: dashboardApi.getLikeIncomeByLevel,
topAssets: dashboardApi.getTopAssets,
levels: dashboardApi.getLevelDistribution,
upgrades: dashboardApi.getUpgradeProgress,
}
/**
* 数据看板聚合 composable
* - 7 个接口并发Promise.allSettled单失败不阻塞
* - 按 section 维度暴露 loading/error/data
* - 30 分钟内 refresh 不重发请求lastFetched 缓存)
*
* @param {object} options
* @param {number|null} options.starId 顶粉星城 ID
*/
export function useDashboardData({ starId = null } = {}) {
const loading = ref({
overall: false,
today: false,
curve: false,
exhibition: false,
likeIncome: false,
topAssets: false,
levels: false,
upgrades: false,
})
const error = ref({
today: null,
curve: null,
exhibition: null,
likeIncome: null,
topAssets: null,
levels: null,
upgrades: null,
})
const data = ref({
today: null,
curve: null,
exhibition: null,
likeIncome: null,
topAssets: null,
levels: null,
upgrades: null,
})
const lastFetched = ref(0)
const STALE_MS = 30 * 60 * 1000
const isReady = computed(() =>
Object.values(data.value).every((v) => v !== null && v !== undefined)
)
// 内部辅助:单 section 加载data 解包在 boundary 处进行
async function loadSection(section, fetcher) {
loading.value[section] = true
error.value[section] = null
try {
const result = await fetcher(starId)
data.value[section] = result?.data ?? result
} catch (e) {
error.value[section] = e?.message || '加载失败'
data.value[section] = null
} finally {
loading.value[section] = false
}
}
// 全量加载
async function loadAll(force = false) {
if (!force && Date.now() - lastFetched.value < STALE_MS) return
loading.value.overall = true
try {
await Promise.allSettled(
Object.entries(SECTION_FETCHERS).map(([section, fetcher]) =>
loadSection(section, fetcher)
)
)
lastFetched.value = Date.now()
} finally {
loading.value.overall = false
}
}
// 局部刷新refresh('curve') 只刷一个refresh() 全量 cache-awarerefresh(true) 全量强制
async function refresh(section, force = false) {
if (section) {
const fetcher = SECTION_FETCHERS[section]
if (!fetcher) return
return loadSection(section, fetcher)
}
return loadAll(force)
}
function dispose() {
// ref 状态由 Vue 自动 GC保留接口给未来清理如取消 in-flight 请求)
}
loadAll()
return {
loading,
error,
data,
refresh,
isReady,
lastFetched,
dispose,
}
}