fix: 有效期30天不再显示"包月"、我的订阅移到tab外、确认订单展示完整信息
1. subscription-utils: day=30 不再特殊处理为"包月",直接显示"30天" 2. pay page: "我的订阅"移到 tab 外部,按量付费和包月套餐下都可见 3. SubscriptionConfirm: 展示平台badge、倍率、限额grid、OpenAI messages调度信息
This commit is contained in:
@@ -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} />
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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`;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user