121 lines
3.2 KiB
JavaScript
121 lines
3.2 KiB
JavaScript
import { ref, computed } from 'vue'
|
||
import { dashboardApi } from '@/utils/api'
|
||
|
||
/**
|
||
* 数据看板聚合 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([
|
||
loadSection('today', dashboardApi.getTodayOverview),
|
||
loadSection('curve', dashboardApi.get7DayIncomeCurve),
|
||
loadSection('exhibition', dashboardApi.getExhibitionSummary),
|
||
loadSection('likeIncome', dashboardApi.getLikeIncomeByLevel),
|
||
loadSection('topAssets', dashboardApi.getTopAssets),
|
||
loadSection('levels', dashboardApi.getLevelDistribution),
|
||
loadSection('upgrades', dashboardApi.getUpgradeProgress),
|
||
])
|
||
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 fetcherMap = {
|
||
today: dashboardApi.getTodayOverview,
|
||
curve: dashboardApi.get7DayIncomeCurve,
|
||
exhibition: dashboardApi.getExhibitionSummary,
|
||
likeIncome: dashboardApi.getLikeIncomeByLevel,
|
||
topAssets: dashboardApi.getTopAssets,
|
||
levels: dashboardApi.getLevelDistribution,
|
||
upgrades: dashboardApi.getUpgradeProgress,
|
||
}
|
||
if (!fetcherMap[section]) return
|
||
return loadSection(section, fetcherMap[section])
|
||
}
|
||
return loadAll(force)
|
||
}
|
||
|
||
function dispose() {
|
||
// ref 状态由 Vue 自动 GC;保留接口给未来清理(如取消 in-flight 请求)
|
||
}
|
||
|
||
loadAll()
|
||
|
||
return {
|
||
loading,
|
||
error,
|
||
data,
|
||
refresh,
|
||
isReady,
|
||
lastFetched,
|
||
dispose,
|
||
}
|
||
}
|