React中如何为ag-Grid右侧表格设置默认分类并自动归类拖拽行
实现ag-Grid右侧表格默认分类及拖拽自动归类功能
问题概述
我正在开发一个使用ag-Grid的React应用,现有左右两个ag-Grid表格,已实现左侧行拖拽到右侧的功能,需要优化以下用户体验:
- 右侧表格无数据时,默认显示“Male”和“Female”分类
- 拖拽过来的行自动归入对应分类(如男性运动员到Male分类)
核心需求:
- 右侧表格始终展示Male、Female分组,无数据时不消失
- 拖拽行自动匹配到性别对应的分组
解决方案
利用ag-Grid的分组功能和拖拽事件处理,通过以下步骤实现需求:
1. 配置右侧表格分组规则
让右侧表格按gender字段分组,设置默认展开分组,同时保留空分组的显示。
2. 初始化空分组占位数据
为右侧表格添加Male、Female的空占位数据,确保无数据时分组依然可见。
3. 处理拖拽事件实现自动归类
修改拖拽结束事件,根据行数据的gender字段,将其添加到对应分组下,并移除对应性别的占位数据。
修改后的完整代码
import 'ag-grid-community/styles/ag-grid.css'; import 'ag-grid-community/styles/ag-theme-alpine.css'; import React, { useCallback, useEffect, useState } from 'react'; import { createRoot } from 'react-dom/client'; import { AgGridReact } from 'ag-grid-react'; const SportRenderer = (props) => { return ( <i className="far fa-trash-alt" style={{ cursor: 'pointer' }} onClick={() => props.api.applyTransaction({ remove: [props.node.data] })} ></i> ); }; const leftColumns = [ { rowDrag: true, maxWidth: 50, suppressMenu: true, rowDragText: (params, dragItemCount) => { if (dragItemCount > 1) { return dragItemCount + ' athletes'; } return params.rowNode.data.athlete; }, }, { colId: 'checkbox', maxWidth: 50, checkboxSelection: true, suppressMenu: true, headerCheckboxSelection: true, }, { field: 'athlete' }, { field: 'sport' }, { field: 'gender' }, ]; const rightColumns = [ { rowDrag: true, maxWidth: 50, suppressMenu: true, rowDragText: (params, dragItemCount) => { if (dragItemCount > 1) { return dragItemCount + ' athletes'; } return params.rowNode.data.athlete; }, }, { field: 'athlete' }, { field: 'sport' }, { field: 'gender' }, { suppressMenu: true, maxWidth: 50, cellRenderer: SportRenderer, }, ]; const defaultColDef = { flex: 1, minWidth: 100, sortable: true, filter: true, resizable: true, }; // 初始化空分组占位数据 const initialRightData = [ { athlete: '', sport: '', gender: 'Male', __isPlaceholder: true }, { athlete: '', sport: '', gender: 'Female', __isPlaceholder: true }, ]; const GridExample = () => { const [leftApi, setLeftApi] = useState(null); const [leftColumnApi, setLeftColumnApi] = useState(null); const [rightApi, setRightApi] = useState(null); const [rawData, setRawData] = useState([]); const [leftRowData, setLeftRowData] = useState(null); const [rightRowData, setRightRowData] = useState([...initialRightData]); const [radioChecked, setRadioChecked] = useState(0); const [checkBoxSelected, setCheckBoxSelected] = useState(true); useEffect(() => { if (!rawData.length) { fetch('https://www.ag-grid.com/example-assets/olympic-winners.json') .then((resp) => resp.json()) .then((data) => { const athletes = []; let i = 0; while (athletes.length < 20 && i < data.length) { var pos = i++; if (athletes.some((rec) => rec.athlete === data[pos].athlete)) { continue; } athletes.push(data[pos]); } setRawData(athletes); }); } }, [rawData]); const loadGrids = useCallback(() => { setLeftRowData([...rawData]); setRightRowData([...initialRightData]); leftApi.deselectAll(); }, [leftApi, rawData]); useEffect(() => { if (rawData.length) { loadGrids(); } }, [rawData, loadGrids]); useEffect(() => { if (leftColumnApi && leftApi) { leftColumnApi.setColumnVisible('checkbox', checkBoxSelected); leftApi.setSuppressRowClickSelection(checkBoxSelected); } }, [leftColumnApi, leftApi, checkBoxSelected]); const reset = () => { setRadioChecked(0); setCheckBoxSelected(true); loadGrids(); }; const onRadioChange = (e) => { setRadioChecked(parseInt(e.target.value, 10)); }; const onCheckboxChange = (e) => { const checked = e.target.checked; setCheckBoxSelected(checked); }; const getRowId = (params) => params.data.athlete || `${params.data.gender}-placeholder`; const onDragStop = useCallback( (params) => { const draggedNodes = params.nodes; const draggedData = draggedNodes.map(node => node.data); // 更新右侧表格数据:移除对应性别的占位符,添加拖拽数据 const updatedRightData = [...rightRowData]; draggedData.forEach(item => { // 移除该性别下的占位符(如果存在) const placeholderIndex = updatedRightData.findIndex( data => data.__isPlaceholder && data.gender === item.gender ); if (placeholderIndex !== -1) { updatedRightData.splice(placeholderIndex, 1); } // 添加拖拽过来的数据 updatedRightData.push(item); }); setRightRowData(updatedRightData); // 处理左侧表格的操作 if (radioChecked === 0) { leftApi.applyTransaction({ remove: draggedData, }); } else if (radioChecked === 1) { leftApi.setNodesSelected({ nodes: draggedNodes, newValue: false }); } }, [leftApi, radioChecked, rightRowData] ); useEffect(() => { if (!leftApi || !rightApi) { return; } const dropZoneParams = rightApi.getRowDropZoneParams({ onDragStop }); leftApi.removeRowDropZone(dropZoneParams); leftApi.addRowDropZone(dropZoneParams); }, [leftApi, rightApi, onDragStop]); const onGridReady = (params, side) => { if (side === 0) { setLeftApi(params.api); setLeftColumnApi(params.columnApi); } if (side === 1) { setRightApi(params.api); // 配置分组 params.columnApi.setRowGroupColumns(['gender']); } }; const getTopToolBar = () => ( <div className="example-toolbar panel panel-default"> <div className="panel-body"> <div onChange={onRadioChange}> <input type="radio" id="move" name="radio" value="0" checked={radioChecked === 0} /> {' '} <label htmlFor="move">Remove Source Rows</label> <input type="radio" id="deselect" name="radio" value="1" checked={radioChecked === 1} /> {' '} <label htmlFor="deselect">Only Deselect Source Rows</label> <input type="radio" id="none" name="radio" value="2" checked={radioChecked === 2} /> {' '} <label htmlFor="none">None</label> </div> <input type="checkbox" id="toggleCheck" checked={checkBoxSelected} onChange={onCheckboxChange} /> <label htmlFor="toggleCheck">Checkbox Select</label> <span className="input-group-button"> <button type="button" className="btn btn-default reset" style={{ marginLeft: '5px' }} onClick={reset} > <i className="fas fa-redo" style={{ marginRight: '5px' }}></i>Reset </button> </span> </div> </div> ); const getGridWrapper = (id) => ( <div className="panel panel-primary" style={{ marginRight: '10px', width: '48%' }}> <div className="panel-heading"> {id === 0 ? 'Athletes' : 'Selected Athletes'} </div> <div className="panel-body" style={{ height: '500px' }}> <AgGridReact style={{ height: '100%' }} defaultColDef={defaultColDef} getRowId={getRowId} rowDragManaged={true} animateRows={true} rowSelection={id === 0 ? 'multiple' : undefined} rowDragMultiRow={id === 0} suppressRowClickSelection={id === 0} suppressMoveWhenRowDragging={id === 0} rowData={id === 0 ? leftRowData : rightRowData} columnDefs={id === 0 ? leftColumns : rightColumns} onGridReady={(params) => onGridReady(params, id)} // 右侧表格分组配置 groupDefaultExpanded={1} suppressEmptyChildren={false} /> </div> </div> ); return ( <div className="top-container"> {getTopToolBar()} <div className="grid-wrapper ag-theme-alpine" style={{ display: 'flex', justifyContent: 'flex-start' }}> {getGridWrapper(0)} {getGridWrapper(1)} </div> </div> ); }; const root = createRoot(document.getElementById('root')); root.render(<GridExample />);
关键修改说明
- 空分组初始化:创建
initialRightData数组,包含Male和Female的占位数据,通过__isPlaceholder标记区分真实数据 - 分组配置:在右侧表格的
onGridReady中设置按gender分组,开启groupDefaultExpanded默认展开分组,suppressEmptyChildren={false}保留空分组显示 - 拖拽逻辑优化:修改
onDragStop事件,先移除对应性别的占位数据,再添加拖拽行,实现自动归类 - 行ID处理:更新
getRowId方法,为占位数据生成唯一ID,避免ag-Grid出现重复行ID报错
内容的提问来源于stack exchange,提问作者vishnu vinod
相关产品推荐
相关产品推荐

