From eb129a4f859e7e3244356535faef0a1483920674 Mon Sep 17 00:00:00 2001 From: kyx236 Date: Sat, 7 Feb 2026 19:12:48 +0800 Subject: [PATCH] feat(s2a): Improve group name caching and display with api_base isolation - Refactor groupNameCache structure to isolate caches by api_base: { [apiBase]: { [groupId]: groupName } } - Add normalizeBase() utility to remove trailing slashes and prevent cache key mismatches - Update group fetching logic to deduplicate requests by api_base and admin_key combination - Auto-fetch available groups when entering edit mode for a profile with valid credentials - Enhance group label display to show "name #id" format instead of just name or #id - Sort profiles in list view to display active profile first - Update ProfileCard to use normalized api_base for groupNameCache lookup - Simplify group label rendering by removing conditional groupsFetched check --- frontend/src/pages/S2AConfig.tsx | 49 +++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/frontend/src/pages/S2AConfig.tsx b/frontend/src/pages/S2AConfig.tsx index d2429d9..56e5489 100644 --- a/frontend/src/pages/S2AConfig.tsx +++ b/frontend/src/pages/S2AConfig.tsx @@ -33,8 +33,8 @@ export default function S2AConfig() { const [fetchingGroups, setFetchingGroups] = useState(false) const [groupsFetched, setGroupsFetched] = useState(false) - // 列表视图分组名称缓存: { [groupId]: groupName } - const [groupNameCache, setGroupNameCache] = useState>({}) + // 列表视图分组名称缓存: { [apiBase]: { [groupId]: groupName } } + const [groupNameCache, setGroupNameCache] = useState>>({}) const [saving, setSaving] = useState(false) const [testing, setTesting] = useState(false) @@ -68,25 +68,30 @@ export default function S2AConfig() { Promise.all([fetchProfiles(), fetchActiveConfig()]).finally(() => setLoading(false)) }, [fetchProfiles, fetchActiveConfig]) - // 列表视图: 批量获取各 profile 的分组名称 + // 规范化 api_base: 去掉尾部斜杠,防止缓存 key 不匹配 + const normalizeBase = (base: string) => base.replace(/\/+$/, '') + + // 列表视图: 批量获取各 profile 的分组名称(按 api_base 隔离) useEffect(() => { if (viewMode !== 'list' || profiles.length === 0) return const fetchGroupNames = async () => { - const cache: Record = {} + const cache: Record> = {} // 按 api_base+admin_key 去重,避免重复请求 const seen = new Map() for (const p of profiles) { if (p.api_base && p.admin_key) { - const key = `${p.api_base}|${p.admin_key}` + const key = `${normalizeBase(p.api_base)}|${p.admin_key}` if (!seen.has(key)) seen.set(key, p) } } for (const p of seen.values()) { try { const groups = await fetchGroupsWithCredentials(p.api_base, p.admin_key) + const map: Record = {} for (const g of groups) { - cache[g.id] = g.name + map[g.id] = g.name } + cache[normalizeBase(p.api_base)] = map } catch { /* ignore - will show #id fallback */ } } setGroupNameCache(cache) @@ -141,9 +146,21 @@ export default function S2AConfig() { setFormProxyAddress(profile.proxy_address || '') setTestResult(null) setAvailableGroups([]) - setFetchingGroups(false) setGroupsFetched(false) setViewMode('edit') + // 自动获取分组列表 + if (profile.api_base && profile.admin_key) { + setFetchingGroups(true) + fetchGroupsWithCredentials(profile.api_base, profile.admin_key) + .then(groups => { + setAvailableGroups(groups) + setGroupsFetched(true) + }) + .catch(() => { /* ignore - will show #id fallback */ }) + .finally(() => setFetchingGroups(false)) + } else { + setFetchingGroups(false) + } } // 返回列表 @@ -279,10 +296,10 @@ export default function S2AConfig() { } } - // 获取分组名称 - const getGroupName = (id: number): string => { + // 获取分组显示文字: "name #id" 或 "#id" + const getGroupLabel = (id: number): string => { const group = availableGroups.find(g => g.id === id) - return group ? group.name : `#${id}` + return group ? `${group.name} #${id}` : `#${id}` } const handleAddGroupId = () => { @@ -424,7 +441,7 @@ export default function S2AConfig() {
{formGroupIds.map(id => ( - {groupsFetched ? getGroupName(id) : `#${id}`} + {getGroupLabel(id)} ))} @@ -549,13 +566,17 @@ export default function S2AConfig() { ) : (
- {profiles.map(profile => ( + {[...profiles].sort((a, b) => { + const aActive = a.api_base === activeApiBase ? 1 : 0 + const bActive = b.api_base === activeApiBase ? 1 : 0 + return bActive - aActive + }).map(profile => ( handleActivate(profile)} onEdit={() => handleEdit(profile)} onDelete={() => handleDelete(profile.id, profile.name)} @@ -609,7 +630,7 @@ function ProfileCard({ profile, isActive, isActivating, groupNameCache, onActiva {parsedGroups.length > 0 && ( - 分组 {parsedGroups.map(id => groupNameCache[id] || `#${id}`).join(', ')} + 分组 {parsedGroups.map(id => groupNameCache[id] ? `${groupNameCache[id]} #${id}` : `#${id}`).join(', ')} )} {profile.proxy_enabled && (