fix: 有效期30天不再显示"包月"、我的订阅移到tab外、确认订单展示完整信息

1. subscription-utils: day=30 不再特殊处理为"包月",直接显示"30天"
2. pay page: "我的订阅"移到 tab 外部,按量付费和包月套餐下都可见
3. SubscriptionConfirm: 展示平台badge、倍率、限额grid、OpenAI messages调度信息
This commit is contained in:
erio
2026-03-14 01:57:09 +08:00
parent 14ec33fc69
commit d8078eb38c
3 changed files with 161 additions and 98 deletions

View File

@@ -684,27 +684,6 @@ function PayContent() {
/>
)}
{/* 用户已有订阅 */}
{userSubscriptions.length > 0 && (
<div className="mt-8">
<h3 className={['text-lg font-semibold mb-3', isDark ? 'text-slate-200' : 'text-slate-800'].join(' ')}>
{pickLocaleText(locale, '我的订阅', 'My Subscriptions')}
</h3>
<UserSubscriptions
subscriptions={userSubscriptions}
onRenew={(groupId) => {
const plan = plans.find((p) => p.groupId === groupId);
if (plan) {
setSelectedPlan(plan);
setMainTab('subscribe');
}
}}
isDark={isDark}
locale={locale}
/>
</div>
)}
{renderHelpSection()}
</div>
)}
@@ -723,28 +702,31 @@ function PayContent() {
))}
</div>
{/* 用户已有订阅 */}
{userSubscriptions.length > 0 && (
<div className="mt-8">
<h3 className={['text-lg font-semibold mb-3', isDark ? 'text-slate-200' : 'text-slate-800'].join(' ')}>
{pickLocaleText(locale, '我的订阅', 'My Subscriptions')}
</h3>
<UserSubscriptions
subscriptions={userSubscriptions}
onRenew={(groupId) => {
const plan = plans.find((p) => p.groupId === groupId);
if (plan) setSelectedPlan(plan);
}}
isDark={isDark}
locale={locale}
/>
</div>
)}
{renderHelpSection()}
</div>
)}
{/* 用户已有订阅 — 所有 tab 共用 */}
{userSubscriptions.length > 0 && (
<div className="mt-8">
<h3 className={['text-lg font-semibold mb-3', isDark ? 'text-slate-200' : 'text-slate-800'].join(' ')}>
{pickLocaleText(locale, '我的订阅', 'My Subscriptions')}
</h3>
<UserSubscriptions
subscriptions={userSubscriptions}
onRenew={(groupId) => {
const plan = plans.find((p) => p.groupId === groupId);
if (plan) {
setSelectedPlan(plan);
setMainTab('subscribe');
}
}}
isDark={isDark}
locale={locale}
/>
</div>
)}
<PurchaseFlow isDark={isDark} locale={locale} />
</>
)}

View File

@@ -7,6 +7,7 @@ import { pickLocaleText } from '@/lib/locale';
import { getPaymentTypeLabel, getPaymentIconSrc } from '@/lib/pay-utils';
import type { PlanInfo } from '@/components/SubscriptionPlanCard';
import { formatValidityLabel } from '@/lib/subscription-utils';
import { PlatformBadge } from '@/lib/platform-style';
interface SubscriptionConfirmProps {
plan: PlanInfo;
@@ -31,6 +32,14 @@ export default function SubscriptionConfirm({
const periodLabel = formatValidityLabel(plan.validityDays, plan.validityUnit ?? 'day', locale);
const hasLimits = plan.limits && (
plan.limits.daily_limit_usd !== null ||
plan.limits.weekly_limit_usd !== null ||
plan.limits.monthly_limit_usd !== null
);
const isOpenAI = plan.platform?.toLowerCase() === 'openai';
const handleSubmit = () => {
if (selectedPayment && !loading) {
onSubmit(selectedPayment);
@@ -59,73 +68,148 @@ export default function SubscriptionConfirm({
{pickLocaleText(locale, '确认订单', 'Confirm Order')}
</h2>
{/* Plan info card */}
{/* Plan detail card */}
<div
className={[
'rounded-xl border p-4 space-y-3',
isDark ? 'border-slate-700 bg-slate-800/80' : 'border-slate-200 bg-slate-50',
'rounded-2xl border p-5 space-y-4',
isDark ? 'border-slate-700 bg-slate-800/80' : 'border-slate-200 bg-white',
].join(' ')}
>
<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}
</span>
<span
className={[
'rounded-full px-2 py-0.5 text-xs font-medium',
isDark ? 'bg-emerald-900/40 text-emerald-300' : 'bg-emerald-50 text-emerald-700',
].join(' ')}
>
{periodLabel}
</span>
</div>
{/* Header: Platform badge + Name + Period */}
<div className="flex items-center gap-2 flex-wrap">
{plan.platform && <PlatformBadge platform={plan.platform} />}
<span className={['text-lg font-bold', isDark ? 'text-slate-100' : 'text-slate-900'].join(' ')}>
{plan.name}
</span>
<span
className={[
'rounded-full px-2.5 py-0.5 text-xs font-medium',
isDark ? 'bg-emerald-900/40 text-emerald-300' : 'bg-emerald-50 text-emerald-700',
].join(' ')}
>
{periodLabel}
</span>
</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>
)}
{/* Price */}
<div className="flex items-baseline gap-2">
{plan.originalPrice !== null && (
<span className={['text-sm line-through', isDark ? 'text-slate-500' : 'text-slate-400'].join(' ')}>
¥{plan.originalPrice}
</span>
)}
<span className="text-2xl font-bold text-emerald-500">¥{plan.price}</span>
</div>
{/* Description */}
{plan.description && (
<p className={['text-sm leading-relaxed', isDark ? 'text-slate-400' : 'text-slate-500'].join(' ')}>
{plan.description}
</p>
)}
{/* Rate + Limits grid */}
{(plan.rateMultiplier != null || hasLimits) && (
<div className="grid grid-cols-2 gap-3">
{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>
<span className={['text-xs', isDark ? 'text-slate-500' : 'text-slate-400'].join(' ')}>
{pickLocaleText(locale, '倍率', 'Rate')}
</span>
<div className="flex items-baseline">
<span className="text-lg font-bold text-emerald-500">1</span>
<span className={['mx-1 text-base', isDark ? 'text-slate-500' : 'text-slate-400'].join(' ')}>:</span>
<span className="text-lg font-bold text-emerald-500">{plan.rateMultiplier}</span>
</div>
</div>
)}
{plan.limits?.daily_limit_usd != null && (
<div>
<span className={['text-xs', isDark ? 'text-slate-500' : 'text-slate-400'].join(' ')}>
{pickLocaleText(locale, '日限额', 'Daily Limit')}
</span>
<div className={['text-lg font-semibold', isDark ? 'text-slate-200' : 'text-slate-800'].join(' ')}>
${plan.limits.daily_limit_usd}
</div>
</div>
)}
{plan.limits?.weekly_limit_usd != null && (
<div>
<span className={['text-xs', isDark ? 'text-slate-500' : 'text-slate-400'].join(' ')}>
{pickLocaleText(locale, '周限额', 'Weekly Limit')}
</span>
<div className={['text-lg font-semibold', isDark ? 'text-slate-200' : 'text-slate-800'].join(' ')}>
${plan.limits.weekly_limit_usd}
</div>
</div>
)}
{plan.limits?.monthly_limit_usd != null && (
<div>
<span className={['text-xs', isDark ? 'text-slate-500' : 'text-slate-400'].join(' ')}>
{pickLocaleText(locale, '月限额', 'Monthly Limit')}
</span>
<div className={['text-lg font-semibold', isDark ? 'text-slate-200' : 'text-slate-800'].join(' ')}>
${plan.limits.monthly_limit_usd}
</div>
</div>
)}
</div>
)}
{/* OpenAI specific: messages dispatch + default model */}
{isOpenAI && (
<div className={[
'rounded-lg border p-3',
isDark ? 'border-green-500/20 bg-green-500/5' : 'border-green-500/20 bg-green-50/50',
].join(' ')}>
<div className="space-y-1.5 text-sm">
<div className="flex items-center justify-between">
<span className={isDark ? 'text-slate-400' : 'text-slate-500'}>
{pickLocaleText(locale, '/v1/messages 调度', '/v1/messages Dispatch')}
</span>
<span className={[
'rounded-full px-2 py-0.5 text-xs font-medium',
plan.allowMessagesDispatch
? isDark ? 'bg-green-500/20 text-green-300' : 'bg-green-100 text-green-700'
: isDark ? 'bg-slate-700 text-slate-400' : 'bg-slate-100 text-slate-500',
].join(' ')}>
{plan.allowMessagesDispatch
? pickLocaleText(locale, '已启用', 'Enabled')
: pickLocaleText(locale, '未启用', 'Disabled')}
</span>
</div>
{plan.defaultMappedModel && (
<div className="flex items-center justify-between">
<span className={isDark ? 'text-slate-400' : 'text-slate-500'}>
{pickLocaleText(locale, '默认模型', 'Default Model')}
</span>
<span className={['text-xs font-mono', isDark ? 'text-slate-300' : 'text-slate-700'].join(' ')}>
{plan.defaultMappedModel}
</span>
</div>
)}
</div>
</div>
)}
{/* Features */}
{plan.features.length > 0 && (
<ul className="space-y-1">
{plan.features.map((feature) => (
<li key={feature} className={['flex items-center gap-1.5 text-sm', isDark ? 'text-slate-400' : 'text-slate-500'].join(' ')}>
<svg className="h-3.5 w-3.5 shrink-0 text-emerald-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2.5}>
<path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
</svg>
{feature}
</li>
))}
</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>
<p className={['mb-2 text-xs', isDark ? 'text-slate-500' : 'text-slate-400'].join(' ')}>
{pickLocaleText(locale, '功能特性', 'Features')}
</p>
<div className="flex flex-wrap gap-1.5">
{plan.features.map((feature) => (
<span
key={feature}
className={[
'rounded-md px-2 py-1 text-xs',
isDark ? 'bg-emerald-500/10 text-emerald-400' : 'bg-emerald-50 text-emerald-700',
].join(' ')}
>
{feature}
</span>
))}
</div>
</div>
)}

View File

@@ -39,8 +39,7 @@ export function formatValidityLabel(
return locale === 'zh' ? `${value}` : `${value} Weeks`;
}
// day
if (value === 30) return locale === 'zh' ? '包月' : 'Monthly';
return locale === 'zh' ? `${value}` : `${value} Days`;
return locale === 'zh' ? `${value}` : `${value} Days`;
}
/**
@@ -48,8 +47,7 @@ export function formatValidityLabel(
* - unit=month, value=1 → /月 / /mo
* - unit=month, value=3 → /3月 / /3mo
* - unit=week, value=2 → /2周 / /2wk
* - unit=day, value=30 → / / /mo
* - unit=day, value=90 → /90天 / /90d
* - unit=day, value=30 → /30天 / /30d
*/
export function formatValiditySuffix(
value: number,
@@ -65,7 +63,6 @@ export function formatValiditySuffix(
return locale === 'zh' ? `/${value}` : `/${value}wk`;
}
// day
if (value === 30) return locale === 'zh' ? '/月' : '/mo';
return locale === 'zh' ? `/${value}` : `/${value}d`;
}