💄 style(query-editor): 调整 v2 查询工具栏布局样式

- 为查询工具栏控件增加 v2 专用 class,移除 v2 下 inline 固定宽度依赖

- 使用内容宽度约束选择区,避免最大行数后出现多余空白

- 覆盖 Ant Design Button.Group 负 margin 和伪元素合并效果

- 增加 CSS 静态断言覆盖对齐、间距和响应式布局
This commit is contained in:
Syngnat
2026-06-02 11:54:06 +08:00
parent e421662576
commit 7612657ded
3 changed files with 237 additions and 80 deletions

View File

@@ -2359,6 +2359,41 @@ describe('QueryEditor external SQL save', () => {
expect(css).toContain('body[data-ui-version="v2"] .gn-v2-query-results .query-result-tab-text {');
});
it('keeps the v2 query editor toolbar grouped and compact', () => {
const source = readFileSync(new URL('./QueryEditor.tsx', import.meta.url), 'utf8');
const css = readFileSync(new URL('../v2-theme.css', import.meta.url), 'utf8');
expect(source).toContain('gn-v2-query-toolbar-selects');
expect(source).toContain('gn-v2-query-toolbar-actions');
expect(source).toContain('gn-v2-query-toolbar-connection-select');
expect(source).toContain('gn-v2-query-toolbar-database-select');
expect(source).toContain('gn-v2-query-toolbar-max-rows-select');
expect(source).toContain('gn-v2-query-toolbar-action-group');
expect(source).toContain('style={isV2Ui ? undefined : { width: 150 }}');
expect(source).toContain('style={isV2Ui ? undefined : { width: 200 }}');
expect(source).toContain('style={isV2Ui ? undefined : { width: 170 }}');
expect(css).toContain('body[data-ui-version="v2"] .gn-v2-query-toolbar-selects');
expect(css).toContain('body[data-ui-version="v2"] .gn-v2-query-toolbar-actions');
expect(css).toContain('flex: 0 1 auto !important;');
expect(css).toContain('justify-content: flex-start;');
expect(css).toContain('height: 32px !important;');
expect(css).toContain('line-height: 30px !important;');
expect(css).toContain('display: inline-flex !important;');
expect(css).toContain('gap: 6px;');
expect(css).toContain('margin-left: 0 !important;');
expect(css).toContain('max-width: 520px;');
expect(css).toContain('width: 140px !important;');
expect(css).toContain('width: 166px !important;');
expect(css).toContain('width: 132px !important;');
expect(css).toContain('width: 34px !important;');
expect(css).toContain('@media (max-width: 900px)');
const queryToolbarCss = css.slice(css.indexOf('body[data-ui-version="v2"] .gn-v2-query-toolbar {'), css.indexOf('body[data-ui-version="v2"] .gn-v2-query-monaco-shell {'));
expect(queryToolbarCss).not.toContain('margin-left: auto;');
expect(queryToolbarCss).not.toContain('justify-content: flex-end;');
});
it('coalesces editor result splitter dragging through requestAnimationFrame', async () => {
const moveListeners: Array<(event: MouseEvent) => void> = [];
const upListeners: Array<() => void> = [];

View File

@@ -4875,92 +4875,105 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
`}</style>
<div ref={editorPaneRef} className={isV2Ui ? 'gn-v2-query-editor-pane' : undefined}>
<div className={isV2Ui ? 'gn-v2-query-toolbar' : undefined} style={{ padding: '4px 8px 8px', display: 'flex', gap: '8px', flexShrink: 0, alignItems: 'center' }}>
<Select
style={{ width: 150 }}
placeholder="选择连接"
value={currentConnectionId}
onChange={(val) => {
setCurrentConnectionId(val);
setCurrentDb('');
}}
options={queryCapableConnections.map(c => ({ label: c.name, value: c.id }))}
showSearch
/>
<Select
style={{ width: 200 }}
placeholder="选择数据库"
value={currentDb}
onChange={setCurrentDb}
options={dbList.map(db => ({ label: db, value: db }))}
showSearch
/>
<Tooltip title="最大返回行数(会对 SELECT 自动加 LIMIT防止大结果集卡死">
<Select
style={{ width: 170 }}
value={queryOptions?.maxRows ?? 5000}
onChange={(val) => setQueryOptions({ maxRows: Number(val) })}
options={[
{ label: '最大行数500', value: 500 },
{ label: '最大行数1000', value: 1000 },
{ label: '最大行数5000', value: 5000 },
{ label: '最大行数20000', value: 20000 },
{ label: '最大行数:不限', value: 0 },
]}
/>
</Tooltip>
<Button.Group>
<Tooltip
title={
runQueryShortcutBinding.enabled && runQueryShortcutBinding.combo
? `运行(${getShortcutDisplayLabel(runQueryShortcutBinding.combo, activeShortcutPlatform)}`
: '运行'
}
>
<Button type="primary" icon={<PlayCircleOutlined />} onMouseDown={captureEditorCursorPosition} onClick={handleRun} loading={loading}>
</Button>
<div
className={isV2Ui ? 'gn-v2-query-toolbar-selects' : undefined}
style={{ display: 'flex', gap: '8px', flexShrink: 0, alignItems: 'center' }}
>
<Select
className={isV2Ui ? 'gn-v2-query-toolbar-select gn-v2-query-toolbar-connection-select' : undefined}
style={isV2Ui ? undefined : { width: 150 }}
placeholder="选择连接"
value={currentConnectionId}
onChange={(val) => {
setCurrentConnectionId(val);
setCurrentDb('');
}}
options={queryCapableConnections.map(c => ({ label: c.name, value: c.id }))}
showSearch
/>
<Select
className={isV2Ui ? 'gn-v2-query-toolbar-select gn-v2-query-toolbar-database-select' : undefined}
style={isV2Ui ? undefined : { width: 200 }}
placeholder="选择数据库"
value={currentDb}
onChange={setCurrentDb}
options={dbList.map(db => ({ label: db, value: db }))}
showSearch
/>
<Tooltip title="最大返回行数(会对 SELECT 自动加 LIMIT防止大结果集卡死">
<Select
className={isV2Ui ? 'gn-v2-query-toolbar-select gn-v2-query-toolbar-max-rows-select' : undefined}
style={isV2Ui ? undefined : { width: 170 }}
value={queryOptions?.maxRows ?? 5000}
onChange={(val) => setQueryOptions({ maxRows: Number(val) })}
options={[
{ label: '最大行数500', value: 500 },
{ label: '最大行数1000', value: 1000 },
{ label: '最大行数5000', value: 5000 },
{ label: '最大行数20000', value: 20000 },
{ label: '最大行数:不限', value: 0 },
]}
/>
</Tooltip>
{loading && (
<Button type="primary" danger icon={<StopOutlined />} onClick={handleCancel}>
</Button>
)}
</Button.Group>
<Button.Group>
</div>
<div
className={isV2Ui ? 'gn-v2-query-toolbar-actions' : undefined}
style={{ display: 'flex', gap: '8px', flexShrink: 0, alignItems: 'center' }}
>
<Button.Group className={isV2Ui ? 'gn-v2-query-toolbar-action-group' : undefined}>
<Tooltip
title={
saveQueryShortcutBinding.enabled && saveQueryShortcutBinding.combo
? `保存${getShortcutDisplayLabel(saveQueryShortcutBinding.combo, activeShortcutPlatform)}`
: '保存'
runQueryShortcutBinding.enabled && runQueryShortcutBinding.combo
? `运行${getShortcutDisplayLabel(runQueryShortcutBinding.combo, activeShortcutPlatform)}`
: '运行'
}
>
<Button icon={<SaveOutlined />} onClick={handleQuickSave}>
<Button className={isV2Ui ? 'gn-v2-query-toolbar-run-action' : undefined} type="primary" icon={<PlayCircleOutlined />} onMouseDown={captureEditorCursorPosition} onClick={handleRun} loading={loading}>
</Button>
</Tooltip>
{loading && (
<Button type="primary" danger icon={<StopOutlined />} onClick={handleCancel}>
</Button>
</Tooltip>
<Dropdown menu={{ items: saveMoreMenuItems }} placement="bottomRight">
<Button></Button>
</Dropdown>
</Button.Group>
<Button.Group>
<Tooltip title="美化 SQL">
<Button icon={<FormatPainterOutlined />} onClick={handleFormat}></Button>
</Tooltip>
<Dropdown menu={{ items: formatSettingsMenu }} placement="bottomRight">
<Button icon={<SettingOutlined />} />
</Dropdown>
</Button.Group>
)}
</Button.Group>
<Button.Group className={isV2Ui ? 'gn-v2-query-toolbar-action-group' : undefined}>
<Tooltip
title={
saveQueryShortcutBinding.enabled && saveQueryShortcutBinding.combo
? `保存(${getShortcutDisplayLabel(saveQueryShortcutBinding.combo, activeShortcutPlatform)}`
: '保存'
}
>
<Button icon={<SaveOutlined />} onClick={handleQuickSave}>
</Button>
</Tooltip>
<Dropdown menu={{ items: saveMoreMenuItems }} placement="bottomRight">
<Button></Button>
</Dropdown>
</Button.Group>
<Dropdown menu={{ items: [
{ key: 'ai-generate', label: '生成 SQL', icon: <RobotOutlined />, onClick: () => handleAIAction('generate') },
{ key: 'ai-explain', label: '解释 SQL', icon: <RobotOutlined />, onClick: () => handleAIAction('explain') },
{ key: 'ai-optimize', label: '优化 SQL', icon: <RobotOutlined />, onClick: () => handleAIAction('optimize') },
{ type: 'divider' as const },
{ key: 'ai-schema', label: 'Schema 分析', icon: <RobotOutlined />, onClick: () => handleAIAction('schema') },
] }} placement="bottomRight">
<Button icon={<RobotOutlined />} style={{ color: '#818cf8' }}>AI</Button>
</Dropdown>
<Button.Group className={isV2Ui ? 'gn-v2-query-toolbar-action-group' : undefined}>
<Tooltip title="美化 SQL">
<Button icon={<FormatPainterOutlined />} onClick={handleFormat}></Button>
</Tooltip>
<Dropdown menu={{ items: formatSettingsMenu }} placement="bottomRight">
<Button className={isV2Ui ? 'gn-v2-query-toolbar-icon-action' : undefined} icon={<SettingOutlined />} />
</Dropdown>
</Button.Group>
<Dropdown menu={{ items: [
{ key: 'ai-generate', label: '生成 SQL', icon: <RobotOutlined />, onClick: () => handleAIAction('generate') },
{ key: 'ai-explain', label: '解释 SQL', icon: <RobotOutlined />, onClick: () => handleAIAction('explain') },
{ key: 'ai-optimize', label: '优化 SQL', icon: <RobotOutlined />, onClick: () => handleAIAction('optimize') },
{ type: 'divider' as const },
{ key: 'ai-schema', label: 'Schema 分析', icon: <RobotOutlined />, onClick: () => handleAIAction('schema') },
] }} placement="bottomRight">
<Button className={isV2Ui ? 'gn-v2-query-toolbar-ai-action' : undefined} icon={<RobotOutlined />} style={{ color: '#818cf8' }}>AI</Button>
</Dropdown>
</div>
</div>
<div ref={editorShellRef} className={isV2Ui ? 'gn-v2-query-monaco-shell' : undefined} style={{ height: editorHeight, minHeight: '100px' }}>

View File

@@ -4662,8 +4662,117 @@ body[data-ui-version="v2"] .gn-v2-designer-toolbar {
border-radius: 0 !important;
}
body[data-ui-version="v2"] .gn-v2-query-toolbar {
min-height: 48px;
padding: 8px 12px !important;
gap: 6px 10px !important;
align-items: center !important;
background: var(--gn-bg-panel) !important;
border-bottom: 0.5px solid var(--gn-br-1) !important;
}
body[data-ui-version="v2"] .gn-v2-query-toolbar-selects,
body[data-ui-version="v2"] .gn-v2-query-toolbar-actions {
min-width: 0;
gap: 6px !important;
}
body[data-ui-version="v2"] .gn-v2-query-toolbar-selects {
flex: 0 1 auto !important;
flex-wrap: nowrap;
max-width: 520px;
}
body[data-ui-version="v2"] .gn-v2-query-toolbar-actions {
flex: 0 1 auto !important;
flex-wrap: wrap;
justify-content: flex-start;
}
body[data-ui-version="v2"] .gn-v2-query-toolbar .ant-select {
min-width: 132px;
min-width: 0;
}
body[data-ui-version="v2"] .gn-v2-query-toolbar-connection-select {
width: 140px !important;
flex: 0 1 140px !important;
}
body[data-ui-version="v2"] .gn-v2-query-toolbar-database-select {
width: 166px !important;
flex: 1 1 166px !important;
max-width: 220px;
}
body[data-ui-version="v2"] .gn-v2-query-toolbar-max-rows-select {
width: 132px !important;
flex: 0 0 132px !important;
}
body[data-ui-version="v2"] .gn-v2-query-toolbar .ant-select-selector {
height: 32px !important;
padding: 0 10px !important;
border-radius: 9px !important;
}
body[data-ui-version="v2"] .gn-v2-query-toolbar .ant-select-selection-item,
body[data-ui-version="v2"] .gn-v2-query-toolbar .ant-select-selection-placeholder {
line-height: 30px !important;
}
body[data-ui-version="v2"] .gn-v2-query-toolbar .ant-btn {
height: 32px !important;
padding: 0 11px !important;
border-radius: 9px !important;
font-size: 12.5px !important;
}
body[data-ui-version="v2"] .gn-v2-query-toolbar-action-group.ant-btn-group {
display: inline-flex !important;
align-items: center;
flex: 0 0 auto;
gap: 6px;
}
body[data-ui-version="v2"] .gn-v2-query-toolbar-action-group.ant-btn-group > .ant-btn {
flex: 0 0 auto;
border-radius: 9px !important;
}
body[data-ui-version="v2"] .gn-v2-query-toolbar-action-group.ant-btn-group > .ant-btn:not(:first-child) {
margin-left: 0 !important;
}
body[data-ui-version="v2"] .gn-v2-query-toolbar-action-group.ant-btn-group > .ant-btn::before {
display: none !important;
}
body[data-ui-version="v2"] .gn-v2-query-toolbar-icon-action.ant-btn,
body[data-ui-version="v2"] .gn-v2-query-toolbar .ant-btn-icon-only {
width: 34px !important;
padding: 0 !important;
}
body[data-ui-version="v2"] .gn-v2-query-toolbar-run-action.ant-btn {
min-width: 64px;
padding: 0 13px !important;
font-weight: 650 !important;
}
body[data-ui-version="v2"] .gn-v2-query-toolbar-ai-action.ant-btn {
font-weight: 650 !important;
}
@media (max-width: 900px) {
body[data-ui-version="v2"] .gn-v2-query-toolbar-selects {
flex: 1 1 100% !important;
max-width: none;
}
body[data-ui-version="v2"] .gn-v2-query-toolbar-actions {
width: 100%;
justify-content: flex-start;
}
}
body[data-ui-version="v2"] .gn-v2-query-monaco-shell {