This commit is contained in:
wintsa 2026-05-07 10:00:19 +08:00
parent 36fee1f291
commit 5b540acade
6 changed files with 268 additions and 42 deletions

Binary file not shown.

Binary file not shown.

View File

@ -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

View File

@ -14,46 +14,122 @@ 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<HTMLElement>(null);
const chartFrameRef = useRef<HTMLDivElement>(null);
const [statisticKey, setStatisticKey] = useState<StatisticKey>('avgValue');
const [metricKey, setMetricKey] = useState<MetricKey>('cost');
const [groupKey, setGroupKey] = useState<GroupKey>('year');
const [statisticMenuOpen, setStatisticMenuOpen] = useState(false);
const [metricMenuOpen, setMetricMenuOpen] = useState(false);
const [chartData, setChartData] = useState<ChartDatum[]>([]);
const [loading, setLoading] = useState(true);
const [loadError, setLoadError] = useState<string | null>(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<HTMLButtonElement>('.chart-fullscreen-button');
const getStatisticButton = () => frame.querySelector<HTMLElement>('.ag-charts-myButton-statistic')?.closest<HTMLButtonElement>('.ag-charts-toolbar__button');
const syncFullscreenButton = () => {
const syncToolbarButtons = () => {
const button = getFullscreenButton();
if (!button) return;
if (button) {
let icon = button.querySelector<HTMLElement>('.ag-charts-myButton-fullScreen');
if (!icon) {
button.innerHTML = '<i class="anticon anticon-arrow-salt ag-charts-myButton-fullScreen ag-charts-diy-button"></i>';
@ -64,6 +140,12 @@ function App() {
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<HTMLButtonElement>(
'.chart-fullscreen-button',
'.chart-fullscreen-button, .chart-statistic-button',
);
if (!button || !frame.contains(button)) return;
event.preventDefault();
event.stopPropagation();
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<HTMLButtonElement>(
'.chart-fullscreen-button',
'.chart-fullscreen-button, .chart-statistic-button',
);
if (!button || !frame.contains(button)) return;
event.preventDefault();
event.stopPropagation();
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<AgCartesianChartOptions>(() => {
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: `<span class="ag-charts-myButton-statistic ag-charts-diy-button">${selectedStatistic.shortLabel}</span>`,
},
{
icon: 'trend-line-drawing',
value: 'line-menu',
@ -222,15 +319,15 @@ function App() {
value: 'clear',
tooltip: 'Clear annotations',
},
],
] as unknown as NonNullable<NonNullable<AgCartesianChartOptions['annotations']>['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 (
<main className="dashboard-shell">
@ -320,6 +417,25 @@ function App() {
<section className="workspace" aria-label="年度费用模板" ref={workspaceRef}>
<section className="chart-area" aria-label="年度总费用图表">
<div className="chart-frame" ref={chartFrameRef}>
{statisticMenuOpen ? (
<div className="statistic-switcher-menu" role="menu" aria-label="切换统计指标">
{statisticOptions.map((option) => (
<button
className="statistic-switcher-menu-item"
type="button"
role="menuitem"
key={option.key}
aria-current={option.key === statisticKey}
onClick={() => {
setStatisticKey(option.key);
setStatisticMenuOpen(false);
}}
>
{option.label}
</button>
))}
</div>
) : null}
<div className="metric-switcher">
<button
className="metric-switcher-button"
@ -327,7 +443,11 @@ function App() {
title="切换纵坐标指标"
aria-expanded={metricMenuOpen}
aria-haspopup="menu"
onClick={() => setMetricMenuOpen((open) => !open)}
aria-label={`纵坐标:${selectedMetric.label}`}
onClick={() => {
setStatisticMenuOpen(false);
setMetricMenuOpen((open) => !open);
}}
>
{selectedMetric.label}
</button>
@ -351,6 +471,7 @@ function App() {
</div>
) : null}
</div>
{loading || loadError ? <div className="chart-status">{loading ? '加载中' : loadError}</div> : null}
<button className="chart-fullscreen-button ag-charts-toolbar__button" type="button" title="全屏(F11)">
<i className="anticon anticon-arrow-salt ag-charts-myButton-fullScreen ag-charts-diy-button" />
</button>

View File

@ -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;

View File

@ -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