diff --git a/src/modules/assets/components/OverviewView.tsx b/src/modules/assets/components/OverviewView.tsx index 276bc67..cc7d711 100644 --- a/src/modules/assets/components/OverviewView.tsx +++ b/src/modules/assets/components/OverviewView.tsx @@ -8,15 +8,17 @@ import type { OverviewViewProps } from './overview/types'; export function OverviewView(props: OverviewViewProps) { return ( <> - +
+ - + +
{/* Header Summary - Ultra Compact */} -
+
{/* Total Assets */}
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', source: 'asset', title: '资产概览' })}> @@ -66,7 +66,7 @@ export function SummaryMetrics({
-
+
运营概览
diff --git a/src/modules/assets/history/AssetHistorySection.tsx b/src/modules/assets/history/AssetHistorySection.tsx index 7f45019..0a5e82f 100644 --- a/src/modules/assets/history/AssetHistorySection.tsx +++ b/src/modules/assets/history/AssetHistorySection.tsx @@ -69,18 +69,17 @@ export function AssetHistorySection({ subject, onSubjects }: { subject: string |
{mode === 'monthly' && ({ value: String(y), label: `${y}年` }))} onChange={value => { setYear(Number(value)); setDetails(null); }} />} {data && { setSelected(value); setDetails(null); }} options={data.points.map(p => ({ value: p.period, label: p.label, detail: p.state === 'RECONSTRUCTED' ? '估算' : p.state === 'LIVE' ? '实时' : p.state === 'ARCHIVED' ? '归档' : p.state === 'FUTURE' ? '未来' : p.state === 'MISSING' ? '缺失' : HISTORY_STATE_LABELS[p.state] }))} />} - 月末 17:00 + 月末 17:00
{loading && !data ?

正在加载资产历史趋势…

: !data ?

{error || '暂无可用数据'}

: <> {error &&

{error} · 保留上次查询结果

} -

数量(台)

- { const p = chart.find(p => p.label === String(state?.activeLabel)); if (p) setSelected(p.period); }}> + { const p = chart.find(p => p.label === String(state?.activeLabel)); if (p) setSelected(p.period); }}> - + { const item = payload?.[0]?.payload as HistoryPoint | undefined; const values = item && displayHistoryCounts(item); @@ -92,16 +91,19 @@ export function AssetHistorySection({ subject, onSubjects }: { subject: string |
- + {point &&
-
{mode === 'annual' || point.state === 'LIVE' ? point.label : `${point.label}末`}{HISTORY_STATE_LABELS[point.state]}{point.asOf && }
-
{([['total', '总数'], ['inventory', '库存'], ['operating', '运营']] as const).map(([key, label]) =>
{label}(台){displayHistoryCounts(point)?.[key]?.toLocaleString() ?? (point.reconstruction && key === 'total' ? '待核实' : '—')}
)}
+
{mode === 'annual' || point.state === 'LIVE' ? point.label : `${point.label}末`}{HISTORY_STATE_LABELS[point.state]}
+
{([['total', '总数'], ['inventory', '库存'], ['operating', '运营']] as const).map(([key, label]) =>
{label}{displayHistoryCounts(point)?.[key]?.toLocaleString() ?? (point.reconstruction && key === 'total' ? '待核实' : '—')}
)}
{hasComparison ?
{point.comparisonLabel}
{(['total', 'inventory', 'operating'] as const).map(key => { const difference = displayHistoryDifference(point, key); return 0 ? 'asset-history-increase' : difference != null && difference < 0 ? 'asset-history-decrease' : ''}>{difference != null ? signedCount(difference) : '—'}; - })}
:

{point.comparisonLabel}

} + })}
: null} +
+
{point.asOf && }{!hasComparison && {point.comparisonLabel}}
+ {mode === 'monthly' && } +
} - {point && mode === 'monthly' && }

历史库存状态快照缺失,核对中,暂不展示。

} {details && } diff --git a/src/modules/assets/history/history.css b/src/modules/assets/history/history.css index 7bc74fe..980a651 100644 --- a/src/modules/assets/history/history.css +++ b/src/modules/assets/history/history.css @@ -1,41 +1,43 @@ -.asset-history { --history-blue:#1559e8; margin:8px 0 16px; padding:16px; border:1px solid #e6edf5; border-radius:20px; background:#fff; color:#142b4e; box-shadow:0 5px 24px rgb(22 53 90 / 3%); } -.asset-history button,.asset-history-dialog button { cursor:pointer; } +.asset-history { --history-blue:#1559e8; --history-ink:#142b4e; --history-muted:#62748b; --history-border:#e3ebf5; margin:0; padding:16px; border:1px solid var(--history-border); border-radius:16px; background:#fff; color:var(--history-ink); box-shadow:0 2px 12px rgb(22 53 90 / 3%); } +.asset-history button,.asset-history-dialog button { cursor:pointer; touch-action:manipulation; } .asset-history button:disabled,.asset-history-dialog button:disabled { cursor:default; opacity:.45; } -.asset-history button:focus-visible,.asset-history select:focus-visible,.asset-history-dialog :is(button,input,select):focus-visible { outline:2px solid #2563eb; outline-offset:3px; } -.asset-history-header { display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap; gap:12px; } -.asset-history-header h2 { margin:0; font-size:20px; font-weight:750; letter-spacing:-.5px; } -.asset-history-modes { display:flex; gap:2px; background:#eef3fa; border:1px solid #e7edf6; border-radius:11px; padding:3px; } -.asset-history-modes button { padding:7px 20px; min-height:34px; border-radius:8px; font-size:13px; font-weight:600; transition:background .15s,color .15s,box-shadow .15s; color:#64748b; } -.asset-history-modes button[aria-pressed=true] { background:#fff; color:#1559e8; box-shadow:0 1px 4px #193c6920, inset 0 0 0 1px #dce7f5; } -.asset-history-toolbar { display:flex; align-items:center; gap:10px; flex-wrap:wrap; margin:8px 0 4px; font-size:12px; color:#64748b; } +.asset-history button:focus-visible,.asset-history-dialog :is(button,input,select):focus-visible { outline:2px solid #2563eb; outline-offset:3px; } +.asset-history-header { display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap; gap:8px; } +.asset-history-header h2 { margin:0; font-size:18px; font-weight:700; letter-spacing:-.4px; } +.asset-history-modes { display:flex; gap:2px; background:#f0f4f9; border-radius:10px; padding:0 3px; } +.asset-history-modes button { position:relative; isolation:isolate; min-width:52px; min-height:44px; padding:8px 14px; font-size:13px; font-weight:600; color:var(--history-muted); transition:color .16s; } +.asset-history-modes button::before { content:""; position:absolute; inset:5px 0; z-index:-1; border-radius:7px; transition:background .16s,box-shadow .16s; } +.asset-history-modes button[aria-pressed=true] { color:var(--history-blue); } +.asset-history-modes button[aria-pressed=true]::before { background:#fff; box-shadow:0 1px 4px #193c691a,0 0 0 1px #e0e8f3; } +.asset-history-toolbar { display:flex; align-items:center; gap:8px; flex-wrap:wrap; margin:4px 0 0; color:var(--history-muted); font-size:12px; } .asset-history-toolbar>span { margin-left:auto; } -.asset-history-toolbar .asset-history-annual-note { margin-left:0; margin-right:auto; } -.asset-history-refresh { display:grid; place-items:center; padding:8px; border-radius:8px; color:#7c8da5; transition:background .15s,color .15s; } +.asset-history-refresh { display:grid; place-items:center; min-width:44px; min-height:44px; padding:8px; border-radius:8px; color:var(--history-muted); transition:background .16s,color .16s; } .asset-history-warning { margin:8px 0; font-size:12px; line-height:1.7; color:#74613a; background:#fff9ec; padding:9px 12px; border-radius:9px; } -.asset-history-warning button,.asset-history-empty button { color:#1559e8; text-decoration:underline; padding:6px; } -.asset-history-unit { font-size:12px; color:#64748b; margin:6px 0 0; } -.asset-history-chart { height:200px; width:100%; min-width:0; } -.asset-history-tooltip { background:#fff; border:1px solid #dce5ef; border-radius:10px; padding:10px 12px; box-shadow:0 4px 16px #142b4e15; font-size:12px; } +.asset-history-warning button,.asset-history-empty button { color:var(--history-blue); text-decoration:underline; padding:6px; } +.asset-history-chart { height:180px; width:100%; min-width:0; } +.asset-history-tooltip { background:#fff; border:1px solid var(--history-border); border-radius:10px; padding:10px 12px; box-shadow:0 4px 16px #142b4e15; font-size:12px; } .asset-history-tooltip p { margin-top:4px; } -.asset-history-legend { display:flex; justify-content:center; flex-wrap:wrap; gap:22px; color:#64748b; font-size:12px; margin:6px 0; } -.asset-history-legend span { display:flex; align-items:center; gap:7px; } -.asset-history-legend i { display:inline-block; height:11px; width:11px; border-radius:2px; } +.asset-history-legend { display:flex; justify-content:center; align-items:center; flex-wrap:wrap; gap:16px; color:var(--history-muted); font-size:12px; line-height:18px; margin:2px 0 8px; } +.asset-history-legend span { display:flex; align-items:center; gap:6px; } +.asset-history-legend i { display:inline-block; height:8px; width:8px; border-radius:2px; } .asset-history-legend .operating { background:#347df5; }.asset-history-legend .inventory { background:#9ed1fa; } -.asset-history-legend .total { width:20px; height:2px; background:#132e57; } -.asset-history-selected { margin-top:8px; display:grid; grid-template-columns:90px minmax(0,1fr); align-items:center; gap:0 10px; padding:10px 14px; background:linear-gradient(115deg,#f3f7fe,#edf5ff); border:1px solid #e7effb; border-radius:12px; } +.asset-history-legend .total { position:relative; width:16px; height:2px; background:#132e57; } +.asset-history-legend .total::after { content:""; position:absolute; top:-2px; left:6px; width:6px; height:6px; border:2px solid #132e57; border-radius:50%; background:white; } +.asset-history-legend .asset-history-unit { padding-left:8px; border-left:1px solid var(--history-border); } +.asset-history-selected { display:grid; grid-template-columns:94px minmax(0,1fr); align-items:center; gap:0 8px; padding:10px 12px 0; background:#f2f7ff; border:1px solid #e4edfa; border-radius:10px; } .asset-history-selected-heading { display:flex; flex-direction:column; align-items:flex-start; gap:3px; min-width:0; } -.asset-history-selected-heading>strong { color:#1559e8; font-size:17px; font-weight:750; } -.asset-history-selected-heading>span,.asset-history-selected-heading>time { color:#64748b; font-size:11px; } +.asset-history-selected-heading>strong { color:var(--history-blue); font-size:18px; line-height:24px; font-weight:700; } +.asset-history-selected-heading>span { color:var(--history-muted); font-size:12px; line-height:18px; } .asset-history-counts { display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); } -.asset-history-counts>div { display:flex; flex-direction:column; align-items:center; justify-content:center; gap:2px; min-width:0; border-right:1px solid #d4e2f4; } -.asset-history-counts>div:last-child { border:0; } -.asset-history-counts>div>span { color:#64748b; font-size:12px; } -.asset-history-counts strong { font-size:23px; font-weight:750; line-height:1.2; font-variant-numeric:tabular-nums; } +.asset-history-counts>div { display:flex; flex-direction:column; align-items:center; gap:3px; min-width:0; border-left:1px solid #dce7f6; } +.asset-history-counts>div>span { color:var(--history-muted); font-size:12px; line-height:18px; } +.asset-history-counts strong { font-size:24px; font-weight:700; line-height:28px; letter-spacing:-.4px; font-variant-numeric:tabular-nums; } .asset-history-increase { color:#bf2f30; }.asset-history-decrease { color:#14864a; } -.asset-history-selected>p { grid-column:1 / -1; color:#64748b; font-size:11px; text-align:center; padding-top:6px; } -.asset-history-detail-button { display:flex; width:100%; align-items:center; justify-content:space-between; min-height:44px; padding:8px 12px; margin-top:8px; border:1px solid #dce8f8; border-radius:10px; background:#f7faff; color:#18385d; font-size:14px; font-weight:600; } -.asset-history-footnote { display:flex; align-items:center; flex-wrap:wrap; gap:6px; color:#64748b; font-size:11px; margin-top:8px; } -.asset-history-empty { padding:32px 12px; text-align:center; font-size:13px; color:#64748b; } +.asset-history-selected-footer { grid-column:1/-1; display:flex; align-items:center; justify-content:space-between; gap:8px; margin-top:8px; border-top:1px solid #e0eaf8; min-height:44px; } +.asset-history-point-meta { display:flex; flex-wrap:wrap; gap:2px 12px; color:var(--history-muted); font-size:12px; line-height:18px; } +.asset-history-detail-button { display:flex; flex-shrink:0; align-items:center; justify-content:space-between; gap:4px; min-height:44px; padding:8px 0 8px 8px; color:var(--history-blue); font-size:12px; font-weight:600; border-radius:6px; transition:color .16s,background .16s; } +.asset-history-footnote { color:var(--history-muted); font-size:12px; line-height:18px; margin-top:8px; } +.asset-history-empty { padding:32px 12px; text-align:center; font-size:13px; color:var(--history-muted); } .asset-history-overlay { position:fixed; inset:0; z-index:120; background:#0c1e3b66; display:flex; justify-content:center; align-items:center; padding:24px; } .asset-history-dialog { display:flex; flex-direction:column; width:min(980px,100%); max-height:88dvh; border-radius:18px; background:white; color:#183454; box-shadow:0 24px 64px #142b4e30; overflow:hidden; outline:none; } .asset-history-dialog>header { display:flex; align-items:flex-start; justify-content:space-between; gap:12px; padding:20px 22px; border-bottom:1px solid #e6edf5; } @@ -58,12 +60,12 @@ .asset-history-dialog footer { display:flex; align-items:center; justify-content:space-between; gap:12px; padding:12px 22px; border-top:1px solid #e6edf5; color:#64748b; font-size:12px; } .asset-history-dialog footer>div { display:flex; gap:8px; }.asset-history-dialog footer button { padding:9px; border:1px solid #dce5f0; border-radius:7px; } @media(max-width:640px) { - .asset-history { padding:12px; border-radius:16px; } - .asset-history-header h2 { font-size:18px; }.asset-history-modes button { padding:7px 15px; min-height:38px; } - .asset-history-toolbar { gap:6px; }.asset-history-toolbar>span { font-size:10px; } - .asset-history-chart { height:170px; } - .asset-history-selected { grid-template-columns:72px minmax(0,1fr); gap:0 6px; padding:8px 10px; }.asset-history-selected-heading { gap:2px; }.asset-history-selected-heading>span,.asset-history-selected-heading>time { font-size:10px; } - .asset-history-counts strong { font-size:21px; }.asset-history-counts>div>span { font-size:11px; } + .asset-history { padding:12px; border-radius:14px; } + .asset-history-header h2 { font-size:17px; } + .asset-history-chart { height:140px; } + .asset-history-selected { grid-template-columns:84px minmax(0,1fr); padding:8px 10px 0; gap:0 6px; } + .asset-history-counts strong { font-size:22px; line-height:26px; } + .asset-history-point-meta { flex-direction:column; gap:0; } .asset-history-overlay { padding:10px; }.asset-history-dialog { max-height:94dvh; border-radius:14px; } .asset-history-dialog>header { padding:14px; }.asset-history-dialog h2 { font-size:16px; }.asset-history-detail-tools { padding:12px 14px; } .asset-history-detail-note { padding:10px 14px; }.asset-history-dialog :is(input,select) { font-size:16px; }.asset-history-dialog footer { padding:12px 14px; } @@ -79,31 +81,18 @@ } @media(min-width:900px) { - .asset-history-controls { display:flex; align-items:center; flex-wrap:wrap; gap:8px 24px; } + .asset-history-controls { display:flex; align-items:center; gap:24px; flex-wrap:wrap; } .asset-history-header { flex:1; gap:16px; } .asset-history-toolbar { margin:0; } - .asset-history-selected { display:grid; grid-template-columns:minmax(150px,1fr) 3fr; align-items:center; gap:0 18px; } - .asset-history-selected-heading { display:grid; grid-template-columns:auto 1fr; align-items:center; gap:2px 12px; margin:0; } - .asset-history-selected-heading>strong { grid-row:span 2; } - .asset-history-selected-heading>span,.asset-history-selected-heading>time { grid-column:2; } - .asset-history-selected>p { grid-column:1 / -1; } + .asset-history-selected { grid-template-columns:minmax(150px,1fr) 3fr; gap:0 16px; } + .asset-history-selected-heading { flex-direction:row; align-items:center; gap:12px; } } -@media(max-width:640px) { - .asset-history-modes button,.asset-history-refresh { min-height:44px; } - .asset-history-refresh { min-width:44px; } - .asset-history-toolbar { gap:4px; } -} - -.asset-history-comparison { grid-column:1 / -1; display:grid; grid-template-columns:90px minmax(0,1fr); gap:10px; align-items:center; margin-top:6px; padding-top:6px; border-top:1px solid #d4e2f4; font-size:11px; color:#64748b; } +.asset-history-comparison { grid-column:1/-1; display:grid; grid-template-columns:94px minmax(0,1fr); gap:8px; align-items:center; margin-top:8px; padding-top:6px; border-top:1px solid #dce7f6; font-size:12px; color:var(--history-muted); } .asset-history-comparison>div { display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); text-align:center; font-size:13px; font-weight:600; } -@media(min-width:900px) { - .asset-history-comparison { grid-template-columns:minmax(150px,1fr) 3fr; gap:18px; } -} -@media(max-width:640px) { - .asset-history-comparison { grid-template-columns:72px minmax(0,1fr); gap:6px; } -} +@media(min-width:900px) { .asset-history-comparison { grid-template-columns:minmax(150px,1fr) 3fr; gap:16px; } } +@media(max-width:640px) { .asset-history-comparison { grid-template-columns:84px minmax(0,1fr); gap:6px; } } -.asset-history-select { display:inline-flex; align-items:center; justify-content:space-between; gap:12px; min-width:76px; min-height:36px; padding:7px 11px; border:1px solid #dce5f1; border-radius:9px; background:#fff; color:#254365; font-size:13px; font-weight:550; box-shadow:0 1px 2px #173c6310; transition:border-color .15s,box-shadow .15s; } +.asset-history-select { display:inline-flex; align-items:center; justify-content:space-between; gap:12px; min-width:76px; min-height:44px; padding:7px 11px; border:1px solid #dce5f1; border-radius:8px; background:#fff; color:#254365; font-size:13px; font-weight:550; box-shadow:0 1px 2px #173c6310; transition:border-color .15s,box-shadow .15s; } .asset-history-select[data-state=open] { border-color:#8fb4f2; box-shadow:0 0 0 3px #dbeaff80; } .asset-history-select-icon { color:#8b9db5; display:flex; transition:transform .15s; } .asset-history-select[data-state=open] .asset-history-select-icon { transform:rotate(180deg); } @@ -115,17 +104,19 @@ .asset-history-option-detail { margin-left:auto; color:#7b8da5; font-size:11px; font-weight:400; } .asset-history-option-check { position:absolute; right:9px; display:flex; color:#2563eb; } .asset-history-select-scroll { display:flex; align-items:center; justify-content:center; height:24px; color:#7b8da5; } -.asset-history-detail-button>svg { padding:3px; width:24px; height:24px; border-radius:7px; background:#eaf2ff; color:#527eb7; } +.asset-history-detail-button>svg { flex-shrink:0; } @media(hover:hover) { - .asset-history-modes button:not([aria-pressed=true]):hover { color:#1559e8; background:#e5edf9; } + .asset-history-modes button:not([aria-pressed=true]):hover::before { background:#e5edf9; } .asset-history-select:hover { border-color:#adc5e8; } .asset-history-refresh:hover { color:#2563eb; background:#eef5ff; } - .asset-history-detail-button:hover { background:#eef5ff; border-color:#bcd3f3; } + .asset-history-detail-button:hover { color:#103eaa; background:#e9f1ff; } } @media(max-width:640px) { .asset-history-select { min-height:44px; padding:8px 10px; font-size:12px; gap:10px; } .asset-history-select-option { min-height:44px; } } @media(prefers-reduced-motion:reduce) { - .asset-history-modes button,.asset-history-select,.asset-history-select-icon,.asset-history-refresh { transition:none; } + .asset-history-modes button,.asset-history-modes button::before,.asset-history-select,.asset-history-select-icon,.asset-history-refresh,.asset-history-detail-button { transition:none; } } + +.asset-history :is(.asset-history-select,.asset-history-refresh,.asset-history-detail-button):active { background:#e6effe; }