From 47723a15f1db19118bdd43ffcf9ffa4af9e4943a Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Sun, 20 Sep 2026 01:03:35 +0800 Subject: [PATCH] feat(web): hide credit/points UI behind ENABLE_CREDIT_SYSTEM=false flag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - add src/config/features.ts with ENABLE_CREDIT_SYSTEM=false toggle - PointsBadge / PointsCost early-return null when disabled - Header user dropdown hides 我的积分/积分明细/充值积分 menu items - sidebar navigation hides points entry - GeneratePage skips points pre-check, hides insufficient-points CTAs - Plans page hides points balance/recharge/packages blocks; keeps VIP plans - pointsStore skips balance/rules/dailyUsage fetches when disabled, but still pulls subscription+membership so VIP badge/plan UI work - no code deleted — flipping the flag back to true restores full UI --- .../components/common/PointsBadge/index.tsx | 5 + .../components/common/PointsCost/index.tsx | 6 +- apps/web/src/components/layout/Header.tsx | 63 +++--- apps/web/src/config/features.ts | 13 ++ apps/web/src/config/navigation.ts | 35 ++-- apps/web/src/pages/generate/GeneratePage.tsx | 55 ++--- apps/web/src/pages/subscription/Plans.tsx | 191 ++++++++++-------- apps/web/src/store/pointsStore.ts | 24 ++- 8 files changed, 235 insertions(+), 157 deletions(-) create mode 100644 apps/web/src/config/features.ts diff --git a/apps/web/src/components/common/PointsBadge/index.tsx b/apps/web/src/components/common/PointsBadge/index.tsx index 3ac88e25f..fd493bae4 100644 --- a/apps/web/src/components/common/PointsBadge/index.tsx +++ b/apps/web/src/components/common/PointsBadge/index.tsx @@ -17,6 +17,7 @@ import { } from "@ant-design/icons" import { useNavigate } from "react-router-dom" import { usePointsStore } from "@/store/pointsStore" +import { ENABLE_CREDIT_SYSTEM } from "@/config/features" import "./PointsBadge.css" const { Text, Paragraph } = Typography @@ -32,9 +33,13 @@ const PointsBadge: React.FC = () => { const { balance, membership, subscription, dailyUsage, init, loading } = usePointsStore() useEffect(() => { + if (!ENABLE_CREDIT_SYSTEM) return if (!balance) init() }, [balance, init]) + // 功能开关:积分系统关闭时直接隐藏徽章 + if (!ENABLE_CREDIT_SYSTEM) return null + // 余额:优先用 membership.points_balance(冗余字段),降级 balance.balance const bal = membership?.points_balance ?? balance?.balance ?? 0 const lowBalance = bal > 0 && bal < 10 diff --git a/apps/web/src/components/common/PointsCost/index.tsx b/apps/web/src/components/common/PointsCost/index.tsx index e9ec2edba..95af32a7d 100644 --- a/apps/web/src/components/common/PointsCost/index.tsx +++ b/apps/web/src/components/common/PointsCost/index.tsx @@ -15,6 +15,7 @@ import React, { useMemo } from "react" import { Tooltip } from "antd" import { WarningOutlined } from "@ant-design/icons" import { usePointsStore } from "@/store/pointsStore" +import { ENABLE_CREDIT_SYSTEM } from "@/config/features" import type { PointsSource } from "@/api/points/types" import "./PointsCost.css" @@ -53,7 +54,7 @@ const PointsCost: React.FC = ({ compact = false, showRechargeHint = true, className = "", -}) => { +}: Props) => { const { balance, dailyUsage, rules, membership } = usePointsStore() const qty = quantity ?? units ?? 1 @@ -118,6 +119,9 @@ const PointsCost: React.FC = ({ } }, [rules, balance, dailyUsage, membership, scene, qty, durationMinutes]) + // 积分系统关闭时不展示消耗提示(组件保留,hooks 必须在 return 前调用) + if (!ENABLE_CREDIT_SYSTEM) return null + if (!rule || !balance) { return } diff --git a/apps/web/src/components/layout/Header.tsx b/apps/web/src/components/layout/Header.tsx index 301ee1a7c..72665734d 100644 --- a/apps/web/src/components/layout/Header.tsx +++ b/apps/web/src/components/layout/Header.tsx @@ -21,6 +21,7 @@ import { useLogout } from "@/hooks/useAuth" import type { MenuProps } from "antd" import { NAV_ITEMS } from "@/config/navigation" import PointsBadge from "@/components/common/PointsBadge" +import { ENABLE_CREDIT_SYSTEM } from "@/config/features" import { usePointsStore } from "@/store/pointsStore" import "./Header.css" @@ -57,30 +58,36 @@ const Header: React.FC = () => { label: "订阅管理", onClick: () => navigate("/app/subscription"), }, - // v2: 我的积分入口 - { - key: "points-center", - icon: , - label: ( - - 我的积分 - {balance && {balance.balance}} - - ), - onClick: () => navigate("/app/points"), - }, - { - key: "points-history", - icon: , - label: "积分明细", - onClick: () => navigate("/app/points/transactions"), - }, - { - key: "recharge", - icon: , - label: "充值积分", - onClick: () => navigate("/app/points/recharge"), - }, + // 积分系统开关关闭时隐藏积分相关菜单项(代码保留不删除) + ...(ENABLE_CREDIT_SYSTEM + ? [ + { + key: "points-center", + icon: , + label: ( + + 我的积分 + {balance && ( + {balance.balance} + )} + + ), + onClick: () => navigate("/app/points"), + }, + { + key: "points-history", + icon: , + label: "积分明细", + onClick: () => navigate("/app/points/transactions"), + }, + { + key: "recharge", + icon: , + label: "充值积分", + onClick: () => navigate("/app/points/recharge"), + }, + ] + : []), { type: "divider" }, { key: "logout", @@ -130,7 +137,13 @@ const Header: React.FC = () => { {/* v2: 升级会员入口(仅免费用户显示) */} {!isMember && ( - + - + ENABLE_CREDIT_SYSTEM ? ( + + + + ) : null } /> @@ -296,13 +303,15 @@ const Plans: React.FC = () => { )} -
- 可用积分 -
- - {bal} + {ENABLE_CREDIT_SYSTEM && ( +
+ 可用积分 +
+ + {bal} +
-
+ )} {!isMember && freeLimit > 0 && (
今日免费混剪 @@ -319,18 +328,20 @@ const Plans: React.FC = () => { )} - - - + {ENABLE_CREDIT_SYSTEM && ( + + + + )} @@ -461,69 +472,71 @@ const Plans: React.FC = () => { - {/* 积分充值 */} -
- - <ThunderboltOutlined style={{ color: "#8b5cf6", marginRight: 8 }} /> - 积分充值 - <Tooltip title="积分永久有效,可用于所有 AI 功能;付费会员享折扣"> - <Text type="secondary" style={{ fontSize: 13, marginLeft: 8, fontWeight: "normal" }}> - (永久有效) - </Text> - </Tooltip> - + {/* 积分充值(积分系统关闭时隐藏,代码保留不删除) */} + {ENABLE_CREDIT_SYSTEM && ( +
+ + <ThunderboltOutlined style={{ color: "#8b5cf6", marginRight: 8 }} /> + 积分充值 + <Tooltip title="积分永久有效,可用于所有 AI 功能;付费会员享折扣"> + <Text type="secondary" style={{ fontSize: 13, marginLeft: 8, fontWeight: "normal" }}> + (永久有效) + </Text> + </Tooltip> + - - {packages.map((pkg) => { - const priceCents = getDiscountPriceCents(pkg, userDiscount) - const originalCents = pkg.price_cents - const discount = - priceCents < originalCents ? Math.round((1 - priceCents / originalCents) * 100) : 0 - const unit = priceCents / 100 / pkg.points - const isHot = pkg.unit_price < 0.1 - return ( - - 0 ? "has-discount" : ""} ${isHot ? "recommended" : ""}`} - hoverable - > - {isHot &&
热门
} - {discount > 0 && ( - - {Math.round((priceCents / originalCents) * 10) / 1}折 - - )} -
{pkg.name}
-
- {pkg.points.toLocaleString()} 积分 -
-
- ¥ - - {(priceCents / 100) - .toFixed(priceCents % 100 === 0 ? 0 : 1) - .replace(/\.0$/, "")} - - {discount > 0 && ( - ¥{(originalCents / 100).toFixed(0)} - )} -
-
≈¥{unit.toFixed(3)}/积分
- -
- - ) - })} -
-
+ {isHot &&
热门
} + {discount > 0 && ( + + {Math.round((priceCents / originalCents) * 10) / 1}折 + + )} +
{pkg.name}
+
+ {pkg.points.toLocaleString()} 积分 +
+
+ ¥ + + {(priceCents / 100) + .toFixed(priceCents % 100 === 0 ? 0 : 1) + .replace(/\.0$/, "")} + + {discount > 0 && ( + ¥{(originalCents / 100).toFixed(0)} + )} +
+
≈¥{unit.toFixed(3)}/积分
+ + + + ) + })} + +
+ )}
) } diff --git a/apps/web/src/store/pointsStore.ts b/apps/web/src/store/pointsStore.ts index f64cd1144..9594f05d8 100644 --- a/apps/web/src/store/pointsStore.ts +++ b/apps/web/src/store/pointsStore.ts @@ -8,6 +8,7 @@ * - subscription: GET /subscription/current(plan_id + billing_cycle) */ import { create } from "zustand" +import { ENABLE_CREDIT_SYSTEM } from "@/config/features" import { getPointsBalance, getPointsRules, getDailyUsage, getMembership } from "@/api/points" import { getCurrentSubscription } from "@/api/subscription" import type { @@ -49,14 +50,29 @@ export const usePointsStore = create((set, get) => ({ init: async () => { // 已加载过不重复拉取 - if (get().balance && get().rules && get().subscription) return + // 积分系统关闭时:只要 subscription/membership 已有值就跳过;开启时需 balance+rules+subscription 齐了才跳过 + if (ENABLE_CREDIT_SYSTEM) { + if (get().balance && get().rules && get().subscription) return + } else { + if (get().subscription && get().membership) return + } set({ loading: true, error: null }) try { + // 积分系统关闭时不拉取余额/规则/每日额度,但仍拉会员/订阅用于 VIP 标识展示 + const balancePromise = ENABLE_CREDIT_SYSTEM + ? getPointsBalance().catch(() => null) + : Promise.resolve(null) + const rulesPromise = ENABLE_CREDIT_SYSTEM + ? getPointsRules().catch(() => null) + : Promise.resolve(null) + const dailyUsagePromise = ENABLE_CREDIT_SYSTEM + ? getDailyUsage().catch(() => null) + : Promise.resolve(null) const [balance, rules, subscription, dailyUsage, membership] = await Promise.all([ - getPointsBalance().catch(() => null), - getPointsRules().catch(() => null), + balancePromise, + rulesPromise, getCurrentSubscription().catch(() => null), - getDailyUsage().catch(() => null), + dailyUsagePromise, getMembership().catch(() => null), ]) set({ -- 2.54.0