Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
new-api
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
edf46c70
authored
Sep 08, 2025
by
Little Write
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
前端部分,调试 完善
parent
51a7aa44
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
630 additions
and
2 deletions
+630
-2
web/src/components/settings/PaymentSetting.js
+16
-0
web/src/pages/Setting/Payment/SettingsPaymentGatewayCreem.js
+374
-0
web/src/pages/TopUp/index.js
+240
-2
No files found.
web/src/components/settings/PaymentSetting.js
View file @
edf46c70
...
@@ -3,9 +3,11 @@ import { Card, Spin } from '@douyinfe/semi-ui';
...
@@ -3,9 +3,11 @@ import { Card, Spin } from '@douyinfe/semi-ui';
import
SettingsGeneralPayment
from
'../../pages/Setting/Payment/SettingsGeneralPayment.js'
;
import
SettingsGeneralPayment
from
'../../pages/Setting/Payment/SettingsGeneralPayment.js'
;
import
SettingsPaymentGateway
from
'../../pages/Setting/Payment/SettingsPaymentGateway.js'
;
import
SettingsPaymentGateway
from
'../../pages/Setting/Payment/SettingsPaymentGateway.js'
;
import
SettingsPaymentGatewayStripe
from
'../../pages/Setting/Payment/SettingsPaymentGatewayStripe.js'
;
import
SettingsPaymentGatewayStripe
from
'../../pages/Setting/Payment/SettingsPaymentGatewayStripe.js'
;
import
SettingsPaymentGatewayCreem
from
'../../pages/Setting/Payment/SettingsPaymentGatewayCreem.js'
;
import
{
API
,
showError
,
toBoolean
}
from
'../../helpers'
;
import
{
API
,
showError
,
toBoolean
}
from
'../../helpers'
;
import
{
useTranslation
}
from
'react-i18next'
;
import
{
useTranslation
}
from
'react-i18next'
;
const
PaymentSetting
=
()
=>
{
const
PaymentSetting
=
()
=>
{
const
{
t
}
=
useTranslation
();
const
{
t
}
=
useTranslation
();
let
[
inputs
,
setInputs
]
=
useState
({
let
[
inputs
,
setInputs
]
=
useState
({
...
@@ -24,6 +26,9 @@ const PaymentSetting = () => {
...
@@ -24,6 +26,9 @@ const PaymentSetting = () => {
StripePriceId
:
''
,
StripePriceId
:
''
,
StripeUnitPrice
:
8.0
,
StripeUnitPrice
:
8.0
,
StripeMinTopUp
:
1
,
StripeMinTopUp
:
1
,
CreemApiKey
:
''
,
CreemProducts
:
'[]'
,
});
});
let
[
loading
,
setLoading
]
=
useState
(
false
);
let
[
loading
,
setLoading
]
=
useState
(
false
);
...
@@ -43,6 +48,14 @@ const PaymentSetting = () => {
...
@@ -43,6 +48,14 @@ const PaymentSetting = () => {
newInputs
[
item
.
key
]
=
item
.
value
;
newInputs
[
item
.
key
]
=
item
.
value
;
}
}
break
;
break
;
case
'CreemProducts'
:
try
{
newInputs
[
item
.
key
]
=
item
.
value
;
}
catch
(
error
)
{
console
.
error
(
'解析CreemProducts出错:'
,
error
);
newInputs
[
item
.
key
]
=
'[]'
;
}
break
;
case
'Price'
:
case
'Price'
:
case
'MinTopUp'
:
case
'MinTopUp'
:
case
'StripeUnitPrice'
:
case
'StripeUnitPrice'
:
...
@@ -92,6 +105,9 @@ const PaymentSetting = () => {
...
@@ -92,6 +105,9 @@ const PaymentSetting = () => {
<
Card
style
=
{{
marginTop
:
'10px'
}}
>
<
Card
style
=
{{
marginTop
:
'10px'
}}
>
<
SettingsPaymentGatewayStripe
options
=
{
inputs
}
refresh
=
{
onRefresh
}
/
>
<
SettingsPaymentGatewayStripe
options
=
{
inputs
}
refresh
=
{
onRefresh
}
/
>
<
/Card
>
<
/Card
>
<
Card
style
=
{{
marginTop
:
'10px'
}}
>
<
SettingsPaymentGatewayCreem
options
=
{
inputs
}
refresh
=
{
onRefresh
}
/
>
<
/Card
>
<
/Spin
>
<
/Spin
>
<
/
>
<
/
>
);
);
...
...
web/src/pages/Setting/Payment/SettingsPaymentGatewayCreem.js
0 → 100644
View file @
edf46c70
import
React
,
{
useEffect
,
useState
,
useRef
}
from
'react'
;
import
{
Banner
,
Button
,
Form
,
Row
,
Col
,
Typography
,
Spin
,
Table
,
Modal
,
Input
,
InputNumber
,
Select
,
}
from
'@douyinfe/semi-ui'
;
const
{
Text
}
=
Typography
;
import
{
API
,
showError
,
showSuccess
,
}
from
'../../../helpers'
;
import
{
useTranslation
}
from
'react-i18next'
;
import
{
Plus
,
Trash2
}
from
'lucide-react'
;
export
default
function
SettingsPaymentGatewayCreem
(
props
)
{
const
{
t
}
=
useTranslation
();
const
[
loading
,
setLoading
]
=
useState
(
false
);
const
[
inputs
,
setInputs
]
=
useState
({
CreemApiKey
:
''
,
CreemProducts
:
'[]'
,
CreemTestMode
:
false
,
});
const
[
originInputs
,
setOriginInputs
]
=
useState
({});
const
[
products
,
setProducts
]
=
useState
([]);
const
[
showProductModal
,
setShowProductModal
]
=
useState
(
false
);
const
[
editingProduct
,
setEditingProduct
]
=
useState
(
null
);
const
[
productForm
,
setProductForm
]
=
useState
({
name
:
''
,
productId
:
''
,
price
:
0
,
quota
:
0
,
currency
:
'USD'
,
});
const
formApiRef
=
useRef
(
null
);
useEffect
(()
=>
{
if
(
props
.
options
&&
formApiRef
.
current
)
{
const
currentInputs
=
{
CreemApiKey
:
props
.
options
.
CreemApiKey
||
''
,
CreemProducts
:
props
.
options
.
CreemProducts
||
'[]'
,
CreemTestMode
:
props
.
options
.
CreemTestMode
===
'true'
,
};
setInputs
(
currentInputs
);
setOriginInputs
({
...
currentInputs
});
formApiRef
.
current
.
setValues
(
currentInputs
);
// Parse products
try
{
const
parsedProducts
=
JSON
.
parse
(
currentInputs
.
CreemProducts
);
setProducts
(
parsedProducts
);
}
catch
(
e
)
{
setProducts
([]);
}
}
},
[
props
.
options
]);
const
handleFormChange
=
(
values
)
=>
{
setInputs
(
values
);
};
const
submitCreemSetting
=
async
()
=>
{
setLoading
(
true
);
try
{
const
options
=
[];
if
(
inputs
.
CreemApiKey
&&
inputs
.
CreemApiKey
!==
''
)
{
options
.
push
({
key
:
'CreemApiKey'
,
value
:
inputs
.
CreemApiKey
});
}
// Save test mode setting
options
.
push
({
key
:
'CreemTestMode'
,
value
:
inputs
.
CreemTestMode
?
'true'
:
'false'
});
// Save products as JSON string
options
.
push
({
key
:
'CreemProducts'
,
value
:
JSON
.
stringify
(
products
)
});
// 发送请求
const
requestQueue
=
options
.
map
(
opt
=>
API
.
put
(
'/api/option/'
,
{
key
:
opt
.
key
,
value
:
opt
.
value
,
})
);
const
results
=
await
Promise
.
all
(
requestQueue
);
// 检查所有请求是否成功
const
errorResults
=
results
.
filter
(
res
=>
!
res
.
data
.
success
);
if
(
errorResults
.
length
>
0
)
{
errorResults
.
forEach
(
res
=>
{
showError
(
res
.
data
.
message
);
});
}
else
{
showSuccess
(
t
(
'更新成功'
));
// 更新本地存储的原始值
setOriginInputs
({
...
inputs
});
props
.
refresh
?.();
}
}
catch
(
error
)
{
showError
(
t
(
'更新失败'
));
}
setLoading
(
false
);
};
const
openProductModal
=
(
product
=
null
)
=>
{
if
(
product
)
{
setEditingProduct
(
product
);
setProductForm
({
...
product
});
}
else
{
setEditingProduct
(
null
);
setProductForm
({
name
:
''
,
productId
:
''
,
price
:
0
,
quota
:
0
,
currency
:
'USD'
,
});
}
setShowProductModal
(
true
);
};
const
closeProductModal
=
()
=>
{
setShowProductModal
(
false
);
setEditingProduct
(
null
);
setProductForm
({
name
:
''
,
productId
:
''
,
price
:
0
,
quota
:
0
,
currency
:
'USD'
,
});
};
const
saveProduct
=
()
=>
{
if
(
!
productForm
.
name
||
!
productForm
.
productId
||
productForm
.
price
<=
0
||
productForm
.
quota
<=
0
||
!
productForm
.
currency
)
{
showError
(
t
(
'请填写完整的产品信息'
));
return
;
}
let
newProducts
=
[...
products
];
if
(
editingProduct
)
{
// 编辑现有产品
const
index
=
newProducts
.
findIndex
(
p
=>
p
.
productId
===
editingProduct
.
productId
);
if
(
index
!==
-
1
)
{
newProducts
[
index
]
=
{
...
productForm
};
}
}
else
{
// 添加新产品
if
(
newProducts
.
find
(
p
=>
p
.
productId
===
productForm
.
productId
))
{
showError
(
t
(
'产品ID已存在'
));
return
;
}
newProducts
.
push
({
...
productForm
});
}
setProducts
(
newProducts
);
closeProductModal
();
};
const
deleteProduct
=
(
productId
)
=>
{
const
newProducts
=
products
.
filter
(
p
=>
p
.
productId
!==
productId
);
setProducts
(
newProducts
);
};
const
columns
=
[
{
title
:
t
(
'产品名称'
),
dataIndex
:
'name'
,
key
:
'name'
,
},
{
title
:
t
(
'产品ID'
),
dataIndex
:
'productId'
,
key
:
'productId'
,
},
{
title
:
t
(
'价格'
),
dataIndex
:
'price'
,
key
:
'price'
,
render
:
(
price
,
record
)
=>
`
${
record
.
currency
===
'EUR'
?
'€'
:
'$'
}${
price
}
`
,
},
{
title
:
t
(
'充值额度'
),
dataIndex
:
'quota'
,
key
:
'quota'
,
},
{
title
:
t
(
'操作'
),
key
:
'action'
,
render
:
(
_
,
record
)
=>
(
<
div
className
=
'flex gap-2'
>
<
Button
type
=
'tertiary'
size
=
'small'
onClick
=
{()
=>
openProductModal
(
record
)}
>
{
t
(
'编辑'
)}
<
/Button
>
<
Button
type
=
'danger'
theme
=
'borderless'
size
=
'small'
icon
=
{
<
Trash2
size
=
{
14
}
/>
}
onClick
=
{()
=>
deleteProduct
(
record
.
productId
)}
/
>
<
/div
>
),
},
];
return
(
<
Spin
spinning
=
{
loading
}
>
<
Form
initValues
=
{
inputs
}
onValueChange
=
{
handleFormChange
}
getFormApi
=
{(
api
)
=>
(
formApiRef
.
current
=
api
)}
>
<
Form
.
Section
text
=
{
t
(
'Creem 设置'
)}
>
<
Text
>
Creem
是一个简单的支付处理平台,支持固定金额的产品销售。请在
<
a
href
=
'https://creem.io'
target
=
'_blank'
rel
=
'noreferrer'
>
Creem
官网
<
/a
>
创建账户并获取
API
密钥。
<
br
/>
<
/Text
>
<
Banner
type
=
'info'
description
=
{
t
(
'Creem 只支持预设的固定金额产品,不支持自定义金额充值'
)}
/
>
<
Row
gutter
=
{{
xs
:
8
,
sm
:
16
,
md
:
24
,
lg
:
24
,
xl
:
24
,
xxl
:
24
}}
>
<
Col
xs
=
{
24
}
sm
=
{
24
}
md
=
{
12
}
lg
=
{
12
}
xl
=
{
12
}
>
<
Form
.
Input
field
=
'CreemApiKey'
label
=
{
t
(
'API 密钥'
)}
placeholder
=
{
t
(
'creem_xxx 的 Creem API 密钥,敏感信息不显示'
)}
type
=
'password'
/>
<
/Col
>
<
Col
xs
=
{
24
}
sm
=
{
24
}
md
=
{
12
}
lg
=
{
12
}
xl
=
{
12
}
>
<
Form
.
Switch
field
=
'CreemTestMode'
label
=
{
t
(
'测试模式'
)}
extraText
=
{
t
(
'启用后将使用 Creem 测试环境,可使用测试卡号 4242 4242 4242 4242 进行测试'
)}
/
>
<
/Col
>
<
/Row
>
<
div
style
=
{{
marginTop
:
24
}}
>
<
div
className
=
'flex justify-between items-center mb-4'
>
<
Text
strong
>
{
t
(
'产品配置'
)}
<
/Text
>
<
Button
type
=
'primary'
icon
=
{
<
Plus
size
=
{
16
}
/>
}
onClick
=
{()
=>
openProductModal
()}
>
{
t
(
'添加产品'
)}
<
/Button
>
<
/div
>
<
Table
columns
=
{
columns
}
dataSource
=
{
products
}
pagination
=
{
false
}
empty
=
{
<
div
className
=
'text-center py-8'
>
<
Text
type
=
'tertiary'
>
{
t
(
'暂无产品配置'
)}
<
/Text
>
<
/div
>
}
/
>
<
/div
>
<
Button
onClick
=
{
submitCreemSetting
}
style
=
{{
marginTop
:
16
}}
>
{
t
(
'更新 Creem 设置'
)}
<
/Button
>
<
/Form.Section
>
<
/Form
>
{
/* 产品配置模态框 */
}
<
Modal
title
=
{
editingProduct
?
t
(
'编辑产品'
)
:
t
(
'添加产品'
)}
visible
=
{
showProductModal
}
onOk
=
{
saveProduct
}
onCancel
=
{
closeProductModal
}
maskClosable
=
{
false
}
size
=
'small'
centered
>
<
div
className
=
'space-y-4'
>
<
div
>
<
Text
strong
className
=
'block mb-2'
>
{
t
(
'产品名称'
)}
<
/Text
>
<
Input
value
=
{
productForm
.
name
}
onChange
=
{(
value
)
=>
setProductForm
({
...
productForm
,
name
:
value
})}
placeholder
=
{
t
(
'例如:基础套餐'
)}
size
=
'large'
/>
<
/div
>
<
div
>
<
Text
strong
className
=
'block mb-2'
>
{
t
(
'产品ID'
)}
<
/Text
>
<
Input
value
=
{
productForm
.
productId
}
onChange
=
{(
value
)
=>
setProductForm
({
...
productForm
,
productId
:
value
})}
placeholder
=
{
t
(
'例如:prod_6I8rBerHpPxyoiU9WK4kot'
)}
size
=
'large'
disabled
=
{
!!
editingProduct
}
/
>
<
/div
>
<
div
>
<
Text
strong
className
=
'block mb-2'
>
{
t
(
'货币'
)}
<
/Text
>
<
Select
value
=
{
productForm
.
currency
}
onChange
=
{(
value
)
=>
setProductForm
({
...
productForm
,
currency
:
value
})}
size
=
'large'
className
=
'w-full'
>
<
Select
.
Option
value
=
'USD'
>
USD
(
美元
)
<
/Select.Option
>
<
Select
.
Option
value
=
'EUR'
>
EUR
(
欧元
)
<
/Select.Option
>
<
/Select
>
<
/div
>
<
div
>
<
Text
strong
className
=
'block mb-2'
>
{
t
(
'价格'
)}
({
productForm
.
currency
===
'EUR'
?
'欧元'
:
'美元'
})
<
/Text
>
<
InputNumber
value
=
{
productForm
.
price
}
onChange
=
{(
value
)
=>
setProductForm
({
...
productForm
,
price
:
value
})}
placeholder
=
{
t
(
'例如:4.99'
)}
min
=
{
0.01
}
precision
=
{
2
}
size
=
'large'
className
=
'w-full'
/>
<
/div
>
<
div
>
<
Text
strong
className
=
'block mb-2'
>
{
t
(
'充值额度'
)}
<
/Text
>
<
InputNumber
value
=
{
productForm
.
quota
}
onChange
=
{(
value
)
=>
setProductForm
({
...
productForm
,
quota
:
value
})}
placeholder
=
{
t
(
'例如:100000'
)}
min
=
{
1
}
precision
=
{
0
}
size
=
'large'
className
=
'w-full'
/>
<
/div
>
<
/div
>
<
/Modal
>
<
/Spin
>
);
}
\ No newline at end of file
web/src/pages/TopUp/index.js
View file @
edf46c70
...
@@ -66,6 +66,11 @@ const TopUp = () => {
...
@@ -66,6 +66,11 @@ const TopUp = () => {
const
[
enableStripeTopUp
,
setEnableStripeTopUp
]
=
useState
(
statusState
?.
status
?.
enable_stripe_topup
||
false
);
const
[
enableStripeTopUp
,
setEnableStripeTopUp
]
=
useState
(
statusState
?.
status
?.
enable_stripe_topup
||
false
);
const
[
stripeOpen
,
setStripeOpen
]
=
useState
(
false
);
const
[
stripeOpen
,
setStripeOpen
]
=
useState
(
false
);
const
[
creemProducts
,
setCreemProducts
]
=
useState
([]);
const
[
enableCreemTopUp
,
setEnableCreemTopUp
]
=
useState
(
false
);
const
[
creemOpen
,
setCreemOpen
]
=
useState
(
false
);
const
[
selectedCreemProduct
,
setSelectedCreemProduct
]
=
useState
(
null
);
const
[
userQuota
,
setUserQuota
]
=
useState
(
0
);
const
[
userQuota
,
setUserQuota
]
=
useState
(
0
);
const
[
isSubmitting
,
setIsSubmitting
]
=
useState
(
false
);
const
[
isSubmitting
,
setIsSubmitting
]
=
useState
(
false
);
const
[
open
,
setOpen
]
=
useState
(
false
);
const
[
open
,
setOpen
]
=
useState
(
false
);
...
@@ -296,6 +301,50 @@ const TopUp = () => {
...
@@ -296,6 +301,50 @@ const TopUp = () => {
window
.
open
(
data
.
pay_link
,
'_blank'
);
window
.
open
(
data
.
pay_link
,
'_blank'
);
};
};
const
creemPreTopUp
=
async
(
product
)
=>
{
if
(
!
enableCreemTopUp
)
{
showError
(
t
(
'管理员未开启 Creem 充值!'
));
return
;
}
setSelectedCreemProduct
(
product
);
setCreemOpen
(
true
);
};
const
onlineCreemTopUp
=
async
()
=>
{
if
(
!
selectedCreemProduct
)
{
showError
(
t
(
'请选择产品'
));
return
;
}
setConfirmLoading
(
true
);
try
{
const
res
=
await
API
.
post
(
'/api/user/creem/pay'
,
{
product_id
:
selectedCreemProduct
.
productId
,
payment_method
:
'creem'
,
});
if
(
res
!==
undefined
)
{
const
{
message
,
data
}
=
res
.
data
;
if
(
message
===
'success'
)
{
processCreemCallback
(
data
);
}
else
{
showError
(
data
);
}
}
else
{
showError
(
res
);
}
}
catch
(
err
)
{
console
.
log
(
err
);
showError
(
t
(
'支付请求失败'
));
}
finally
{
setCreemOpen
(
false
);
setConfirmLoading
(
false
);
}
};
const
processCreemCallback
=
(
data
)
=>
{
// 与 Stripe 保持一致的实现方式
window
.
open
(
data
.
checkout_url
,
'_blank'
);
};
const
getUserQuota
=
async
()
=>
{
const
getUserQuota
=
async
()
=>
{
setUserDataLoading
(
true
);
setUserDataLoading
(
true
);
let
res
=
await
API
.
get
(
`/api/user/self`
);
let
res
=
await
API
.
get
(
`/api/user/self`
);
...
@@ -396,6 +445,15 @@ const TopUp = () => {
...
@@ -396,6 +445,15 @@ const TopUp = () => {
setStripeMinTopUp
(
statusState
.
status
.
stripe_min_topup
||
1
);
setStripeMinTopUp
(
statusState
.
status
.
stripe_min_topup
||
1
);
setStripeTopUpCount
(
statusState
.
status
.
stripe_min_topup
||
1
);
setStripeTopUpCount
(
statusState
.
status
.
stripe_min_topup
||
1
);
setEnableStripeTopUp
(
statusState
.
status
.
enable_stripe_topup
||
false
);
setEnableStripeTopUp
(
statusState
.
status
.
enable_stripe_topup
||
false
);
// Creem settings
setEnableCreemTopUp
(
statusState
.
status
.
enable_creem_topup
||
false
);
try
{
const
products
=
JSON
.
parse
(
statusState
.
status
.
creem_products
||
'[]'
);
setCreemProducts
(
products
);
}
catch
(
e
)
{
setCreemProducts
([]);
}
}
}
},
[
statusState
?.
status
]);
},
[
statusState
?.
status
]);
...
@@ -470,6 +528,11 @@ const TopUp = () => {
...
@@ -470,6 +528,11 @@ const TopUp = () => {
setStripeOpen
(
false
);
setStripeOpen
(
false
);
};
};
const
handleCreemCancel
=
()
=>
{
setCreemOpen
(
false
);
setSelectedCreemProduct
(
null
);
};
const
handleTransferCancel
=
()
=>
{
const
handleTransferCancel
=
()
=>
{
setOpenTransfer
(
false
);
setOpenTransfer
(
false
);
};
};
...
@@ -623,6 +686,32 @@ const TopUp = () => {
...
@@ -623,6 +686,32 @@ const TopUp = () => {
<
p
>
{
t
(
'是否确认充值?'
)}
<
/p
>
<
p
>
{
t
(
'是否确认充值?'
)}
<
/p
>
<
/Modal
>
<
/Modal
>
<
Modal
title
=
{
t
(
'确定要充值吗'
)}
visible
=
{
creemOpen
}
onOk
=
{
onlineCreemTopUp
}
onCancel
=
{
handleCreemCancel
}
maskClosable
=
{
false
}
size
=
'small'
centered
confirmLoading
=
{
confirmLoading
}
>
{
selectedCreemProduct
&&
(
<>
<
p
>
{
t
(
'产品名称'
)}
:
{
selectedCreemProduct
.
name
}
<
/p
>
<
p
>
{
t
(
'价格'
)}
:
{
selectedCreemProduct
.
currency
===
'EUR'
?
'€'
:
'$'
}{
selectedCreemProduct
.
price
}
<
/p
>
<
p
>
{
t
(
'充值额度'
)}
:
{
selectedCreemProduct
.
quota
}
<
/p
>
<
p
>
{
t
(
'是否确认充值?'
)}
<
/p
>
<
/
>
)}
<
/Modal
>
<
div
className
=
'grid grid-cols-1 lg:grid-cols-12 gap-6'
>
<
div
className
=
'grid grid-cols-1 lg:grid-cols-12 gap-6'
>
{
/* 左侧充值区域 */
}
{
/* 左侧充值区域 */
}
<
div
className
=
'lg:col-span-7 space-y-6 w-full'
>
<
div
className
=
'lg:col-span-7 space-y-6 w-full'
>
...
@@ -925,7 +1014,7 @@ const TopUp = () => {
...
@@ -925,7 +1014,7 @@ const TopUp = () => {
<
/
>
<
/
>
)}
)}
{
!
enableOnlineTopUp
&&
!
enableStripeTopUp
&&
(
{
!
enableOnlineTopUp
&&
!
enableStripeTopUp
&&
!
enableCreemTopUp
&&
(
<
Banner
<
Banner
type
=
'warning'
type
=
'warning'
description
=
{
t
(
description
=
{
t
(
...
@@ -1016,6 +1105,150 @@ const TopUp = () => {
...
@@ -1016,6 +1105,150 @@ const TopUp = () => {
<
/div
>
<
/div
>
<
/div
>
<
/div
>
<
/div
>
<
/div
>
{
/* 移动端 Stripe 充值区域 */
}
<
div
className
=
'md:hidden space-y-4'
>
<
Divider
style
=
{{
margin
:
'24px 0'
}}
>
<
Text
className
=
'text-sm font-medium'
>
{
t
(
'Stripe 充值'
)}
<
/Text
>
<
/Divider
>
<
div
>
<
div
className
=
'flex justify-between mb-2'
>
<
Text
strong
>
{
t
(
'充值数量'
)}
<
/Text
>
{
amountLoading
?
(
<
Skeleton
.
Title
style
=
{{
width
:
'80px'
,
height
:
'16px'
}}
/
>
)
:
(
<
Text
type
=
'tertiary'
>
{
t
(
'实付金额:'
)
+
renderStripeAmount
()}
<
/Text
>
)}
<
/div
>
<
InputNumber
disabled
=
{
!
enableStripeTopUp
}
placeholder
=
{
t
(
'充值数量,最低 '
)
+
renderQuotaWithAmount
(
stripeMinTopUp
)
}
value
=
{
stripeTopUpCount
}
min
=
{
stripeMinTopUp
}
max
=
{
999999999
}
step
=
{
1
}
precision
=
{
0
}
onChange
=
{
async
(
value
)
=>
{
if
(
value
&&
value
>=
1
)
{
setStripeTopUpCount
(
value
);
setSelectedPreset
(
null
);
await
getStripeAmount
(
value
);
}
}}
onBlur
=
{(
e
)
=>
{
const
value
=
parseInt
(
e
.
target
.
value
);
if
(
!
value
||
value
<
1
)
{
setStripeTopUpCount
(
1
);
getStripeAmount
(
1
);
}
}}
className
=
'w-full'
formatter
=
{(
value
)
=>
(
value
?
`
${
value
}
`
:
''
)}
parser
=
{(
value
)
=>
value
?
parseInt
(
value
.
replace
(
/
[^\d]
/g
,
''
))
:
0
}
/
>
<
/div
>
<
div
>
<
Button
type
=
'primary'
onClick
=
{()
=>
stripePreTopUp
()}
disabled
=
{
!
enableStripeTopUp
}
loading
=
{
paymentLoading
&&
payWay
===
'stripe'
}
icon
=
{
<
CreditCard
size
=
{
16
}
/>
}
style
=
{{
height
:
'40px'
,
color
:
'#b161fe'
,
}}
className
=
'transition-all hover:shadow-md w-full'
>
<
span
className
=
'ml-1'
>
Stripe
<
/span
>
<
/Button
>
<
/div
>
<
/div
>
<
/
>
)}
{
enableCreemTopUp
&&
creemProducts
.
length
>
0
&&
(
<>
<
div
className
=
'hidden md:block space-y-4'
>
<
Divider
style
=
{{
margin
:
'24px 0'
}}
>
<
Text
className
=
'text-sm font-medium'
>
{
t
(
'Creem 充值'
)}
<
/Text
>
<
/Divider
>
<
div
>
<
Text
strong
className
=
'block mb-3'
>
{
t
(
'选择充值套餐'
)}
<
/Text
>
<
div
className
=
'grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-3'
>
{
creemProducts
.
map
((
product
,
index
)
=>
(
<
Card
key
=
{
index
}
onClick
=
{()
=>
creemPreTopUp
(
product
)}
className
=
'cursor-pointer !rounded-2xl transition-all hover:shadow-md border-gray-200 hover:border-gray-300'
bodyStyle
=
{{
textAlign
:
'center'
,
padding
:
'16px'
}}
>
<
div
className
=
'font-medium text-lg mb-2'
>
{
product
.
name
}
<
/div
>
<
div
className
=
'text-sm text-gray-600 mb-2'
>
{
t
(
'充值额度'
)}:
{
product
.
quota
}
<
/div
>
<
div
className
=
'text-lg font-semibold text-blue-600'
>
{
product
.
currency
===
'EUR'
?
'€'
:
'$'
}{
product
.
price
}
<
/div
>
<
/Card
>
))}
<
/div
>
<
/div
>
<
/div
>
{
/* 移动端 Creem 充值区域 */
}
<
div
className
=
'md:hidden space-y-4'
>
<
Divider
style
=
{{
margin
:
'24px 0'
}}
>
<
Text
className
=
'text-sm font-medium'
>
{
t
(
'Creem 充值'
)}
<
/Text
>
<
/Divider
>
<
div
>
<
Text
strong
className
=
'block mb-3'
>
{
t
(
'选择充值套餐'
)}
<
/Text
>
<
div
className
=
'grid grid-cols-1 sm:grid-cols-2 gap-3'
>
{
creemProducts
.
map
((
product
,
index
)
=>
(
<
Card
key
=
{
index
}
onClick
=
{()
=>
creemPreTopUp
(
product
)}
className
=
'cursor-pointer !rounded-2xl transition-all hover:shadow-md border-gray-200 hover:border-gray-300'
bodyStyle
=
{{
textAlign
:
'center'
,
padding
:
'16px'
}}
>
<
div
className
=
'font-medium text-lg mb-2'
>
{
product
.
name
}
<
/div
>
<
div
className
=
'text-sm text-gray-600 mb-2'
>
{
t
(
'充值额度'
)}:
{
product
.
quota
}
<
/div
>
<
div
className
=
'text-lg font-semibold text-blue-600'
>
{
product
.
currency
===
'EUR'
?
'€'
:
'$'
}{
product
.
price
}
<
/div
>
<
/Card
>
))}
<
/div
>
<
/div
>
<
/div
>
<
/
>
<
/
>
)}
)}
...
@@ -1185,7 +1418,12 @@ const TopUp = () => {
...
@@ -1185,7 +1418,12 @@ const TopUp = () => {
<
/div
>
<
/div
>
<
/div
>
<
/div
>
{
/* 移动端底部固定的自定义金额和支付区域 */
}
{
/* 移动端底部间距,避免内容被固定区域遮挡 */
}
{
enableOnlineTopUp
&&
(
<
div
className
=
'md:hidden h-32'
><
/div
>
)}
{
/* 移动端底部固定的自定义金额和支付区域 - 仅限在线充值 */
}
{
enableOnlineTopUp
&&
(
{
enableOnlineTopUp
&&
(
<
div
<
div
className
=
'md:hidden fixed bottom-0 left-0 right-0 p-4 shadow-lg z-50'
className
=
'md:hidden fixed bottom-0 left-0 right-0 p-4 shadow-lg z-50'
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment