Show forecast completion state in dashboard panels

This commit is contained in:
2569718930@qq.com
2026-04-18 01:04:36 +08:00
parent 37f4705f9c
commit 16033033a7
3 changed files with 86 additions and 16 deletions
@@ -1245,9 +1245,30 @@
}
.root :global(.forecast-inline-note) {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 10px;
padding: 8px 10px;
border: 1px solid rgba(34, 211, 238, 0.18);
border-radius: 8px;
color: var(--text-secondary);
background: rgba(34, 211, 238, 0.06);
font-size: 12px;
line-height: 1.45;
}
.root :global(.forecast-inline-note::before) {
content: "";
width: 8px;
height: 8px;
flex: 0 0 auto;
border-radius: 999px;
background: #22d3ee;
box-shadow: 0 0 0 4px rgba(34, 211, 238, 0.12);
animation: pulseGlow 1.25s ease-in-out infinite;
}
.root :global(.forecast-day) {
background: rgba(255, 255, 255, 0.03);
border: 1px solid var(--border-subtle);
@@ -1287,6 +1308,28 @@
color: var(--accent-cyan);
}
.root :global(.forecast-day-sync) {
cursor: wait;
opacity: 0.76;
background:
linear-gradient(90deg, rgba(15, 23, 42, 0.86), rgba(30, 41, 59, 0.72), rgba(15, 23, 42, 0.86));
background-size: 220% 100%;
animation: panelSkeletonSweep 1.35s ease-in-out infinite;
}
.root :global(.forecast-day-sync .f-temp) {
color: var(--text-muted);
}
@keyframes panelSkeletonSweep {
0% {
background-position: 120% 0;
}
100% {
background-position: -120% 0;
}
}
@media (max-width: 720px) {
.root :global(.forecast-table) {
display: flex;
+13 -3
View File
@@ -190,6 +190,12 @@ export function DetailPanel() {
const heroSettlementLabel =
detail?.current?.settlement_source_label || basicSettlementLabel;
const heroAirportLabel = detail?.risk?.airport || basicAirportLabel;
const isDetailCompleting = Boolean(
detail &&
(detail.detail_depth !== "full" ||
(detail.forecast?.daily?.length ?? 0) <= 1),
);
const isPanelSyncing = store.loadingState.cityDetail || isDetailCompleting;
const blurActiveElement = () => {
if (typeof document === "undefined") return;
@@ -309,13 +315,17 @@ export function DetailPanel() {
</div>
<h2>{panelDisplayName}</h2>
</div>
{store.loadingState.cityDetail && (
{isPanelSyncing && (
<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}...`}
? isDetailCompleting
? `Completing ${panelDisplayName} cards...`
: `Syncing ${panelDisplayName}...`
: isDetailCompleting
? `正在补齐 ${panelDisplayName} 卡片...`
: `正在同步 ${panelDisplayName}...`}
</span>
</div>
)}
+30 -13
View File
@@ -993,11 +993,15 @@ export function ModelForecast({
export function ForecastTable() {
const store = useDashboardStore();
const { data } = useCityData();
const { t } = useI18n();
const { locale, t } = useI18n();
if (!data) return null;
const daily = data.forecast?.daily || [];
const isSparseDaily = daily.length <= 1;
const isForecastCompleting =
store.loadingState.cityDetail ||
data.detail_depth !== "full" ||
isSparseDaily;
const resolveForecastTemp = (date: string, fallback: number | null | undefined) => {
const debPrediction = data.multi_model_daily?.[date]?.deb?.prediction;
return debPrediction ?? fallback ?? null;
@@ -1006,17 +1010,14 @@ export function ForecastTable() {
<section className="forecast-section">
<h3>{t("forecast.title")}</h3>
{isSparseDaily && (
<div
className="forecast-inline-note"
style={{
color: "var(--text-secondary)",
fontSize: "12px",
marginBottom: "10px",
}}
>
{store.loadingState.cityDetail
? "多日预报同步中,正在刷新完整日序列。"
: "当前只收到当日预报,其他日期结果暂未回传。"}
<div className="forecast-inline-note">
{isForecastCompleting
? locale === "en-US"
? "Multi-day forecast is syncing. Only the current-day card has arrived."
: "多日预报同步中,当前只到达当日卡片。"
: locale === "en-US"
? "Only the current-day forecast is available right now."
: "当前只收到当日预报,其他日期结果暂未回传。"}
</div>
)}
<div className="forecast-table">
@@ -1062,7 +1063,23 @@ export function ForecastTable() {
</div>
</button>
);
})
}).concat(
isForecastCompleting
? Array.from({ length: Math.max(0, 5 - daily.length) }).map((_, index) => (
<button
key={`forecast-sync-${index}`}
type="button"
className="forecast-day forecast-day-sync"
disabled
>
<div className="f-date">
{locale === "en-US" ? "Syncing" : "同步中"}
</div>
<div className="f-temp">--</div>
</button>
))
: [],
)
)}
</div>
</section>