136 lines
3.8 KiB
JavaScript
136 lines
3.8 KiB
JavaScript
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-aware;refresh(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,
|
||
}
|
||
}
|