apply.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474
  1. <template>
  2. <cwg-page-wrapper>
  3. <view class="page page-shadow">
  4. <u-form ref="formRef" :rules="rules" :model="formData" class="kyc-form">
  5. <cwg-input v-model:value="infoForm.lastName" fkey="lastName" :required="true" :label="t('card.Form.f4')"
  6. rulesKey="lastName" :readonly="true" :disabled="true" @change="handleChange" />
  7. <cwg-input v-model:value="infoForm.firstName" fkey="firstName" :required="true"
  8. :label="t('card.Form.f5')" rulesKey="firstName" :readonly="true" :disabled="true"
  9. @change="handleChange" />
  10. <cwg-input v-model:value="infoForm.email" fkey="email" :label="t('card.Form.f3')" :required="true"
  11. rulesKey="email" :readonly="true" :disabled="true" @change="handleChange" />
  12. <cwg-input v-model:value="infoForm.birthday" :required="true" type="date" fkey="birthday"
  13. :label="t('card.Form.f6')" rulesKey="birthday" :readonly="true" :disabled="true"
  14. @change="handleChange" />
  15. <cwg-input v-model:value="infoForm.gender" fkey="gender" type="select" :required="true"
  16. :columns="sexOptions" :label="t('card.Form.f8')" rulesKey="gender" :readonly="true" :disabled="true"
  17. @change="handleChange" />
  18. <cwg-input v-model:value="infoForm.mailingAreaCode" fkey="mailingAreaCode" type="select"
  19. :required="true" :columns="phoneCodes" :label="t('card.Form.f1')" rulesKey="mailingAreaCode"
  20. @change="handleChange" />
  21. <cwg-input v-model:value="infoForm.mailingMobile" fkey="mailingMobile" :required="true"
  22. :label="t('card.Form.f2')" rulesKey="mailingMobile" :readonly="isReadonly" :disabled="isReadonly"
  23. @change="handleChange" />
  24. <cwg-input v-model:value="infoForm.mailingCountry" fkey="mailingCountry" type="select" :required="true"
  25. :columns="countryOptions" :label="t('card.New1.d8')" rulesKey="mailingCountry"
  26. @change="handleChange" />
  27. <cwg-input v-model:value="infoForm.mailingTown" fkey="mailingTown" type="select" :required="true"
  28. :columns="cityOptions" :label="t('card.New1.d9')" rulesKey="mailingTown" @change="handleChange" />
  29. <cwg-input v-model:value="infoForm.addressCn" fkey="addressCn" :required="true"
  30. :label="t('card.New1.d10')" rulesKey="addressCn" :readonly="isReadonly" :disabled="isReadonly"
  31. @change="handleChange" />
  32. <cwg-input v-model:value="infoForm.address" fkey="address" :required="true" :label="t('card.New1.d11')"
  33. rulesKey="address" :readonly="isReadonly" :disabled="isReadonly" @change="handleChange" />
  34. <cwg-input v-model:value="infoForm.mailingPostCode" fkey="mailingPostCode" :required="true"
  35. :label="t('card.New1.d12')" rulesKey="mailingPostCode" :readonly="isReadonly" :disabled="isReadonly"
  36. @change="handleChange" />
  37. <cwg-input v-model:value="infoForm.login" fkey="login" type="select" :required="true"
  38. :columns="typesOptions" :label="t('card.New.n10')" rulesKey="login" @change="handleChange" />
  39. <view class="fixed-btn">
  40. <view class="cwg-button">
  41. <u-button type="primary" block @click="infoSubmit">{{ t('card.Btn.Submit') }}</u-button>
  42. </view>
  43. </view>
  44. </u-form>
  45. <card-websdk-link ref="cardWebsdkLinkRef" />
  46. </view>
  47. </cwg-page-wrapper>
  48. </template>
  49. <script setup lang="ts">
  50. import { ref, computed, onMounted } from 'vue'
  51. import { pinyin } from 'pinyin-pro'
  52. import { useI18n } from 'vue-i18n'
  53. import { onLoad } from '@dcloudio/uni-app'
  54. import useRouter from '@/hooks/useRouter'
  55. import useRoute from '@/hooks/useRoute'
  56. import { ucardApi } from '@/api/ucard'
  57. import { userApi } from '@/api/user'
  58. import useUserStore from '@/stores/use-user-store'
  59. import { Patterns, Validators } from '@/utils/validators'
  60. import CardWebsdkLink from '@/components/card-websdkLink.vue'
  61. const cardWebsdkLinkRef = ref<InstanceType<typeof CardWebsdkLink> | null>(null)
  62. const userStore = useUserStore()
  63. const userInfo = computed(() => userStore.userInfo)
  64. const { t } = useI18n()
  65. const router = useRouter()
  66. const route = useRoute()
  67. const formRef = ref()
  68. const reductionData = ref(0)
  69. const isViews = ref(false)
  70. // 路由参数
  71. const type = ref<string>('')
  72. const cardTypeId = ref<string>('')
  73. onLoad((options) => {
  74. type.value = options?.type || ''
  75. cardTypeId.value = options?.cardTypeId || ''
  76. })
  77. // 国家选项
  78. const countryOptions = ref<Array<{ text: string, value: string }>>([])
  79. const cityOptions = ref<Array<{ text: string, value: string }>>([])
  80. const phoneCodes = ref<Array<{ text: string, value: string }>>([])
  81. const isReadonly = computed(() => infoForm.value?.authStatus == 1)
  82. const sexOptions = ref([
  83. { text: t('card.Form.v1'), value: 'M' },
  84. { text: t('card.Form.v2'), value: 'F' },
  85. ])
  86. const typesOptions = ref<Array<any>>([])
  87. const idTypeOptions = ref([
  88. { text: t('card.Form.v3'), value: 'EUROPEAN_ID' },
  89. { text: t('card.Form.v4'), value: 'PASSPORT' },
  90. ])
  91. // 表单验证规则
  92. const rules = {
  93. mailingAreaCode: [Validators.required(t('card.vaildate.v1'))],
  94. mailingMobile: [Validators.required(t('card.vaildate.v2')), Validators.pattern(Patterns.mobile, t('card.vaildate.v44'))],
  95. mailingCountry: [Validators.required(t('card.vaildate.v6'), 'change')],
  96. mailingTown: [Validators.required(t('card.vaildate.v7'), 'change')],
  97. login: [Validators.required(t('vaildate.select.empty') + t('card.New.n10'), 'change')],
  98. address: [
  99. Validators.required(t('card.vaildate.v27')),
  100. Validators.custom((val: any) => {
  101. const v = String(val ?? '').trim()
  102. if (v.length < 2 || v.length > 40)
  103. return t('card.New.n1')
  104. return Patterns.address.test(v) ? true : t('card.New.n1')
  105. }),
  106. ],
  107. addressCn: [Validators.required(t('card.vaildate.v27')), Validators.pattern(Patterns.addressCn, t('card.vaildate.v27'))],
  108. mailingPostCode: [Validators.required(t('card.vaildate.v8')), Validators.pattern(Patterns.postcode, t('card.New.n2'))],
  109. }
  110. const infoForm = ref({
  111. gender: undefined,
  112. mailingMobile: undefined,
  113. mailingAreaCode: undefined,
  114. mailingCountry: undefined,
  115. mailingTown: undefined,
  116. addressCn: undefined,
  117. address: undefined,
  118. mailingPostCode: undefined,
  119. login: undefined,
  120. email: undefined,
  121. lastName: undefined,
  122. firstName: undefined,
  123. birthday: undefined,
  124. })
  125. const formData = ref<typeof infoForm.value>({} as any)
  126. const formDatas = ref<typeof infoForm.value>({} as any)
  127. const isShow = ref(false)
  128. async function infoSubmit() {
  129. try {
  130. const requiredFields = [
  131. 'mailingMobile',
  132. 'mailingAreaCode',
  133. 'mailingCountry',
  134. 'mailingTown',
  135. 'addressCn',
  136. 'address',
  137. 'mailingPostCode',
  138. 'login',
  139. ] as const
  140. await formRef.value?.validate(requiredFields)
  141. }
  142. catch (error: any) {
  143. if (Array.isArray(error) && error.length > 0) {
  144. uni.showToast({
  145. title: error[0].message,
  146. icon: 'none'
  147. })
  148. }
  149. else {
  150. uni.showToast({
  151. title: t('card.New.errer'),
  152. icon: 'none'
  153. })
  154. }
  155. return
  156. }
  157. const res = await ucardApi.ucardApply({ ...formData.value, cardTypeId: cardTypeId.value })
  158. if (res.code === 200) {
  159. uni.showToast({
  160. title: t('card.Msg.m1'),
  161. icon: 'success'
  162. })
  163. // 成功后打开 WebSDK 弹窗
  164. if (cardWebsdkLinkRef.value) {
  165. cardWebsdkLinkRef.value.getWebsdkLink(
  166. (res.data as any)?.cardId ||
  167. (formData.value as any)?.cId ||
  168. cardTypeId.value
  169. )
  170. }
  171. goCardPage()
  172. }
  173. else {
  174. uni.showToast({
  175. title: res.msg || t('common.error'),
  176. icon: 'none'
  177. })
  178. }
  179. }
  180. async function handleChange(value: any) {
  181. formData.value = { ...formData.value, [value.key]: value.value }
  182. if (value.key === 'login') {
  183. if (value.value == -1) {
  184. formData.value.discountType = 2
  185. }
  186. else {
  187. const res = typesOptions.value.find((item: any) => item.login == value.value)
  188. formData.value.discountType = 1
  189. formData.value.platform = res?.platform
  190. }
  191. }
  192. if (value.key === 'addressCn') {
  193. const containsChinese = (str: string) => /[\u4E00-\u9FA5]/.test(str)
  194. if (containsChinese(value.value)) {
  195. formData.value.address = await formatText(value.value)
  196. infoForm.value.address = await formatText(value.value)
  197. }
  198. else {
  199. formData.value.address = value.value
  200. infoForm.value.address = value.value
  201. }
  202. }
  203. if (value.key === 'mailingCountry') {
  204. formData.value.mailingCountry = value.value
  205. await getCityListForSelect(value.value)
  206. formData.value.mailingTown = ''
  207. infoForm.value.mailingTown = ''
  208. }
  209. }
  210. function formatText(input: string) {
  211. const chinesePattern = /[\u4E00-\u9FA5]+/g
  212. const formattedText = input.replace(chinesePattern, (match) => {
  213. return ` ${pinyin(match, { toneType: 'none', type: 'capitalize' })} `
  214. })
  215. return formattedText
  216. }
  217. async function reductionNum() {
  218. const { cId } = formData.value
  219. if (!cId) return
  220. const res = await ucardApi.reductionNum({ cId })
  221. if (res.code === 200) {
  222. reductionData.value = res.data
  223. }
  224. }
  225. async function accountDropdown() {
  226. const res = await userApi.accountDropdown()
  227. if (res.code === 200) {
  228. const data = flatData(res.data)
  229. const loginOptions = data.map((item: any) => {
  230. item.discountType = 1
  231. item.text
  232. = `${item.login} - ${groupTypeName(item.type)} - ${t('kyc.AvailableBalance')}${groupCurrency(item.currency)}${item.balance}`
  233. item.value = item.login
  234. item.disabled = item.closeFunctions?.includes('1')
  235. if (item.balance == 0) {
  236. item.disabled = true
  237. }
  238. return item
  239. })
  240. loginOptions.push({
  241. discountType: 2,
  242. text: t('kyc.AvailableBalance1') + reductionData.value,
  243. login: -1,
  244. value: -1,
  245. disabled: reductionData.value == 0,
  246. })
  247. typesOptions.value = loginOptions
  248. }
  249. }
  250. function groupTypeName(type: string) {
  251. if (type == '1') {
  252. return t('AccountType.ClassicAccount')
  253. }
  254. else if (type == '2') {
  255. return t('AccountType.SeniorAccount')
  256. }
  257. else if (type == '5') {
  258. return t('AccountType.SpeedAccount')
  259. }
  260. else if (type == '6') {
  261. return t('AccountType.SpeedAccount')
  262. }
  263. else if (type == '7') {
  264. return t('AccountType.StandardAccount')
  265. }
  266. else if (type == '8') {
  267. return t('AccountType.CentAccount')
  268. }
  269. else if (type == '3') {
  270. return t('AccountType.AgencyAccount')
  271. }
  272. return ''
  273. }
  274. function groupCurrency(type: string) {
  275. if (type == 'GBP') {
  276. return ': £'
  277. }
  278. else if (type == 'USD') {
  279. return ': $'
  280. }
  281. else if (type == 'EUR') {
  282. return ': €'
  283. }
  284. else if (type == 'USC') {
  285. return ': ¢'
  286. }
  287. else {
  288. return ': $'
  289. }
  290. }
  291. function flatData(data: any[]) {
  292. return data.flatMap((card) => {
  293. if (!card.rechargeCurrencyInfoList || card.rechargeCurrencyInfoList.length === 0) {
  294. return [
  295. {
  296. ...card,
  297. currency: null,
  298. rechargeFeeRate: null,
  299. rechargeFixedFee: null,
  300. rechargeMaxQuota: null,
  301. rechargeMinQuota: null,
  302. },
  303. ]
  304. }
  305. return card.rechargeCurrencyInfoList.map((recharge: any) => ({
  306. ...card,
  307. ...recharge,
  308. }))
  309. })
  310. }
  311. // 获取国家列表
  312. async function getCountryListForSelect() {
  313. try {
  314. const res = await ucardApi.ucardCountryCity({})
  315. if (res.code === 200 || res.code === 0) {
  316. countryOptions.value = res.data.map((item: any) => ({
  317. text: item.enName,
  318. value: item.code,
  319. }))
  320. phoneCodes.value = res.data
  321. .map((item: any) => ({
  322. text: `${item.enName} ${item.areaCode}`,
  323. value: item.areaCode,
  324. }))
  325. .filter((item: any) => item.value !== null && item.value !== '-')
  326. }
  327. }
  328. catch (error) {
  329. console.error('获取国家列表失败:', error)
  330. countryOptions.value = []
  331. }
  332. }
  333. // 获取城市列表
  334. async function getCityListForSelect(countryCode: string) {
  335. try {
  336. const res = await ucardApi.ucardCountryCity({ code: countryCode })
  337. if (res.code === 200 || res.code === 0) {
  338. const cityList = res.data.map((item: any) => ({
  339. text: item.enName,
  340. value: item.code,
  341. }))
  342. cityOptions.value = cityList
  343. }
  344. }
  345. catch (error) {
  346. console.error('获取城市列表失败:', error)
  347. cityOptions.value = []
  348. }
  349. }
  350. function goCardPage() {
  351. router.push({
  352. path: '/pages/card/index',
  353. })
  354. }
  355. onMounted(async () => {
  356. console.log(userInfo.value, 1988)
  357. // 第一步:先回显基本信息(不依赖下拉数据的字段)
  358. const userData = userInfo.value as any
  359. infoForm.value = {
  360. lastName: userData.lastName,
  361. firstName: userData.firstName,
  362. email: userData.email,
  363. birthday: userData.birthday,
  364. gender: userData.gender,
  365. }
  366. formData.value = {
  367. lastName: userData.lastName,
  368. firstName: userData.firstName,
  369. email: userData.email,
  370. birthday: userData.birthday,
  371. gender: userData.gender,
  372. }
  373. // 第二步:加载下拉数据
  374. await getCountryListForSelect()
  375. // 第三步:等待下拉数据加载完成后,再回显国家和城市等字段
  376. if (userData.mailingCountry) {
  377. await getCityListForSelect(userData.mailingCountry)
  378. // 城市列表加载完成后再回显
  379. infoForm.value.mailingCountry = userData.mailingCountry
  380. infoForm.value.mailingTown = userData.mailingTown
  381. formData.value.mailingCountry = userData.mailingCountry
  382. formData.value.mailingTown = userData.mailingTown
  383. }
  384. // 回显其他地址相关字段
  385. infoForm.value.mailingAreaCode = userData.mailingAreaCode
  386. infoForm.value.mailingMobile = userData.mailingMobile
  387. infoForm.value.addressCn = userData.addressCn
  388. infoForm.value.address = userData.address
  389. infoForm.value.mailingPostCode = userData.mailingPostCode
  390. formData.value.mailingAreaCode = userData.mailingAreaCode
  391. formData.value.mailingMobile = userData.mailingMobile
  392. formData.value.addressCn = userData.addressCn
  393. formData.value.address = userData.address
  394. formData.value.mailingPostCode = userData.mailingPostCode
  395. // 加载其他数据
  396. await reductionNum()
  397. await accountDropdown()
  398. // 最后回显登录账户字段(需要等待 accountDropdown 完成)
  399. if (userData.login) {
  400. infoForm.value.login = userData.login
  401. formData.value.login = userData.login
  402. }
  403. })
  404. </script>
  405. <style scoped lang="scss">
  406. @import "@/uni.scss";
  407. .page {
  408. // padding: px2rpx(12) px2rpx(16);
  409. padding-bottom: px2rpx(100);
  410. }
  411. .pointer-none {
  412. pointer-events: none;
  413. }
  414. .f {
  415. display: flex;
  416. align-items: flex-end;
  417. gap: px2rpx(12);
  418. .l {
  419. flex: 1;
  420. }
  421. .r {
  422. width: px2rpx(273);
  423. }
  424. }
  425. .fixed-btn {
  426. position: fixed;
  427. bottom: 0;
  428. left: 0;
  429. right: 0;
  430. padding: px2rpx(16);
  431. background: #fff;
  432. box-shadow: 0 px2rpx(-2) px2rpx(10) rgba(0, 0, 0, 0.1);
  433. z-index: 100;
  434. }
  435. </style>