Add loading indicators for city detail sync

This commit is contained in:
2569718930@qq.com
2026-04-09 08:29:02 +08:00
parent 799abd71b0
commit 24ef8ee8be
3 changed files with 102 additions and 0 deletions
@@ -606,6 +606,30 @@
margin-bottom: 6px;
}
.root :global(.panel-loading-hint) {
display: inline-flex;
align-items: center;
gap: 8px;
margin-bottom: 10px;
padding: 6px 10px;
border-radius: 999px;
border: 1px solid rgba(34, 211, 238, 0.18);
background: rgba(12, 24, 42, 0.78);
color: var(--accent-cyan);
font-size: 11px;
font-weight: 600;
letter-spacing: 0.2px;
}
.root :global(.panel-loading-spinner) {
width: 10px;
height: 10px;
border-radius: 999px;
border: 2px solid rgba(34, 211, 238, 0.22);
border-top-color: rgba(34, 211, 238, 0.92);
animation: loading-spin 0.8s linear infinite;
}
.root :global(.panel-meta) {
display: flex;
align-items: center;
@@ -613,6 +637,46 @@
flex-wrap: wrap;
}
.root :global(.city-loading-toast) {
position: fixed;
top: 78px;
left: 50%;
transform: translateX(-50%);
z-index: 1200;
display: inline-flex;
align-items: center;
gap: 10px;
padding: 10px 14px;
border-radius: 999px;
border: 1px solid rgba(34, 211, 238, 0.18);
background: linear-gradient(
180deg,
rgba(10, 18, 34, 0.94),
rgba(10, 18, 34, 0.82)
);
box-shadow:
0 18px 40px rgba(2, 6, 23, 0.38),
0 0 0 1px rgba(34, 211, 238, 0.04) inset;
backdrop-filter: blur(18px);
pointer-events: none;
}
.root :global(.city-loading-dot) {
width: 10px;
height: 10px;
border-radius: 999px;
background: var(--accent-cyan);
box-shadow: 0 0 0 0 rgba(34, 211, 238, 0.5);
animation: city-loading-pulse 1.35s ease-out infinite;
}
.root :global(.city-loading-copy) {
color: var(--text-primary);
font-size: 12px;
font-weight: 600;
letter-spacing: 0.25px;
}
.root :global(.pro-locked) {
filter: grayscale(0.8) opacity(0.7);
position: relative;
@@ -1596,6 +1660,21 @@
}
}
@keyframes city-loading-pulse {
0% {
transform: scale(0.92);
box-shadow: 0 0 0 0 rgba(34, 211, 238, 0.44);
}
70% {
transform: scale(1);
box-shadow: 0 0 0 10px rgba(34, 211, 238, 0);
}
100% {
transform: scale(0.92);
box-shadow: 0 0 0 0 rgba(34, 211, 238, 0);
}
}
@keyframes radar-sweep {
from {
transform: rotate(0deg);
@@ -317,6 +317,16 @@ export function DetailPanel() {
</button>
<div className="panel-title-area">
<h2>{panelDisplayName.toUpperCase()}</h2>
{store.loadingState.cityDetail && (
<div className="panel-loading-hint" role="status" aria-live="polite">
<span className="panel-loading-spinner" aria-hidden="true" />
<span>
{locale === "en-US"
? `Syncing ${panelDisplayName}...`
: `正在同步 ${panelDisplayName}...`}
</span>
</div>
)}
<div className="panel-meta">
<span className={clsx("risk-badge", panelRiskLevel)}>
{getRiskBadgeLabel(panelRiskLevel, locale)}
@@ -45,6 +45,11 @@ const FutureForecastModal = dynamic(
function DashboardScreen() {
const store = useDashboardStore();
const { t } = useI18n();
const activeCityName =
store.selectedDetail?.display_name ||
store.cities.find((city) => city.name === store.selectedCity)?.display_name ||
store.selectedCity ||
"";
useEffect(() => {
const onKeyDown = (event: KeyboardEvent) => {
@@ -79,6 +84,14 @@ function DashboardScreen() {
<HeaderBar />
<CitySidebar />
<DetailPanel />
{store.loadingState.cityDetail && activeCityName ? (
<div className="city-loading-toast" role="status" aria-live="polite">
<span className="city-loading-dot" aria-hidden="true" />
<span className="city-loading-copy">
{t("dashboard.loading")} {activeCityName}
</span>
</div>
) : null}
{store.historyState.isOpen && <HistoryModal />}
{store.futureModalDate && <FutureForecastModal />}
{showLoading && (