From d03dd7ca42e04e419ab4f630fc1db78842daf77b Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Wed, 16 Sep 2026 10:44:36 +0800 Subject: [PATCH] chore(points): fix lint - apps/web/src/pages/points/Center.tsx --- apps/web/src/pages/points/Center.tsx | 447 +++++++++++++++++++-------- 1 file changed, 322 insertions(+), 125 deletions(-) diff --git a/apps/web/src/pages/points/Center.tsx b/apps/web/src/pages/points/Center.tsx index d53251a04..546fbfb6d 100644 --- a/apps/web/src/pages/points/Center.tsx +++ b/apps/web/src/pages/points/Center.tsx @@ -1,154 +1,351 @@ /** - * 积分中心首页 - * 展示: - * - 当前积分余额 + 会员状态(来自 balance + membership) - * - 每日免费额度(来自 dailyUsage) - * - 快捷入口(充值 / 消费记录 / 订阅) + * 积分中心主页(/points 或 /app/points) + * 展示余额、会员信息、免费额度、快捷入口、最近流水 + * + * 字段对齐新契约(2026-09-16): + * - balance 不含 free_clips_*,从 dailyUsage 取 + * - subscription.member_type → plan_id(free/monthly/quarterly/yearly) + * - subscription.member_type_name → 前端 PLAN_LABEL 映射 */ import React, { useEffect } from "react" -import { Card, Button, Space, Statistic, Tag, Progress, Alert } from "antd" -import { useNavigate } from "react-router-dom" +import { + Card, + Col, + Row, + Statistic, + Button, + Space, + Tag, + Typography, + Progress, + List, + Avatar, + Empty, +} from "antd" import { ThunderboltOutlined, - CrownOutlined, + CrownFilled, + ArrowUpOutlined, + ArrowDownOutlined, HistoryOutlined, - PlusCircleOutlined, - GiftOutlined, + WalletOutlined, + FileTextOutlined, + InfoCircleOutlined, } from "@ant-design/icons" +import { useNavigate } from "react-router-dom" +import PageHead from "@/components/layout/PageHead" import { usePointsStore } from "@/store/pointsStore" -import styles from "./Points.css" +import { PLAN_LABEL } from "@/api/subscription/types" +import "./Points.css" -const MEMBER_LABEL: Record = { - monthly: "月卡会员", - quarterly: "季卡会员", - yearly: "年卡会员", +const { 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 封面", + sign_up: "注册赠送", + bind_phone: "绑定手机", + gift: "活动赠送", + admin: "管理员调整", } -const PointsCenterPage: React.FC = () => { +const sourceLabel = (src: string): string => { + if (src.startsWith("refund:")) return `${SOURCE_NAME[src.slice(7)] || src.slice(7)}退款` + return SOURCE_NAME[src] || src +} + +/** 会员标签:优先取 membership.member_type,降级 subscription.plan_id */ +const memberKey = ( + membership: { member_type: string | null } | null, + subscription: { plan_id: string } | null, +): string | null => + membership?.member_type ?? + (subscription?.plan_id && subscription.plan_id !== "free" ? subscription.plan_id : null) + +const memberLabel = ( + membership: { member_type: string | null } | null, + subscription: { plan_id: string; plan_name?: string } | null, +): string => { + const key = memberKey(membership, subscription) + if (!key) return "免费会员" + return PLAN_LABEL[key as keyof typeof PLAN_LABEL] || subscription?.plan_name || "付费会员" +} + +const PointsCenter: React.FC = () => { const navigate = useNavigate() - const { balance, dailyUsage, membership, subscription, rules, init } = usePointsStore() + const { balance, dailyUsage, membership, subscription, rules, init, loading } = usePointsStore() useEffect(() => { init() }, [init]) + const bal = membership?.points_balance ?? balance?.balance ?? 0 + const earned = balance?.total_earned ?? 0 + const spent = balance?.total_spent ?? 0 const isMember = membership?.is_member ?? balance?.is_member ?? false - const memberKey = - membership?.member_type ?? - (subscription?.plan_id && subscription.plan_id !== "free" ? subscription.plan_id : null) - const memberLabel = memberKey ? MEMBER_LABEL[memberKey] || "" : "" - const freeLimit = dailyUsage?.free_clips_limit ?? 0 + + // 免费额度从 dailyUsage 取 const freeUsed = dailyUsage?.free_clips_used ?? 0 + const freeLimit = dailyUsage?.free_clips_limit ?? (isMember ? 0 : 3) const freeRemain = dailyUsage?.free_clips_remaining ?? 0 - const freePercent = freeLimit > 0 ? Math.round((freeUsed / freeLimit) * 100) : 0 - const freeMultiplier = rules?.free_user_multiplier ?? 1.15 + + // 最近流水 mock(后续可改为调用 getPointsTransactions(1,5)) + const recentTx = [ + { type: "deduct" as const, source: "ai_voice", amount: 1, time: "今天 10:30" }, + { type: "deduct" as const, source: "ai_video", amount: 3, time: "今天 09:15" }, + { type: "add" as const, source: "recharge", amount: 100, time: "昨天" }, + ] return ( -
-

积分中心

- - {/* 余额卡片 */} - -
- } - valueStyle={{ color: "#faad14", fontSize: 36, fontWeight: 700 }} - /> - {isMember && memberLabel && ( - } className={styles.memberTag}> - {memberLabel} - {membership?.max_resolution ? ` · ${membership.max_resolution}` : ""} - - )} -
- - - - - - {balance?.member_expires_at && isMember && ( -
- 会员到期时间:{new Date(balance.member_expires_at).toLocaleDateString("zh-CN")} -
- )} -
- - {/* 每日免费额度卡片 */} - - {!isMember && freeLimit > 0 ? ( - <> - `${freeUsed}/${freeLimit}`} - /> -
- 今日还剩 {freeRemain} 次免费生成额度 - {dailyUsage?.reset_at && ( - - ( - {new Date(dailyUsage.reset_at).toLocaleTimeString("zh-CN", { - hour: "2-digit", - minute: "2-digit", - })}{" "} - 重置) - - )} -
- - ) : isMember ? ( - - ) : ( - - )} -
- - {/* 快捷提示 */} - {!isMember && rules && ( - navigate("/subscription")}> - 开通会员 +
+ + - } - style={{ marginTop: 16 }} - /> - )} + + + } + /> - {/* 累计统计 */} - -
- - -
+ {/* 顶部大卡 */} + + + + + + 当前可用积分 + +
+ + {loading ? "…" : bal.toLocaleString()} +
+ + {isMember ? ( + } style={{ padding: "4px 10px" }}> + {memberLabel(membership, subscription)} + + ) : ( + + 免费会员 + + )} + {balance?.member_expires_at && isMember && ( + + 到期 {new Date(balance.member_expires_at).toLocaleDateString("zh-CN")} + + )} + {membership?.max_resolution && isMember && ( + + · {membership.max_resolution} + + )} + {!isMember && ( + + )} + +
+ + + {/* 今日免费额度 */} + {!isMember && freeLimit > 0 && ( +
+
+ 今日免费混剪 +
+ 0 ? Math.round((freeUsed / freeLimit) * 100) : 0} + strokeColor={{ "0%": "#f59e0b", "100%": "#ef4444" }} + format={() => `${freeUsed}/${freeLimit} 条`} + /> + + 剩余 {freeRemain} 条免费混剪,超出部分消耗积分 + {rules?.free_user_multiplier ? `(×${rules.free_user_multiplier} 倍率)` : ""} + {dailyUsage?.reset_at && ( + + {" "} + ·{" "} + {new Date(dailyUsage.reset_at).toLocaleTimeString("zh-CN", { + hour: "2-digit", + minute: "2-digit", + })} + 重置 + + )} + +
+ )} + +
+ + {/* 统计 */} + + + + } + valueStyle={{ color: "#10b981" }} + /> + + + + + } + valueStyle={{ color: "#ef4444" }} + /> + + + + + } + valueStyle={{ color: "#8b5cf6" }} + /> + + + + + } + valueStyle={{ color: "#f59e0b" }} + /> + + + + + {/* 快捷入口 & 最近流水 */} + + + + + 最近流水 + + } + extra={ navigate("/points/transactions")}>查看全部 →} + > + {recentTx.length === 0 ? ( + + ) : ( + ( + + : } + /> + } + title={sourceLabel(item.source)} + description={item.time} + /> +
+ {item.type === "add" ? "+" : "-"} + {item.amount} +
+
+ )} + /> + )} +
+ + + + + + + + + + + +
) } -export default PointsCenterPage +export default PointsCenter +export const Component = PointsCenter