feat: 渠道/订阅管理UI优化 — 平台图标、布局改善、分组信息卡片
- 渠道管理:平台列用 PlatformBadge 带图标,分类下拉显示 label - 渠道管理:添加 antigravity/anthropic 平台选项 - 订阅管理:/v1/messages 调度改为"已启用/未启用"文字 - 订阅管理:编辑 modal 选择分组后展示只读分组信息卡片 - 订阅管理:有效期+单位独立一行,排序单独一行
This commit is contained in:
@@ -4,6 +4,7 @@ import { useSearchParams } from 'next/navigation';
|
||||
import { useState, useEffect, useCallback, Suspense } from 'react';
|
||||
import PayPageLayout from '@/components/PayPageLayout';
|
||||
import { resolveLocale, type Locale } from '@/lib/locale';
|
||||
import { PlatformBadge, getPlatformStyle } from '@/lib/platform-style';
|
||||
|
||||
// ── Types ──
|
||||
|
||||
@@ -44,15 +45,7 @@ interface ChannelFormData {
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
const PLATFORMS = ['claude', 'openai', 'gemini', 'codex', 'sora'] as const;
|
||||
|
||||
const PLATFORM_COLORS: Record<string, { bg: string; text: string }> = {
|
||||
claude: { bg: 'bg-orange-100 dark:bg-orange-900/40', text: 'text-orange-700 dark:text-orange-300' },
|
||||
openai: { bg: 'bg-green-100 dark:bg-green-900/40', text: 'text-green-700 dark:text-green-300' },
|
||||
gemini: { bg: 'bg-blue-100 dark:bg-blue-900/40', text: 'text-blue-700 dark:text-blue-300' },
|
||||
codex: { bg: 'bg-purple-100 dark:bg-purple-900/40', text: 'text-purple-700 dark:text-purple-300' },
|
||||
sora: { bg: 'bg-pink-100 dark:bg-pink-900/40', text: 'text-pink-700 dark:text-pink-300' },
|
||||
};
|
||||
const PLATFORMS = ['claude', 'anthropic', 'openai', 'gemini', 'codex', 'sora', 'antigravity'] as const;
|
||||
|
||||
// ── i18n ──
|
||||
|
||||
@@ -706,7 +699,6 @@ function ChannelsContent() {
|
||||
</thead>
|
||||
<tbody>
|
||||
{channels.map((channel) => {
|
||||
const pc = PLATFORM_COLORS[channel.platform] ?? PLATFORM_COLORS.claude;
|
||||
return (
|
||||
<tr
|
||||
key={channel.id}
|
||||
@@ -722,11 +714,7 @@ function ChannelsContent() {
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-4 py-3">
|
||||
<span
|
||||
className={`inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium ${isDark ? pc.bg.replace('dark:', '') : pc.bg.split(' ')[0]} ${isDark ? pc.text.replace('dark:', '') : pc.text.split(' ')[0]}`}
|
||||
>
|
||||
{channel.platform}
|
||||
</span>
|
||||
<PlatformBadge platform={channel.platform} />
|
||||
</td>
|
||||
<td className={`px-4 py-3 ${isDark ? 'text-slate-300' : 'text-slate-700'}`}>
|
||||
{channel.rateMultiplier}x
|
||||
@@ -849,7 +837,7 @@ function ChannelsContent() {
|
||||
>
|
||||
{PLATFORMS.map((p) => (
|
||||
<option key={p} value={p}>
|
||||
{p}
|
||||
{getPlatformStyle(p).label || p}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
@@ -1019,16 +1007,7 @@ function ChannelsContent() {
|
||||
<span className={`text-xs ${isDark ? 'text-slate-500' : 'text-slate-400'}`}>
|
||||
#{group.id}
|
||||
</span>
|
||||
{(() => {
|
||||
const gpc = PLATFORM_COLORS[group.platform] ?? PLATFORM_COLORS.claude;
|
||||
return (
|
||||
<span
|
||||
className={`inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-medium ${isDark ? gpc.bg.replace('dark:', '') : gpc.bg.split(' ')[0]} ${isDark ? gpc.text.replace('dark:', '') : gpc.text.split(' ')[0]}`}
|
||||
>
|
||||
{group.platform}
|
||||
</span>
|
||||
);
|
||||
})()}
|
||||
<PlatformBadge platform={group.platform} className="text-[10px]" />
|
||||
{alreadyImported && (
|
||||
<span className="text-[10px] text-amber-500 font-medium">{t.syncAlreadyExists}</span>
|
||||
)}
|
||||
|
||||
@@ -918,22 +918,15 @@ function SubscriptionsContent() {
|
||||
{plan.groupPlatform?.toLowerCase() === 'openai' && (
|
||||
<>
|
||||
<div>
|
||||
<span className={isDark ? 'text-slate-500' : 'text-slate-400'}>/v1/messages</span>
|
||||
<div className="mt-0.5">
|
||||
<span className={[
|
||||
'inline-block rounded-full px-1.5 py-0.5 text-[10px] font-medium',
|
||||
plan.groupAllowMessagesDispatch
|
||||
? isDark ? 'bg-green-500/20 text-green-300' : 'bg-green-50 text-green-700'
|
||||
: isDark ? 'bg-slate-700 text-slate-400' : 'bg-slate-100 text-slate-500',
|
||||
].join(' ')}>
|
||||
{plan.groupAllowMessagesDispatch ? '✓' : '✗'}
|
||||
</span>
|
||||
<span className={isDark ? 'text-slate-500' : 'text-slate-400'}>/v1/messages 调度</span>
|
||||
<div className={['mt-0.5 text-xs font-medium', plan.groupAllowMessagesDispatch ? 'text-green-600 dark:text-green-400' : isDark ? 'text-slate-400' : 'text-slate-500'].join(' ')}>
|
||||
{plan.groupAllowMessagesDispatch ? '已启用' : '未启用'}
|
||||
</div>
|
||||
</div>
|
||||
{plan.groupDefaultMappedModel && (
|
||||
<div className="sm:col-span-2">
|
||||
<span className={isDark ? 'text-slate-500' : 'text-slate-400'}>默认模型</span>
|
||||
<div className={['font-mono text-[10px]', isDark ? 'text-slate-300' : 'text-slate-600'].join(' ')}>
|
||||
<div className={['mt-0.5 font-mono text-xs', isDark ? 'text-slate-300' : 'text-slate-600'].join(' ')}>
|
||||
{plan.groupDefaultMappedModel}
|
||||
</div>
|
||||
</div>
|
||||
@@ -1199,6 +1192,64 @@ function SubscriptionsContent() {
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Selected group info card (read-only) */}
|
||||
{(() => {
|
||||
const selectedGroup = groups.find((g) => String(g.id) === formGroupId);
|
||||
if (!selectedGroup) return null;
|
||||
return (
|
||||
<div className={['rounded-lg border p-3 text-xs', isDark ? 'border-slate-700 bg-slate-800/60' : 'border-slate-200 bg-slate-50'].join(' ')}>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<span className={['font-medium', isDark ? 'text-slate-300' : 'text-slate-600'].join(' ')}>
|
||||
{t.groupInfo}
|
||||
</span>
|
||||
<span className={['text-[10px]', isDark ? 'text-slate-500' : 'text-slate-400'].join(' ')}>
|
||||
{t.groupInfoReadonly}
|
||||
</span>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-x-4 gap-y-2">
|
||||
{selectedGroup.platform && (
|
||||
<div>
|
||||
<span className={isDark ? 'text-slate-500' : 'text-slate-400'}>{t.platform}</span>
|
||||
<div className="mt-0.5"><PlatformBadge platform={selectedGroup.platform} /></div>
|
||||
</div>
|
||||
)}
|
||||
{selectedGroup.rate_multiplier != null && (
|
||||
<div>
|
||||
<span className={isDark ? 'text-slate-500' : 'text-slate-400'}>{t.rateMultiplier}</span>
|
||||
<div className={isDark ? 'text-slate-300' : 'text-slate-600'}>{selectedGroup.rate_multiplier}x</div>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<span className={isDark ? 'text-slate-500' : 'text-slate-400'}>{t.dailyLimit}</span>
|
||||
<div className={isDark ? 'text-slate-300' : 'text-slate-600'}>
|
||||
{selectedGroup.daily_limit_usd != null ? `$${selectedGroup.daily_limit_usd}` : t.unlimited}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span className={isDark ? 'text-slate-500' : 'text-slate-400'}>{t.weeklyLimit}</span>
|
||||
<div className={isDark ? 'text-slate-300' : 'text-slate-600'}>
|
||||
{selectedGroup.weekly_limit_usd != null ? `$${selectedGroup.weekly_limit_usd}` : t.unlimited}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span className={isDark ? 'text-slate-500' : 'text-slate-400'}>{t.monthlyLimit}</span>
|
||||
<div className={isDark ? 'text-slate-300' : 'text-slate-600'}>
|
||||
{selectedGroup.monthly_limit_usd != null ? `$${selectedGroup.monthly_limit_usd}` : t.unlimited}
|
||||
</div>
|
||||
</div>
|
||||
{selectedGroup.platform?.toLowerCase() === 'openai' && (
|
||||
<div>
|
||||
<span className={isDark ? 'text-slate-500' : 'text-slate-400'}>/v1/messages 调度</span>
|
||||
<div className={['mt-0.5 font-medium', selectedGroup.allow_messages_dispatch ? 'text-green-600 dark:text-green-400' : isDark ? 'text-slate-400' : 'text-slate-500'].join(' ')}>
|
||||
{selectedGroup.allow_messages_dispatch ? '已启用' : '未启用'}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
|
||||
{/* Name */}
|
||||
<div>
|
||||
<label className={labelCls}>{t.fieldName} *</label>
|
||||
@@ -1251,8 +1302,8 @@ function SubscriptionsContent() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Valid days + Unit + Sort */}
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
{/* Valid days + Unit */}
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label className={labelCls}>{t.fieldValidDays}</label>
|
||||
<input
|
||||
@@ -1275,6 +1326,9 @@ function SubscriptionsContent() {
|
||||
<option value="month">{t.unitMonth}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Sort Order */}
|
||||
<div>
|
||||
<label className={labelCls}>{t.fieldSortOrder}</label>
|
||||
<input
|
||||
@@ -1284,7 +1338,6 @@ function SubscriptionsContent() {
|
||||
className={inputCls}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Features */}
|
||||
<div>
|
||||
|
||||
Reference in New Issue
Block a user