JGJS2026/src/lib/diyAgGridOptions.ts
2026-03-26 14:54:26 +08:00

205 lines
5.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import type {
CellPosition,
ColDef,
GridApi,
GridSizeChangedEvent,
FirstDataRenderedEvent,
RowDataUpdatedEvent,
ColumnResizedEvent,
GridOptions,
SuppressKeyboardEventParams
} from 'ag-grid-community'
import { themeQuartz } from 'ag-grid-community'
const borderConfig = {
style: 'solid',
width: 0.3,
color: 'var(--border)'
}
export const myTheme = themeQuartz.withParams({
wrapperBorder: false,
wrapperBorderRadius: 0,
headerBackgroundColor: 'var(--muted)',
headerTextColor: 'var(--foreground)',
headerFontSize: 15,
headerFontWeight: 'normal',
rowBorder: borderConfig,
columnBorder: borderConfig,
headerRowBorder: borderConfig,
dataBackgroundColor: 'var(--card)'
})
// AG Grid 容器通用 class占满父容器配合父元素为 flex/grid 且有明确高度使用)
export const agGridWrapClass = 'ag-theme-quartz h-full min-h-0 w-full flex-1'
// AG Grid 组件通用 style撑满容器 div
export const agGridStyle = { height: '100%' }
const numericFieldKeywords = [
'amount',
'area',
'cost',
'price',
'fee',
'budget',
'subtotal',
'total',
'ratio',
'rate',
'quantity',
'count',
'num',
'workday',
'workload',
'hourly',
'investscale',
'landscale',
'scale',
'finalfee',
'value',
'coe',
'factor'
]
const isLikelyNumericColumn = (params: any) => {
const value = params?.value
if (typeof value === 'number' && Number.isFinite(value)) return true
const field = String(params?.colDef?.field || params?.column?.getColId?.() || '').toLowerCase()
if (!field) return false
return numericFieldKeywords.some(keyword => field.includes(keyword))
}
const isPlainEnterKey = (event: KeyboardEvent) =>
event.key === 'Enter' && !event.altKey && !event.ctrlKey && !event.metaKey
const findNextEditableCellInColumn = (
params: SuppressKeyboardEventParams,
startRowIndex: number
): CellPosition | null => {
const column = params.column
for (let rowIndex = startRowIndex + 1; rowIndex < params.api.getDisplayedRowCount(); rowIndex += 1) {
const rowNode = params.api.getDisplayedRowAtIndex(rowIndex)
if (!rowNode || rowNode.group || rowNode.rowPinned) continue
if (!column.isCellEditable(rowNode)) continue
return {
rowIndex,
rowPinned: rowNode.rowPinned ?? null,
column
}
}
return null
}
const focusCellPosition = (
params: SuppressKeyboardEventParams,
cellPosition: CellPosition | null
) => {
const target = cellPosition || {
rowIndex: params.node.rowIndex ?? 0,
rowPinned: params.node.rowPinned ?? null,
column: params.column
}
window.setTimeout(() => {
if (params.api.isDestroyed?.()) return
params.api.ensureIndexVisible(target.rowIndex)
params.api.setFocusedCell(target.rowIndex, target.column, target.rowPinned)
}, 0)
}
const suppressExcelLikeEnter = (params: SuppressKeyboardEventParams) => {
if (!isPlainEnterKey(params.event)) return false
if (params.event.defaultPrevented || params.event.isComposing) return false
params.event.preventDefault()
params.event.stopPropagation()
params.api.stopEditing()
const currentRowIndex = params.node.rowIndex
if (currentRowIndex == null) {
focusCellPosition(params, null)
return true
}
const nextCell = findNextEditableCellInColumn(params, currentRowIndex)
focusCellPosition(params, nextCell)
return true
}
const syncRowHeightsWithJs = (api: GridApi | null | undefined) => {
if (!api || api.isDestroyed?.()) return
// 统一使用 JS 重算,规避 wrapText/居中样式组合导致的高度滞后。
setTimeout(() => {
if (!api || api.isDestroyed?.()) return
api.onRowHeightChanged()
api.refreshCells({ force: true })
api.redrawRows()
}, 0)
}
export const agGridDefaultColDef: ColDef = {
resizable: true,
sortable: false,
filter: false,
wrapHeaderText: true,
autoHeaderHeight: true,
suppressKeyboardEvent: suppressExcelLikeEnter,
// 默认把数值型单元格右对齐,减少每个列重复配置。
cellClassRules: {
'ag-right-aligned-cell': params => isLikelyNumericColumn(params)
}
}
export const gridOptions: GridOptions = {
treeData: true,
animateRows: true,
tooltipShowMode: 'whenTruncated',
suppressAggFuncInHeader: true,
singleClickEdit: true,
stopEditingWhenCellsLoseFocus: true,
suppressClickEdit: false,
suppressContextMenu: false,
groupDefaultExpanded: -1,
suppressFieldDotNotation: true,
enterNavigatesVertically: true,
enterNavigatesVerticallyAfterEdit: true,
// rowData 更新后通过稳定 ID 维持展开状态和编辑上下文。
getRowId: params => {
const id = params.data?.id
if (id != null && String(id).trim()) return String(id)
const path = Array.isArray(params.data?.path)
? params.data.path.map((segment: unknown) => String(segment ?? '').trim()).filter(Boolean)
: []
if (path.length > 0) return path.join('/')
return '__row__'
},
// 兜底避免 AG Grid #185treeData 模式下 path 不能为空数组。
getDataPath: data => {
const path = Array.isArray(data?.path)
? data.path.map((segment: unknown) => String(segment ?? '').trim()).filter(Boolean)
: []
if (path.length > 0) return path
const fallback = String(data?.id ?? '').trim()
return [fallback || '__row__']
},
getContextMenuItems: () => ['copy', 'paste', 'separator', 'export'],
defaultColDef: agGridDefaultColDef,
defaultColGroupDef: {
wrapHeaderText: true,
autoHeaderHeight: true
},
onFirstDataRendered: (event: FirstDataRenderedEvent) => {
syncRowHeightsWithJs(event.api)
},
onRowDataUpdated: (event: RowDataUpdatedEvent) => {
syncRowHeightsWithJs(event.api)
},
onGridSizeChanged: (event: GridSizeChangedEvent) => {
syncRowHeightsWithJs(event.api)
},
onColumnResized: (event: ColumnResizedEvent) => {
syncRowHeightsWithJs(event.api)
}
}