187 lines
6.4 KiB
Vue
187 lines
6.4 KiB
Vue
<script lang='ts' setup>
|
|
import type { GenericObject } from "vee-validate";
|
|
import { SelectChangeEventDetail, toastController } from "@ionic/vue";
|
|
import { toTypedSchema } from "@vee-validate/yup";
|
|
import { informationCircle, shieldCheckmark } from "ionicons/icons";
|
|
import { ErrorMessage, Field, Form } from "vee-validate";
|
|
import * as yup from "yup";
|
|
import { client, safeClient } from "@/api";
|
|
|
|
const { t } = useI18n();
|
|
const router = useRouter();
|
|
|
|
// 银行列表数据
|
|
const walletStore = useWalletStore();
|
|
const { supportBanks } = storeToRefs(walletStore);
|
|
const formInst = useTemplateRef<FormInstance>("formInst");
|
|
|
|
// 表单验证 Schema
|
|
const schema = toTypedSchema(
|
|
yup.object({
|
|
bankName: yup.string().required(t("bankCard.form.validation.bankRequired")),
|
|
accountNumber: yup
|
|
.string()
|
|
.required(t("bankCard.form.validation.accountNumberRequired")),
|
|
accountName: yup.string().required(t("bankCard.form.validation.accountNameRequired")),
|
|
}),
|
|
);
|
|
|
|
async function handleSubmit(values: GenericObject) {
|
|
try {
|
|
await safeClient(() => client.api.bank_account.post(values as any));
|
|
const toast = await toastController.create({
|
|
message: t("bankCard.messages.addSuccess"),
|
|
duration: 2000,
|
|
position: "bottom",
|
|
color: "success",
|
|
});
|
|
|
|
await toast.present();
|
|
router.back();
|
|
}
|
|
catch (error) {
|
|
console.error("添加银行卡失败:", error);
|
|
}
|
|
}
|
|
function handleBankChange(event: any) {
|
|
const item = event.detail.value;
|
|
const current = supportBanks.value.find(bank => bank.bankCode === item);
|
|
formInst.value?.setFieldValue("bankName", current?.nameCn);
|
|
formInst.value?.setFieldValue("swiftCode", current?.swiftCode);
|
|
formInst.value?.setFieldValue("bankCode", current?.bankCode);
|
|
}
|
|
|
|
// 格式化银行卡号显示
|
|
function formatCardNumber(value: string) {
|
|
if (!value)
|
|
return value;
|
|
const numbers = value.replace(/\D/g, "");
|
|
return numbers.replace(/(\d{4})(?=\d)/g, "$1 ");
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<IonPage>
|
|
<ion-header>
|
|
<ion-toolbar class="ui-toolbar">
|
|
<ui-back-button slot="start" />
|
|
<ion-title>{{ t('bankCard.add') }}</ion-title>
|
|
</ion-toolbar>
|
|
</ion-header>
|
|
|
|
<IonContent :fullscreen="true">
|
|
<div class="min-h-full">
|
|
<div class="p-4">
|
|
<!-- 表单说明 -->
|
|
<div class="border border-blue-200 dark:border-blue-900 rounded-xl p-4 mb-6">
|
|
<div class="flex items-start gap-3">
|
|
<ion-icon
|
|
:icon="informationCircle"
|
|
class="text-blue-500 text-xl mt-0.5 shrink-0"
|
|
/>
|
|
<div class="text-sm text-blue-700 dark:text-blue-300">
|
|
<p class="font-medium mb-1">
|
|
{{ t('bankCard.form.tips.title') }}
|
|
</p>
|
|
<p class="leading-relaxed">
|
|
{{ t('bankCard.form.tips.description') }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<Form ref="formInst" :validation-schema="schema" class="space-y-5" @submit="handleSubmit">
|
|
<div class="space-y-4">
|
|
<Field v-slot="{ field }" name="bankCode">
|
|
<ion-select class="ui-select" interface="action-sheet" toggle-icon="" v-bind="field" :label="t('bankCard.form.bankName')" :placeholder="t('bankCard.form.bankNamePlaceholder')" @ion-change="handleBankChange">
|
|
<ion-select-option v-for="item in supportBanks" :key="item.bankCode" :value="item.bankCode">
|
|
{{ item.nameCn }}
|
|
</ion-select-option>
|
|
</ion-select>
|
|
</Field>
|
|
|
|
<div>
|
|
<Field name="accountNumber">
|
|
<template #default="{ field }">
|
|
<ui-input-label
|
|
v-bind="field"
|
|
:label="t('bankCard.form.accountNumber')"
|
|
:placeholder="t('bankCard.form.accountNumberPlaceholder')"
|
|
type="text"
|
|
inputmode="numeric"
|
|
:maxlength="23"
|
|
:helper-text="t('bankCard.form.accountNumberHelper')"
|
|
required
|
|
@input="(e:any) => field.value = formatCardNumber(e.target.value)"
|
|
/>
|
|
</template>
|
|
</Field>
|
|
<ErrorMessage name="accountNumber" class="text-red-500 text-sm mt-1" />
|
|
</div>
|
|
|
|
<div>
|
|
<Field name="accountName">
|
|
<template #default="{ field }">
|
|
<ui-input-label
|
|
v-bind="field"
|
|
:label="t('bankCard.form.accountName')"
|
|
:placeholder="t('bankCard.form.accountNamePlaceholder')"
|
|
type="text"
|
|
:helper-text="t('bankCard.form.accountNameHelper')"
|
|
required
|
|
/>
|
|
</template>
|
|
</Field>
|
|
<ErrorMessage name="accountName" class="text-red-500 text-sm mt-1" />
|
|
</div>
|
|
|
|
<div class="bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800 rounded-xl p-4">
|
|
<div class="flex gap-3">
|
|
<ion-icon
|
|
:icon="shieldCheckmark"
|
|
class="text-amber-500 text-xl mt-0.5 shrink-0"
|
|
/>
|
|
<div class="text-sm text-amber-700 dark:text-amber-300">
|
|
<p class="font-medium mb-1">
|
|
{{ t('bankCard.form.security.title') }}
|
|
</p>
|
|
<ul class="space-y-1 text-xs leading-relaxed">
|
|
<li>• {{ t('bankCard.form.security.encryption') }}</li>
|
|
<li>• {{ t('bankCard.form.security.standard') }}</li>
|
|
<li>• {{ t('bankCard.form.security.privacy') }}</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<ion-button type="submit" expand="block">
|
|
{{ t('bankCard.form.submit') }}
|
|
</ion-button>
|
|
</div>
|
|
</Form>
|
|
</div>
|
|
</div>
|
|
</IonContent>
|
|
</IonPage>
|
|
</template>
|
|
|
|
<style scoped>
|
|
/* Tailwind CSS 处理所有样式 */
|
|
.ui-select {
|
|
--padding-start: 16px;
|
|
--padding-end: 16px;
|
|
border: 1px solid #d1d5db;
|
|
border-radius: 8px;
|
|
}
|
|
|
|
.ui-select.ion-invalid {
|
|
border-color: #ef4444;
|
|
}
|
|
|
|
.ion-palette-dark {
|
|
.ui-select {
|
|
border-color: #4b5563;
|
|
}
|
|
}
|
|
</style>
|