🐛 fix(definition-viewer): 修复对象定义页行号重影

This commit is contained in:
Syngnat
2026-06-30 20:31:44 +08:00
parent 4b18075314
commit b0d8ccbf23
4 changed files with 105 additions and 6 deletions

View File

@@ -1,4 +1,4 @@
import React, { useState, useEffect, useRef } from 'react';
import React, { useState, useEffect, useRef, useCallback } from 'react';
import Editor from './MonacoEditor';
import { Button, Spin, Alert } from 'antd';
import { EditOutlined } from '@ant-design/icons';
@@ -125,6 +125,7 @@ const DefinitionViewer: React.FC<DefinitionViewerProps> = ({ tab }) => {
const [openingObjectEdit, setOpeningObjectEdit] = useState(false);
const isMountedRef = useRef(true);
const loadedDefinitionKeyRef = useRef('');
const editorRef = useRef<any>(null);
const connections = useStore(state => state.connections);
const theme = useStore(state => state.theme);
@@ -896,6 +897,44 @@ const DefinitionViewer: React.FC<DefinitionViewerProps> = ({ tab }) => {
name: normalizedObjectName,
}),
};
const editorModelPath = `gonavi-definition://${encodeURIComponent(objectIdentityKey)}`;
const refreshEditorLayout = useCallback(() => {
const editor = editorRef.current;
if (!editor) {
return;
}
const run = () => {
editor.layout?.();
editor.render?.();
editor.setScrollTop?.(0);
editor.setScrollLeft?.(0);
};
if (typeof requestAnimationFrame === 'function') {
requestAnimationFrame(() => run());
return;
}
setTimeout(run, 0);
}, []);
const handleEditorMount = useCallback((editor: any) => {
editorRef.current = editor;
refreshEditorLayout();
}, [refreshEditorLayout]);
useEffect(() => {
if (!displayedDefinition) {
return;
}
refreshEditorLayout();
}, [displayedDefinition, refreshEditorLayout]);
useEffect(() => () => {
editorRef.current = null;
}, []);
const openObjectEditQuery = async () => {
if (!normalizedObjectName || openingObjectEdit) return;
@@ -967,15 +1006,19 @@ const DefinitionViewer: React.FC<DefinitionViewerProps> = ({ tab }) => {
)}
<div style={{ flex: 1, minHeight: 0 }}>
<Editor
path={editorModelPath}
height="100%"
language="sql"
theme={darkMode ? 'transparent-dark' : 'transparent-light'}
value={displayedDefinition}
onMount={handleEditorMount}
options={{
readOnly: true,
minimap: { enabled: false },
fontSize: 14,
lineHeight: 24,
lineNumbers: 'on',
lineNumbersMinChars: 4,
scrollBeyondLastLine: false,
wordWrap: 'on',
automaticLayout: true,

View File

@@ -129,13 +129,17 @@ const MonacoEditor: React.FC<MonacoEditorProps> = ({
const resolvedFontSize = gonaviTypography === 'data'
? effectiveDataTableFontSize
: Math.max(10, Math.round(effectiveDataTableFontSize * 0.92));
const effectiveEditorFontSize = Math.max(
10,
Math.round(Number(options?.fontSize) || resolvedFontSize),
);
return {
...options,
editContext: false,
fontFamily: options?.fontFamily ?? monoFontFamily ?? DEFAULT_MONO_FONT_FAMILY,
fontSize: options?.fontSize ?? resolvedFontSize,
lineHeight: options?.lineHeight ?? Math.max(18, Math.round(resolvedFontSize * 1.62)),
lineHeight: options?.lineHeight ?? Math.max(18, Math.round(effectiveEditorFontSize * 1.62)),
};
}, [
dataTableFontSize,

View File

@@ -64,6 +64,15 @@ describe('MonacoEditor typography', () => {
expect(markup).toContain('&quot;lineHeight&quot;:21');
});
it('derives line height from the final explicit editor font size', () => {
const markup = renderToStaticMarkup(
<MonacoEditor options={{ fontSize: 18, minimap: { enabled: false } }} />,
);
expect(markup).toContain('&quot;fontSize&quot;:18');
expect(markup).toContain('&quot;lineHeight&quot;:29');
});
it('uses data-table font size for data-oriented editors in v2', () => {
storeState.fontSize = 16;
storeState.appearance.dataTableFontSizeFollowGlobal = false;

View File

@@ -1,4 +1,4 @@
import React, { useState, useEffect, useRef } from 'react';
import React, { useState, useEffect, useRef, useCallback } from 'react';
import Editor from './MonacoEditor';
import { Button, Spin, Alert } from 'antd';
import { EditOutlined } from '@ant-design/icons';
@@ -110,6 +110,7 @@ const TriggerViewer: React.FC<TriggerViewerProps> = ({ tab }) => {
const [openingObjectEdit, setOpeningObjectEdit] = useState(false);
const isMountedRef = useRef(true);
const loadedDefinitionKeyRef = useRef('');
const editorRef = useRef<any>(null);
const connections = useStore(state => state.connections);
const theme = useStore(state => state.theme);
@@ -465,6 +466,47 @@ LIMIT 1`];
isMountedRef.current = false;
}, []);
const displayedDefinition = loadedDefinitionKeyRef.current === objectIdentityKey ? triggerDefinition : '';
const hasDefinition = String(displayedDefinition || '').trim() !== '';
const editorModelPath = `gonavi-trigger://${encodeURIComponent(objectIdentityKey)}`;
const refreshEditorLayout = useCallback(() => {
const editor = editorRef.current;
if (!editor) {
return;
}
const run = () => {
editor.layout?.();
editor.render?.();
editor.setScrollTop?.(0);
editor.setScrollLeft?.(0);
};
if (typeof requestAnimationFrame === 'function') {
requestAnimationFrame(() => run());
return;
}
setTimeout(run, 0);
}, []);
const handleEditorMount = useCallback((editor: any) => {
editorRef.current = editor;
refreshEditorLayout();
}, [refreshEditorLayout]);
useEffect(() => {
if (!displayedDefinition) {
return;
}
refreshEditorLayout();
}, [displayedDefinition, refreshEditorLayout]);
useEffect(() => () => {
editorRef.current = null;
}, []);
if (loading) {
return (
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100%' }}>
@@ -473,9 +515,6 @@ LIMIT 1`];
);
}
const displayedDefinition = loadedDefinitionKeyRef.current === objectIdentityKey ? triggerDefinition : '';
const hasDefinition = String(displayedDefinition || '').trim() !== '';
if (error && !hasDefinition) {
return (
<div style={{ padding: 16 }}>
@@ -537,15 +576,19 @@ LIMIT 1`];
)}
<div style={{ flex: 1, minHeight: 0 }}>
<Editor
path={editorModelPath}
height="100%"
language="sql"
theme={darkMode ? 'transparent-dark' : 'transparent-light'}
value={displayedDefinition}
onMount={handleEditorMount}
options={{
readOnly: true,
minimap: { enabled: false },
fontSize: 14,
lineHeight: 24,
lineNumbers: 'on',
lineNumbersMinChars: 4,
scrollBeyondLastLine: false,
wordWrap: 'on',
automaticLayout: true,