This commit is contained in:
wintsa 2026-08-11 15:25:32 +08:00
parent d5799d8d99
commit 559a296c3a
2 changed files with 94 additions and 13 deletions

View File

@ -50,6 +50,8 @@ const metricOptions = [
{ key: 'dataCount', label: '数据量' }, { key: 'dataCount', label: '数据量' },
] as const; ] as const;
const gridMetricOptions = metricOptions.filter((option) => option.key !== 'dataCount');
const metricShortLabels: Record<MetricKey, string> = { const metricShortLabels: Record<MetricKey, string> = {
cost: '价', cost: '价',
buildingArea: '建', buildingArea: '建',
@ -748,6 +750,7 @@ function App() {
const [rightPanelCollapsed, setRightPanelCollapsed] = useState(false); const [rightPanelCollapsed, setRightPanelCollapsed] = useState(false);
const [statisticMenuOpen, setStatisticMenuOpen] = useState(false); const [statisticMenuOpen, setStatisticMenuOpen] = useState(false);
const [metricMenuOpen, setMetricMenuOpen] = useState(false); const [metricMenuOpen, setMetricMenuOpen] = useState(false);
const [gridContextMenuPosition, setGridContextMenuPosition] = useState<{ left: number; top: number } | null>(null);
const [activeContentKey, setActiveContentKey] = useState<ContentKey>('geoLocation'); const [activeContentKey, setActiveContentKey] = useState<ContentKey>('geoLocation');
const [treeByContent, setTreeByContent] = useState<Record<ContentKey, TreeNode[]>>({ const [treeByContent, setTreeByContent] = useState<Record<ContentKey, TreeNode[]>>({
geoLocation: [], geoLocation: [],
@ -1012,7 +1015,7 @@ function App() {
], ],
}, },
{ {
headerName: '样本统计值', headerName: `样本统计值(${pivotGridRowData.length}`,
children: [ children: [
{ {
field: 'maxValue', field: 'maxValue',
@ -1069,7 +1072,7 @@ function App() {
}, },
]; ];
}, },
[requestMetricKey], [pivotGridRowData.length, requestMetricKey],
); );
const fitPivotGridColumns = useCallback(() => { const fitPivotGridColumns = useCallback(() => {
window.requestAnimationFrame(() => { window.requestAnimationFrame(() => {
@ -1082,6 +1085,29 @@ function App() {
}); });
}); });
}, []); }, []);
const exportPivotGrid = useCallback(() => {
pivotGridApiRef.current?.exportDataAsCsv({ fileName: '建筑设施对象统计表.csv' });
setGridContextMenuPosition(null);
}, []);
useEffect(() => {
if (!gridContextMenuPosition) return;
const closeMenu = () => setGridContextMenuPosition(null);
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === 'Escape') closeMenu();
};
document.addEventListener('mousedown', closeMenu);
document.addEventListener('keydown', handleKeyDown);
window.addEventListener('blur', closeMenu);
window.addEventListener('resize', closeMenu);
return () => {
document.removeEventListener('mousedown', closeMenu);
document.removeEventListener('keydown', handleKeyDown);
window.removeEventListener('blur', closeMenu);
window.removeEventListener('resize', closeMenu);
};
}, [gridContextMenuPosition]);
const selectedNodeKeys = useMemo( const selectedNodeKeys = useMemo(
() => new Set(selectedContentNodes.map((node) => getSelectionKey(node.contentKey, node.id))), () => new Set(selectedContentNodes.map((node) => getSelectionKey(node.contentKey, node.id))),
[selectedContentNodes], [selectedContentNodes],
@ -1654,9 +1680,16 @@ function App() {
}; };
const togglePivotView = useCallback(() => { const togglePivotView = useCallback(() => {
setChartViewKey((current) => (current === 'trend' ? 'pivot' : 'trend')); setChartViewKey((current) => {
if (current === 'trend') {
setMetricKey((selectedKey) => selectedKey === 'dataCount' ? metricOptions[0].key : selectedKey);
return 'pivot';
}
return 'trend';
});
setMetricMenuOpen(false); setMetricMenuOpen(false);
setStatisticMenuOpen(false); setStatisticMenuOpen(false);
setGridContextMenuPosition(null);
}, []); }, []);
const toggleWorkspaceFullscreen = useCallback(() => { const toggleWorkspaceFullscreen = useCallback(() => {
@ -1926,10 +1959,6 @@ function App() {
handleToolbarAction(button); handleToolbarAction(button);
}; };
const suppressBrowserContextMenu = (event: MouseEvent) => {
event.preventDefault();
};
const observer = new MutationObserver(syncToolbarButtons); const observer = new MutationObserver(syncToolbarButtons);
const initialSyncTimers = [ const initialSyncTimers = [
window.setTimeout(syncToolbarButtons, 0), window.setTimeout(syncToolbarButtons, 0),
@ -1939,8 +1968,6 @@ function App() {
]; ];
document.addEventListener('keydown', handleKeyDown, true); document.addEventListener('keydown', handleKeyDown, true);
document.addEventListener('fullscreenchange', handleFullscreenChange); document.addEventListener('fullscreenchange', handleFullscreenChange);
document.addEventListener('contextmenu', suppressBrowserContextMenu);
frame.addEventListener('contextmenu', suppressBrowserContextMenu);
frame.addEventListener('click', handleToolbarClick, true); frame.addEventListener('click', handleToolbarClick, true);
frame.addEventListener('keydown', handleToolbarKeyDown, true); frame.addEventListener('keydown', handleToolbarKeyDown, true);
observer.observe(frame, { childList: true, subtree: true }); observer.observe(frame, { childList: true, subtree: true });
@ -1950,8 +1977,6 @@ function App() {
initialSyncTimers.forEach((timer) => window.clearTimeout(timer)); initialSyncTimers.forEach((timer) => window.clearTimeout(timer));
document.removeEventListener('keydown', handleKeyDown, true); document.removeEventListener('keydown', handleKeyDown, true);
document.removeEventListener('fullscreenchange', handleFullscreenChange); document.removeEventListener('fullscreenchange', handleFullscreenChange);
document.removeEventListener('contextmenu', suppressBrowserContextMenu);
frame.removeEventListener('contextmenu', suppressBrowserContextMenu);
frame.removeEventListener('click', handleToolbarClick, true); frame.removeEventListener('click', handleToolbarClick, true);
frame.removeEventListener('keydown', handleToolbarKeyDown, true); frame.removeEventListener('keydown', handleToolbarKeyDown, true);
observer.disconnect(); observer.disconnect();
@ -2217,7 +2242,7 @@ function App() {
</button> </button>
{metricMenuOpen ? ( {metricMenuOpen ? (
<div className="metric-switcher-menu" role="menu" aria-label="切换纵坐标指标"> <div className="metric-switcher-menu" role="menu" aria-label="切换纵坐标指标">
{metricOptions.map((option) => ( {(variant === 'grid' ? gridMetricOptions : metricOptions).map((option) => (
<button <button
className="metric-switcher-menu-item" className="metric-switcher-menu-item"
type="button" type="button"
@ -2325,7 +2350,30 @@ function App() {
{loading || loadError ? <div className="chart-status">{loading ? loadingHint || '加载中' : loadError}</div> : null} {loading || loadError ? <div className="chart-status">{loading ? loadingHint || '加载中' : loadError}</div> : null}
<AgCharts options={chartOptions} /> <AgCharts options={chartOptions} />
{chartViewKey === 'pivot' ? ( {chartViewKey === 'pivot' ? (
<div className="chart-pivot-grid-panel ag-theme-quartz"> <div
className="chart-pivot-grid-panel ag-theme-quartz"
onContextMenu={(event) => {
event.preventDefault();
setMetricMenuOpen(false);
setStatisticMenuOpen(false);
setGridContextMenuPosition({
left: Math.max(8, Math.min(event.clientX, window.innerWidth - 132)),
top: Math.max(8, Math.min(event.clientY, window.innerHeight - 44)),
});
}}
>
{gridContextMenuPosition ? (
<div
className="grid-context-menu"
role="menu"
style={gridContextMenuPosition}
onMouseDown={(event) => event.stopPropagation()}
>
<button type="button" role="menuitem" onClick={exportPivotGrid}>
</button>
</div>
) : null}
<button <button
className="chart-grid-tool-button chart-grid-tool-button--template" className="chart-grid-tool-button chart-grid-tool-button--template"
type="button" type="button"

View File

@ -452,6 +452,39 @@ button {
border-radius: 3px; border-radius: 3px;
} }
.grid-context-menu {
position: fixed;
z-index: 50;
min-width: 128px;
padding: 4px;
border: 1px solid rgba(90, 82, 72, 0.22);
border-radius: 3px;
background: #fffaf4;
box-shadow: 0 6px 18px rgba(69, 54, 36, 0.18);
}
.grid-context-menu button {
display: block;
width: 100%;
min-height: 32px;
padding: 0 10px;
border: 0;
border-radius: 2px;
color: #262a33;
background: transparent;
font-size: 13px;
line-height: 32px;
text-align: left;
cursor: pointer;
}
.grid-context-menu button:hover,
.grid-context-menu button:focus-visible {
color: #0078a8;
background: rgba(0, 120, 168, 0.08);
outline: none;
}
.chart-grid-tool-button { .chart-grid-tool-button {
position: absolute; position: absolute;
left: 24px; left: 24px;