| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928 |
- <template>
- <cwg-page-wrapper>
- <view class="page page-shadow">
- <u-form ref="formRef" :rules="rules" :model="formData" class="payment-form">
- <!-- 第一步:基本信息 -->
- <view v-show="currentStep === 1" class="form-section">
- <h3 class="section-title">{{ t("card.Info.s1") }}</h3>
- <cwg-input v-model:value="formData.lastName" fkey="lastName" :required="true" :label="t('card.Form.f4')"
- rulesKey="lastName" :readonly="isReadonly" :disabled="isReadonly" @change="handleChange" />
- <cwg-input v-model:value="formData.firstName" fkey="firstName" :required="true" :label="t('card.Form.f5')"
- rulesKey="firstName" :readonly="isReadonly" :disabled="isReadonly" @change="handleChange" />
- <cwg-input v-model:value="formData.email" fkey="email" :label="t('card.Form.f3')" :required="true"
- rulesKey="email" :readonly="isReadonly" :disabled="isReadonly" @change="handleChange" />
- <view class="f" v-if="phoneCodes.length > 0">
- <cwg-input v-model:value="formData.areaCode" class="l" fkey="areaCode" :required="true" type="select"
- :show-search="true" :columns="phoneCodes" :label="t('card.Form.f2')" rulesKey="areaCode"
- :readonly="isReadonly" :disabled="isReadonly" @change="handleChange" />
- <cwg-input v-model:value="formData.mobile" class="r" fkey="mobile" label=" " rulesKey="mobile"
- :readonly="isReadonly" :disabled="isReadonly" @change="handleChange" />
- </view>
- <cwg-input v-model:value="formData.gender" fkey="gender" type="select" :required="true" rulesKey="gender"
- :columns="sexOptions" :label="t('card.Form.f8')" :readonly="isReadonly" :disabled="isReadonly"
- @change="handleChange" />
- <cwg-input v-model:value="formData.birthday" :required="true" type="date" fkey="birthday" rulesKey="birthday"
- :label="t('card.Form.f6')" :readonly="isReadonly" :disabled="isReadonly" @change="handleChange" />
- </view>
- <!-- 第二步:国籍信息 -->
- <view v-show="currentStep === 2" class="form-section">
- <h3 class="section-title">{{ t("ImproveImmediately.Title.AddressInformation") }}</h3>
- <cwg-input v-if="countryOptions.length > 0" v-model:value="formData.nationality" fkey="nationality"
- type="select" :required="true" :show-search="true" rulesKey="nationality" :columns="countryOptions"
- :label="t('card.Form.f7')" :readonly="isReadonly" :disabled="isReadonly" @change="handleChange" />
- <cwg-input v-if="cityOptions.length > 0" v-model:value="formData.town" fkey="town" :required="true"
- :show-search="true" rulesKey="town" type="select" :columns="cityOptions" :label="t('card.Form.f9')"
- :readonly="isReadonly" :disabled="isReadonly" @change="handleChange" />
- <cwg-input v-model:value="formData.address" fkey="address" :label="t('card.Form.f10')" :required="true"
- rulesKey="address" :readonly="isReadonly" :disabled="isReadonly" @change="handleChange" />
- <cwg-input v-model:value="formData.postCode" :required="true" fkey="postCode" rulesKey="postCode"
- :label="t('card.Form.f11')" :readonly="isReadonly" :disabled="isReadonly" @change="handleChange" />
- </view>
- <!-- 第三步:职业信息 -->
- <view v-show="currentStep === 3" class="form-section">
- <h3 class="section-title">{{ t("card.Info.s0") }}</h3>
- <cwg-input v-if="occupationList.length > 0" v-model:value="formData.occupation" fkey="occupation"
- type="select" :columns="occupationList" :label="t('card.Form.f12')" :readonly="isReadonly"
- :disabled="isReadonly" @change="handleChange" />
- <cwg-input v-model:value="formData.annualSalary" fkey="annualSalary" :label="t('card.Form.f13')"
- :readonly="isReadonly" :disabled="isReadonly" @change="handleChange" />
- <cwg-input v-model:value="formData.accountPurpose" fkey="accountPurpose" :label="t('card.Form.f14')"
- :readonly="isReadonly" :disabled="isReadonly" @change="handleChange" />
- <cwg-input v-model:value="formData.expectedMonthlyVolume" fkey="expectedMonthlyVolume"
- :label="t('card.Form.f15')" :readonly="isReadonly" :disabled="isReadonly" @change="handleChange" />
- </view>
- <!-- 第四步:证件信息 -->
- <view v-show="currentStep === 4" class="form-section">
- <h3 class="section-title">{{ t("card.Info.s2") }}</h3>
- <cwg-input v-if="availableIdTypeOptions.length" v-model:value="formData.idType" rulesKey="idType"
- fkey="idType" :required="true" type="select" :columns="availableIdTypeOptions" :label="t('card.Form.f16')"
- :readonly="isReadonly" :disabled="isReadonly" @change="handleChange" />
- <cwg-input v-model:value="formData.idNumber" :required="true" rulesKey="idNumber" fkey="idNumber"
- :label="t('card.Form.f17')" :readonly="isReadonly" :disabled="isReadonly" @change="handleChange" />
- <cwg-input v-if="formData.nationality == 'US'" v-model:value="formData.ssn" fkey="ssn" rulesKey="ssn"
- :label="t('card.Form.f18')" :readonly="isReadonly" :disabled="isReadonly" @change="handleChange" />
- <cwg-input v-model:value="formData.issueDate" :required="true" type="date" fkey="issueDate"
- rulesKey="issueDate" :label="t('card.Form.f19')" :readonly="isReadonly" :disabled="isReadonly"
- @change="handleChange" />
- <cwg-input v-model:value="formData.idNoExpiryDate" :required="true" type="date" fkey="idNoExpiryDate"
- rulesKey="idNoExpiryDate" :label="t('card.Form.f20')" :readonly="isReadonly" :disabled="isReadonly"
- @change="handleChange" />
- <template v-if="formData.photoStatus == '1'">
- <cwg-input v-model:value="formData.idFrontUrl" :required="true" type="upload" fkey="idFrontUrl"
- rulesKey="idFrontUrl" :label="t('card.Form.f21')" :is-upload-d="true"
- accept="image/png, image/jpeg, image/jpg" :readonly="isReadonly" :disabled="isReadonly"
- @change="handleChange">
- <view class="cwg-upload">
- <cwg-icon name="back-top" />
- <view class="name">
- {{ t("card.Form.f23_2") }}{{ t("card.Form.f23") }}
- </view>
- <view class="back">{{ t("card.Form.f23_3") }}</view>
- </view>
- </cwg-input>
- <cwg-input v-model:value="formData.idBackUrl" type="upload" :required="true" fkey="idBackUrl"
- rulesKey="idBackUrl" :label="t('card.Form.f22')" :is-upload-d="true"
- accept="image/png, image/jpeg, image/jpg" :readonly="isReadonly" :disabled="isReadonly"
- @change="handleChange">
- <view class="cwg-upload">
- <cwg-icon name="back-top" />
- <view class="name">
- {{ t("card.Form.f23_2") }}{{ t("card.Form.f23") }}
- </view>
- <view class="back">{{ t("card.Form.f23_3") }}</view>
- </view>
- </cwg-input>
- <cwg-input v-model:value="formData.idHoldUrl" type="upload" fkey="idHoldUrl" :required="true"
- rulesKey="idHoldUrl" :label="t('card.Form.f23')" :is-upload-d="true"
- accept="image/png, image/jpeg, image/jpg" :readonly="isReadonly" :disabled="isReadonly"
- @change="handleChange">
- <view class="cwg-upload">
- <cwg-icon name="back-top" />
- <view class="name">
- {{ t("card.Form.f23_2") }}{{ t("card.Form.f23") }}
- </view>
- <view class="back">{{ t("card.Form.f23_3") }}</view>
- </view>
- </cwg-input>
- </template>
- </view>
- <!-- 步骤控制按钮 -->
- <view class="form-actions">
- <template v-if="currentStep === 3">
- <view class="fixed-btn">
- <view class="cwg-button two-btn">
- <u-button plain block class="prev-btn" @click="goStep(2)">
- {{ t("card.Btn.Previous") }}
- </u-button>
- <u-button type="primary" block :loading="loadingStates.validateStep3" @click="validateStep3">
- {{ t("card.Btn.Next") }}
- </u-button>
- </view>
- </view>
- </template>
- <template v-else-if="currentStep === 2">
- <view class="fixed-btn">
- <view class="cwg-button two-btn">
- <u-button plain block class="prev-btn" @click="goStep(1)">
- {{ t("card.Btn.Previous") }}
- </u-button>
- <u-button type="warning" block :loading="loadingStates.validateStep2" @click="validateStep2">
- {{ t("card.Btn.Next") }}
- </u-button>
- </view>
- </view>
- </template>
- <template v-else-if="currentStep === 1">
- <view class="fixed-btn">
- <view class="cwg-button two-btn">
- <u-button type="warning" block :loading="loadingStates.validateStep1" @click="validateStep1">
- {{ t("card.Btn.Next") }}
- </u-button>
- </view>
- </view>
- </template>
- <template v-else-if="currentStep === 4">
- <view class="fixed-btn">
- <view class="cwg-button two-btn">
- <u-button plain block class="prev-btn" @click="goStep(3)">
- {{ t("card.Btn.Previous") }}
- </u-button>
- <u-button v-if="!isUpdate" type="warning" block :loading="loadingStates.validateStep4"
- @click="validateStep4(1)">
- {{ t("card.Btn.Submit") }}
- </u-button>
- <u-button v-if="isUpdate && !isAuthInfo" type="warning" block :loading="loadingStates.validateStep4"
- @click="validateStep4(2)">
- {{ t("card.Btn.Update") }}
- </u-button>
- <u-button v-if="
- isUpdate &&
- formData.kycStatus != '2' &&
- formData.photoStatus != '1'
- " type="warning" block :loading="loadingStates.validateStep4" @click="validateStep4(3)">
- {{ t("card.Btn.Auth") }}
- </u-button>
- </view>
- </view>
- </template>
- </view>
- </u-form>
- </view>
- <view class="form-tab"></view>
- </cwg-page-wrapper>
- <card-websdk-link ref="cardWebsdkLinkRef" />
- </template>
- <script setup lang="ts">
- import { ref, onMounted, watch, computed } from "vue";
- import { pinyin } from "pinyin-pro";
- import { useI18n } from "vue-i18n";
- import { onLoad } from '@dcloudio/uni-app'
- import { userToken } from "@/composables/config";
- import { ucardApi } from "@/api/ucard";
- import { userApi } from "@/api/user";
- import useUserStore from "@/stores/use-user-store";
- import useIdTypeOptions from "@/composables/useIdTypeOptions";
- import { Patterns, Validators } from "@/utils/validators";
- import CardWebsdkLink from "@/components/card-websdkLink.vue";
- import useRouter from "@/hooks/useRouter";
- const router = useRouter();
- const { availableIdTypeOptions, loadIdTypesConfig } = useIdTypeOptions()
- console.log(availableIdTypeOptions, 1212121, 'storeIdTypeOptionsstoreIdTypeOptions');
- const { t } = useI18n();
- const currentStep = ref<number>(1);
- const cardWebsdkLinkRef = ref<InstanceType<typeof CardWebsdkLink> | null>(null);
- onLoad((options) => {
- currentStep.value = parseInt(options?.currentStep || '1', 10);
- });
- const formRef = ref();
- function goStep(step: number) {
- currentStep.value = step;
- }
- const sexOptions = ref([
- { text: t("card.Form.v1"), value: "M" },
- { text: t("card.Form.v2"), value: "F" },
- ]);
- function validateName(a: any, b?: any, c?: any) {
- const reg = /^[A-Z\s]+$/i;
- // Element 风格: (rule, value, callback)
- if (typeof c === "function") {
- const value = b;
- const callback = c;
- const val = String(value ?? "").trim();
- if (!val) return callback(new Error(t("card.vaildate.v4")));
- if (!reg.test(val)) return callback(new Error(t("card.vaildate.v38")));
- if (val.length < 2 || val.length > 23)
- return callback(new Error(t("card.vaildate.v39")));
- const firstName = String(formData.value?.firstName ?? "").trim();
- const lastName = String(formData.value?.lastName ?? "").trim();
- if (`${firstName} ${lastName}`.length > 23)
- return callback(new Error(t("card.vaildate.v40")));
- return callback();
- }
- // Vant 风格: (value) => boolean | string | Promise
- const val = String(a ?? "").trim();
- if (!val) return t("card.vaildate.v4");
- if (!reg.test(val)) return t("card.vaildate.v38");
- if (val.length < 2 || val.length > 23) return t("card.vaildate.v39");
- const firstName = String(formData.value?.firstName ?? "").trim();
- const lastName = String(formData.value?.lastName ?? "").trim();
- if (`${firstName} ${lastName}`.length > 23) return t("card.vaildate.v40");
- return true;
- }
- // 生日校验:18 岁以上
- function validateBirthday(a: any, b?: any, c?: any) {
- // Element 风格: (rule, value, callback)
- if (typeof c === "function") {
- const value = b;
- const callback = c;
- const val = value;
- if (!val) return callback(new Error(t("card.vaildate.v5")));
- const today = new Date();
- const birthDate = new Date(val);
- let age = today.getFullYear() - birthDate.getFullYear();
- const month = today.getMonth() - birthDate.getMonth();
- if (month < 0 || (month === 0 && today.getDate() < birthDate.getDate()))
- age--;
- if (age < 18) return callback(new Error(t("card.New.n3")));
- return callback();
- }
- // uview-plus / Vant 风格: (value) => boolean | string
- const val = a;
- if (!val) return t("card.vaildate.v5");
- const today = new Date();
- const birthDate = new Date(val);
- let age = today.getFullYear() - birthDate.getFullYear();
- const month = today.getMonth() - birthDate.getMonth();
- if (month < 0 || (month === 0 && today.getDate() < birthDate.getDate()))
- age--;
- return age < 18 ? t("card.New.n3") : true;
- }
- function validateAddress(a: any, b?: any, c?: any) {
- // 地址只需要校验长度和基础字符合法性,允许数字、字母、空格等
- // Element 风格: (rule, value, callback)
- if (typeof c === "function") {
- const value = b;
- const callback = c;
- const val = String(value ?? "").trim();
- if (!val) return callback(new Error(t("card.vaildate.v27")));
- if (val.length < 2 || val.length > 40)
- return callback(new Error(t("card.New.n1")));
- if (!Patterns.address.test(val))
- return callback(new Error(t("card.New.n1")));
- return callback();
- }
- // Vant 风格: (value) => boolean | string | Promise
- const val = String(a ?? "").trim();
- if (!val) return t("card.vaildate.v27");
- if (val.length < 2 || val.length > 40) return t("card.New.n1");
- return Patterns.address.test(val) ? true : t("card.New.n1");
- }
- // 表单验证规则
- const rules = {
- areaCode: [Validators.required(t("card.vaildate.v1"))],
- mobile: [
- Validators.required(t("card.vaildate.v2")),
- Validators.pattern(Patterns.mobile, t("card.vaildate.v44")),
- ],
- email: [
- Validators.required(t("card.vaildate.v28")),
- Validators.pattern(Patterns.email, t("card.vaildate.v28")),
- ],
- firstName: [
- Validators.required(t("card.vaildate.v3")),
- Validators.custom(validateName),
- ],
- lastName: [
- Validators.required(t("card.vaildate.v4")),
- Validators.custom(validateName),
- ],
- birthday: [
- Validators.required(t("card.vaildate.v5"), "change"),
- Validators.custom(validateBirthday, "change"),
- ],
- nationality: [Validators.required(t("card.vaildate.v6"), "change")],
- town: [Validators.required(t("card.vaildate.v7"), "change")],
- address: [
- Validators.required(t("card.vaildate.v27")),
- Validators.custom(validateAddress),
- ],
- gender: [Validators.required(t("card.vaildate.v9"), "change")],
- occupation: [Validators.required(t("card.vaildate.v10"), "change")],
- annualSalary: [Validators.required(t("card.vaildate.v11"))],
- accountPurpose: [Validators.required(t("card.vaildate.v12"))],
- expectedMonthlyVolume: [Validators.required(t("card.vaildate.v13"))],
- idType: [Validators.required(t("card.vaildate.v14"), "change")],
- idNumber: [
- Validators.required(t("card.vaildate.v15")),
- Validators.pattern(Patterns.idNumber, t("card.vaildate.v45")),
- ],
- ssn: [Validators.required(t("card.vaildate.v16"))],
- issueDate: [Validators.required(t("card.vaildate.v17"), "change")],
- idNoExpiryDate: [Validators.required(t("card.vaildate.v18"), "change")],
- idFrontUrl: [Validators.required(t("card.vaildate.v19"), "change")],
- idBackUrl: [Validators.required(t("card.vaildate.v20"), "change")],
- idHoldUrl: [Validators.required(t("card.vaildate.v21"), "change")],
- postCode: [
- Validators.required(t("card.vaildate.v8")),
- Validators.pattern(Patterns.postcode, t("card.New.n2")),
- ],
- };
- const formData = ref({
- merchantOrderNo: undefined,
- cardTypeId: undefined,
- areaCode: undefined,
- mobile: undefined,
- email: undefined,
- firstName: undefined,
- lastName: undefined,
- birthday: undefined,
- nationality: undefined,
- country: undefined,
- town: undefined,
- address: undefined,
- postCode: undefined,
- gender: undefined,
- occupation: undefined,
- annualSalary: undefined,
- accountPurpose: undefined,
- expectedMonthlyVolume: undefined,
- idType: undefined,
- idNumber: undefined,
- ssn: undefined,
- issueDate: undefined,
- idNoExpiryDate: undefined,
- idFrontUrl: undefined,
- idBackUrl: undefined,
- idHoldUrl: undefined,
- ipAddress: undefined,
- cId: undefined,
- customId: undefined,
- photoStatus: '1',
- kycStatus: '1'
- });
- const isUpdate = ref(false);
- const isAuthInfo = ref(false);
- // 国家选项
- const countryOptions = ref<Array<{ text: string; value: string }>>([]);
- const cityOptions = ref<Array<{ text: string; value: string }>>([]);
- const phoneCodes = ref<Array<{ text: string; value: string }>>([]);
- const occupationList = ref<Array<{ text: string; value: string }>>([]);
- // 获取国家列表
- async function getCountryListForSelect() {
- try {
- const res = await ucardApi.ucardCountryCity({});
- if (res.code === 200 || res.code === 0) {
- countryOptions.value = res.data.map((item: any) => ({
- text: item.enName,
- value: item.code,
- }));
- phoneCodes.value = res.data.map((item: any) => ({
- text: `${item.areaCode} ${item.enName}`,
- value: item.areaCode,
- }));
- }
- } catch (error) {
- console.error('Error loading country list:', error);
- countryOptions.value = [];
- }
- }
- // 获取城市列表
- async function getCityListForSelect(countryCode: string) {
- try {
- const res = await ucardApi.ucardCountryCity({ code: countryCode });
- if (res.code === 200 || res.code === 0) {
- const cityList = res.data.map((item: any) => ({
- text: item.enName,
- value: item.code,
- }));
- cityOptions.value = cityList;
- }
- } catch (error) {
- console.error('Error loading city list:', error);
- cityOptions.value = [];
- }
- }
- // 获取职业
- async function getOccupationList() {
- try {
- const res = await ucardApi.getOccupationList();
- if (res.code === 200 || res.code === 0) {
- const list = res.data.map((item: any) => ({
- text: item.description,
- value: item.occupationCode,
- }));
- occupationList.value = list;
- }
- } catch (error) {
- console.error('Error loading occupation list:', error);
- occupationList.value = [];
- }
- }
- function handleChange(value: any) {
- formData.value = { ...formData.value, [value.key]: value.value };
- if (value.key === "nationality") {
- formData.value = { ...formData.value, country: value.value };
- getCityListForSelect(value.value);
- loadIdTypesConfig(value.value);
- formData.value.town = "";
- }
- }
- const containsChinese = (str: string) => /[\u4E00-\u9FA5]/.test(str);
- const convertToPinyin = (value: string) =>
- containsChinese(value)
- ? pinyin(value, { toneType: "none", type: "capitalize" })
- : value;
- // 初始化表单数据 - 使用现有用户信息进行回显
- function initFormDataWithUserInfo() {
- if (!userInfo.value) return;
- const customInfo = userInfo.value.customInfo || userInfo.value;
- // 基本信息
- formData.value.email = customInfo.email || formData.value.email;
- formData.value.areaCode = customInfo.areaCode || formData.value.areaCode;
- formData.value.mobile = customInfo.mobile || formData.value.mobile;
- formData.value.birthday = customInfo.birthday || formData.value.birthday;
- formData.value.gender = customInfo.gender || formData.value.gender;
- // 姓名处理
- if (customInfo.lastName) {
- formData.value.lastName = convertToPinyin(customInfo.lastName);
- }
- if (customInfo.firstName) {
- formData.value.firstName = convertToPinyin(customInfo.firstName);
- }
- // 地址信息
- formData.value.nationality = customInfo.nationality || formData.value.nationality;
- formData.value.town = customInfo.town || formData.value.town;
- formData.value.address = customInfo.address || formData.value.address;
- formData.value.postCode = customInfo.postCode || formData.value.postCode;
- // 职业信息
- formData.value.occupation = customInfo.occupation || formData.value.occupation;
- formData.value.annualSalary = customInfo.annualSalary || formData.value.annualSalary;
- formData.value.accountPurpose = customInfo.accountPurpose || formData.value.accountPurpose;
- formData.value.expectedMonthlyVolume = customInfo.expectedMonthlyVolume || formData.value.expectedMonthlyVolume;
- // 证件信息
- formData.value.idType = customInfo.idType || formData.value.idType;
- formData.value.idNumber = customInfo.idNumber || formData.value.idNumber;
- formData.value.ssn = customInfo.ssn || formData.value.ssn;
- formData.value.issueDate = customInfo.issueDate || formData.value.issueDate;
- formData.value.idNoExpiryDate = customInfo.idNoExpiryDate || formData.value.idNoExpiryDate;
- formData.value.idFrontUrl = customInfo.idFrontUrl || formData.value.idFrontUrl;
- formData.value.idBackUrl = customInfo.idBackUrl || formData.value.idBackUrl;
- formData.value.idHoldUrl = customInfo.idHoldUrl || formData.value.idHoldUrl;
- // 状态信息
- formData.value.photoStatus = customInfo.photoStatus || '1';
- formData.value.kycStatus = customInfo.kycStatus || '1';
- // 对于下拉选择字段,需要确保它们在选项列表中存在
- setTimeout(() => {
- // 确保 areaCode 在选项中
- if (formData.value.areaCode && phoneCodes.value.length > 0) {
- const areaCodeExists = phoneCodes.value.some(item => item.value === formData.value.areaCode);
- if (!areaCodeExists) {
- // 如果不存在,添加该项到选项中
- phoneCodes.value.push({
- text: formData.value.areaCode,
- value: formData.value.areaCode
- });
- }
- }
- // 确保 nationality 在选项中
- if (formData.value.nationality && countryOptions.value.length > 0) {
- const nationalityExists = countryOptions.value.some(item => item.value === formData.value.nationality);
- if (!nationalityExists) {
- countryOptions.value.push({
- text: formData.value.nationality,
- value: formData.value.nationality
- });
- }
- }
- // 确保 occupation 在选项中
- if (formData.value.occupation && occupationList.value.length > 0) {
- const occupationExists = occupationList.value.some(item => item.value === formData.value.occupation);
- if (!occupationExists) {
- occupationList.value.push({
- text: formData.value.occupation,
- value: formData.value.occupation
- });
- }
- }
- // 确保 gender 在选项中
- if (formData.value.gender) {
- const genderExists = sexOptions.value.some(item => item.value === formData.value.gender);
- if (!genderExists) {
- sexOptions.value.push({
- text: formData.value.gender === 'M' ? t("card.Form.v1") : t("card.Form.v2"),
- value: formData.value.gender
- });
- }
- }
- }, 100);
- }
- async function getUserSingle() {
- if (!userToken.value) {
- uni.showToast({ title: t("common.loginFirst"), icon: 'none' });
- return;
- }
- try {
- const res = await userApi.getUserSingle();
- if (res.code === 200 && res.data) {
- // 更新表单数据
- formData.value = { ...formData.value, ...res.data };
- userStore.saveUserInfo(res.data);
- // 设置状态
- if (res.data) {
- isUpdate.value = true;
- }
- if (res.data.approveStatus == 2 || res.data.kycStatus == 2) {
- isAuthInfo.value = true;
- }
- if (res.data.approveStatus == 3) {
- isAuthInfo.value = false;
- }
- // 如果有国籍信息,加载对应的城市列表
- if (res.data.nationality) {
- await getCityListForSelect(res.data.nationality);
- await loadIdTypesConfig(res.data.nationality);
- }
- }
- } catch (error: any) {
- console.error('Error in getUserSingle:', error);
- uni.showToast({ title: error.message || t("common.error"), icon: 'none' });
- }
- }
- // 加载状态
- const loadingStates = ref({
- validateStep1: false,
- validateStep2: false,
- validateStep3: false,
- validateStep4: false,
- });
- // 第一步校验 - 基本信息完整性
- async function validateStep1() {
- loadingStates.value.validateStep1 = true;
- try {
- const requiredFields = [
- "email",
- "lastName",
- "firstName",
- "mobile",
- "areaCode",
- "birthday",
- "gender",
- ] as const;
- await formRef.value?.validateField(requiredFields, (errorsRes: any) => {
- const hasError = Array.isArray(errorsRes) && errorsRes.length > 0;
- if (hasError) {
- uni.showToast({ title: errorsRes[0].message, icon: 'none' });
- } else {
- goStep(2);
- }
- });
- } catch (error: any) {
- if (Array.isArray(error) && error.length > 0) {
- uni.showToast({ title: error[0].message, icon: 'none' });
- } else {
- }
- } finally {
- loadingStates.value.validateStep1 = false;
- }
- }
- // 第二步校验 - 国籍信息
- async function validateStep2() {
- loadingStates.value.validateStep2 = true;
- try {
- const requiredFields = [
- "nationality",
- "town",
- "postCode",
- "address",
- ] as const;
- await formRef.value?.validateField(requiredFields, (errorsRes: any) => {
- const hasError = Array.isArray(errorsRes) && errorsRes.length > 0;
- if (hasError) {
- uni.showToast({ title: errorsRes[0].message, icon: 'none' });
- } else {
- goStep(3);
- }
- });
- } catch (error: any) {
- if (Array.isArray(error) && error.length > 0) {
- uni.showToast({ title: error[0].message, icon: 'none' });
- } else {
- }
- } finally {
- loadingStates.value.validateStep2 = false;
- }
- }
- // 第三步校验 - 职业信息
- async function validateStep3() {
- loadingStates.value.validateStep3 = true;
- try {
- const requiredFields = [
- "occupation",
- "annualSalary",
- "accountPurpose",
- "expectedMonthlyVolume",
- ] as const;
- await formRef.value?.validateField(requiredFields, (errorsRes: any) => {
- const hasError = Array.isArray(errorsRes) && errorsRes.length > 0;
- if (hasError) {
- uni.showToast({ title: errorsRes[0].message, icon: 'none' });
- } else {
- goStep(4);
- }
- });
- } catch (error: any) {
- if (Array.isArray(error) && error.length > 0) {
- uni.showToast({ title: error[0].message, icon: 'none' });
- } else {
- }
- } finally {
- loadingStates.value.validateStep3 = false;
- }
- }
- // 第四步校验 校验所有信息
- async function validateStep4(type: number) {
- loadingStates.value.validateStep4 = true;
- try {
- const requiredFields: Array<keyof typeof formData.value> = [
- "email",
- "lastName",
- "firstName",
- "mobile",
- "areaCode",
- "nationality",
- "town",
- "postCode",
- "address",
- "birthday",
- "gender",
- "idType",
- "idNumber",
- "issueDate",
- "idNoExpiryDate",
- "occupation",
- "annualSalary",
- "accountPurpose",
- "expectedMonthlyVolume",
- ];
- // 只有在需要上传照片时才验证照片字段
- if (formData.value.photoStatus === '1') {
- requiredFields.push("idFrontUrl", "idBackUrl", "idHoldUrl");
- }
- await formRef.value?.validateField(requiredFields, (errorsRes: any) => {
- const hasError = Array.isArray(errorsRes) && errorsRes.length > 0;
- console.log(hasError, 121212);
- if (hasError) {
- uni.showToast({ title: errorsRes[0].message, icon: 'none' });
- } else {
- // 最终校验所有信息
- switch (type) {
- case 1:
- infoSubmit();
- break;
- case 2:
- infoUpdate();
- break;
- case 3:
- infoAuth();
- break;
- default:
- break;
- }
- }
- });
- } catch (error: any) {
- if (Array.isArray(error) && error.length > 0) {
- uni.showToast({ title: error[0].message, icon: 'none' });
- } else {
- }
- } finally {
- loadingStates.value.validateStep4 = false;
- }
- }
- async function infoSubmit() {
- try {
- const res = await ucardApi.merchantRegister(formData.value);
- if (res.code === 200) {
- uni.showToast({ title: t("common.success"), icon: 'success' });
- // 提交成功后打开 WebSDK 弹窗
- router.push({
- path: "/pages/mine/kyc",
- query: { cardId: (formData.value as any).id },
- });
- // cardWebsdkLinkRef.value?.getWebsdkLink(
- // (formData.value as any).id
- // );
- } else {
- uni.showToast({ title: res.msg, icon: 'none' });
- }
- } catch (error: any) {
- uni.showToast({ title: error.message || t("common.error"), icon: 'none' });
- }
- }
- async function infoUpdate() {
- try {
- const res = await ucardApi.merchantUpdate(formData.value);
- if (res.code === 200) {
- uni.showToast({ title: t("common.success"), icon: 'success' });
- // 更新成功后打开 WebSDK 弹窗
- // router.push({
- // path: "/pages/mine/kyc",
- // query: { cardId: (formData.value as any).id },
- // });
- } else {
- uni.showToast({ title: res.msg, icon: 'none' });
- }
- } catch (error: any) {
- if (Array.isArray(error) && error.length > 0) {
- uni.showToast({ title: error[0].message, icon: 'none' });
- } else {
- uni.showToast({ title: t("card.New.errer"), icon: 'none' });
- }
- }
- }
- async function infoAuth() {
- try {
- // 认证操作后也打开 WebSDK 弹窗(无需等待额外接口)
- router.push({
- path: "/pages/mine/kyc",
- query: { cardId: (formData.value as any).id },
- });
- } catch (error: any) {
- if (Array.isArray(error) && error.length > 0) {
- uni.showToast({ title: error[0].message, icon: 'none' });
- } else {
- uni.showToast({ title: t("card.New.errer"), icon: 'none' });
- }
- }
- }
- const isReadonly = computed(() => isAuthInfo.value);
- onMounted(async () => {
- // 用现有用户信息回显
- initFormDataWithUserInfo();
- // 先加载选项数据
- await Promise.all([
- getOccupationList(),
- getCountryListForSelect()
- ]);
- // 延迟加载用户详细信息
- setTimeout(async () => {
- await getUserSingle();
- }, 100);
- });
- </script>
- <style scoped lang="scss">
- @import "@/uni.scss";
- .form-tab {
- height: px2rpx(100);
- }
- .form-section {
- margin: px2rpx(8) 0;
- }
- .section-title {
- color: #1a1a1a;
- font-family: Roboto;
- font-size: px2rpx(22);
- font-style: normal;
- font-weight: 600;
- line-height: px2rpx(22);
- margin-bottom: px2rpx(16);
- }
- .two-btn {
- display: flex;
- align-items: center;
- gap: px2rpx(31);
- .prev-btn {
- border: 1px solid var(--main-yellow) !important;
- color: #fff !important;
- background: transparent;
- }
- }
- .f {
- display: flex;
- align-items: flex-end;
- gap: px2rpx(12);
- .l {
- flex: 1;
- }
- .r {
- width: px2rpx(203);
- }
- }
- :deep(.u-uploader) {
- width: 100%;
- .u-uploader__wrapper {
- width: 100%;
- display: block;
- }
- .u-uploader__preview {
- width: 100% !important;
- height: px2rpx(160) !important;
- border-radius: px2rpx(24);
- overflow: hidden;
- .u-uploader__preview-image {
- width: 100%;
- height: 100%;
- .u-image__img {
- object-fit: contain;
- }
- }
- .u-uploader__preview-delete {
- position: absolute;
- top: 0;
- right: 0;
- width: px2rpx(30);
- height: px2rpx(30);
- border-radius: 0 px2rpx(24) 0 0;
- i {
- text-align: center;
- line-height: px2rpx(30);
- font-size: px2rpx(30);
- }
- }
- }
- }
- </style>
|