diff --git a/.gitnexus/lbug b/.gitnexus/lbug index a7ab41f..ae67321 100644 Binary files a/.gitnexus/lbug and b/.gitnexus/lbug differ diff --git a/.gitnexus/lbug.wal b/.gitnexus/lbug.wal index b2bebd8..96362eb 100644 Binary files a/.gitnexus/lbug.wal and b/.gitnexus/lbug.wal differ diff --git a/.playwright-mcp/console-2026-05-07T01-42-43-218Z.log b/.playwright-mcp/console-2026-05-07T01-42-43-218Z.log new file mode 100644 index 0000000..7298485 --- /dev/null +++ b/.playwright-mcp/console-2026-05-07T01-42-43-218Z.log @@ -0,0 +1,23 @@ +[ 239614ms] [WARNING] AG Charts - Option `annotations.toolbar.buttons[0].value` cannot be set to `"statistic-select"`; expecting a keyword such as 'line-menu', 'fibonacci-menu', 'text-menu', 'shape-menu', 'measurer-menu', 'line', 'horizontal-line', 'vertical-line', 'parallel-channel', 'disjoint-channel', 'fibonacci-retracement', 'fibonacci-retracement-trend-based', 'text', 'comment', 'callout', 'note' or 'clear', ignoring. @ http://localhost:5174/node_modules/.vite/deps/chunk-XDC3NMYR.js?v=2852bbef:834 +[ 240878ms] [WARNING] AG Charts - Option `annotations.toolbar.buttons[0].value` cannot be set to `"statistic-select"`; expecting a keyword such as 'line-menu', 'fibonacci-menu', 'text-menu', 'shape-menu', 'measurer-menu', 'line', 'horizontal-line', 'vertical-line', 'parallel-channel', 'disjoint-channel', 'fibonacci-retracement', 'fibonacci-retracement-trend-based', 'text', 'comment', 'callout', 'note' or 'clear', ignoring. @ http://localhost:5174/node_modules/.vite/deps/chunk-XDC3NMYR.js?v=2852bbef:834 +[ 241566ms] [WARNING] AG Charts - Option `annotations.toolbar.buttons[0].value` cannot be set to `"statistic-select"`; expecting a keyword such as 'line-menu', 'fibonacci-menu', 'text-menu', 'shape-menu', 'measurer-menu', 'line', 'horizontal-line', 'vertical-line', 'parallel-channel', 'disjoint-channel', 'fibonacci-retracement', 'fibonacci-retracement-trend-based', 'text', 'comment', 'callout', 'note' or 'clear', ignoring. @ http://localhost:5174/node_modules/.vite/deps/chunk-XDC3NMYR.js?v=2852bbef:834 +[ 242175ms] [WARNING] AG Charts - Option `annotations.toolbar.buttons[0].value` cannot be set to `"statistic-select"`; expecting a keyword such as 'line-menu', 'fibonacci-menu', 'text-menu', 'shape-menu', 'measurer-menu', 'line', 'horizontal-line', 'vertical-line', 'parallel-channel', 'disjoint-channel', 'fibonacci-retracement', 'fibonacci-retracement-trend-based', 'text', 'comment', 'callout', 'note' or 'clear', ignoring. @ http://localhost:5174/node_modules/.vite/deps/chunk-XDC3NMYR.js?v=2852bbef:834 +[ 242757ms] [WARNING] AG Charts - Option `annotations.toolbar.buttons[0].value` cannot be set to `"statistic-select"`; expecting a keyword such as 'line-menu', 'fibonacci-menu', 'text-menu', 'shape-menu', 'measurer-menu', 'line', 'horizontal-line', 'vertical-line', 'parallel-channel', 'disjoint-channel', 'fibonacci-retracement', 'fibonacci-retracement-trend-based', 'text', 'comment', 'callout', 'note' or 'clear', ignoring. @ http://localhost:5174/node_modules/.vite/deps/chunk-XDC3NMYR.js?v=2852bbef:834 +[ 244362ms] [WARNING] AG Charts - Option `annotations.toolbar.buttons[0].value` cannot be set to `"statistic-select"`; expecting a keyword such as 'line-menu', 'fibonacci-menu', 'text-menu', 'shape-menu', 'measurer-menu', 'line', 'horizontal-line', 'vertical-line', 'parallel-channel', 'disjoint-channel', 'fibonacci-retracement', 'fibonacci-retracement-trend-based', 'text', 'comment', 'callout', 'note' or 'clear', ignoring. @ http://localhost:5174/node_modules/.vite/deps/chunk-XDC3NMYR.js?v=2852bbef:834 +[ 254208ms] [WARNING] AG Charts - Option `annotations.toolbar.buttons[0].value` cannot be set to `"statistic-select"`; expecting a keyword such as 'line-menu', 'fibonacci-menu', 'text-menu', 'shape-menu', 'measurer-menu', 'line', 'horizontal-line', 'vertical-line', 'parallel-channel', 'disjoint-channel', 'fibonacci-retracement', 'fibonacci-retracement-trend-based', 'text', 'comment', 'callout', 'note' or 'clear', ignoring. @ http://localhost:5174/node_modules/.vite/deps/chunk-XDC3NMYR.js?v=2852bbef:834 +[ 632838ms] [WARNING] AG Charts - Option `annotations.toolbar.buttons[0].value` cannot be set to `"statistic-select"`; expecting a keyword such as 'line-menu', 'fibonacci-menu', 'text-menu', 'shape-menu', 'measurer-menu', 'line', 'horizontal-line', 'vertical-line', 'parallel-channel', 'disjoint-channel', 'fibonacci-retracement', 'fibonacci-retracement-trend-based', 'text', 'comment', 'callout', 'note' or 'clear', ignoring. @ http://localhost:5174/node_modules/.vite/deps/chunk-XDC3NMYR.js?v=2852bbef:834 +[ 637178ms] [WARNING] AG Charts - Option `annotations.toolbar.buttons[0].value` cannot be set to `"statistic-select"`; expecting a keyword such as 'line-menu', 'fibonacci-menu', 'text-menu', 'shape-menu', 'measurer-menu', 'line', 'horizontal-line', 'vertical-line', 'parallel-channel', 'disjoint-channel', 'fibonacci-retracement', 'fibonacci-retracement-trend-based', 'text', 'comment', 'callout', 'note' or 'clear', ignoring. @ http://localhost:5174/node_modules/.vite/deps/chunk-XDC3NMYR.js?v=2852bbef:834 +[ 687035ms] [WARNING] AG Charts - Option `annotations.toolbar.buttons[0].value` cannot be set to `"statistic-select"`; expecting a keyword such as 'line-menu', 'fibonacci-menu', 'text-menu', 'shape-menu', 'measurer-menu', 'line', 'horizontal-line', 'vertical-line', 'parallel-channel', 'disjoint-channel', 'fibonacci-retracement', 'fibonacci-retracement-trend-based', 'text', 'comment', 'callout', 'note' or 'clear', ignoring. @ http://localhost:5174/node_modules/.vite/deps/chunk-XDC3NMYR.js?v=2852bbef:834 +[ 690304ms] [WARNING] AG Charts - Option `annotations.toolbar.buttons[0].value` cannot be set to `"statistic-select"`; expecting a keyword such as 'line-menu', 'fibonacci-menu', 'text-menu', 'shape-menu', 'measurer-menu', 'line', 'horizontal-line', 'vertical-line', 'parallel-channel', 'disjoint-channel', 'fibonacci-retracement', 'fibonacci-retracement-trend-based', 'text', 'comment', 'callout', 'note' or 'clear', ignoring. @ http://localhost:5174/node_modules/.vite/deps/chunk-XDC3NMYR.js?v=2852bbef:834 +[ 739631ms] [WARNING] AG Charts - Option `annotations.toolbar.buttons[0].value` cannot be set to `"statistic-select"`; expecting a keyword such as 'line-menu', 'fibonacci-menu', 'text-menu', 'shape-menu', 'measurer-menu', 'line', 'horizontal-line', 'vertical-line', 'parallel-channel', 'disjoint-channel', 'fibonacci-retracement', 'fibonacci-retracement-trend-based', 'text', 'comment', 'callout', 'note' or 'clear', ignoring. @ http://localhost:5174/node_modules/.vite/deps/chunk-XDC3NMYR.js?v=2852bbef:834 +[ 772552ms] [WARNING] AG Charts - Option `annotations.toolbar.buttons[0].value` cannot be set to `"statistic-select"`; expecting a keyword such as 'line-menu', 'fibonacci-menu', 'text-menu', 'shape-menu', 'measurer-menu', 'line', 'horizontal-line', 'vertical-line', 'parallel-channel', 'disjoint-channel', 'fibonacci-retracement', 'fibonacci-retracement-trend-based', 'text', 'comment', 'callout', 'note' or 'clear', ignoring. @ http://localhost:5174/node_modules/.vite/deps/chunk-XDC3NMYR.js?v=2852bbef:834 +[ 772670ms] [WARNING] AG Charts - Option `annotations.toolbar.buttons[0].value` cannot be set to `"statistic-select"`; expecting a keyword such as 'line-menu', 'fibonacci-menu', 'text-menu', 'shape-menu', 'measurer-menu', 'line', 'horizontal-line', 'vertical-line', 'parallel-channel', 'disjoint-channel', 'fibonacci-retracement', 'fibonacci-retracement-trend-based', 'text', 'comment', 'callout', 'note' or 'clear', ignoring. @ http://localhost:5174/node_modules/.vite/deps/chunk-XDC3NMYR.js?v=2852bbef:834 +[ 773677ms] [WARNING] AG Charts - Option `annotations.toolbar.buttons[0].value` cannot be set to `"statistic-select"`; expecting a keyword such as 'line-menu', 'fibonacci-menu', 'text-menu', 'shape-menu', 'measurer-menu', 'line', 'horizontal-line', 'vertical-line', 'parallel-channel', 'disjoint-channel', 'fibonacci-retracement', 'fibonacci-retracement-trend-based', 'text', 'comment', 'callout', 'note' or 'clear', ignoring. @ http://localhost:5174/node_modules/.vite/deps/chunk-XDC3NMYR.js?v=2852bbef:834 +[ 773698ms] [WARNING] AG Charts - Option `annotations.toolbar.buttons[0].value` cannot be set to `"statistic-select"`; expecting a keyword such as 'line-menu', 'fibonacci-menu', 'text-menu', 'shape-menu', 'measurer-menu', 'line', 'horizontal-line', 'vertical-line', 'parallel-channel', 'disjoint-channel', 'fibonacci-retracement', 'fibonacci-retracement-trend-based', 'text', 'comment', 'callout', 'note' or 'clear', ignoring. @ http://localhost:5174/node_modules/.vite/deps/chunk-XDC3NMYR.js?v=2852bbef:834 +[ 795038ms] [WARNING] AG Charts - Option `annotations.toolbar.buttons[0].value` cannot be set to `"statistic-select"`; expecting a keyword such as 'line-menu', 'fibonacci-menu', 'text-menu', 'shape-menu', 'measurer-menu', 'line', 'horizontal-line', 'vertical-line', 'parallel-channel', 'disjoint-channel', 'fibonacci-retracement', 'fibonacci-retracement-trend-based', 'text', 'comment', 'callout', 'note' or 'clear', ignoring. @ http://localhost:5174/node_modules/.vite/deps/chunk-XDC3NMYR.js?v=2852bbef:834 +[ 796833ms] [WARNING] AG Charts - Option `annotations.toolbar.buttons[0].value` cannot be set to `"statistic-select"`; expecting a keyword such as 'line-menu', 'fibonacci-menu', 'text-menu', 'shape-menu', 'measurer-menu', 'line', 'horizontal-line', 'vertical-line', 'parallel-channel', 'disjoint-channel', 'fibonacci-retracement', 'fibonacci-retracement-trend-based', 'text', 'comment', 'callout', 'note' or 'clear', ignoring. @ http://localhost:5174/node_modules/.vite/deps/chunk-XDC3NMYR.js?v=2852bbef:834 +[ 796944ms] [WARNING] AG Charts - Option `annotations.toolbar.buttons[0].value` cannot be set to `"statistic-select"`; expecting a keyword such as 'line-menu', 'fibonacci-menu', 'text-menu', 'shape-menu', 'measurer-menu', 'line', 'horizontal-line', 'vertical-line', 'parallel-channel', 'disjoint-channel', 'fibonacci-retracement', 'fibonacci-retracement-trend-based', 'text', 'comment', 'callout', 'note' or 'clear', ignoring. @ http://localhost:5174/node_modules/.vite/deps/chunk-XDC3NMYR.js?v=2852bbef:834 +[ 807892ms] [WARNING] AG Charts - Option `annotations.toolbar.buttons[0].value` cannot be set to `"statistic-select"`; expecting a keyword such as 'line-menu', 'fibonacci-menu', 'text-menu', 'shape-menu', 'measurer-menu', 'line', 'horizontal-line', 'vertical-line', 'parallel-channel', 'disjoint-channel', 'fibonacci-retracement', 'fibonacci-retracement-trend-based', 'text', 'comment', 'callout', 'note' or 'clear', ignoring. @ http://localhost:5174/node_modules/.vite/deps/chunk-XDC3NMYR.js?v=2852bbef:834 +[ 807915ms] [WARNING] AG Charts - Option `annotations.toolbar.buttons[0].value` cannot be set to `"statistic-select"`; expecting a keyword such as 'line-menu', 'fibonacci-menu', 'text-menu', 'shape-menu', 'measurer-menu', 'line', 'horizontal-line', 'vertical-line', 'parallel-channel', 'disjoint-channel', 'fibonacci-retracement', 'fibonacci-retracement-trend-based', 'text', 'comment', 'callout', 'note' or 'clear', ignoring. @ http://localhost:5174/node_modules/.vite/deps/chunk-XDC3NMYR.js?v=2852bbef:834 +[ 809704ms] [WARNING] AG Charts - Option `annotations.toolbar.buttons[0].value` cannot be set to `"statistic-select"`; expecting a keyword such as 'line-menu', 'fibonacci-menu', 'text-menu', 'shape-menu', 'measurer-menu', 'line', 'horizontal-line', 'vertical-line', 'parallel-channel', 'disjoint-channel', 'fibonacci-retracement', 'fibonacci-retracement-trend-based', 'text', 'comment', 'callout', 'note' or 'clear', ignoring. @ http://localhost:5174/node_modules/.vite/deps/chunk-XDC3NMYR.js?v=2852bbef:834 +[ 809719ms] [WARNING] AG Charts - Option `annotations.toolbar.buttons[0].value` cannot be set to `"statistic-select"`; expecting a keyword such as 'line-menu', 'fibonacci-menu', 'text-menu', 'shape-menu', 'measurer-menu', 'line', 'horizontal-line', 'vertical-line', 'parallel-channel', 'disjoint-channel', 'fibonacci-retracement', 'fibonacci-retracement-trend-based', 'text', 'comment', 'callout', 'note' or 'clear', ignoring. @ http://localhost:5174/node_modules/.vite/deps/chunk-XDC3NMYR.js?v=2852bbef:834 diff --git a/src/App.tsx b/src/App.tsx index b337fc3..acd0196 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -14,56 +14,138 @@ import { AG_CHARTS_LOCALE_ZH_CN } from 'ag-charts-locale'; LicenseManager.setLicenseKey('[v3][RELEASE][0102]_NDg2Njc4MzY3MDgzNw==16d78ca762fb5d2ff740aed081e2af7b'); ModuleRegistry.registerModules([AnnotationsModule, ContextMenuModule, ZoomModule, CrosshairModule]); -const yearlyCostData = [ - { year: '2021', cost: 2200000, buildingArea: 18600, builtArea: 17200, usableArea: 14800 }, - { year: '2022', cost: 2500000, buildingArea: 19100, builtArea: 17600, usableArea: 15300 }, - { year: '2023', cost: 3450000, buildingArea: 20600, builtArea: 19000, usableArea: 16400 }, - { year: '2024', cost: 9600000, buildingArea: 24800, builtArea: 23200, usableArea: 20100 }, - { year: '2025', cost: null, buildingArea: null, builtArea: null, usableArea: null }, -]; +const API_BASE_URL = 'http://127.0.0.1:9089/api/v1'; -const metricOptions = [ - { key: 'cost', label: '造价(元)', shortLabel: '造价' }, - { key: 'buildingArea', label: '建筑面积指标(元/m²)', shortLabel: '建筑' }, - { key: 'builtArea', label: '建造面积指标(元/m²)', shortLabel: '建造' }, - { key: 'usableArea', label: '使用面积指标(元/m²)', shortLabel: '使用' }, +const statisticOptions = [ + { key: 'minValue', label: '最低值', shortLabel: '低' }, + { key: 'maxValue', label: '最高值', shortLabel: '高' }, + { key: 'avgValue', label: '平均值', shortLabel: '均' }, + { key: 'medianValue', label: '中位数', shortLabel: '中' }, + { key: 'dataCount', label: '数据量', shortLabel: '量' }, ] as const; +const metricOptions = [ + { key: 'cost', label: '造价(元)' }, + { key: 'buildingArea', label: '建筑面积指标(元/m²)' }, + { key: 'builtArea', label: '建造面积指标(元/m²)' }, + { key: 'usableArea', label: '使用面积指标(元/m²)' }, +] as const; + +type StatisticKey = (typeof statisticOptions)[number]['key']; type MetricKey = (typeof metricOptions)[number]['key']; +type GroupKey = 'year'; +type ApiBuildingFunctionStat = { + group_key?: string | number | null; + group_name?: string | null; + min_value?: number | null; + max_value?: number | null; + avg_value?: number | null; + median_value?: number | null; + data_count?: number | null; +}; +type ChartDatum = { + groupName: string; + minValue: number | null; + maxValue: number | null; + avgValue: number | null; + medianValue: number | null; + dataCount: number | null; +}; function formatWan(value: number) { return `${Math.round(value / 10000).toLocaleString('zh-CN')}万`; } +function normalizeStat(row: ApiBuildingFunctionStat): ChartDatum { + return { + groupName: row.group_name || String(row.group_key ?? '未命名'), + minValue: row.min_value ?? null, + maxValue: row.max_value ?? null, + avgValue: row.avg_value ?? null, + medianValue: row.median_value ?? null, + dataCount: row.data_count ?? null, + }; +} + function App() { const workspaceRef = useRef(null); 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