From de5f505e01b3cede26ec67d9e26d3e2e95e00dc2 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Tue, 15 Sep 2026 08:38:39 +0800 Subject: [PATCH] feat(points): apps/web/src/pages/points/Center.tsx --- apps/web/src/pages/points/Center.tsx | 308 +++++++++++++++++++++++++++ 1 file changed, 308 insertions(+) create mode 100644 apps/web/src/pages/points/Center.tsx diff --git a/apps/web/src/pages/points/Center.tsx b/apps/web/src/pages/points/Center.tsx new file mode 100644 index 000000000..ce340f23b --- /dev/null +++ b/apps/web/src/pages/points/Center.tsx @@ -0,0 +1,308 @@ +/** + * 积分中心主页(/app/points) + * 展示余额、会员信息、本月统计、快捷入口 + */ +import React, { useEffect } from "react" +import { + Card, + Col, + Row, + Statistic, + Button, + Space, + Tag, + Typography, + Progress, + List, + Avatar, + Empty, +} from "antd" +import { + DiamondOutlined, + CrownFilled, + ArrowUpOutlined, + ArrowDownOutlined, + HistoryOutlined, + RechargeOutlined, + FileTextOutlined, + InfoCircleOutlined, +} from "@ant-design/icons" +import { useNavigate } from "react-router-dom" +import PageHead from "@/components/layout/PageHead" +import { usePointsStore } from "@/store/pointsStore" +import "./Points.css" + +const { Title, Text } = Typography + +const SOURCE_NAME: Record = { + recharge: "充值", + task_reward: "任务奖励", + ai_voice: "AI 配音", + ai_digital_human: "AI 数字人", + ai_video: "智能混剪", + voice_clone_train: "声音克隆训练", + voice_clone_synth: "声音克隆合成", + douyin_extract: "抖音提取", + ai_rewrite: "AI 改写", + ai_title: "AI 标题", + ai_cover: "AI 封面", + subscription_bonus: "会员赠送", + admin_adjust: "管理员调整", + refund: "失败退还", +} + +const PointsCenter: React.FC = () => { + const navigate = useNavigate() + const { balance, subscription, init, loading } = usePointsStore() + + useEffect(() => { + init() + }, [init]) + + const bal = balance?.balance ?? 0 + const earned = balance?.total_earned ?? 0 + const spent = balance?.total_spent ?? 0 + const isMember = !!balance?.is_member + const freeUsed = balance?.free_clips_used ?? 0 + const freeLimit = balance?.free_clips_limit ?? 2 + const freeRemain = balance?.free_clips_remaining ?? (isMember ? 0 : 2) + + // 近 5 条流水 mock(实际从 transactions 页加载) + const recentTx = [ + { type: "spend", source: "ai_voice", amount: 1, time: "今天 10:30" }, + { type: "spend", source: "ai_video", amount: 3, time: "今天 09:15" }, + { type: "earn", source: "task_reward", amount: 20, time: "昨天" }, + ] + + return ( +
+ + + + + } + /> + + {/* 顶部大卡 */} + + + + + + 当前可用积分 + +
+ + {loading ? "…" : bal.toLocaleString()} +
+ + {isMember ? ( + } style={{ padding: "4px 10px" }}> + {subscription?.member_type === "yearly" + ? "年卡" + : subscription?.member_type === "quarterly" + ? "季卡" + : "月卡"} + 会员 + + ) : ( + + 免费会员 + + )} + {balance?.member_expires_at && ( + + 到期 {new Date(balance.member_expires_at).toLocaleDateString("zh-CN")} + + )} + {!isMember && ( + + )} + +
+ + + {/* 今日免费额度 */} + {!isMember && freeLimit > 0 && ( +
+
+ 今日免费混剪 +
+ `${freeUsed}/${freeLimit} 条`} + /> + + 剩余 {freeRemain} 条免费混剪,超出部分消耗积分 + +
+ )} + +
+
+ + {/* 统计 */} + + + + } + valueStyle={{ color: "#10b981" }} + /> + + + + + } + valueStyle={{ color: "#ef4444" }} + /> + + + + + } + valueStyle={{ color: "#8b5cf6" }} + /> + + + + + } + valueStyle={{ color: "#f59e0b" }} + /> + + + + + {/* 快捷入口 & 最近流水 */} + + + + + 最近流水 + + } + extra={ navigate("/app/points/transactions")}>查看全部 →} + > + {recentTx.length === 0 ? ( + + ) : ( + ( + + : } + /> + } + title={SOURCE_NAME[item.source] ?? item.source} + description={item.time} + /> +
+ {item.type === "earn" ? "+" : "-"} + {item.amount} +
+
+ )} + /> + )} +
+ + + + + + + + + + + +
+
+ ) +} + +export default PointsCenter +export const Component = PointsCenter