| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590 |
- <template>
- <view class="personal-info-tab">
- <uni-loading v-if="loading" />
- <view v-else class="user-form">
- <uni-row class="demo-uni-row">
- <!-- <uni-col :xs="24" :sm="24" :md="24" :lg="6" :xl="6">-->
- <!-- <view class="avatar-section">-->
- <!-- <cwg-input v-model:value="formData.idBackUrl" type="upload" fkey="idBackUrl"-->
- <!-- rulesKey="idBackUrl" :is-upload-d="true" accept="image/png, image/jpeg, image/jpg"-->
- <!-- :readonly="isReadonly" :disabled="isReadonly" @change="handleChange">-->
- <!-- <view class="cwg-upload">-->
- <!-- </view>-->
- <!-- </cwg-input>-->
- <!-- <view class="text name">{{ formData.firstName }} {{ formData.lastName }}</view>-->
- <!-- <view class="text cid">CID:{{ formData.cId }}</view>-->
- <!-- <view class="btn-primary" @click="handleEditProfile">-->
- <!-- <cwg-icon name="crm-photo-film" :size="16" color="#fff" />-->
- <!-- 上传头像-->
- <!-- </view>-->
- <!-- </view>-->
- <!-- </uni-col>-->
- <uni-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
- <uni-forms ref="baseForm" :model="formData" labelWidth="200" label-position="top" :rules="rules"
- class="base-info-form">
- <uni-row class="demo-uni-row uni-row1">
- <!-- cid -->
- <uni-col :xs="24" :sm="24" :md="12" :lg="8" :xl="8">
- <uni-forms-item :label="t('PersonalManagement.Label.UserCID')">
- <uni-easyinput :disabled="isInfo" :clearable="false" v-model:value="formData.cId" />
- </uni-forms-item>
- </uni-col>
- <!-- 客户类型 -->
- <uni-col v-if="formData.customType" :xs="24" :sm="24" :md="12" :lg="8" :xl="8">
- <uni-forms-item :label="t('ImproveImmediately.Label.CustomerType')">
- <cwg-combox :disabled="isInfo" :clearable="false" v-model:value="formData.customType"
- :options="customTypeOptions" :placeholder="t('placeholder.choose')" />
- </uni-forms-item>
- </uni-col>
- <!-- 公司名称 -->
- <uni-col v-if="formData.companyName" :xs="24" :sm="24" :md="12" :lg="8" :xl="8">
- <uni-forms-item :label="t('ImproveImmediately.Label.CompanyName')">
- <uni-easyinput :disabled="isInfo" :clearable="false" v-model="formData.companyName"
- :placeholder="t('placeholder.input')" />
- </uni-forms-item>
- </uni-col>
- <!-- 性别 -->
- <uni-col :xs="24" :sm="24" :md="12" :lg="8" :xl="8">
- <uni-forms-item :label="t('ImproveImmediately.Label.Gender')">
- <cwg-combox :disabled="isInfo" :clearable="false" v-model:value="formData.gender"
- :options="sexOptions" :placeholder="t('placeholder.choose')" />
- </uni-forms-item>
- </uni-col>
- <!-- 国家 -->
- <uni-col :xs="24" :sm="24" :md="12" :lg="8" :xl="8" v-if="countryOptions.length">
- <uni-forms-item :label="t('ImproveImmediately.Label.Nationality')">
- <cwg-combox :disabled="isInfo" :clearable="false" :filterable="true"
- v-model:value="formData.nationality" :options="countryOptions"
- :placeholder="t('placeholder.choose')" @change="changeCountry" />
- </uni-forms-item>
- </uni-col>
- <!-- 姓 -->
- <uni-col :xs="24" :sm="24" :md="12" :lg="8" :xl="8">
- <uni-forms-item :label="formData.customType == 2
- ? t('ImproveImmediately.Label.CorporationLastName')
- : t('ImproveImmediately.Label.LastName')">
- <uni-easyinput :disabled="isInfo" :clearable="false" v-model="formData.lastName"
- :placeholder="t('placeholder.input')" />
- </uni-forms-item>
- </uni-col>
- <!-- 名 -->
- <uni-col :xs="24" :sm="24" :md="12" :lg="8" :xl="8">
- <uni-forms-item :label="formData.customType == 2
- ? t('ImproveImmediately.Label.CorporationName')
- : t('ImproveImmediately.Label.Name')">
- <uni-easyinput :disabled="isInfo" :clearable="false" v-model="formData.firstName"
- :placeholder="t('placeholder.input')" />
- </uni-forms-item>
- </uni-col>
- <!-- 法人中间名 -->
- <uni-col :xs="24" :sm="24" :md="12" :lg="8" :xl="8" v-if="formData.lang == 'en'">
- <uni-forms-item :label="formData.customType == 2
- ? t('ImproveImmediately.Label.CorporationMName')
- : t('placeholder.middle')">
- <uni-easyinput :disabled="isInfo" :clearable="false" v-model="formData.middle"
- :placeholder="t('placeholder.Not')" />
- </uni-forms-item>
- </uni-col>
- <!-- 拼音 -->
- <uni-col :xs="24" :sm="24" :md="12" :lg="8" :xl="8" v-if="formData.lang == 'cn'">
- <uni-forms-item :label="t('ImproveImmediately.Label.NamePinYin')">
- <uni-easyinput :disabled="isInfo" :clearable="false" v-model="formData.nameEn"
- :placeholder="t('placeholder.input')" />
- </uni-forms-item>
- </uni-col>
- <!-- 手机号 -->
- <uni-col :xs="24" :sm="24" :md="12" :lg="8" :xl="8">
- <uni-forms-item :label="t('PersonalManagement.Label.Phone')">
- <view style="display: flex; gap: 8px; width: 100%;">
- <view style="flex: 0 0 100px;">
- <uni-easyinput :disabled="isInfo" :clearable="false" v-model="formData.areaCode"
- :placeholder="t('placeholder.areaCode')" />
- </view>
- <view style="flex: 1;">
- <uni-easyinput :disabled="isInfo" :clearable="false" v-model="formData.phone"
- :placeholder="t('placeholder.input')" />
- </view>
- </view>
- </uni-forms-item>
- </uni-col>
- <!-- 邮箱 -->
- <uni-col :xs="24" :sm="24" :md="12" :lg="8" :xl="8">
- <uni-forms-item :label="t('PersonalManagement.Label.Email')">
- <uni-easyinput :disabled="isInfo" :clearable="false" v-model="formData.email"
- :placeholder="t('placeholder.input')" />
- </uni-forms-item>
- </uni-col>
- <!-- 证件号 -->
- <uni-col :xs="24" :sm="24" :md="12" :lg="8" :xl="8">
- <uni-forms-item :label="t('ImproveImmediately.Label.IdentityID')">
- <uni-easyinput :disabled="isInfo" :clearable="false" v-model="formData.identity"
- :placeholder="t('placeholder.input')" />
- </uni-forms-item>
- </uni-col>
- <!-- 生日 -->
- <!-- <uni-col :xs="24" :sm="24" :md="12" :lg="8" :xl="8">-->
- <!-- <uni-forms-item :label="t('ImproveImmediately.Label.Birthday')">-->
- <!-- <uni-datetime-picker :disabled="isInfo" :clear-icon="false" type="date" return-type="timestamp"-->
- <!-- v-model="formData.birth" :placeholder="t('placeholder.choose')" />-->
- <!-- </uni-forms-item>-->
- <!-- </uni-col>-->
- <!-- 国家/地区 -->
- <uni-col :xs="24" :sm="24" :md="12" :lg="8" :xl="8" v-if="countryOptions.length">
- <uni-forms-item :label="t('ImproveImmediately.Label.CountryRegionOfResidence')">
- <cwg-combox :disabled="isInfo" :clearable="false" :filterable="true" v-model:value="formData.country"
- :options="countryOptions" :placeholder="t('placeholder.choose')" />
- </uni-forms-item>
- </uni-col>
- <!-- 省份/州 -->
- <uni-col :xs="24" :sm="24" :md="12" :lg="8" :xl="8" v-if="stateOptions.length">
- <uni-forms-item :label="t('ImproveImmediately.Label.ProvinceRegion')">
- <cwg-combox :disabled="isInfo" :clearable="false" :filterable="true" v-model:value="formData.state"
- :options="stateOptions" :placeholder="t('placeholder.choose')" @change="onStateChange" />
- </uni-forms-item>
- </uni-col>
- <!-- 城市 -->
- <uni-col :xs="24" :sm="24" :md="12" :lg="8" :xl="8" v-if="cityOptions.length">
- <uni-forms-item :label="t('ImproveImmediately.Label.City')">
- <cwg-combox :disabled="isInfo" :clearable="false" :filterable="true" v-model:value="formData.city"
- :options="cityOptions" :placeholder="t('placeholder.choose')" />
- </uni-forms-item>
- </uni-col>
- <uni-col :xs="24" :sm="24" :md="12" :lg="8" :xl="8">
- <uni-forms-item :label="t('ImproveImmediately.Label.ZipCode')">
- <uni-easyinput :disabled="isInfo" :clearable="false" v-model="formData.zipCode"
- :placeholder="t('placeholder.input')" />
- </uni-forms-item>
- </uni-col>
- <uni-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
- <uni-forms-item :label="t('ImproveImmediately.Label.DetailedAddress')">
- <uni-easyinput :disabled="isInfo" :clearable="false" v-model="formData.addressLines1"
- :placeholder="t('placeholder.input')" />
- </uni-forms-item>
- </uni-col>
- <uni-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
- <uni-forms-item :label="t('ImproveImmediately.Label.DetailedAddressStandby')">
- <uni-easyinput :disabled="isInfo" :clearable="false" v-model="formData.addressLines2"
- :placeholder="t('placeholder.input')" />
- </uni-forms-item>
- </uni-col>
- </uni-row>
- </uni-forms>
- </uni-col>
- </uni-row>
- </view>
- </view>
- </template>
- <script setup lang="ts">
- import { ref, computed, onMounted } from 'vue'
- import { useI18n } from 'vue-i18n'
- import { personalApi } from '@/service/personal'
- import { Patterns, Validators } from '@/utils/validators'
- const { t, locale } = useI18n()
- interface PersonalInfo {
- merchantOrderNo?: string;
- cardTypeId?: string;
- areaCode?: string;
- mobile?: string;
- email?: string;
- firstName?: string;
- lastName?: string;
- middle?: string;
- birthday?: string;
- nationality?: string;
- country?: string;
- state?: string;
- city?: string;
- town?: string;
- address?: string;
- postCode?: string;
- gender?: number;
- occupation?: string;
- annualSalary?: string;
- accountPurpose?: string;
- expectedMonthlyVolume?: string;
- idType?: number;
- idNumber?: string;
- identity?: string;
- ssn?: string;
- issueDate?: string;
- idNoExpiryDate?: string;
- idFrontUrl?: string;
- idBackUrl?: string;
- idHoldUrl?: string;
- ipAddress?: string;
- cId?: string;
- customId?: string;
- customType?: number;
- companyName?: string;
- nameEn?: string;
- birth?: string;
- addressLines1?: string;
- addressLines2?: string;
- zipCode?: string;
- photoStatus?: string;
- kycStatus?: string;
- }
- const props = defineProps<{
- modelValue: PersonalInfo;
- isReadonly?: boolean;
- }>()
- const emit = defineEmits<{
- 'update:modelValue': [value: PersonalInfo];
- 'cancel': [];
- 'next': [];
- }>()
- const loading = ref(false)
- const formData = computed({
- get: () => props.modelValue,
- set: (val) => emit('update:modelValue', val),
- })
- const baseForm = ref<any>(null)
- const sexOptions = ref([
- { text: t('PersonalManagement.Label.Men'), value: 1 },
- { text: t('PersonalManagement.Label.Women'), value: 2 },
- ])
- const customTypeOptions = ref([
- { text: t('ImproveImmediately.Label.CustomerType1'), value: 1 },
- { text: t('ImproveImmediately.Label.CustomerType2'), value: 2 },
- ])
- const idTypeOptions = ref([
- { text: t('ImproveImmediately.Label.IDCard'), value: 2 },
- { text: t('ImproveImmediately.Label.Passport'), value: 3 },
- ])
- // 验证函数
- function validateName(a: any, b?: any, c?: any) {
- const reg = /^[A-Z\s]+$/i
- 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()
- }
- 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
- }
- function validateBirthday(a: any, b?: any, c?: any) {
- 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()
- }
- 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) {
- 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()
- }
- 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')],
- postCode: [
- Validators.required(t('card.vaildate.v8')),
- Validators.pattern(Patterns.postcode, t('card.New.n2')),
- ],
- }
- // 国家省份城市数据
- const countryList = ref<Array<any>>([])
- const stateList = ref<Array<any>>([])
- const cityList = ref<Array<any>>([])
- const phoneCodes = ref<Array<{ text: string; value: string }>>([])
- const isInfo = ref<boolean>(true)
- const isZh = computed(() => ['cn', 'zh', 'zhHant'].includes(locale.value))
- const isCountryCN = computed(() => ['CN', 'CNX', 'CNA', 'CNT'].includes(formData.value.country || ''))
- const getLangName = (item: any) => (isZh.value ? item.name : item.enName)
- const createOptions = (list: any[], valueKey = 'code') => {
- return list.map((item) => ({
- text: getLangName(item),
- value: valueKey === 'name' ? getLangName(item) : item[valueKey],
- id: item.id,
- }))
- }
- const countryOptions = computed(() => createOptions(countryList.value, 'code'))
- const stateOptions = computed(() => createOptions(stateList.value, 'name'))
- const cityOptions = computed(() => createOptions(cityList.value, 'name'))
- const getCountry = async () => {
- const res = await personalApi.Country({})
- if (res.code === 200) {
- countryList.value = res.data
- phoneCodes.value = res.data.map((item: any) => ({
- text: `+ ${item.callingCode}`,
- value: item.callingCode,
- }))
- if (formData.value.country) {
- const country = countryList.value.find((i) => i.code === formData.value.country)
- if (country) {
- await getState(country.id)
- }
- }
- }
- }
- const fetchRegion = async (pid?: number) => {
- const res = await personalApi.Country({ pid })
- if (res.code !== 200) {
- return []
- }
- return res.data || []
- }
- const getState = async (pid: number) => {
- stateList.value = await fetchRegion(pid)
- if (formData.value.state) {
- const item = stateList.value.find(
- (i) => i.enName === formData.value.state || i.name === formData.value.state,
- )
- if (item) {
- await getCity(item.id)
- }
- }
- }
- const getCity = async (pid: number) => {
- cityList.value = await fetchRegion(pid)
- }
- const changeCountry = async (val: any) => {
- formData.value.state = ''
- formData.value.city = ''
- const item = countryOptions.value.find((i: any) => i.value === val)
- if (item) {
- await getState(item.id)
- }
- }
- const onStateChange = async (val: any) => {
- formData.value.city = ''
- const item = stateOptions.value.find((i: any) => i.value === val)
- if (item) {
- await getCity(item.id)
- }
- }
- function handleChange(value: any) {
- emit('update:modelValue', { ...formData.value, [value.key]: value.value })
- }
- const handleEditProfile = () => {
- // 头像上传逻辑
- }
- const handleCancel = () => {
- emit('cancel')
- }
- const handleNext = () => {
- emit('next')
- }
- onMounted(async () => {
- loading.value = true
- await getCountry()
- loading.value = false
- })
- </script>
- <style scoped lang="scss">
- @import "@/uni.scss";
- .personal-info-tab {
- .user-form {
- margin-top: px2rpx(30);
- .demo-uni-row {
- margin-bottom: px2rpx(16);
- :deep(.form-label) {
- margin: 0 !important;
- }
- :deep(.form-input) {
- border: none !important;
- }
- .avatar-section {
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: px2rpx(12);
- padding: px2rpx(16);
- .cwg-upload {
- width: px2rpx(120);
- height: px2rpx(120);
- border-radius: 50%;
- margin: 0 auto;
- }
- .text {
- font-size: px2rpx(20);
- font-weight: 700;
- color: var(--bs-heading-color);
- }
- .btn-primary {
- min-width: px2rpx(120);
- background-color: var(--bs-heading-color);
- color: white;
- padding: px2rpx(12);
- border: none;
- font-size: px2rpx(14);
- text-align: center;
- cursor: pointer;
- display: flex;
- align-items: center;
- justify-content: center;
- gap: px2rpx(8);
- &:active {
- background-color: #cf1322;
- ;
- }
- }
- .cid {
- color: var(--color-error);
- font-size: px2rpx(12);
- margin-top: px2rpx(4);
- }
- }
- }
- }
- .btns {
- display: flex;
- justify-content: flex-end;
- gap: 30px;
- margin-top: 30px;
- .btn-primary {
- min-width: px2rpx(120);
- background-color: var(--bs-heading-color);
- color: white;
- padding: 12px;
- border-radius: 8px;
- border: none;
- font-size: 14px;
- text-align: center;
- cursor: pointer;
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 8px;
- &:active {
- background-color: #cf1322;
- ;
- }
- }
- }
- }
- :deep(.uni-row1) {
- .uni-col {
- padding: 0 10px !important;
- }
- .base-info-form>span {
- display: contents;
- }
- .uni-forms-item {
- min-height: px2rpx(79);
- margin-bottom: px2rpx(10);
- }
- .uni-select,
- .uni-combox,
- .uni-easyinput__content,
- .uni-date-editor--x {
- background-color: var(--bs-secondary-bg)!important;
- //background-color: var(--color-zinc-100) !important;
- }
- .uni-date-x {
- border: none !important;
- //background-color: rgba(195, 195, 195, 0) !important;
- }
- }
- </style>
|