🐛 fix(query-editor): 优化事务下拉显示与浮层交互

- 自动提交延迟选项改为多语言完整文案,避免短标签语义不清
- 收窄并校准 V2 工具栏事务下拉宽度,兼顾不截断与紧凑布局
- 事务模式 Select 展开时自动隐藏 DBeaver 参考 Tooltip,避免浮层互相遮挡
- 补充事务设置行为测试和布局守护测试
This commit is contained in:
Syngnat
2026-06-24 17:50:57 +08:00
parent 9da9a36cf3
commit 8a552c4cb3
13 changed files with 144 additions and 23 deletions

View File

@@ -7227,8 +7227,9 @@ describe('QueryEditor external SQL save', () => {
expect(transactionSettingsSource).toContain('query_editor.transaction.mode.auto');
expect(transactionSettingsSource).not.toContain("label: '手动提交'");
expect(transactionSettingsSource).not.toContain("label: '自动提交'");
expect(transactionSettingsSource).toContain('query_editor.transaction.delay.immediate');
expect(transactionSettingsSource).toContain("label: '3s'");
expect(transactionSettingsSource).toContain('query_editor.transaction.delay.immediate_commit');
expect(transactionSettingsSource).toContain('query_editor.transaction.delay.seconds_commit');
expect(transactionSettingsSource).not.toContain("label: '3s'");
expect(source).toContain('QueryEditorTransactionToolbar');
expect(transactionToolbarSource).toContain("className={isV2Ui ? 'gn-v2-query-transaction-toolbar' : undefined}");
expect(transactionToolbarSource).toContain(": null;");
@@ -7255,8 +7256,8 @@ describe('QueryEditor external SQL save', () => {
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('width: 74px !important;');
expect(css).toContain('width: 62px !important;');
expect(css).toContain('width: 78px !important;');
expect(css).toContain('width: 104px !important;');
expect(css).toContain('flex: 0 0 auto !important;');
expect(css).toContain('justify-content: flex-start;');
expect(css).toContain('height: 32px !important;');

View File

@@ -2553,8 +2553,9 @@ describe('QueryEditor external SQL save', () => {
expect(transactionSettingsSource).toContain('query_editor.transaction.mode.auto');
expect(transactionSettingsSource).not.toContain("label: '手动提交'");
expect(transactionSettingsSource).not.toContain("label: '自动提交'");
expect(transactionSettingsSource).toContain('query_editor.transaction.delay.immediate');
expect(transactionSettingsSource).toContain("label: '3s'");
expect(transactionSettingsSource).toContain('query_editor.transaction.delay.immediate_commit');
expect(transactionSettingsSource).toContain('query_editor.transaction.delay.seconds_commit');
expect(transactionSettingsSource).not.toContain("label: '3s'");
expect(source).toContain('QueryEditorTransactionToolbar');
expect(transactionToolbarSource).toContain("className={isV2Ui ? 'gn-v2-query-transaction-toolbar' : undefined}");
expect(transactionToolbarSource).toContain(": null;");
@@ -2581,8 +2582,8 @@ describe('QueryEditor external SQL save', () => {
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('width: 74px !important;');
expect(css).toContain('width: 62px !important;');
expect(css).toContain('width: 78px !important;');
expect(css).toContain('width: 104px !important;');
expect(css).toContain('flex: 0 0 auto !important;');
expect(css).toContain('justify-content: flex-start;');
expect(css).toContain('height: 32px !important;');

View File

@@ -62,4 +62,21 @@ describe('QueryEditorToolbar layout', () => {
expect(commitHoverCss).toContain('box-shadow:');
expect(commitKbdHoverCss).toContain('background:');
});
it('keeps transaction selects wide enough for localized auto-commit labels', () => {
const css = readV2ThemeCss();
const transactionModeCss = css.slice(
css.indexOf('body[data-ui-version="v2"] .gn-v2-query-toolbar-transaction-mode-select {'),
css.indexOf('body[data-ui-version="v2"] .gn-v2-query-toolbar-transaction-delay-select {'),
);
const transactionDelayCss = css.slice(
css.indexOf('body[data-ui-version="v2"] .gn-v2-query-toolbar-transaction-delay-select {'),
css.indexOf('body[data-ui-version="v2"] .gn-v2-query-toolbar .ant-select-selector {'),
);
expect(transactionModeCss).toContain('width: 78px !important;');
expect(transactionModeCss).toContain('flex: 0 0 78px !important;');
expect(transactionDelayCss).toContain('width: 104px !important;');
expect(transactionDelayCss).toContain('flex: 0 0 104px !important;');
});
});

View File

@@ -20,6 +20,8 @@ const toolbarLegacyLiterals = [
const requiredKeys = [
'query_editor.transaction.delay.immediate',
'query_editor.transaction.delay.immediate_commit',
'query_editor.transaction.delay.seconds_commit',
'query_editor.transaction.mode.tooltip',
'query_editor.transaction.mode.manual',
'query_editor.transaction.mode.auto',

View File

@@ -0,0 +1,74 @@
import React from 'react';
import { act, create, type ReactTestRenderer } from 'react-test-renderer';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import QueryEditorTransactionSettings from './QueryEditorTransactionSettings';
const antdState = vi.hoisted(() => ({
selectProps: [] as any[],
tooltipProps: [] as any[],
}));
vi.mock('antd', () => ({
Select: (props: any) => {
antdState.selectProps.push(props);
return <button type="button">{props.value}</button>;
},
Tooltip: (props: any) => {
antdState.tooltipProps.push(props);
return <div>{props.children}</div>;
},
}));
vi.mock('../i18n/provider', () => ({
useOptionalI18n: () => ({
t: (key: string, params?: Record<string, unknown>) => params?.seconds ? `${params.seconds}s后提交` : key,
}),
}));
const latestSelectProps = () => antdState.selectProps[antdState.selectProps.length - 1];
const latestTooltipProps = () => antdState.tooltipProps[antdState.tooltipProps.length - 1];
describe('QueryEditorTransactionSettings', () => {
let renderer: ReactTestRenderer | null = null;
beforeEach(() => {
antdState.selectProps = [];
antdState.tooltipProps = [];
});
afterEach(() => {
renderer?.unmount();
renderer = null;
});
it('hides the DBeaver reference tooltip while the transaction mode select is open', () => {
act(() => {
renderer = create(
<QueryEditorTransactionSettings
isV2Ui
commitMode="manual"
autoCommitDelayMs={0}
onCommitModeChange={vi.fn()}
onAutoCommitDelayMsChange={vi.fn()}
/>,
);
});
act(() => {
latestTooltipProps().onOpenChange(true);
});
expect(latestTooltipProps().open).toBe(true);
act(() => {
latestSelectProps().onOpenChange(true);
});
expect(latestTooltipProps().open).toBe(false);
act(() => {
latestSelectProps().onOpenChange(false);
latestTooltipProps().onOpenChange(true);
});
expect(latestTooltipProps().open).toBe(true);
});
});

View File

@@ -8,18 +8,14 @@ export type SqlEditorCommitMode = 'manual' | 'auto';
type SqlEditorAutoCommitDelayOption = {
value: number;
label: string;
} | {
value: number;
labelKey: string;
};
export const SQL_EDITOR_AUTO_COMMIT_DELAY_OPTIONS: SqlEditorAutoCommitDelayOption[] = [
{ value: 0, labelKey: 'query_editor.transaction.delay.immediate' },
{ value: 3000, label: '3s' },
{ value: 5000, label: '5s' },
{ value: 10000, label: '10s' },
{ value: 30000, label: '30s' },
{ value: 0 },
{ value: 3000 },
{ value: 5000 },
{ value: 10000 },
{ value: 30000 },
];
type QueryEditorTransactionSettingsProps = {
@@ -39,18 +35,36 @@ const QueryEditorTransactionSettings: React.FC<QueryEditorTransactionSettingsPro
}) => {
const i18n = useOptionalI18n();
const t = i18n?.t ?? defaultTranslate;
const [isModeSelectOpen, setIsModeSelectOpen] = React.useState(false);
const [isModeTooltipOpen, setIsModeTooltipOpen] = React.useState(false);
const autoCommitDelayOptions = SQL_EDITOR_AUTO_COMMIT_DELAY_OPTIONS.map((option) => ({
value: option.value,
label: 'labelKey' in option ? t(option.labelKey) : option.label,
label: option.value === 0
? t('query_editor.transaction.delay.immediate_commit')
: t('query_editor.transaction.delay.seconds_commit', { seconds: Math.round(option.value / 1000) }),
}));
const handleModeSelectOpenChange = (open: boolean) => {
setIsModeSelectOpen(open);
if (open) {
setIsModeTooltipOpen(false);
}
};
const handleModeTooltipOpenChange = (open: boolean) => {
setIsModeTooltipOpen(open);
};
return (
<>
<Tooltip title={t('query_editor.transaction.mode.tooltip')}>
<Tooltip
title={t('query_editor.transaction.mode.tooltip')}
open={isModeTooltipOpen && !isModeSelectOpen}
onOpenChange={handleModeTooltipOpenChange}
>
<Select
className={isV2Ui ? 'gn-v2-query-toolbar-select gn-v2-query-toolbar-transaction-mode-select' : undefined}
style={isV2Ui ? undefined : { width: 78 }}
value={commitMode}
onOpenChange={handleModeSelectOpenChange}
onChange={(mode) => onCommitModeChange(mode === 'auto' ? 'auto' : 'manual')}
options={[
{ label: t('query_editor.transaction.mode.manual'), value: 'manual' },

View File

@@ -154,13 +154,13 @@ body[data-ui-version="v2"] .gn-v2-query-toolbar-max-rows-select {
}
body[data-ui-version="v2"] .gn-v2-query-toolbar-transaction-mode-select {
width: 74px !important;
flex: 0 0 74px !important;
width: 78px !important;
flex: 0 0 78px !important;
}
body[data-ui-version="v2"] .gn-v2-query-toolbar-transaction-delay-select {
width: 62px !important;
flex: 0 0 62px !important;
width: 104px !important;
flex: 0 0 104px !important;
}
body[data-ui-version="v2"] .gn-v2-query-toolbar .ant-select-selector {

View File

@@ -6249,6 +6249,8 @@
"query_editor.transaction.action.commit_with_count": "Commit ({{count}})",
"query_editor.transaction.action.rollback": "Rollback",
"query_editor.transaction.delay.immediate": "Sofort",
"query_editor.transaction.delay.immediate_commit": "Sofort committen",
"query_editor.transaction.delay.seconds_commit": "Commit in {{seconds}}s",
"query_editor.transaction.message.pending_managed_transaction": "Im SQL-Editor ist bereits eine nicht festgeschriebene Transaktion offen. Führen Sie zuerst Commit oder Rollback aus, bevor Sie eine weitere DML-Anweisung starten.",
"query_editor.transaction.mode.auto": "Automatisch",
"query_editor.transaction.mode.manual": "Manuell",

View File

@@ -6249,6 +6249,8 @@
"query_editor.transaction.action.commit_with_count": "Commit ({{count}})",
"query_editor.transaction.action.rollback": "Rollback",
"query_editor.transaction.delay.immediate": "Immediately",
"query_editor.transaction.delay.immediate_commit": "Commit now",
"query_editor.transaction.delay.seconds_commit": "Commit in {{seconds}}s",
"query_editor.transaction.message.pending_managed_transaction": "The SQL editor already has a pending transaction. Commit or roll it back before running another DML statement.",
"query_editor.transaction.mode.auto": "Auto",
"query_editor.transaction.mode.manual": "Manual",

View File

@@ -6249,6 +6249,8 @@
"query_editor.transaction.action.commit_with_count": "コミット ({{count}})",
"query_editor.transaction.action.rollback": "ロールバック",
"query_editor.transaction.delay.immediate": "即時",
"query_editor.transaction.delay.immediate_commit": "即時コミット",
"query_editor.transaction.delay.seconds_commit": "{{seconds}}s 後にコミット",
"query_editor.transaction.message.pending_managed_transaction": "SQL エディターには未コミットのトランザクションがあります。新しい DML 文を実行する前にコミットまたはロールバックしてください。",
"query_editor.transaction.mode.auto": "自動",
"query_editor.transaction.mode.manual": "手動",

View File

@@ -6249,6 +6249,8 @@
"query_editor.transaction.action.commit_with_count": "Commit ({{count}})",
"query_editor.transaction.action.rollback": "Rollback",
"query_editor.transaction.delay.immediate": "Сразу",
"query_editor.transaction.delay.immediate_commit": "Commit сразу",
"query_editor.transaction.delay.seconds_commit": "Commit через {{seconds}}s",
"query_editor.transaction.message.pending_managed_transaction": "В SQL-редакторе уже есть незавершённая транзакция. Перед выполнением нового DML-оператора выполните commit или rollback.",
"query_editor.transaction.mode.auto": "Авто",
"query_editor.transaction.mode.manual": "Вручную",

View File

@@ -6249,6 +6249,8 @@
"query_editor.transaction.action.commit_with_count": "提交 ({{count}})",
"query_editor.transaction.action.rollback": "回滚",
"query_editor.transaction.delay.immediate": "立即",
"query_editor.transaction.delay.immediate_commit": "立即提交",
"query_editor.transaction.delay.seconds_commit": "{{seconds}}s后提交",
"query_editor.transaction.message.pending_managed_transaction": "当前 SQL 编辑器已有未提交事务,请先提交或回滚后再执行新的增删改语句。",
"query_editor.transaction.mode.auto": "自动",
"query_editor.transaction.mode.manual": "手动",

View File

@@ -6249,6 +6249,8 @@
"query_editor.transaction.action.commit_with_count": "提交 ({{count}})",
"query_editor.transaction.action.rollback": "回滾",
"query_editor.transaction.delay.immediate": "立即",
"query_editor.transaction.delay.immediate_commit": "立即提交",
"query_editor.transaction.delay.seconds_commit": "{{seconds}}s後提交",
"query_editor.transaction.message.pending_managed_transaction": "目前 SQL 編輯器已有未提交交易,請先提交或回滾後再執行新的新增、更新或刪除語句。",
"query_editor.transaction.mode.auto": "自動",
"query_editor.transaction.mode.manual": "手動",