feat(data-grid): 支持表头整列选择

- 单元格编辑模式下点击可写列表头选中整列数据

- 保留排序、筛选、外键跳转和列宽调整交互

- 补充表头整列选择回归测试

Fixes #622
This commit is contained in:
Syngnat
2026-07-10 22:37:39 +08:00
parent 328c9dbb28
commit cf979f436c
2 changed files with 78 additions and 2 deletions

View File

@@ -1036,6 +1036,54 @@ describe('DataGrid DDL interactions', () => {
},
);
it('selects every editable cell in a column when its header is clicked in cell edit mode', async () => {
messageApi.info.mockResolvedValue(undefined);
let renderer: ReactTestRenderer;
await act(async () => {
renderer = create(
<DataGrid
data={[
{ __gonavi_row_key__: 'row-1', id: 1, name: 'Ada' },
{ __gonavi_row_key__: 'row-2', id: 2, name: 'Linus' },
]}
columnNames={['id', 'name']}
loading={false}
tableName="users"
dbName="main"
connectionId="conn-1"
pkColumns={['id']}
/>,
);
});
await waitForEffects();
await act(async () => {
renderer!.root.findByType(DataGridToolbarFrame).props.onToggleCellEditMode();
});
await waitForEffects();
const nameColumn = testRenderState.latestColumns.find((column) => column.key === 'name');
expect(nameColumn?.editable).toBe(true);
const headerProps = nameColumn.onHeaderCell(nameColumn);
const event = {
target: { closest: vi.fn(() => null) },
currentTarget: { querySelector: vi.fn(() => null) },
preventDefault: vi.fn(),
stopPropagation: vi.fn(),
};
await act(async () => {
headerProps.onClickCapture(event);
});
const toolbar = renderer!.root.findByType(DataGridToolbarFrame);
expect(event.preventDefault).toHaveBeenCalledOnce();
expect(event.stopPropagation).toHaveBeenCalledOnce();
expect(toolbar.props.cellEditMode).toBe(true);
expect(toolbar.props.selectedCellsSize).toBe(2);
renderer!.unmount();
});
it('opens the v2 column header context menu from table headers', async () => {
setCurrentLanguage('en-US');
storeState.appearance.uiVersion = 'v2';

View File

@@ -1599,6 +1599,27 @@ const DataGrid: React.FC<DataGridProps> = ({
resetCellSelection();
}, [resetCellSelection]);
const selectEditableColumnCells = useCallback((columnName: string) => {
if (
!cellEditModeRef.current
|| !canModifyData
|| !isWritableResultColumn(columnName, effectiveEditLocator)
) {
return;
}
resetCellSelection(false);
const nextSelection = new Set<string>();
displayDataRef.current.forEach((row) => {
const rowKey = row?.[GONAVI_ROW_KEY];
if (rowKey === undefined || rowKey === null) return;
nextSelection.add(makeCellKey(rowKeyStr(rowKey), columnName));
});
currentSelectionRef.current = nextSelection;
setSelectedCells(nextSelection);
updateCellSelection(nextSelection);
}, [canModifyData, effectiveEditLocator, makeCellKey, resetCellSelection, rowKeyStr, updateCellSelection]);
useEffect(() => {
closeCellEditModeRef.current = closeCellEditMode;
}, [closeCellEditMode]);
@@ -2548,12 +2569,19 @@ const DataGrid: React.FC<DataGridProps> = ({
showColumnHeaderContextMenu(event, key);
},
onClickCapture: (event: React.MouseEvent<HTMLElement>) => {
if (!onSort) return;
const eventTarget = event.target as HTMLElement | null;
if (eventTarget?.closest?.('[data-grid-fk-jump="true"]')) return;
if (eventTarget?.closest?.('[data-grid-column-filter-trigger="true"]')) return;
if (eventTarget?.closest?.('[data-grid-column-filter-popover="true"]')) return;
if (eventTarget?.closest?.('.ant-select-dropdown')) return;
if (eventTarget?.closest?.('.react-resizable-handle')) return;
if (cellEditMode && canModifyData && isWritableResultColumn(key, effectiveEditLocator)) {
event.preventDefault();
event.stopPropagation();
selectEditableColumnCells(key);
return;
}
if (!onSort) return;
const headerCell = event.currentTarget as HTMLElement;
const upArrow = headerCell.querySelector('.ant-table-column-sorter-up') as HTMLElement | null;
const downArrow = headerCell.querySelector('.ant-table-column-sorter-down') as HTMLElement | null;
@@ -2574,7 +2602,7 @@ const DataGrid: React.FC<DataGridProps> = ({
},
}),
}));
}, [canModifyData, columnWidths, currentConnConfig, dataTableDensity, displayColumnNames, displayColumnTypeMap, enableVirtual, handleResizeAutoFit, handleResizeStart, isV2Ui, language, normalizedPageFindText, onSort, pinnedLeftColumnSet, renderColumnTitle, showColumnComment, showColumnHeaderContextMenu, showColumnType, sortInfo]);
}, [canModifyData, cellEditMode, columnWidths, currentConnConfig, dataTableDensity, displayColumnNames, displayColumnTypeMap, effectiveEditLocator, enableVirtual, handleResizeAutoFit, handleResizeStart, isV2Ui, language, normalizedPageFindText, onSort, pinnedLeftColumnSet, renderColumnTitle, selectEditableColumnCells, showColumnComment, showColumnHeaderContextMenu, showColumnType, sortInfo]);
const mergedColumns = useMemo(() => columns.map((col): ColumnType<any> => {
const dataIndex = String(col.dataIndex);