diff --git a/web/src/components/topup/RechargeCard.jsx b/web/src/components/topup/RechargeCard.jsx index 264c965b..1ce02309 100644 --- a/web/src/components/topup/RechargeCard.jsx +++ b/web/src/components/topup/RechargeCard.jsx @@ -17,7 +17,7 @@ along with this program. If not, see . For commercial licensing, please contact support@quantumnous.com */ -import React, { useRef } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import { Avatar, Typography, @@ -32,6 +32,8 @@ import { Col, Spin, Tooltip, + Tabs, + TabPane, } from '@douyinfe/semi-ui'; import { SiAlipay, SiWechat, SiStripe } from 'react-icons/si'; import { @@ -41,10 +43,12 @@ import { BarChart2, TrendingUp, Receipt, + Sparkles, } from 'lucide-react'; import { IconGift } from '@douyinfe/semi-icons'; import { useMinimumLoadingTime } from '../../hooks/common/useMinimumLoadingTime'; import { getCurrencyConfig } from '../../helpers/render'; +import SubscriptionPlansCard from './SubscriptionPlansCard'; const { Text } = Typography; @@ -83,16 +87,497 @@ const RechargeCard = ({ statusLoading, topupInfo, onOpenHistory, + subscriptionLoading = false, + subscriptionPlans = [], + billingPreference, + onChangeBillingPreference, + activeSubscriptions = [], + allSubscriptions = [], + reloadSubscriptionSelf, }) => { const onlineFormApiRef = useRef(null); const redeemFormApiRef = useRef(null); + const initialTabSetRef = useRef(false); const showAmountSkeleton = useMinimumLoadingTime(amountLoading); - console.log( - ' enabled screem ?', - enableCreemTopUp, - ' products ?', - creemProducts, + const [activeTab, setActiveTab] = useState('topup'); + const shouldShowSubscription = + !subscriptionLoading && subscriptionPlans.length > 0; + + useEffect(() => { + if (initialTabSetRef.current) return; + if (subscriptionLoading) return; + setActiveTab(shouldShowSubscription ? 'subscription' : 'topup'); + initialTabSetRef.current = true; + }, [shouldShowSubscription, subscriptionLoading]); + + useEffect(() => { + if (!shouldShowSubscription && activeTab !== 'topup') { + setActiveTab('topup'); + } + }, [shouldShowSubscription, activeTab]); + const topupContent = ( + + {/* 统计数据 */} + +
+
+ + {t('账户统计')} + +
+ + {/* 统计数据 */} +
+ {/* 当前余额 */} +
+
+ {renderQuota(userState?.user?.quota)} +
+
+ + + {t('当前余额')} + +
+
+ + {/* 历史消耗 */} +
+
+ {renderQuota(userState?.user?.used_quota)} +
+
+ + + {t('历史消耗')} + +
+
+ + {/* 请求次数 */} +
+
+ {userState?.user?.request_count || 0} +
+
+ + + {t('请求次数')} + +
+
+
+
+ + } + > + {/* 在线充值表单 */} + {statusLoading ? ( +
+ +
+ ) : enableOnlineTopUp || enableStripeTopUp || enableCreemTopUp ? ( +
(onlineFormApiRef.current = api)} + initValues={{ topUpCount: topUpCount }} + > +
+ {(enableOnlineTopUp || enableStripeTopUp) && ( + + + { + if (value && value >= 1) { + setTopUpCount(value); + setSelectedPreset(null); + await getAmount(value); + } + }} + onBlur={(e) => { + const value = parseInt(e.target.value); + if (!value || value < 1) { + setTopUpCount(1); + getAmount(1); + } + }} + formatter={(value) => (value ? `${value}` : '')} + parser={(value) => + value ? parseInt(value.replace(/[^\d]/g, '')) : 0 + } + extraText={ + + } + > + + {t('实付金额:')} + + {renderAmount()} + + + + } + style={{ width: '100%' }} + /> + + + + {payMethods && payMethods.length > 0 ? ( + + {payMethods.map((payMethod) => { + const minTopupVal = Number(payMethod.min_topup) || 0; + const isStripe = payMethod.type === 'stripe'; + const disabled = + (!enableOnlineTopUp && !isStripe) || + (!enableStripeTopUp && isStripe) || + minTopupVal > Number(topUpCount || 0); + + const buttonEl = ( + + ); + + return disabled && + minTopupVal > Number(topUpCount || 0) ? ( + + {buttonEl} + + ) : ( + + {buttonEl} + + ); + })} + + ) : ( +
+ {t('暂无可用的支付方式,请联系管理员配置')} +
+ )} +
+ +
+ )} + + {(enableOnlineTopUp || enableStripeTopUp) && ( + + {t('选择充值额度')} + {(() => { + const { symbol, rate, type } = getCurrencyConfig(); + if (type === 'USD') return null; + + return ( + + (1 $ = {rate.toFixed(2)} {symbol}) + + ); + })()} +
+ } + > +
+ {presetAmounts.map((preset, index) => { + const discount = + preset.discount || topupInfo?.discount?.[preset.value] || 1.0; + const originalPrice = preset.value * priceRatio; + const discountedPrice = originalPrice * discount; + const hasDiscount = discount < 1.0; + const actualPay = discountedPrice; + const save = originalPrice - discountedPrice; + + // 根据当前货币类型换算显示金额和数量 + const { symbol, rate, type } = getCurrencyConfig(); + const statusStr = localStorage.getItem('status'); + let usdRate = 7; // 默认CNY汇率 + try { + if (statusStr) { + const s = JSON.parse(statusStr); + usdRate = s?.usd_exchange_rate || 7; + } + } catch (e) { } + + let displayValue = preset.value; // 显示的数量 + let displayActualPay = actualPay; + let displaySave = save; + + if (type === 'USD') { + // 数量保持USD,价格从CNY转USD + displayActualPay = actualPay / usdRate; + displaySave = save / usdRate; + } else if (type === 'CNY') { + // 数量转CNY,价格已是CNY + displayValue = preset.value * usdRate; + } else if (type === 'CUSTOM') { + // 数量和价格都转自定义货币 + displayValue = preset.value * rate; + displayActualPay = (actualPay / usdRate) * rate; + displaySave = (save / usdRate) * rate; + } + + return ( + { + selectPresetAmount(preset); + onlineFormApiRef.current?.setValue( + 'topUpCount', + preset.value, + ); + }} + > +
+ + + {formatLargeNumber(displayValue)} {symbol} + {hasDiscount && ( + + {t('折').includes('off') + ? ((1 - parseFloat(discount)) * 100).toFixed(1) + : (discount * 10).toFixed(1)} + {t('折')} + + )} + +
+ {t('实付')} {symbol} + {displayActualPay.toFixed(2)}, + {hasDiscount + ? `${t('节省')} ${symbol}${displaySave.toFixed(2)}` + : `${t('节省')} ${symbol}0.00`} +
+
+
+ ); + })} +
+ + )} + + {/* Creem 充值区域 */} + {enableCreemTopUp && creemProducts.length > 0 && ( + +
+ {creemProducts.map((product, index) => ( + creemPreTopUp(product)} + className='cursor-pointer !rounded-2xl transition-all hover:shadow-md border-gray-200 hover:border-gray-300' + bodyStyle={{ textAlign: 'center', padding: '16px' }} + > +
+ {product.name} +
+
+ {t('充值额度')}: {product.quota} +
+
+ {product.currency === 'EUR' ? '€' : '$'} + {product.price} +
+
+ ))} +
+
+ )} + +
+ ) : ( + + )} +
+ + {/* 兑换码充值 */} + + {t('兑换码充值')} + + } + > +
(redeemFormApiRef.current = api)} + initValues={{ redemptionCode: redemptionCode }} + > + setRedemptionCode(value)} + prefix={} + suffix={ +
+ +
+ } + showClear + style={{ width: '100%' }} + extraText={ + topUpLink && ( + + {t('在找兑换码?')} + + {t('购买兑换码')} + + + ) + } + /> + +
+
); + return ( {/* 卡片头部 */} @@ -117,472 +602,50 @@ const RechargeCard = ({ - - {/* 统计数据 */} - -
-
- - {t('账户统计')} - -
- - {/* 统计数据 */} -
- {/* 当前余额 */} -
-
- {renderQuota(userState?.user?.quota)} -
-
- - - {t('当前余额')} - -
-
- - {/* 历史消耗 */} -
-
- {renderQuota(userState?.user?.used_quota)} -
-
- - - {t('历史消耗')} - -
-
- - {/* 请求次数 */} -
-
- {userState?.user?.request_count || 0} -
-
- - - {t('请求次数')} - -
-
-
+ {shouldShowSubscription ? ( + + + + {t('订阅套餐')}
- - } - > - {/* 在线充值表单 */} - {statusLoading ? ( -
- -
- ) : enableOnlineTopUp || enableStripeTopUp || enableCreemTopUp ? ( -
(onlineFormApiRef.current = api)} - initValues={{ topUpCount: topUpCount }} - > -
- {(enableOnlineTopUp || enableStripeTopUp) && ( - - - { - if (value && value >= 1) { - setTopUpCount(value); - setSelectedPreset(null); - await getAmount(value); - } - }} - onBlur={(e) => { - const value = parseInt(e.target.value); - if (!value || value < 1) { - setTopUpCount(1); - getAmount(1); - } - }} - formatter={(value) => (value ? `${value}` : '')} - parser={(value) => - value ? parseInt(value.replace(/[^\d]/g, '')) : 0 - } - extraText={ - - } - > - - {t('实付金额:')} - - {renderAmount()} - - - - } - style={{ width: '100%' }} - /> - - - - {payMethods && payMethods.length > 0 ? ( - - {payMethods.map((payMethod) => { - const minTopupVal = - Number(payMethod.min_topup) || 0; - const isStripe = payMethod.type === 'stripe'; - const disabled = - (!enableOnlineTopUp && !isStripe) || - (!enableStripeTopUp && isStripe) || - minTopupVal > Number(topUpCount || 0); - - const buttonEl = ( - - ); - - return disabled && - minTopupVal > Number(topUpCount || 0) ? ( - - {buttonEl} - - ) : ( - - {buttonEl} - - ); - })} - - ) : ( -
- {t('暂无可用的支付方式,请联系管理员配置')} -
- )} -
- -
- )} - - {(enableOnlineTopUp || enableStripeTopUp) && ( - - {t('选择充值额度')} - {(() => { - const { symbol, rate, type } = getCurrencyConfig(); - if (type === 'USD') return null; - - return ( - - (1 $ = {rate.toFixed(2)} {symbol}) - - ); - })()} -
- } - > -
- {presetAmounts.map((preset, index) => { - const discount = - preset.discount || - topupInfo?.discount?.[preset.value] || - 1.0; - const originalPrice = preset.value * priceRatio; - const discountedPrice = originalPrice * discount; - const hasDiscount = discount < 1.0; - const actualPay = discountedPrice; - const save = originalPrice - discountedPrice; - - // 根据当前货币类型换算显示金额和数量 - const { symbol, rate, type } = getCurrencyConfig(); - const statusStr = localStorage.getItem('status'); - let usdRate = 7; // 默认CNY汇率 - try { - if (statusStr) { - const s = JSON.parse(statusStr); - usdRate = s?.usd_exchange_rate || 7; - } - } catch (e) {} - - let displayValue = preset.value; // 显示的数量 - let displayActualPay = actualPay; - let displaySave = save; - - if (type === 'USD') { - // 数量保持USD,价格从CNY转USD - displayActualPay = actualPay / usdRate; - displaySave = save / usdRate; - } else if (type === 'CNY') { - // 数量转CNY,价格已是CNY - displayValue = preset.value * usdRate; - } else if (type === 'CUSTOM') { - // 数量和价格都转自定义货币 - displayValue = preset.value * rate; - displayActualPay = (actualPay / usdRate) * rate; - displaySave = (save / usdRate) * rate; - } - - return ( - { - selectPresetAmount(preset); - onlineFormApiRef.current?.setValue( - 'topUpCount', - preset.value, - ); - }} - > -
- - - {formatLargeNumber(displayValue)} {symbol} - {hasDiscount && ( - - {t('折').includes('off') - ? ( - (1 - parseFloat(discount)) * - 100 - ).toFixed(1) - : (discount * 10).toFixed(1)} - {t('折')} - - )} - -
- {t('实付')} {symbol} - {displayActualPay.toFixed(2)}, - {hasDiscount - ? `${t('节省')} ${symbol}${displaySave.toFixed(2)}` - : `${t('节省')} ${symbol}0.00`} -
-
-
- ); - })} -
- - )} - - {/* Creem 充值区域 */} - {enableCreemTopUp && creemProducts.length > 0 && ( - -
- {creemProducts.map((product, index) => ( - creemPreTopUp(product)} - className='cursor-pointer !rounded-2xl transition-all hover:shadow-md border-gray-200 hover:border-gray-300' - bodyStyle={{ textAlign: 'center', padding: '16px' }} - > -
- {product.name} -
-
- {t('充值额度')}: {product.quota} -
-
- {product.currency === 'EUR' ? '€' : '$'} - {product.price} -
-
- ))} -
-
- )} - -
- ) : ( - - )} -
- - {/* 兑换码充值 */} - - {t('兑换码充值')} - - } - > -
(redeemFormApiRef.current = api)} - initValues={{ redemptionCode: redemptionCode }} + } + itemKey='subscription' > - setRedemptionCode(value)} - prefix={} - suffix={ -
- -
- } - showClear - style={{ width: '100%' }} - extraText={ - topUpLink && ( - - {t('在找兑换码?')} - - {t('购买兑换码')} - - - ) - } - /> - -
-
+
+ +
+ + + + {t('额度充值')} + + } + itemKey='topup' + > +
{topupContent}
+
+ + ) : ( + topupContent + )}
); }; diff --git a/web/src/components/topup/SubscriptionPlansCard.jsx b/web/src/components/topup/SubscriptionPlansCard.jsx index e9d25e54..54b4506b 100644 --- a/web/src/components/topup/SubscriptionPlansCard.jsx +++ b/web/src/components/topup/SubscriptionPlansCard.jsx @@ -19,7 +19,6 @@ For commercial licensing, please contact support@quantumnous.com import React, { useMemo, useState } from 'react'; import { - Avatar, Badge, Button, Card, @@ -33,7 +32,7 @@ import { } from '@douyinfe/semi-ui'; import { API, showError, showSuccess, renderQuota } from '../../helpers'; import { getCurrencyConfig } from '../../helpers/render'; -import { Crown, RefreshCw, Sparkles } from 'lucide-react'; +import { RefreshCw, Sparkles } from 'lucide-react'; import SubscriptionPurchaseModal from './modals/SubscriptionPurchaseModal'; import { formatSubscriptionDuration, @@ -83,6 +82,7 @@ const SubscriptionPlansCard = ({ activeSubscriptions = [], allSubscriptions = [], reloadSubscriptionSelf, + withCard = true, }) => { const [open, setOpen] = useState(false); const [selectedPlan, setSelectedPlan] = useState(null); @@ -241,33 +241,9 @@ const SubscriptionPlansCard = ({ return Math.round((used / total) * 100); }; - return ( - + const cardContent = ( + <> {/* 卡片头部 */} -
-
- - - -
- {t('订阅套餐')} -
{t('购买订阅获得模型额度/次数')}
-
-
- {/* 扣费策略 - 右上角 */} - +
{hasAnySubscription ? ( @@ -451,7 +440,7 @@ const SubscriptionPlansCard = ({ {/* 可购买套餐 - 标准定价卡片 */} {plans.length > 0 ? ( -
+
{plans.map((p, index) => { const plan = p?.plan; const totalAmount = Number(plan?.total_amount || 0); @@ -482,9 +471,9 @@ const SubscriptionPlansCard = ({ resetLabel ? { label: resetLabel } : null, totalAmount > 0 ? { - label: totalLabel, - tooltip: `${t('原生额度')}:${totalAmount}`, - } + label: totalLabel, + tooltip: `${t('原生额度')}:${totalAmount}`, + } : { label: totalLabel }, limitLabel ? { label: limitLabel } : null, upgradeLabel ? { label: upgradeLabel } : null, @@ -493,9 +482,8 @@ const SubscriptionPlansCard = ({ return (
@@ -583,7 +571,7 @@ const SubscriptionPlansCard = ({ const buttonEl = (