info.vue 12 KB

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