feat: 金额上限校验、订阅详情展示优化、支付商品名称区分

- 硬编码 MAX_AMOUNT=99999999.99,所有金额输入(API+前端)统一校验上限
- 管理后台订阅列表改为卡片布局,Sub2API 分组信息嵌套只读展示(平台/倍率/限额/模型)
- 用户端套餐卡片和确认页展示平台、倍率、用量限制
- 订阅订单支付商品名改为 "Sub2API 订阅 {分组名}",余额充值保持原格式
This commit is contained in:
erio
2026-03-13 23:40:23 +08:00
parent ca03a501f2
commit 1bb11ee32b
8 changed files with 275 additions and 106 deletions

View File

@@ -62,11 +62,11 @@ export default function SubscriptionConfirm({
{/* Plan info card */}
<div
className={[
'rounded-xl border p-4',
'rounded-xl border p-4 space-y-3',
isDark ? 'border-slate-700 bg-slate-800/80' : 'border-slate-200 bg-slate-50',
].join(' ')}
>
<div className="mb-2 flex items-center justify-between">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<span className={['text-base font-semibold', isDark ? 'text-slate-100' : 'text-slate-900'].join(' ')}>
{plan.name}
@@ -81,6 +81,24 @@ export default function SubscriptionConfirm({
</span>
</div>
</div>
{/* Platform & Rate tags */}
{(plan.platform || plan.rateMultiplier != null) && (
<div className={['flex flex-wrap gap-2 text-xs', isDark ? 'text-slate-400' : 'text-slate-500'].join(' ')}>
{plan.platform && (
<span className={['inline-flex items-center rounded-md px-2 py-0.5', isDark ? 'bg-slate-700/60' : 'bg-slate-100'].join(' ')}>
{pickLocaleText(locale, '平台', 'Platform')}: {plan.platform}
</span>
)}
{plan.rateMultiplier != null && (
<span className={['inline-flex items-center rounded-md px-2 py-0.5', isDark ? 'bg-slate-700/60' : 'bg-slate-100'].join(' ')}>
{pickLocaleText(locale, '倍率', 'Rate')}: {plan.rateMultiplier}x
</span>
)}
</div>
)}
{/* Features */}
{plan.features.length > 0 && (
<ul className="space-y-1">
{plan.features.map((feature) => (
@@ -93,6 +111,24 @@ export default function SubscriptionConfirm({
))}
</ul>
)}
{/* Usage limits */}
{plan.limits && (plan.limits.daily_limit_usd != null || plan.limits.weekly_limit_usd != null || plan.limits.monthly_limit_usd != null) && (
<div className={['rounded-lg p-2.5 text-xs', isDark ? 'bg-slate-900/60 text-slate-400' : 'bg-white/80 text-slate-500'].join(' ')}>
<p className="mb-1 font-medium">{pickLocaleText(locale, '用量限制', 'Usage Limits')}</p>
<div className="space-y-0.5">
{plan.limits.daily_limit_usd != null && (
<p>{pickLocaleText(locale, `每日: $${plan.limits.daily_limit_usd}`, `Daily: $${plan.limits.daily_limit_usd}`)}</p>
)}
{plan.limits.weekly_limit_usd != null && (
<p>{pickLocaleText(locale, `每周: $${plan.limits.weekly_limit_usd}`, `Weekly: $${plan.limits.weekly_limit_usd}`)}</p>
)}
{plan.limits.monthly_limit_usd != null && (
<p>{pickLocaleText(locale, `每月: $${plan.limits.monthly_limit_usd}`, `Monthly: $${plan.limits.monthly_limit_usd}`)}</p>
)}
</div>
</div>
)}
</div>
{/* Payment method selector */}

View File

@@ -8,6 +8,7 @@ import { formatValidityLabel, formatValiditySuffix, type ValidityUnit } from '@/
export interface PlanInfo {
id: string;
groupId: number;
groupName: string | null;
name: string;
price: number;
originalPrice: number | null;
@@ -15,6 +16,8 @@ export interface PlanInfo {
validityUnit?: ValidityUnit;
features: string[];
description: string | null;
platform: string | null;
rateMultiplier: number | null;
limits: {
daily_limit_usd: number | null;
weekly_limit_usd: number | null;
@@ -76,6 +79,22 @@ export default function SubscriptionPlanCard({ plan, onSubscribe, isDark, locale
</p>
)}
{/* Platform & Rate */}
{(plan.platform || plan.rateMultiplier != null) && (
<div className={['mb-3 flex flex-wrap gap-2 text-xs', isDark ? 'text-slate-400' : 'text-slate-500'].join(' ')}>
{plan.platform && (
<span className={['inline-flex items-center gap-1 rounded-md px-2 py-0.5', isDark ? 'bg-slate-700/60' : 'bg-slate-100'].join(' ')}>
{pickLocaleText(locale, '平台', 'Platform')}: {plan.platform}
</span>
)}
{plan.rateMultiplier != null && (
<span className={['inline-flex items-center gap-1 rounded-md px-2 py-0.5', isDark ? 'bg-slate-700/60' : 'bg-slate-100'].join(' ')}>
{pickLocaleText(locale, '倍率', 'Rate')}: {plan.rateMultiplier}x
</span>
)}
</div>
)}
{/* Features */}
{plan.features.length > 0 && (
<ul className="mb-4 space-y-2">