新增:提现弹窗支持比例手续费展示与合计,固定与比例可同时生效。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
马丁 2026-08-06 20:28:28 +08:00
parent 19e4322abd
commit 596f015873
7 changed files with 54 additions and 13 deletions

View File

@ -177,8 +177,10 @@ export function amountToUsdcDisplay(raw: number | undefined): string {
/** 提现配置(GET /pmset/getWithdrawConfig) */ /** 提现配置(GET /pmset/getWithdrawConfig) */
export interface WithdrawConfig { export interface WithdrawConfig {
/** 手续费,最小精度整数(1U=1e6;系统参数配置口径为 1=1U) */ /** 固定手续费,最小精度整数(1U=1e6;系统参数配置口径为 1=1U) */
fee: number fee: number
/** 比例手续费百分比(1=1%,可与固定手续费同时生效) */
feePercent: number
/** 最低提现金额,最小精度整数(1U=1e6;系统参数配置口径为 1=1U) */ /** 最低提现金额,最小精度整数(1U=1e6;系统参数配置口径为 1=1U) */
minWithdrawAmount: number minWithdrawAmount: number
} }
@ -191,7 +193,7 @@ export interface WithdrawConfigResponse extends ApiResponse<WithdrawConfig> {
/** /**
* GET /pmset/getWithdrawConfig * GET /pmset/getWithdrawConfig
* 获取提现手续费与最低提现金额(系统参数 withdrawFee / minWithdrawAmount,配置 1=1U;缺失自动插入) * 获取提现固定/比例手续费与最低提现金额(withdrawFee / withdrawFeePercent / minWithdrawAmount;缺失自动插入)
*/ */
export async function getWithdrawConfig( export async function getWithdrawConfig(
config?: { headers?: Record<string, string> }, config?: { headers?: Record<string, string> },
@ -199,6 +201,14 @@ export async function getWithdrawConfig(
return get<WithdrawConfigResponse>('/pmset/getWithdrawConfig', undefined, config) return get<WithdrawConfigResponse>('/pmset/getWithdrawConfig', undefined, config)
} }
/** 计算提现总手续费(展示金额):固定 + 金额×百分比 */
export function calcWithdrawFeeDisplay(amount: number, fixedFee: number, feePercent: number): number {
const fixed = Number.isFinite(fixedFee) && fixedFee > 0 ? fixedFee : 0
const percent = Number.isFinite(feePercent) && feePercent > 0 ? feePercent : 0
const amt = Number.isFinite(amount) && amount > 0 ? amount : 0
return Math.round((fixed + (amt * percent) / 100) * 1e6) / 1e6
}
/** 充值配置(GET /pmset/getDepositConfig) */ /** 充值配置(GET /pmset/getDepositConfig) */
export interface DepositConfig { export interface DepositConfig {
/** 最小到账金额,最小精度整数(1U=1e6;系统参数配置口径 1=1U,默认 10) */ /** 最小到账金额,最小精度整数(1U=1e6;系统参数配置口径 1=1U,默认 10) */

View File

@ -40,8 +40,16 @@
<span>${{ minAmountDisplay }}</span> <span>${{ minAmountDisplay }}</span>
</div> </div>
<div class="withdraw-meta-row"> <div class="withdraw-meta-row">
<span>{{ t('withdraw.feeFixed') }}</span>
<span>${{ feeFixedDisplay }}</span>
</div>
<div class="withdraw-meta-row">
<span>{{ t('withdraw.feePercent') }}</span>
<span>{{ feePercentDisplay }}%</span>
</div>
<div v-if="amountNum > 0" class="withdraw-meta-row">
<span>{{ t('withdraw.fee') }}</span> <span>{{ t('withdraw.fee') }}</span>
<span>${{ feeDisplay }}</span> <span>${{ totalFeeDisplay }}</span>
</div> </div>
<div v-if="amountNum > 0" class="withdraw-meta-row withdraw-meta-row--receive"> <div v-if="amountNum > 0" class="withdraw-meta-row withdraw-meta-row--receive">
<span>{{ t('withdraw.receiveAmount') }}</span> <span>{{ t('withdraw.receiveAmount') }}</span>
@ -106,6 +114,7 @@ import {
usdcToAmount, usdcToAmount,
getWithdrawConfig, getWithdrawConfig,
amountToUsdcDisplay, amountToUsdcDisplay,
calcWithdrawFeeDisplay,
} from '@/api/pmset' } from '@/api/pmset'
import { WALLET_PRIMARY_NETWORK_ID, useWalletNetworkGuard } from '@/composables/useWalletNetworkPolicy' import { WALLET_PRIMARY_NETWORK_ID, useWalletNetworkGuard } from '@/composables/useWalletNetworkPolicy'
@ -128,6 +137,7 @@ const connecting = ref(false)
const submitting = ref(false) const submitting = ref(false)
const errorMessage = ref('') const errorMessage = ref('')
const feeRaw = ref(0) const feeRaw = ref(0)
const feePercent = ref(0)
const minWithdrawRaw = ref(0) const minWithdrawRaw = ref(0)
const configLoading = ref(false) const configLoading = ref(false)
@ -151,12 +161,21 @@ const { onWalletNetworkChange } = useWalletNetworkGuard(
const balanceNum = computed(() => parseFloat(props.balance) || 0) const balanceNum = computed(() => parseFloat(props.balance) || 0)
const amountNum = computed(() => parseFloat(amount.value) || 0) const amountNum = computed(() => parseFloat(amount.value) || 0)
const feeDisplayNum = computed(() => feeRaw.value / 1_000_000) const feeFixedDisplayNum = computed(() => feeRaw.value / 1_000_000)
const minAmountDisplayNum = computed(() => minWithdrawRaw.value / 1_000_000) const minAmountDisplayNum = computed(() => minWithdrawRaw.value / 1_000_000)
const feeDisplay = computed(() => amountToUsdcDisplay(feeRaw.value)) const feeFixedDisplay = computed(() => amountToUsdcDisplay(feeRaw.value))
const feePercentDisplay = computed(() => {
const p = feePercent.value
if (!Number.isFinite(p) || p <= 0) return '0'
return Number.isInteger(p) ? String(p) : p.toFixed(4).replace(/\.?0+$/, '')
})
const minAmountDisplay = computed(() => amountToUsdcDisplay(minWithdrawRaw.value)) const minAmountDisplay = computed(() => amountToUsdcDisplay(minWithdrawRaw.value))
const totalFeeDisplayNum = computed(() =>
calcWithdrawFeeDisplay(amountNum.value, feeFixedDisplayNum.value, feePercent.value),
)
const totalFeeDisplay = computed(() => totalFeeDisplayNum.value.toFixed(2))
const receiveAmountDisplay = computed(() => { const receiveAmountDisplay = computed(() => {
const receive = Math.max(0, amountNum.value - feeDisplayNum.value) const receive = Math.max(0, amountNum.value - totalFeeDisplayNum.value)
return receive.toFixed(2) return receive.toFixed(2)
}) })
@ -167,8 +186,8 @@ const amountError = computed(() => {
if (minAmountDisplayNum.value > 0 && amountNum.value < minAmountDisplayNum.value) { if (minAmountDisplayNum.value > 0 && amountNum.value < minAmountDisplayNum.value) {
return t('withdraw.belowMinAmount', { min: minAmountDisplay.value }) return t('withdraw.belowMinAmount', { min: minAmountDisplay.value })
} }
if (feeDisplayNum.value > 0 && amountNum.value <= feeDisplayNum.value) { if (totalFeeDisplayNum.value > 0 && amountNum.value <= totalFeeDisplayNum.value) {
return t('withdraw.amountMustExceedFee', { fee: feeDisplay.value }) return t('withdraw.amountMustExceedFee', { fee: totalFeeDisplay.value })
} }
return '' return ''
}) })
@ -219,11 +238,13 @@ async function loadWithdrawConfig() {
const res = await getWithdrawConfig() const res = await getWithdrawConfig()
if (res.code === 0 || res.code === 200) { if (res.code === 0 || res.code === 200) {
feeRaw.value = Number(res.data?.fee ?? 0) || 0 feeRaw.value = Number(res.data?.fee ?? 0) || 0
feePercent.value = Number(res.data?.feePercent ?? 0) || 0
minWithdrawRaw.value = Number(res.data?.minWithdrawAmount ?? 0) || 0 minWithdrawRaw.value = Number(res.data?.minWithdrawAmount ?? 0) || 0
} }
} catch (e) { } catch (e) {
console.error('[loadWithdrawConfig]', e) console.error('[loadWithdrawConfig]', e)
feeRaw.value = 0 feeRaw.value = 0
feePercent.value = 0
minWithdrawRaw.value = 0 minWithdrawRaw.value = 0
} finally { } finally {
configLoading.value = false configLoading.value = false

View File

@ -522,7 +522,9 @@
"loginWalletLabel": "Your login wallet", "loginWalletLabel": "Your login wallet",
"connectingWallet": "Connecting wallet…", "connectingWallet": "Connecting wallet…",
"walletMismatch": "Switch your browser wallet to the same account you used to sign in.", "walletMismatch": "Switch your browser wallet to the same account you used to sign in.",
"fee": "Fee", "fee": "Total fee",
"feeFixed": "Fixed fee",
"feePercent": "Fee rate",
"minAmount": "Minimum withdrawal", "minAmount": "Minimum withdrawal",
"receiveAmount": "You will receive", "receiveAmount": "You will receive",
"belowMinAmount": "Amount must be at least {min} USDC", "belowMinAmount": "Amount must be at least {min} USDC",

View File

@ -522,7 +522,9 @@
"loginWalletLabel": "ログイン中のウォレット", "loginWalletLabel": "ログイン中のウォレット",
"connectingWallet": "ウォレットに接続しています…", "connectingWallet": "ウォレットに接続しています…",
"walletMismatch": "ログイン時に使用したアカウントにブラウザのウォレットを切り替えてください。", "walletMismatch": "ログイン時に使用したアカウントにブラウザのウォレットを切り替えてください。",
"fee": "手数料", "fee": "手数料合計",
"feeFixed": "固定手数料",
"feePercent": "手数料率",
"minAmount": "最低出金額", "minAmount": "最低出金額",
"receiveAmount": "受取金額", "receiveAmount": "受取金額",
"belowMinAmount": "出金額は {min} USDC 以上である必要があります", "belowMinAmount": "出金額は {min} USDC 以上である必要があります",

View File

@ -522,7 +522,9 @@
"loginWalletLabel": "로그인 지갑", "loginWalletLabel": "로그인 지갑",
"connectingWallet": "지갑 연결 중…", "connectingWallet": "지갑 연결 중…",
"walletMismatch": "로그인에 사용한 계정과 동일한 계정으로 브라우저 지갑을 전환해 주세요.", "walletMismatch": "로그인에 사용한 계정과 동일한 계정으로 브라우저 지갑을 전환해 주세요.",
"fee": "수수료", "fee": "총 수수료",
"feeFixed": "고정 수수료",
"feePercent": "수수료율",
"minAmount": "최소 출금 금액", "minAmount": "최소 출금 금액",
"receiveAmount": "실수령액", "receiveAmount": "실수령액",
"belowMinAmount": "출금 금액은 최소 {min} USDC 이상이어야 합니다", "belowMinAmount": "출금 금액은 최소 {min} USDC 이상이어야 합니다",

View File

@ -535,7 +535,9 @@
"loginWalletLabel": "登录钱包(提现地址)", "loginWalletLabel": "登录钱包(提现地址)",
"connectingWallet": "正在连接钱包…", "connectingWallet": "正在连接钱包…",
"walletMismatch": "请在浏览器钱包中切换到您登录时使用的账户。", "walletMismatch": "请在浏览器钱包中切换到您登录时使用的账户。",
"fee": "手续费", "fee": "手续费合计",
"feeFixed": "固定手续费",
"feePercent": "比例手续费",
"minAmount": "最低提现金额", "minAmount": "最低提现金额",
"receiveAmount": "实际到账", "receiveAmount": "实际到账",
"belowMinAmount": "提现金额不能低于 {min} USDC", "belowMinAmount": "提现金额不能低于 {min} USDC",

View File

@ -522,7 +522,9 @@
"loginWalletLabel": "登入錢包(提現地址)", "loginWalletLabel": "登入錢包(提現地址)",
"connectingWallet": "正在連接錢包…", "connectingWallet": "正在連接錢包…",
"walletMismatch": "請在瀏覽器錢包中切換至您登入時使用的帳戶。", "walletMismatch": "請在瀏覽器錢包中切換至您登入時使用的帳戶。",
"fee": "手續費", "fee": "手續費合計",
"feeFixed": "固定手續費",
"feePercent": "比例手續費",
"minAmount": "最低提現金額", "minAmount": "最低提現金額",
"receiveAmount": "實際到帳", "receiveAmount": "實際到帳",
"belowMinAmount": "提現金額不能低於 {min} USDC", "belowMinAmount": "提現金額不能低於 {min} USDC",