, title: '新建表', kbd: '⌘N', featured: true },
+ { action: 'new-table', icon:
, title: '新建表', kbd: primaryShortcut('N', shortcutPlatform), featured: true },
...(supportsSchemaActions ? [{ action: 'new-schema', icon:
, title: '运行外部 SQL 文件' },
@@ -370,6 +377,13 @@ export const V2DatabaseContextMenuView: React.FC<{
);
};
+const DEFAULT_V2_CONTEXT_MENU_SHORTCUT_PLATFORM: ShortcutPlatform = 'windows';
+
+const primaryShortcut = (
+ key: string,
+ shortcutPlatform: ShortcutPlatform = DEFAULT_V2_CONTEXT_MENU_SHORTCUT_PLATFORM,
+): string => getPrimaryShortcutDisplayLabel(key, shortcutPlatform);
+
export type V2ConnectionContextMenuActionKey =
| 'new-db'
| 'refresh'
@@ -432,6 +446,7 @@ export const V2ConnectionGroupContextMenuView: React.FC<{
export const V2ConnectionContextMenuView: React.FC<{
connectionName: string;
+ shortcutPlatform?: ShortcutPlatform;
hostSummary?: string;
driverLabel?: string;
isRedis?: boolean;
@@ -440,6 +455,7 @@ export const V2ConnectionContextMenuView: React.FC<{
onAction?: (action: V2ConnectionContextMenuActionKey) => void;
}> = ({
connectionName,
+ shortcutPlatform = DEFAULT_V2_CONTEXT_MENU_SHORTCUT_PLATFORM,
hostSummary,
driverLabel,
isRedis = false,
@@ -468,12 +484,12 @@ export const V2ConnectionContextMenuView: React.FC<{
{isRedis ? renderItems([
- { action: 'refresh', icon:
, title: '刷新连接', kbd: '⌘R', featured: true },
+ { action: 'refresh', icon:
, title: '刷新连接', kbd: primaryShortcut('R', shortcutPlatform), featured: true },
{ action: 'new-command', icon:
, title: '新建命令窗口', featured: true },
{ action: 'open-monitor', icon:
, title: 'Redis 实例监控' },
]) : renderItems([
- ...(supportsCreateDatabase ? [{ action: 'new-db' as const, icon:
, title: '新建数据库', kbd: '⌘N', featured: true }] : []),
- { action: 'refresh', icon:
, title: '刷新连接', kbd: '⌘R' },
+ ...(supportsCreateDatabase ? [{ action: 'new-db' as const, icon:
, title: '新建数据库', kbd: primaryShortcut('N', shortcutPlatform), featured: true }] : []),
+ { action: 'refresh', icon:
, title: '刷新连接', kbd: primaryShortcut('R', shortcutPlatform) },
{ action: 'new-query', icon:
, title: '新建查询' },
{ action: 'open-sql-file', icon:
, title: '运行外部 SQL 文件' },
])}
@@ -519,6 +535,8 @@ export const V2ConnectionContextMenuView: React.FC<{
export type V2CellContextMenuActionKey =
| 'copy-field-name'
| 'copy-row-data'
+ | 'copy-row-for-paste'
+ | 'paste-row-as-new'
| 'copy-column-data'
| 'set-null'
| 'edit-row'
@@ -550,6 +568,7 @@ export type V2ColumnHeaderContextMenuActionKey =
export const V2ColumnHeaderContextMenuView: React.FC<{
fieldName: string;
+ shortcutPlatform?: ShortcutPlatform;
columnType?: string;
columnComment?: string;
sortOrder?: 'ascend' | 'descend' | null;
@@ -558,6 +577,7 @@ export const V2ColumnHeaderContextMenuView: React.FC<{
onAction?: (action: V2ColumnHeaderContextMenuActionKey) => void;
}> = ({
fieldName,
+ shortcutPlatform = DEFAULT_V2_CONTEXT_MENU_SHORTCUT_PLATFORM,
columnType,
columnComment,
sortOrder,
@@ -587,7 +607,7 @@ export const V2ColumnHeaderContextMenuView: React.FC<{
{renderItems([
- { action: 'copy-field-name', icon:
, title: '复制字段名称', kbd: '⌘C', featured: true },
+ { action: 'copy-field-name', icon:
, title: '复制字段名称', kbd: primaryShortcut('C', shortcutPlatform), featured: true },
{ action: 'copy-column-data', icon:
, title: '复制列数据' },
])}
@@ -622,19 +642,23 @@ export const V2ColumnHeaderContextMenuView: React.FC<{
export const V2CellContextMenuView: React.FC<{
fieldName: string;
+ shortcutPlatform?: ShortcutPlatform;
tableName?: string;
rowLabel?: string;
selectedRowCount?: number;
canModifyData?: boolean;
+ copiedRowCount?: number;
canPasteCopiedColumns?: boolean;
supportsCopyInsert?: boolean;
onAction?: (action: V2CellContextMenuActionKey) => void;
}> = ({
fieldName,
+ shortcutPlatform = DEFAULT_V2_CONTEXT_MENU_SHORTCUT_PLATFORM,
tableName,
rowLabel,
selectedRowCount = 0,
canModifyData = false,
+ copiedRowCount = 0,
canPasteCopiedColumns = false,
supportsCopyInsert = true,
onAction,
@@ -658,7 +682,7 @@ export const V2CellContextMenuView: React.FC<{
{renderItems([
- { action: 'copy-field-name', icon: , title: '复制字段名称', kbd: '⌘C', featured: true },
+ { action: 'copy-field-name', icon: , title: '复制字段名称', kbd: primaryShortcut('C', shortcutPlatform), featured: true },
])}
{canModifyData && (
@@ -667,6 +691,13 @@ export const V2CellContextMenuView: React.FC<{
{renderItems([
{ action: 'set-null', icon: , title: '设置为 NULL' },
{ action: 'edit-row', icon: , title: '编辑本行', kbd: '↵' },
+ { action: 'copy-row-for-paste', icon: , title: '复制本行为新增行' },
+ {
+ action: 'paste-row-as-new',
+ icon: ,
+ title: copiedRowCount > 0 ? `粘贴为新增行 (${copiedRowCount})` : '粘贴为新增行',
+ disabled: copiedRowCount <= 0,
+ },
{
action: 'fill-selected',
icon: ,
diff --git a/frontend/src/v2-theme.css b/frontend/src/v2-theme.css
index b768ee3..8b1dad2 100644
--- a/frontend/src/v2-theme.css
+++ b/frontend/src/v2-theme.css
@@ -3983,9 +3983,19 @@ body[data-ui-version="v2"] .gn-v2-table-context-menu-popup {
max-width: calc(100vw - 24px);
}
+body[data-ui-version="v2"] .gn-v2-sidebar-context-menu-portal {
+ width: 264px;
+ max-width: calc(100vw - 24px);
+}
+
+body[data-ui-version="v2"] .gn-v2-table-overview-context-menu-portal {
+ width: 264px;
+ max-width: calc(100vw - 24px);
+}
+
body[data-ui-version="v2"] .gn-v2-table-context-menu {
width: 264px;
- max-height: min(582px, calc(100vh - 28px));
+ max-height: min(582px, var(--gn-v2-context-menu-max-height, calc(100vh - 28px)));
display: flex;
flex-direction: column;
box-sizing: border-box;
@@ -4006,10 +4016,10 @@ body[data-ui-version="v2"] .gn-v2-context-menu-body {
max-height: none;
overflow-y: auto;
overflow-x: hidden;
- padding: 4px 4px 12px;
+ padding: 4px 4px 20px;
box-sizing: border-box;
overscroll-behavior: contain;
- scroll-padding-bottom: 12px;
+ scroll-padding-bottom: 20px;
}
body[data-ui-version="v2"] .gn-v2-context-menu-body::-webkit-scrollbar {
@@ -4351,6 +4361,15 @@ body[data-ui-version="v2"] .gn-v2-query-results .query-result-tabs > .ant-tabs-n
background: var(--gn-bg-panel-2);
}
+body[data-ui-version="v2"] .gn-v2-query-results .query-result-tabs > .ant-tabs-nav .ant-tabs-tab {
+ min-height: 34px;
+ padding: 4px 10px !important;
+}
+
+body[data-ui-version="v2"] .gn-v2-query-results .query-result-tabs > .ant-tabs-nav .ant-tabs-nav-list {
+ align-items: stretch;
+}
+
body[data-ui-version="v2"] .gn-v2-query-empty,
body[data-ui-version="v2"] .gn-v2-query-success {
display: flex;