| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527 |
- <template>
- <cwg-page-wrapper :isHeaderFixed="true">
- <cwg-header :showBack="true" :title="title[type]">
- <view v-if="type == 2" @click="goRechargeRecord">
- <cwg-icon name="icon_history" :size="24" color="#000" />
- </view>
- </cwg-header>
- <view class="page page-shadow">
- <u-form ref="formRef" :rules="rules" :model="infoForm" class="kyc-form">
- <template v-if="type == '1'">
- <cwg-input v-model:value="infoForm.cardNumber1" fkey="cardNumber1" :required="true"
- :label="`${t('card.Form.f24')}:`" rulesKey="cardNumber1" @change="handleChange" />
- <cwg-input v-model:value="infoForm.activeCode" fkey="activeCode" :required="true"
- :label="`${t('card.Form.f26')}:`" rulesKey="activeCode" @change="handleChange" />
- <cwg-input v-model:value="infoForm.pin" fkey="pin" :required="true" :maxlength="6" :label="t('card.Info.s26')"
- rulesKey="pin" @change="handleChange" />
- <cwg-input v-model:value="infoForm.password" fkey="password" :required="true" :maxlength="6"
- :label="`${t('card.Info.s26_1')}`" rulesKey="password" @change="handleChange" />
- <view class="pwd">
- <view class="lis" v-t="'card.vaildate.v32'" :class="{ fit: rule1 }"></view>
- <view class="lis" v-t="'card.vaildate.v33'" :class="{ fit: rule2 }"></view>
- </view>
- </template>
- <template v-if="type == '2'">
- <cwg-input v-model:value="infoForm.cardNumber1" fkey="cardNumber1" :required="true"
- :label="`${t('card.Form.f24')}:`" rulesKey="cardNumber1" @change="handleChange" />
- <cwg-input v-model:value="infoForm.amount" fkey="amount" type="number" :label="`${t('card.Form.f28')}:`"
- :required="true" :min="rechargeMinQuota" :max="rechargeMaxQuota" :placeholder="t('card.vaildate.v26')"
- @change="handleChange">
- </cwg-input>
- <view class="balance-info">
- <text class="balance-key">{{ t("card.Form.f56") }}</text>
- <text class="balance-value">{{ userBalance }} USD</text>
- <view class="all-btn" @click="allBalance">{{
- t("card.Form.f57")
- }}</view>
- </view>
- <view class="balance-info">
- <text class="balance-key">{{ t("card.Form.f58") }}</text>
- <text class="balance-value">{{ exchangeRate }}%</text>
- <text></text>
- </view>
- <view class="balance-info">
- <text class="balance-key">{{ t("card.Form.f59") }}</text>
- <text class="balance-value">{{ fee }}</text>
- <text></text>
- </view>
- </template>
- <template v-if="type == '3'">
- <cwg-input v-model:value="infoForm.cardNumber1" fkey="cardNumber1" :required="true"
- :label="`${t('card.Form.f24')}:`" rulesKey="cardNumber1" @change="handleChange" />
- <cwg-input v-model:value="infoForm.pin" fkey="pin" :required="true" :label="t('card.Info.s26')" :maxlength="6"
- rulesKey="pin" @change="handleChange" />
- <cwg-input v-model:value="infoForm.password" fkey="password" :required="true" :maxlength="6"
- :label="`${t('card.Info.s26_1')}`" rulesKey="password" @change="handleChange" />
- <view class="pwd">
- <view class="lis" v-t="'card.vaildate.v32'" :class="{ fit: rule1 }"></view>
- <view class="lis" v-t="'card.vaildate.v33'" :class="{ fit: rule2 }"></view>
- </view>
- </template>
- <template v-if="type == '4' || type == '5'">
- <cwg-input v-model:value="infoForm.cardNumber1" fkey="cardNumber1" :required="true"
- :label="`${t('card.Form.f24')}:`" rulesKey="cardNumber1" @change="handleChange" />
- <cwg-input v-model:value="infoForm.clientRemark" fkey="clientRemark" :label="`${t('card.Form.f27')}:`"
- @change="handleChange" />
- </template>
- <view v-if="infoForm.authStatus != 1" class="fixed-btn">
- <view class="cwg-button">
- <u-button type="primary" block @click="infoSubmit">{{
- t(btn[type])
- }}</u-button>
- </view>
- </view>
- </u-form>
- </view>
- <cwg-SuccessPrompt v-model:show="showSuccessPrompt" :title="t(title[type])" :desc="t('card.Msg.m2')"
- :btn-click="btnClick" />
- </cwg-page-wrapper>
- </template>
- <script setup lang="ts">
- import { ref, onMounted, watch, computed } from "vue";
- import { showToast } from "@/utils/toast";
- import { useI18n } from "vue-i18n";
- import useRouter from "@/hooks/useRouter";
- import { onLoad } from '@dcloudio/uni-app'
- import { ucardApi } from "@/api/ucard";
- import config from "@/config";
- import { Validators } from "@/utils/validators";
- const { t } = useI18n();
- const router = useRouter();
- const form = ref({});
- const formRef = ref(null);
- const infoForm = ref({
- password: undefined,
- cardNumber1: undefined,
- cardNo: undefined,
- activeCode: undefined,
- pin: undefined,
- amount: undefined,
- clientRemark: undefined,
- });
- const title = ref({
- 1: t('card.Btn.b1'),
- 2: t('card.Btn.b3'),
- 3: t('card.Btn.b4'),
- 4: t('card.Btn.b5'),
- 5: t('card.Btn.b6')
- })
- const btn = ref({
- 1: t('card.Btn.Activate'),
- 2: t('card.Btn.Recharge'),
- 3: t('card.Btn.Submit'),
- 4: t('card.Btn.Freeze'),
- 5: t('card.Btn.Unfreeze')
- })
- const type = ref()
- const id = ref()
- onLoad((options) => {
- id.value = options.id
- type.value = options.type
- })
- const showSuccessPrompt = ref(false);
- const btnClick = () => {
- router.push('/pages/card/index');
- };
- // 动态限制
- const exchangeRate = ref(0);
- const rechargeMinQuota = ref(0);
- const rechargeMaxQuota = ref(0);
- const rechargeFixedFee = ref(0);
- const userBalance = ref(0);
- const pictLoading = ref(false);
- function validatePin(s) {
- if (!/^\d{6}$/.test(s)) return false;
- if (/(.)\1\1/.test(s)) return false;
- const isSequential = (str) => {
- let inc = true;
- let dec = true;
- for (let i = 1; i < str.length; i++) {
- if (str[i] - str[i - 1] !== 1) inc = false;
- if (str[i - 1] - str[i] !== 1) dec = false;
- }
- return inc || dec;
- };
- if (isSequential(s)) return false;
- if (/^(\d{2})\1\1$/.test(s)) return false;
- if (/^(\d{3})\1$/.test(s)) return false;
- return true;
- }
- function validatePina(a: any, b?: any, c?: any) {
- if (typeof c === "function") {
- const value = b;
- const callback = c;
- const val = String(value ?? "").trim();
- if (validatePin(val)) {
- return callback();
- } else {
- return callback(new Error(t("card.vaildate.v23")));
- }
- }
- }
- function validatePassword(a: any, b?: any, c?: any) {
- if (typeof c === "function") {
- const value = b;
- const callback = c;
- const val = String(value ?? "").trim();
- if (validatePin(val)) {
- if (formData.value.pin != formData.value.password) {
- callback(new Error(t("vaildate.password.same")));
- return;
- } else {
- return callback();
- }
- } else {
- callback(new Error(t("vaildate.password.same")));
- }
- }
- }
- const rules = {
- pin: [
- Validators.required(t("card.vaildate.v23")),
- Validators.custom(validatePina),
- ],
- password: [
- Validators.required(t("card.vaildate.v31")),
- Validators.custom(validatePassword)
- ],
- cardNumber1: [
- {
- required: true,
- message: t("card.vaildate.v41"),
- trigger: "blur",
- },
- ],
- amount: [
- {
- required: true,
- message: t("card.vaildate.v26"),
- trigger: "blur",
- },
- {
- validator: (rule, value, callback) => validateAmount(value, callback),
- trigger: "blur",
- },
- ],
- activeCode: [
- {
- required: true,
- message: t("card.vaildate.v24"),
- trigger: "blur",
- },
- ],
- };
- const formData = ref<typeof infoForm.value>({ id } as any);
- const headerTitleMap: Record<number, string> = {
- 1: "card.tab10",
- 2: "card.tab11",
- 3: "card.tab12",
- 4: "card.tab13",
- 5: "card.tab14",
- };
- // 根据 type 自动取标题
- const maxRecharge = computed(() => {
- const balance = Number(userBalance.value) || 0;
- const rate = Number(exchangeRate.value) / 100;
- if (balance <= 0) return 0;
- return Math.floor((balance / (1 + rate)) * 100) / 100;
- });
- const fee = computed(() => {
- if (!formData.value.amount) {
- return 0;
- }
- if (rechargeFixedFee.value) {
- return rechargeFixedFee.value;
- }
- const amount = Math.ceil(formData.value.amount);
- return ((amount * exchangeRate.value) / 100).toFixed(2);
- });
- function goRechargeRecord() {
- router.push(`/pages/recharge-record/list?cardNo=${formData.value?.cardNo}`);
- }
- // 计算属性 rule1
- const rule1 = computed(() => {
- if (!formData.value.pin) return false;
- return /^(\d)\d{5}$/.test(formData.value.pin);
- });
- // 计算属性 rule2
- const rule2 = computed(() => {
- if (!formData.value.pin) return false;
- return config.Pattern.Pin.test(formData.value.pin);
- });
- async function infoSubmit() {
- if (formData.value.cardNumber1 != infoForm.value.cardNumber) {
- showToast(t("card.vaildate.v41"));
- return;
- }
- if (pictLoading.value) {
- return
- }
- pictLoading.value = true;
- try {
- switch (type.value) {
- case "1":
- await formRef.value?.validate();
- ucardActivate();
- break;
- case "2":
- await formRef.value?.validate();
- ucardRecharge();
- break;
- case "3":
- await formRef.value?.validate();
- ucardResetPassword();
- break;
- case "4":
- await formRef.value?.validate();
- ucardFreeze();
- break;
- case "5":
- await formRef.value?.validate();
- ucardUnfreeze();
- break;
- }
- } catch (error) {
- pictLoading.value = false;
- }
- }
- function backActivity() {
- setTimeout(() => {
- router.back();
- }, 3000);
- }
- async function ucardActivate() {
- if (formData.value.pin != formData.value.password) {
- showToast(t("card.Msg.m11"));
- return;
- }
- const res = await ucardApi.ucardActivate(formData.value);
- if (res.code == 200) {
- showToast(t("card.Msg.m3"));
- backActivity();
- } else {
- showToast(res.msg);
- }
- pictLoading.value = false;
- }
- async function ucardResetPassword() {
- const res = await ucardApi.ucardResetPassword(formData.value);
- if (res.code == 200) {
- showToast(t("card.Msg.m6"));
- backActivity();
- } else {
- showToast(res.msg);
- }
- pictLoading.value = false;
- }
- async function ucardFreeze() {
- const res = await ucardApi.ucardFreeze(formData.value);
- if (res.code == 200) {
- showToast(t("card.Msg.m4"));
- backActivity();
- } else {
- showToast(res.msg);
- }
- pictLoading.value = false;
- }
- async function ucardUnfreeze() {
- const res = await ucardApi.ucardUnfreeze(formData.value);
- if (res.code == 200) {
- showToast(t("card.Msg.m5"));
- backActivity();
- } else {
- showToast(res.msg);
- }
- pictLoading.value = false;
- }
- async function getCardInfo() {
- try {
- if (!id) return;
- const res = await ucardApi.getCardInfo({ id: id.value });
- // 只更新后端返回的字段,避免整体替换导致输入被清空
- Object.assign(infoForm.value, res.data);
- Object.assign(formData.value, res.data);
- exchangeRate.value = res.data.rechargeFeeRate;
- rechargeMaxQuota.value = res.data.rechargeMaxQuota;
- rechargeMinQuota.value = res.data.rechargeMinQuota;
- rechargeFixedFee.value = res.data.rechargeFixedFee;
- } catch (error) {
- console.log(error);
- }
- }
- // 一键填充最大金额
- function allBalance() {
- infoForm.value.amount = maxRecharge.value;
- }
- // 金额验证函数(配合 Vant <Field /> 的 rules)
- function validateAmount(value: string | number) {
- const num = Number(value);
- if (!num || num <= 0) {
- return t("card.vaildate.v34"); // 请输入有效金额
- } else if (maxRecharge.value === 0) {
- return `${t("card.Form.f56")} 0 USD`;
- } else if (num > maxRecharge.value) {
- return `${t("card.vaildate.v35")} ${maxRecharge.value} USD`;
- } else if (num < rechargeMinQuota.value) {
- return `${t("card.vaildate.v36")} ${rechargeMinQuota.value} USD`;
- } else if (num >= rechargeMaxQuota.value) {
- return `${t("card.vaildate.v37")} ${rechargeMaxQuota.value} USD`;
- }
- return true;
- }
- // 获取钱包余额
- async function walletBalance() {
- try {
- const res = await ucardApi.walletBalance();
- if (res.code === 200) {
- userBalance.value = res.data.balance || 0;
- } else {
- showToast(res.msg);
- userBalance.value = 0;
- }
- } catch (err) {
- userBalance.value = 0;
- }
- }
- // 银行卡充值
- async function ucardRecharge() {
- const amount = Number(formData.value.amount);
- const cardNo = formData.value.cardNo;
- if (validateAmount(amount) !== true) {
- showToast(validateAmount(amount));
- return;
- }
- try {
- const res = await ucardApi.ucardRecharge({ amount, cardNo });
- if (res.code === 200) {
- // showToast(t("card.Msg.m2"));
- showSuccessPrompt.value = true;
- } else {
- showToast(res.msg);
- }
- } catch (err) {
- console.log(err);
- } finally {
- pictLoading.value = false;
- }
- }
- onMounted(() => {
- getCardInfo();
- if (type.value == "2") {
- walletBalance();
- }
- });
- function handleChange(value: any) {
- formData.value = { ...formData.value, [value.key]: value.value };
- }
- </script>
- <style scoped lang="scss">
- @import "@/uni.scss";
- .pointer-none {
- pointer-events: none;
- }
- .f {
- display: flex;
- align-items: flex-end;
- gap: px2rpx(12);
- .l {
- flex: 1;
- }
- .r {
- width: px2rpx(273);
- }
- }
- .pwd {
- line-height: px2rpx(20);
- .lis {
- list-style-type: disc;
- line-height: 1.2;
- margin: px2rpx(4) 0 px2rpx(4) px2rpx(10);
- color: #c0c4cc;
- position: relative;
- &::after {
- content: '';
- position: absolute;
- left: px2rpx(-6);
- top: px2rpx(6);
- width: px2rpx(4);
- height: px2rpx(4);
- border-radius: 50%;
- background: #c0c4cc;
- }
- }
- .fit {
- color: var(--main-yellow);
- &::after {
- background: var(--main-yellow);
- }
- }
- }
- .balance-info {
- width: 100%;
- color: #bdbdbd;
- font-size: px2rpx(14);
- margin-bottom: px2rpx(44);
- display: flex;
- align-items: center;
- /* justify-content: space-between; */
- }
- .balance-value {
- width: px2rpx(140);
- color: var(--white);
- font-weight: bold;
- margin: 0 px2rpx(8);
- }
- .balance-key {
- color: #8e8a8a;
- width: px2rpx(130);
- }
- .all-btn {
- min-width: px2rpx(30);
- display: flex;
- padding: px2rpx(4) px2rpx(12);
- justify-content: center;
- align-items: center;
- gap: px2rpx(10);
- border-radius: px2rpx(35);
- border: 1px solid #ea002a;
- color: var(--Brand-color, #ea002a);
- font-family: Roboto;
- font-size: px2rpx(14);
- font-style: normal;
- font-weight: 600;
- line-height: px2rpx(20);
- letter-spacing: px2rpx(0.07);
- }
- </style>
|