Show stale detail blocker while refreshing city data

This commit is contained in:
2569718930@qq.com
2026-04-18 23:14:25 +08:00
parent 25122fed4a
commit f521cb537e
4 changed files with 54 additions and 7 deletions
@@ -774,8 +774,39 @@
.root :global(.panel-body) {
overflow-y: auto;
flex: 1;
position: relative;
padding: 0 20px 24px;
}
.root :global(.panel-sync-blocker) {
position: sticky;
top: 0;
z-index: 6;
display: flex;
align-items: center;
gap: 10px;
margin: 0 0 12px;
padding: 12px 14px;
border: 1px solid rgba(34, 211, 238, 0.22);
border-radius: 8px;
background: rgba(8, 16, 30, 0.96);
color: var(--text-primary);
box-shadow: 0 14px 34px rgba(2, 6, 23, 0.38);
backdrop-filter: blur(18px);
font-size: 12px;
font-weight: 650;
line-height: 1.45;
}
.root :global(.panel-content-stale) {
opacity: 0.28;
pointer-events: none;
user-select: none;
}
.root :global(.panel-content-stale canvas) {
visibility: hidden;
}
.root :global(.panel-body::-webkit-scrollbar) {
width: 4px;
}
+14 -1
View File
@@ -196,6 +196,7 @@ export function DetailPanel() {
(detail.forecast?.daily?.length ?? 0) <= 1),
);
const isPanelSyncing = store.loadingState.cityDetail;
const isShowingCachedDetailDuringSync = Boolean(detail && isPanelSyncing);
const blurActiveElement = () => {
if (typeof document === "undefined") return;
@@ -384,7 +385,18 @@ export function DetailPanel() {
</div>
</div>
<div className="panel-body">
<div className={clsx("panel-body", isShowingCachedDetailDuringSync && "is-syncing")}>
{isShowingCachedDetailDuringSync ? (
<div className="panel-sync-blocker" role="status" aria-live="polite">
<span className="panel-loading-spinner" aria-hidden="true" />
<span>
{locale === "en-US"
? `Refreshing ${panelDisplayName}. Current cards are paused until the latest data arrives.`
: `正在刷新 ${panelDisplayName},最新数据返回前暂停展示旧卡片。`}
</span>
</div>
) : null}
<div className={clsx(isShowingCachedDetailDuringSync && "panel-content-stale")}>
{!hasBasicPanelContent ? (
<section className="detail-summary-shell detail-empty-state">
<div className="detail-section-head">
@@ -479,6 +491,7 @@ export function DetailPanel() {
{heavyContentReady ? <ForecastTable /> : null}
</>
)}
</div>
</div>
</aside>
);
+1 -5
View File
@@ -788,11 +788,7 @@ export function DashboardStoreProvider({
return;
}
const cachedDetail = cityDetailsByName[cityName];
const needsDetailRefresh = hasSparseDetailCoverage(
cachedDetail,
cachedDetail?.local_date,
);
const needsDetailRefresh = true;
setLoadingState((current) => ({ ...current, cityDetail: true }));
const detailPromise = ensureCityDetail(cityName, needsDetailRefresh, "panel");
void Promise.allSettled([summaryPromise, detailPromise])
+8 -1
View File
@@ -593,6 +593,13 @@ export function useLeafletMap({
}
async function maybeAutoShowNearbyStations() {
if (selectedCity && isLoadingDetail) {
autoNearbyCityRef.current = selectedCity;
clearNearbyRefreshTimer();
layer.clearLayers();
return;
}
if (handlingAutoNearbyRef.current) {
return;
}
@@ -700,7 +707,7 @@ export function useLeafletMap({
map.off("zoomend", syncVisibility);
map.off("moveend", maybeAutoShowNearbyStations);
};
}, [cityDetailsByName, selectedCity, selectedDetail, suspendMotion]);
}, [cityDetailsByName, selectedCity, selectedDetail, suspendMotion, isLoadingDetail]);
// Centralized City Selection Zoom Effect
// Higher level than selection: we only flyTo once the data is loaded (selectedDetail)