mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-06-16 21:03:58 +08:00
feat(authFiles): add sorting functionality and update UI components
This commit is contained in:
@@ -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) };
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user