diff --git a/frontend/src/components/DataGrid.tsx b/frontend/src/components/DataGrid.tsx index 3af5d3b..f7b197c 100644 --- a/frontend/src/components/DataGrid.tsx +++ b/frontend/src/components/DataGrid.tsx @@ -759,6 +759,12 @@ const DataGrid: React.FC = ({ const setEnableColumnOrderMemory = useStore(state => state.setEnableColumnOrderMemory); const clearTableColumnOrder = useStore(state => state.clearTableColumnOrder); + const tableHiddenColumns = useStore(state => state.tableHiddenColumns); + const enableHiddenColumnMemory = useStore(state => state.enableHiddenColumnMemory); + const setTableHiddenColumns = useStore(state => state.setTableHiddenColumns); + const setEnableHiddenColumnMemory = useStore(state => state.setEnableHiddenColumnMemory); + const clearTableHiddenColumns = useStore(state => state.clearTableHiddenColumns); + const isMacLike = useMemo(() => isMacLikePlatform(), []); const darkMode = theme === 'dark'; const resolvedAppearance = resolveAppearanceValues(appearance); @@ -767,9 +773,45 @@ const DataGrid: React.FC = ({ const showColumnComment = queryOptions?.showColumnComment !== false; const showColumnType = queryOptions?.showColumnType !== false; - // --- Display Columns Order Management --- + // --- Display Columns Order & Visibility Management --- + const [allOrderedColumnNames, setAllOrderedColumnNames] = useState([]); const [displayColumnNames, setDisplayColumnNames] = useState([]); - + const [localHiddenColumns, setLocalHiddenColumns] = useState([]); + const [columnSearchText, setColumnSearchText] = useState(''); + + // Sync hidden columns from store + useEffect(() => { + if (enableHiddenColumnMemory && connectionId && dbName && tableName) { + const storedHidden = tableHiddenColumns[`${connectionId}-${dbName}-${tableName}`]; + setLocalHiddenColumns(Array.isArray(storedHidden) ? storedHidden : []); + } else { + setLocalHiddenColumns([]); + } + }, [tableHiddenColumns, enableHiddenColumnMemory, connectionId, dbName, tableName]); + + const toggleColumnVisibility = useCallback((col: string, visible: boolean) => { + setLocalHiddenColumns(prev => { + const nextSet = new Set(prev); + if (visible) nextSet.delete(col); + else nextSet.add(col); + const nextArray = Array.from(nextSet); + if (enableHiddenColumnMemory && connectionId && dbName && tableName) { + setTableHiddenColumns(connectionId, dbName, tableName, nextArray); + } + return nextArray; + }); + }, [enableHiddenColumnMemory, connectionId, dbName, tableName, setTableHiddenColumns]); + + const toggleAllColumnsVisibility = useCallback((visible: boolean) => { + setLocalHiddenColumns(() => { + const nextArray = visible ? [] : [...allOrderedColumnNames]; + if (enableHiddenColumnMemory && connectionId && dbName && tableName) { + setTableHiddenColumns(connectionId, dbName, tableName, nextArray); + } + return nextArray; + }); + }, [allOrderedColumnNames, enableHiddenColumnMemory, connectionId, dbName, tableName, setTableHiddenColumns]); + // Sync display order from incoming prop and store memory useEffect(() => { let nextOrder = [...columnNames]; @@ -784,9 +826,15 @@ const DataGrid: React.FC = ({ nextOrder = [...validStored, ...missingNew]; } } - setDisplayColumnNames(nextOrder); + setAllOrderedColumnNames(nextOrder); }, [columnNames, tableColumnOrders, enableColumnOrderMemory, connectionId, dbName, tableName]); + // Compute final display columns + useEffect(() => { + const hiddenSet = new Set(localHiddenColumns); + setDisplayColumnNames(allOrderedColumnNames.filter(col => !hiddenSet.has(col))); + }, [allOrderedColumnNames, localHiddenColumns]); + // Handle Dragging const sensors = useSensors( useSensor(PointerSensor, { activationConstraint: { distance: 8 } }), @@ -797,14 +845,36 @@ const DataGrid: React.FC = ({ const handleDragEnd = (event: DragEndEvent) => { const { active, over } = event; if (active.id !== over?.id && over) { - setDisplayColumnNames((prev) => { - const oldIndex = prev.indexOf(active.id as string); - const newIndex = prev.indexOf(over.id as string); - const nextOrder = arrayMove(prev, oldIndex, newIndex); - if (enableColumnOrderMemory && connectionId && dbName && tableName) { - setTableColumnOrder(connectionId, dbName, tableName, nextOrder); - } - return nextOrder; + setAllOrderedColumnNames((prevAllOrder) => { + // Calculate the new order of all columns by applying the movement + // We only move the visible columns relative to each other, but the easiest way + // is to map the visible column movement back to the full array. + const hiddenSet = new Set(localHiddenColumns); + const visibleOrder = prevAllOrder.filter(col => !hiddenSet.has(col)); + + const oldVisibleIndex = visibleOrder.indexOf(active.id as string); + const newVisibleIndex = visibleOrder.indexOf(over.id as string); + + if (oldVisibleIndex === -1 || newVisibleIndex === -1) return prevAllOrder; + + const nextVisibleOrder = arrayMove(visibleOrder, oldVisibleIndex, newVisibleIndex); + + // Reconstruct allOrderedColumnNames by inserting hidden columns back to their original relative positions + // Or simpler: just keep hidden columns at the end, but that ruins user's layout. + // Better approach: build a new array + let vIndex = 0; + const nextOrder = prevAllOrder.map(col => { + if (hiddenSet.has(col)) { + return col; // Hidden columns stay at their absolute index in the master list + } else { + return nextVisibleOrder[vIndex++]; + } + }); + + if (enableColumnOrderMemory && connectionId && dbName && tableName) { + setTableColumnOrder(connectionId, dbName, tableName, nextOrder); + } + return nextOrder; }); } }; @@ -2068,7 +2138,7 @@ const DataGrid: React.FC = ({ const formMap: Record = {}; const nullCols = new Set(); - displayColumnNames.forEach((col) => { + columnNames.forEach((col) => { const baseVal = (baseRow as any)?.[col]; const displayVal = (displayRow as any)?.[col]; baseRawMap[col] = baseVal; @@ -2178,7 +2248,7 @@ const DataGrid: React.FC = ({ const keyStr = rowKeyStr(rowKey); const normalizedNext: Record = {}; let hasAnyVisibleChange = false; - displayColumnNames.forEach((col) => { + columnNames.forEach((col) => { const currentVal = (currentRow as any)?.[col]; const editedVal = Object.prototype.hasOwnProperty.call(nextItem, col) ? (nextItem as any)[col] : currentVal; if (!isJsonViewValueEqual(currentVal, editedVal)) hasAnyVisibleChange = true; @@ -2197,7 +2267,7 @@ const DataGrid: React.FC = ({ const originalRow = originalMap.get(keyStr); if (!originalRow) continue; const patch: Record = {}; - displayColumnNames.forEach((col) => { + columnNames.forEach((col) => { const prevVal = (originalRow as any)?.[col]; const nextVal = normalizedNext[col]; if (!isCellValueEqualForDiff(prevVal, nextVal)) patch[col] = nextVal; @@ -2389,11 +2459,10 @@ const DataGrid: React.FC = ({ const handleAddRow = () => { const newKey = `new-${Date.now()}`; const newRow: any = { [GONAVI_ROW_KEY]: newKey }; - displayColumnNames.forEach(col => newRow[col] = ''); + columnNames.forEach(col => newRow[col] = ''); pendingScrollToBottomRef.current = true; setAddedRows(prev => [...prev, newRow]); }; - const handleDeleteSelected = () => { setDeletedRowKeys(prev => { const newDeleted = new Set(prev); @@ -2898,19 +2967,49 @@ const DataGrid: React.FC = ({ ]; const columnInfoSettingContent = ( -
+
+
显示设置
setQueryOptions({ showColumnComment: e.target.checked })} > - 下方显示备注 + 表头显示备注 setQueryOptions({ showColumnType: e.target.checked })} > - 下方显示类型 + 表头显示类型 +
+ + + setColumnSearchText(e.target.value)} + allowClear + /> +
+ {allOrderedColumnNames.filter(col => !columnSearchText || col.toLowerCase().includes(columnSearchText.toLowerCase())).map(col => ( + toggleColumnVisibility(col, e.target.checked)} + style={{ marginLeft: 0 }} + > + {col} + + ))} +
+
= ({ > 记忆自定义列序 - + 记忆隐藏列配置 + +
+ + +
); diff --git a/frontend/src/store.ts b/frontend/src/store.ts index bd424a5..172099d 100644 --- a/frontend/src/store.ts +++ b/frontend/src/store.ts @@ -418,6 +418,8 @@ interface AppState { tableSortPreference: Record; tableColumnOrders: Record; enableColumnOrderMemory: boolean; + tableHiddenColumns: Record; + enableHiddenColumnMemory: boolean; addConnection: (conn: SavedConnection) => void; updateConnection: (conn: SavedConnection) => void; @@ -463,6 +465,10 @@ interface AppState { setTableColumnOrder: (connectionId: string, dbName: string, tableName: string, order: string[]) => void; setEnableColumnOrderMemory: (enabled: boolean) => void; clearTableColumnOrder: (connectionId: string, dbName: string, tableName: string) => void; + + setTableHiddenColumns: (connectionId: string, dbName: string, tableName: string, hiddenColumns: string[]) => void; + setEnableHiddenColumnMemory: (enabled: boolean) => void; + clearTableHiddenColumns: (connectionId: string, dbName: string, tableName: string) => void; } const sanitizeSavedQueries = (value: unknown): SavedQuery[] => { @@ -537,6 +543,17 @@ const sanitizeTableColumnOrders = (value: unknown): Record => return result; }; +const sanitizeTableHiddenColumns = (value: unknown): Record => { + const raw = (value && typeof value === 'object') ? value as Record : {}; + const result: Record = {}; + Object.entries(raw).forEach(([key, hiddenArray]) => { + if (Array.isArray(hiddenArray)) { + result[key] = hiddenArray.map(col => String(col)); + } + }); + return result; +}; + const sanitizeAppearance = ( appearance: Partial<{ enabled: boolean; opacity: number; blur: number }> | undefined, version: number @@ -616,6 +633,8 @@ export const useStore = create()( tableSortPreference: {}, tableColumnOrders: {}, enableColumnOrderMemory: true, + tableHiddenColumns: {}, + enableHiddenColumnMemory: true, addConnection: (conn) => set((state) => ({ connections: [...state.connections, conn] })), updateConnection: (conn) => set((state) => ({ @@ -837,6 +856,25 @@ export const useStore = create()( }), setEnableColumnOrderMemory: (enabled) => set({ enableColumnOrderMemory: !!enabled }), + + setTableHiddenColumns: (connectionId, dbName, tableName, hiddenColumns) => set((state) => { + const key = `${connectionId}-${dbName}-${tableName}`; + return { + tableHiddenColumns: { + ...state.tableHiddenColumns, + [key]: hiddenColumns + } + }; + }), + + clearTableHiddenColumns: (connectionId, dbName, tableName) => set((state) => { + const key = `${connectionId}-${dbName}-${tableName}`; + const newHidden = { ...state.tableHiddenColumns }; + delete newHidden[key]; + return { tableHiddenColumns: newHidden }; + }), + + setEnableHiddenColumnMemory: (enabled) => set({ enableHiddenColumnMemory: !!enabled }), }), { name: 'lite-db-storage', // name of the item in the storage (must be unique) @@ -866,6 +904,9 @@ export const useStore = create()( const safeOrders = sanitizeTableColumnOrders(state.tableColumnOrders); nextState.tableColumnOrders = safeOrders; nextState.enableColumnOrderMemory = state.enableColumnOrderMemory !== false; + const safeHidden = sanitizeTableHiddenColumns(state.tableHiddenColumns); + nextState.tableHiddenColumns = safeHidden; + nextState.enableHiddenColumnMemory = state.enableHiddenColumnMemory !== false; return nextState as AppState; }, merge: (persistedState, currentState) => { @@ -885,6 +926,8 @@ export const useStore = create()( tableSortPreference: sanitizeTableSortPreference(state.tableSortPreference), tableColumnOrders: sanitizeTableColumnOrders(state.tableColumnOrders), enableColumnOrderMemory: state.enableColumnOrderMemory !== false, + tableHiddenColumns: sanitizeTableHiddenColumns(state.tableHiddenColumns), + enableHiddenColumnMemory: state.enableHiddenColumnMemory !== false, sqlFormatOptions: sanitizeSqlFormatOptions(state.sqlFormatOptions), queryOptions: sanitizeQueryOptions(state.queryOptions), @@ -906,7 +949,11 @@ export const useStore = create()( queryOptions: state.queryOptions, shortcutOptions: state.shortcutOptions, tableAccessCount: state.tableAccessCount, - tableSortPreference: state.tableSortPreference + tableSortPreference: state.tableSortPreference, + tableColumnOrders: state.tableColumnOrders, + enableColumnOrderMemory: state.enableColumnOrderMemory, + tableHiddenColumns: state.tableHiddenColumns, + enableHiddenColumnMemory: state.enableHiddenColumnMemory }), // Don't persist logs } )