This commit is contained in:
wintsa 2026-05-08 17:53:56 +08:00
parent 38978befb7
commit a891b5bccb

View File

@ -21,7 +21,6 @@ const statisticOptions = [
{ key: 'maxValue', label: '最高值', shortLabel: '高' },
{ key: 'avgValue', label: '平均值', shortLabel: '均' },
{ key: 'medianValue', label: '中位数', shortLabel: '中' },
{ key: 'dataCount', label: '数据量', shortLabel: '量' },
] as const;
const metricOptions = [
@ -29,6 +28,7 @@ const metricOptions = [
{ key: 'buildingArea', label: '建筑面积指标(元/m²' },
{ key: 'builtArea', label: '建造面积指标(元/m²' },
{ key: 'usableArea', label: '使用面积指标(元/m²' },
{ key: 'dataCount', label: '数据量' },
] as const;
const contentOptions = [
@ -179,8 +179,8 @@ function formatAreaMetricValue(value: number) {
return `${formatNumber(value, fractionDigits)}元/m²`;
}
function formatChartValue(value: number, metricKey: MetricKey, statisticKey: StatisticKey) {
if (statisticKey === 'dataCount') {
function formatChartValue(value: number, metricKey: MetricKey) {
if (metricKey === 'dataCount') {
return formatNumber(value, 0);
}
if (metricKey === 'cost') {
@ -306,7 +306,7 @@ function renderTreeNodes(
<ul className="content-tree-list" role={depth === 0 ? 'tree' : 'group'}>
{nodes.map((node) => {
const selected = selectedNodeKeys.has(getSelectionKey(contentKey, node.id));
const color = node.canClick ? getNodeColor(contentKey, node.id) : undefined;
const color = getNodeColor(contentKey, node.id);
return (
<li className="content-tree-node" role="treeitem" aria-expanded={node.hasChildren ? node.expanded : undefined} key={node.id}>
@ -322,13 +322,10 @@ function renderTreeNodes(
className="content-tree-select"
type="button"
aria-pressed={selected}
disabled={!node.canClick}
onClick={() => {
if (node.canClick) onSelect(node);
}}
onClick={() => onSelect(node)}
title={node.label}
>
{node.canClick ? <span className="content-tree-series-mark" style={{ backgroundColor: color }} /> : null}
<span className="content-tree-series-mark" style={{ backgroundColor: color }} />
<span className="content-tree-label">{node.label}</span>
</button>
{node.loading ? <span className="content-tree-loading"></span> : null}
@ -387,6 +384,9 @@ function App() {
const selectedMetric = metricOptions.find((option) => option.key === metricKey) ?? metricOptions[0];
const activeContent = contentOptions.find((option) => option.key === activeContentKey) ?? contentOptions[0];
const activeTree = treeByContent[activeContentKey];
const selectedValueKey = metricKey === 'dataCount' ? 'dataCount' : statisticKey;
const requestMetricKey = metricKey === 'dataCount' ? 'cost' : metricKey;
const seriesValueLabel = metricKey === 'dataCount' ? selectedMetric.label : selectedStatistic.label;
const selectedNodeKeys = useMemo(
() => new Set(selectedContentNodes.map((node) => getSelectionKey(node.contentKey, node.id))),
[selectedContentNodes],
@ -536,6 +536,16 @@ function App() {
});
};
const handleActiveContentKeyChange = (nextContentKey: ContentKey) => {
if (nextContentKey === activeContentKey) return;
setActiveContentKey(nextContentKey);
setSelectedContentNodes([]);
setChartDataBySelection({});
setLoadError(null);
setLoadingHint('');
setLoading(false);
};
const updateMetricKey = (nextMetricKey: MetricKey) => {
setMetricKey(nextMetricKey);
setChartDataBySelection({});
@ -599,12 +609,12 @@ function App() {
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
groupBy: groupKey,
metric: metricKey,
nodes: selectedContentNodes.map((node) => ({
key: getSelectionKey(node.contentKey, node.id),
contentKey: node.contentKey,
body: JSON.stringify({
groupBy: groupKey,
metric: requestMetricKey,
nodes: selectedContentNodes.map((node) => ({
key: getSelectionKey(node.contentKey, node.id),
contentKey: node.contentKey,
nodeId: node.id,
})),
}),
@ -636,7 +646,7 @@ function App() {
return () => {
controller.abort();
};
}, [chartQueryVersion, groupKey, metricKey, selectedContentNodes]);
}, [chartQueryVersion, groupKey, metricKey, requestMetricKey, selectedContentNodes]);
useEffect(() => {
const frame = chartFrameRef.current;
@ -751,7 +761,6 @@ function App() {
}, [statisticMenuOpen]);
const chartOptions = useMemo<AgCartesianChartOptions>(() => {
const isCount = statisticKey === 'dataCount';
const groupNames: string[] = [];
const groupNameSeen = new Set<string>();
selectedContentNodes.forEach((node) => {
@ -766,7 +775,7 @@ function App() {
const row: Record<string, string | number | null> = { groupName };
selectedContentNodes.forEach((node, index) => {
const datum = chartDataBySelection[getSelectionKey(node.contentKey, node.id)]?.find((item) => item.groupName === groupName);
row[getSeriesValueKey(index)] = datum?.[statisticKey] ?? null;
row[getSeriesValueKey(index)] = datum?.[selectedValueKey] ?? null;
});
return row;
});
@ -866,7 +875,7 @@ function App() {
type: 'line',
xKey: 'groupName',
yKey: getSeriesValueKey(index),
yName: `${node.label} ${selectedStatistic.label}`,
yName: `${node.label} ${seriesValueLabel}`,
stroke: node.color,
strokeWidth: 2,
marker: {
@ -882,7 +891,7 @@ function App() {
renderer: ({ datum, yKey, yName }) => ({
title: yName,
data: [
{ label: selectedMetric.label, value: formatChartValue(Number(datum[yKey]), metricKey, statisticKey) },
{ label: selectedMetric.label, value: formatChartValue(Number(datum[yKey]), metricKey) },
],
}),
},
@ -915,7 +924,7 @@ function App() {
label: {
color: '#1f2933',
fontSize: 12,
formatter: ({ value }) => formatChartValue(Number(value), metricKey, statisticKey),
formatter: ({ value }) => formatChartValue(Number(value), metricKey),
},
line: {
enabled: false,
@ -942,9 +951,11 @@ function App() {
},
tooltip: {
enabled: true,
mode: 'shared',
pagination: true,
},
};
}, [chartDataBySelection, metricKey, selectedContentNodes, selectedMetric.label, selectedStatistic.label, statisticKey]);
}, [chartDataBySelection, metricKey, requestMetricKey, selectedContentNodes, selectedMetric.label, seriesValueLabel, selectedValueKey, statisticKey]);
return (
<main className="dashboard-shell">
@ -1033,7 +1044,7 @@ function App() {
role="tab"
key={option.key}
aria-selected={option.key === activeContentKey}
onClick={() => setActiveContentKey(option.key)}
onClick={() => handleActiveContentKeyChange(option.key)}
>
{option.label}
</button>