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(