diff --git a/frontend/src/components/Sidebar.locate-toolbar.test.tsx b/frontend/src/components/Sidebar.locate-toolbar.test.tsx index f0303778..f3adcab1 100644 --- a/frontend/src/components/Sidebar.locate-toolbar.test.tsx +++ b/frontend/src/components/Sidebar.locate-toolbar.test.tsx @@ -2254,6 +2254,20 @@ describe('Sidebar locate toolbar', () => { expect(metaSource).not.toContain('— 行'); }); + it('sorts sidebar table names in natural numeric order', () => { + const entries = [ + { tableName: 'table_10', displayName: 'table_10' }, + { tableName: 'table_2', displayName: 'table_2' }, + { tableName: 'table_1', displayName: 'table_1' }, + ]; + + expect(sortSidebarTableEntries(entries, { + connectionId: 'conn-1', + dbName: 'main', + sortBy: 'name', + }).map((entry) => entry.tableName)).toEqual(['table_1', 'table_2', 'table_10']); + }); + it('sorts pinned sidebar tables before the active sort mode', () => { const pinnedSidebarTables = [ buildSidebarTablePinKey('conn-1', 'main', 'orders', 'public'), diff --git a/frontend/src/components/TableDesigner.tsx b/frontend/src/components/TableDesigner.tsx index a8f4ed6f..f50f56e7 100644 --- a/frontend/src/components/TableDesigner.tsx +++ b/frontend/src/components/TableDesigner.tsx @@ -243,6 +243,10 @@ const COMMON_DEFAULTS = [ { value: "''" }, ]; +const isMySQLCharacterColumnType = (columnType: string): boolean => ( + /^(?:char|varchar|tinytext|text|mediumtext|longtext|enum|set|nchar|nvarchar)\b/i.test(String(columnType || '').trim()) +); + const PGLIKE_INDEX_TYPE_OPTIONS = [ { label: 'DEFAULT', value: 'DEFAULT' }, @@ -285,7 +289,16 @@ const COLLATIONS = { { label: 'utf8_unicode_ci', value: 'utf8_unicode_ci' }, { label: 'utf8_general_ci', value: 'utf8_general_ci' }, { label: 'utf8_bin', value: 'utf8_bin' }, - ] + ], + 'latin1': [ + { label: 'latin1_swedish_ci', value: 'latin1_swedish_ci' }, + { label: 'latin1_general_ci', value: 'latin1_general_ci' }, + { label: 'latin1_bin', value: 'latin1_bin' }, + ], + 'ascii': [ + { label: 'ascii_general_ci', value: 'ascii_general_ci' }, + { label: 'ascii_bin', value: 'ascii_bin' }, + ], }; const getCollationOptions = (i18nLanguage: string) => Object.fromEntries( @@ -476,7 +489,12 @@ const TableDesigner: React.FC<{ tab: TabData; embedded?: boolean }> = ({ tab, em const [isCommentModalOpen, setIsCommentModalOpen] = useState(false); const [commentEditorColumnKey, setCommentEditorColumnKey] = useState(''); const [commentEditorColumnName, setCommentEditorColumnName] = useState(''); + const [commentEditorColumnType, setCommentEditorColumnType] = useState(''); const [commentEditorValue, setCommentEditorValue] = useState(''); + const [columnDefaultEnabled, setColumnDefaultEnabled] = useState(false); + const [columnDefaultValue, setColumnDefaultValue] = useState(''); + const [columnCharset, setColumnCharset] = useState(); + const [columnCollation, setColumnCollation] = useState(); const [inlineCommentEditingKey, setInlineCommentEditingKey] = useState(''); const connections = useStore(state => state.connections); @@ -515,7 +533,12 @@ const TableDesigner: React.FC<{ tab: TabData; embedded?: boolean }> = ({ tab, em setInlineCommentEditingKey(''); setCommentEditorColumnKey(record._key); setCommentEditorColumnName(record.name || ''); + setCommentEditorColumnType(record.type || ''); setCommentEditorValue(record.comment || ''); + setColumnDefaultEnabled(record.hasDefault === true); + setColumnDefaultValue(record.default ?? ''); + setColumnCharset(record.charset); + setColumnCollation(record.collation); setIsCommentModalOpen(true); }, []); @@ -523,7 +546,12 @@ const TableDesigner: React.FC<{ tab: TabData; embedded?: boolean }> = ({ tab, em setIsCommentModalOpen(false); setCommentEditorColumnKey(''); setCommentEditorColumnName(''); + setCommentEditorColumnType(''); setCommentEditorValue(''); + setColumnDefaultEnabled(false); + setColumnDefaultValue(''); + setColumnCharset(undefined); + setColumnCollation(undefined); }, []); // 透明 Monaco Editor 主题由 MonacoEditor 包装组件按需注册(含 stickyScroll 不透明背景) @@ -718,11 +746,25 @@ const TableDesigner: React.FC<{ tab: TabData; embedded?: boolean }> = ({ tab, em dataIndex: 'default', key: 'default', width: 180, // Increased default width - render: (text: string, record: EditableColumn) => readOnly ? text : ( - renderDesignerCellField( - handleColumnChange(record._key, 'default', val)} style={{ width: '100%' }} variant="borderless" placeholder="NULL" /> - ) - ) + render: (text: string | undefined, record: EditableColumn) => { + const value = record.hasDefault + ? (text === '' ? "''" : (text ?? '')) + : undefined; + if (readOnly) return value; + return renderDesignerCellField( + { + const hasDefault = val.length > 0; + handleColumnChange(record._key, 'default', hasDefault ? (val === "''" ? '' : val) : undefined); + handleColumnChange(record._key, 'hasDefault', hasDefault); + }} + style={{ width: '100%' }} + variant="borderless" + /> + ); + } }, { title: renderDesignerHeaderTitle(t('table_designer.column.comment', undefined, i18nLanguage)), @@ -754,7 +796,7 @@ const TableDesigner: React.FC<{ tab: TabData; embedded?: boolean }> = ({ tab, em variant="borderless" /> )} - +