BankItem.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583
  1. <template>
  2. <view class="user-form crm-form">
  3. <uni-row class="demo-uni-row uni-row1">
  4. <uni-col :xs="24" :sm="24" :md="24" :lg="6" :xl="6">
  5. <view class="bank-menu">
  6. <view v-for="item in bankTypes" :key="item.key" class="bank-menu-item"
  7. :class="{ active: selectedBankType === item.key }" @click="selectedBankType = item.key">
  8. <image class="bank-icon" :src="item.icon" mode="widthFix" />
  9. <text>{{ item.label }}</text>
  10. </view>
  11. </view>
  12. </uni-col>
  13. <uni-col :xs="24" :sm="24" :md="24" :lg="18" :xl="18">
  14. <view class="bank-content" v-if="selectedBankType === 'crypto'">
  15. <view class="bank-info" v-for="(item, index) in cryptoWallets" :key="item.id">
  16. <view class="card-header">
  17. <view class="bank-header">
  18. <uni-row style="width: 100%;">
  19. <uni-col :xs="24" :sm="24" :md="8" :lg="8" :xl="8">
  20. <view class="bank-title">
  21. <cwg-icon v-if="item.defaultBank" name="crm-star" color="#ea2027" />
  22. <text>{{ currentBankType?.label }} {{ index + 1 }} </text>
  23. <text v-if="item.defaultBank" v-t="'PersonalManagement.Title.Default'" />
  24. </view>
  25. </uni-col>
  26. <uni-col :xs="24" :sm="24" :md="16" :lg="16" :xl="16">
  27. <view class="bank-actions">
  28. <view class="action-btn bg-secondary" v-if="item.authStatus == 0"
  29. type="primary" v-t="'State.ToCertified'"
  30. @click="doReady(item.id, item)" />
  31. <view class="action-btn bg-secondary" v-if="item.authStatus == 0"
  32. type="primary" @click="openCardDialog(item.id, item)"
  33. v-t="'PersonalManagement.CardVerify.Title'" />
  34. <view class="action-btn bg-secondary"
  35. v-if="!editingId && item.authStatus !== 1" @tap="startEdit(item)"
  36. v-t="'Btn.Editor'" />
  37. <template v-if="editingId === item.id">
  38. <view class="action-btn bg-secondary" @tap="saveBank(item)"
  39. v-t="'Btn.Save'" />
  40. <view class="action-btn bg-secondary" @tap="cancelEdit()"
  41. v-t="'Btn.Cancel'" />
  42. </template>
  43. <view class="action-btn delete" @tap="confirmDelete(item)"
  44. v-t="'Btn.Delete'" />
  45. </view>
  46. </uni-col>
  47. </uni-row>
  48. </view>
  49. </view>
  50. <uni-forms :model="item" labelWidth="200" label-position="top">
  51. <uni-row class="demo-uni-row">
  52. <uni-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
  53. <uni-forms-item :label="t('blockchain.item3')">
  54. <uni-easyinput :clearable="false" v-model="item.addressName"
  55. :disabled="editingId !== item.id"
  56. :placeholder="locale == 'es' ? 'Introduzca el nombre de la red' : t('placeholder.input')" />
  57. </uni-forms-item>
  58. </uni-col>
  59. <uni-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
  60. <uni-forms-item :label="t('blockchain.item4')">
  61. <uni-easyinput :clearable="false" v-model="item.address"
  62. :disabled="editingId !== item.id"
  63. :placeholder="locale == 'es' ? 'Introduzca la dirección de la billetera' : t('placeholder.input')" />
  64. </uni-forms-item>
  65. </uni-col>
  66. <uni-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24"
  67. v-if="item.cardFiles && item.cardFiles.length">
  68. <uni-forms-item :label="t('PersonalManagement.Label.CertificationPhoto')">
  69. <view class="photo-upload">
  70. <view v-for="(file, idx) in item.cardFiles" :key="idx" class="photo-item">
  71. <image class="photo-preview" :src="updateUrl + file.path"
  72. mode="aspectFill" />
  73. </view>
  74. </view>
  75. </uni-forms-item>
  76. </uni-col>
  77. </uni-row>
  78. </uni-forms>
  79. </view>
  80. <view class="add-wallet-btn" @click="openAddBankCard" v-if="cryptoWallets.length < 2">
  81. <text>+</text>
  82. <text>添加加密货币钱包</text>
  83. </view>
  84. </view>
  85. <view class="bank-content" v-if="selectedBankType === 'unionpay'">
  86. <view class="bank-info" v-for="item in unionpayCards" :key="item.id">
  87. </view>
  88. <view class="add-wallet-btn" @click="openAddBankCard">
  89. <text>+</text>
  90. <text>{{ t('ImproveImmediately.Label.AddCryptoWallet') }}</text>
  91. </view>
  92. </view>
  93. </uni-col>
  94. </uni-row>
  95. </view>
  96. <!-- 删除确认弹窗 -->
  97. <uni-popup ref="deletePopup" type="dialog">
  98. <uni-popup-dialog :title="t('Msg.SystemPrompt')" :content="t('Msg.Delete')" @confirm="deleteBank"
  99. @close="closeDeletePopup" />
  100. </uni-popup>
  101. <!-- 新增银行弹窗 -->
  102. <cwg-add-bank-dialog ref="addBankDialogRef" @success="addSuccess" />
  103. <!-- kyc认证弹窗 -->
  104. <kyc-dialog ref="kycDialogRef" />
  105. <!-- 证件认证弹窗 -->
  106. <card-dialog ref="cardDialogRef" />
  107. </template>
  108. <script setup lang="ts">
  109. import { computed, ref, onMounted } from 'vue';
  110. import { useI18n } from 'vue-i18n';
  111. import { personalApi } from '@/service/personal';
  112. import KycDialog from './KycAuthDialog.vue';
  113. import CardDialog from './CardAuthDialog.vue';
  114. import config from '@/config'
  115. import { BankType, AuthStatus, ApproveStatus } from './bank'
  116. const { t } = useI18n();
  117. interface BankListType {
  118. key: string;
  119. label: string;
  120. icon: string;
  121. }
  122. interface BankInfo {
  123. id?: string;
  124. type: number;
  125. defaultBank: boolean;
  126. approveStatus?: number;
  127. authStatus?: number;
  128. blockchainName: string;
  129. walletAddress: string;
  130. photos?: string[];
  131. expiryYearMonth?: string;
  132. expiryYear?: string;
  133. expiryMonth?: string;
  134. [key: string]: any;
  135. }
  136. const selectedBankType = ref<string>('crypto');
  137. const bankTypes = computed<BankListType[]>(() => [
  138. { key: 'crypto', label: t('blockchain.item2'), icon: '/static/images/info/bank-info_1.png' },
  139. { key: 'unionpay', label: t('PersonalManagement.Title.ChinaUnionPayCard'), icon: '/static/images/info/bank-info_2.png' },
  140. { key: 'bank', label: t('PersonalManagement.Title.BankWireTransfer'), icon: '/static/images/info/bank-info_3.png' },
  141. { key: 'credit', label: t('PersonalManagement.Label.CreditCard'), icon: '/static/images/info/bank-info_4.png' }
  142. ]);
  143. const currentBankType = computed(() => bankTypes.value.find((item: BankListType) => item.key === selectedBankType.value));
  144. // 状态
  145. const updateUrl = config.Host80
  146. const editingId = ref(null)
  147. const bankOptions = ref([])
  148. // 银行数据
  149. const bankData = ref({
  150. [BankType.CRYPTO]: [],
  151. [BankType.UNIONPAY]: [],
  152. [BankType.WIRE_TRANSFER]: [],
  153. [BankType.CREDIT_CARD]: []
  154. })
  155. // 计算属性 - 各类型银行列表
  156. const cryptoWallets = computed(() => bankData.value[BankType.CRYPTO] || [])
  157. const unionpayCards = computed(() => bankData.value[BankType.UNIONPAY] || [])
  158. const wireTransfers = computed(() => bankData.value[BankType.WIRE_TRANSFER] || [])
  159. const creditCards = computed(() => bankData.value[BankType.CREDIT_CARD] || [])
  160. console.log(unionpayCards, bankData.value, 231232);
  161. // 弹出层ref
  162. const deletePopup = ref(null)
  163. const deleteTarget = ref(null)
  164. // 开始编辑
  165. const startEdit = (item) => {
  166. editingId.value = item.id
  167. }
  168. // 取消编辑
  169. const cancelEdit = () => {
  170. editingId.value = null
  171. getBankInfo() // 重新加载数据
  172. }
  173. // 保存银行信息
  174. const saveBank = async (item) => {
  175. if (item.approveStatus == 1) {
  176. uni.showToast({
  177. title: "加密钱包认证审核中",
  178. icon: "none"
  179. });
  180. return;
  181. }
  182. try {
  183. const params = { ...item }
  184. params.defaultBank = params.defaultBank ? 1 : 0
  185. if (params.type === BankType.CREDIT_CARD && params.expiryYearMonth) {
  186. const [year, month] = params.expiryYearMonth.split('/')
  187. params.expiryYear = year
  188. params.expiryMonth = month
  189. }
  190. const res = await personalApi.customBankUpdate(params)
  191. if (res.code === 200) {
  192. uni.hideLoading()
  193. uni.showToast({
  194. title: t('Msg.Success'),
  195. icon: 'success'
  196. })
  197. editingId.value = null
  198. getBankInfo()
  199. } else {
  200. throw new Error(res.msg)
  201. }
  202. } catch (error) {
  203. uni.hideLoading()
  204. uni.showToast({
  205. title: error.message || t('Msg.Fail'),
  206. icon: 'none'
  207. })
  208. }
  209. }
  210. // 删除
  211. const confirmDelete = (item) => {
  212. if (item.approveStatus == 1) {
  213. uni.showToast({
  214. title: '加密钱包认证审核中',
  215. icon: 'none'
  216. })
  217. return
  218. }
  219. deleteTarget.value = item
  220. deletePopup.value.open()
  221. }
  222. // 关闭删除弹窗
  223. const closeDeletePopup = () => {
  224. deletePopup.value.close()
  225. deleteTarget.value = null
  226. }
  227. // 执行删除
  228. const deleteBank = async () => {
  229. if (!deleteTarget.value) return
  230. uni.showLoading({ title: t('Btn.Deleting') })
  231. try {
  232. const res = await personalApi.customBankDelete({
  233. ids: [deleteTarget.value.id]
  234. })
  235. if (res.code === 200) {
  236. uni.hideLoading()
  237. uni.showToast({
  238. title: t('Msg.DeleteSuccess'),
  239. icon: 'success'
  240. })
  241. deletePopup.value.close()
  242. deleteTarget.value = null
  243. getBankInfo()
  244. } else {
  245. throw new Error(res.msg)
  246. }
  247. } catch (error) {
  248. uni.hideLoading()
  249. uni.showToast({
  250. title: error.message || t('Msg.Fail'),
  251. icon: 'none'
  252. })
  253. deletePopup.value.close()
  254. }
  255. }
  256. // 新增银行信息
  257. const addBankDialogRef = ref(null);
  258. function openAddBankCard() {
  259. const wallets = cryptoWallets.value || [];
  260. // 1️⃣ 没有钱包
  261. if (wallets.length === 0) {
  262. addBankDialogRef.value?.open();
  263. return;
  264. }
  265. // 2️⃣ 是否存在未认证钱包
  266. const hasUnAuth = wallets.some(
  267. item => item.authStatus === 0 || item.approveStatus === 1
  268. );
  269. if (hasUnAuth) {
  270. uni.showToast({
  271. title: "加密钱包未认证",
  272. icon: "none"
  273. });
  274. return;
  275. }
  276. // 3️⃣ 是否达到上限
  277. if (wallets.length >= 2) {
  278. uni.showToast({
  279. title: t('blockchain.item9'),
  280. icon: "none"
  281. });
  282. return;
  283. }
  284. // 4️⃣ 正常打开
  285. addBankDialogRef.value?.open();
  286. }
  287. // 新增银行信息成功回调
  288. const addSuccess = (e) => {
  289. openKycDialog(e)
  290. getBankInfo();
  291. }
  292. // kyc认证和证件认证
  293. const kycDialogRef = ref(null);
  294. const cardDialogRef = ref(null);
  295. // kyc认证
  296. const doReady = (bankId, item) => {
  297. if (item.approveStatus == 1) {
  298. uni.showToast({
  299. title: "加密钱包认证审核中",
  300. icon: "none"
  301. });
  302. return;
  303. }
  304. if (item.authStatus == 1) {
  305. uni.showToast({
  306. title: "加密钱包已认证",
  307. icon: "none"
  308. });
  309. return;
  310. }
  311. openKycDialog(bankId)
  312. }
  313. // 打开kyc认证弹窗
  314. function openKycDialog(e) {
  315. kycDialogRef.value?.open(e);
  316. }
  317. // 打开证件认证弹窗
  318. function openCardDialog(e, item) {
  319. if (item.approveStatus == 1) {
  320. uni.showToast({
  321. title: "加密钱包认证审核中",
  322. icon: "none"
  323. });
  324. return;
  325. }
  326. if (item.authStatus == 1) {
  327. uni.showToast({
  328. title: "加密钱包已认证",
  329. icon: "none"
  330. });
  331. return;
  332. }
  333. cardDialogRef.value?.open(e, item);
  334. }
  335. // 预览图片
  336. const previewImage = (path) => {
  337. uni.previewImage({
  338. urls: [updateUrl + path]
  339. })
  340. }
  341. // 掩码卡号
  342. const maskCardNumber = (num) => {
  343. if (!num) return '--'
  344. if (num.length <= 4) return num
  345. return '**** **** **** ' + num.slice(-4)
  346. }
  347. // 获取银行列表
  348. const getBankList = async () => {
  349. const res = await personalApi.BankList({})
  350. if (res.code === 200) {
  351. bankOptions.value = res.data
  352. }
  353. }
  354. // 获取银行信息
  355. const getBankInfo = async () => {
  356. uni.showLoading({ title: t('Btn.Loading') })
  357. try {
  358. const res = await personalApi.customBankList({})
  359. if (res.code === 200) {
  360. // 清空数据
  361. bankData.value = {
  362. [BankType.CRYPTO]: [],
  363. [BankType.UNIONPAY]: [],
  364. [BankType.WIRE_TRANSFER]: [],
  365. [BankType.CREDIT_CARD]: []
  366. }
  367. // 分类数据
  368. res.data.forEach(item => {
  369. item.defaultBank = !!item.defaultBank
  370. if (item.type === BankType.UNIONPAY) {
  371. bankData.value[BankType.UNIONPAY].push(item)
  372. } else if (item.type === BankType.WIRE_TRANSFER) {
  373. bankData.value[BankType.WIRE_TRANSFER].push(item)
  374. } else if (item.type === BankType.CREDIT_CARD) {
  375. item.expiryYearMonth = item.expiryYear && item.expiryMonth
  376. ? `${item.expiryYear}/${item.expiryMonth}`
  377. : ''
  378. bankData.value[BankType.CREDIT_CARD].push(item)
  379. } else if (item.type === BankType.CRYPTO) {
  380. bankData.value[BankType.CRYPTO].push(item)
  381. }
  382. })
  383. }
  384. } catch (error) {
  385. uni.showToast({
  386. title: error.message || t('Msg.Fail'),
  387. icon: 'none'
  388. })
  389. } finally {
  390. uni.hideLoading()
  391. }
  392. }
  393. // 获取银行信息
  394. onMounted(() => {
  395. getBankList()
  396. getBankInfo();
  397. });
  398. </script>
  399. <style scoped lang="scss">
  400. .user-form {
  401. margin-top: px2rpx(30);
  402. }
  403. :deep(.uni-row1) {
  404. .uni-col {
  405. padding: 0 10px !important;
  406. }
  407. .uni-forms-item {
  408. min-height: px2rpx(79);
  409. margin-bottom: px2rpx(10);
  410. }
  411. .uni-easyinput__content {
  412. border: none !important;
  413. background-color: var(--color-zinc-100) !important;
  414. }
  415. }
  416. .bank-menu {
  417. background: #fff;
  418. overflow: hidden;
  419. .bank-menu-item {
  420. display: flex;
  421. align-items: center;
  422. gap: px2rpx(12);
  423. padding: px2rpx(10) px2rpx(16);
  424. cursor: pointer;
  425. border: 1px solid #f3f4f6;
  426. font-size: px2rpx(16);
  427. font-weight: 500;
  428. color: #1f2937;
  429. transition: all 0.3s;
  430. height: px2rpx(50);
  431. border-radius: px2rpx(8);
  432. margin-bottom: px2rpx(8);
  433. .bank-icon {
  434. width: px2rpx(50);
  435. }
  436. &.active {
  437. background: #ea2027;
  438. color: var(--bs-emphasis-color);
  439. border-radius: px2rpx(0);
  440. }
  441. &:hover {
  442. background: #f9fafb;
  443. }
  444. &.active:hover {
  445. background: #d11920;
  446. }
  447. }
  448. }
  449. .bank-content {
  450. background: #fff;
  451. border-radius: px2rpx(8);
  452. padding: 0 px2rpx(24);
  453. .bank-info {
  454. .bank-header {
  455. display: flex;
  456. justify-content: space-between;
  457. align-items: center;
  458. margin-bottom: px2rpx(24);
  459. padding-bottom: px2rpx(16);
  460. border-bottom: 1px solid #f3f4f6;
  461. .bank-title {
  462. display: flex;
  463. align-items: center;
  464. font-size: px2rpx(20);
  465. font-weight: 600;
  466. color: #1f2937;
  467. margin-bottom: px2rpx(20);
  468. }
  469. .bank-actions {
  470. display: flex;
  471. gap: px2rpx(12);
  472. align-items: center;
  473. justify-content: flex-end;
  474. .action-btn {
  475. padding: px2rpx(8) px2rpx(16);
  476. border: 1px solid #d1d5db;
  477. border-radius: px2rpx(4);
  478. font-size: px2rpx(14);
  479. cursor: pointer;
  480. background: #fff;
  481. transition: all 0.3s;
  482. &:hover {
  483. background: #f3f4f6;
  484. }
  485. &.delete {
  486. background: #ea2027;
  487. color: var(--bs-emphasis-color);
  488. border-color: #ea2027;
  489. &:hover {
  490. background: #d11920;
  491. }
  492. }
  493. }
  494. }
  495. }
  496. }
  497. .photo-upload {
  498. display: flex;
  499. gap: px2rpx(16);
  500. .photo-item {
  501. width: px2rpx(120);
  502. height: px2rpx(120);
  503. border-radius: px2rpx(8);
  504. overflow: hidden;
  505. background: #f3f4f6;
  506. .photo-preview {
  507. width: 100%;
  508. height: 100%;
  509. }
  510. }
  511. }
  512. .add-wallet-btn {
  513. margin-top: px2rpx(24);
  514. height: px2rpx(48);
  515. background: #ea2027;
  516. color: var(--bs-emphasis-color);
  517. border-radius: px2rpx(4);
  518. display: flex;
  519. align-items: center;
  520. justify-content: center;
  521. gap: px2rpx(8);
  522. font-size: px2rpx(16);
  523. font-weight: 600;
  524. cursor: pointer;
  525. transition: all 0.3s;
  526. &:hover {
  527. background: #d11920;
  528. }
  529. }
  530. }
  531. </style>