mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-07-11 07:21:37 +08:00
🐛 fix(query-editor): 优化事务工具栏排版并修复 Wails 开发页加载
- 将手动事务提交回滚按钮并入主工具栏,移除重复入口和未提交文案 - 调整 v2 查询工具栏顺序,收起结果区入口并统一更多、AI、设置交互 - 修正 Wails dev 地址为 127.0.0.1,避免 index.html not found
This commit is contained in:
@@ -417,6 +417,9 @@ const textContent = (node: any): string =>
|
||||
const findButton = (renderer: ReactTestRenderer, text: string) =>
|
||||
renderer.root.findAll((node) => node.type === 'button' && textContent(node).includes(text))[0];
|
||||
|
||||
const findButtons = (renderer: ReactTestRenderer, text: string) =>
|
||||
renderer.root.findAll((node) => node.type === 'button' && textContent(node).includes(text));
|
||||
|
||||
const findExactButton = (renderer: ReactTestRenderer, text: string) =>
|
||||
renderer.root.findAll((node) => node.type === 'button' && textContent(node) === text)[0];
|
||||
|
||||
@@ -2448,7 +2451,8 @@ describe('QueryEditor external SQL save', () => {
|
||||
'query-1',
|
||||
);
|
||||
expect(backendApp.DBQueryMulti).not.toHaveBeenCalled();
|
||||
expect(textContent(renderer!.root)).toContain('未提交');
|
||||
expect(textContent(renderer!.root)).not.toContain('未提交');
|
||||
expect(textContent(renderer!.root)).toContain('提交');
|
||||
expect(textContent(renderer!.root)).toContain('影响行数:2');
|
||||
|
||||
await act(async () => {
|
||||
@@ -2494,7 +2498,8 @@ describe('QueryEditor external SQL save', () => {
|
||||
'query-1',
|
||||
);
|
||||
expect(backendApp.DBQueryMulti).not.toHaveBeenCalled();
|
||||
expect(textContent(renderer!.root)).toContain('未提交');
|
||||
expect(textContent(renderer!.root)).not.toContain('未提交');
|
||||
expect(textContent(renderer!.root)).toContain('提交');
|
||||
|
||||
await act(async () => {
|
||||
await findButton(renderer!, '提交').props.onClick();
|
||||
@@ -2544,7 +2549,7 @@ describe('QueryEditor external SQL save', () => {
|
||||
expect.stringContaining('DELETE FROM users'),
|
||||
'query-1',
|
||||
);
|
||||
expect(textContent(renderer!.root)).toContain('未提交');
|
||||
expect(textContent(renderer!.root)).not.toContain('未提交');
|
||||
expect(textContent(renderer!.root)).toContain('提交 (2)');
|
||||
expect(storeState.sqlEditorPendingTransactions['tab-1']).toMatchObject({
|
||||
id: 'tx-multi-dml',
|
||||
@@ -2583,6 +2588,35 @@ describe('QueryEditor external SQL save', () => {
|
||||
expect(backendApp.DBQueryMultiTransactional).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('keeps manual SQL transaction actions inline in the top toolbar without duplicating them in result tabs', async () => {
|
||||
backendApp.DBQueryMultiTransactional.mockResolvedValueOnce({
|
||||
success: true,
|
||||
transactionId: 'tx-toolbar-inline',
|
||||
transactionPending: true,
|
||||
data: [
|
||||
{ columns: ['affectedRows'], rows: [{ affectedRows: 1 }], statementIndex: 1 },
|
||||
],
|
||||
});
|
||||
|
||||
let renderer!: ReactTestRenderer;
|
||||
await act(async () => {
|
||||
renderer = create(<QueryEditor tab={createTab({ query: "UPDATE users SET active = 0 WHERE id = 1" })} />);
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await findButton(renderer!, '运行').props.onClick();
|
||||
});
|
||||
await act(async () => {
|
||||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
const pageText = textContent(renderer!.root);
|
||||
expect(pageText).not.toContain('未提交');
|
||||
expect(findButtons(renderer!, '提交')).toHaveLength(1);
|
||||
expect(findButtons(renderer!, '回滚')).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('adds pagination to limited query results and reloads the selected page only', async () => {
|
||||
const firstPageRows = Array.from({ length: 500 }, (_item, index) => ({ id: index + 1 }));
|
||||
const secondPageRows = Array.from({ length: 500 }, (_item, index) => ({ id: index + 501 }));
|
||||
@@ -2673,7 +2707,7 @@ describe('QueryEditor external SQL save', () => {
|
||||
'query-1',
|
||||
);
|
||||
expect(backendApp.DBQueryMulti).not.toHaveBeenCalled();
|
||||
expect(textContent(renderer!.root)).toContain('未提交');
|
||||
expect(textContent(renderer!.root)).not.toContain('未提交');
|
||||
});
|
||||
|
||||
it('auto commits SQL editor DML transactions after the configured delay', async () => {
|
||||
@@ -3980,6 +4014,7 @@ describe('QueryEditor external SQL save', () => {
|
||||
it('keeps the v2 query editor toolbar grouped and compact', () => {
|
||||
const source = readFileSync(new URL('./QueryEditor.tsx', import.meta.url), 'utf8');
|
||||
const toolbarSource = readFileSync(new URL('./QueryEditorToolbar.tsx', import.meta.url), 'utf8');
|
||||
const resultsPanelSource = readFileSync(new URL('./QueryEditorResultsPanel.tsx', import.meta.url), 'utf8');
|
||||
const transactionSettingsSource = readFileSync(new URL('./QueryEditorTransactionSettings.tsx', import.meta.url), 'utf8');
|
||||
const transactionToolbarSource = readFileSync(new URL('./QueryEditorTransactionToolbar.tsx', import.meta.url), 'utf8');
|
||||
const css = readFileSync(new URL('../v2-theme.css', import.meta.url), 'utf8');
|
||||
@@ -4002,12 +4037,23 @@ describe('QueryEditor external SQL save', () => {
|
||||
expect(transactionSettingsSource).toContain("label: '3s'");
|
||||
expect(source).toContain('QueryEditorTransactionToolbar');
|
||||
expect(transactionToolbarSource).toContain("className={isV2Ui ? 'gn-v2-query-transaction-toolbar' : undefined}");
|
||||
expect(transactionToolbarSource).toContain("'未提交'");
|
||||
expect(transactionToolbarSource).toContain(": null;");
|
||||
expect(transactionToolbarSource).toContain('gn-v2-query-transaction-commit-button');
|
||||
expect(transactionToolbarSource).toContain('gn-v2-toolbar-kbd');
|
||||
expect(transactionToolbarSource).toContain("'自动提交中'");
|
||||
expect(transactionToolbarSource).toContain('onFinish');
|
||||
expect(toolbarSource).toContain('{isV2Ui && pendingTransactionToolbar}');
|
||||
expect(toolbarSource).not.toContain('gn-v2-query-toolbar-transaction-row');
|
||||
expect(resultsPanelSource).not.toContain('transactionToolbar?: React.ReactNode;');
|
||||
expect(toolbarSource).toContain('gn-v2-query-toolbar-action-group');
|
||||
expect(toolbarSource).toContain('gn-v2-query-toolbar-action-pair');
|
||||
expect(toolbarSource).toContain('const aiMenuItems');
|
||||
expect(toolbarSource).toContain('key: "toggle-result-panel"');
|
||||
expect(toolbarSource).toContain('{!isV2Ui && (');
|
||||
expect(toolbarSource).toContain('trigger={["click"]}');
|
||||
expect(toolbarSource.indexOf('onClick={onQuickSave}')).toBeLessThan(toolbarSource.indexOf('menu={{ items: aiMenuItems }}'));
|
||||
expect(toolbarSource.indexOf('menu={{ items: aiMenuItems }}')).toBeLessThan(toolbarSource.indexOf('menu={{ items: moreMenuItems }}'));
|
||||
expect(toolbarSource.indexOf('menu={{ items: moreMenuItems }}')).toBeLessThan(toolbarSource.indexOf('icon={<FormatPainterOutlined />}'));
|
||||
expect(transactionSettingsSource).toContain('style={isV2Ui ? undefined : { width: 78 }}');
|
||||
expect(transactionSettingsSource).toContain('style={isV2Ui ? undefined : { width: 68 }}');
|
||||
expect(toolbarSource).toContain('style={isV2Ui ? undefined : { width: 200 }}');
|
||||
@@ -4023,6 +4069,8 @@ describe('QueryEditor external SQL save', () => {
|
||||
expect(css).toContain('line-height: 30px !important;');
|
||||
expect(css).toContain('display: inline-flex !important;');
|
||||
expect(css).toContain('gap: 6px;');
|
||||
expect(css).toContain('body[data-ui-version="v2"] .gn-v2-query-toolbar-action-pair');
|
||||
expect(css).toContain('gap: 8px;');
|
||||
expect(css).toContain('margin-left: 0 !important;');
|
||||
expect(css).toContain('max-width: 760px;');
|
||||
expect(css).toContain('width: 140px !important;');
|
||||
@@ -4030,6 +4078,7 @@ describe('QueryEditor external SQL save', () => {
|
||||
expect(css).toContain('width: 132px !important;');
|
||||
expect(css).toContain('width: 34px !important;');
|
||||
expect(css).toContain('@media (max-width: 900px)');
|
||||
expect(css).not.toContain('body[data-ui-version="v2"] .gn-v2-query-toolbar-transaction-row {');
|
||||
|
||||
const queryToolbarMainCss = css.slice(css.indexOf('body[data-ui-version="v2"] .gn-v2-query-toolbar-main {'), css.indexOf('body[data-ui-version="v2"] .gn-v2-query-toolbar-selects {'));
|
||||
expect(queryToolbarMainCss).not.toContain('margin-left: auto;');
|
||||
|
||||
@@ -5279,7 +5279,6 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
|
||||
currentDb={currentDb}
|
||||
currentConnectionId={currentConnectionId}
|
||||
toggleShortcutLabel={toggleQueryResultsPanelShortcutLabel}
|
||||
transactionToolbar={sqlEditorTransactionToolbar}
|
||||
onActiveResultKeyChange={setActiveResultKey}
|
||||
onHide={() => updateResultPanelVisibility(false)}
|
||||
onCloseResult={handleCloseResult}
|
||||
|
||||
@@ -35,7 +35,6 @@ interface QueryEditorResultsPanelProps {
|
||||
currentDb: string;
|
||||
currentConnectionId: string;
|
||||
toggleShortcutLabel: string;
|
||||
transactionToolbar?: React.ReactNode;
|
||||
onActiveResultKeyChange: (key: string) => void;
|
||||
onHide: () => void;
|
||||
onCloseResult: (key: string) => void;
|
||||
@@ -61,7 +60,6 @@ const QueryEditorResultsPanel: React.FC<QueryEditorResultsPanelProps> = ({
|
||||
currentDb,
|
||||
currentConnectionId,
|
||||
toggleShortcutLabel,
|
||||
transactionToolbar,
|
||||
onActiveResultKeyChange,
|
||||
onHide,
|
||||
onCloseResult,
|
||||
@@ -138,12 +136,11 @@ const QueryEditorResultsPanel: React.FC<QueryEditorResultsPanelProps> = ({
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
const tabsExtraContent = transactionToolbar || !activeResultUsesDataGrid
|
||||
const tabsExtraContent = !activeResultUsesDataGrid
|
||||
? {
|
||||
right: (
|
||||
<div style={{ display: 'inline-flex', alignItems: 'center', gap: 8 }}>
|
||||
{transactionToolbar}
|
||||
{!activeResultUsesDataGrid ? tabsHideButton : null}
|
||||
{tabsHideButton}
|
||||
</div>
|
||||
),
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { Button, Dropdown, Select, Space, Tooltip, type MenuProps } from 'antd';
|
||||
import React from "react";
|
||||
import { Button, Dropdown, Select, Space, Tooltip, type MenuProps } from "antd";
|
||||
import {
|
||||
EyeInvisibleOutlined,
|
||||
EyeOutlined,
|
||||
@@ -9,11 +9,17 @@ import {
|
||||
SaveOutlined,
|
||||
SettingOutlined,
|
||||
StopOutlined,
|
||||
} from '@ant-design/icons';
|
||||
} from "@ant-design/icons";
|
||||
|
||||
import type { SavedConnection } from '../types';
|
||||
import { getShortcutDisplayLabel, type ShortcutPlatform, type ShortcutPlatformBinding } from '../utils/shortcuts';
|
||||
import QueryEditorTransactionSettings, { type SqlEditorCommitMode } from './QueryEditorTransactionSettings';
|
||||
import type { SavedConnection } from "../types";
|
||||
import {
|
||||
getShortcutDisplayLabel,
|
||||
type ShortcutPlatform,
|
||||
type ShortcutPlatformBinding,
|
||||
} from "../utils/shortcuts";
|
||||
import QueryEditorTransactionSettings, {
|
||||
type SqlEditorCommitMode,
|
||||
} from "./QueryEditorTransactionSettings";
|
||||
|
||||
type QueryEditorToolbarProps = {
|
||||
isV2Ui: boolean;
|
||||
@@ -31,8 +37,8 @@ type QueryEditorToolbarProps = {
|
||||
activeShortcutPlatform: ShortcutPlatform;
|
||||
isResultPanelVisible: boolean;
|
||||
loading: boolean;
|
||||
saveMoreMenuItems: MenuProps['items'];
|
||||
formatSettingsMenu: MenuProps['items'];
|
||||
saveMoreMenuItems: MenuProps["items"];
|
||||
formatSettingsMenu: MenuProps["items"];
|
||||
onConnectionChange: (connectionId: string) => void;
|
||||
onDatabaseChange: (dbName: string) => void;
|
||||
onMaxRowsChange: (maxRows: number) => void;
|
||||
@@ -44,7 +50,7 @@ type QueryEditorToolbarProps = {
|
||||
onQuickSave: () => void;
|
||||
onFormat: () => void;
|
||||
onToggleResultPanelVisibility: () => void;
|
||||
onAIAction: (action: 'generate' | 'explain' | 'optimize' | 'schema') => void;
|
||||
onAIAction: (action: "generate" | "explain" | "optimize" | "schema") => void;
|
||||
};
|
||||
|
||||
const QueryEditorToolbar: React.FC<QueryEditorToolbarProps> = ({
|
||||
@@ -78,41 +84,112 @@ const QueryEditorToolbar: React.FC<QueryEditorToolbarProps> = ({
|
||||
onToggleResultPanelVisibility,
|
||||
onAIAction,
|
||||
}) => {
|
||||
const baseMoreMenuItems = saveMoreMenuItems ?? [];
|
||||
const toggleResultPanelTitle =
|
||||
toggleQueryResultsPanelShortcutBinding.enabled &&
|
||||
toggleQueryResultsPanelShortcutBinding.combo
|
||||
? `${isResultPanelVisible ? "隐藏结果区" : "显示结果区"}(${getShortcutDisplayLabel(toggleQueryResultsPanelShortcutBinding.combo, activeShortcutPlatform)})`
|
||||
: isResultPanelVisible
|
||||
? "隐藏结果区"
|
||||
: "显示结果区";
|
||||
const aiMenuItems: MenuProps["items"] = [
|
||||
{
|
||||
key: "ai-generate",
|
||||
label: "生成 SQL",
|
||||
icon: <RobotOutlined />,
|
||||
onClick: () => onAIAction("generate"),
|
||||
},
|
||||
{
|
||||
key: "ai-explain",
|
||||
label: "解释 SQL",
|
||||
icon: <RobotOutlined />,
|
||||
onClick: () => onAIAction("explain"),
|
||||
},
|
||||
{
|
||||
key: "ai-optimize",
|
||||
label: "优化 SQL",
|
||||
icon: <RobotOutlined />,
|
||||
onClick: () => onAIAction("optimize"),
|
||||
},
|
||||
{ type: "divider" as const },
|
||||
{
|
||||
key: "ai-schema",
|
||||
label: "Schema 分析",
|
||||
icon: <RobotOutlined />,
|
||||
onClick: () => onAIAction("schema"),
|
||||
},
|
||||
];
|
||||
const moreMenuItems: MenuProps["items"] = isV2Ui
|
||||
? [
|
||||
...baseMoreMenuItems,
|
||||
...(baseMoreMenuItems.length > 0 ? [{ type: "divider" as const }] : []),
|
||||
{
|
||||
key: "toggle-result-panel",
|
||||
label: toggleResultPanelTitle,
|
||||
icon: isResultPanelVisible ? (
|
||||
<EyeInvisibleOutlined />
|
||||
) : (
|
||||
<EyeOutlined />
|
||||
),
|
||||
onClick: onToggleResultPanelVisibility,
|
||||
},
|
||||
]
|
||||
: baseMoreMenuItems;
|
||||
const selects = (
|
||||
<div
|
||||
className={isV2Ui ? 'gn-v2-query-toolbar-selects' : undefined}
|
||||
style={{ display: 'flex', gap: '8px', flexShrink: 0, alignItems: 'center' }}
|
||||
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}
|
||||
className={
|
||||
isV2Ui
|
||||
? "gn-v2-query-toolbar-select gn-v2-query-toolbar-connection-select"
|
||||
: undefined
|
||||
}
|
||||
style={isV2Ui ? undefined : { width: 150 }}
|
||||
placeholder="选择连接"
|
||||
value={currentConnectionId}
|
||||
onChange={onConnectionChange}
|
||||
options={queryCapableConnections.map(c => ({ label: c.name, value: c.id }))}
|
||||
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}
|
||||
className={
|
||||
isV2Ui
|
||||
? "gn-v2-query-toolbar-select gn-v2-query-toolbar-database-select"
|
||||
: undefined
|
||||
}
|
||||
style={isV2Ui ? undefined : { width: 200 }}
|
||||
placeholder="选择数据库"
|
||||
value={currentDb}
|
||||
onChange={onDatabaseChange}
|
||||
options={dbList.map(db => ({ label: db, value: db }))}
|
||||
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}
|
||||
className={
|
||||
isV2Ui
|
||||
? "gn-v2-query-toolbar-select gn-v2-query-toolbar-max-rows-select"
|
||||
: undefined
|
||||
}
|
||||
style={isV2Ui ? undefined : { width: 170 }}
|
||||
value={maxRows}
|
||||
onChange={(val) => onMaxRowsChange(Number(val))}
|
||||
options={[
|
||||
{ label: '最大行数:500', value: 500 },
|
||||
{ label: '最大行数:1000', value: 1000 },
|
||||
{ label: '最大行数:5000', value: 5000 },
|
||||
{ label: '最大行数:20000', value: 20000 },
|
||||
{ label: '最大行数:不限', value: 0 },
|
||||
{ label: "最大行数:500", value: 500 },
|
||||
{ label: "最大行数:1000", value: 1000 },
|
||||
{ label: "最大行数:5000", value: 5000 },
|
||||
{ label: "最大行数:20000", value: 20000 },
|
||||
{ label: "最大行数:不限", value: 0 },
|
||||
]}
|
||||
/>
|
||||
</Tooltip>
|
||||
@@ -129,83 +206,132 @@ const QueryEditorToolbar: React.FC<QueryEditorToolbarProps> = ({
|
||||
|
||||
const actions = (
|
||||
<div
|
||||
className={isV2Ui ? 'gn-v2-query-toolbar-actions' : undefined}
|
||||
style={{ display: 'flex', gap: '8px', flexShrink: 0, alignItems: 'center' }}
|
||||
className={isV2Ui ? "gn-v2-query-toolbar-actions" : undefined}
|
||||
style={{
|
||||
display: "flex",
|
||||
gap: "8px",
|
||||
flexShrink: 0,
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Space.Compact className={isV2Ui ? 'gn-v2-query-toolbar-action-group' : undefined}>
|
||||
<Space.Compact
|
||||
className={isV2Ui ? "gn-v2-query-toolbar-action-group" : undefined}
|
||||
>
|
||||
<Tooltip
|
||||
title={
|
||||
runQueryShortcutBinding.enabled && runQueryShortcutBinding.combo
|
||||
? `运行(${getShortcutDisplayLabel(runQueryShortcutBinding.combo, activeShortcutPlatform)})`
|
||||
: '运行'
|
||||
: "运行"
|
||||
}
|
||||
>
|
||||
<Button className={isV2Ui ? 'gn-v2-query-toolbar-run-action' : undefined} type="primary" icon={<PlayCircleOutlined />} onMouseDown={onCaptureEditorCursorPosition} onClick={onRun} loading={loading}>
|
||||
<Button
|
||||
className={isV2Ui ? "gn-v2-query-toolbar-run-action" : undefined}
|
||||
type="primary"
|
||||
icon={<PlayCircleOutlined />}
|
||||
onMouseDown={onCaptureEditorCursorPosition}
|
||||
onClick={onRun}
|
||||
loading={loading}
|
||||
>
|
||||
运行
|
||||
</Button>
|
||||
</Tooltip>
|
||||
{loading && (
|
||||
<Button type="primary" danger icon={<StopOutlined />} onClick={onCancel}>
|
||||
<Button
|
||||
type="primary"
|
||||
danger
|
||||
icon={<StopOutlined />}
|
||||
onClick={onCancel}
|
||||
>
|
||||
停止
|
||||
</Button>
|
||||
)}
|
||||
</Space.Compact>
|
||||
<Space.Compact className={isV2Ui ? 'gn-v2-query-toolbar-action-group' : undefined}>
|
||||
{isV2Ui && pendingTransactionToolbar}
|
||||
<div
|
||||
className={isV2Ui ? "gn-v2-query-toolbar-action-pair" : undefined}
|
||||
style={{ display: "flex", gap: "8px", alignItems: "center" }}
|
||||
>
|
||||
<Tooltip
|
||||
title={
|
||||
saveQueryShortcutBinding.enabled && saveQueryShortcutBinding.combo
|
||||
? `保存(${getShortcutDisplayLabel(saveQueryShortcutBinding.combo, activeShortcutPlatform)})`
|
||||
: '保存'
|
||||
: "保存"
|
||||
}
|
||||
>
|
||||
<Button icon={<SaveOutlined />} onClick={onQuickSave}>
|
||||
保存
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Dropdown menu={{ items: saveMoreMenuItems }} placement="bottomRight">
|
||||
<Dropdown
|
||||
menu={{ items: aiMenuItems }}
|
||||
placement="bottomRight"
|
||||
trigger={["click"]}
|
||||
>
|
||||
<Button
|
||||
className={isV2Ui ? "gn-v2-query-toolbar-ai-action" : undefined}
|
||||
icon={<RobotOutlined />}
|
||||
style={{ color: "#818cf8" }}
|
||||
>
|
||||
AI
|
||||
</Button>
|
||||
</Dropdown>
|
||||
<Dropdown
|
||||
menu={{ items: moreMenuItems }}
|
||||
placement="bottomRight"
|
||||
trigger={["click"]}
|
||||
>
|
||||
<Button>更多</Button>
|
||||
</Dropdown>
|
||||
</Space.Compact>
|
||||
</div>
|
||||
|
||||
<Space.Compact className={isV2Ui ? 'gn-v2-query-toolbar-action-group' : undefined}>
|
||||
<Tooltip title="美化 SQL">
|
||||
<Button icon={<FormatPainterOutlined />} onClick={onFormat}>美化</Button>
|
||||
</Tooltip>
|
||||
<Dropdown menu={{ items: formatSettingsMenu }} placement="bottomRight">
|
||||
<Button className={isV2Ui ? 'gn-v2-query-toolbar-icon-action' : undefined} icon={<SettingOutlined />} />
|
||||
</Dropdown>
|
||||
</Space.Compact>
|
||||
|
||||
<Tooltip
|
||||
title={
|
||||
toggleQueryResultsPanelShortcutBinding.enabled && toggleQueryResultsPanelShortcutBinding.combo
|
||||
? `${isResultPanelVisible ? '隐藏结果区' : '显示结果区'}(${getShortcutDisplayLabel(toggleQueryResultsPanelShortcutBinding.combo, activeShortcutPlatform)})`
|
||||
: (isResultPanelVisible ? '隐藏结果区' : '显示结果区')
|
||||
}
|
||||
<div
|
||||
className={isV2Ui ? "gn-v2-query-toolbar-action-pair" : undefined}
|
||||
style={{ display: "flex", gap: "8px", alignItems: "center" }}
|
||||
>
|
||||
<Button
|
||||
icon={isResultPanelVisible ? <EyeInvisibleOutlined /> : <EyeOutlined />}
|
||||
onClick={onToggleResultPanelVisibility}
|
||||
<Tooltip title="美化 SQL">
|
||||
<Button icon={<FormatPainterOutlined />} onClick={onFormat}>
|
||||
美化
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Dropdown
|
||||
menu={{ items: formatSettingsMenu }}
|
||||
placement="bottomRight"
|
||||
trigger={["click"]}
|
||||
>
|
||||
结果
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Button
|
||||
className={isV2Ui ? "gn-v2-query-toolbar-icon-action" : undefined}
|
||||
icon={<SettingOutlined />}
|
||||
/>
|
||||
</Dropdown>
|
||||
</div>
|
||||
|
||||
<Dropdown menu={{ items: [
|
||||
{ key: 'ai-generate', label: '生成 SQL', icon: <RobotOutlined />, onClick: () => onAIAction('generate') },
|
||||
{ key: 'ai-explain', label: '解释 SQL', icon: <RobotOutlined />, onClick: () => onAIAction('explain') },
|
||||
{ key: 'ai-optimize', label: '优化 SQL', icon: <RobotOutlined />, onClick: () => onAIAction('optimize') },
|
||||
{ type: 'divider' as const },
|
||||
{ key: 'ai-schema', label: 'Schema 分析', icon: <RobotOutlined />, onClick: () => onAIAction('schema') },
|
||||
] }} placement="bottomRight">
|
||||
<Button className={isV2Ui ? 'gn-v2-query-toolbar-ai-action' : undefined} icon={<RobotOutlined />} style={{ color: '#818cf8' }}>AI</Button>
|
||||
</Dropdown>
|
||||
{!isV2Ui && (
|
||||
<Tooltip title={toggleResultPanelTitle}>
|
||||
<Button
|
||||
icon={
|
||||
isResultPanelVisible ? <EyeInvisibleOutlined /> : <EyeOutlined />
|
||||
}
|
||||
onClick={onToggleResultPanelVisibility}
|
||||
>
|
||||
结果
|
||||
</Button>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
if (!isV2Ui) {
|
||||
return (
|
||||
<div className={undefined} style={{ padding: '4px 8px 8px', display: 'flex', gap: '8px', flexShrink: 0, alignItems: 'center' }}>
|
||||
<div
|
||||
className={undefined}
|
||||
style={{
|
||||
padding: "4px 8px 8px",
|
||||
display: "flex",
|
||||
gap: "8px",
|
||||
flexShrink: 0,
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
{selects}
|
||||
{actions}
|
||||
</div>
|
||||
@@ -213,19 +339,27 @@ const QueryEditorToolbar: React.FC<QueryEditorToolbarProps> = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="gn-v2-query-toolbar" style={{ padding: '4px 8px 8px', display: 'flex', gap: '8px', flexShrink: 0 }}>
|
||||
<div
|
||||
className="gn-v2-query-toolbar"
|
||||
style={{
|
||||
padding: "4px 8px 8px",
|
||||
display: "flex",
|
||||
gap: "8px",
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="gn-v2-query-toolbar-main"
|
||||
style={{ display: 'flex', gap: '8px', flexShrink: 0, alignItems: 'center' }}
|
||||
style={{
|
||||
display: "flex",
|
||||
gap: "8px",
|
||||
flexShrink: 0,
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
{selects}
|
||||
{actions}
|
||||
</div>
|
||||
{pendingTransactionToolbar && (
|
||||
<div className="gn-v2-query-toolbar-transaction-row">
|
||||
{pendingTransactionToolbar}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -35,7 +35,7 @@ const QueryEditorTransactionToolbar: React.FC<QueryEditorTransactionToolbarProps
|
||||
? autoCommitRemainingSeconds !== null && autoCommitRemainingSeconds > 0
|
||||
? `${autoCommitRemainingSeconds}s 后自动提交`
|
||||
: '自动提交中'
|
||||
: '未提交';
|
||||
: null;
|
||||
const commitLabel = isV2Ui
|
||||
? (
|
||||
<>
|
||||
@@ -56,9 +56,11 @@ const QueryEditorTransactionToolbar: React.FC<QueryEditorTransactionToolbarProps
|
||||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
>
|
||||
<span style={{ fontSize: 12, color: darkMode ? '#d4d4d4' : '#666' }}>
|
||||
{statusText}
|
||||
</span>
|
||||
{statusText ? (
|
||||
<span style={{ fontSize: 12, color: darkMode ? '#d4d4d4' : '#666' }}>
|
||||
{statusText}
|
||||
</span>
|
||||
) : null}
|
||||
<Button
|
||||
className={isV2Ui ? 'gn-v2-query-transaction-commit-button' : undefined}
|
||||
size="small"
|
||||
|
||||
@@ -4796,9 +4796,7 @@ body[data-ui-version="v2"] .gn-v2-query-toolbar {
|
||||
min-height: 48px;
|
||||
padding: 8px 12px !important;
|
||||
gap: 6px 10px !important;
|
||||
flex-direction: column;
|
||||
flex-wrap: nowrap;
|
||||
align-items: stretch !important;
|
||||
align-items: center !important;
|
||||
background: var(--gn-bg-panel) !important;
|
||||
border-bottom: 0.5px solid var(--gn-br-1) !important;
|
||||
}
|
||||
@@ -4830,16 +4828,10 @@ body[data-ui-version="v2"] .gn-v2-query-toolbar-actions {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-query-toolbar-transaction-row {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-query-transaction-toolbar {
|
||||
display: inline-flex;
|
||||
max-width: 100%;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-query-toolbar .ant-select {
|
||||
@@ -4897,6 +4889,13 @@ body[data-ui-version="v2"] .gn-v2-query-toolbar-action-group.ant-btn-group {
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-query-toolbar-action-pair {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
flex: 0 0 auto;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-query-toolbar-action-group.ant-btn-group > .ant-btn {
|
||||
flex: 0 0 auto;
|
||||
border-radius: 9px !important;
|
||||
@@ -4976,10 +4975,6 @@ body[data-ui-version="v2"] .gn-v2-query-toolbar-ai-action.ant-btn {
|
||||
width: 100%;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-query-toolbar-transaction-row {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
body[data-ui-version="v2"] .gn-v2-query-monaco-shell {
|
||||
|
||||
Reference in New Issue
Block a user