|
|
@@ -36,6 +36,9 @@ export interface WalletAddress {
|
|
|
export interface SupportCoinInfo {
|
|
|
unit: string
|
|
|
aliasName?: string
|
|
|
+ nameCn?: string
|
|
|
+ name?: string
|
|
|
+ balance?: string | number
|
|
|
minWithdrawAmount: string
|
|
|
maxWithdrawAmount: string
|
|
|
minTxFee: string
|
|
|
@@ -46,6 +49,39 @@ export interface SupportCoinInfo {
|
|
|
withdrawFeeValue?: string
|
|
|
}
|
|
|
|
|
|
+/** coin 表顶级主币(parent_coin_name 为空且 can_recharge=1,内部转账用) */
|
|
|
+export interface TransferParentCoin {
|
|
|
+ name: string
|
|
|
+ nameCn: string
|
|
|
+ unit: string
|
|
|
+ sort: number
|
|
|
+ minWithdrawAmount?: string | number
|
|
|
+ withdrawFeeValue?: string | number
|
|
|
+}
|
|
|
+
|
|
|
+/** coin_network 表一行(提币网络列表) */
|
|
|
+export interface WithdrawNetworkItem {
|
|
|
+ id: number
|
|
|
+ name: string
|
|
|
+ protocol: string
|
|
|
+ sort: number
|
|
|
+}
|
|
|
+
|
|
|
+/** 可提/可转币种 */
|
|
|
+export interface WithdrawCoinOption {
|
|
|
+ unit: string
|
|
|
+ displayName: string
|
|
|
+ networkLabel: string
|
|
|
+ networkId?: number
|
|
|
+ balance: string
|
|
|
+ minWithdraw: string
|
|
|
+ minTransfer: string
|
|
|
+ withdrawFee: string
|
|
|
+ isUsdtFamily: boolean
|
|
|
+ /** 主币(内部转账仅主币) */
|
|
|
+ isMainCoin: boolean
|
|
|
+}
|
|
|
+
|
|
|
export interface WithdrawRecordCoin {
|
|
|
coinName: string
|
|
|
aliasName: string
|
|
|
@@ -206,6 +242,88 @@ function walletUnitToNetworkLabel(unit: string | undefined): string {
|
|
|
/** 网络 Tab 排序:与 App 一致 */
|
|
|
const NETWORK_TAB_ORDER: Record<string, number> = { TRC20: 0, ERC20: 1, BEP20: 2 }
|
|
|
|
|
|
+function isUsdtFamilyUnit(unit: string): boolean {
|
|
|
+ const u = unit.toUpperCase()
|
|
|
+ return u === 'USDT' || u === 'EUSDT' || u === 'TUSDT' || u.includes('BEP20')
|
|
|
+}
|
|
|
+
|
|
|
+function mapTransferParent(
|
|
|
+ row: Record<string, unknown>,
|
|
|
+ supportMap: Map<string, SupportCoinInfo>,
|
|
|
+): WithdrawCoinOption {
|
|
|
+ const unit = String(row.unit ?? '').trim().toLowerCase()
|
|
|
+ const name = String(row.name ?? '')
|
|
|
+ const support = supportMap.get(unit) ?? supportMap.get(name.toLowerCase())
|
|
|
+ const minFromCoin = firstNonEmptyStr(row.minWithdrawAmount)
|
|
|
+ const minFromSupport = firstNonEmptyStr(support?.minAmount, support?.minWithdrawAmount, support?.minTransferAmount)
|
|
|
+ return {
|
|
|
+ unit,
|
|
|
+ displayName: firstNonEmptyStr(row.nameCn, support?.nameCn, support?.aliasName, name) || unit.toUpperCase(),
|
|
|
+ networkLabel: '',
|
|
|
+ balance: firstNonEmptyStr(support?.balance, '0') || '0',
|
|
|
+ minWithdraw: minFromSupport || minFromCoin || '0',
|
|
|
+ minTransfer: firstNonEmptyStr(support?.minTransferAmount, support?.minAmount, minFromSupport, minFromCoin) || '0',
|
|
|
+ withdrawFee: firstNonEmptyStr(support?.withdrawFeeValue, support?.fee, row.withdrawFeeValue) || '0',
|
|
|
+ isUsdtFamily: isUsdtFamilyUnit(unit),
|
|
|
+ isMainCoin: true,
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function mapNetworkItem(row: Record<string, unknown>): WithdrawNetworkItem {
|
|
|
+ return {
|
|
|
+ id: Number(row.id ?? 0),
|
|
|
+ name: String(row.name ?? ''),
|
|
|
+ protocol: String(row.protocol ?? ''),
|
|
|
+ sort: Number(row.sort ?? 0),
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function supportInfoByUnit(list: SupportCoinInfo[]): Map<string, SupportCoinInfo> {
|
|
|
+ const map = new Map<string, SupportCoinInfo>()
|
|
|
+ for (const c of list) {
|
|
|
+ const u = String(c.unit ?? '').toLowerCase()
|
|
|
+ if (u) {
|
|
|
+ map.set(u, c)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return map
|
|
|
+}
|
|
|
+
|
|
|
+function mapWithdrawConfigFromDb(
|
|
|
+ row: Record<string, unknown>,
|
|
|
+ supportMap: Map<string, SupportCoinInfo>,
|
|
|
+): WithdrawCoinOption {
|
|
|
+ const unit = String(row.unit ?? '').trim().toLowerCase()
|
|
|
+ const coinName = String(row.coinName ?? '')
|
|
|
+ const support = supportMap.get(unit) ?? supportMap.get(coinName.toLowerCase())
|
|
|
+ const networkName = String(row.networkName ?? '')
|
|
|
+ const minFromDb = firstNonEmptyStr(row.minWithdrawAmount, row.minAmount)
|
|
|
+ const minFromSupport = firstNonEmptyStr(support?.minAmount, support?.minWithdrawAmount)
|
|
|
+ return {
|
|
|
+ unit,
|
|
|
+ displayName: firstNonEmptyStr(row.coinNameCn, support?.nameCn, support?.aliasName, coinName) || unit.toUpperCase(),
|
|
|
+ networkLabel: networkName,
|
|
|
+ networkId: Number(row.networkId ?? 0) || undefined,
|
|
|
+ balance: firstNonEmptyStr(support?.balance, '0') || '0',
|
|
|
+ minWithdraw: minFromSupport || minFromDb || '0',
|
|
|
+ minTransfer: firstNonEmptyStr(support?.minTransferAmount, support?.minAmount, minFromSupport) || '0',
|
|
|
+ withdrawFee: firstNonEmptyStr(support?.withdrawFeeValue, support?.fee, row.withdrawFeeValue) || '0',
|
|
|
+ isUsdtFamily: isUsdtFamilyUnit(unit),
|
|
|
+ isMainCoin: false,
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function sortWithdrawCoins(list: WithdrawCoinOption[]): WithdrawCoinOption[] {
|
|
|
+ return [...list].sort((a, b) => {
|
|
|
+ const oa = NETWORK_TAB_ORDER[a.networkLabel] ?? 99
|
|
|
+ const ob = NETWORK_TAB_ORDER[b.networkLabel] ?? 99
|
|
|
+ if (oa !== ob) {
|
|
|
+ return oa - ob
|
|
|
+ }
|
|
|
+ return a.displayName.localeCompare(b.displayName)
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
/**
|
|
|
* 各链默认手续费 / 最小提币(仅当钱包与支持币种接口均未返回有效值时使用,与 App 展示对齐)
|
|
|
*/
|
|
|
@@ -220,6 +338,15 @@ export const useWithdrawStore = defineStore('withdraw', () => {
|
|
|
const security = ref<AccountAuth | null>(null)
|
|
|
const coinInfoList = ref<SupportCoinInfo[]>([])
|
|
|
const walletAddresses = ref<WalletAddress[]>([])
|
|
|
+ /** 链上:coin_network 表 */
|
|
|
+ const networkList = ref<WithdrawNetworkItem[]>([])
|
|
|
+ const selectedNetworkId = ref(0)
|
|
|
+ const onchainSubCoins = ref<WithdrawCoinOption[]>([])
|
|
|
+ const onchainSubCoinsLoading = ref(false)
|
|
|
+ const selectedSubCoinIndex = ref(0)
|
|
|
+ /** 内部转账:仅主币 */
|
|
|
+ const transferMainCoins = ref<WithdrawCoinOption[]>([])
|
|
|
+ const selectedTransferCoinIndex = ref(0)
|
|
|
const initLoading = ref(false)
|
|
|
const initError = ref<string | null>(null)
|
|
|
|
|
|
@@ -257,16 +384,25 @@ export const useWithdrawStore = defineStore('withdraw', () => {
|
|
|
initLoading.value = true
|
|
|
initError.value = null
|
|
|
try {
|
|
|
- const [balRes, secRes, coinRes, addrRes] = await Promise.all([
|
|
|
+ const [balRes, secRes, coinRes, netRes, parentRes] = await Promise.all([
|
|
|
post<WithdrawBalance>('/uc/asset/wallet/usdt', {}),
|
|
|
postForm<AccountAuth>('/uc/approve/security/setting', {}),
|
|
|
post<SupportCoinInfo[]>('/uc/withdraw/support/coin/info', {}),
|
|
|
- post<WalletAddress[]>('/uc/asset/wallet/address', {}),
|
|
|
+ get<Record<string, unknown>[]>('/uc/coin-network/withdraw-networks'),
|
|
|
+ get<Record<string, unknown>[]>('/uc/coin-network/transfer-parents'),
|
|
|
])
|
|
|
walletBalance.value = balRes
|
|
|
security.value = secRes
|
|
|
coinInfoList.value = Array.isArray(coinRes) ? coinRes : []
|
|
|
- walletAddresses.value = Array.isArray(addrRes) ? addrRes : []
|
|
|
+ const nets = Array.isArray(netRes) ? netRes : []
|
|
|
+ networkList.value = nets.map(r => mapNetworkItem(r)).filter(n => n.id > 0)
|
|
|
+ const parents = Array.isArray(parentRes) ? parentRes : []
|
|
|
+ const supportMap = supportInfoByUnit(coinInfoList.value)
|
|
|
+ transferMainCoins.value = parents
|
|
|
+ .map(r => mapTransferParent(r, supportMap))
|
|
|
+ .filter(c => c.unit)
|
|
|
+ .sort((a, b) => a.displayName.localeCompare(b.displayName))
|
|
|
+ await resetSelection()
|
|
|
} catch (e) {
|
|
|
initError.value = e instanceof Error ? e.message : 'i18n:withdraw.initFailed'
|
|
|
throw e
|
|
|
@@ -335,14 +471,167 @@ export const useWithdrawStore = defineStore('withdraw', () => {
|
|
|
return NETWORK_WITHDRAW_DEFAULTS[network]?.min ?? '0'
|
|
|
}
|
|
|
|
|
|
- /** Min transfer amount from support/coin/info (look up USDT entry) */
|
|
|
- const minTransferAmount = computed(() => {
|
|
|
- const usdt = coinInfoList.value.find(c => {
|
|
|
- const unit = c.unit?.toUpperCase() ?? ''
|
|
|
- return unit === 'USDT'
|
|
|
- })
|
|
|
- return usdt?.minAmount ?? usdt?.minTransferAmount ?? '0'
|
|
|
- })
|
|
|
+ const onchainSelectedCoin = computed(
|
|
|
+ () => onchainSubCoins.value[selectedSubCoinIndex.value] ?? null,
|
|
|
+ )
|
|
|
+
|
|
|
+ const transferSelectedCoin = computed(
|
|
|
+ () => transferMainCoins.value[selectedTransferCoinIndex.value] ?? null,
|
|
|
+ )
|
|
|
+
|
|
|
+ const minTransferAmount = computed(() => transferSelectedCoin.value?.minTransfer ?? '0')
|
|
|
+
|
|
|
+ async function loadOnchainSubCoins(networkId: number) {
|
|
|
+ if (!networkId) {
|
|
|
+ onchainSubCoins.value = []
|
|
|
+ return
|
|
|
+ }
|
|
|
+ onchainSubCoinsLoading.value = true
|
|
|
+ try {
|
|
|
+ const raw = await get<Record<string, unknown>[]>('/uc/coin-network/withdraw-configs', {
|
|
|
+ networkId,
|
|
|
+ })
|
|
|
+ const list = Array.isArray(raw) ? raw : []
|
|
|
+ const supportMap = supportInfoByUnit(coinInfoList.value)
|
|
|
+ onchainSubCoins.value = list
|
|
|
+ .map(r => mapWithdrawConfigFromDb(r, supportMap))
|
|
|
+ .filter(c => c.unit)
|
|
|
+ if (selectedSubCoinIndex.value >= onchainSubCoins.value.length) {
|
|
|
+ selectedSubCoinIndex.value = 0
|
|
|
+ }
|
|
|
+ } finally {
|
|
|
+ onchainSubCoinsLoading.value = false
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ async function resetSelection() {
|
|
|
+ const nets = networkList.value
|
|
|
+ if (nets.length > 0) {
|
|
|
+ const stillValid = nets.some(n => n.id === selectedNetworkId.value)
|
|
|
+ if (!stillValid) {
|
|
|
+ selectedNetworkId.value = nets[0].id
|
|
|
+ }
|
|
|
+ await loadOnchainSubCoins(selectedNetworkId.value)
|
|
|
+ } else {
|
|
|
+ selectedNetworkId.value = 0
|
|
|
+ onchainSubCoins.value = []
|
|
|
+ }
|
|
|
+ selectedSubCoinIndex.value = 0
|
|
|
+ if (selectedTransferCoinIndex.value >= transferMainCoins.value.length) {
|
|
|
+ selectedTransferCoinIndex.value = 0
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ async function selectNetwork(networkId: number) {
|
|
|
+ if (!networkId || !networkList.value.some(n => n.id === networkId)) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ selectedNetworkId.value = networkId
|
|
|
+ selectedSubCoinIndex.value = 0
|
|
|
+ await loadOnchainSubCoins(networkId)
|
|
|
+ }
|
|
|
+
|
|
|
+ function selectSubCoin(index: number) {
|
|
|
+ if (index < 0 || index >= onchainSubCoins.value.length) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ selectedSubCoinIndex.value = index
|
|
|
+ }
|
|
|
+
|
|
|
+ function selectTransferCoin(index: number) {
|
|
|
+ if (index < 0 || index >= transferMainCoins.value.length) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ selectedTransferCoinIndex.value = index
|
|
|
+ }
|
|
|
+
|
|
|
+ function balanceForCoin(coin: WithdrawCoinOption | null, mode: 'withdraw' | 'transfer'): string {
|
|
|
+ if (!coin) {
|
|
|
+ return '0'
|
|
|
+ }
|
|
|
+ if (mode === 'transfer' && coin.unit.toUpperCase() === 'USDT') {
|
|
|
+ const bal = walletBalance.value
|
|
|
+ return bal?.transferableBalance ?? bal?.balance ?? '0'
|
|
|
+ }
|
|
|
+ if (mode === 'withdraw' && coin.isUsdtFamily) {
|
|
|
+ const bal = walletBalance.value
|
|
|
+ return bal?.withdrawableBalance ?? bal?.balance ?? '0'
|
|
|
+ }
|
|
|
+ return coin.balance
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 链上可提余额 */
|
|
|
+ const onchainAvailableBalance = computed(() =>
|
|
|
+ balanceForCoin(onchainSelectedCoin.value, 'withdraw'),
|
|
|
+ )
|
|
|
+
|
|
|
+ /** 内部转账可转余额 */
|
|
|
+ const transferAvailableBalance = computed(() =>
|
|
|
+ balanceForCoin(transferSelectedCoin.value, 'transfer'),
|
|
|
+ )
|
|
|
+
|
|
|
+ function getOnchainFee(): string {
|
|
|
+ return onchainSelectedCoin.value?.withdrawFee ?? '0'
|
|
|
+ }
|
|
|
+
|
|
|
+ function getOnchainMinWithdraw(): string {
|
|
|
+ const coin = onchainSelectedCoin.value
|
|
|
+ if (!coin) {
|
|
|
+ return '0'
|
|
|
+ }
|
|
|
+ const min = coin.minWithdraw
|
|
|
+ if (min && Number(min) > 0) {
|
|
|
+ return min
|
|
|
+ }
|
|
|
+ return NETWORK_WITHDRAW_DEFAULTS[coin.networkLabel]?.min ?? '0'
|
|
|
+ }
|
|
|
+
|
|
|
+ function getOnchainSubmitUnit(): string {
|
|
|
+ return onchainSelectedCoin.value?.unit ?? 'usdt'
|
|
|
+ }
|
|
|
+
|
|
|
+ function getTransferSubmitUnit(): string {
|
|
|
+ return transferSelectedCoin.value?.unit ?? 'usdt'
|
|
|
+ }
|
|
|
+
|
|
|
+ function symbolForCoin(coin: WithdrawCoinOption | null): string {
|
|
|
+ if (!coin) {
|
|
|
+ return 'USDT'
|
|
|
+ }
|
|
|
+ if (coin.isMainCoin && coin.unit.toUpperCase() === 'USDT') {
|
|
|
+ return 'USDT'
|
|
|
+ }
|
|
|
+ if (coin.isUsdtFamily && coin.networkLabel && coin.unit.toUpperCase() !== 'USDT') {
|
|
|
+ return `${coin.displayName || 'USDT'} (${coin.unit.toUpperCase()})`
|
|
|
+ }
|
|
|
+ return coin.displayName || coin.unit.toUpperCase()
|
|
|
+ }
|
|
|
+
|
|
|
+ function getOnchainCoinSymbol(): string {
|
|
|
+ return symbolForCoin(onchainSelectedCoin.value)
|
|
|
+ }
|
|
|
+
|
|
|
+ function getTransferCoinSymbol(): string {
|
|
|
+ return symbolForCoin(transferSelectedCoin.value)
|
|
|
+ }
|
|
|
+
|
|
|
+ /** @deprecated 链上请用 getOnchainSubmitUnit / 转账请用 getTransferSubmitUnit */
|
|
|
+ function getSubmitUnit(): string {
|
|
|
+ return getOnchainSubmitUnit()
|
|
|
+ }
|
|
|
+
|
|
|
+ /** @deprecated */
|
|
|
+ function getCoinSymbol(): string {
|
|
|
+ return getOnchainCoinSymbol()
|
|
|
+ }
|
|
|
+
|
|
|
+ function getSelectedFee(): string {
|
|
|
+ return getOnchainFee()
|
|
|
+ }
|
|
|
+
|
|
|
+ function getSelectedMinWithdraw(): string {
|
|
|
+ return getOnchainMinWithdraw()
|
|
|
+ }
|
|
|
|
|
|
async function sendEmailCode(unit: string, address: string, amount: string) {
|
|
|
sendingCode.value = true
|
|
|
@@ -466,12 +755,8 @@ export const useWithdrawStore = defineStore('withdraw', () => {
|
|
|
return normalizeWithdrawRecord(raw, isTransfer)
|
|
|
}
|
|
|
|
|
|
- const availableBalance = computed(() =>
|
|
|
- walletBalance.value?.withdrawableBalance ?? walletBalance.value?.balance ?? '0',
|
|
|
- )
|
|
|
- const transferableBalance = computed(() =>
|
|
|
- walletBalance.value?.transferableBalance ?? walletBalance.value?.balance ?? '0',
|
|
|
- )
|
|
|
+ const availableBalance = onchainAvailableBalance
|
|
|
+ const transferableBalance = transferAvailableBalance
|
|
|
const googleBound = computed(() => {
|
|
|
const s = security.value as (AccountAuth & Record<string, unknown>) | null
|
|
|
if (!s) {
|
|
|
@@ -483,12 +768,20 @@ export const useWithdrawStore = defineStore('withdraw', () => {
|
|
|
const fundPasswordSet = computed(() => isSecurityFlagOn(security.value?.fundsVerified))
|
|
|
|
|
|
return {
|
|
|
- walletBalance, security, coinInfoList, walletAddresses, initLoading, initError,
|
|
|
+ walletBalance, security, coinInfoList, walletAddresses,
|
|
|
+ networkList, selectedNetworkId, onchainSubCoins, onchainSubCoinsLoading,
|
|
|
+ selectedSubCoinIndex, transferMainCoins, selectedTransferCoinIndex,
|
|
|
+ initLoading, initError,
|
|
|
sendingCode, codeCountdown, submitting, formError,
|
|
|
records, recordLoading, recordHasMore,
|
|
|
availableBalance, transferableBalance, googleBound, fundPasswordSet,
|
|
|
- usdtWallets, availableNetworks, minTransferAmount,
|
|
|
+ onchainSelectedCoin, transferSelectedCoin, minTransferAmount,
|
|
|
+ usdtWallets, availableNetworks,
|
|
|
getFee, getMinWithdrawAmount, getWalletByNetwork,
|
|
|
+ getOnchainFee, getOnchainMinWithdraw, getOnchainSubmitUnit, getTransferSubmitUnit,
|
|
|
+ getOnchainCoinSymbol, getTransferCoinSymbol,
|
|
|
+ getSelectedFee, getSelectedMinWithdraw, getSubmitUnit, getCoinSymbol,
|
|
|
+ selectNetwork, selectSubCoin, selectTransferCoin, resetSelection, loadOnchainSubCoins,
|
|
|
init, sendEmailCode, submitOnchain, submitTransfer,
|
|
|
loadRecords, cancelRecord, fetchDetail, stopCountdown,
|
|
|
}
|