fix(web): 次级页稳定外壳与缓存预取,跨部署导航自愈(BUG-966/967)

This commit is contained in:
jesse-ux
2026-09-18 18:45:48 +08:00
parent dccedf372e
commit 9ccb65b71f
25 changed files with 1066 additions and 160 deletions
+31 -10
View File
@@ -6,6 +6,12 @@ import { fetchChartView } from "@/lib/chart-view-client";
import type { ChartViewResponse } from "@/lib/chart-view-contract";
import type { ChartViewLayer } from "@/lib/chart-view-engine";
import { CHART_VIEW_COPY } from "@/lib/chart-view-labels";
import {
peekChartPage,
refreshChartPage,
writeChartPage,
type ChartPageSnapshot,
} from "@/lib/secondary-page-data";
function unavailableBody(): ChartViewResponse {
return { status: "chart_unavailable", billed: false, message: CHART_VIEW_COPY.unavailable };
@@ -19,29 +25,43 @@ function warnClientFailure(label: string, error: unknown, extra?: Record<string,
});
}
function viewFromSnapshot(snapshot: ChartPageSnapshot | null): ChartViewResponse | null {
return snapshot?.kind === "view" ? snapshot.view : null;
}
export function useChartPage() {
const [view, setView] = useState<ChartViewResponse | null>(null);
const [view, setView] = useState<ChartViewResponse | null>(() => viewFromSnapshot(peekChartPage()));
const [pendingLayers, setPendingLayers] = useState<ReadonlySet<ChartViewLayer>>(new Set());
const loadedLayers = useRef(new Set<ChartViewLayer>());
const inflightLayers = useRef(new Set<ChartViewLayer>());
useEffect(() => {
const controller = new AbortController();
void fetchChartView({ signal: controller.signal })
.then((result) => {
if (controller.signal.aborted) return;
if (result.httpStatus === 401) {
const cached = peekChartPage();
if (cached?.kind === "unauthenticated") {
window.location.assign("/login");
return;
}
let cancelled = false;
void refreshChartPage()
.then((snapshot) => {
if (cancelled) return;
if (snapshot.kind === "unauthenticated") {
window.location.assign("/login");
return;
}
setView(result.body ?? unavailableBody());
setView(snapshot.view);
})
.catch((error: unknown) => {
if (controller.signal.aborted) return;
if (cancelled) return;
warnClientFailure("chart_page_fetch_failed", error);
setView(unavailableBody());
if (peekChartPage()?.kind === "view") return;
const fallback = unavailableBody();
writeChartPage({ kind: "view", view: fallback });
setView(fallback);
});
return () => controller.abort();
return () => {
cancelled = true;
};
}, []);
const requestLayer = useCallback((layer: ChartViewLayer) => {
@@ -57,6 +77,7 @@ export function useChartPage() {
.then((result) => {
if (result.body?.status === "ok") {
loadedLayers.current.add(layer);
writeChartPage({ kind: "view", view: result.body });
setView(result.body);
}
})