diff --git a/frontend/src/components/DataGrid.ddl.test.tsx b/frontend/src/components/DataGrid.ddl.test.tsx index 1b64b7b6..a7595d51 100644 --- a/frontend/src/components/DataGrid.ddl.test.tsx +++ b/frontend/src/components/DataGrid.ddl.test.tsx @@ -1924,6 +1924,46 @@ describe('DataGrid DDL interactions', () => { expect(content).toContain('name'); }); + it('notifies deferred data loading only after leaving the embedded object designer', async () => { + storeState.appearance.uiVersion = 'v2'; + backendApp.DBGetColumns.mockResolvedValueOnce({ + success: true, + data: [ + { name: 'id', type: 'bigint', key: 'PRI', nullable: 'NO', default: '', comment: '' }, + { name: 'name', type: 'varchar(255)', key: '', nullable: 'YES', default: '', comment: '' }, + ], + }); + const handleDataViewActivate = vi.fn(); + + let renderer: ReactTestRenderer; + await act(async () => { + renderer = create( + , + ); + }); + await waitForEffects(); + + expect(handleDataViewActivate).not.toHaveBeenCalled(); + + await act(async () => { + findButton(renderer!, '数据预览').props.onClick(); + }); + await waitForEffects(); + + expect(handleDataViewActivate).toHaveBeenCalledTimes(1); + renderer!.unmount(); + }); + it('returns to the legacy table view when v2-only footer views are active during UI switch', async () => { storeState.appearance.uiVersion = 'v2'; diff --git a/frontend/src/components/DataGrid.tsx b/frontend/src/components/DataGrid.tsx index 7ec559f3..f8da98af 100644 --- a/frontend/src/components/DataGrid.tsx +++ b/frontend/src/components/DataGrid.tsx @@ -288,7 +288,8 @@ const DataGrid: React.FC = ({ onApplyQuickWhereCondition, scrollSnapshot, onScrollSnapshotChange, toolbarExtraActions, showRowNumberColumn = false, isActive = true, enableSqlLogEvent = false, initialViewMode, - initialViewModeRequestId + initialViewModeRequestId, + onDataViewActivate, }) => { const connections = useStore(state => state.connections); const addTab = useStore(state => state.addTab); @@ -1450,6 +1451,23 @@ const DataGrid: React.FC = ({ return () => window.removeEventListener('gonavi:show-sql-execution-log', handleOpenSqlExecutionLog as EventListener); }, [enableSqlLogEvent, handleViewModeChange, isActive, isV2Ui]); + const dataViewActiveRef = useRef(false); + useEffect(() => { + const requiresTableData = isActive && ( + viewMode === 'table' + || viewMode === 'json' + || viewMode === 'text' + || (viewMode === 'ddl' && isTableSurfaceActive) + ); + if (!requiresTableData) { + dataViewActiveRef.current = false; + return; + } + if (dataViewActiveRef.current) return; + dataViewActiveRef.current = true; + onDataViewActivate?.(); + }, [isActive, isTableSurfaceActive, onDataViewActivate, viewMode]); + useEffect(() => { if (!isTableSurfaceActive || !isV2Ui || !cellContextMenu.visible) return; const portal = cellContextMenuPortalRef.current; diff --git a/frontend/src/components/DataGridCore.tsx b/frontend/src/components/DataGridCore.tsx index c8741e59..28fb28d7 100644 --- a/frontend/src/components/DataGridCore.tsx +++ b/frontend/src/components/DataGridCore.tsx @@ -1338,6 +1338,7 @@ interface DataGridProps { enableSqlLogEvent?: boolean; initialViewMode?: GridViewMode; initialViewModeRequestId?: string; + onDataViewActivate?: () => void; } type GridFilterCondition = FilterCondition & { diff --git a/frontend/src/components/DataViewer.primary-key.test.tsx b/frontend/src/components/DataViewer.primary-key.test.tsx index 4d3ff741..d8ff9583 100644 --- a/frontend/src/components/DataViewer.primary-key.test.tsx +++ b/frontend/src/components/DataViewer.primary-key.test.tsx @@ -147,6 +147,29 @@ describe('DataViewer safe editing locator', () => { renderer!.unmount(); }); + it('defers the initial table query when the table opens in embedded object design', async () => { + backendApp.DBGetColumns.mockResolvedValue({ + success: true, + data: [{ name: 'ID', key: 'PRI' }, { name: 'NAME', key: '' }], + }); + + let renderer: ReactTestRenderer; + await act(async () => { + renderer = create(); + }); + await flushPromises(); + + expect(backendApp.DBQuery).not.toHaveBeenCalled(); + + await act(async () => { + dataGridState.latestProps?.onDataViewActivate?.(); + }); + await flushPromises(); + + expect(backendApp.DBQuery).toHaveBeenCalled(); + renderer!.unmount(); + }); + it('keeps DataViewer message wrappers and SQL log phase labels keyed', () => { const source = readFileSync(new URL('./DataViewer.tsx', import.meta.url), 'utf8'); diff --git a/frontend/src/components/DataViewer.tsx b/frontend/src/components/DataViewer.tsx index f209c7e1..0f7d2d24 100644 --- a/frontend/src/components/DataViewer.tsx +++ b/frontend/src/components/DataViewer.tsx @@ -291,6 +291,9 @@ type ViewerScrollSnapshot = { const viewerFilterSnapshotsByTab = new Map(); const MAX_VIEWER_FILTER_SNAPSHOTS = 64; const VIEWER_SCROLL_SNAPSHOT_PERSIST_DELAY_MS = 160; +const shouldDeferInitialDataViewerFetch = (initialViewMode?: TabData['initialViewMode']): boolean => ( + initialViewMode === 'fields' +); const trimViewerFilterSnapshots = () => { while (viewerFilterSnapshotsByTab.size > MAX_VIEWER_FILTER_SNAPSHOTS) { @@ -387,6 +390,7 @@ const DataViewer: React.FC<{ tab: TabData; isActive?: boolean }> = React.memo(({ const scrollSnapshotPersistTimerRef = useRef(null); const initialLoadRef = useRef(false); const skipNextAutoFetchRef = useRef(false); + const deferredInitialFetchRef = useRef(shouldDeferInitialDataViewerFetch(tab.initialViewMode)); const [pagination, setPagination] = useState({ current: initialViewerSnapshot.currentPage, @@ -474,6 +478,7 @@ const DataViewer: React.FC<{ tab: TabData; isActive?: boolean }> = React.memo(({ latestCountKeyRef.current = ''; scrollSnapshotRef.current = { top: snapshot.scrollTop, left: snapshot.scrollLeft }; initialLoadRef.current = false; + deferredInitialFetchRef.current = shouldDeferInitialDataViewerFetch(tab.initialViewMode); skipNextAutoFetchRef.current = true; setPagination(prev => ({ ...prev, @@ -1129,6 +1134,12 @@ const DataViewer: React.FC<{ tab: TabData; isActive?: boolean }> = React.memo(({ // 定位信息只会在表上下文变化后重新加载,避免循环查询。 // Handlers memoized + const handleDataViewActivate = useCallback(() => { + if (!deferredInitialFetchRef.current || initialLoadRef.current) return; + deferredInitialFetchRef.current = false; + initialLoadRef.current = true; + void fetchData(pagination.current, pagination.pageSize); + }, [fetchData, pagination.current, pagination.pageSize]); const handleReload = useCallback(() => { fetchData(pagination.current, pagination.pageSize); }, [fetchData, pagination.current, pagination.pageSize]); @@ -1200,6 +1211,9 @@ const DataViewer: React.FC<{ tab: TabData; isActive?: boolean }> = React.memo(({ }, [tab.tableName, currentConnConfig?.type, currentConnConfig?.driver, filterConditions, quickWhereCondition, sortInfo, editLocator, pkColumns]); useEffect(() => { + if (deferredInitialFetchRef.current && !initialLoadRef.current) { + return; + } const action = resolveDataViewerAutoFetchAction({ skipNextAutoFetch: skipNextAutoFetchRef.current, hasInitialLoad: initialLoadRef.current, @@ -1251,6 +1265,7 @@ const DataViewer: React.FC<{ tab: TabData; isActive?: boolean }> = React.memo(({ enableSqlLogEvent initialViewMode={tab.initialViewMode} initialViewModeRequestId={tab.initialViewModeRequestId} + onDataViewActivate={handleDataViewActivate} /> );