From 8a9c2e48685cafe45dc41720f606b367cf7d5362 Mon Sep 17 00:00:00 2001 From: QihuAI Design Date: Sun, 23 Aug 2026 12:28:41 +0800 Subject: [PATCH] =?UTF-8?q?feat(frontend-v2):=20ProfilePage=20=E7=9C=9F?= =?UTF-8?q?=E5=AE=9E=E6=95=B0=E6=8D=AE=E9=A1=B5=20(#987-#990)=20=E6=8E=A5?= =?UTF-8?q?=20profile=20API=206=20=E5=8C=BA=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/pages/front/ProfilePage/index.tsx | 393 ++++++++++++++++++ 1 file changed, 393 insertions(+) create mode 100644 frontend-v2/src/pages/front/ProfilePage/index.tsx diff --git a/frontend-v2/src/pages/front/ProfilePage/index.tsx b/frontend-v2/src/pages/front/ProfilePage/index.tsx new file mode 100644 index 0000000..56734ac --- /dev/null +++ b/frontend-v2/src/pages/front/ProfilePage/index.tsx @@ -0,0 +1,393 @@ +import { useState } from 'react'; +import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Badge } from '@/components/ui/badge'; +import { + User as UserIcon, + Crown, + Coins, + History, + KeyRound, + ShoppingBag, + Loader2, + ChevronLeft, + ChevronRight, + Mail, + Phone, + CalendarDays, + type LucideIcon, +} from 'lucide-react'; +import { + useProfile, + useProfileBalance, + useProfileMembership, + useProfileLoginLogs, + useProfileApiKeys, + useProfilePurchasedApps, +} from '@/hooks/useApi'; + +function StatCard({ + icon: Icon, + label, + value, + hint, + accent, +}: { + icon: LucideIcon; + label: string; + value: string; + hint?: string; + accent: string; +}) { + return ( + + +
+ {label} +
+ +
+
+
{value}
+ {hint &&

{hint}

} +
+
+ ); +} + +function formatNumber(n: number): string { + return n.toLocaleString('zh-CN'); +} + +function formatTime(iso: string | null): string { + if (!iso) return '—'; + const d = new Date(iso); + if (isNaN(d.getTime())) return iso; + return d.toLocaleString('zh-CN', { hour12: false }); +} + +function InfoRow({ icon: Icon, label, value }: { icon: LucideIcon; label: string; value: string }) { + return ( +
+ + {label} + {value} +
+ ); +} + +export function ProfilePage() { + const [logPage, setLogPage] = useState(1); + const logPageSize = 8; + + const { data: profile, isLoading: pLoading } = useProfile(); + const { data: balance, isLoading: bLoading } = useProfileBalance(); + const { data: membership } = useProfileMembership(); + const { data: loginLogs, isLoading: logsLoading, isFetching: logsFetching } = useProfileLoginLogs( + logPage, + logPageSize + ); + const { data: apiKeys, isLoading: keysLoading } = useProfileApiKeys(); + const { data: purchasedApps, isLoading: appsLoading } = useProfilePurchasedApps(); + + const logTotal = loginLogs?.total ?? 0; + const logItems = loginLogs?.items ?? []; + const logTotalPages = Math.max(1, Math.ceil(logTotal / logPageSize)); + + const isExpired = membership?.is_expired ?? false; + const hasMembership = membership?.has_membership ?? false; + + return ( +
+ {/* Header */} +
+
+

个人中心

+

+ 管理账户资料、会员状态、余额与 API 密钥 +

+
+ {profile?.created_at && ( + + 注册于 {formatTime(profile.created_at).split(' ')[0]} + + )} +
+ + {/* Profile card + membership */} +
+ {/* Profile identity */} + + + 账户资料 + 当前登录账户的基础信息 + + +
+
+ {profile?.username?.[0]?.toUpperCase() ?? '—'} +
+
+

+ {pLoading ? '加载中…' : profile?.username ?? '—'} +

+

+ {profile?.role ?? 'user'} · {profile?.status ?? 'active'} +

+
+
+
+
+ + + + +
+ + + + {/* Membership */} + + + + 会员状态 + + 当前订阅与有效期 + + + {!hasMembership ? ( +
+ +

尚未开通会员

+
+ ) : ( + <> +
+ 套餐 + + {isExpired ? '已过期' : '生效中'} + +
+
+ 计划 ID + {membership?.plan_id ?? '—'} +
+
+ 生效 + {formatTime(membership?.start_at ?? null).split(' ')[0]} +
+
+ 到期 + {formatTime(membership?.expire_at ?? null).split(' ')[0]} +
+ + )} +
+
+
+ + {/* Balance stat row */} +
+ + + +
+ + {/* Login logs */} + + +
+ + 登录记录 + + 最近账户登录 IP 与设备信息 +
+
+ +
+ + + + + + + + + + {logsLoading ? ( + + + + ) : logItems.length === 0 ? ( + + + + ) : ( + logItems.map((log) => ( + + + + + + )) + )} + +
时间IPUser Agent
+ +
+ 暂无登录记录 +
+ {formatTime(log.login_at)} + {log.ip} + {log.user_agent} +
+
+ + {/* Pagination */} +
+ + 第 {logPage} / {logTotalPages} 页 · 共 {formatNumber(logTotal)} 条 + +
+ + +
+
+
+
+ + {/* Two-column: API keys + purchased apps */} +
+ {/* API Keys */} + + + + API 密钥 + + 用于第三方集成的访问凭证 + + + {keysLoading ? ( +
+ +
+ ) : !apiKeys || apiKeys.length === 0 ? ( +
尚未创建密钥
+ ) : ( + apiKeys.map((k) => ( +
+
+

{k.name}

+

+ {k.key_preview} +

+
+ + {k.status === 'active' ? '启用' : k.status} + +
+ )) + )} +
+
+ + {/* Purchased apps */} + + + + 已购应用 + + 已下单的应用服务列表 + + + {appsLoading ? ( +
+ +
+ ) : !purchasedApps || purchasedApps.length === 0 ? ( +
暂无已购应用
+ ) : ( + purchasedApps.map((app) => ( +
+ {app.app_icon ? ( + + ) : ( +
+ +
+ )} +
+

{app.app_name}

+

+ {app.app_type} · {formatTime(app.purchased_at).split(' ')[0]} +

+
+ + ¥{formatNumber(app.price)} + +
+ )) + )} +
+
+
+
+ ); +} + +export default ProfilePage;