mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-07-03 01:11:27 +08:00
✨ feat(designer): 将对象设计整合进数据视图并统一设计表交互样式
This commit is contained in:
@@ -65,6 +65,8 @@ const backendApp = vi.hoisted(() => ({
|
||||
DBGetColumns: vi.fn(),
|
||||
DBGetIndexes: vi.fn(),
|
||||
DBGetForeignKeys: vi.fn(),
|
||||
DBGetTriggers: vi.fn(),
|
||||
DBQuery: vi.fn(),
|
||||
DBShowCreateTable: vi.fn(),
|
||||
}));
|
||||
|
||||
@@ -112,6 +114,16 @@ vi.mock('./ImportPreviewModal', () => ({
|
||||
default: () => null,
|
||||
}));
|
||||
|
||||
vi.mock('./TableDesigner', () => ({
|
||||
default: ({ tab, embedded }: { tab: { tableName?: string; initialTab?: string }; embedded?: boolean }) => (
|
||||
<div data-table-designer={embedded ? 'embedded' : 'standalone'}>
|
||||
<span>SCHEMA DESIGNER</span>
|
||||
<span>{tab.tableName || 'unknown-table'}</span>
|
||||
<span>{tab.initialTab || 'columns'}</span>
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock('@ant-design/icons', () => {
|
||||
const Icon = () => <span />;
|
||||
|
||||
@@ -152,6 +164,7 @@ vi.mock('@ant-design/icons', () => {
|
||||
vi.mock('@dnd-kit/core', () => ({
|
||||
DndContext: ({ children }: any) => <>{children}</>,
|
||||
PointerSensor: vi.fn(),
|
||||
KeyboardSensor: vi.fn(),
|
||||
MouseSensor: vi.fn(),
|
||||
TouchSensor: vi.fn(),
|
||||
useSensor: vi.fn(() => ({})),
|
||||
@@ -170,6 +183,7 @@ vi.mock('@dnd-kit/sortable', () => ({
|
||||
isDragging: false,
|
||||
})),
|
||||
horizontalListSortingStrategy: vi.fn(),
|
||||
sortableKeyboardCoordinates: vi.fn(),
|
||||
arrayMove: (items: any[], from: number, to: number) => {
|
||||
const next = [...items];
|
||||
const [item] = next.splice(from, 1);
|
||||
@@ -223,6 +237,29 @@ vi.mock('antd', () => {
|
||||
Modal.useModal = () => [{ info: vi.fn(() => ({ destroy: vi.fn() })) }, null];
|
||||
|
||||
const passthrough = ({ children }: any) => <>{children}</>;
|
||||
const Space = ({ children }: any) => <div>{children}</div>;
|
||||
const Tabs = ({ items = [], activeKey, onChange }: any) => {
|
||||
const resolvedActiveKey = activeKey ?? items[0]?.key;
|
||||
const activeItem = items.find((item: any) => item.key === resolvedActiveKey) || items[0];
|
||||
return (
|
||||
<div data-tabs-active-key={resolvedActiveKey}>
|
||||
<div>
|
||||
{items.map((item: any) => (
|
||||
<button key={item.key} type="button" data-tab-key={item.key} onClick={() => onChange?.(item.key)}>
|
||||
{item.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div>{activeItem?.children ?? null}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
const Empty: any = ({ description }: any) => <div>{description || 'empty'}</div>;
|
||||
Empty.PRESENTED_IMAGE_SIMPLE = 'presented-image-simple';
|
||||
const Tag = ({ children }: any) => <span>{children}</span>;
|
||||
const Radio: any = ({ children }: any) => <span>{children}</span>;
|
||||
Radio.Group = ({ children }: any) => <div>{children}</div>;
|
||||
Radio.Button = ({ children }: any) => <button type="button">{children}</button>;
|
||||
const Segmented = ({ value, options, onChange }: any) => (
|
||||
<div data-segmented-value={value}>
|
||||
{(options || []).map((option: any) => (
|
||||
@@ -255,7 +292,7 @@ vi.mock('antd', () => {
|
||||
Dropdown: passthrough,
|
||||
Form,
|
||||
Pagination: () => null,
|
||||
Select: () => null,
|
||||
Select: ({ children }: any) => <div>{children}</div>,
|
||||
Modal,
|
||||
Checkbox: ({ checked, onChange }: any) => <input type="checkbox" checked={checked} onChange={onChange} />,
|
||||
Segmented,
|
||||
@@ -264,6 +301,11 @@ vi.mock('antd', () => {
|
||||
DatePicker: () => null,
|
||||
TimePicker: () => null,
|
||||
AutoComplete: ({ children }: any) => <>{children}</>,
|
||||
Tabs,
|
||||
Empty,
|
||||
Space,
|
||||
Tag,
|
||||
Radio,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -509,6 +551,8 @@ describe('DataGrid DDL interactions', () => {
|
||||
backendApp.DBGetColumns.mockResolvedValue({ success: true, data: [] });
|
||||
backendApp.DBGetIndexes.mockResolvedValue({ success: true, data: [] });
|
||||
backendApp.DBGetForeignKeys.mockResolvedValue({ success: true, data: [] });
|
||||
backendApp.DBGetTriggers.mockResolvedValue({ success: true, data: [] });
|
||||
backendApp.DBQuery.mockResolvedValue({ success: true, data: [] });
|
||||
backendApp.DBShowCreateTable.mockResolvedValue({ success: true, data: 'CREATE TABLE users' });
|
||||
storeState.appearance.uiVersion = 'legacy';
|
||||
storeState.addTab.mockReset();
|
||||
@@ -557,6 +601,8 @@ describe('DataGrid DDL interactions', () => {
|
||||
backendApp.DBGetColumns.mockReset();
|
||||
backendApp.DBGetIndexes.mockReset();
|
||||
backendApp.DBGetForeignKeys.mockReset();
|
||||
backendApp.DBGetTriggers.mockReset();
|
||||
backendApp.DBQuery.mockReset();
|
||||
backendApp.DBShowCreateTable.mockReset();
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
@@ -654,6 +700,7 @@ describe('DataGrid DDL interactions', () => {
|
||||
connectionId: 'conn-1',
|
||||
dbName: 'main',
|
||||
tableName: 'customers',
|
||||
objectType: 'table',
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -949,8 +996,15 @@ describe('DataGrid DDL interactions', () => {
|
||||
renderer!.unmount();
|
||||
});
|
||||
|
||||
it('switches the v2 footer field tab into the main fields view', async () => {
|
||||
it('switches the v2 footer object tab into the embedded designer view', async () => {
|
||||
storeState.appearance.uiVersion = 'v2';
|
||||
backendApp.DBGetColumns.mockResolvedValueOnce({
|
||||
success: true,
|
||||
data: [
|
||||
{ name: 'id', type: 'bigint', key: 'PRI', nullable: 'NO', default: '', comment: '' },
|
||||
{ name: 'name', type: 'varchar(255)', key: '', nullable: 'YES', default: '', comment: '' },
|
||||
],
|
||||
});
|
||||
|
||||
let renderer: ReactTestRenderer;
|
||||
await act(async () => {
|
||||
@@ -968,12 +1022,12 @@ describe('DataGrid DDL interactions', () => {
|
||||
await waitForEffects();
|
||||
|
||||
await act(async () => {
|
||||
findButton(renderer!, '字段信息').props.onClick();
|
||||
findButton(renderer!, '对象设计').props.onClick();
|
||||
});
|
||||
|
||||
const content = textContent(renderer!.root);
|
||||
expect(content).toContain('FIELDS');
|
||||
expect(content).toContain('2 个字段');
|
||||
expect(content).toContain('SCHEMA DESIGNER');
|
||||
expect(content).toContain('字段');
|
||||
expect(content).toContain('id');
|
||||
expect(content).toContain('name');
|
||||
});
|
||||
@@ -997,9 +1051,9 @@ describe('DataGrid DDL interactions', () => {
|
||||
await waitForEffects();
|
||||
|
||||
await act(async () => {
|
||||
findButton(renderer!, '字段信息').props.onClick();
|
||||
findButton(renderer!, '对象设计').props.onClick();
|
||||
});
|
||||
expect(textContent(renderer!.root)).toContain('FIELDS');
|
||||
expect(textContent(renderer!.root)).toContain('SCHEMA DESIGNER');
|
||||
|
||||
storeState.appearance.uiVersion = 'legacy';
|
||||
await act(async () => {
|
||||
@@ -1017,13 +1071,54 @@ describe('DataGrid DDL interactions', () => {
|
||||
await waitForEffects();
|
||||
|
||||
const content = textContent(renderer!.root);
|
||||
expect(content).not.toContain('FIELDS');
|
||||
expect(content).not.toContain('SCHEMA DESIGNER');
|
||||
expect(content).not.toContain('gn-v2-data-grid-fields-view');
|
||||
expect(content).toContain('数据预览');
|
||||
expect(content).toContain('结果视图');
|
||||
expect(content).toContain('字段信息');
|
||||
});
|
||||
|
||||
it('keeps the v2 fields tab as read-only field info for views', async () => {
|
||||
storeState.appearance.uiVersion = 'v2';
|
||||
backendApp.DBGetColumns.mockResolvedValueOnce({
|
||||
success: true,
|
||||
data: [
|
||||
{ name: 'id', type: 'bigint', key: '', nullable: 'NO', default: '', comment: '' },
|
||||
{ name: 'name', type: 'varchar(255)', key: '', nullable: 'YES', default: '', comment: '' },
|
||||
],
|
||||
});
|
||||
|
||||
let renderer: ReactTestRenderer;
|
||||
await act(async () => {
|
||||
renderer = create(
|
||||
<DataGrid
|
||||
data={[{ __gonavi_row_key__: 'row-1', id: 1, name: 'alpha' }]}
|
||||
columnNames={['id', 'name']}
|
||||
loading={false}
|
||||
tableName="user_view"
|
||||
dbName="main"
|
||||
connectionId="conn-1"
|
||||
objectType="view"
|
||||
/>,
|
||||
);
|
||||
});
|
||||
await waitForEffects();
|
||||
|
||||
expect(textContent(renderer!.root)).toContain('字段信息');
|
||||
expect(textContent(renderer!.root)).not.toContain('对象设计');
|
||||
|
||||
await act(async () => {
|
||||
findButton(renderer!, '字段信息').props.onClick();
|
||||
});
|
||||
|
||||
const content = textContent(renderer!.root);
|
||||
expect(content).toContain('FIELDS');
|
||||
expect(content).toContain('2 个字段');
|
||||
expect(content).toContain('id');
|
||||
expect(content).toContain('name');
|
||||
expect(content).not.toContain('SCHEMA DESIGNER');
|
||||
});
|
||||
|
||||
it('renders the v2 footer DDL view with the Monaco SQL editor', async () => {
|
||||
storeState.appearance.uiVersion = 'v2';
|
||||
backendApp.DBShowCreateTable.mockResolvedValueOnce({
|
||||
|
||||
@@ -79,6 +79,8 @@ describe('DataGrid layout', () => {
|
||||
columnNames={['id', 'name']}
|
||||
loading={false}
|
||||
tableName="users"
|
||||
dbName="main"
|
||||
connectionId="conn-1"
|
||||
readOnly
|
||||
pagination={{
|
||||
current: 1,
|
||||
@@ -95,6 +97,7 @@ describe('DataGrid layout', () => {
|
||||
expect(markup).toContain('data-grid-column-quick-find-action="true"');
|
||||
expect(markup).toContain('字段显示');
|
||||
expect(markup).toContain('跳列');
|
||||
expect(markup).toContain('对象设计');
|
||||
expect(markup).toContain('data-grid-page-find="true"');
|
||||
expect(markup).toContain('data-grid-page-find-prev="true"');
|
||||
expect(markup).toContain('data-grid-page-find-next="true"');
|
||||
@@ -112,6 +115,34 @@ describe('DataGrid layout', () => {
|
||||
expect(markup).toContain('当前页查找...');
|
||||
});
|
||||
|
||||
it('keeps the v2 footer fields action labeled as field info for views', () => {
|
||||
const markup = renderToStaticMarkup(
|
||||
<DataGrid
|
||||
data={[
|
||||
{
|
||||
__gonavi_row_key__: 'row-1',
|
||||
id: 1,
|
||||
name: 'alpha',
|
||||
},
|
||||
]}
|
||||
columnNames={['id', 'name']}
|
||||
loading={false}
|
||||
tableName="user_view"
|
||||
objectType="view"
|
||||
readOnly
|
||||
pagination={{
|
||||
current: 1,
|
||||
pageSize: 100,
|
||||
total: 1,
|
||||
}}
|
||||
onPageChange={() => {}}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(markup).toContain('字段信息');
|
||||
expect(markup).not.toContain('对象设计');
|
||||
});
|
||||
|
||||
it('hides current-page find in JSON and text record views', () => {
|
||||
const source = readFileSync(new URL('./DataGrid.tsx', import.meta.url), 'utf8');
|
||||
|
||||
@@ -321,6 +352,7 @@ describe('DataGrid layout', () => {
|
||||
|
||||
expect(tableMarkup).toContain('data-grid-ddl-action="true"');
|
||||
expect(tableMarkup).toContain('查看 DDL');
|
||||
expect(tableMarkup).toContain('对象设计');
|
||||
expect(tableMarkup).not.toContain('data-grid-locate-sidebar-action="true"');
|
||||
|
||||
const schemaTableMarkup = renderToStaticMarkup(
|
||||
@@ -342,6 +374,7 @@ describe('DataGrid layout', () => {
|
||||
|
||||
expect(schemaTableMarkup).toContain('data-grid-ddl-action="true"');
|
||||
expect(schemaTableMarkup).toContain('查看 DDL');
|
||||
expect(schemaTableMarkup).toContain('对象设计');
|
||||
expect(schemaTableMarkup).toContain('data-grid-page-find="true"');
|
||||
|
||||
const queryMarkup = renderToStaticMarkup(
|
||||
@@ -363,6 +396,8 @@ describe('DataGrid layout', () => {
|
||||
);
|
||||
|
||||
expect(queryMarkup).not.toContain('data-grid-ddl-action="true"');
|
||||
expect(queryMarkup).toContain('字段信息');
|
||||
expect(queryMarkup).not.toContain('对象设计');
|
||||
});
|
||||
|
||||
it('keeps row copy and paste as context menu actions instead of toolbar buttons', () => {
|
||||
|
||||
@@ -129,6 +129,7 @@ import DataGridPreviewPanel from './DataGridPreviewPanel';
|
||||
import { DataGridJsonView, DataGridTextView } from './DataGridRecordViews';
|
||||
import { DataGridV2DdlSideWorkspace, DataGridV2DdlView } from './DataGridV2DdlWorkspace';
|
||||
import { DataGridV2ErView, DataGridV2FieldsView } from './DataGridV2MetadataViews';
|
||||
import TableDesigner from './TableDesigner';
|
||||
import { useDataGridFilters } from './useDataGridFilters';
|
||||
import { useDataGridDdlView } from './useDataGridDdlView';
|
||||
import { useDataGridModalEditors } from './useDataGridModalEditors';
|
||||
@@ -1191,6 +1192,7 @@ interface DataGridProps {
|
||||
columnNames: string[];
|
||||
loading: boolean;
|
||||
tableName?: string;
|
||||
objectType?: 'table' | 'view' | 'materialized-view';
|
||||
exportScope?: 'table' | 'queryResult';
|
||||
resultSql?: string;
|
||||
dbName?: string;
|
||||
@@ -1482,7 +1484,7 @@ const VIRTUAL_EDITING_CELL_STYLE: React.CSSProperties = {
|
||||
};
|
||||
|
||||
const DataGrid: React.FC<DataGridProps> = ({
|
||||
data, columnNames, loading, tableName, exportScope = 'table', dbName, connectionId, pkColumns = [], editLocator, readOnly = false,
|
||||
data, columnNames, loading, tableName, objectType = 'table', exportScope = 'table', dbName, connectionId, pkColumns = [], editLocator, readOnly = false,
|
||||
onReload, onSort, onPageChange, pagination, onRequestTotalCount, onCancelTotalCount, sortInfoExternal, showFilter, onToggleFilter, exportSqlWithFilter, onApplyFilter, appliedFilterConditions, quickWhereCondition,
|
||||
onApplyQuickWhereCondition,
|
||||
scrollSnapshot, onScrollSnapshotChange
|
||||
@@ -1720,6 +1722,7 @@ const DataGrid: React.FC<DataGridProps> = ({
|
||||
const canImport = exportScope === 'table' && !!tableName;
|
||||
const canExport = !!connectionId && (isQueryResultExport || !!tableName);
|
||||
const canViewDdl = exportScope === 'table' && !!connectionId && !!tableName;
|
||||
const canOpenObjectDesigner = exportScope === 'table' && objectType === 'table' && !!connectionId && !!tableName;
|
||||
const filteredExportSql = useMemo(() => String(exportSqlWithFilter || '').trim(), [exportSqlWithFilter]);
|
||||
const hasFilteredExportSql = exportScope === 'table' && filteredExportSql.length > 0;
|
||||
|
||||
@@ -2357,6 +2360,7 @@ const DataGrid: React.FC<DataGridProps> = ({
|
||||
connectionId,
|
||||
dbName: targetDbName,
|
||||
tableName: refTableName,
|
||||
objectType: 'table',
|
||||
});
|
||||
}, [addTab, connectionId, dbName, setActiveContext]);
|
||||
|
||||
@@ -3225,6 +3229,22 @@ const DataGrid: React.FC<DataGridProps> = ({
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const handleExternalViewModeChange = (event: Event) => {
|
||||
const detail = (event as CustomEvent<any>)?.detail || {};
|
||||
if (String(detail.connectionId || '') !== String(connectionId || '')) return;
|
||||
if (String(detail.dbName || '') !== String(dbName || '')) return;
|
||||
if (String(detail.tableName || '') !== String(tableName || '')) return;
|
||||
const nextMode = String(detail.viewMode || '').trim();
|
||||
if (!nextMode) return;
|
||||
if (!['table', 'json', 'text', 'fields', 'ddl', 'er'].includes(nextMode)) return;
|
||||
handleViewModeChange(nextMode as GridViewMode);
|
||||
};
|
||||
|
||||
window.addEventListener('gonavi:data-grid:set-view-mode', handleExternalViewModeChange as EventListener);
|
||||
return () => window.removeEventListener('gonavi:data-grid:set-view-mode', handleExternalViewModeChange as EventListener);
|
||||
}, [canOpenObjectDesigner, connectionId, dbName, handleViewModeChange, tableName]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isTableSurfaceActive || !isV2Ui || !cellContextMenu.visible) return;
|
||||
const portal = cellContextMenuPortalRef.current;
|
||||
@@ -7542,14 +7562,31 @@ const DataGrid: React.FC<DataGridProps> = ({
|
||||
{viewMode === 'table' ? (
|
||||
renderDataTableView()
|
||||
) : isV2Ui && viewMode === 'fields' ? (
|
||||
<DataGridV2FieldsView
|
||||
tableName={tableName}
|
||||
displayOutputColumnNames={displayOutputColumnNames}
|
||||
pkColumns={pkColumns}
|
||||
locatorColumns={effectiveEditLocator?.columns}
|
||||
columnMetaMap={columnMetaMap}
|
||||
columnMetaMapByLowerName={columnMetaMapByLowerName}
|
||||
/>
|
||||
canOpenObjectDesigner ? (
|
||||
<TableDesigner
|
||||
embedded
|
||||
tab={{
|
||||
id: `embedded-design-${connectionId || ''}-${dbName || ''}-${tableName || ''}`,
|
||||
title: `设计表 (${tableName || ''})`,
|
||||
type: 'design',
|
||||
connectionId: String(connectionId || ''),
|
||||
dbName,
|
||||
tableName,
|
||||
initialTab: 'columns',
|
||||
readOnly,
|
||||
objectType: 'table',
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<DataGridV2FieldsView
|
||||
tableName={tableName}
|
||||
displayOutputColumnNames={displayOutputColumnNames}
|
||||
pkColumns={pkColumns}
|
||||
locatorColumns={effectiveEditLocator?.columns}
|
||||
columnMetaMap={columnMetaMap}
|
||||
columnMetaMapByLowerName={columnMetaMapByLowerName}
|
||||
/>
|
||||
)
|
||||
) : isV2Ui && viewMode === 'ddl' && ddlViewLayout === 'side' ? (
|
||||
<DataGridV2DdlSideWorkspace
|
||||
tableContent={renderDataTableView()}
|
||||
@@ -7759,6 +7796,7 @@ const DataGrid: React.FC<DataGridProps> = ({
|
||||
<DataGridSecondaryActions
|
||||
isV2Ui={isV2Ui}
|
||||
canViewDdl={canViewDdl}
|
||||
canOpenObjectDesigner={canOpenObjectDesigner}
|
||||
viewMode={viewMode}
|
||||
ddlLoading={ddlLoading}
|
||||
showColumnComment={showColumnComment}
|
||||
|
||||
@@ -14,6 +14,7 @@ type GridViewMode = 'table' | 'json' | 'text' | 'fields' | 'ddl' | 'er';
|
||||
export interface DataGridSecondaryActionsProps {
|
||||
isV2Ui: boolean;
|
||||
canViewDdl: boolean;
|
||||
canOpenObjectDesigner: boolean;
|
||||
viewMode: GridViewMode;
|
||||
ddlLoading: boolean;
|
||||
showColumnComment: boolean;
|
||||
@@ -35,6 +36,7 @@ export interface DataGridSecondaryActionsProps {
|
||||
const DataGridSecondaryActions: React.FC<DataGridSecondaryActionsProps> = ({
|
||||
isV2Ui,
|
||||
canViewDdl,
|
||||
canOpenObjectDesigner,
|
||||
viewMode,
|
||||
ddlLoading,
|
||||
showColumnComment,
|
||||
@@ -53,9 +55,11 @@ const DataGridSecondaryActions: React.FC<DataGridSecondaryActionsProps> = ({
|
||||
onOpenTableDdl,
|
||||
}) => {
|
||||
if (isV2Ui) {
|
||||
const fieldsActionLabel = canOpenObjectDesigner ? '对象设计' : '字段信息';
|
||||
const fieldsActionIcon = canOpenObjectDesigner ? <EditOutlined /> : <FileTextOutlined />;
|
||||
const viewTabItems: Array<{ key: GridViewMode; label: string; icon: React.ReactNode; disabled?: boolean }> = [
|
||||
{ key: 'table', label: '数据预览', icon: <TableOutlined /> },
|
||||
{ key: 'fields', label: '字段信息', icon: <FileTextOutlined /> },
|
||||
{ key: 'fields', label: fieldsActionLabel, icon: fieldsActionIcon },
|
||||
{ key: 'ddl', label: '查看 DDL', icon: <ConsoleSqlOutlined />, disabled: !canViewDdl },
|
||||
{ key: 'er', label: 'ER 图', icon: <LinkOutlined /> },
|
||||
];
|
||||
|
||||
@@ -1095,6 +1095,7 @@ const DataViewer: React.FC<{ tab: TabData; isActive?: boolean }> = React.memo(({
|
||||
columnNames={columnNames}
|
||||
loading={loading}
|
||||
tableName={tab.tableName}
|
||||
objectType={tab.objectType || 'table'}
|
||||
exportScope="table"
|
||||
dbName={tab.dbName}
|
||||
connectionId={tab.connectionId}
|
||||
|
||||
@@ -2726,6 +2726,7 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
|
||||
connectionId,
|
||||
dbName: targetDbName,
|
||||
tableName: targetTableName,
|
||||
objectType: 'table',
|
||||
});
|
||||
dispatchQueryEditorSidebarLocate({
|
||||
connectionId,
|
||||
|
||||
@@ -3415,6 +3415,7 @@ const Sidebar: React.FC<{
|
||||
connectionId: id,
|
||||
dbName,
|
||||
tableName,
|
||||
objectType: 'table',
|
||||
});
|
||||
return;
|
||||
} else if (node.type === 'view' || node.type === 'materialized-view') {
|
||||
@@ -3426,6 +3427,7 @@ const Sidebar: React.FC<{
|
||||
connectionId: id,
|
||||
dbName,
|
||||
tableName: viewName,
|
||||
objectType: node.type === 'materialized-view' ? 'materialized-view' : 'view',
|
||||
});
|
||||
return;
|
||||
} else if (node.type === 'saved-query') {
|
||||
|
||||
@@ -360,7 +360,23 @@ const SortableRow = ({ children, ...props }: RowProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
const TableDesigner: React.FC<{ tab: TabData }> = ({ tab }) => {
|
||||
const renderDesignerCellField = (content: React.ReactNode, className?: string) => (
|
||||
<div className={`table-designer-cell-field${className ? ` ${className}` : ''}`}>
|
||||
{content}
|
||||
</div>
|
||||
);
|
||||
|
||||
const renderDesignerCellCheck = (content: React.ReactNode, className?: string) => (
|
||||
<div className={`table-designer-cell-check${className ? ` ${className}` : ''}`}>
|
||||
{content}
|
||||
</div>
|
||||
);
|
||||
|
||||
const renderDesignerHeaderTitle = (title: string) => (
|
||||
<span className="table-designer-header-title">{title}</span>
|
||||
);
|
||||
|
||||
const TableDesigner: React.FC<{ tab: TabData; embedded?: boolean }> = ({ tab, embedded = false }) => {
|
||||
const isNewTable = !tab.tableName;
|
||||
|
||||
const [columns, setColumns] = useState<EditableColumn[]>([]);
|
||||
@@ -431,6 +447,7 @@ const TableDesigner: React.FC<{ tab: TabData }> = ({ tab }) => {
|
||||
const [commentEditorColumnKey, setCommentEditorColumnKey] = useState('');
|
||||
const [commentEditorColumnName, setCommentEditorColumnName] = useState('');
|
||||
const [commentEditorValue, setCommentEditorValue] = useState('');
|
||||
const [inlineCommentEditingKey, setInlineCommentEditingKey] = useState('');
|
||||
|
||||
const connections = useStore(state => state.connections);
|
||||
const theme = useStore(state => state.theme);
|
||||
@@ -439,9 +456,6 @@ const TableDesigner: React.FC<{ tab: TabData }> = ({ tab }) => {
|
||||
const isV2Ui = appearance.uiVersion === 'v2';
|
||||
const resizeGuideColor = darkMode ? '#f6c453' : '#1890ff';
|
||||
const readOnly = !!tab.readOnly;
|
||||
const designerTableTitle = tab.tableName || newTableName || '未命名表';
|
||||
const designerDbTitle = tab.dbName || '默认库';
|
||||
const designerColumnSummary = `${columns.length} 字段`;
|
||||
const panelRadius = 10;
|
||||
const panelFrameColor = darkMode ? 'rgba(0, 0, 0, 0.18)' : 'rgba(0, 0, 0, 0.12)';
|
||||
const panelToolbarBorder = darkMode ? 'rgba(255, 255, 255, 0.12)' : 'rgba(0, 0, 0, 0.10)';
|
||||
@@ -458,6 +472,7 @@ const TableDesigner: React.FC<{ tab: TabData }> = ({ tab }) => {
|
||||
|
||||
const openCommentEditor = useCallback((record: EditableColumn) => {
|
||||
if (!record?._key) return;
|
||||
setInlineCommentEditingKey('');
|
||||
setCommentEditorColumnKey(record._key);
|
||||
setCommentEditorColumnName(record.name || '');
|
||||
setCommentEditorValue(record.comment || '');
|
||||
@@ -518,6 +533,10 @@ const TableDesigner: React.FC<{ tab: TabData }> = ({ tab }) => {
|
||||
setSelectedColumnRowKeys(prev => prev.filter(key => columns.some(c => c._key === key)));
|
||||
}, [columns]);
|
||||
|
||||
useEffect(() => {
|
||||
setInlineCommentEditingKey(prev => (prev && columns.some(c => c._key === prev) ? prev : ''));
|
||||
}, [columns]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (focusHighlightTimerRef.current !== null) {
|
||||
@@ -552,6 +571,15 @@ const TableDesigner: React.FC<{ tab: TabData }> = ({ tab }) => {
|
||||
return true;
|
||||
}, [activeKey, readOnly]);
|
||||
|
||||
const startInlineCommentEdit = useCallback((record: EditableColumn) => {
|
||||
if (readOnly || !record?._key) return;
|
||||
setInlineCommentEditingKey(record._key);
|
||||
}, [readOnly]);
|
||||
|
||||
const finishInlineCommentEdit = useCallback(() => {
|
||||
setInlineCommentEditingKey('');
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const pendingKey = pendingFocusColumnKeyRef.current;
|
||||
if (!pendingKey || activeKey !== 'columns') return;
|
||||
@@ -578,64 +606,80 @@ const TableDesigner: React.FC<{ tab: TabData }> = ({ tab }) => {
|
||||
const columnTypeOptions = resolveColumnTypeOptions(getDbType());
|
||||
const initialCols = [
|
||||
{
|
||||
title: '名',
|
||||
title: renderDesignerHeaderTitle('名'),
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
width: 180,
|
||||
render: (text: string, record: EditableColumn) => readOnly ? text : (
|
||||
<Input {...noAutoCapInputProps} value={text} onChange={e => handleColumnChange(record._key, 'name', e.target.value)} variant="borderless" />
|
||||
renderDesignerCellField(
|
||||
<Input {...noAutoCapInputProps} value={text} onChange={e => handleColumnChange(record._key, 'name', e.target.value)} variant="borderless" />
|
||||
)
|
||||
)
|
||||
},
|
||||
{
|
||||
title: '类型',
|
||||
title: renderDesignerHeaderTitle('类型'),
|
||||
dataIndex: 'type',
|
||||
key: 'type',
|
||||
width: 150,
|
||||
render: (text: string, record: EditableColumn) => readOnly ? text : (
|
||||
<AutoComplete options={columnTypeOptions} value={text} onChange={val => handleColumnChange(record._key, 'type', val)} style={{ width: '100%' }} variant="borderless" />
|
||||
renderDesignerCellField(
|
||||
<AutoComplete options={columnTypeOptions} value={text} onChange={val => handleColumnChange(record._key, 'type', val)} style={{ width: '100%' }} variant="borderless" />,
|
||||
'is-compact'
|
||||
)
|
||||
)
|
||||
},
|
||||
{
|
||||
title: '主键',
|
||||
title: renderDesignerHeaderTitle('主键'),
|
||||
dataIndex: 'key',
|
||||
key: 'key',
|
||||
width: 60,
|
||||
align: 'center',
|
||||
render: (text: string, record: EditableColumn) => (
|
||||
<Checkbox checked={text === 'PRI'} disabled={readOnly} onChange={e => handleColumnChange(record._key, 'key', e.target.checked ? 'PRI' : '')} />
|
||||
renderDesignerCellCheck(
|
||||
<Checkbox checked={text === 'PRI'} disabled={readOnly} onChange={e => handleColumnChange(record._key, 'key', e.target.checked ? 'PRI' : '')} />,
|
||||
'is-left-aligned'
|
||||
)
|
||||
)
|
||||
},
|
||||
{
|
||||
title: '自增',
|
||||
title: renderDesignerHeaderTitle('自增'),
|
||||
dataIndex: 'isAutoIncrement',
|
||||
key: 'isAutoIncrement',
|
||||
width: 60,
|
||||
align: 'center',
|
||||
render: (val: boolean, record: EditableColumn) => (
|
||||
<Checkbox checked={val} disabled={readOnly} onChange={e => handleColumnChange(record._key, 'isAutoIncrement', e.target.checked)} />
|
||||
renderDesignerCellCheck(
|
||||
<Checkbox checked={val} disabled={readOnly} onChange={e => handleColumnChange(record._key, 'isAutoIncrement', e.target.checked)} />,
|
||||
'is-left-aligned'
|
||||
)
|
||||
)
|
||||
},
|
||||
{
|
||||
title: '不是 Null',
|
||||
title: renderDesignerHeaderTitle('不是 Null'),
|
||||
dataIndex: 'nullable',
|
||||
key: 'nullable',
|
||||
width: 80,
|
||||
align: 'center',
|
||||
render: (text: string, record: EditableColumn) => (
|
||||
<Checkbox checked={text === 'NO'} disabled={readOnly || record.key === 'PRI'} onChange={e => handleColumnChange(record._key, 'nullable', e.target.checked ? 'NO' : 'YES')} />
|
||||
renderDesignerCellCheck(
|
||||
<Checkbox checked={text === 'NO'} disabled={readOnly || record.key === 'PRI'} onChange={e => handleColumnChange(record._key, 'nullable', e.target.checked ? 'NO' : 'YES')} />,
|
||||
'is-left-aligned'
|
||||
)
|
||||
)
|
||||
},
|
||||
{
|
||||
title: '默认',
|
||||
title: renderDesignerHeaderTitle('默认'),
|
||||
dataIndex: 'default',
|
||||
key: 'default',
|
||||
width: 180, // Increased default width
|
||||
render: (text: string, record: EditableColumn) => readOnly ? text : (
|
||||
<AutoComplete options={COMMON_DEFAULTS} value={text} onChange={val => handleColumnChange(record._key, 'default', val)} style={{ width: '100%' }} variant="borderless" placeholder="NULL" />
|
||||
renderDesignerCellField(
|
||||
<AutoComplete options={COMMON_DEFAULTS} value={text} onChange={val => handleColumnChange(record._key, 'default', val)} style={{ width: '100%' }} variant="borderless" placeholder="NULL" />
|
||||
)
|
||||
)
|
||||
},
|
||||
{
|
||||
title: '注释',
|
||||
title: renderDesignerHeaderTitle('注释'),
|
||||
dataIndex: 'comment',
|
||||
key: 'comment',
|
||||
width: 200,
|
||||
@@ -644,13 +688,26 @@ const TableDesigner: React.FC<{ tab: TabData }> = ({ tab }) => {
|
||||
<div style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{text || ''}</div>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
|
||||
<Input
|
||||
value={text}
|
||||
onChange={e => handleColumnChange(record._key, 'comment', e.target.value)}
|
||||
onDoubleClick={() => openCommentEditor(record)}
|
||||
variant="borderless"
|
||||
/>
|
||||
<div className="table-designer-cell-field table-designer-comment-field">
|
||||
{inlineCommentEditingKey !== record._key ? (
|
||||
<Tooltip title={text || ''}>
|
||||
<div
|
||||
className={`table-designer-comment-display${text ? '' : ' is-empty'}`}
|
||||
onDoubleClick={() => startInlineCommentEdit(record)}
|
||||
>
|
||||
{text || '\u00A0'}
|
||||
</div>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Input
|
||||
value={text}
|
||||
onChange={e => handleColumnChange(record._key, 'comment', e.target.value)}
|
||||
onBlur={finishInlineCommentEdit}
|
||||
onPressEnter={finishInlineCommentEdit}
|
||||
autoFocus={inlineCommentEditingKey === record._key}
|
||||
variant="borderless"
|
||||
/>
|
||||
)}
|
||||
<Tooltip title="弹框编辑注释">
|
||||
<Button
|
||||
type="text"
|
||||
@@ -663,16 +720,25 @@ const TableDesigner: React.FC<{ tab: TabData }> = ({ tab }) => {
|
||||
)
|
||||
},
|
||||
...(readOnly ? [] : [{
|
||||
title: '操作',
|
||||
title: renderDesignerHeaderTitle('操作'),
|
||||
key: 'action',
|
||||
width: 60,
|
||||
width: 92,
|
||||
className: 'table-designer-action-column',
|
||||
onHeaderCell: () => ({ className: 'table-designer-action-column' }),
|
||||
render: (_: any, record: EditableColumn) => (
|
||||
<Button type="text" danger icon={<DeleteOutlined />} onClick={() => handleDeleteColumn(record._key)} />
|
||||
<div className="table-designer-action-cell">
|
||||
<Tooltip title="编辑注释">
|
||||
<Button type="text" size="small" icon={<EditOutlined />} onClick={() => openCommentEditor(record)} />
|
||||
</Tooltip>
|
||||
<Tooltip title="删除字段">
|
||||
<Button type="text" size="small" danger icon={<DeleteOutlined />} onClick={() => handleDeleteColumn(record._key)} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
)
|
||||
}])
|
||||
];
|
||||
setTableColumns(initialCols);
|
||||
}, [connections, openCommentEditor, readOnly, tab.connectionId]); // Re-create when datasource dialect or readonly state changes
|
||||
}, [connections, embedded, finishInlineCommentEdit, inlineCommentEditingKey, openCommentEditor, readOnly, startInlineCommentEdit, tab.connectionId]); // Re-create when datasource dialect or readonly state changes
|
||||
|
||||
const flushResizeGhost = useCallback(() => {
|
||||
resizeRafRef.current = null;
|
||||
@@ -2211,29 +2277,36 @@ END;`;
|
||||
|
||||
const columnSelectCol = useMemo(() => ({
|
||||
title: () => (
|
||||
<Checkbox
|
||||
checked={isAllColumnsSelected}
|
||||
indeterminate={isColumnsIndeterminate}
|
||||
onChange={(e: any) => setSelectedColumnRowKeys(e.target.checked ? allColumnKeys : [])}
|
||||
style={{ margin: 0 }}
|
||||
/>
|
||||
<div className="table-designer-select-check">
|
||||
<Checkbox
|
||||
checked={isAllColumnsSelected}
|
||||
indeterminate={isColumnsIndeterminate}
|
||||
onChange={(e: any) => setSelectedColumnRowKeys(e.target.checked ? allColumnKeys : [])}
|
||||
style={{ margin: 0 }}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
dataIndex: '_select',
|
||||
key: '_select',
|
||||
width: 48,
|
||||
width: 44,
|
||||
className: 'table-designer-select-column',
|
||||
onHeaderCell: () => ({ className: 'table-designer-select-column' }),
|
||||
onCell: () => ({ className: 'table-designer-select-column' }),
|
||||
render: (_: any, record: any) => (
|
||||
<Checkbox
|
||||
checked={selectedColumnRowKeys.includes(record._key)}
|
||||
onChange={(e: any) => {
|
||||
e.stopPropagation();
|
||||
setSelectedColumnRowKeys((prev: string[]) =>
|
||||
e.target.checked
|
||||
? [...prev, record._key]
|
||||
: prev.filter((k: string) => k !== record._key)
|
||||
);
|
||||
}}
|
||||
style={{ margin: 0 }}
|
||||
/>
|
||||
<div className="table-designer-select-check">
|
||||
<Checkbox
|
||||
checked={selectedColumnRowKeys.includes(record._key)}
|
||||
onChange={(e: any) => {
|
||||
e.stopPropagation();
|
||||
setSelectedColumnRowKeys((prev: string[]) =>
|
||||
e.target.checked
|
||||
? [...prev, record._key]
|
||||
: prev.filter((k: string) => k !== record._key)
|
||||
);
|
||||
}}
|
||||
style={{ margin: 0 }}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
}), [selectedColumnRowKeys, allColumnKeys, isAllColumnsSelected, isColumnsIndeterminate]);
|
||||
|
||||
@@ -2331,6 +2404,9 @@ END;`;
|
||||
dataIndex: '_select',
|
||||
key: '_select',
|
||||
width: 48,
|
||||
className: 'table-designer-select-column',
|
||||
onHeaderCell: () => ({ className: 'table-designer-select-column' }),
|
||||
onCell: () => ({ className: 'table-designer-select-column' }),
|
||||
render: (_: any, record: any) => (
|
||||
<span
|
||||
onClick={(e) => {
|
||||
@@ -2548,7 +2624,11 @@ END;`;
|
||||
);
|
||||
|
||||
return (
|
||||
<div ref={shellRef} className={`table-designer-shell${isV2Ui ? ' gn-v2-table-designer' : ''}`} style={{ display: 'flex', flexDirection: 'column', height: '100%', minHeight: 0, padding: '6px 0', position: 'relative' }}>
|
||||
<div
|
||||
ref={shellRef}
|
||||
className={`table-designer-shell${isV2Ui ? ' gn-v2-table-designer' : ''}${embedded ? ' is-embedded' : ''}`}
|
||||
style={{ display: 'flex', flexDirection: 'column', height: '100%', minHeight: 0, padding: embedded ? 0 : '6px 0', position: 'relative' }}
|
||||
>
|
||||
<style>{`
|
||||
.table-designer-shell .ant-table,
|
||||
.table-designer-shell .ant-table-wrapper,
|
||||
@@ -2580,6 +2660,116 @@ END;`;
|
||||
.table-designer-shell .ant-table-tbody td .ant-select .ant-select-selector {
|
||||
padding-left: 0 !important;
|
||||
}
|
||||
.table-designer-shell .table-designer-cell-field {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 34px;
|
||||
padding: 0 10px;
|
||||
border: 1px solid ${darkMode ? 'rgba(255,255,255,0.10)' : 'rgba(0,0,0,0.10)'};
|
||||
border-radius: 10px;
|
||||
background: ${darkMode ? 'rgba(255,255,255,0.02)' : 'rgba(255,255,255,0.72)'};
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.table-designer-shell .table-designer-cell-field .ant-input,
|
||||
.table-designer-shell .table-designer-cell-field .ant-select,
|
||||
.table-designer-shell .table-designer-cell-field .ant-select-selector,
|
||||
.table-designer-shell .table-designer-cell-field .ant-select-selection-search,
|
||||
.table-designer-shell .table-designer-cell-field .ant-select-selection-item {
|
||||
background: transparent !important;
|
||||
}
|
||||
.table-designer-shell .table-designer-cell-field .ant-input,
|
||||
.table-designer-shell .table-designer-cell-field .ant-select-selection-item,
|
||||
.table-designer-shell .table-designer-cell-field input {
|
||||
font-size: 13px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.table-designer-shell .table-designer-cell-field .ant-select {
|
||||
width: 100%;
|
||||
}
|
||||
.table-designer-shell .table-designer-cell-field .ant-select-selector,
|
||||
.table-designer-shell .table-designer-cell-field .ant-input {
|
||||
padding: 0 !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
.table-designer-shell .table-designer-cell-field.is-compact {
|
||||
padding-right: 6px;
|
||||
}
|
||||
.table-designer-shell .table-designer-cell-check {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
min-height: 34px;
|
||||
}
|
||||
.table-designer-shell .table-designer-cell-check .ant-checkbox-wrapper {
|
||||
margin-inline-end: 0 !important;
|
||||
}
|
||||
.table-designer-shell .table-designer-cell-check.is-left-aligned {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
.table-designer-shell .table-designer-header-title {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
width: 100%;
|
||||
line-height: 1.1;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.table-designer-shell .table-designer-select-check {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 28px;
|
||||
}
|
||||
.table-designer-shell .table-designer-select-check .ant-checkbox-wrapper {
|
||||
margin-inline-end: 0 !important;
|
||||
}
|
||||
.table-designer-shell .table-designer-select-column {
|
||||
text-align: center !important;
|
||||
vertical-align: middle !important;
|
||||
}
|
||||
.table-designer-shell .table-designer-action-column {
|
||||
text-align: left !important;
|
||||
}
|
||||
.table-designer-shell .table-designer-comment-field {
|
||||
gap: 4px;
|
||||
padding-right: 4px;
|
||||
}
|
||||
.table-designer-shell .table-designer-comment-field .ant-input {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.table-designer-shell .table-designer-comment-display {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
min-height: 28px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font: inherit;
|
||||
line-height: 1.4;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
cursor: text;
|
||||
}
|
||||
.table-designer-shell .table-designer-comment-display.is-empty {
|
||||
color: ${darkMode ? 'rgba(255,255,255,0.28)' : 'rgba(0,0,0,0.28)'};
|
||||
}
|
||||
.table-designer-shell .table-designer-action-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
width: 100%;
|
||||
}
|
||||
.table-designer-shell .table-designer-action-cell .ant-btn {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
padding: 0;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.table-designer-shell .ant-table-thead > tr > th::before {
|
||||
display: none !important;
|
||||
}
|
||||
@@ -2595,6 +2785,12 @@ END;`;
|
||||
.table-designer-shell .ant-tabs-nav {
|
||||
margin-bottom: 8px !important;
|
||||
}
|
||||
.table-designer-shell.gn-v2-table-designer .ant-tabs-nav {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
.table-designer-shell.is-embedded .ant-tabs-nav {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
.table-designer-shell .ant-tabs-nav::before {
|
||||
border-bottom-color: ${darkMode ? 'rgba(255,255,255,0.08)' : 'rgba(0,0,0,0.08)'} !important;
|
||||
}
|
||||
@@ -2605,6 +2801,100 @@ END;`;
|
||||
.table-designer-shell .ant-tabs-tab {
|
||||
transition: color 0.15s ease !important;
|
||||
}
|
||||
.table-designer-shell.gn-v2-table-designer .ant-tabs-nav-wrap,
|
||||
.table-designer-shell.gn-v2-table-designer .ant-tabs-nav-list {
|
||||
width: auto !important;
|
||||
min-height: 34px !important;
|
||||
align-items: center !important;
|
||||
}
|
||||
.table-designer-shell.gn-v2-table-designer .ant-tabs-tab {
|
||||
width: auto !important;
|
||||
min-width: 0 !important;
|
||||
max-width: none !important;
|
||||
min-height: 34px !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 12px !important;
|
||||
border-right: 0 !important;
|
||||
border-bottom: 0 !important;
|
||||
white-space: nowrap !important;
|
||||
}
|
||||
.table-designer-shell.gn-v2-table-designer .ant-tabs-tab-btn {
|
||||
width: auto !important;
|
||||
display: inline-flex !important;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
}
|
||||
.table-designer-shell.is-embedded .ant-tabs-nav-wrap,
|
||||
.table-designer-shell.is-embedded .ant-tabs-nav-list {
|
||||
width: auto !important;
|
||||
min-height: 34px !important;
|
||||
align-items: center !important;
|
||||
}
|
||||
.table-designer-shell.is-embedded .ant-tabs-tab {
|
||||
width: auto !important;
|
||||
min-width: 0 !important;
|
||||
max-width: none !important;
|
||||
min-height: 34px !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 12px !important;
|
||||
border-right: 0 !important;
|
||||
border-bottom: 0 !important;
|
||||
white-space: nowrap !important;
|
||||
}
|
||||
.table-designer-shell.is-embedded .ant-tabs-tab-btn {
|
||||
width: auto !important;
|
||||
display: inline-flex !important;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
}
|
||||
.table-designer-shell.gn-v2-table-designer .table-designer-cell-field {
|
||||
min-height: 28px;
|
||||
padding-inline: 0;
|
||||
border: none !important;
|
||||
border-radius: 0;
|
||||
background: transparent !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
.table-designer-shell.gn-v2-table-designer .table-designer-cell-field .ant-input,
|
||||
.table-designer-shell.gn-v2-table-designer .table-designer-cell-field .ant-input:focus,
|
||||
.table-designer-shell.gn-v2-table-designer .table-designer-cell-field .ant-input-focused,
|
||||
.table-designer-shell.gn-v2-table-designer .table-designer-cell-field .ant-select-selector,
|
||||
.table-designer-shell.gn-v2-table-designer .table-designer-cell-field .ant-select-focused .ant-select-selector {
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
.table-designer-shell.gn-v2-table-designer .table-designer-comment-display,
|
||||
.table-designer-shell.gn-v2-table-designer .table-designer-comment-field .ant-input,
|
||||
.table-designer-shell.gn-v2-table-designer .table-designer-comment-field .ant-input input {
|
||||
font-size: 12px !important;
|
||||
line-height: 1.4 !important;
|
||||
font-family: inherit !important;
|
||||
}
|
||||
.table-designer-shell.gn-v2-table-designer .table-designer-cell-field.is-compact {
|
||||
padding-right: 0;
|
||||
}
|
||||
.table-designer-shell.gn-v2-table-designer .table-designer-comment-field {
|
||||
padding-right: 0;
|
||||
}
|
||||
.table-designer-shell.gn-v2-table-designer .table-designer-cell-check {
|
||||
min-height: 30px;
|
||||
}
|
||||
.table-designer-shell.gn-v2-table-designer .table-designer-select-check {
|
||||
min-height: 22px;
|
||||
}
|
||||
.table-designer-shell.is-embedded .table-designer-select-check {
|
||||
min-height: 14px !important;
|
||||
}
|
||||
.table-designer-shell.gn-v2-table-designer .table-designer-action-cell {
|
||||
justify-content: flex-start;
|
||||
gap: 4px;
|
||||
}
|
||||
.table-designer-shell.gn-v2-table-designer .table-designer-action-cell .ant-btn {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
border-radius: 7px;
|
||||
}
|
||||
.table-designer-shell .ant-tabs-content-holder,
|
||||
.table-designer-shell .ant-tabs-content,
|
||||
.table-designer-shell .ant-tabs-tabpane {
|
||||
@@ -2639,29 +2929,16 @@ END;`;
|
||||
willChange: 'transform',
|
||||
}}
|
||||
/>
|
||||
{isV2Ui && (
|
||||
<div className="gn-v2-designer-header">
|
||||
<div className="gn-v2-designer-title">
|
||||
<span>SCHEMA DESIGNER</span>
|
||||
<strong>{designerTableTitle}</strong>
|
||||
</div>
|
||||
<div className="gn-v2-designer-meta">
|
||||
<span><TableOutlined /> {designerDbTitle}</span>
|
||||
<span>{designerColumnSummary}</span>
|
||||
{readOnly && <span>只读</span>}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={isV2Ui ? 'gn-v2-designer-toolbar' : undefined}
|
||||
style={{
|
||||
padding: '10px 12px 8px 12px',
|
||||
borderBottom: `1px solid ${panelToolbarBorder}`,
|
||||
borderTopLeftRadius: panelRadius,
|
||||
borderTopRightRadius: panelRadius,
|
||||
borderTopLeftRadius: embedded ? 0 : panelRadius,
|
||||
borderTopRightRadius: embedded ? 0 : panelRadius,
|
||||
borderLeft: `1px solid ${panelFrameColor}`,
|
||||
borderRight: `1px solid ${panelFrameColor}`,
|
||||
borderTop: `1px solid ${panelFrameColor}`,
|
||||
borderTop: embedded ? 'none' : `1px solid ${panelFrameColor}`,
|
||||
background: panelToolbarBg,
|
||||
display: 'flex',
|
||||
gap: '8px',
|
||||
@@ -2731,9 +3008,9 @@ END;`;
|
||||
style={{
|
||||
flex: 1,
|
||||
minHeight: 0,
|
||||
padding: '8px 10px 10px 10px',
|
||||
borderBottomLeftRadius: panelRadius,
|
||||
borderBottomRightRadius: panelRadius,
|
||||
padding: embedded ? 0 : '0 10px 10px 10px',
|
||||
borderBottomLeftRadius: embedded ? 0 : panelRadius,
|
||||
borderBottomRightRadius: embedded ? 0 : panelRadius,
|
||||
borderLeft: `1px solid ${panelFrameColor}`,
|
||||
borderRight: `1px solid ${panelFrameColor}`,
|
||||
borderBottom: `1px solid ${panelFrameColor}`,
|
||||
@@ -2757,7 +3034,7 @@ END;`;
|
||||
key: 'indexes',
|
||||
label: '索引',
|
||||
children: (
|
||||
<div className={`index-table-wrap${isV2Ui ? ' gn-v2-designer-tab-content' : ''}`} style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
||||
<div className={`index-table-wrap${isV2Ui ? ' gn-v2-designer-tab-content gn-v2-designer-index-table' : ''}`} style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
||||
{!readOnly && (
|
||||
<div className={isV2Ui ? 'gn-v2-designer-actionbar' : undefined} style={{ display: 'flex', gap: 8 }}>
|
||||
<Button size="small" icon={<PlusOutlined />} disabled={!supportsIndexSchemaOps()} onClick={openCreateIndexModal}>新增</Button>
|
||||
@@ -2935,11 +3212,11 @@ END;`;
|
||||
}
|
||||
] : []),
|
||||
...(!isNewTable ? [{
|
||||
key: 'ddl',
|
||||
label: 'DDL',
|
||||
icon: <FileTextOutlined />,
|
||||
children: (
|
||||
<div className={isV2Ui ? 'gn-v2-designer-ddl-shell' : undefined} style={{ height: 'calc(100vh - 200px)', border: `1px solid ${panelFrameColor}`, borderRadius: panelRadius, background: panelBodyBg }}>
|
||||
key: 'ddl',
|
||||
label: 'DDL',
|
||||
icon: <FileTextOutlined />,
|
||||
children: (
|
||||
<div className={isV2Ui ? 'gn-v2-designer-ddl-shell' : undefined} style={{ height: '100%', minHeight: 320, border: `1px solid ${panelFrameColor}`, borderRadius: panelRadius, background: panelBodyBg }}>
|
||||
<Editor
|
||||
height="100%"
|
||||
language="sql"
|
||||
|
||||
@@ -406,6 +406,7 @@ const TableOverview: React.FC<TableOverviewProps> = ({ tab }) => {
|
||||
connectionId: connection.id,
|
||||
dbName: tab.dbName,
|
||||
tableName,
|
||||
objectType: 'table',
|
||||
});
|
||||
}, [connection, tab.dbName, addTab, setActiveContext]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user