topfans/frontend/composables/useDashboardData.js
2026-06-03 01:20:51 +08:00

136 lines
3.8 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'
import * as mock from '@/utils/mock/dashboard'
// USE_MOCK_API 是写死常量——直接内联短路逻辑,绕开 api.js 的 dashboardRequest 链路
// 原因:标准基座 + Vite HMR 在 App 端会让 api.js 内部的 async 包装 + signal 透传
// 导致 useDashboardData.loadSection 里的 await 永远不 resolve。
// 直接调 mock 工厂200-600ms 内必定 resolve。
const MOCK_FACTORIES = {
today: mock.mockTodayOverview,
curve: mock.mock7DayIncomeCurve,
exhibition: mock.mockExhibitionSummary,
likeIncome: mock.mockLikeIncomeByLevel,
topAssets: mock.mockTopAssets,
levels: mock.mockLevelDistribution,
upgrades: mock.mockUpgradeProgress,
}
// 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 加载
async function loadSection(section, fetcher) {
loading.value[section] = true
error.value[section] = null
try {
// USE_MOCK_API 走 mock直接调本地工厂绕开 api.js 的 dashboardRequest 包装
// 解决标准基座 + Vite HMR 下 await fetcher(starId, { signal }) 永远不 resolve 的问题
const mockFactory = MOCK_FACTORIES[section]
const result = mockFactory
? await mockFactory({ star_id: starId })
: 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(null, true) 全量强制
async function refresh(section, force = false) {
if (section) {
const fetcher = SECTION_FETCHERS[section]
if (!fetcher) return
return loadSection(section, fetcher)
}
return loadAll(force)
}
// 初次加载
loadAll()
return {
loading,
error,
data,
refresh,
isReady,
lastFetched,
}
}