From 0fdad71c57c9173b47efb86a5cf4caa551d7df96 Mon Sep 17 00:00:00 2001 From: yuzhiran-dev Date: Mon, 18 May 2026 10:37:58 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20P0d=20=E4=BC=9A=E5=91=98=E5=AE=9A?= =?UTF-8?q?=E4=BB=B7=E9=A1=B5=E9=87=8D=E6=9E=84=20=E2=80=94=20=E7=94=A8?= =?UTF-8?q?=E9=87=8F=E5=8F=AF=E8=A7=86=E5=8C=96=20+=20=E6=96=B9=E6=A1=88?= =?UTF-8?q?=E5=AF=B9=E6=AF=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 三栏定价卡片(免费/月卡/年卡),参考 opencode Go 页面风格 - 日配额用量进度条 - 功能对比表(沙盒/模型/提示词/课程/广告) - 当前方案标记 + 最受欢迎标记 - useT 全量翻译 - 补充中/英 30+ 定价相关翻译 key --- frontend/src/app/my/member/page.tsx | 167 ++++++++++++++-------------- frontend/src/i18n/locales/en.ts | 32 ++++++ frontend/src/i18n/locales/zh.ts | 32 ++++++ 3 files changed, 147 insertions(+), 84 deletions(-) diff --git a/frontend/src/app/my/member/page.tsx b/frontend/src/app/my/member/page.tsx index 5118ea9..4c8f08b 100644 --- a/frontend/src/app/my/member/page.tsx +++ b/frontend/src/app/my/member/page.tsx @@ -4,61 +4,58 @@ import { useEffect, useState } from 'react'; import Link from 'next/link'; import { apiFetch } from '../../../lib/auth'; import { Skeleton } from '@/components/ui/skeleton'; +import { useT } from '@/i18n'; interface Subscription { - id: number; - plan: string; - startDate: string; - endDate: string; - status: string; + id: number; plan: string; startDate: string; endDate: string; status: string; } interface Order { - id: number; - orderNo: string; - amount: number; - planType: string; - status: string; - createdAt: string; + id: number; orderNo: string; amount: number; planType: string; status: string; createdAt: string; } +const PLANS = [ + { id: 'FREE', nameKey: 'planFree' as const, price: 0, period: '', popular: false, features: ['featureSandboxFree', 'featureModelsFree', 'featurePromptsFree', 'featureCoursesFree', 'featureAdsFree'] as const }, + { id: 'MONTHLY', nameKey: 'planMonthly' as const, price: 29.9, period: 'perMonth', popular: true, features: ['featureSandboxPro', 'featureModelsPro', 'featurePromptsPro', 'featureCoursesPro', 'featureAdsPro'] as const }, + { id: 'YEARLY', nameKey: 'planYearly' as const, price: 199, period: 'perYear', popular: false, features: ['featureSandboxUnlimited', 'featureModelsPremium', 'featurePromptsPremium', 'featureCoursesPremium', 'featureAdsPremium'] as const }, +]; + +const FEATURE_LABELS = ['featureSandbox', 'featureModels', 'featurePrompts', 'featureCourses', 'featureAds'] as const; + export default function MemberPage() { + const t = useT(); const [subscription, setSubscription] = useState(null); const [orders, setOrders] = useState([]); + const [quota, setQuota] = useState<{ used: number; remaining: number; dailyLimit: number } | null>(null); const [loading, setLoading] = useState(true); - const [payLoading, setPayLoading] = useState(false); + const [payLoading, setPayLoading] = useState(null); - useEffect(() => { - loadData(); - }, []); + useEffect(() => { loadData(); }, []); async function loadData() { try { - const [subRes, ordersRes] = await Promise.all([ + const [subRes, ordersRes, quotaRes] = await Promise.all([ apiFetch('/subscriptions/current').catch(() => ({ ok: false })), apiFetch('/orders'), + apiFetch('/sandbox/quota'), ]); - - if (subRes.ok) { - const subData = await subRes.json(); - setSubscription(subData); - } - + if (subRes.ok) setSubscription(await subRes.json()); const ordersData = await ordersRes.json(); setOrders(ordersData.items || []); + const quotaData = await quotaRes.json(); + if (quotaData.remaining !== undefined) setQuota(quotaData); } catch (e) { console.error(e) } setLoading(false); } async function handleSubscribe(planType: string) { - setPayLoading(true); + setPayLoading(planType); try { const res = await apiFetch('/orders/create', { method: 'POST', body: JSON.stringify({ amount: planType === 'MONTHLY' ? 29.9 : 199, - planType, - payChannel: 'wxpay', + planType, payChannel: 'wxpay', }), }); const data = await res.json(); @@ -67,91 +64,93 @@ export default function MemberPage() { loadData(); } } catch (e) { console.error(e) } - setPayLoading(false); + setPayLoading(null); } if (loading) return (
- -
- - - + + +
+ {[1,2,3].map(i => )}
-
); return (
- - ← 返回我的 - -

会员中心

-

管理你的会员订阅

+ ← 返回我的 +

{t.member.title}

+

{t.member.desc}

-
-

当前会员

- {subscription ? ( -
-
- - {subscription.plan === 'YEARLY' ? '年卡会员' : '月卡会员'} - - - 到期时间:{new Date(subscription.endDate).toLocaleDateString()} - -
-
- 会员权益:全部课程 + 不限次沙箱 + 专属提示词库 + 去广告 -
+ {quota && ( +
+
+ {t.member.dailyQuota} + {t.member.used.replace('{n}', String(quota.used))} / {quota.dailyLimit}
- ) : ( -
-

你当前是免费用户

-
- - -
+
+
- )} +
+ )} + +
+ {PLANS.map(plan => { + const isCurrent = subscription?.plan === plan.id; + return ( +
+ {plan.popular && !isCurrent && ( + {t.member.popular} + )} + {isCurrent && ( + {t.member.currentPlan_badge} + )} +

{t.member[plan.nameKey]}

+
+ {plan.price > 0 ? ( + {plan.price === 29.9 ? t.member.priceMonthly : t.member.priceYearly}{t.member[plan.period]} + ) : ( + ¥0 + )} +
+
+ {(plan.id === 'FREE' ? FEATURE_LABELS : FEATURE_LABELS).map((f, fi) => ( +
+ + {t.member[f]} + {t.member[plan.features[fi]]} +
+ ))} +
+ {plan.id !== 'FREE' && ( + + )} +
+ ); + })}
-
-

订单记录

+
+

{t.member.orderHistory}

{orders.length === 0 ? ( -

暂无订单记录

+

{t.member.noOrders}

) : (
{orders.map(order => ( -
+
-
{order.planType === 'MONTHLY' ? '月卡会员' : '年卡会员'}
+
{t.member[order.planType === 'YEARLY' ? 'yearly' : 'monthly']}
{new Date(order.createdAt).toLocaleDateString()}
¥{order.amount}
- + {order.status}
diff --git a/frontend/src/i18n/locales/en.ts b/frontend/src/i18n/locales/en.ts index e591d14..990b9b3 100644 --- a/frontend/src/i18n/locales/en.ts +++ b/frontend/src/i18n/locales/en.ts @@ -97,6 +97,38 @@ const en: Translations = { orderHistory: 'Order History', noOrders: 'No orders yet', processing: 'Processing...', + planFree: 'Free', + planMonthly: 'Monthly', + planYearly: 'Yearly', + priceMonthly: '¥29.9', + priceYearly: '¥199', + perMonth: '/mo', + perYear: '/yr', + popular: 'Most Popular', + featureSandbox: 'AI Sandbox', + featureSandboxFree: '10/day', + featureSandboxPro: '100/day', + featureSandboxUnlimited: 'Unlimited', + featureModels: 'Models', + featureModelsFree: '1 model', + featureModelsPro: '2 models', + featureModelsPremium: 'All models', + featurePrompts: 'Prompts', + featurePromptsFree: 'Basic', + featurePromptsPro: 'All', + featurePromptsPremium: 'All + Exclusive', + featureCourses: 'Courses', + featureCoursesFree: 'Some free', + featureCoursesPro: 'All', + featureCoursesPremium: 'All', + featureAds: 'Ads', + featureAdsFree: 'Yes', + featureAdsPro: 'Ad-free', + featureAdsPremium: 'Ad-free', + dailyQuota: 'Daily Quota Usage', + used: '{n} used', + subscribe: 'Subscribe', + currentPlan_badge: 'Current Plan', }, compare: { title: 'Compare Lab', diff --git a/frontend/src/i18n/locales/zh.ts b/frontend/src/i18n/locales/zh.ts index 0db7e91..8c8cbcd 100644 --- a/frontend/src/i18n/locales/zh.ts +++ b/frontend/src/i18n/locales/zh.ts @@ -95,6 +95,38 @@ const zh = { orderHistory: '订单记录', noOrders: '暂无订单记录', processing: '处理中...', + planFree: '免费', + planMonthly: '月卡', + planYearly: '年卡', + priceMonthly: '¥29.9', + priceYearly: '¥199', + perMonth: '/月', + perYear: '/年', + popular: '最受欢迎', + featureSandbox: 'AI 沙盒', + featureSandboxFree: '10 次/日', + featureSandboxPro: '100 次/日', + featureSandboxUnlimited: '不限次', + featureModels: '模型选择', + featureModelsFree: '1 个模型', + featureModelsPro: '2 个模型', + featureModelsPremium: '全部模型', + featurePrompts: '提示词库', + featurePromptsFree: '基础', + featurePromptsPro: '全部', + featurePromptsPremium: '全部 + 专属', + featureCourses: '课程学习', + featureCoursesFree: '部分免费', + featureCoursesPro: '全部', + featureCoursesPremium: '全部', + featureAds: '广告', + featureAdsFree: '有广告', + featureAdsPro: '去广告', + featureAdsPremium: '去广告', + dailyQuota: '日配额用量', + used: '已用 {n} 次', + subscribe: '开通', + currentPlan_badge: '当前方案', }, compare: { title: '对比实验室',