🐛 fix(data-viewer): 延迟对象设计页的数据预览加载

This commit is contained in:
Syngnat
2026-07-02 09:39:52 +08:00
parent faaf7169da
commit 0428a30071
5 changed files with 98 additions and 1 deletions

View File

@@ -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(
<DataGrid
data={[{ __gonavi_row_key__: 'row-1', id: 1, name: 'alpha' }]}
columnNames={['id', 'name']}
loading={false}
tableName="users"
dbName="main"
connectionId="conn-1"
initialViewMode="fields"
initialViewModeRequestId="query-editor-jump-2"
onDataViewActivate={handleDataViewActivate}
/>,
);
});
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';

View File

@@ -288,7 +288,8 @@ const DataGrid: React.FC<DataGridProps> = ({
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<DataGridProps> = ({
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;

View File

@@ -1338,6 +1338,7 @@ interface DataGridProps {
enableSqlLogEvent?: boolean;
initialViewMode?: GridViewMode;
initialViewModeRequestId?: string;
onDataViewActivate?: () => void;
}
type GridFilterCondition = FilterCondition & {

View File

@@ -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(<DataViewer tab={createTab({ initialViewMode: 'fields', initialViewModeRequestId: 'open-design-1' })} />);
});
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');

View File

@@ -291,6 +291,9 @@ type ViewerScrollSnapshot = {
const viewerFilterSnapshotsByTab = new Map<string, ViewerFilterSnapshot>();
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<number | null>(null);
const initialLoadRef = useRef(false);
const skipNextAutoFetchRef = useRef(false);
const deferredInitialFetchRef = useRef(shouldDeferInitialDataViewerFetch(tab.initialViewMode));
const [pagination, setPagination] = useState<ViewerPaginationState>({
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}
/>
</div>
);