diff --git a/web/src/components/table/mj-logs/MjLogsActions.jsx b/web/src/components/table/mj-logs/MjLogsActions.jsx index 06ced53e..f4bafad1 100644 --- a/web/src/components/table/mj-logs/MjLogsActions.jsx +++ b/web/src/components/table/mj-logs/MjLogsActions.jsx @@ -36,7 +36,7 @@ const MjLogsActions = ({ const showSkeleton = useMinimumLoadingTime(loading); const placeholder = ( -
+
@@ -45,7 +45,7 @@ const MjLogsActions = ({ return (
-
+
{isAdminUser && showBanner diff --git a/web/src/components/topup/InvitationCard.jsx b/web/src/components/topup/InvitationCard.jsx index 2519f336..8b89967b 100644 --- a/web/src/components/topup/InvitationCard.jsx +++ b/web/src/components/topup/InvitationCard.jsx @@ -48,7 +48,7 @@ const InvitationCard = ({
{t('邀请奖励')} -
{t('邀请好友获得额外奖励')}
+
{t('邀请好友获得额外奖励')}
diff --git a/web/src/components/topup/RechargeCard.jsx b/web/src/components/topup/RechargeCard.jsx index ae1261f1..208a0eb0 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 from 'react'; +import React, { useRef } from 'react'; import { Avatar, Typography, @@ -28,13 +28,17 @@ import { Banner, Skeleton, Divider, - Tabs, - TabPane, + Form, + Space, + Row, + Col, + Spin, } from '@douyinfe/semi-ui'; import { SiAlipay, SiWechat, SiStripe } from 'react-icons/si'; -import { CreditCard, Gift, Link as LinkIcon, Coins } from 'lucide-react'; +import { CreditCard, Gift, Link as LinkIcon, Coins, Wallet, BarChart2, TrendingUp } from 'lucide-react'; import { IconGift } from '@douyinfe/semi-icons'; -import RightStatsCard from './RightStatsCard'; +import { useMinimumLoadingTime } from '../../hooks/common/useMinimumLoadingTime'; + const { Text } = Typography; @@ -70,91 +74,98 @@ const RechargeCard = ({ renderQuota, statusLoading, }) => { + const onlineFormApiRef = useRef(null); + const redeemFormApiRef = useRef(null); + const showAmountSkeleton = useMinimumLoadingTime(amountLoading); return ( {/* 卡片头部 */} -
-
- - - -
- {t('账户充值')} -
{t('多种充值方式,安全便捷')}
-
+
+ + + +
+ {t('账户充值')} +
{t('多种充值方式,安全便捷')}
-
- - {/* 在线充值 Tab */} - - - {t('在线充值')} -
- } - itemKey="online" - > -
- {statusLoading ? ( -
-
-
- ) : (enableOnlineTopUp || enableStripeTopUp) ? ( -
- {/* 预设充值额度选择 */} - {(enableOnlineTopUp || enableStripeTopUp) && ( -
- - {t('选择充值额度')} - -
- {presetAmounts.map((preset, index) => ( - selectPresetAmount(preset)} - className={`cursor-pointer !rounded-xl transition-all hover:shadow-md ${selectedPreset === preset.value - ? 'border-blue-500 shadow-md' - : 'border-slate-200 hover:border-slate-300 dark:border-slate-600 dark:hover:border-slate-500' - }`} - bodyStyle={{ textAlign: 'center', padding: '12px' }} - > -
- - {formatLargeNumber(preset.value)} -
-
- {t('实付')} ¥{(preset.value * priceRatio).toFixed(2)} -
-
- ))} + + {/* 统计数据 */} + +
+
+ {t('账户统计')} +
+ + {/* 统计数据 */} +
+ {/* 当前余额 */} +
+
+ {renderQuota(userState?.user?.quota)} +
+
+ + {t('当前余额')}
- )} - {/* 自定义充值金额 */} + {/* 历史消耗 */} +
+
+ {renderQuota(userState?.user?.used_quota)} +
+
+ + {t('历史消耗')} +
+
+ + {/* 请求次数 */} +
+
+ {userState?.user?.request_count || 0} +
+
+ + {t('请求次数')} +
+
+
+
+
+ } + > + {/* 在线充值表单 */} + {statusLoading ? ( +
+ +
+ ) : (enableOnlineTopUp || enableStripeTopUp) ? ( +
(onlineFormApiRef.current = api)} + initValues={{ topUpCount: topUpCount }} + > +
{(enableOnlineTopUp || enableStripeTopUp) && ( -
- - - {t('或输入自定义金额')} - - - -
-
- {t('充值数量')} - {amountLoading ? ( - - ) : ( - - {t('实付金额:')}{renderAmount()} - - )} -
- + + (value ? `${value}` : '')} parser={(value) => value ? parseInt(value.replace(/[^\d]/g, '')) : 0} - /> -
- - {/* 支付方式选择 */} -
- - {t('选择支付方式')} - -
- {payMethods.map((payMethod) => ( - preTopUp(payMethod.type)} - className={`cursor-pointer !rounded-xl transition-all hover:shadow-md ${paymentLoading && payWay === payMethod.type - ? 'border-blue-500 shadow-md' - : 'border-slate-200 hover:border-slate-300 dark:border-slate-600 dark:hover:border-slate-500' - } ${(!enableOnlineTopUp && payMethod.type !== 'stripe') || - (!enableStripeTopUp && payMethod.type === 'stripe') - ? 'opacity-50 cursor-not-allowed' - : '' - }`} - bodyStyle={{ padding: '12px', textAlign: 'center' }} + extraText={ + } > - {paymentLoading && payWay === payMethod.type ? ( -
-
-
-
-
{t('处理中')}
-
- ) : ( - <> -
- {payMethod.type === 'zfb' ? ( - - ) : payMethod.type === 'wx' ? ( - - ) : payMethod.type === 'stripe' ? ( - - ) : ( - - )} -
-
{payMethod.name}
- - )} -
- ))} -
-
-
+ + {t('实付金额:')}{renderAmount()} + + + } + style={{ width: '100%' }} + /> + + + + + {payMethods.map((payMethod) => ( + + ))} + + + + + )} + + {(enableOnlineTopUp || enableStripeTopUp) && ( + + + {presetAmounts.map((preset, index) => ( + + ))} + + )}
- ) : ( - - )} -
- + + ) : ( + + )} + - {/* 兑换码充值 Tab */} - - + {/* 兑换码充值 */} + {t('兑换码充值')} -
+ } - itemKey="redeem" > -
-
- setRedemptionCode(value)} - className='!rounded-lg' - prefix={} - showClear - /> - -
- {topUpLink && ( +
(redeemFormApiRef.current = api)} + initValues={{ redemptionCode: redemptionCode }} + > + setRedemptionCode(value)} + prefix={} + suffix={ +
- )} - -
-
-
- - +
+ } + showClear + style={{ width: '100%' }} + extraText={topUpLink && ( + + {t('在找兑换码?')} + + {t('购买兑换码')} + + + )} + /> + + + ); }; diff --git a/web/src/components/topup/RightStatsCard.jsx b/web/src/components/topup/RightStatsCard.jsx deleted file mode 100644 index 8daa8d35..00000000 --- a/web/src/components/topup/RightStatsCard.jsx +++ /dev/null @@ -1,60 +0,0 @@ -/* -Copyright (C) 2025 QuantumNous - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU Affero General Public License as -published by the Free Software Foundation, either version 3 of the -License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Affero General Public License for more details. - -You should have received a copy of the GNU Affero General Public License -along with this program. If not, see . - -For commercial licensing, please contact support@quantumnous.com -*/ - -import React from 'react'; -import { Card, Typography, Divider } from '@douyinfe/semi-ui'; -import { Wallet, Coins, BarChart2 } from 'lucide-react'; - -const { Text } = Typography; - -const RightStatsCard = ({ t, userState, renderQuota }) => { - return ( - -
-
- -
- {t('当前余额')} -
{renderQuota(userState?.user?.quota)}
-
-
- -
- -
- {t('历史消耗')} -
{renderQuota(userState?.user?.used_quota)}
-
-
- -
- -
- {t('请求次数')} -
{userState?.user?.request_count || 0}
-
-
-
-
- ); -}; - -export default RightStatsCard; - - diff --git a/web/src/components/topup/index.jsx b/web/src/components/topup/index.jsx index 4c9a5267..5fada938 100644 --- a/web/src/components/topup/index.jsx +++ b/web/src/components/topup/index.jsx @@ -17,7 +17,7 @@ along with this program. If not, see . For commercial licensing, please contact support@quantumnous.com */ -import React, { useEffect, useState, useContext } from 'react'; +import React, { useEffect, useState, useContext, useRef } from 'react'; import { API, showError, @@ -63,42 +63,25 @@ const TopUp = () => { const [enableStripeTopUp, setEnableStripeTopUp] = useState(statusState?.status?.enable_stripe_topup || false); const [statusLoading, setStatusLoading] = useState(true); - const [userQuota, setUserQuota] = useState(0); const [isSubmitting, setIsSubmitting] = useState(false); const [open, setOpen] = useState(false); const [payWay, setPayWay] = useState(''); - const [userDataLoading, setUserDataLoading] = useState(true); const [amountLoading, setAmountLoading] = useState(false); const [paymentLoading, setPaymentLoading] = useState(false); const [confirmLoading, setConfirmLoading] = useState(false); const [payMethods, setPayMethods] = useState([]); + const affFetchedRef = useRef(false); + // 邀请相关状态 const [affLink, setAffLink] = useState(''); const [openTransfer, setOpenTransfer] = useState(false); const [transferAmount, setTransferAmount] = useState(0); // 预设充值额度选项 - const [presetAmounts, setPresetAmounts] = useState([ - { value: 5 }, - { value: 10 }, - { value: 30 }, - { value: 50 }, - { value: 100 }, - { value: 300 }, - { value: 500 }, - { value: 1000 }, - ]); + const [presetAmounts, setPresetAmounts] = useState([]); const [selectedPreset, setSelectedPreset] = useState(null); - const getUsername = () => { - if (userState.user) { - return userState.user.username; - } else { - return 'null'; - } - }; - const topUp = async () => { if (redemptionCode === '') { showInfo(t('请输入兑换码!')); @@ -117,9 +100,6 @@ const TopUp = () => { content: t('成功兑换额度:') + renderQuota(data), centered: true, }); - setUserQuota((quota) => { - return quota + data; - }); if (userState.user) { const updatedUser = { ...userState.user, @@ -260,16 +240,13 @@ const TopUp = () => { }; const getUserQuota = async () => { - setUserDataLoading(true); let res = await API.get(`/api/user/self`); const { success, message, data } = res.data; if (success) { - setUserQuota(data.quota); userDispatch({ type: 'login', payload: data }); } else { showError(message); } - setUserDataLoading(false); }; // 获取邀请链接 @@ -310,13 +287,9 @@ const TopUp = () => { }; useEffect(() => { - if (userState?.user?.id) { - setUserDataLoading(false); - setUserQuota(userState.user.quota); - } else { + if (!userState?.user?.id) { getUserQuota().then(); } - getAffLink().then(); setTransferAmount(getQuotaPerUnit()); let payMethods = localStorage.getItem('pay_methods'); @@ -330,9 +303,9 @@ const TopUp = () => { // 如果没有color,则设置默认颜色 payMethods = payMethods.map((method) => { if (!method.color) { - if (method.type === 'zfb') { + if (method.type === 'alipay') { method.color = 'rgba(var(--semi-blue-5), 1)'; - } else if (method.type === 'wx') { + } else if (method.type === 'wxpay') { method.color = 'rgba(var(--semi-green-5), 1)'; } else if (method.type === 'stripe') { method.color = 'rgba(var(--semi-purple-5), 1)'; @@ -365,14 +338,27 @@ const TopUp = () => { } }, [statusState?.status?.enable_stripe_topup]); + useEffect(() => { + if (affFetchedRef.current) return; + affFetchedRef.current = true; + getAffLink().then(); + }, []); + useEffect(() => { if (statusState?.status) { - setMinTopUp(statusState.status.min_topup || 1); - setTopUpCount(statusState.status.min_topup || 1); + const minTopUpValue = statusState.status.min_topup || 1; + setMinTopUp(minTopUpValue); + setTopUpCount(minTopUpValue); setTopUpLink(statusState.status.top_up_link || ''); setEnableOnlineTopUp(statusState.status.enable_online_topup || false); setPriceRatio(statusState.status.price || 1); setEnableStripeTopUp(statusState.status.enable_stripe_topup || false); + + // 根据最小充值金额生成预设充值额度选项 + setPresetAmounts(generatePresetAmounts(minTopUpValue)); + // 初始化显示实付金额 + getAmount(minTopUpValue); + setStatusLoading(false); } }, [statusState?.status]); @@ -454,6 +440,14 @@ const TopUp = () => { return num.toString(); }; + // 根据最小充值金额生成预设充值额度选项 + const generatePresetAmounts = (minAmount) => { + const multipliers = [1, 5, 10, 30, 50, 100, 300, 500]; + return multipliers.map(multiplier => ({ + value: minAmount * multiplier + })); + }; + return (
{/* 划转模态框 */} diff --git a/web/src/components/topup/modals/PaymentConfirmModal.jsx b/web/src/components/topup/modals/PaymentConfirmModal.jsx index 12fcac95..111b9bf9 100644 --- a/web/src/components/topup/modals/PaymentConfirmModal.jsx +++ b/web/src/components/topup/modals/PaymentConfirmModal.jsx @@ -85,9 +85,9 @@ const PaymentConfirmModal = ({ if (payMethod) { return ( <> - {payMethod.type === 'zfb' ? ( + {payMethod.type === 'alipay' ? ( - ) : payMethod.type === 'wx' ? ( + ) : payMethod.type === 'wxpay' ? ( ) : payMethod.type === 'stripe' ? ( @@ -99,7 +99,7 @@ const PaymentConfirmModal = ({ ); } else { // 默认充值方式 - if (payWay === 'zfb') { + if (payWay === 'alipay') { return ( <> diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json index 6db0e7ee..56a4236b 100644 --- a/web/src/i18n/locales/en.json +++ b/web/src/i18n/locales/en.json @@ -159,7 +159,6 @@ "测试所有已启用通道": "Test all enabled channels", "更新所有已启用通道余额": "Update balance for all enabled channels", "刷新": "Refresh", - "处理中...": "Processing...", "绑定成功!": "Binding successful!", "登录成功!": "Login successful!", "操作失败,重定向至登录界面中...": "Operation failed, redirecting to login page...", @@ -168,7 +167,7 @@ "渠道": "Channel", "渠道管理": "Channels", "令牌": "Tokens", - "兑换": "Redeem", + "兑换额度": "Redeem", "充值": "Recharge", "用户": "Users", "日志": "Logs", @@ -846,7 +845,6 @@ "充值记录": "Recharge record", "返利记录": "Rebate record", "确定要充值 $": "Confirm to top up $", - "兑换中...": "Redemming", "微信/支付宝 实付金额:": "WeChat/Alipay actual payment amount:", "Stripe 实付金额:": "Stripe actual payment amount:", "支付中...": "Paying", @@ -857,7 +855,9 @@ "兑换码充值": "Redemption code recharge", "奖励说明": "Reward description", "选择支付方式": "Select payment method", - "处理中": "Processing", + "在找兑换码?": "Looking for a redemption code? ", + "购买兑换码": "Buy redemption code", + "账户统计": "Account statistics", "账户充值": "Account recharge", "多种充值方式,安全便捷": "Multiple recharge methods, safe and convenient", "支付方式": "Payment method",