✨ feat(layout): refine footer visibility logic to target CardPro component pages
- Replace blanket console route footer hiding with specific page targeting - Only hide footer on pages that use CardPro component: * /console/channel (channels management) * /console/log (usage logs) * /console/redemption (redemption codes) * /console/user (user management) * /console/token (token management) * /console/midjourney (midjourney logs) * /console/task (task logs) * /console/models (model management) * /pricing (pricing page) - Footer now displays on other console pages (dashboard, settings, topup, etc.) - Improves UI consistency by showing footer where CardPro's internal pagination isn't used This change ensures footer is only hidden when CardPro component provides its own pagination/footer functionality, while preserving footer visibility on other pages that benefit from the global footer navigation.
This commit is contained in:
@@ -135,7 +135,9 @@ const TwoFactorAuthModal = ({
|
||||
autoFocus
|
||||
/>
|
||||
<Typography.Text type='tertiary' size='small' className='mt-2 block'>
|
||||
{t('支持6位TOTP验证码或8位备用码,可到`个人设置-安全设置-两步验证设置`配置或查看。')}
|
||||
{t(
|
||||
'支持6位TOTP验证码或8位备用码,可到`个人设置-安全设置-两步验证设置`配置或查看。',
|
||||
)}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -48,9 +48,19 @@ const PageLayout = () => {
|
||||
const { i18n } = useTranslation();
|
||||
const location = useLocation();
|
||||
|
||||
const shouldHideFooter =
|
||||
location.pathname.startsWith('/console') ||
|
||||
location.pathname === '/pricing';
|
||||
const cardProPages = [
|
||||
'/console/channel',
|
||||
'/console/log',
|
||||
'/console/redemption',
|
||||
'/console/user',
|
||||
'/console/token',
|
||||
'/console/midjourney',
|
||||
'/console/task',
|
||||
'/console/models',
|
||||
'/pricing',
|
||||
];
|
||||
|
||||
const shouldHideFooter = cardProPages.includes(location.pathname);
|
||||
|
||||
const shouldInnerPadding =
|
||||
location.pathname.includes('/console') &&
|
||||
|
||||
@@ -46,7 +46,6 @@ import { useTranslation } from 'react-i18next';
|
||||
const SystemSetting = () => {
|
||||
const { t } = useTranslation();
|
||||
let [inputs, setInputs] = useState({
|
||||
|
||||
PasswordLoginEnabled: '',
|
||||
PasswordRegisterEnabled: '',
|
||||
EmailVerificationEnabled: '',
|
||||
@@ -212,7 +211,9 @@ const SystemSetting = () => {
|
||||
setInputs(newInputs);
|
||||
setOriginInputs(newInputs);
|
||||
// 同步模式布尔到本地状态
|
||||
if (typeof newInputs['fetch_setting.domain_filter_mode'] !== 'undefined') {
|
||||
if (
|
||||
typeof newInputs['fetch_setting.domain_filter_mode'] !== 'undefined'
|
||||
) {
|
||||
setDomainFilterMode(!!newInputs['fetch_setting.domain_filter_mode']);
|
||||
}
|
||||
if (typeof newInputs['fetch_setting.ip_filter_mode'] !== 'undefined') {
|
||||
@@ -749,14 +750,17 @@ const SystemSetting = () => {
|
||||
noLabel
|
||||
extraText={t('SSRF防护开关详细说明')}
|
||||
onChange={(e) =>
|
||||
handleCheckboxChange('fetch_setting.enable_ssrf_protection', e)
|
||||
handleCheckboxChange(
|
||||
'fetch_setting.enable_ssrf_protection',
|
||||
e,
|
||||
)
|
||||
}
|
||||
>
|
||||
{t('启用SSRF防护(推荐开启以保护服务器安全)')}
|
||||
</Form.Checkbox>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
|
||||
<Row
|
||||
gutter={{ xs: 8, sm: 16, md: 24, lg: 24, xl: 24, xxl: 24 }}
|
||||
style={{ marginTop: 16 }}
|
||||
@@ -767,14 +771,19 @@ const SystemSetting = () => {
|
||||
noLabel
|
||||
extraText={t('私有IP访问详细说明')}
|
||||
onChange={(e) =>
|
||||
handleCheckboxChange('fetch_setting.allow_private_ip', e)
|
||||
handleCheckboxChange(
|
||||
'fetch_setting.allow_private_ip',
|
||||
e,
|
||||
)
|
||||
}
|
||||
>
|
||||
{t('允许访问私有IP地址(127.0.0.1、192.168.x.x等内网地址)')}
|
||||
{t(
|
||||
'允许访问私有IP地址(127.0.0.1、192.168.x.x等内网地址)',
|
||||
)}
|
||||
</Form.Checkbox>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
|
||||
<Row
|
||||
gutter={{ xs: 8, sm: 16, md: 24, lg: 24, xl: 24, xxl: 24 }}
|
||||
style={{ marginTop: 16 }}
|
||||
@@ -785,7 +794,10 @@ const SystemSetting = () => {
|
||||
noLabel
|
||||
extraText={t('域名IP过滤详细说明')}
|
||||
onChange={(e) =>
|
||||
handleCheckboxChange('fetch_setting.apply_ip_filter_for_domain', e)
|
||||
handleCheckboxChange(
|
||||
'fetch_setting.apply_ip_filter_for_domain',
|
||||
e,
|
||||
)
|
||||
}
|
||||
style={{ marginBottom: 8 }}
|
||||
>
|
||||
@@ -794,17 +806,23 @@ const SystemSetting = () => {
|
||||
<Text strong>
|
||||
{t(domainFilterMode ? '域名白名单' : '域名黑名单')}
|
||||
</Text>
|
||||
<Text type="secondary" style={{ display: 'block', marginBottom: 8 }}>
|
||||
{t('支持通配符格式,如:example.com, *.api.example.com')}
|
||||
<Text
|
||||
type='secondary'
|
||||
style={{ display: 'block', marginBottom: 8 }}
|
||||
>
|
||||
{t(
|
||||
'支持通配符格式,如:example.com, *.api.example.com',
|
||||
)}
|
||||
</Text>
|
||||
<Radio.Group
|
||||
type='button'
|
||||
value={domainFilterMode ? 'whitelist' : 'blacklist'}
|
||||
onChange={(val) => {
|
||||
const selected = val && val.target ? val.target.value : val;
|
||||
const selected =
|
||||
val && val.target ? val.target.value : val;
|
||||
const isWhitelist = selected === 'whitelist';
|
||||
setDomainFilterMode(isWhitelist);
|
||||
setInputs(prev => ({
|
||||
setInputs((prev) => ({
|
||||
...prev,
|
||||
'fetch_setting.domain_filter_mode': isWhitelist,
|
||||
}));
|
||||
@@ -819,9 +837,9 @@ const SystemSetting = () => {
|
||||
onChange={(value) => {
|
||||
setDomainList(value);
|
||||
// 触发Form的onChange事件
|
||||
setInputs(prev => ({
|
||||
setInputs((prev) => ({
|
||||
...prev,
|
||||
'fetch_setting.domain_list': value
|
||||
'fetch_setting.domain_list': value,
|
||||
}));
|
||||
}}
|
||||
placeholder={t('输入域名后回车,如:example.com')}
|
||||
@@ -838,17 +856,21 @@ const SystemSetting = () => {
|
||||
<Text strong>
|
||||
{t(ipFilterMode ? 'IP白名单' : 'IP黑名单')}
|
||||
</Text>
|
||||
<Text type="secondary" style={{ display: 'block', marginBottom: 8 }}>
|
||||
<Text
|
||||
type='secondary'
|
||||
style={{ display: 'block', marginBottom: 8 }}
|
||||
>
|
||||
{t('支持CIDR格式,如:8.8.8.8, 192.168.1.0/24')}
|
||||
</Text>
|
||||
<Radio.Group
|
||||
type='button'
|
||||
value={ipFilterMode ? 'whitelist' : 'blacklist'}
|
||||
onChange={(val) => {
|
||||
const selected = val && val.target ? val.target.value : val;
|
||||
const selected =
|
||||
val && val.target ? val.target.value : val;
|
||||
const isWhitelist = selected === 'whitelist';
|
||||
setIpFilterMode(isWhitelist);
|
||||
setInputs(prev => ({
|
||||
setInputs((prev) => ({
|
||||
...prev,
|
||||
'fetch_setting.ip_filter_mode': isWhitelist,
|
||||
}));
|
||||
@@ -863,9 +885,9 @@ const SystemSetting = () => {
|
||||
onChange={(value) => {
|
||||
setIpList(value);
|
||||
// 触发Form的onChange事件
|
||||
setInputs(prev => ({
|
||||
setInputs((prev) => ({
|
||||
...prev,
|
||||
'fetch_setting.ip_list': value
|
||||
'fetch_setting.ip_list': value,
|
||||
}));
|
||||
}}
|
||||
placeholder={t('输入IP地址后回车,如:8.8.8.8')}
|
||||
@@ -880,7 +902,10 @@ const SystemSetting = () => {
|
||||
>
|
||||
<Col xs={24} sm={24} md={24} lg={24} xl={24}>
|
||||
<Text strong>{t('允许的端口')}</Text>
|
||||
<Text type="secondary" style={{ display: 'block', marginBottom: 8 }}>
|
||||
<Text
|
||||
type='secondary'
|
||||
style={{ display: 'block', marginBottom: 8 }}
|
||||
>
|
||||
{t('支持单个端口和端口范围,如:80, 443, 8000-8999')}
|
||||
</Text>
|
||||
<TagInput
|
||||
@@ -888,15 +913,18 @@ const SystemSetting = () => {
|
||||
onChange={(value) => {
|
||||
setAllowedPorts(value);
|
||||
// 触发Form的onChange事件
|
||||
setInputs(prev => ({
|
||||
setInputs((prev) => ({
|
||||
...prev,
|
||||
'fetch_setting.allowed_ports': value
|
||||
'fetch_setting.allowed_ports': value,
|
||||
}));
|
||||
}}
|
||||
placeholder={t('输入端口后回车,如:80 或 8000-8999')}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
<Text type="secondary" style={{ display: 'block', marginBottom: 8 }}>
|
||||
<Text
|
||||
type='secondary'
|
||||
style={{ display: 'block', marginBottom: 8 }}
|
||||
>
|
||||
{t('端口配置详细说明')}
|
||||
</Text>
|
||||
</Col>
|
||||
|
||||
@@ -91,22 +91,7 @@ const REGION_EXAMPLE = {
|
||||
|
||||
// 支持并且已适配通过接口获取模型列表的渠道类型
|
||||
const MODEL_FETCHABLE_TYPES = new Set([
|
||||
1,
|
||||
4,
|
||||
14,
|
||||
34,
|
||||
17,
|
||||
26,
|
||||
24,
|
||||
47,
|
||||
25,
|
||||
20,
|
||||
23,
|
||||
31,
|
||||
35,
|
||||
40,
|
||||
42,
|
||||
48,
|
||||
1, 4, 14, 34, 17, 26, 24, 47, 25, 20, 23, 31, 35, 40, 42, 48,
|
||||
43,
|
||||
]);
|
||||
|
||||
@@ -279,8 +264,8 @@ const EditChannelModal = (props) => {
|
||||
const scrollToSection = (sectionKey) => {
|
||||
const sectionElement = formSectionRefs.current[sectionKey];
|
||||
if (sectionElement) {
|
||||
sectionElement.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
sectionElement.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'start',
|
||||
inline: 'nearest'
|
||||
});
|
||||
@@ -301,7 +286,7 @@ const EditChannelModal = (props) => {
|
||||
} else {
|
||||
newIndex = currentSectionIndex < availableSections.length - 1 ? currentSectionIndex + 1 : 0;
|
||||
}
|
||||
|
||||
|
||||
setCurrentSectionIndex(newIndex);
|
||||
scrollToSection(availableSections[newIndex]);
|
||||
};
|
||||
@@ -1340,7 +1325,7 @@ const EditChannelModal = (props) => {
|
||||
type='tertiary'
|
||||
icon={<IconChevronUp />}
|
||||
onClick={() => navigateToSection('up')}
|
||||
style={{
|
||||
style={{
|
||||
borderRadius: '50%',
|
||||
width: '32px',
|
||||
height: '32px',
|
||||
@@ -1356,7 +1341,7 @@ const EditChannelModal = (props) => {
|
||||
type='tertiary'
|
||||
icon={<IconChevronDown />}
|
||||
onClick={() => navigateToSection('down')}
|
||||
style={{
|
||||
style={{
|
||||
borderRadius: '50%',
|
||||
width: '32px',
|
||||
height: '32px',
|
||||
@@ -1398,8 +1383,8 @@ const EditChannelModal = (props) => {
|
||||
>
|
||||
{() => (
|
||||
<Spin spinning={loading}>
|
||||
<div
|
||||
className='p-2'
|
||||
<div
|
||||
className='p-2'
|
||||
ref={formContainerRef}
|
||||
>
|
||||
<div ref={el => formSectionRefs.current.basicInfo = el}>
|
||||
|
||||
@@ -56,10 +56,10 @@ const MjLogsFilters = ({
|
||||
showClear
|
||||
pure
|
||||
size='small'
|
||||
presets={DATE_RANGE_PRESETS.map(preset => ({
|
||||
presets={DATE_RANGE_PRESETS.map((preset) => ({
|
||||
text: t(preset.text),
|
||||
start: preset.start(),
|
||||
end: preset.end()
|
||||
end: preset.end(),
|
||||
}))}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -36,8 +36,9 @@ import {
|
||||
} from 'lucide-react';
|
||||
import {
|
||||
TASK_ACTION_FIRST_TAIL_GENERATE,
|
||||
TASK_ACTION_GENERATE, TASK_ACTION_REFERENCE_GENERATE,
|
||||
TASK_ACTION_TEXT_GENERATE
|
||||
TASK_ACTION_GENERATE,
|
||||
TASK_ACTION_REFERENCE_GENERATE,
|
||||
TASK_ACTION_TEXT_GENERATE,
|
||||
} from '../../../constants/common.constant';
|
||||
import { CHANNEL_OPTIONS } from '../../../constants/channel.constants';
|
||||
|
||||
|
||||
@@ -56,10 +56,10 @@ const TaskLogsFilters = ({
|
||||
showClear
|
||||
pure
|
||||
size='small'
|
||||
presets={DATE_RANGE_PRESETS.map(preset => ({
|
||||
presets={DATE_RANGE_PRESETS.map((preset) => ({
|
||||
text: t(preset.text),
|
||||
start: preset.start(),
|
||||
end: preset.end()
|
||||
end: preset.end(),
|
||||
}))}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -57,10 +57,10 @@ const LogsFilters = ({
|
||||
showClear
|
||||
pure
|
||||
size='small'
|
||||
presets={DATE_RANGE_PRESETS.map(preset => ({
|
||||
presets={DATE_RANGE_PRESETS.map((preset) => ({
|
||||
text: t(preset.text),
|
||||
start: preset.start(),
|
||||
end: preset.end()
|
||||
end: preset.end(),
|
||||
}))}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -30,7 +30,8 @@ import {
|
||||
Space,
|
||||
Row,
|
||||
Col,
|
||||
Spin, Tooltip
|
||||
Spin,
|
||||
Tooltip,
|
||||
} from '@douyinfe/semi-ui';
|
||||
import { SiAlipay, SiWechat, SiStripe } from 'react-icons/si';
|
||||
import { CreditCard, Coins, Wallet, BarChart2, TrendingUp } from 'lucide-react';
|
||||
@@ -266,7 +267,8 @@ const RechargeCard = ({
|
||||
{payMethods && payMethods.length > 0 ? (
|
||||
<Space wrap>
|
||||
{payMethods.map((payMethod) => {
|
||||
const minTopupVal = Number(payMethod.min_topup) || 0;
|
||||
const minTopupVal =
|
||||
Number(payMethod.min_topup) || 0;
|
||||
const isStripe = payMethod.type === 'stripe';
|
||||
const disabled =
|
||||
(!enableOnlineTopUp && !isStripe) ||
|
||||
@@ -280,7 +282,9 @@ const RechargeCard = ({
|
||||
type='tertiary'
|
||||
onClick={() => preTopUp(payMethod.type)}
|
||||
disabled={disabled}
|
||||
loading={paymentLoading && payWay === payMethod.type}
|
||||
loading={
|
||||
paymentLoading && payWay === payMethod.type
|
||||
}
|
||||
icon={
|
||||
payMethod.type === 'alipay' ? (
|
||||
<SiAlipay size={18} color='#1677FF' />
|
||||
@@ -291,7 +295,10 @@ const RechargeCard = ({
|
||||
) : (
|
||||
<CreditCard
|
||||
size={18}
|
||||
color={payMethod.color || 'var(--semi-color-text-2)'}
|
||||
color={
|
||||
payMethod.color ||
|
||||
'var(--semi-color-text-2)'
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -301,12 +308,22 @@ const RechargeCard = ({
|
||||
</Button>
|
||||
);
|
||||
|
||||
return disabled && minTopupVal > Number(topUpCount || 0) ? (
|
||||
<Tooltip content={t('此支付方式最低充值金额为') + ' ' + minTopupVal} key={payMethod.type}>
|
||||
return disabled &&
|
||||
minTopupVal > Number(topUpCount || 0) ? (
|
||||
<Tooltip
|
||||
content={
|
||||
t('此支付方式最低充值金额为') +
|
||||
' ' +
|
||||
minTopupVal
|
||||
}
|
||||
key={payMethod.type}
|
||||
>
|
||||
{buttonEl}
|
||||
</Tooltip>
|
||||
) : (
|
||||
<React.Fragment key={payMethod.type}>{buttonEl}</React.Fragment>
|
||||
<React.Fragment key={payMethod.type}>
|
||||
{buttonEl}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</Space>
|
||||
@@ -324,23 +341,27 @@ const RechargeCard = ({
|
||||
<Form.Slot label={t('选择充值额度')}>
|
||||
<div className='grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-2'>
|
||||
{presetAmounts.map((preset, index) => {
|
||||
const discount = preset.discount || topupInfo?.discount?.[preset.value] || 1.0;
|
||||
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;
|
||||
|
||||
|
||||
return (
|
||||
<Card
|
||||
key={index}
|
||||
style={{
|
||||
cursor: 'pointer',
|
||||
border: selectedPreset === preset.value
|
||||
? '2px solid var(--semi-color-primary)'
|
||||
: '1px solid var(--semi-color-border)',
|
||||
border:
|
||||
selectedPreset === preset.value
|
||||
? '2px solid var(--semi-color-primary)'
|
||||
: '1px solid var(--semi-color-border)',
|
||||
height: '100%',
|
||||
width: '100%'
|
||||
width: '100%',
|
||||
}}
|
||||
bodyStyle={{ padding: '12px' }}
|
||||
onClick={() => {
|
||||
@@ -352,24 +373,35 @@ const RechargeCard = ({
|
||||
}}
|
||||
>
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<Typography.Title heading={6} style={{ margin: '0 0 8px 0' }}>
|
||||
<Typography.Title
|
||||
heading={6}
|
||||
style={{ margin: '0 0 8px 0' }}
|
||||
>
|
||||
<Coins size={18} />
|
||||
{formatLargeNumber(preset.value)}
|
||||
{hasDiscount && (
|
||||
<Tag style={{ marginLeft: 4 }} color="green">
|
||||
{t('折').includes('off') ?
|
||||
((1 - parseFloat(discount)) * 100).toFixed(1) :
|
||||
(discount * 10).toFixed(1)}{t('折')}
|
||||
</Tag>
|
||||
<Tag style={{ marginLeft: 4 }} color='green'>
|
||||
{t('折').includes('off')
|
||||
? (
|
||||
(1 - parseFloat(discount)) *
|
||||
100
|
||||
).toFixed(1)
|
||||
: (discount * 10).toFixed(1)}
|
||||
{t('折')}
|
||||
</Tag>
|
||||
)}
|
||||
</Typography.Title>
|
||||
<div style={{
|
||||
color: 'var(--semi-color-text-2)',
|
||||
fontSize: '12px',
|
||||
margin: '4px 0'
|
||||
}}>
|
||||
<div
|
||||
style={{
|
||||
color: 'var(--semi-color-text-2)',
|
||||
fontSize: '12px',
|
||||
margin: '4px 0',
|
||||
}}
|
||||
>
|
||||
{t('实付')} {actualPay.toFixed(2)},
|
||||
{hasDiscount ? `${t('节省')} ${save.toFixed(2)}` : `${t('节省')} 0.00`}
|
||||
{hasDiscount
|
||||
? `${t('节省')} ${save.toFixed(2)}`
|
||||
: `${t('节省')} 0.00`}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
@@ -80,11 +80,11 @@ const TopUp = () => {
|
||||
// 预设充值额度选项
|
||||
const [presetAmounts, setPresetAmounts] = useState([]);
|
||||
const [selectedPreset, setSelectedPreset] = useState(null);
|
||||
|
||||
|
||||
// 充值配置信息
|
||||
const [topupInfo, setTopupInfo] = useState({
|
||||
amount_options: [],
|
||||
discount: {}
|
||||
discount: {},
|
||||
});
|
||||
|
||||
const topUp = async () => {
|
||||
@@ -262,9 +262,9 @@ const TopUp = () => {
|
||||
if (success) {
|
||||
setTopupInfo({
|
||||
amount_options: data.amount_options || [],
|
||||
discount: data.discount || {}
|
||||
discount: data.discount || {},
|
||||
});
|
||||
|
||||
|
||||
// 处理支付方式
|
||||
let payMethods = data.pay_methods || [];
|
||||
try {
|
||||
@@ -280,10 +280,15 @@ const TopUp = () => {
|
||||
payMethods = payMethods.map((method) => {
|
||||
// 规范化最小充值数
|
||||
const normalizedMinTopup = Number(method.min_topup);
|
||||
method.min_topup = Number.isFinite(normalizedMinTopup) ? normalizedMinTopup : 0;
|
||||
method.min_topup = Number.isFinite(normalizedMinTopup)
|
||||
? normalizedMinTopup
|
||||
: 0;
|
||||
|
||||
// Stripe 的最小充值从后端字段回填
|
||||
if (method.type === 'stripe' && (!method.min_topup || method.min_topup <= 0)) {
|
||||
if (
|
||||
method.type === 'stripe' &&
|
||||
(!method.min_topup || method.min_topup <= 0)
|
||||
) {
|
||||
const stripeMin = Number(data.stripe_min_topup);
|
||||
if (Number.isFinite(stripeMin)) {
|
||||
method.min_topup = stripeMin;
|
||||
@@ -313,7 +318,11 @@ const TopUp = () => {
|
||||
setPayMethods(payMethods);
|
||||
const enableStripeTopUp = data.enable_stripe_topup || false;
|
||||
const enableOnlineTopUp = data.enable_online_topup || false;
|
||||
const minTopUpValue = enableOnlineTopUp? data.min_topup : enableStripeTopUp? data.stripe_min_topup : 1;
|
||||
const minTopUpValue = enableOnlineTopUp
|
||||
? data.min_topup
|
||||
: enableStripeTopUp
|
||||
? data.stripe_min_topup
|
||||
: 1;
|
||||
setEnableOnlineTopUp(enableOnlineTopUp);
|
||||
setEnableStripeTopUp(enableStripeTopUp);
|
||||
setMinTopUp(minTopUpValue);
|
||||
@@ -330,12 +339,12 @@ const TopUp = () => {
|
||||
console.log('解析支付方式失败:', e);
|
||||
setPayMethods([]);
|
||||
}
|
||||
|
||||
|
||||
// 如果有自定义充值数量选项,使用它们替换默认的预设选项
|
||||
if (data.amount_options && data.amount_options.length > 0) {
|
||||
const customPresets = data.amount_options.map(amount => ({
|
||||
const customPresets = data.amount_options.map((amount) => ({
|
||||
value: amount,
|
||||
discount: data.discount[amount] || 1.0
|
||||
discount: data.discount[amount] || 1.0,
|
||||
}));
|
||||
setPresetAmounts(customPresets);
|
||||
}
|
||||
@@ -483,7 +492,7 @@ const TopUp = () => {
|
||||
const selectPresetAmount = (preset) => {
|
||||
setTopUpCount(preset.value);
|
||||
setSelectedPreset(preset.value);
|
||||
|
||||
|
||||
// 计算实际支付金额,考虑折扣
|
||||
const discount = preset.discount || topupInfo.discount[preset.value] || 1.0;
|
||||
const discountedAmount = preset.value * priceRatio * discount;
|
||||
|
||||
@@ -40,9 +40,10 @@ const PaymentConfirmModal = ({
|
||||
amountNumber,
|
||||
discountRate,
|
||||
}) => {
|
||||
const hasDiscount = discountRate && discountRate > 0 && discountRate < 1 && amountNumber > 0;
|
||||
const originalAmount = hasDiscount ? (amountNumber / discountRate) : 0;
|
||||
const discountAmount = hasDiscount ? (originalAmount - amountNumber) : 0;
|
||||
const hasDiscount =
|
||||
discountRate && discountRate > 0 && discountRate < 1 && amountNumber > 0;
|
||||
const originalAmount = hasDiscount ? amountNumber / discountRate : 0;
|
||||
const discountAmount = hasDiscount ? originalAmount - amountNumber : 0;
|
||||
return (
|
||||
<Modal
|
||||
title={
|
||||
|
||||
Reference in New Issue
Block a user