diff --git a/frontend/src/components/TableOverview.tsx b/frontend/src/components/TableOverview.tsx index bf687a1..db65b99 100644 --- a/frontend/src/components/TableOverview.tsx +++ b/frontend/src/components/TableOverview.tsx @@ -148,7 +148,7 @@ const TableOverview: React.FC = ({ tab }) => { const [searchText, setSearchText] = useState(''); const [sortField, setSortField] = useState('name'); const [sortOrder, setSortOrder] = useState('asc'); - const [viewMode, setViewMode] = useState('card'); + const [viewMode, setViewMode] = useState('list'); const connection = useMemo(() => connections.find(c => c.id === tab.connectionId), [connections, tab.connectionId]); @@ -338,6 +338,9 @@ const TableOverview: React.FC = ({ tab }) => { const totalRows = tables.reduce((s, t) => s + t.rows, 0); const totalSize = tables.reduce((s, t) => s + t.dataSize + t.indexSize, 0); + const maxCombinedSize = sortedFiltered.reduce((max, table) => { + return Math.max(max, table.dataSize + table.indexSize); + }, 0); if (loading) { return ( @@ -486,115 +489,143 @@ const TableOverview: React.FC = ({ tab }) => { ))} ) : ( - /* ========== 列表/表格视图 ========== */ -
- - - - {[ - { field: 'name' as SortField, label: '表名', width: undefined }, - { field: null, label: '注释', width: undefined }, - { field: 'rows' as SortField, label: '行数', width: 100 }, - { field: 'dataSize' as SortField, label: '数据大小', width: 110 }, - { field: null, label: '索引大小', width: 110 }, - { field: null, label: '引擎', width: 90 }, - ].map((col, idx) => ( - - ))} - - - - {sortedFiltered.map((t, rowIdx) => ( - , onClick: () => { - setActiveContext({ connectionId: tab.connectionId, dbName: tab.dbName || '' }); - addTab({ - id: `query-${Date.now()}`, - title: '新建查询', - type: 'query', - connectionId: tab.connectionId, - dbName: tab.dbName, - query: `SELECT * FROM ${t.name};`, - }); - }}, - { type: 'divider' }, - { key: 'design-table', label: '设计表', icon: , onClick: () => openDesign(t.name) }, - { key: 'copy-structure', label: '复制表结构', icon: , onClick: () => handleCopyStructure(t.name) }, - { key: 'backup-table', label: '备份表 (SQL)', icon: , onClick: () => handleExport(t.name, 'sql') }, - { key: 'rename-table', label: '重命名表', icon: , onClick: () => handleRenameTable(t.name) }, - { key: 'danger-zone', label: '危险操作', icon: , children: [ - { key: 'drop-table', label: '删除表', icon: , danger: true, onClick: () => handleDeleteTable(t.name) } - ]}, - { type: 'divider' }, - { key: 'export', label: '导出表数据', icon: , children: [ - { key: 'export-csv', label: '导出 CSV', onClick: () => handleExport(t.name, 'csv') }, - { key: 'export-xlsx', label: '导出 Excel (XLSX)', onClick: () => handleExport(t.name, 'xlsx') }, - { key: 'export-json', label: '导出 JSON', onClick: () => handleExport(t.name, 'json') }, - { key: 'export-md', label: '导出 Markdown', onClick: () => handleExport(t.name, 'md') }, - { key: 'export-html', label: '导出 HTML', onClick: () => handleExport(t.name, 'html') }, - ]}, - ], + /* ========== 行视图 ========== */ +
+ {sortedFiltered.map(t => { + const combinedSize = t.dataSize + t.indexSize; + const sizeRatio = maxCombinedSize > 0 ? combinedSize / maxCombinedSize : 0; + const fillWidth = maxCombinedSize > 0 ? `${Math.max(10, Math.round(sizeRatio * 100))}%` : '0%'; + const fillColor = darkMode ? 'rgba(22,119,255,0.18)' : 'rgba(22,119,255,0.12)'; + const rowSecondary = t.comment || (t.engine ? `${t.engine} 表` : '双击打开数据,右键查看更多操作'); + + return ( + , onClick: () => { + setActiveContext({ connectionId: tab.connectionId, dbName: tab.dbName || '' }); + addTab({ + id: `query-${Date.now()}`, + title: '新建查询', + type: 'query', + connectionId: tab.connectionId, + dbName: tab.dbName, + query: `SELECT * FROM ${t.name};`, + }); + }}, + { type: 'divider' }, + { key: 'design-table', label: '设计表', icon: , onClick: () => openDesign(t.name) }, + { key: 'copy-structure', label: '复制表结构', icon: , onClick: () => handleCopyStructure(t.name) }, + { key: 'backup-table', label: '备份表 (SQL)', icon: , onClick: () => handleExport(t.name, 'sql') }, + { key: 'rename-table', label: '重命名表', icon: , onClick: () => handleRenameTable(t.name) }, + { key: 'danger-zone', label: '危险操作', icon: , children: [ + { key: 'drop-table', label: '删除表', icon: , danger: true, onClick: () => handleDeleteTable(t.name) } + ]}, + { type: 'divider' }, + { key: 'export', label: '导出表数据', icon: , children: [ + { key: 'export-csv', label: '导出 CSV', onClick: () => handleExport(t.name, 'csv') }, + { key: 'export-xlsx', label: '导出 Excel (XLSX)', onClick: () => handleExport(t.name, 'xlsx') }, + { key: 'export-json', label: '导出 JSON', onClick: () => handleExport(t.name, 'json') }, + { key: 'export-md', label: '导出 Markdown', onClick: () => handleExport(t.name, 'md') }, + { key: 'export-html', label: '导出 HTML', onClick: () => handleExport(t.name, 'html') }, + ]}, + ], + }} + > +
openTable(t.name)} + style={{ + position: 'relative', + overflow: 'hidden', + borderRadius: 10, + border: `1px solid ${cardBorder}`, + background: cardBg, + cursor: 'pointer', + transition: 'all 0.15s ease', + userSelect: 'none', }} + onMouseEnter={e => { (e.currentTarget as HTMLDivElement).style.background = cardHoverBg; (e.currentTarget as HTMLDivElement).style.borderColor = accentColor; }} + onMouseLeave={e => { (e.currentTarget as HTMLDivElement).style.background = cardBg; (e.currentTarget as HTMLDivElement).style.borderColor = cardBorder; }} > -
openTable(t.name)} +
+
{ (e.currentTarget as HTMLTableRowElement).style.background = cardHoverBg; }} - onMouseLeave={e => { (e.currentTarget as HTMLTableRowElement).style.background = 'transparent'; }} > -
- - - - - - - - ))} - -
toggleSort(col.field!) : undefined} - style={{ - padding: '10px 14px', - textAlign: idx >= 2 ? 'right' : 'left', - fontWeight: 600, - color: textSecondary, - borderBottom: `1px solid ${cardBorder}`, - cursor: col.field ? 'pointer' : 'default', - userSelect: 'none', - whiteSpace: 'nowrap', - width: col.width, - }} - > - {col.label} - {col.field && sortField === col.field && ( - - {sortOrder === 'asc' ? '↑' : '↓'} - - )} -
-
+
+
- {t.name} + + {t.name} + + {t.engine && ( + + {t.engine} + + )}
-
- {t.comment ? ( - {t.comment} - ) : ( - - )} - {formatRows(t.rows)}{formatSize(t.dataSize)}{formatSize(t.indexSize)}{t.engine || '—'}
+ +
+ {rowSecondary} +
+
+
+
+
+
行数
+
{formatRows(t.rows)}
+
+
+
数据大小
+
{formatSize(t.dataSize)}
+
+
+
索引大小
+
{formatSize(t.indexSize)}
+
+
+
相对大小
+
+ {maxCombinedSize > 0 ? `${Math.round(sizeRatio * 100)}%` : '—'} +
+
+
+ + + + ); + })} )}