feat: 添加银行账户和货币选择组件,优化提现页面交互体验

This commit is contained in:
2026-01-10 17:33:59 +07:00
parent 3cd91fcad0
commit 3111436054
8 changed files with 151 additions and 40 deletions

View File

@@ -34,7 +34,7 @@ export type RwaIssuanceProductBody = TreatyBody<typeof client.api.rwa.issuance.p
export type RwaIssuanceCategoriesData = Treaty.Data<typeof client.api.rwa.category.categories.get>;
export type BankAccountsData = Treaty.Data<typeof client.api.bank_account.get>;
export type BankAccountsData = Treaty.Data<typeof client.api.bank_account.get>["data"][number];
export type BankAccountBody = TreatyBody<typeof client.api.bank_account.post>;

View File

@@ -58,11 +58,11 @@
"chooseCurrency": "选择货币",
"chooseMethod": "选择提现方式",
"amount": "金额",
"enterAmountMax": "请输入金额(最大:{amount}",
"enterAmountMax": "请输入金额(最大可用{amount}",
"validAmountError": "请输入有效的金额。",
"bankAccountId": "银行账户ID",
"enterBankAccountId": "请输入银行账户ID",
"validBankAccountError": "请输入有效的银行账户ID。",
"bankAccountId": "银行账户",
"enterBankAccountId": "请输入银行账户",
"validBankAccountError": "请输入有效的银行账户。",
"chooseChain": "选择链",
"cryptoAddress": "加密货币地址",
"enterCryptoAddress": "请输入加密货币地址",

View File

@@ -30,9 +30,9 @@
"chooseCurrency": "選擇貨幣",
"chooseMethod": "選擇提現方式",
"amount": "金額",
"enterAmountMax": "請輸入金額(最大:{amount}",
"enterAmountMax": "請輸入金額(最大可用{amount}",
"validAmountError": "請輸入有效的金額。",
"bankAccountId": "銀行賬戶ID",
"bankAccountId": "銀行賬戶",
"enterBankAccountId": "請輸入銀行賬戶ID",
"validBankAccountError": "請輸入有效的銀行賬戶ID。",
"chooseChain": "選擇鏈",

View File

@@ -7,7 +7,7 @@ interface State {
balances: BalancesData;
fundingBalances: BalancesData;
tradingBalances: BalancesData;
bankAccounts: BankAccountsData["data"];
bankAccounts: BankAccountsData[];
supportBanks: SupportBanksData["data"];
}
@@ -57,7 +57,7 @@ export const useWalletStore = defineStore("wallet", () => {
state.tradingBalances = balances.value || [];
}
async function syncBankAccounts(data?: BankAccountsData["data"]) {
async function syncBankAccounts(data?: BankAccountsData[]) {
if (data) {
state.bankAccounts = data;
return;

View File

@@ -0,0 +1,39 @@
<script lang='ts' setup>
import type { BankAccountsData } from "@/api/types";
import { modalController } from "@ionic/vue";
const emit = defineEmits<{
select: [id: BankAccountsData];
}>();
const walletStore = useWalletStore();
const { bankAccounts } = storeToRefs(walletStore);
function handleSelect(item: BankAccountsData) {
emit("select", item);
modalController.dismiss(item);
}
</script>
<template>
<ion-header class="ion-no-border">
<ion-toolbar class="ion-toolbar">
<ion-title>选择银行账户</ion-title>
</ion-toolbar>
<ion-content>
<div class="mt-2 flex flex-col">
<div v-for="item in bankAccounts" :key="item.id" class="flex items-center px-3 my-3 w-full" @click="handleSelect(item)">
<div class="ml-2 inline-flex gap-2 items-baseline">
<div class="w-10 h-10 rounded-xl flex items-center justify-center text-white font-bold text-xs shadow-sm bg-[#0F5AA6]">
<span class="text-white">{{ item.bankCode }}</span>
</div>
<span class="font-medium text-md">{{ item.bankName }}</span>
</div>
</div>
</div>
</ion-content>
</ion-header>
</template>
<style lang='css' scoped></style>

View File

@@ -0,0 +1,40 @@
<script lang='ts' setup>
import { modalController } from "@ionic/vue";
const emit = defineEmits<{
select: [code: string];
}>();
const walletStore = useWalletStore();
const { fundingBalances } = storeToRefs(walletStore);
function handleSelect(code: string) {
emit("select", code);
modalController.dismiss(code);
}
onMounted(() => {
walletStore.syncFundingBalances();
});
</script>
<template>
<ion-header class="ion-no-border">
<ion-toolbar class="ion-toolbar">
<ion-title>选择货币</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<div class="mt-2 flex flex-col">
<div v-for="item in fundingBalances" :key="item.assetCode" class="flex items-center px-3 my-3 w-full" @click="handleSelect(item.assetCode)">
<Icon :icon="item.asset.iconUrl || ''" class="text-3xl" />
<div class="ml-2 inline-flex gap-2 items-baseline">
<span class="font-medium text-md">{{ item.assetCode }}</span>
</div>
</div>
</div>
</ion-content>
</template>
<style lang='css' scoped></style>

View File

@@ -1,16 +1,21 @@
<script lang='ts' setup>
import type { GenericObject } from "vee-validate";
import type { WithdrawBody } from "@/api/types";
import { loadingController, toastController } from "@ionic/vue";
import type { BankAccountsData, WithdrawBody } from "@/api/types";
import type { FormInstance } from "@/utils";
import { loadingController, modalController, toastController } from "@ionic/vue";
import { ErrorMessage, Field, Form } from "vee-validate";
import { client, safeClient } from "@/api";
import { AssetCodeEnum, ChainEnum, WithdrawMethodEnum } from "@/api/enum";
import SelectBankAccount from "./components/select-bank-account.vue";
import SelectCurrency from "./components/select-currency.vue";
import { createWithdrawSchema } from "./rules";
const { t } = useI18n();
const router = useRouter();
const walletStore = useWalletStore();
const { fundingBalances, bankAccounts } = storeToRefs(walletStore);
await walletStore.syncFundingBalances();
const { fundingBalances } = storeToRefs(walletStore);
const formInst = useTemplateRef<FormInstance>("formInst");
const initialValues: WithdrawBody = {
assetCode: AssetCodeEnum.USDT,
@@ -22,11 +27,42 @@ const initialValues: WithdrawBody = {
};
const maxAmount = computed(() => {
const balance = fundingBalances.value?.find(item => item.assetCode === initialValues.assetCode);
return balance ? balance.available : "0";
const balance = fundingBalances.value?.find(item => item.assetCode === (formInst.value?.getValues().assetCode || initialValues.assetCode));
return Number(balance ? balance.available : "0").toFixed(2);
});
const schema = computed(() => createWithdrawSchema(t, maxAmount.value));
const currentBankAccount = ref<BankAccountsData | null>(null);
async function openSelectCurrency() {
const modal = await modalController.create({
component: SelectCurrency,
componentProps: {
onSelect: (code: string) => {
formInst.value?.setFieldValue("assetCode", code);
},
},
breakpoints: [0, 0.8],
initialBreakpoint: 0.8,
handle: true,
});
await modal.present();
}
async function openSelectBankAccount() {
const modal = await modalController.create({
component: SelectBankAccount,
componentProps: {
onSelect: (item: BankAccountsData) => {
currentBankAccount.value = item;
formInst.value?.setFieldValue("bankAccountId", item.id);
},
},
breakpoints: [0, 0.8],
initialBreakpoint: 0.8,
handle: true,
});
await modal.present();
}
async function onSubmit(values: GenericObject) {
const loading = await loadingController.create({
@@ -61,6 +97,7 @@ async function onSubmit(values: GenericObject) {
</IonHeader>
<IonContent :fullscreen="true" class="ion-padding">
<Form
ref="formInst"
:validation-schema="schema"
:initial-values="initialValues"
@submit="onSubmit"
@@ -68,17 +105,13 @@ async function onSubmit(values: GenericObject) {
<div class="flex flex-col gap-5">
<div>
<Field name="assetCode">
<template #default="{ field, value }">
<ion-radio-group v-bind="field" :model-value="value">
<ion-label class="text-sm">
{{ t("withdraw.chooseCurrency") }}
</ion-label>
<ion-item v-for="item in AssetCodeEnum" :key="item">
<ion-radio :value="item" justify="space-between">
{{ t(`withdraw.assetCode.${item}`) }}
</ion-radio>
</ion-item>
</ion-radio-group>
<template #default="{ value }">
<ion-label class="text-sm">
{{ t("withdraw.chooseCurrency") }}
</ion-label>
<ion-item button class="ion-no-border" @click="openSelectCurrency">
<ion-label>{{ value }}</ion-label>
</ion-item>
</template>
</Field>
<ErrorMessage name="assetCode" class="text-red-500 text-xs mt-1" />
@@ -105,21 +138,18 @@ async function onSubmit(values: GenericObject) {
<Field name="withdrawMethod">
<template #default="{ value: withdrawMethod }">
<div v-if="withdrawMethod === WithdrawMethodEnum.BANK">
<Field name="bankAccountId" type="text">
<template #default="{ field }">
<ion-select
v-bind="field"
class="ui-select"
interface="action-sheet"
toggle-icon=""
:label="t('withdraw.bankAccountId')"
:placeholder="t('withdraw.enterBankAccountId')"
>
<ion-select-option v-for="item in bankAccounts" :key="item.id" :value="item.id">
{{ item.bankName }} - {{ item.maskAccountNumber }}
</ion-select-option>
</ion-select>
</template>
<Field name="bankAccountId">
<ion-label class="text-sm">
{{ t('withdraw.bankAccountId') }}
</ion-label>
<ion-item button class="ion-no-border" @click="openSelectBankAccount">
<template v-if="currentBankAccount">
<div class="w-8 h-8 rounded-md mr-2 flex items-center justify-center text-white font-bold text-xs shadow-sm bg-[#0F5AA6]">
<span class="text-white">{{ currentBankAccount?.bankCode }}</span>
</div>
<ion-label>{{ currentBankAccount?.bankName }}</ion-label>
</template>
</ion-item>
</Field>
<ErrorMessage name="bankAccountId" class="text-red-500 text-xs mt-1" />
</div>