feat(s2a): Improve group display layout with grid-based cards

- Replace badge-style group display with grid-based card layout for better readability
- Add group name and ID display in selected groups section with truncation handling
- Reorganize profile card group summary to use dedicated grid section below parameters
- Remove inline group information from parameter summary badge
- Improve visual hierarchy and spacing in group display components
- Enhance dark mode styling for group cards with better contrast and borders
This commit is contained in:
2026-02-07 19:46:30 +08:00
parent eb129a4f85
commit b07200faec

View File

@@ -420,9 +420,8 @@ export default function S2AConfig() {
<Input label="默认优先级" type="number" min={0} max={100} value={formPriority} onChange={(e) => setFormPriority(Number(e.target.value))} hint="数值越大优先级越高" /> <Input label="默认优先级" type="number" min={0} max={100} value={formPriority} onChange={(e) => setFormPriority(Number(e.target.value))} hint="数值越大优先级越高" />
</div> </div>
<div className="space-y-3"> <div className="space-y-3">
{/* 标题 + 获取按钮 */} {/* 获取按钮 */}
<div className="flex items-center justify-between"> <div className="flex items-center justify-end">
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300"></label>
<Button <Button
variant="outline" variant="outline"
onClick={handleFetchGroups} onClick={handleFetchGroups}
@@ -437,15 +436,21 @@ export default function S2AConfig() {
<p className="text-xs text-slate-400"> S2A API Admin Key</p> <p className="text-xs text-slate-400"> S2A API Admin Key</p>
) : null} ) : null}
{/* 已选分组 badges */} {/* 已选分组 */}
<div className="flex flex-wrap gap-2"> <div className="grid grid-cols-2 sm:grid-cols-3 gap-2">
{formGroupIds.map(id => ( {formGroupIds.map(id => {
<span key={id} className="inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-sm bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400"> const group = availableGroups.find(g => g.id === id)
{getGroupLabel(id)} return (
<button onClick={() => handleRemoveGroupId(id)} className="hover:text-red-500 transition-colors"><X className="h-3 w-3" /></button> <div key={id} className="flex items-center justify-between px-3 py-2 rounded-lg bg-blue-50 dark:bg-blue-900/20 border border-blue-100 dark:border-blue-800/40">
</span> <div className="flex flex-col min-w-0">
))} <span className="text-sm font-medium text-blue-700 dark:text-blue-300 truncate">{group ? group.name : `#${id}`}</span>
{formGroupIds.length === 0 && <span className="text-sm text-slate-400"></span>} {group && <span className="text-[10px] text-blue-400 dark:text-blue-500">#{id}</span>}
</div>
<button onClick={() => handleRemoveGroupId(id)} className="ml-2 text-blue-400 hover:text-red-500 transition-colors flex-shrink-0"><X className="h-3.5 w-3.5" /></button>
</div>
)
})}
{formGroupIds.length === 0 && <span className="text-sm text-slate-400 col-span-full"></span>}
</div> </div>
{/* 可选分组列表 */} {/* 可选分组列表 */}
@@ -621,18 +626,13 @@ function ProfileCard({ profile, isActive, isActivating, groupNameCache, onActiva
</div> </div>
{/* 参数摘要 */} {/* 参数摘要 */}
<div className="flex flex-wrap gap-1.5 mb-4"> <div className="flex flex-wrap gap-1.5 mb-2">
<span className="inline-flex items-center px-2 py-0.5 rounded text-xs bg-slate-100 text-slate-600 dark:bg-slate-700 dark:text-slate-300"> <span className="inline-flex items-center px-2 py-0.5 rounded text-xs bg-slate-100 text-slate-600 dark:bg-slate-700 dark:text-slate-300">
{profile.concurrency} {profile.concurrency}
</span> </span>
<span className="inline-flex items-center px-2 py-0.5 rounded text-xs bg-slate-100 text-slate-600 dark:bg-slate-700 dark:text-slate-300"> <span className="inline-flex items-center px-2 py-0.5 rounded text-xs bg-slate-100 text-slate-600 dark:bg-slate-700 dark:text-slate-300">
{profile.priority} {profile.priority}
</span> </span>
{parsedGroups.length > 0 && (
<span className="inline-flex items-center px-2 py-0.5 rounded text-xs bg-blue-50 text-blue-600 dark:bg-blue-900/30 dark:text-blue-400">
{parsedGroups.map(id => groupNameCache[id] ? `${groupNameCache[id]} #${id}` : `#${id}`).join(', ')}
</span>
)}
{profile.proxy_enabled && ( {profile.proxy_enabled && (
<span className="inline-flex items-center px-2 py-0.5 rounded text-xs bg-orange-50 text-orange-600 dark:bg-orange-900/30 dark:text-orange-400"> <span className="inline-flex items-center px-2 py-0.5 rounded text-xs bg-orange-50 text-orange-600 dark:bg-orange-900/30 dark:text-orange-400">
<Globe className="h-3 w-3 mr-0.5" /> <Globe className="h-3 w-3 mr-0.5" />
@@ -640,6 +640,18 @@ function ProfileCard({ profile, isActive, isActivating, groupNameCache, onActiva
)} )}
</div> </div>
{/* 分组列表 */}
{parsedGroups.length > 0 && (
<div className="grid grid-cols-2 gap-2 mb-4">
{parsedGroups.map(id => (
<div key={id} className="flex flex-col px-2.5 py-1.5 rounded-lg bg-blue-50 dark:bg-blue-900/20 border border-blue-100 dark:border-blue-800/40">
<span className="text-xs font-medium text-blue-700 dark:text-blue-300 truncate">{groupNameCache[id] || `#${id}`}</span>
{groupNameCache[id] && <span className="text-[10px] text-blue-400 dark:text-blue-500">#{id}</span>}
</div>
))}
</div>
)}
{/* 操作按钮 */} {/* 操作按钮 */}
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{!isActive ? ( {!isActive ? (