From c5400635284b447c134f279a1fd5ea33cd4bba2d Mon Sep 17 00:00:00 2001 From: Supra4E8C Date: Tue, 17 Mar 2026 01:39:38 +0800 Subject: [PATCH] feat(authFiles): add sorting functionality and update UI components --- .../hooks/useAuthFilesPrefixProxyEditor.ts | 17 +++--- src/features/authFiles/uiState.ts | 9 ++++ src/pages/AuthFilesPage.module.scss | 14 ----- src/pages/AuthFilesPage.tsx | 52 ++++++++++++++----- 4 files changed, 58 insertions(+), 34 deletions(-) diff --git a/src/features/authFiles/hooks/useAuthFilesPrefixProxyEditor.ts b/src/features/authFiles/hooks/useAuthFilesPrefixProxyEditor.ts index 7591483..eee1df0 100644 --- a/src/features/authFiles/hooks/useAuthFilesPrefixProxyEditor.ts +++ b/src/features/authFiles/hooks/useAuthFilesPrefixProxyEditor.ts @@ -39,6 +39,7 @@ export type PrefixProxyEditorState = { disableCooling: string; websocket: boolean; note: string; + noteTouched: boolean; }; export type UseAuthFilesPrefixProxyEditorOptions = { @@ -95,11 +96,13 @@ const buildPrefixProxyUpdatedText = (editor: PrefixProxyEditorState | null): str next.websocket = editor.websocket; } - const noteValue = editor.note.trim(); - if (noteValue) { - next.note = noteValue; - } else if ('note' in next) { - delete next.note; + if (editor.noteTouched) { + const noteValue = editor.note.trim(); + if (noteValue) { + next.note = editor.note; + } else if ('note' in next) { + delete next.note; + } } return JSON.stringify(next); @@ -156,6 +159,7 @@ export function useAuthFilesPrefixProxyEditor( disableCooling: '', websocket: false, note: '', + noteTouched: false, }); try { @@ -223,6 +227,7 @@ export function useAuthFilesPrefixProxyEditor( disableCoolingValue === undefined ? '' : disableCoolingValue ? 'true' : 'false', websocket: websocketValue ?? false, note, + noteTouched: false, error: null, }; }); @@ -247,7 +252,7 @@ export function useAuthFilesPrefixProxyEditor( if (field === 'priority') return { ...prev, priority: String(value) }; if (field === 'excludedModelsText') return { ...prev, excludedModelsText: String(value) }; if (field === 'disableCooling') return { ...prev, disableCooling: String(value) }; - if (field === 'note') return { ...prev, note: String(value) }; + if (field === 'note') return { ...prev, note: String(value), noteTouched: true }; return { ...prev, websocket: Boolean(value) }; }); }; diff --git a/src/features/authFiles/uiState.ts b/src/features/authFiles/uiState.ts index effa815..edc4c28 100644 --- a/src/features/authFiles/uiState.ts +++ b/src/features/authFiles/uiState.ts @@ -1,12 +1,21 @@ +export const AUTH_FILES_SORT_MODES = ['default', 'az', 'priority'] as const; + +export type AuthFilesSortMode = (typeof AUTH_FILES_SORT_MODES)[number]; + export type AuthFilesUiState = { filter?: string; problemOnly?: boolean; search?: string; page?: number; pageSize?: number; + sortMode?: AuthFilesSortMode; }; const AUTH_FILES_UI_STATE_KEY = 'authFilesPage.uiState'; +const AUTH_FILES_SORT_MODE_SET = new Set(AUTH_FILES_SORT_MODES); + +export const isAuthFilesSortMode = (value: unknown): value is AuthFilesSortMode => + typeof value === 'string' && AUTH_FILES_SORT_MODE_SET.has(value as AuthFilesSortMode); export const readAuthFilesUiState = (): AuthFilesUiState | null => { if (typeof window === 'undefined') return null; diff --git a/src/pages/AuthFilesPage.module.scss b/src/pages/AuthFilesPage.module.scss index e865756..0bfeeff 100644 --- a/src/pages/AuthFilesPage.module.scss +++ b/src/pages/AuthFilesPage.module.scss @@ -623,21 +623,7 @@ } .sortSelect { - padding: 8px 12px; - border: 1px solid var(--border-color); - border-radius: $radius-md; - background-color: var(--bg-primary); - color: var(--text-primary); - font-size: 14px; - cursor: pointer; - height: 38px; - box-sizing: border-box; min-width: 140px; - - &:focus { - outline: none; - border-color: var(--primary-color); - } } .healthStatusMessage { diff --git a/src/pages/AuthFilesPage.tsx b/src/pages/AuthFilesPage.tsx index 30ec0a2..cb485a3 100644 --- a/src/pages/AuthFilesPage.tsx +++ b/src/pages/AuthFilesPage.tsx @@ -18,6 +18,7 @@ import { usePageTransitionLayer } from '@/components/common/PageTransitionLayer' import { Card } from '@/components/ui/Card'; import { Button } from '@/components/ui/Button'; import { Input } from '@/components/ui/Input'; +import { Select } from '@/components/ui/Select'; import { EmptyState } from '@/components/ui/EmptyState'; import { ToggleSwitch } from '@/components/ui/ToggleSwitch'; import { copyToClipboard } from '@/utils/clipboard'; @@ -47,7 +48,12 @@ import { useAuthFilesOauth } from '@/features/authFiles/hooks/useAuthFilesOauth' import { useAuthFilesPrefixProxyEditor } from '@/features/authFiles/hooks/useAuthFilesPrefixProxyEditor'; import { useAuthFilesStats } from '@/features/authFiles/hooks/useAuthFilesStats'; import { useAuthFilesStatusBarCache } from '@/features/authFiles/hooks/useAuthFilesStatusBarCache'; -import { readAuthFilesUiState, writeAuthFilesUiState } from '@/features/authFiles/uiState'; +import { + isAuthFilesSortMode, + readAuthFilesUiState, + writeAuthFilesUiState, + type AuthFilesSortMode, +} from '@/features/authFiles/uiState'; import { useAuthStore, useNotificationStore, useThemeStore } from '@/stores'; import type { AuthFileItem } from '@/types'; import styles from './AuthFilesPage.module.scss'; @@ -75,7 +81,7 @@ export function AuthFilesPage() { const [detailModalOpen, setDetailModalOpen] = useState(false); const [selectedFile, setSelectedFile] = useState(null); const [viewMode, setViewMode] = useState<'diagram' | 'list'>('list'); - const [sortMode, setSortMode] = useState<'default' | 'az' | 'priority'>('default'); + const [sortMode, setSortMode] = useState('default'); const [batchActionBarVisible, setBatchActionBarVisible] = useState(false); const floatingBatchActionsRef = useRef(null); const batchActionAnimationRef = useRef(null); @@ -179,11 +185,14 @@ export function AuthFilesPage() { if (typeof persisted.pageSize === 'number' && Number.isFinite(persisted.pageSize)) { setPageSize(clampCardPageSize(persisted.pageSize)); } + if (isAuthFilesSortMode(persisted.sortMode)) { + setSortMode(persisted.sortMode); + } }, []); useEffect(() => { - writeAuthFilesUiState({ filter, problemOnly, search, page, pageSize }); - }, [filter, problemOnly, search, page, pageSize]); + writeAuthFilesUiState({ filter, problemOnly, search, page, pageSize, sortMode }); + }, [filter, problemOnly, search, page, pageSize, sortMode]); useEffect(() => { setPageSizeInput(String(pageSize)); @@ -225,6 +234,16 @@ export function AuthFilesPage() { setPage(1); }; + const handleSortModeChange = useCallback( + (value: string) => { + if (!isAuthFilesSortMode(value) || value === sortMode) return; + setSortMode(value); + setPage(1); + void loadFiles().catch(() => {}); + }, + [loadFiles, sortMode] + ); + const handleHeaderRefresh = useCallback(async () => { await Promise.all([loadFiles(), refreshKeyStats(), loadExcluded(), loadModelAlias()]); }, [loadFiles, refreshKeyStats, loadExcluded, loadModelAlias]); @@ -261,6 +280,15 @@ export function AuthFilesPage() { [files, problemOnly] ); + const sortOptions = useMemo( + () => [ + { value: 'default', label: t('auth_files.sort_default') }, + { value: 'az', label: t('auth_files.sort_az') }, + { value: 'priority', label: t('auth_files.sort_priority') }, + ], + [t] + ); + const typeCounts = useMemo(() => { const counts: Record = { all: filesMatchingProblemFilter.length }; filesMatchingProblemFilter.forEach((file) => { @@ -578,18 +606,14 @@ export function AuthFilesPage() {
- + options={sortOptions} + onChange={handleSortModeChange} + ariaLabel={t('auth_files.sort_label')} + fullWidth={false} + />