This commit is contained in:
wintsa 2026-08-11 15:25:14 +08:00
parent 1f8325b1c1
commit 2e489de7a3
2 changed files with 94 additions and 13 deletions

View File

@ -44,6 +44,8 @@ const metricOptions = [
{ key: 'dataCount', label: '数据量' },
] as const;
const gridMetricOptions = metricOptions.filter((option) => option.key !== 'dataCount');
const metricShortLabels: Record<MetricKey, string> = {
cost: '价',
buildingArea: '建',
@ -729,6 +731,7 @@ function App() {
const [rightPanelCollapsed, setRightPanelCollapsed] = useState(false);
const [statisticMenuOpen, setStatisticMenuOpen] = useState(false);
const [metricMenuOpen, setMetricMenuOpen] = useState(false);
const [gridContextMenuPosition, setGridContextMenuPosition] = useState<{ left: number; top: number } | null>(null);
const [activeContentKey, setActiveContentKey] = useState<ContentKey>('geoLocation');
const [treeByContent, setTreeByContent] = useState<Record<ContentKey, TreeNode[]>>({
geoLocation: [],
@ -979,7 +982,7 @@ function App() {
],
},
{
headerName: '样本统计值',
headerName: `样本统计值(${pivotGridRowData.length}`,
children: [
{
field: 'maxValue',
@ -1036,7 +1039,7 @@ function App() {
},
];
},
[requestMetricKey],
[pivotGridRowData.length, requestMetricKey],
);
const fitPivotGridColumns = useCallback(() => {
window.requestAnimationFrame(() => {
@ -1049,6 +1052,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(
() => new Set(selectedContentNodes.map((node) => getSelectionKey(node.contentKey, node.id))),
[selectedContentNodes],
@ -1616,9 +1642,16 @@ function App() {
};
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);
setStatisticMenuOpen(false);
setGridContextMenuPosition(null);
}, []);
const toggleWorkspaceFullscreen = useCallback(() => {
@ -1889,10 +1922,6 @@ function App() {
handleToolbarAction(button);
};
const suppressBrowserContextMenu = (event: MouseEvent) => {
event.preventDefault();
};
const observer = new MutationObserver(syncToolbarButtons);
const initialSyncTimers = [
window.setTimeout(syncToolbarButtons, 0),
@ -1902,8 +1931,6 @@ function App() {
];
document.addEventListener('keydown', handleKeyDown, true);
document.addEventListener('fullscreenchange', handleFullscreenChange);
document.addEventListener('contextmenu', suppressBrowserContextMenu);
frame.addEventListener('contextmenu', suppressBrowserContextMenu);
frame.addEventListener('click', handleToolbarClick, true);
frame.addEventListener('keydown', handleToolbarKeyDown, true);
observer.observe(frame, { childList: true, subtree: true });
@ -1913,8 +1940,6 @@ function App() {
initialSyncTimers.forEach((timer) => window.clearTimeout(timer));
document.removeEventListener('keydown', handleKeyDown, true);
document.removeEventListener('fullscreenchange', handleFullscreenChange);
document.removeEventListener('contextmenu', suppressBrowserContextMenu);
frame.removeEventListener('contextmenu', suppressBrowserContextMenu);
frame.removeEventListener('click', handleToolbarClick, true);
frame.removeEventListener('keydown', handleToolbarKeyDown, true);
observer.disconnect();
@ -2180,7 +2205,7 @@ function App() {
</button>
{metricMenuOpen ? (
<div className="metric-switcher-menu" role="menu" aria-label="切换纵坐标指标">
{metricOptions.map((option) => (
{(variant === 'grid' ? gridMetricOptions : metricOptions).map((option) => (
<button
className="metric-switcher-menu-item"
type="button"
@ -2291,7 +2316,30 @@ function App() {
{loading || loadError ? <div className="chart-status">{loading ? loadingHint || '加载中' : loadError}</div> : null}
<AgCharts options={chartOptions} />
{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
className="chart-grid-tool-button chart-grid-tool-button--template"
type="button"

View File

@ -452,6 +452,39 @@ button {
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 {
position: absolute;
left: 24px;