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-aware;refresh(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, } }