From 5b540acade3b2c73c438244c40ac4a530274eadd Mon Sep 17 00:00:00 2001 From: wintsa <770775984@qq.com> Date: Thu, 7 May 2026 10:00:19 +0800 Subject: [PATCH] 1 --- .gitnexus/lbug | Bin 4096 -> 4096 bytes .gitnexus/lbug.wal | Bin 25374 -> 25374 bytes .../console-2026-05-07T01-42-43-218Z.log | 23 ++ src/App.tsx | 203 ++++++++++++++---- src/styles.css | 56 ++++- vite-dev.log | 28 +++ 6 files changed, 268 insertions(+), 42 deletions(-) create mode 100644 .playwright-mcp/console-2026-05-07T01-42-43-218Z.log diff --git a/.gitnexus/lbug b/.gitnexus/lbug index a7ab41fa0610ea851d4354a35c65fe9870ce8489..ae673218166c82621cc18fb6459354fd34843ada 100644 GIT binary patch literal 4096 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 Ckf}5P literal 25374 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 const chartFrameRef = useRef(null); + const [statisticKey, setStatisticKey] = useState('avgValue'); const [metricKey, setMetricKey] = useState('cost'); + const [groupKey, setGroupKey] = useState('year'); + const [statisticMenuOpen, setStatisticMenuOpen] = useState(false); const [metricMenuOpen, setMetricMenuOpen] = useState(false); + const [chartData, setChartData] = useState([]); + const [loading, setLoading] = useState(true); + const [loadError, setLoadError] = useState(null); + const selectedStatistic = statisticOptions.find((option) => option.key === statisticKey) ?? statisticOptions[0]; const selectedMetric = metricOptions.find((option) => option.key === metricKey) ?? metricOptions[0]; + useEffect(() => { + const controller = new AbortController(); + + async function loadStats() { + setLoading(true); + setLoadError(null); + try { + const search = new URLSearchParams({ + groupBy: groupKey, + metric: metricKey, + }); + const response = await fetch(`${API_BASE_URL}/zw/getBuildingFunctionCostStats?${search.toString()}`, { + signal: controller.signal, + }); + if (!response.ok) { + throw new Error(`HTTP ${response.status}`); + } + const payload = (await response.json()) as { data?: ApiBuildingFunctionStat[] }; + setChartData((payload.data ?? []).map(normalizeStat).slice(0, 36)); + } catch (error) { + if (controller.signal.aborted) return; + setLoadError(error instanceof Error ? error.message : '接口请求失败'); + setChartData([]); + } finally { + if (!controller.signal.aborted) { + setLoading(false); + } + } + } + + void loadStats(); + + return () => { + controller.abort(); + }; + }, [groupKey, metricKey]); + useEffect(() => { const frame = chartFrameRef.current; const fullscreenTarget = workspaceRef.current; if (!frame || !fullscreenTarget) return; const getFullscreenButton = () => frame.querySelector('.chart-fullscreen-button'); + const getStatisticButton = () => frame.querySelector('.ag-charts-myButton-statistic')?.closest('.ag-charts-toolbar__button'); - const syncFullscreenButton = () => { + const syncToolbarButtons = () => { const button = getFullscreenButton(); - if (!button) return; + if (button) { + let icon = button.querySelector('.ag-charts-myButton-fullScreen'); + if (!icon) { + button.innerHTML = ''; + icon = button.querySelector('.ag-charts-myButton-fullScreen'); + } - let icon = button.querySelector('.ag-charts-myButton-fullScreen'); - if (!icon) { - button.innerHTML = ''; - icon = button.querySelector('.ag-charts-myButton-fullScreen'); + const isFullscreen = document.fullscreenElement === fullscreenTarget; + button.classList.toggle('ag-charts-toolbar__button--active', isFullscreen); + icon?.classList.toggle('anticon-arrow-salt', !isFullscreen); + icon?.classList.toggle('anticon-shrink', isFullscreen); } - const isFullscreen = document.fullscreenElement === fullscreenTarget; - button.classList.toggle('ag-charts-toolbar__button--active', isFullscreen); - icon?.classList.toggle('anticon-arrow-salt', !isFullscreen); - icon?.classList.toggle('anticon-shrink', isFullscreen); + const statisticButton = getStatisticButton(); + if (statisticButton) { + statisticButton.classList.add('chart-statistic-button'); + } }; const toggleFullscreen = () => { @@ -83,19 +165,24 @@ function App() { }; const handleFullscreenChange = () => { - syncFullscreenButton(); + syncToolbarButtons(); }; const handleToolbarClick = (event: MouseEvent) => { const target = event.target as Element | null; const button = target?.closest( - '.chart-fullscreen-button', + '.chart-fullscreen-button, .chart-statistic-button', ); if (!button || !frame.contains(button)) return; event.preventDefault(); event.stopPropagation(); - toggleFullscreen(); + if (button.classList.contains('chart-statistic-button')) { + setMetricMenuOpen(false); + setStatisticMenuOpen((open) => !open); + } else { + toggleFullscreen(); + } }; const handleToolbarKeyDown = (event: KeyboardEvent) => { @@ -103,20 +190,25 @@ function App() { const target = event.target as Element | null; const button = target?.closest( - '.chart-fullscreen-button', + '.chart-fullscreen-button, .chart-statistic-button', ); if (!button || !frame.contains(button)) return; event.preventDefault(); event.stopPropagation(); - toggleFullscreen(); + if (button.classList.contains('chart-statistic-button')) { + setMetricMenuOpen(false); + setStatisticMenuOpen((open) => !open); + } else { + toggleFullscreen(); + } }; const suppressBrowserContextMenu = (event: MouseEvent) => { event.preventDefault(); }; - const observer = new MutationObserver(syncFullscreenButton); + const observer = new MutationObserver(syncToolbarButtons); document.addEventListener('keydown', handleKeyDown, true); document.addEventListener('fullscreenchange', handleFullscreenChange); document.addEventListener('contextmenu', suppressBrowserContextMenu); @@ -124,7 +216,7 @@ function App() { frame.addEventListener('click', handleToolbarClick, true); frame.addEventListener('keydown', handleToolbarKeyDown, true); observer.observe(frame, { childList: true, subtree: true }); - syncFullscreenButton(); + syncToolbarButtons(); return () => { document.removeEventListener('keydown', handleKeyDown, true); @@ -138,10 +230,10 @@ function App() { }, []); const chartOptions = useMemo(() => { - const isCost = metricKey === 'cost'; - const chartData = yearlyCostData.map((datum) => ({ - year: datum.year, - amount: datum.cost == null ? null : isCost ? datum.cost : Math.round((datum.cost / Number(datum[metricKey])) * 100) / 100, + const isCount = statisticKey === 'dataCount'; + const visibleData = chartData.map((datum) => ({ + groupName: datum.groupName, + amount: datum[statisticKey], })); return { @@ -172,7 +264,7 @@ function App() { bottom: 18, left: 24, }, - data: chartData, + data: visibleData, zoom: { enabled: true, anchorPointX: 'pointer', @@ -196,7 +288,12 @@ function App() { annotations: { enabled: true, toolbar: { - buttons: [ + buttons: ([ + { + value: 'statistic-select', + tooltip: '切换统计指标', + label: `${selectedStatistic.shortLabel}`, + }, { icon: 'trend-line-drawing', value: 'line-menu', @@ -222,15 +319,15 @@ function App() { value: 'clear', tooltip: 'Clear annotations', }, - ], + ] as unknown as NonNullable['toolbar']>['buttons']), }, }, series: [ { type: 'line', - xKey: 'year', + xKey: 'groupName', yKey: 'amount', - yName: selectedMetric.label, + yName: `${selectedMetric.label} ${selectedStatistic.label}`, stroke: '#0078a8', strokeWidth: 2, marker: { @@ -273,7 +370,7 @@ function App() { color: '#1f2933', fontSize: 12, formatter: ({ value }) => - isCost + !isCount ? formatWan(Number(value)) : Number(value).toLocaleString('zh-CN', { minimumFractionDigits: 0, @@ -307,7 +404,7 @@ function App() { enabled: true, }, }; - }, [metricKey, selectedMetric.label]); + }, [chartData, selectedMetric.label, selectedStatistic.label, statisticKey]); return (
@@ -320,6 +417,25 @@ function App() {
+ {statisticMenuOpen ? ( +
+ {statisticOptions.map((option) => ( + + ))} +
+ ) : null}
@@ -351,6 +471,7 @@ function App() {
) : null}
+ {loading || loadError ?
{loading ? '加载中' : loadError}
: null} diff --git a/src/styles.css b/src/styles.css index 6b6bbcc..b084daf 100644 --- a/src/styles.css +++ b/src/styles.css @@ -186,6 +186,11 @@ button { height: 100%; } +.chart-frame > .statistic-switcher-menu, +.chart-frame > .chart-status { + height: auto; +} + .chart-frame .ag-charts-wrapper { --ag-charts-accent-color: #0078a8; --ag-charts-button-background-color: rgba(255, 249, 241, 0.72); @@ -212,6 +217,41 @@ button { overflow: visible !important; } +.statistic-switcher-menu { + position: absolute; + left: 70px; + top: 24px; + z-index: 14; + width: max-content; + min-width: 86px; + padding: 4px 0; + border: 1px solid rgba(90, 82, 72, 0.22); + border-radius: 3px; + background: #fbede1; + box-shadow: 0 4px 14px rgba(69, 54, 36, 0.14); +} + +.statistic-switcher-menu-item { + display: block; + width: 100%; + min-height: 30px; + padding: 0 12px; + border: 0; + color: #262a33; + background: transparent; + font-size: 13px; + line-height: 30px; + text-align: left; + white-space: nowrap; + cursor: pointer; +} + +.statistic-switcher-menu-item:hover, +.statistic-switcher-menu-item[aria-current="true"] { + color: #0078a8; + background: rgba(255, 252, 248, 0.94); +} + .metric-switcher { position: absolute; left: 74px; @@ -284,10 +324,24 @@ button { background: rgba(255, 252, 248, 0.94); } +.chart-status { + position: absolute; + top: 62px; + left: 74px; + z-index: 11; + padding: 4px 8px; + border: 1px solid rgba(90, 82, 72, 0.16); + border-radius: 3px; + color: #262a33; + background: rgba(255, 252, 248, 0.94); + font-size: 12px; + line-height: 18px; +} + .chart-frame .chart-fullscreen-button { position: absolute; left: 24px; - top: 210px; + top: 252px; z-index: 9; display: grid; width: 34px; diff --git a/vite-dev.log b/vite-dev.log index 48235c1..8a61b15 100644 --- a/vite-dev.log +++ b/vite-dev.log @@ -36,3 +36,31 @@ Port 5173 is in use, trying another one... 16:33:55 [vite] (client) hmr update /src/App.tsx 16:38:49 [vite] (client) hmr update /src/App.tsx 16:39:34 [vite] (client) hmr update /src/styles.css +16:46:57 [vite] (client) hmr update /src/App.tsx +16:47:20 [vite] (client) hmr update /src/App.tsx +16:53:41 [vite] (client) hmr update /src/App.tsx +16:54:18 [vite] (client) hmr update /src/styles.css +16:59:35 [vite] (client) hmr update /src/styles.css +17:01:23 [vite] (client) hmr update /src/styles.css +17:03:24 [vite] (client) hmr update /src/styles.css +17:06:50 [vite] (client) hmr update /src/styles.css +17:29:44 [vite] (client) hmr update /src/App.tsx +17:29:52 [vite] (client) hmr update /src/styles.css +17:32:17 [vite] (client) hmr update /src/App.tsx +17:32:43 [vite] (client) hmr update /src/styles.css +17:34:33 [vite] (client) hmr update /src/App.tsx +17:40:09 [vite] (client) hmr update /src/styles.css +17:40:37 [vite] (client) hmr update /src/App.tsx +17:41:32 [vite] (client) hmr update /src/styles.css +17:42:39 [vite] (client) hmr update /src/styles.css +17:44:42 [vite] (client) hmr update /src/App.tsx +09:11:33 [vite] (client) hmr update /src/App.tsx +09:11:59 [vite] (client) hmr update /src/styles.css +09:14:42 [vite] (client) hmr update /src/styles.css +09:15:32 [vite] (client) hmr update /src/styles.css +09:18:16 [vite] (client) hmr update /src/App.tsx +09:18:28 [vite] (client) hmr update /src/styles.css +09:35:28 [vite] (client) hmr update /src/App.tsx +09:35:53 [vite] (client) hmr update /src/App.tsx +09:35:53 [vite] (client) hmr update /src/styles.css +09:36:43 [vite] (client) hmr update /src/App.tsx