feat(game): 更新游戏规则并优化充值提现界面

- 在 api.type.ts 中为支付渠道、存款配置和取款字段添加新的接口属性
- 替换桌面端规则模态框中的纯文本内容为支持 Markdown 格式的组件
- 重构桌面端充值组件的 UI 结构,使用自定义按钮替代原生选择器
- 为取款组件添加最低最高金额验证及支付方式选择功能
- 更新英文本地化文件中的游戏规则内容,提供详细的玩法指南
This commit is contained in:
JiaJun
2026-07-09 16:43:52 +08:00
parent 1535cb243b
commit 69cd9a7521
18 changed files with 3071 additions and 1056 deletions

View File

@@ -1,7 +1,6 @@
import { useCallback, useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import {
DEFAULT_CURRENCY_CODE,
DEFAULT_WITHDRAW_CONFIG,
QUICK_FIAT_AMOUNTS,
WITHDRAW_EMAIL_PATTERN,
@@ -41,15 +40,34 @@ function getCurrencyExchangeRate(
return configuredRate && configuredRate > 0 ? configuredRate : fallbackRate
}
function getActiveCurrencyCode(
currencies: DepositWithdrawConfig['currencies'],
selectedCurrencyCode: string,
function toOptionalNumber(value: string) {
if (value.trim().length === 0) {
return null
}
const numericValue = Number(value)
return Number.isFinite(numericValue) ? numericValue : null
}
function getChannelCurrencies(
config: DepositWithdrawConfig,
channel: DepositWithdrawConfig['withdraw']['payChannels'][number] | null,
) {
return (
currencies.find((item) => item.code === selectedCurrencyCode) ??
currencies[0] ??
DEFAULT_WITHDRAW_CONFIG.currencies[0]
if (!channel || channel.currencyCodes.length === 0) {
return []
}
const currencyMap = new Map(
config.currencies.map((currency) => [currency.code, currency]),
)
return channel.currencyCodes
.map((currencyCode) => currencyMap.get(currencyCode))
.filter(
(currency): currency is DepositWithdrawConfig['currencies'][number] =>
Boolean(currency),
)
}
function getNormalizedConfig(
@@ -105,53 +123,73 @@ export function useWithdrawVm() {
const [amount, setAmountState] = useState(0)
const [hasInitializedAmount, setHasInitializedAmount] = useState(false)
const [currencyCode, setCurrencyCode] = useState(
config.currencies[0]?.code ?? DEFAULT_CURRENCY_CODE,
)
const [currencyCode, setCurrencyCode] = useState('')
const [paymentChannelCode, setPaymentChannelCode] = useState('')
const [paymentType, setPaymentType] = useState('')
const [bankCode, setBankCode] = useState('')
const [holderName, setHolderName] = useState('')
const [bankAccount, setBankAccount] = useState('')
const [receiverEmail, setReceiverEmail] = useState('')
const [receiverPhone, setReceiverPhone] = useState('')
const selectedCurrency = getActiveCurrencyCode(
config.currencies,
currencyCode,
)
const selectedRate = selectedCurrency.withdrawCoinsPerFiatValue || 1
const selectedExchangeRate = getCurrencyExchangeRate(
config,
selectedCurrency.code,
selectedRate,
)
const sortedPayChannels = useMemo(
() =>
[
...(config.withdraw.payChannels.length > 0
? config.withdraw.payChannels
: config.payChannels),
]
[...config.withdraw.payChannels]
.filter((channel) => channel.status === 1)
.sort((left, right) => left.sort - right.sort),
[config.payChannels, config.withdraw.payChannels],
)
const sortedBanks = useMemo(
() =>
[...config.withdraw.banks]
.filter(
(bank) =>
bank.status === 1 &&
(!bank.currencyCode || bank.currencyCode === selectedCurrency.code),
)
.sort((left, right) => left.sort - right.sort),
[config.withdraw.banks, selectedCurrency.code],
[config.withdraw.payChannels],
)
const availableBalance = Number(currentUser?.coin ?? 0)
const maxWithdrawAmount = Math.max(0, Math.floor(availableBalance))
const selectedPaymentChannel =
sortedPayChannels.find((channel) => channel.code === paymentChannelCode) ??
null
const availablePaymentMethods = useMemo(
() => selectedPaymentChannel?.methods ?? [],
[selectedPaymentChannel],
)
const selectedPaymentMethod =
availablePaymentMethods.find((method) => method.code === paymentType) ??
null
const availableCurrencies = useMemo(
() => getChannelCurrencies(config, selectedPaymentChannel),
[config, selectedPaymentChannel],
)
const selectedCurrency =
availableCurrencies.find((item) => item.code === currencyCode) ?? null
const selectedCurrencyCode = selectedCurrency?.code ?? ''
const selectedRate = selectedCurrency?.withdrawCoinsPerFiatValue ?? 0
const selectedExchangeRate = selectedCurrency
? getCurrencyExchangeRate(config, selectedCurrency.code, selectedRate || 1)
: 0
const convertedFiatAmount =
selectedExchangeRate > 0 ? amount * selectedExchangeRate : 0
const selectedPaymentMinimumAmount = selectedPaymentMethod
? toOptionalNumber(selectedPaymentMethod.minimumAmount)
: null
const selectedPaymentMaximumAmount = selectedPaymentMethod
? toOptionalNumber(selectedPaymentMethod.maximumAmount)
: null
const amountBelowPaymentMinimum =
amount > 0 &&
selectedPaymentMinimumAmount !== null &&
convertedFiatAmount < selectedPaymentMinimumAmount
const amountAbovePaymentMaximum =
amount > 0 &&
selectedPaymentMaximumAmount !== null &&
convertedFiatAmount > selectedPaymentMaximumAmount
const sortedBanks = useMemo(
() =>
[...config.withdraw.banks]
.filter(
(bank) =>
bank.status === 1 &&
selectedCurrencyCode.length > 0 &&
(!bank.currencyCode || bank.currencyCode === selectedCurrencyCode),
)
.sort((left, right) => left.sort - right.sort),
[config.withdraw.banks, selectedCurrencyCode],
)
const setAmount = useCallback(
(nextAmount: number) => {
setAmountState(
@@ -161,20 +199,6 @@ export function useWithdrawVm() {
[maxWithdrawAmount],
)
useEffect(() => {
if (
selectedCurrency &&
selectedCurrency.code !== currencyCode &&
config.currencies.some((item) => item.code === currencyCode)
) {
return
}
if (selectedCurrency && selectedCurrency.code !== currencyCode) {
setCurrencyCode(selectedCurrency.code)
}
}, [config.currencies, currencyCode, selectedCurrency])
useEffect(() => {
const firstAvailablePayChannel = sortedPayChannels[0]
@@ -195,6 +219,42 @@ export function useWithdrawVm() {
}
}, [paymentChannelCode, sortedPayChannels])
useEffect(() => {
if (availablePaymentMethods.length === 0) {
if (paymentType) {
setPaymentType('')
}
return
}
const hasSelectedAvailablePaymentMethod = availablePaymentMethods.some(
(method) => method.code === paymentType,
)
if (!hasSelectedAvailablePaymentMethod) {
setPaymentType(availablePaymentMethods[0].code)
}
}, [availablePaymentMethods, paymentType])
useEffect(() => {
if (availableCurrencies.length === 0) {
if (currencyCode) {
setCurrencyCode('')
}
return
}
const hasSelectedAvailableCurrency = availableCurrencies.some(
(currency) => currency.code === currencyCode,
)
if (!hasSelectedAvailableCurrency) {
setCurrencyCode(availableCurrencies[0].code)
}
}, [availableCurrencies, currencyCode])
useEffect(() => {
if (sortedBanks.length === 0) {
if (bankCode) {
@@ -229,6 +289,10 @@ export function useWithdrawVm() {
}, [amount, maxWithdrawAmount, setAmount])
const quickAmounts = useMemo(() => {
if (!selectedCurrency || selectedExchangeRate <= 0) {
return []
}
return QUICK_FIAT_AMOUNTS.map((fiatAmount) => ({
diamonds: Math.min(
maxWithdrawAmount,
@@ -237,26 +301,33 @@ export function useWithdrawVm() {
id: `quick-${selectedCurrency.code}-${fiatAmount}`,
preview: `${selectedCurrency.code} ${formatNumber(locale, fiatAmount)}`,
}))
}, [locale, maxWithdrawAmount, selectedCurrency.code, selectedExchangeRate])
}, [locale, maxWithdrawAmount, selectedCurrency, selectedExchangeRate])
const resetForm = useCallback(() => {
const nextCurrencyCode = config.currencies[0]?.code ?? DEFAULT_CURRENCY_CODE
const nextCurrency = getActiveCurrencyCode(
config.currencies,
nextCurrencyCode,
)
const nextExchangeRate = getCurrencyExchangeRate(
config,
nextCurrency.code,
nextCurrency.withdrawCoinsPerFiatValue || 1,
)
const nextPaymentChannelCode = sortedPayChannels[0]?.code ?? ''
const nextPaymentChannel =
sortedPayChannels.find(
(channel) => channel.code === nextPaymentChannelCode,
) ?? null
const nextCurrency =
getChannelCurrencies(config, nextPaymentChannel)[0] ?? null
const nextExchangeRate = nextCurrency
? getCurrencyExchangeRate(
config,
nextCurrency.code,
nextCurrency.withdrawCoinsPerFiatValue || 1,
)
: 0
setAmountState(
getInitialWithdrawAmount(nextExchangeRate, maxWithdrawAmount),
nextExchangeRate > 0
? getInitialWithdrawAmount(nextExchangeRate, maxWithdrawAmount)
: 0,
)
setHasInitializedAmount(true)
setCurrencyCode(nextCurrencyCode)
setPaymentChannelCode(sortedPayChannels[0]?.code ?? '')
setCurrencyCode(nextCurrency?.code ?? '')
setPaymentChannelCode(nextPaymentChannelCode)
setPaymentType(nextPaymentChannel?.methods[0]?.code ?? '')
setBankCode('')
setHolderName('')
setBankAccount('')
@@ -264,37 +335,41 @@ export function useWithdrawVm() {
setReceiverPhone('')
}, [config, maxWithdrawAmount, sortedPayChannels])
const selectedCurrencyPreview = useMemo(
() => ({
currencyCode: selectedCurrency.code,
currencyLabel: selectedCurrency.label,
const selectedCurrencyPreview = useMemo(() => {
const previewCurrencyCode = selectedCurrency?.code ?? '--'
return {
currencyCode: selectedCurrency?.code ?? '',
currencyLabel: selectedCurrency?.label ?? '',
exchangeRateLabel: t('gameDesktop.withdraw.preview.exchangeRate', {
currency: selectedCurrency.code,
currency: previewCurrencyCode,
}),
exchangeRateValue: `1:${formatNumber(locale, selectedExchangeRate)}`,
exchangeRateValue:
selectedCurrency && selectedExchangeRate > 0
? `1:${formatNumber(locale, selectedExchangeRate)}`
: '--',
convertibleLabel: t('gameDesktop.withdraw.preview.convertible', {
currency: selectedCurrency.code,
currency: previewCurrencyCode,
}),
convertibleValue: `${formatNumber(
locale,
selectedExchangeRate > 0 ? amount * selectedExchangeRate : 0,
)} ${selectedCurrency.code}`,
}),
[
amount,
locale,
selectedCurrency.code,
selectedCurrency.label,
selectedExchangeRate,
t,
],
)
convertibleValue:
selectedCurrency && selectedExchangeRate > 0
? `${formatNumber(
locale,
amount * selectedExchangeRate,
)} ${selectedCurrency.code}`
: '--',
}
}, [amount, locale, selectedCurrency, selectedExchangeRate, t])
return {
amount,
amountAbovePaymentMaximum,
amountBelowPaymentMinimum,
amountExceedsBalance: amount > maxWithdrawAmount,
amountRequiredError: amount <= 0,
availableBalance,
availableCurrencies,
availablePaymentMethods,
bankAccount,
bankAccountError: bankAccount.trim().length === 0,
bankCode,
@@ -308,6 +383,20 @@ export function useWithdrawVm() {
maxWithdrawAmount,
paymentChannelCode,
paymentChannelCodeError: paymentChannelCode.trim().length === 0,
paymentMaximumAmountLabel:
selectedPaymentMaximumAmount !== null && selectedCurrency
? `${formatNumber(locale, selectedPaymentMaximumAmount)} ${
selectedCurrency.code
}`
: '',
paymentMinimumAmountLabel:
selectedPaymentMinimumAmount !== null && selectedCurrency
? `${formatNumber(locale, selectedPaymentMinimumAmount)} ${
selectedCurrency.code
}`
: '',
paymentType,
paymentTypeError: paymentType.trim().length === 0,
quickAmounts,
receiverEmail,
receiverEmailError: !isValidEmail(receiverEmail),
@@ -316,6 +405,7 @@ export function useWithdrawVm() {
selectedCurrency,
selectedCurrencyPreview,
selectedPaymentChannel,
selectedPaymentMethod,
selectedRate,
resetForm,
setAmount,
@@ -324,6 +414,7 @@ export function useWithdrawVm() {
setCurrencyCode,
setHolderName,
setPaymentChannelCode,
setPaymentType,
setReceiverEmail,
setReceiverPhone,
sortedBanks,