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}
/>
);