mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-06-28 01:11:31 +08:00
✨feat(sidebar): 新增侧栏表自定义排序功能
- 支持按名称排序(字母顺序,默认) - 支持按使用频率排序(打开次数降序) - 右键表分组节点选择排序方式 - 排序偏好和访问统计持久化保存 - 每个数据库可独立设置排序方式
This commit is contained in:
@@ -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<TreeNode[]>([]);
|
||||
@@ -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' ? <CheckSquareOutlined /> : 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' ? <CheckSquareOutlined /> : 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' ? (
|
||||
<Badge status={status} style={{ marginRight: 8 }} />
|
||||
) : null;
|
||||
|
||||
@@ -37,11 +37,13 @@ interface AppState {
|
||||
sqlFormatOptions: { keywordCase: 'upper' | 'lower' };
|
||||
queryOptions: { maxRows: number };
|
||||
sqlLogs: SqlLog[];
|
||||
|
||||
tableAccessCount: Record<string, number>;
|
||||
tableSortPreference: Record<string, 'name' | 'frequency'>;
|
||||
|
||||
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<AppState>()(
|
||||
@@ -76,10 +81,12 @@ export const useStore = create<AppState>()(
|
||||
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<AppState>()(
|
||||
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<AppState>()(
|
||||
|
||||
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
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user