feat(auth-files): display OAuth credential priority and add sorting

- Show priority value on AuthFileCard when the credential has a priority field
- Add sort dropdown (Default / A-Z Name / Priority) to the credentials list
- Priority sort orders credentials from highest to lowest
- Add i18n translations for zh-CN, en, and ru

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
RGBadmin
2026-03-14 11:01:06 +08:00
co-authored by Claude Opus 4.6
parent beeecf7d01
commit 8c2d0df32c
6 changed files with 91 additions and 4 deletions
@@ -22,6 +22,7 @@ import {
getTypeColor,
getTypeLabel,
isRuntimeOnlyAuthFile,
parsePriorityValue,
resolveAuthFileStats,
type QuotaProviderType,
type ResolvedTheme,
@@ -110,6 +111,8 @@ export function AuthFileCard(props: AuthFileCardProps) {
const hasStatusWarning =
Boolean(rawStatusMessage) && !HEALTHY_STATUS_MESSAGES.has(rawStatusMessage.toLowerCase());
const priorityValue = parsePriorityValue(file.priority ?? file['priority']);
return (
<div
className={`${styles.fileCard} ${providerCardClass} ${selected ? styles.fileCardSelected : ''} ${file.disabled ? styles.fileCardDisabled : ''}`}
@@ -151,6 +154,11 @@ export function AuthFileCard(props: AuthFileCardProps) {
<span>
{t('auth_files.file_modified')}: {formatModified(file)}
</span>
{priorityValue !== undefined && (
<span className={styles.priorityBadge}>
{t('auth_files.priority_display')}: <span className={styles.priorityValue}>{priorityValue}</span>
</span>
)}
</div>
{rawStatusMessage && hasStatusWarning && (
+5
View File
@@ -495,6 +495,11 @@
"search_placeholder": "Filter by name, type, or provider",
"problem_filter_label": "Problem Filter",
"problem_filter_only": "Only show problematic credentials",
"sort_label": "Sort",
"sort_default": "Default",
"sort_az": "A-Z Name",
"sort_priority": "Priority",
"priority_display": "Priority",
"page_size_label": "Per page",
"page_size_unit": "items",
"view_mode_paged": "Paged",
+5
View File
@@ -495,6 +495,11 @@
"search_placeholder": "Фильтр по имени, типу или провайдеру",
"problem_filter_label": "Фильтр проблем",
"problem_filter_only": "Показывать только проблемные учётные данные",
"sort_label": "Сортировка",
"sort_default": "По умолчанию",
"sort_az": "A-Z Имя",
"sort_priority": "Приоритет",
"priority_display": "Приоритет",
"page_size_label": "На странице",
"page_size_unit": "элементов",
"view_mode_paged": "Постранично",
+5
View File
@@ -495,6 +495,11 @@
"search_placeholder": "输入名称、类型或提供方关键字",
"problem_filter_label": "问题筛选",
"problem_filter_only": "仅显示有问题凭证",
"sort_label": "排序",
"sort_default": "默认",
"sort_az": "A-Z 名称",
"sort_priority": "优先级",
"priority_display": "优先级",
"page_size_label": "单页数量",
"page_size_unit": "个/页",
"view_mode_paged": "按页显示",
+32
View File
@@ -632,6 +632,38 @@
border-bottom: 1px solid var(--border-color);
}
.priorityBadge {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 12px;
color: var(--text-secondary);
.priorityValue {
font-weight: 600;
color: var(--text-primary);
font-variant-numeric: tabular-nums;
}
}
.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 {
font-size: 12px;
color: var(--warning-text);
+36 -4
View File
@@ -31,6 +31,7 @@ import {
hasAuthFileStatusMessage,
isRuntimeOnlyAuthFile,
normalizeProviderKey,
parsePriorityValue,
type QuotaProviderType,
type ResolvedTheme,
} from '@/features/authFiles/constants';
@@ -74,6 +75,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 [batchActionBarVisible, setBatchActionBarVisible] = useState(false);
const floatingBatchActionsRef = useRef<HTMLDivElement>(null);
const batchActionAnimationRef = useRef<AnimationPlaybackControlsWithThen | null>(null);
@@ -281,10 +283,25 @@ export function AuthFilesPage() {
});
}, [filesMatchingProblemFilter, filter, search]);
const totalPages = Math.max(1, Math.ceil(filtered.length / pageSize));
const sorted = useMemo(() => {
if (sortMode === 'default') return filtered;
const copy = [...filtered];
if (sortMode === 'az') {
copy.sort((a, b) => a.name.localeCompare(b.name));
} else if (sortMode === 'priority') {
copy.sort((a, b) => {
const pa = parsePriorityValue(a.priority ?? a['priority']) ?? 0;
const pb = parsePriorityValue(b.priority ?? b['priority']) ?? 0;
return pb - pa; // 高优先级排前面
});
}
return copy;
}, [filtered, sortMode]);
const totalPages = Math.max(1, Math.ceil(sorted.length / pageSize));
const currentPage = Math.min(page, totalPages);
const start = (currentPage - 1) * pageSize;
const pageItems = filtered.slice(start, start + pageSize);
const pageItems = sorted.slice(start, start + pageSize);
const selectablePageItems = useMemo(
() => pageItems.filter((file) => !isRuntimeOnlyAuthFile(file)),
[pageItems]
@@ -559,6 +576,21 @@ export function AuthFilesPage() {
}}
/>
</div>
<div className={styles.filterItem}>
<label>{t('auth_files.sort_label')}</label>
<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>
</div>
<div className={`${styles.filterItem} ${styles.filterToggleItem}`}>
<label>{t('auth_files.problem_filter_label')}</label>
<div className={styles.filterToggle}>
@@ -615,7 +647,7 @@ export function AuthFilesPage() {
</div>
)}
{!loading && filtered.length > pageSize && (
{!loading && sorted.length > pageSize && (
<div className={styles.pagination}>
<Button
variant="secondary"
@@ -629,7 +661,7 @@ export function AuthFilesPage() {
{t('auth_files.pagination_info', {
current: currentPage,
total: totalPages,
count: filtered.length,
count: sorted.length,
})}
</div>
<Button