502 lines
14 KiB
Vue
502 lines
14 KiB
Vue
<template>
|
||
<div class="fullPage">
|
||
<GeneralHeader
|
||
style="width: 100%"
|
||
:title="t('kyc')"
|
||
:showLanguageList="true"
|
||
color="black"
|
||
:backQuery="backQuery"
|
||
/>
|
||
|
||
<div style="padding: 16px; display: flex; flex-direction: column; gap: 8px">
|
||
<!-- 银行卡号 -->
|
||
<div style="font-weight: 600">{{ t('contact_number') }}</div>
|
||
<div
|
||
style="
|
||
border-radius: 8px;
|
||
background: #fff;
|
||
padding: 12px;
|
||
box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.25);
|
||
"
|
||
>
|
||
<input
|
||
type="text"
|
||
v-model="applyInfo.contactNumber"
|
||
:placeholder="t('enter_contact_number')"
|
||
style="width: 100%; border: none; outline: none"
|
||
/>
|
||
</div>
|
||
|
||
<!-- 申请信息 -->
|
||
<div style="font-weight: 600">{{ t('other_description') }}</div>
|
||
<div
|
||
style="
|
||
padding: 12px 12px 20px;
|
||
background: #fff;
|
||
box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.25);
|
||
border-radius: 8px;
|
||
position: relative;
|
||
"
|
||
>
|
||
<textarea
|
||
v-model="applyInfo.otherDescription"
|
||
rows="5"
|
||
maxlength="100"
|
||
:placeholder="t('enter_other_description')"
|
||
style="width: 100%; border: none; outline: none; resize: none"
|
||
></textarea>
|
||
<span
|
||
style="
|
||
position: absolute;
|
||
bottom: 4px;
|
||
right: 4px;
|
||
color: rgba(0, 0, 0, 0.4);
|
||
font-size: 0.8em;
|
||
"
|
||
class="word-count"
|
||
>{{ count }}/100</span
|
||
>
|
||
</div>
|
||
|
||
<!-- 上传图片 -->
|
||
<div style="font-weight: 600">{{ t('passport_id_card_title') }}:</div>
|
||
<div style="display: flex; align-items: center">
|
||
<!-- 预览图片 -->
|
||
<div v-for="(imgUrl, index) in resImgUrls" :key="index" style="position: relative">
|
||
<img
|
||
:src="imgUrl"
|
||
alt=""
|
||
style="display: block; width: 15vw; aspect-ratio: 1/1; object-fit: cover"
|
||
/>
|
||
<img
|
||
src="/src/assets/icon/cancelGray.png"
|
||
alt=""
|
||
style="
|
||
position: absolute;
|
||
top: 6%;
|
||
right: 6%;
|
||
display: block;
|
||
width: 3vw;
|
||
aspect-ratio: 1/1;
|
||
object-fit: cover;
|
||
"
|
||
@click="handleCancel(index)"
|
||
/>
|
||
</div>
|
||
|
||
<!-- 添加按钮 -->
|
||
<div v-if="resImgUrls.length < 3" style="position: relative">
|
||
<img
|
||
src="/src/assets/icon/addImg.png"
|
||
alt=""
|
||
style="display: block; width: 15vw; aspect-ratio: 1/1; object-fit: cover"
|
||
/>
|
||
<button
|
||
@click="onClickImagePicker"
|
||
style="
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
opacity: 0;
|
||
cursor: pointer;
|
||
border: none;
|
||
"
|
||
></button>
|
||
<!-- <input
|
||
v-else
|
||
type="file"
|
||
accept="image/*"
|
||
@click="onFileInputClick"
|
||
@change="handleFileChange"
|
||
style="
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
opacity: 0;
|
||
cursor: pointer;
|
||
"
|
||
/> -->
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 当前状态 -->
|
||
<div v-if="updateStatus" style="display: flex; align-items: flex-end; gap: 8px">
|
||
<div style="font-weight: 600">{{ t('current_status') }}</div>
|
||
<div
|
||
v-if="applyInfo.status === 'PASS'"
|
||
style="
|
||
font-weight: 510;
|
||
font-size: 0.9em;
|
||
background: linear-gradient(248deg, #75ff98 5.66%, #3dff54 42.49%);
|
||
background-clip: text;
|
||
-webkit-background-clip: text;
|
||
-webkit-text-fill-color: transparent;
|
||
"
|
||
>
|
||
{{ t('review_passed') }}
|
||
</div>
|
||
<div
|
||
v-else-if="applyInfo.status === 'NOT_PASS'"
|
||
style="
|
||
font-weight: 510;
|
||
font-size: 0.9em;
|
||
background: linear-gradient(248deg, #ff7578 5.66%, #ff3d40 42.49%);
|
||
background-clip: text;
|
||
-webkit-background-clip: text;
|
||
-webkit-text-fill-color: transparent;
|
||
"
|
||
>
|
||
{{ t('review_failed') }}
|
||
</div>
|
||
<div
|
||
v-else-if="applyInfo.status === 'PENDING'"
|
||
style="
|
||
font-weight: 510;
|
||
font-size: 0.9em;
|
||
background: linear-gradient(248deg, #ffe675 5.66%, #ffc53d 42.49%);
|
||
background-clip: text;
|
||
-webkit-background-clip: text;
|
||
-webkit-text-fill-color: transparent;
|
||
"
|
||
>
|
||
{{ t('awaiting_review') }}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 提现按钮 -->
|
||
<div style="display: flex; justify-content: center; align-items: center">
|
||
<div
|
||
style="
|
||
margin-top: 10px;
|
||
width: 80%;
|
||
border-radius: 32px;
|
||
padding: 12px;
|
||
color: #fff;
|
||
font-weight: 590;
|
||
text-align: center;
|
||
"
|
||
:style="{
|
||
background: checkStatus
|
||
? 'linear-gradient(135deg, #BB92FF 2.82%, #8B45FF 99.15%), linear-gradient(135deg, #D1CED6 2.82%, #7C7882 99.15%)'
|
||
: 'linear-gradient(135deg, #D1CED6 2.82%, #7C7882 99.15%)',
|
||
}"
|
||
@click="Submit"
|
||
>
|
||
{{ t('submit') }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { isInApp } from '@/utils/appBridge.js'
|
||
import { useI18n } from 'vue-i18n'
|
||
import GeneralHeader from '@/components/GeneralHeader.vue'
|
||
import { ref, onMounted, computed, nextTick } from 'vue'
|
||
import { showError, showWarning, showInfo, showSuccess } from '@/utils/toast.js'
|
||
import { useRoute, useRouter } from 'vue-router'
|
||
import { uploadImg } from '@/api/upload.js'
|
||
import { setDocumentDirection } from '@/locales/i18n'
|
||
import { getWithdrawInfoList, addWithdrawInfo, updateWithdrawInfo } from '@/api/wallet'
|
||
import { uploadImgFile, parseAccessOrigin } from '@/utils/uploadFiles.js'
|
||
import { useLastImgStore } from '@/stores/lastImg'
|
||
import { storeToRefs } from 'pinia'
|
||
|
||
const { t, locale } = useI18n()
|
||
const router = useRouter()
|
||
const route = useRoute()
|
||
const backQuery = ref(route.query || null)
|
||
const lastImgStore = useLastImgStore()
|
||
// 初始化时从 localStorage 恢复数据
|
||
lastImgStore.initFromStorage()
|
||
const { imgUrl: lastImgUrl } = storeToRefs(lastImgStore) //获取图片缓存
|
||
|
||
// 监听语言变化并设置文档方向
|
||
locale.value && setDocumentDirection(locale.value)
|
||
|
||
const count = computed(() => applyInfo.value.otherDescription.length) //字数
|
||
|
||
const tempFiles = ref([]) //用于提交的临时图片文件列表
|
||
// const previewUrls = ref([]) //预览图片数组
|
||
const resImgUrls = ref([]) //用于提交申请的图片数组
|
||
|
||
const updateStatus = ref(false) //是否为修改
|
||
|
||
const applyInfo = ref({
|
||
id: '', //提现信息ID
|
||
contactNumber: '', //银行卡号
|
||
category: 'CARD', //类别
|
||
otherDescription: '', //其他描述
|
||
passportFrontUrl: '', //图片
|
||
passportBackUrl: 'meaningless',
|
||
})
|
||
|
||
// 是否全填
|
||
const checkStatus = computed(() => {
|
||
return applyInfo.value.contactNumber != '' && resImgUrls.value.length > 0
|
||
})
|
||
|
||
// 点击图片选择按钮
|
||
const onClickImagePicker = async () => {
|
||
try {
|
||
await callNativeImagePicker()
|
||
} catch (error) {
|
||
console.error('图片选择失败:', error)
|
||
// 可以在这里添加额外的错误处理逻辑
|
||
}
|
||
}
|
||
|
||
// 调用原生图片选择器
|
||
const callNativeImagePicker = async () => {
|
||
console.log('调用原生图片选择器')
|
||
|
||
// 最多3张图片
|
||
if (resImgUrls.value.length >= 3) {
|
||
throw new Error('Maximum number of images reached')
|
||
}
|
||
|
||
// 定义过滤函数
|
||
const filterDuplicateImages = (access) => {
|
||
try {
|
||
// 处理空数据情况
|
||
if (!access || access === '""' || access === '') {
|
||
return true // 空数据不过滤,让上层处理
|
||
}
|
||
|
||
const result = parseAccessOrigin(access)
|
||
|
||
if (result.success && result.data) {
|
||
const file = result.data
|
||
console.log('检查图片数据:', file.path)
|
||
|
||
// 如果是已存在的图片,返回false表示需要继续检查
|
||
if (lastImgUrl.value && file.path === lastImgUrl.value) {
|
||
console.log('检测到重复图片,继续等待新图片...')
|
||
return false
|
||
}
|
||
|
||
// 新图片,可以接受
|
||
return true
|
||
}
|
||
|
||
// 其他情况接受数据
|
||
return true
|
||
} catch (error) {
|
||
console.error('过滤图片数据时出错:', error)
|
||
return true // 出错时不过滤,让上层处理
|
||
}
|
||
}
|
||
|
||
return new Promise((resolve, reject) => {
|
||
try {
|
||
uploadImgFile(
|
||
async (access) => {
|
||
try {
|
||
// 处理空数据情况
|
||
if (!access || access === '""' || access === '') {
|
||
console.warn('接收到空的图片数据')
|
||
reject(new Error('No image selected'))
|
||
return
|
||
}
|
||
|
||
const result = parseAccessOrigin(access)
|
||
|
||
if (result.success && result.data) {
|
||
const file = result.data
|
||
console.log('接收到原生图片数据:', file.path)
|
||
|
||
lastImgStore.setLastImgUrl(file.path) // 更新上一次实际选择的图片路径
|
||
// 生成预览图
|
||
resImgUrls.value.push(file.path)
|
||
resolve()
|
||
} else {
|
||
const errorMsg = result.error
|
||
console.error('图片选择失败:', errorMsg)
|
||
reject(new Error(errorMsg))
|
||
}
|
||
} catch (error) {
|
||
console.error('处理原生图片选择结果失败:', error)
|
||
reject(error)
|
||
}
|
||
},
|
||
filterDuplicateImages // 传递过滤函数
|
||
)
|
||
} catch (error) {
|
||
console.error('调用原生图片选择器失败:', error)
|
||
reject(error)
|
||
}
|
||
})
|
||
}
|
||
|
||
// 取消图片选择
|
||
const handleCancel = (index) => {
|
||
resImgUrls.value.splice(index, 1)
|
||
}
|
||
|
||
// 上传图片
|
||
const uploadImgUrl = async () => {
|
||
for (let index = 0; index < tempFiles.value.length; index++) {
|
||
const file = tempFiles.value[index]
|
||
try {
|
||
const res = await uploadImg(file)
|
||
if (res.status) {
|
||
resImgUrls.value.push(res.body)
|
||
}
|
||
} catch (error) {
|
||
showError(error.response?.errorMsg || t('upload_failed'))
|
||
}
|
||
}
|
||
}
|
||
|
||
// 提交申请
|
||
const Submit = async () => {
|
||
// 检查是否漏填
|
||
console.log('checkStatus.value:', checkStatus.value)
|
||
|
||
if (checkStatus.value) {
|
||
applyInfo.value.passportFrontUrl = JSON.stringify(resImgUrls.value)
|
||
if (updateStatus.value) {
|
||
console.log('更新信息:', applyInfo.value)
|
||
await updateWithdrawInfoData()
|
||
} else {
|
||
console.log('添加信息:', applyInfo.value)
|
||
await addWithdrawInfoData()
|
||
}
|
||
// 先返回到 availableIncome (原始状态)
|
||
router.go(-1)
|
||
|
||
// 检查是否来自 availableIncome 页面
|
||
if (route.query) {
|
||
console.log('route.query:', route.query)
|
||
|
||
// 使用 nextTick 确保上一步完成
|
||
nextTick(() => {
|
||
// 然后使用 replace 替换为带参数的版本
|
||
router.replace({
|
||
path: route.query.from,
|
||
query: {
|
||
activeAction: route.query.activeAction,
|
||
},
|
||
})
|
||
})
|
||
}
|
||
}
|
||
}
|
||
|
||
// 获取提现信息列表
|
||
const getWithdrawInfoListData = async () => {
|
||
try {
|
||
const resWithdrawInfoList = await getWithdrawInfoList()
|
||
console.log('提现信息列表:', resWithdrawInfoList)
|
||
if (resWithdrawInfoList.status && resWithdrawInfoList.body?.length > 0) {
|
||
updateStatus.value = true
|
||
applyInfo.value = resWithdrawInfoList.body[0]
|
||
let imgList = JSON.parse(resWithdrawInfoList.body[0].passportFrontUrl)
|
||
resImgUrls.value = imgList.map((item) => item)
|
||
}
|
||
} catch (error) {
|
||
console.error('获取提现信息列表失败:', error)
|
||
showError(error.response?.errorMsg)
|
||
}
|
||
}
|
||
|
||
// 添加提现信息
|
||
const addWithdrawInfoData = async () => {
|
||
try {
|
||
const resAddWithdrawInfo = await addWithdrawInfo(applyInfo.value)
|
||
if (resAddWithdrawInfo.status) {
|
||
showSuccess(t('application_submitted'))
|
||
// getWithdrawInfoListData() // 重新获取提现信息列表
|
||
}
|
||
} catch (error) {
|
||
showError(error.response?.errorMsg)
|
||
throw error
|
||
}
|
||
}
|
||
|
||
// 更新提现信息
|
||
const updateWithdrawInfoData = async () => {
|
||
try {
|
||
const resUpdateWithdrawInfo = await updateWithdrawInfo(applyInfo.value)
|
||
if (resUpdateWithdrawInfo.status) {
|
||
showSuccess(t('application_submitted'))
|
||
// getWithdrawInfoListData() // 重新获取提现信息列表
|
||
}
|
||
} catch (error) {
|
||
showError(error.response?.errorMsg)
|
||
throw error
|
||
}
|
||
}
|
||
|
||
onMounted(() => {
|
||
getWithdrawInfoListData() // 获取个人申请信息
|
||
})
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
* {
|
||
color: rgba(0, 0, 0, 0.8);
|
||
font-family: 'SF Pro Text';
|
||
}
|
||
|
||
.fullPage {
|
||
width: 100vw;
|
||
min-height: 100vh;
|
||
background-color: #fff;
|
||
background-image: url(/src/assets/images/secondBg.png);
|
||
background-size: 100% auto;
|
||
background-repeat: no-repeat;
|
||
position: relative;
|
||
}
|
||
|
||
input::placeholder {
|
||
font-weight: 510;
|
||
color: rgba(0, 0, 0, 0.4);
|
||
}
|
||
|
||
textarea::placeholder {
|
||
font-weight: 510;
|
||
color: rgba(0, 0, 0, 0.4);
|
||
}
|
||
|
||
textarea::-webkit-scrollbar {
|
||
width: 0;
|
||
height: 0;
|
||
}
|
||
|
||
@media screen and (max-width: 360px) {
|
||
* {
|
||
font-size: 10px;
|
||
}
|
||
}
|
||
|
||
@media screen and (min-width: 360px) {
|
||
* {
|
||
font-size: 16px;
|
||
}
|
||
}
|
||
|
||
@media screen and (min-width: 768px) {
|
||
* {
|
||
font-size: 24px;
|
||
}
|
||
}
|
||
|
||
@media screen and (min-width: 1024px) {
|
||
* {
|
||
font-size: 32px;
|
||
}
|
||
}
|
||
|
||
/* RTL支持 */
|
||
[dir='rtl'] .word-count {
|
||
right: auto;
|
||
left: 4px;
|
||
}
|
||
</style>
|