feat(dashboard): useDashboardData composable(7接口并发+section级refresh)

This commit is contained in:
zheng020 2026-06-02 21:41:58 +08:00
parent a48b6fd8fb
commit ea62b609af

View File

@ -0,0 +1,120 @@
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-awarerefresh(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,
}
}