diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx index d813456..eed1e07 100644 --- a/frontend/src/components/Sidebar.tsx +++ b/frontend/src/components/Sidebar.tsx @@ -54,6 +54,10 @@ const Sidebar: React.FC<{ onEditConnection?: (conn: SavedConnection) => void }> const removeConnection = useStore(state => state.removeConnection); const theme = useStore(state => state.theme); const appearance = useStore(state => state.appearance); + const tableAccessCount = useStore(state => state.tableAccessCount); + const tableSortPreference = useStore(state => state.tableSortPreference); + const recordTableAccess = useStore(state => state.recordTableAccess); + const setTableSortPreference = useStore(state => state.setTableSortPreference); const darkMode = theme === 'dark'; const opacity = normalizeOpacityForPlatform(appearance.opacity); const [treeData, setTreeData] = useState([]); @@ -490,8 +494,28 @@ const Sidebar: React.FC<{ onEditConnection?: (conn: SavedConnection) => void }> loadDatabaseTriggers(conn, conn.dbName), ]); - // Sort tables by display name (case-insensitive) - tables.sort((a, b) => a.title.toLowerCase().localeCompare(b.title.toLowerCase())); + // 获取当前数据库的排序偏好 + const sortPreferenceKey = `${conn.id}-${conn.dbName}`; + const sortBy = tableSortPreference[sortPreferenceKey] || 'name'; + + // 根据排序偏好排序表 + if (sortBy === 'frequency') { + // 按使用频率排序(降序) + tables.sort((a, b) => { + const keyA = `${conn.id}-${conn.dbName}-${a.dataRef.tableName}`; + const keyB = `${conn.id}-${conn.dbName}-${b.dataRef.tableName}`; + const countA = tableAccessCount[keyA] || 0; + const countB = tableAccessCount[keyB] || 0; + if (countA !== countB) { + return countB - countA; // 降序 + } + // 频率相同时按名称排序 + return a.title.toLowerCase().localeCompare(b.title.toLowerCase()); + }); + } else { + // 按名称排序(字母顺序) + tables.sort((a, b) => a.title.toLowerCase().localeCompare(b.title.toLowerCase())); + } // Sort views by name (case-insensitive) views.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase())); @@ -662,6 +686,8 @@ const Sidebar: React.FC<{ onEditConnection?: (conn: SavedConnection) => void }> const onDoubleClick = (e: any, node: any) => { if (node.type === 'table') { const { tableName, dbName, id } = node.dataRef; + // 记录表访问 + recordTableAccess(id, dbName, tableName); addTab({ id: node.key, title: tableName, @@ -718,10 +744,10 @@ const Sidebar: React.FC<{ onEditConnection?: (conn: SavedConnection) => void }> const key = node.key; const isExpanded = expandedKeys.includes(key); - const newExpandedKeys = isExpanded - ? expandedKeys.filter(k => k !== key) + const newExpandedKeys = isExpanded + ? expandedKeys.filter(k => k !== key) : [...expandedKeys, key]; - + setExpandedKeys(newExpandedKeys); if (!isExpanded) setAutoExpandParent(false); }; @@ -1321,6 +1347,42 @@ const Sidebar: React.FC<{ onEditConnection?: (conn: SavedConnection) => void }> const conn = node.dataRef as SavedConnection; const isRedis = conn?.config?.type === 'redis'; + // 表分组节点的右键菜单 + if (node.type === 'object-group' && node.dataRef?.groupKey === 'tables') { + const groupData = node.dataRef; // { ...conn, dbName, groupKey } + const sortPreferenceKey = `${groupData.id}-${groupData.dbName}`; + const currentSort = tableSortPreference[sortPreferenceKey] || 'name'; + + return [ + { + key: 'sort-by-name', + label: '按名称排序', + icon: currentSort === 'name' ? : null, + onClick: () => { + setTableSortPreference(groupData.id, groupData.dbName, 'name'); + const dbNode = { + key: `${groupData.id}-${groupData.dbName}`, + dataRef: groupData + }; + loadTables(dbNode); + } + }, + { + key: 'sort-by-frequency', + label: '按使用频率排序', + icon: currentSort === 'frequency' ? : null, + onClick: () => { + setTableSortPreference(groupData.id, groupData.dbName, 'frequency'); + const dbNode = { + key: `${groupData.id}-${groupData.dbName}`, + dataRef: groupData + }; + loadTables(dbNode); + } + } + ]; + } + if (node.type === 'connection') { // Redis connection menu if (isRedis) { @@ -1687,7 +1749,7 @@ const Sidebar: React.FC<{ onEditConnection?: (conn: SavedConnection) => void }> if (connectionStates[node.key] === 'success') status = 'success'; else if (connectionStates[node.key] === 'error') status = 'error'; } - + const statusBadge = node.type === 'connection' || node.type === 'database' ? ( ) : null; diff --git a/frontend/src/store.ts b/frontend/src/store.ts index e431b79..d8d1fce 100644 --- a/frontend/src/store.ts +++ b/frontend/src/store.ts @@ -37,11 +37,13 @@ interface AppState { sqlFormatOptions: { keywordCase: 'upper' | 'lower' }; queryOptions: { maxRows: number }; sqlLogs: SqlLog[]; - + tableAccessCount: Record; + tableSortPreference: Record; + addConnection: (conn: SavedConnection) => void; updateConnection: (conn: SavedConnection) => void; removeConnection: (id: string) => void; - + addTab: (tab: TabData) => void; closeTab: (id: string) => void; closeOtherTabs: (id: string) => void; @@ -58,9 +60,12 @@ interface AppState { setAppearance: (appearance: Partial<{ opacity: number; blur: number }>) => void; setSqlFormatOptions: (options: { keywordCase: 'upper' | 'lower' }) => void; setQueryOptions: (options: Partial<{ maxRows: number }>) => void; - + addSqlLog: (log: SqlLog) => void; clearSqlLogs: () => void; + + recordTableAccess: (connectionId: string, dbName: string, tableName: string) => void; + setTableSortPreference: (connectionId: string, dbName: string, sortBy: 'name' | 'frequency') => void; } export const useStore = create()( @@ -76,10 +81,12 @@ export const useStore = create()( sqlFormatOptions: { keywordCase: 'upper' }, queryOptions: { maxRows: 5000 }, sqlLogs: [], + tableAccessCount: {}, + tableSortPreference: {}, addConnection: (conn) => set((state) => ({ connections: [...state.connections, conn] })), - updateConnection: (conn) => set((state) => ({ - connections: state.connections.map(c => c.id === conn.id ? conn : c) + updateConnection: (conn) => set((state) => ({ + connections: state.connections.map(c => c.id === conn.id ? conn : c) })), removeConnection: (id) => set((state) => ({ connections: state.connections.filter(c => c.id !== id) })), @@ -145,9 +152,30 @@ export const useStore = create()( setAppearance: (appearance) => set((state) => ({ appearance: { ...state.appearance, ...appearance } })), setSqlFormatOptions: (options) => set({ sqlFormatOptions: options }), setQueryOptions: (options) => set((state) => ({ queryOptions: { ...state.queryOptions, ...options } })), - + addSqlLog: (log) => set((state) => ({ sqlLogs: [log, ...state.sqlLogs].slice(0, 1000) })), // Keep last 1000 logs clearSqlLogs: () => set({ sqlLogs: [] }), + + recordTableAccess: (connectionId, dbName, tableName) => set((state) => { + const key = `${connectionId}-${dbName}-${tableName}`; + const currentCount = state.tableAccessCount[key] || 0; + return { + tableAccessCount: { + ...state.tableAccessCount, + [key]: currentCount + 1 + } + }; + }), + + setTableSortPreference: (connectionId, dbName, sortBy) => set((state) => { + const key = `${connectionId}-${dbName}`; + return { + tableSortPreference: { + ...state.tableSortPreference, + [key]: sortBy + } + }; + }), }), { name: 'lite-db-storage', // name of the item in the storage (must be unique) @@ -178,7 +206,16 @@ export const useStore = create()( return nextState as AppState; }, - partialize: (state) => ({ connections: state.connections, savedQueries: state.savedQueries, theme: state.theme, appearance: state.appearance, sqlFormatOptions: state.sqlFormatOptions, queryOptions: state.queryOptions }), // Don't persist logs + partialize: (state) => ({ + connections: state.connections, + savedQueries: state.savedQueries, + theme: state.theme, + appearance: state.appearance, + sqlFormatOptions: state.sqlFormatOptions, + queryOptions: state.queryOptions, + tableAccessCount: state.tableAccessCount, + tableSortPreference: state.tableSortPreference + }), // Don't persist logs } ) );