feat(dashboard): useDashboardData composable(7接口并发+section级refresh)
This commit is contained in:
parent
a48b6fd8fb
commit
ea62b609af
120
frontend/composables/useDashboardData.js
Normal file
120
frontend/composables/useDashboardData.js
Normal 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-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,
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user