Add loading indicators for city detail sync
This commit is contained in:
@@ -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 && (
|
||||
|
||||
Reference in New Issue
Block a user