info.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  1. <template>
  2. <cwg-page-wrapper>
  3. <view class="info-card">
  4. <view class="content-title" v-if="current != 3 && current != 4">
  5. <view>{{ tabs[current - 1].name }}</view>
  6. <!-- <view class="content-title-btns">-->
  7. <!-- <view v-if="current == 3 && !isSHowIdentity" class="btn-primary" @click="infoSubmit()">-->
  8. <!-- <cwg-icon icon="crm-plus" :size="16" color="#fff" />-->
  9. <!-- <text v-t="'PersonalManagement.Title.ProofOfIdentity'"></text>-->
  10. <!-- </view>-->
  11. <!-- <view v-if="current == 3 && !isSHowAddress" class="btn-primary" @click="cancle()">-->
  12. <!-- <cwg-icon icon="crm-plus" :size="16" color="#fff" />-->
  13. <!-- <text v-t="'PersonalManagement.Title.ProofOfAddress'"></text>-->
  14. <!-- </view>-->
  15. <!-- <view v-if="current == 3" class="btn-primary" @click="cancle()">-->
  16. <!-- <cwg-icon icon="crm-plus" :size="16" color="#fff" />-->
  17. <!-- <text v-t="'PersonalManagement.Title.AttachedFile'"></text>-->
  18. <!-- </view>-->
  19. <!-- </view>-->
  20. </view>
  21. <view v-if="current == 1">
  22. <personal-info-tab v-model="PersonalInformation" :is-readonly="isReadonly" @cancel="cancle"
  23. @next="next(2)" />
  24. </view>
  25. <view v-if="current == 2">
  26. <bank-info-tab />
  27. </view>
  28. <view v-if="current == 3">
  29. <file-management-tab />
  30. </view>
  31. <view v-if="current == 4">
  32. <security-center-tab />
  33. </view>
  34. </view>
  35. <!-- <view class="form-tab"></view> -->
  36. </cwg-page-wrapper>
  37. <card-websdk-link ref="cardWebsdkLinkRef" />
  38. </template>
  39. <script setup lang="ts">
  40. import { ref, onMounted, watch, computed } from "vue";
  41. import { pinyin } from "pinyin-pro";
  42. import { useI18n } from "vue-i18n";
  43. import { onLoad } from '@dcloudio/uni-app'
  44. import { userToken } from "@/composables/config";
  45. import { ucardApi } from "@/api/ucard";
  46. import { personalApi } from "@/service/personal";
  47. import { userApi } from "@/api/user";
  48. import useUserStore from "@/stores/use-user-store";
  49. import useIdTypeOptions from "@/composables/useIdTypeOptions";
  50. import CardWebsdkLink from "@/components/card-websdkLink.vue";
  51. import PersonalInfoTab from "./components/PersonalInfoTab.vue";
  52. import FileManagementTab from "./components/FileManagementTab.vue";
  53. import BankInfoTab from "./components/BankInfoTab.vue";
  54. import SecurityCenterTab from "./components/SecurityCenterTab.vue";
  55. import useRouter from "@/hooks/useRouter";
  56. const notCountry = [
  57. "AF",
  58. "AI",
  59. "AG",
  60. "BS",
  61. "BY",
  62. "BZ",
  63. "BA",
  64. "BI",
  65. "CF",
  66. "CD",
  67. "CU",
  68. "ET",
  69. "FJ",
  70. "PS",
  71. "GN",
  72. "GW",
  73. "HT",
  74. "IR",
  75. "IQ",
  76. "LB",
  77. "LY",
  78. "ML",
  79. "MM",
  80. "NI",
  81. "KP",
  82. "PW",
  83. "RU",
  84. "SO",
  85. "SS",
  86. "SD",
  87. "SY",
  88. "UA",
  89. "US",
  90. "VE",
  91. "YE",
  92. "ZW",
  93. ]
  94. const router = useRouter();
  95. const userStore = useUserStore();
  96. const userInfo = computed(() => userStore.userInfo);
  97. const current = ref(0);
  98. onLoad((options) => {
  99. current.value = options?.type;
  100. });
  101. const tabs = computed(() => [
  102. { id: 1, name: t('PersonalManagement.Title.PersonalInformation'), icon: 'crm-circle-user' },
  103. { id: 2, name: t('PersonalManagement.Title.BankInformation'), icon: 'crm-building-columns' },
  104. { id: 3, name: t('PersonalManagement.Title.FileManagement'), icon: 'crm-file' },
  105. { id: 4, name: t('PersonalManagement.Title.SecurityCenter'), icon: 'crm-lock' }
  106. ]);
  107. const changeTab = (index) => {
  108. router.push(`/pages/mine/info?type=${index + 1}`);
  109. }
  110. const { t, locale } = useI18n();
  111. const cardWebsdkLinkRef = ref<InstanceType<typeof CardWebsdkLink> | null>(null);
  112. const PersonalInformation = ref({
  113. merchantOrderNo: undefined,
  114. cardTypeId: undefined,
  115. areaCode: undefined,
  116. mobile: undefined,
  117. email: undefined,
  118. firstName: undefined,
  119. lastName: undefined,
  120. birthday: undefined,
  121. nationality: undefined,
  122. country: undefined,
  123. town: undefined,
  124. address: undefined,
  125. postCode: undefined,
  126. gender: undefined,
  127. occupation: undefined,
  128. annualSalary: undefined,
  129. accountPurpose: undefined,
  130. expectedMonthlyVolume: undefined,
  131. idType: undefined,
  132. idNumber: undefined,
  133. ssn: undefined,
  134. issueDate: undefined,
  135. idNoExpiryDate: undefined,
  136. idFrontUrl: undefined,
  137. idBackUrl: undefined,
  138. idHoldUrl: undefined,
  139. ipAddress: undefined,
  140. cId: undefined,
  141. customId: undefined,
  142. photoStatus: '1',
  143. kycStatus: '1'
  144. });
  145. const isUpdate = ref(false);
  146. const isAuthInfo = ref(true);
  147. const dialogCheck = ref(false);
  148. const dialogCheck1 = ref(false);
  149. const isApprove = computed(() => [2, 4].includes(userInfo.value.approveStatus))
  150. const countryList = ref<any[]>([]);
  151. const getCountry = async () => {
  152. const res = await personalApi.Country({});
  153. if (res.code === 200) {
  154. countryList.value = res.data || [];
  155. }
  156. };
  157. const cancle = async () => {
  158. if (!isApprove.value) {
  159. dialogCheck.value = true;
  160. dialogCheck1.value = true;
  161. } else {
  162. router.push({ path: "/customer/index" });
  163. }
  164. }
  165. const currentUploadCard = computed(() => {
  166. if (!PersonalInformation.value.nationality || !countryList.value.length) {
  167. return 0;
  168. }
  169. const selectedCountry = countryList.value.find(
  170. (item) => item.code === PersonalInformation.value.nationality
  171. );
  172. return selectedCountry ? (selectedCountry.uploadCard || 0) : 0;
  173. })
  174. const next = async (index) => {
  175. // 从 step 2 跳转到 step 3 时,应该直接跳转,不做验证
  176. // 只有在 step 3 点击下一步时,才检查是否需要跳过到 step 4
  177. if (current.value == 3 && index == 4 && PersonalInformation.value.uploadImage == 0) {
  178. // 如果不需要上传图片,跳过 step 3 直接到 step 4
  179. index = 4;
  180. }
  181. // 从 step 3 跳转到 step 4 时,需要验证身份证明相关字段
  182. if (index == 4) {
  183. // 根据当前选中国籍的 uploadCard 值判断是否需要验证证件照
  184. if (currentUploadCard.value === 1) {
  185. if (!PersonalInformation.value.cardType) {
  186. uni.showToast({ title: t('vaildate.CardType.empty'), icon: 'none' });
  187. return;
  188. }
  189. if (!PersonalInformation.value.fileListID1.path || !PersonalInformation.value.fileListID2.path) {
  190. uni.showToast({ title: t('vaildate.IDPhoto.empty'), icon: 'none' });
  191. return;
  192. }
  193. }
  194. }
  195. router.push({ path: `/pages/mine/info?type=${index}` });
  196. }
  197. function handleChange(value: any) {
  198. PersonalInformation.value = { ...PersonalInformation.value, [value.key]: value.value };
  199. }
  200. const containsChinese = (str: string) => /[\u4E00-\u9FA5]/.test(str);
  201. const convertToPinyin = (value: string) =>
  202. containsChinese(value)
  203. ? pinyin(value, { toneType: "none", type: "capitalize" })
  204. : value;
  205. async function getUserSingle() {
  206. if (!userToken.value) {
  207. uni.showToast({ title: t("common.loginFirst"), icon: 'none' });
  208. return;
  209. }
  210. if (current.value != 1) {
  211. return;
  212. }
  213. try {
  214. const res = await personalApi.CustomLoginInfo();
  215. if (res.code === 200 && res.data) {
  216. // 更新表单数据
  217. PersonalInformation.value = {
  218. ...res.data.customInfo,
  219. customType: res.data.customInfo?.customType || 1,
  220. addressLines1: res.data.customInfo?.addressLines[0] || '',
  221. addressLines2: res.data.customInfo?.addressLines[1] || '',
  222. };
  223. userStore.saveUserInfo(res.data);
  224. // 设置状态
  225. if (res.data) {
  226. isUpdate.value = true;
  227. }
  228. // if (res.data.approveStatus == 2 || res.data.kycStatus == 2) {
  229. // isAuthInfo.value = true;
  230. // }
  231. //
  232. // if (res.data.approveStatus == 3) {
  233. // isAuthInfo.value = false;
  234. // }
  235. }
  236. getCountry()
  237. } catch (error: any) {
  238. console.error('Error in getUserSingle:', error);
  239. uni.showToast({ title: error.message || t("common.error"), icon: 'none' });
  240. }
  241. }
  242. async function infoSubmit() {
  243. try {
  244. const res = await ucardApi.merchantRegister(PersonalInformation.value);
  245. if (res.code === 200) {
  246. uni.showToast({ title: t("common.success"), icon: 'success' });
  247. // 提交成功后打开 WebSDK 弹窗
  248. router.push({
  249. path: "/pages/mine/kyc",
  250. query: { cardId: (PersonalInformation.value as any).id },
  251. });
  252. } else {
  253. uni.showToast({ title: res.msg, icon: 'none' });
  254. }
  255. } catch (error: any) {
  256. uni.showToast({ title: error.message || t("common.error"), icon: 'none' });
  257. }
  258. }
  259. const isReadonly = computed(() => isAuthInfo.value);
  260. onMounted(async () => {
  261. // 先加载选项数据
  262. await Promise.all([
  263. getUserSingle(),
  264. ]);
  265. });
  266. </script>
  267. <style scoped lang="scss">
  268. @import "@/uni.scss";
  269. .tab-title {
  270. display: flex;
  271. align-items: center;
  272. gap: px2rpx(4);
  273. border: 1px solid #f3f4f6;
  274. font-size: px2rpx(18);
  275. padding: px2rpx(8) px2rpx(16);
  276. border-radius: px2rpx(2);
  277. background-color: white;
  278. }
  279. .active {
  280. background-color: var(--color-error);
  281. color: var(--bs-emphasis-color);
  282. border: none;
  283. }
  284. .info-card {
  285. padding: px2rpx(5);
  286. //border: 1px solid rgba(108, 133, 149, 0.12);
  287. border-radius: px2rpx(8);
  288. .btns {
  289. display: flex;
  290. justify-content: flex-end;
  291. gap: px2rpx(30);
  292. margin-top: px2rpx(30);
  293. .btn-primary {
  294. min-width: px2rpx(120);
  295. background-color: var(--bs-heading-color);
  296. color: white;
  297. padding: 0 px2rpx(12);
  298. border-radius: px2rpx(8);
  299. border: none;
  300. font-size: px2rpx(14);
  301. text-align: center;
  302. cursor: pointer;
  303. display: flex;
  304. align-items: center;
  305. justify-content: center;
  306. gap: px2rpx(8);
  307. }
  308. .btn-primary:active {
  309. background-color: #cf1322;
  310. ;
  311. }
  312. }
  313. }
  314. .form-tab {
  315. height: px2rpx(100);
  316. }
  317. .form-section {
  318. margin: px2rpx(8) 0;
  319. }
  320. :deep(.u-uploader) {
  321. width: 100%;
  322. .u-uploader__wrapper {
  323. width: 100%;
  324. display: block;
  325. }
  326. .u-uploader__preview {
  327. width: 100% !important;
  328. height: px2rpx(160) !important;
  329. border-radius: px2rpx(24);
  330. overflow: hidden;
  331. .u-uploader__preview-image {
  332. width: 100%;
  333. height: 100%;
  334. .u-image__img {
  335. object-fit: contain;
  336. }
  337. }
  338. .u-uploader__preview-delete {
  339. position: absolute;
  340. top: 0;
  341. right: 0;
  342. width: px2rpx(30);
  343. height: px2rpx(30);
  344. border-radius: 0 px2rpx(24) 0 0;
  345. i {
  346. text-align: center;
  347. line-height: px2rpx(30);
  348. font-size: px2rpx(30);
  349. }
  350. }
  351. }
  352. }
  353. </style>