feat(authFiles): add sorting functionality and update UI components

This commit is contained in:
Supra4E8C
2026-03-17 01:39:38 +08:00
Unverified
parent acddeace52
commit c540063528
4 changed files with 58 additions and 34 deletions
@@ -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) };
});
};
+9
View File
@@ -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<AuthFilesSortMode>(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;
-14
View File
@@ -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 {
+38 -14
View File
@@ -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<AuthFileItem | null>(null);
const [viewMode, setViewMode] = useState<'diagram' | 'list'>('list');
const [sortMode, setSortMode] = useState<'default' | 'az' | 'priority'>('default');
const [sortMode, setSortMode] = useState<AuthFilesSortMode>('default');
const [batchActionBarVisible, setBatchActionBarVisible] = useState(false);
const floatingBatchActionsRef = useRef<HTMLDivElement>(null);
const batchActionAnimationRef = useRef<AnimationPlaybackControlsWithThen | null>(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<string, number> = { all: filesMatchingProblemFilter.length };
filesMatchingProblemFilter.forEach((file) => {
@@ -578,18 +606,14 @@ export function AuthFilesPage() {
</div>
<div className={styles.filterItem}>
<label>{t('auth_files.sort_label')}</label>
<select
<Select
className={styles.sortSelect}
value={sortMode}
onChange={(e) => {
setSortMode(e.target.value as 'default' | 'az' | 'priority');
setPage(1);
}}
>
<option value="default">{t('auth_files.sort_default')}</option>
<option value="az">{t('auth_files.sort_az')}</option>
<option value="priority">{t('auth_files.sort_priority')}</option>
</select>
options={sortOptions}
onChange={handleSortModeChange}
ariaLabel={t('auth_files.sort_label')}
fullWidth={false}
/>
</div>
<div className={`${styles.filterItem} ${styles.filterToggleItem}`}>
<label>{t('auth_files.problem_filter_label')}</label>