From 17e4e3ad1c77d0d081237276cb995d165e0b651b Mon Sep 17 00:00:00 2001 From: Syngnat Date: Fri, 20 Mar 2026 15:37:17 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(data-grid):=20=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E5=BA=95=E9=83=A8=E6=95=B0=E6=8D=AE=E9=A2=84=E8=A7=88?= =?UTF-8?q?=E9=9D=A2=E6=9D=BF=E6=94=AF=E6=8C=81=E9=95=BF=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=E5=AE=8C=E6=95=B4=E6=9F=A5=E7=9C=8B=E4=B8=8E?= =?UTF-8?q?=E7=BC=96=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 工具栏新增「数据预览」切换按钮,点击展开/收起底部面板 - 单击单元格自动更新面板内容,完整展示长文本和 JSON 数据 - 面板使用 Monaco Editor,JSON 数据自动语法高亮 - 编辑模式下支持直接修改并保存,只读模式下 Editor 设为 readOnly - 支持 JSON 一键格式化功能 - 通过 ref 追踪面板状态避免 mergedColumns 过度重渲染 - refs #271 --- frontend/src/components/DataGrid.tsx | 137 +++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) diff --git a/frontend/src/components/DataGrid.tsx b/frontend/src/components/DataGrid.tsx index b5a1ff4..bb3f353 100644 --- a/frontend/src/components/DataGrid.tsx +++ b/frontend/src/components/DataGrid.tsx @@ -1112,6 +1112,14 @@ const DataGrid: React.FC = ({ const cellEditorApplyRef = useRef<((val: string) => void) | null>(null); const [jsonEditorOpen, setJsonEditorOpen] = useState(false); const [jsonEditorValue, setJsonEditorValue] = useState(''); + + // --- Data Preview Panel State --- + const [dataPanelOpen, setDataPanelOpen] = useState(false); + const dataPanelOpenRef = useRef(false); + const [focusedCellInfo, setFocusedCellInfo] = useState<{ record: Item; dataIndex: string; title: string } | null>(null); + const [dataPanelValue, setDataPanelValue] = useState(''); + const [dataPanelIsJson, setDataPanelIsJson] = useState(false); + const dataPanelDirtyRef = useRef(false); const [rowEditorOpen, setRowEditorOpen] = useState(false); const [rowEditorRowKey, setRowEditorRowKey] = useState(''); const rowEditorBaseRawRef = useRef>({}); @@ -1420,6 +1428,34 @@ const DataGrid: React.FC = ({ cellEditorApplyRef.current = null; }, []); + // --- Data Preview Panel Helpers --- + const updateFocusedCell = useCallback((record: Item, dataIndex: string) => { + if (!record || !dataIndex) return; + const raw = record?.[dataIndex]; + const text = toEditableText(raw); + const isJson = looksLikeJsonText(text); + setFocusedCellInfo({ record, dataIndex, title: dataIndex }); + // 仅在面板未被用户手动编辑时自动同步值 + if (!dataPanelDirtyRef.current) { + setDataPanelValue(text); + setDataPanelIsJson(isJson); + } + }, []); + + const handleDataPanelFormatJson = useCallback(() => { + if (!dataPanelIsJson) return; + try { + const obj = JSON.parse(dataPanelValue); + setDataPanelValue(JSON.stringify(obj, null, 2)); + dataPanelDirtyRef.current = true; + } catch (e: any) { + void message.error('JSON 格式无效:' + (e?.message || String(e))); + } + }, [dataPanelIsJson, dataPanelValue]); + + // 同步 ref 用于 onCell 闭包 + useEffect(() => { dataPanelOpenRef.current = dataPanelOpen; }, [dataPanelOpen]); + const openCellEditor = useCallback((record: Item, dataIndex: string, title: React.ReactNode, onApplyValue?: (val: string) => void) => { if (!record || !dataIndex) return; const raw = record?.[dataIndex]; @@ -2818,6 +2854,14 @@ const DataGrid: React.FC = ({ } }, [addedRows]); + const handleDataPanelSave = useCallback(() => { + if (!focusedCellInfo) return; + const nextRow: any = { ...focusedCellInfo.record, [focusedCellInfo.dataIndex]: dataPanelValue }; + handleCellSave(nextRow); + dataPanelDirtyRef.current = false; + void message.success('已保存'); + }, [focusedCellInfo, dataPanelValue, handleCellSave]); + const handleCellSetNull = useCallback(() => { if (!cellContextMenu.record) return; handleCellSave({ ...cellContextMenu.record, [cellContextMenu.dataIndex]: null }); @@ -3228,6 +3272,12 @@ const DataGrid: React.FC = ({ 'data-row-key': rowKey === undefined || rowKey === null ? undefined : String(rowKey), 'data-col-name': dataIndex, }; + // 数据预览面板:单击单元格时更新聚焦信息 + cellProps.onClick = () => { + if (dataPanelOpenRef.current) { + updateFocusedCell(record, dataIndex); + } + }; if (col.editable && enableInlineEditableCell) { // 可编辑模式(非虚拟):传递给 EditableCell 的 props @@ -4613,6 +4663,24 @@ const DataGrid: React.FC = ({ )}
+
+ +
= ({
)} + {/* Data Preview Panel */} + {dataPanelOpen && viewMode === 'table' && ( +
+
+ + {focusedCellInfo ? focusedCellInfo.dataIndex : '点击单元格查看数据'} + + {focusedCellInfo && (() => { + const meta = columnMetaMap[focusedCellInfo.dataIndex] || columnMetaMapByLowerName[focusedCellInfo.dataIndex.toLowerCase()]; + return meta?.type ? ({meta.type}) : null; + })()} +
+ {dataPanelIsJson && ( + + )} + {canModifyData && focusedCellInfo && ( + + )} +
+
+ {focusedCellInfo ? ( + { + setDataPanelValue(val || ''); + dataPanelDirtyRef.current = true; + }} + options={{ + minimap: { enabled: false }, + scrollBeyondLastLine: false, + wordWrap: 'on', + fontSize: 13, + tabSize: 2, + automaticLayout: true, + readOnly: !canModifyData, + lineNumbers: 'off', + glyphMargin: false, + folding: false, + lineDecorationsWidth: 4, + padding: { top: 6, bottom: 6 }, + }} + /> + ) : ( +
+ 点击表格中的单元格以预览完整数据 +
+ )} +
+
+ )} + {/* Cell Context Menu - 使用 Portal 渲染到 body,避免 backdropFilter 影响 fixed 定位 */} {viewMode === 'table' && cellContextMenu.visible && createPortal(