From 1660c47db550cd40f126d78095dbb25897473b2e Mon Sep 17 00:00:00 2001 From: "Apple\\Apple" Date: Fri, 23 May 2025 19:31:36 +0800 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8Frefactor:=20Completely=20rede?= =?UTF-8?q?sign=20TopUp=20page=20with=20modern=20card-based=20UI=20and=20e?= =?UTF-8?q?nhanced=20UX?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace simple form layout with sophisticated card-based design system - Implement bank card-style wallet display with gradient backgrounds and decorative elements - Integrate real user data from UserContext (username, quota, usage stats, user role, group) - Add personalized color schemes using stringToColor for unique user identification - Implement comprehensive responsive design for mobile, tablet, and desktop devices - Add skeleton loading states for all data-dependent components and API calls - Replace basic Input with InputNumber component for amount input with built-in validation (min: 1) - Add official brand icons for payment methods (Alipay, WeChat) using react-icons/si - Integrate Semi UI Banner component for better warning notifications - Implement real-time data synchronization between local state and UserContext - Add sophisticated loading states with proper error handling and user feedback - Clean up all code comments and remove unused imports, functions, and state variables - Enhance visual hierarchy with proper spacing, shadows, and modern typography - Add glass-morphism effects and backdrop filters for premium visual experience - Improve accessibility with proper text truncation and responsive font sizing This update transforms the TopUp page from a basic form into a professional, modern payment interface that provides excellent user experience across all devices while maintaining full functionality and adding comprehensive data validation. --- web/package.json | 4 +- web/src/i18n/locales/en.json | 10 +- web/src/pages/TopUp/index.js | 487 +++++++++++++++++++++++++---------- 3 files changed, 367 insertions(+), 134 deletions(-) diff --git a/web/package.json b/web/package.json index 7dfd8f80..2abb1897 100644 --- a/web/package.json +++ b/web/package.json @@ -11,23 +11,25 @@ "@visactor/vchart": "~1.8.8", "@visactor/vchart-semi-theme": "~1.8.8", "axios": "^0.27.2", + "country-flag-icons": "^1.5.19", "dayjs": "^1.11.11", "history": "^5.3.0", "i18next": "^23.16.8", "i18next-browser-languagedetector": "^7.2.0", + "lucide-react": "^0.511.0", "marked": "^4.1.1", "react": "^18.2.0", "react-dom": "^18.2.0", "react-dropzone": "^14.2.3", "react-fireworks": "^1.0.4", "react-i18next": "^13.0.0", + "react-icons": "^5.5.0", "react-router-dom": "^6.3.0", "react-telegram-login": "^1.1.2", "react-toastify": "^9.0.8", "react-turnstile": "^1.0.5", "semantic-ui-offline": "^2.5.0", "semantic-ui-react": "^2.1.3", - "country-flag-icons": "^1.5.19", "sse": "https://github.com/mpetazzoni/sse.js" }, "scripts": { diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json index eef1bb20..063a855e 100644 --- a/web/src/i18n/locales/en.json +++ b/web/src/i18n/locales/en.json @@ -1178,7 +1178,7 @@ "请输入新的密码,最短 8 位": "Please enter a new password, at least 8 characterss", "添加额度": "Add quota", "以下信息不可修改": "The following information cannot be modified", - "确定要充值吗": "Check to confirm recharge", + "充值确认": "Recharge confirmation", "充值数量": "Recharge quantity", "实付金额": "Actual payment amount", "是否确认充值?": "Confirm recharge?", @@ -1449,5 +1449,11 @@ "用户分组和额度管理": "User Group and Quota Management", "绑定信息": "Binding Information", "第三方账户绑定状态(只读)": "Third-party account binding status (read-only)", - "已绑定的 OIDC 账户": "Bound OIDC accounts" + "已绑定的 OIDC 账户": "Bound OIDC accounts", + "使用兑换码充值余额": "Recharge balance with redemption code", + "支持多种支付方式": "Support multiple payment methods", + "尊敬的": "Dear", + "请输入兑换码": "Please enter the redemption code", + "在线充值功能未开启": "Online recharge function is not enabled", + "管理员未开启在线充值功能,请联系管理员开启或使用兑换码充值。": "The administrator has not enabled the online recharge function, please contact the administrator to enable it or recharge with a redemption code." } \ No newline at end of file diff --git a/web/src/pages/TopUp/index.js b/web/src/pages/TopUp/index.js index 7f302cd5..893a31b1 100644 --- a/web/src/pages/TopUp/index.js +++ b/web/src/pages/TopUp/index.js @@ -1,34 +1,41 @@ -import React, { useEffect, useState } from 'react'; -import { API, isMobile, showError, showInfo, showSuccess } from '../../helpers'; +import React, { useEffect, useState, useContext } from 'react'; +import { API, showError, showInfo, showSuccess } from '../../helpers'; import { - renderNumber, renderQuota, renderQuotaWithAmount, + stringToColor, } from '../../helpers/render'; import { - Col, Layout, - Row, Typography, Card, Button, - Form, - Divider, - Space, Modal, Toast, + Input, + InputNumber, + Banner, + Skeleton, } from '@douyinfe/semi-ui'; -import Title from '@douyinfe/semi-ui/lib/es/typography/title'; -import Text from '@douyinfe/semi-ui/lib/es/typography/text'; -import { Link } from 'react-router-dom'; +import { + IconCreditCard, + IconGift, + IconPlus, + IconLink, +} from '@douyinfe/semi-icons'; +import { SiAlipay, SiWechat } from 'react-icons/si'; import { useTranslation } from 'react-i18next'; +import { UserContext } from '../../context/User'; + +const { Text } = Typography; const TopUp = () => { const { t } = useTranslation(); + const [userState, userDispatch] = useContext(UserContext); + const [redemptionCode, setRedemptionCode] = useState(''); const [topUpCode, setTopUpCode] = useState(''); const [topUpCount, setTopUpCount] = useState(0); - const [minTopupCount, setMinTopUpCount] = useState(1); const [amount, setAmount] = useState(0.0); const [minTopUp, setMinTopUp] = useState(1); const [topUpLink, setTopUpLink] = useState(''); @@ -37,6 +44,30 @@ const TopUp = () => { 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 getUsername = () => { + if (userState.user) { + return userState.user.username; + } else { + return 'null'; + } + }; + + const getUserRole = () => { + if (!userState.user) return t('普通用户'); + + switch (userState.user.role) { + case 100: + return t('超级管理员'); + case 10: + return t('管理员'); + case 0: + default: + return t('普通用户'); + } + }; const topUp = async () => { if (redemptionCode === '') { @@ -59,6 +90,13 @@ const TopUp = () => { setUserQuota((quota) => { return quota + data; }); + if (userState.user) { + const updatedUser = { + ...userState.user, + quota: userState.user.quota + data + }; + userDispatch({ type: 'login', payload: updatedUser }); + } setRedemptionCode(''); } else { showError(message); @@ -109,14 +147,12 @@ const TopUp = () => { }); if (res !== undefined) { const { message, data } = res.data; - // showInfo(message); if (message === 'success') { let params = data; let url = res.data.url; let form = document.createElement('form'); form.action = url; form.method = 'POST'; - // 判断是否为safari浏览器 let isSafari = navigator.userAgent.indexOf('Safari') > -1 && navigator.userAgent.indexOf('Chrome') < 1; @@ -135,26 +171,26 @@ const TopUp = () => { document.body.removeChild(form); } else { showError(data); - // setTopUpCount(parseInt(res.data.count)); - // setAmount(parseInt(data)); } } else { showError(res); } } catch (err) { console.log(err); - } finally { } }; 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); }; useEffect(() => { @@ -171,11 +207,16 @@ const TopUp = () => { setEnableOnlineTopUp(status.enable_online_topup); } } - getUserQuota().then(); + + if (userState?.user?.id) { + setUserDataLoading(false); + setUserQuota(userState.user.quota); + } else { + getUserQuota().then(); + } }, []); const renderAmount = () => { - // console.log(amount); return amount + ' ' + t('元'); }; @@ -183,6 +224,7 @@ const TopUp = () => { if (value === undefined) { value = topUpCount; } + setAmountLoading(true); try { const res = await API.post('/api/user/amount', { amount: parseFloat(value), @@ -190,22 +232,19 @@ const TopUp = () => { }); if (res !== undefined) { const { message, data } = res.data; - // showInfo(message); if (message === 'success') { setAmount(parseFloat(data)); } else { setAmount(0); Toast.error({ content: '错误:' + data, id: 'getAmount' }); - // setTopUpCount(parseInt(res.data.count)); - // setAmount(parseInt(data)); } } else { showError(res); } } catch (err) { console.log(err); - } finally { } + setAmountLoading(false); }; const handleCancel = () => { @@ -213,14 +252,16 @@ const TopUp = () => { }; return ( -
+
- -

{t('我的钱包')}

-
+ + {t('充值确认')} +
+ } visible={open} onOk={onlineTopUp} onCancel={handleCancel} @@ -228,111 +269,295 @@ const TopUp = () => { size={'small'} centered={true} > -

- {t('充值数量')}:{topUpCount} -

-

- {t('实付金额')}:{renderAmount()} -

-

{t('是否确认充值?')}

+
+
+ {t('充值数量')}: + {topUpCount} +
+
+ {t('实付金额')}: + {amountLoading ? ( + + ) : ( + {renderAmount()} + )} +
+
-
- - - {t('余额')} {renderQuota(userQuota)} - -
- {t('兑换余额')} -
- { - setRedemptionCode(value); - }} - /> - - {topUpLink ? ( - - ) : null} - - - -
-
- {t('在线充值')} -
- { - if (value < 1) { - value = 1; - } - setTopUpCount(value); - await getAmount(value); - }} - /> - - - - - -
- {/*
*/} - {/* */} - {/* {*/} - {/* window.location.href = '/topup/history'*/} - {/* }*/} - {/* }>充值记录*/} - {/* */} - {/*
*/} -
+ +
+
+ +
+
+ {t('当前余额')} +
+ {userDataLoading ? ( + + ) : ( +
+ {renderQuota(userState?.user?.quota || userQuota)} +
+ )} +
+ +
+
+
+
+ {t('历史消耗')} +
+ {userDataLoading ? ( + + ) : ( +
+ {renderQuota(userState?.user?.used_quota || 0)} +
+ )} +
+
+
+ {t('用户分组')} +
+ {userDataLoading ? ( + + ) : ( +
+ {userState?.user?.group || t('默认')} +
+ )} +
+
+
+ {t('用户角色')} +
+ {userDataLoading ? ( + + ) : ( +
+ {getUserRole()} +
+ )} +
+
+ +
+ {userDataLoading ? ( + + ) : ( +
+ ID: {userState?.user?.id || '---'} +
+ )} +
+
+ +
+ + + +
+
+
+
+
+ +
+
+ {t('兑换余额')} +
{t('使用兑换码充值余额')}
+
+
+ +
+
+ {t('兑换码')} + setRedemptionCode(value)} + size="large" + className="!rounded-lg" + prefix={} + /> +
+ +
+ {topUpLink && ( + + )} + +
+
+
+ +
+
+
+ +
+
+ {t('在线充值')} +
{t('支持多种支付方式')}
+
+
+ +
+
+
+ {t('充值数量')} + {amountLoading ? ( + + ) : ( + {t('实付金额:') + ' ' + renderAmount()} + )} +
+ { + if (value && value >= 1) { + setTopUpCount(value); + await getAmount(value); + } + }} + onBlur={(e) => { + const value = parseInt(e.target.value); + if (!value || value < 1) { + setTopUpCount(1); + getAmount(1); + } + }} + size="large" + className="!rounded-lg w-full" + prefix={} + formatter={(value) => value ? `${value}` : ''} + parser={(value) => value ? parseInt(value.replace(/[^\d]/g, '')) : 0} + /> +
+ +
+ + +
+ + {!enableOnlineTopUp && ( + + {t('在线充值功能未开启')} +
+ } + description={ +
+ {t('管理员未开启在线充值功能,请联系管理员开启或使用兑换码充值。')} +
+ } + /> + )} +
+
+
+ + +