From bfcea1e44392c8895a6828e419d988e885f2ef9d Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Wed, 16 Sep 2026 10:43:33 +0800 Subject: [PATCH] chore(points): fix lint - apps/web/src/components/common/PointsBadge/index.tsx --- .../components/common/PointsBadge/index.tsx | 144 +++++++++++++----- 1 file changed, 108 insertions(+), 36 deletions(-) diff --git a/apps/web/src/components/common/PointsBadge/index.tsx b/apps/web/src/components/common/PointsBadge/index.tsx index 3e25927b0..3ac88e25f 100644 --- a/apps/web/src/components/common/PointsBadge/index.tsx +++ b/apps/web/src/components/common/PointsBadge/index.tsx @@ -1,58 +1,130 @@ /** - * 头部积分徽章组件 - * - 展示当前积分余额 - * - 会员标签(基于 membership 聚合信息或 subscription.plan_id 判断) - * - 点击跳转积分中心 + * Header 右上角积分徽章 + * - 余额 <10 时橙色告警 + * - 点击弹出 Popover:余额、会员信息、充值入口、积分明细入口 + * + * 字段对齐新契约: + * - balance.is_member / balance.member_type 保留但降级;推荐用 membership.member_type + * - 免费额度、会员 max_resolution 在 popover 展示 */ -import React from "react" +import React, { useEffect } from "react" +import { Popover, Button, Tag, Space, Typography, Badge } from "antd" +import { + ThunderboltOutlined, + CrownOutlined, + RightOutlined, + WarningOutlined, +} from "@ant-design/icons" import { useNavigate } from "react-router-dom" -import { Tooltip, Typography } from "antd" -import { CrownOutlined, StarFilled } from "@ant-design/icons" import { usePointsStore } from "@/store/pointsStore" -import styles from "./PointsBadge.module.css" +import "./PointsBadge.css" -const { Text } = Typography +const { Text, Paragraph } = Typography -/** 会员类型标签映射(基于 GET /points/subscription/membership 返回的 member_type 字符串) */ const MEMBER_LABEL: Record = { monthly: "月卡会员", quarterly: "季卡会员", yearly: "年卡会员", } -const MEMBER_COLOR: Record = { - monthly: "#fa8c16", - quarterly: "#eb2f96", - yearly: "#fadb14", -} - const PointsBadge: React.FC = () => { const navigate = useNavigate() - const { balance, membership, subscription } = usePointsStore() + const { balance, membership, subscription, dailyUsage, init, loading } = usePointsStore() - const points = membership?.points_balance ?? balance?.balance ?? 0 - // 优先用 membership.member_type 判定会员状态;否则用 subscription.plan_id - const memberKey: string | null = + useEffect(() => { + if (!balance) init() + }, [balance, init]) + + // 余额:优先用 membership.points_balance(冗余字段),降级 balance.balance + const bal = membership?.points_balance ?? balance?.balance ?? 0 + const lowBalance = bal > 0 && bal < 10 + const zero = bal === 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 isMember = membership?.is_member ?? balance?.is_member ?? false - const memberLabel = memberKey ? MEMBER_LABEL[memberKey] || "" : "" - const memberColor = memberKey ? MEMBER_COLOR[memberKey] || "#fa8c16" : "#fa8c16" + const memberLabel = memberKey ? MEMBER_LABEL[memberKey] || "付费会员" : "免费会员" + const maxRes = membership?.max_resolution + + const freeRemain = dailyUsage?.free_clips_remaining ?? 0 + + const popContent = ( +
+
+
+ + {loading ? "…" : bal} + 积分 +
+ }> + {memberLabel} + +
+ + {(zero || lowBalance) && ( + + 积分不足,充值后可继续使用 AI 功能 + + )} + + {!isMember && dailyUsage && freeRemain > 0 && ( + + 今日剩余免费次数:{freeRemain}/{dailyUsage.free_clips_limit} + + )} + + {balance?.member_expires_at && isMember && ( + + 会员到期:{new Date(balance.member_expires_at).toLocaleDateString("zh-CN")} + {maxRes ? ` · ${maxRes}` : ""} + + )} + +
+
+ 累计获得 +
+{balance?.total_earned ?? 0}
+
+
+ 累计消耗 +
-{balance?.total_spent ?? 0}
+
+
+ + + + + {!isMember && ( + + )} + +
+ ) return ( -
navigate("/points")}> - - - {points} - - {isMember && memberLabel && ( - - - {memberLabel} - - - )} -
+ + + ) }