@@ -6,12 +6,39 @@ const sources = ref<any>(null);
|
||||
const notices = ref<any[]>([]);
|
||||
const loading = ref(false);
|
||||
const error = ref("");
|
||||
const loadedAt = ref("");
|
||||
const requestState = ref<Record<string, "idle" | "loading" | "ready" | "error">>({
|
||||
bootstrap: "idle",
|
||||
releases: "idle",
|
||||
sources: "idle",
|
||||
notices: "idle",
|
||||
});
|
||||
let loaded = false;
|
||||
|
||||
const endpointLabels: Record<string, string> = {
|
||||
"/api/client/bootstrap": "客户端启动配置",
|
||||
"/api/client/releases": "发布信息",
|
||||
"/api/client/sources": "接口源目录",
|
||||
"/api/client/notices": "版本日志",
|
||||
};
|
||||
|
||||
async function fetchJSON(path: string) {
|
||||
const res = await fetch(path);
|
||||
if (!res.ok) throw new Error(`${path} returned HTTP ${res.status}`);
|
||||
return res.json();
|
||||
let res: Response;
|
||||
try {
|
||||
res = await fetch(path, { headers: { Accept: "application/json" } });
|
||||
} catch {
|
||||
throw new Error(`${endpointLabels[path] || path} 暂时无法连接`);
|
||||
}
|
||||
if (!res.ok) throw new Error(`${endpointLabels[path] || path} 返回 HTTP ${res.status}`);
|
||||
try {
|
||||
return await res.json();
|
||||
} catch {
|
||||
throw new Error(`${endpointLabels[path] || path} 返回内容不是有效 JSON`);
|
||||
}
|
||||
}
|
||||
|
||||
function failureMessage(reason: unknown) {
|
||||
return reason instanceof Error ? reason.message : String(reason || "读取失败");
|
||||
}
|
||||
|
||||
export function usePortalState() {
|
||||
@@ -26,11 +53,16 @@ export function usePortalState() {
|
||||
const downloadUrl = computed(() => releases.value?.download_url || bootstrap.value?.release?.download_url || packages.value[0]?.url || "");
|
||||
const appVersion = computed(() => releases.value?.app_version || bootstrap.value?.release?.app_version || latestNotice.value?.version || "未发布");
|
||||
const serviceVersion = computed(() => bootstrap.value?.serviceVersion || "-");
|
||||
const isReady = computed(() => loaded && !loading.value && !error.value);
|
||||
const hasPartialData = computed(() => Boolean(bootstrap.value || releases.value || sources.value || notices.value.length));
|
||||
const releasesEmpty = computed(() => !loading.value && packages.value.length === 0 && notices.value.length === 0);
|
||||
const sourcesEmpty = computed(() => !loading.value && categories.value.length === 0);
|
||||
|
||||
async function load(force = false) {
|
||||
if (loaded && !force) return;
|
||||
loading.value = true;
|
||||
error.value = "";
|
||||
requestState.value = { bootstrap: "loading", releases: "loading", sources: "loading", notices: "loading" };
|
||||
try {
|
||||
const [bootstrapData, releaseData, sourceData, noticeData] = await Promise.allSettled([
|
||||
fetchJSON("/api/client/bootstrap"),
|
||||
@@ -38,15 +70,36 @@ export function usePortalState() {
|
||||
fetchJSON("/api/client/sources"),
|
||||
fetchJSON("/api/client/notices"),
|
||||
]);
|
||||
if (bootstrapData.status === "fulfilled") bootstrap.value = bootstrapData.value;
|
||||
if (releaseData.status === "fulfilled") releases.value = releaseData.value;
|
||||
if (sourceData.status === "fulfilled") sources.value = sourceData.value;
|
||||
if (noticeData.status === "fulfilled") notices.value = noticeData.value.items || [];
|
||||
if (bootstrapData.status === "fulfilled") {
|
||||
bootstrap.value = bootstrapData.value;
|
||||
requestState.value.bootstrap = "ready";
|
||||
} else {
|
||||
requestState.value.bootstrap = "error";
|
||||
}
|
||||
if (releaseData.status === "fulfilled") {
|
||||
releases.value = releaseData.value;
|
||||
requestState.value.releases = "ready";
|
||||
} else {
|
||||
requestState.value.releases = "error";
|
||||
}
|
||||
if (sourceData.status === "fulfilled") {
|
||||
sources.value = sourceData.value;
|
||||
requestState.value.sources = "ready";
|
||||
} else {
|
||||
requestState.value.sources = "error";
|
||||
}
|
||||
if (noticeData.status === "fulfilled") {
|
||||
notices.value = noticeData.value.items || [];
|
||||
requestState.value.notices = "ready";
|
||||
} else {
|
||||
requestState.value.notices = "error";
|
||||
}
|
||||
const firstFailure = [bootstrapData, releaseData, sourceData, noticeData].find((item) => item.status === "rejected") as PromiseRejectedResult | undefined;
|
||||
if (firstFailure && !bootstrap.value) error.value = firstFailure.reason?.message || String(firstFailure.reason);
|
||||
if (firstFailure && !hasPartialData.value) error.value = failureMessage(firstFailure.reason);
|
||||
loaded = true;
|
||||
loadedAt.value = new Date().toISOString();
|
||||
} catch (err) {
|
||||
error.value = err instanceof Error ? err.message : String(err);
|
||||
error.value = failureMessage(err);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
@@ -59,6 +112,8 @@ export function usePortalState() {
|
||||
notices,
|
||||
loading,
|
||||
error,
|
||||
loadedAt,
|
||||
requestState,
|
||||
packages,
|
||||
categories,
|
||||
latestNotice,
|
||||
@@ -68,6 +123,10 @@ export function usePortalState() {
|
||||
downloadUrl,
|
||||
appVersion,
|
||||
serviceVersion,
|
||||
isReady,
|
||||
hasPartialData,
|
||||
releasesEmpty,
|
||||
sourcesEmpty,
|
||||
load,
|
||||
sourceStatus,
|
||||
statusTone,
|
||||
|
||||
Reference in New Issue
Block a user