feat(提现申请功能): 对接接口完成

This commit is contained in:
hzj 2025-10-23 21:10:54 +08:00
parent bb04c3c6db
commit 0aeea9dd20
6 changed files with 198 additions and 66 deletions

14
src/api/upload.js Normal file
View File

@ -0,0 +1,14 @@
import { get, post } from '../utils/http.js'
// 上传图片
export const uploadImg = async (file) => {
try {
const formData = new FormData()
formData.append('file', file) // 这里的'file'需要与后端接口期望的参数名一致
const response = await post('/external/oss/upload', formData)
return response
} catch (error) {
console.error('Failed to upload img:', error)
throw error
}
}

39
src/utils/applyStore.js Normal file
View File

@ -0,0 +1,39 @@
let applyInfo = null
/**
* 设置申请资料缓存
* @param {Object} applyInfo - 申请资料
*/
export function setApplyInfo(data) {
applyInfo = data
// 可选存储到localStorage以便持久化
if (data) {
localStorage.setItem('applyInfo', JSON.stringify(data))
} else {
localStorage.removeItem('applyInfo')
}
}
/**
* 获取申请资料
* @returns {Object|null}
*/
export function getApplyInfo() {
if (applyInfo) {
return applyInfo
}
// 从localStorage恢复
const stored = localStorage.getItem('applyInfo')
if (stored) {
try {
applyInfo = JSON.parse(stored)
return applyInfo
} catch (error) {
console.error('解析申请资料缓存失败:', error)
}
}
return null
}

View File

@ -1,5 +1,5 @@
// HTTP请求配置 // HTTP请求配置
import {getApiBaseUrl, getCurrentEnv, getUserAuth, isDebugMode} from './env.js' import { getApiBaseUrl, getCurrentEnv, getUserAuth, isDebugMode } from './env.js'
/** /**
* 自定义API错误类 * 自定义API错误类
@ -8,14 +8,14 @@ class ApiError extends Error {
constructor(message, options = {}) { constructor(message, options = {}) {
super(message) super(message)
this.name = 'ApiError' this.name = 'ApiError'
this.status = options.status || null // HTTP状态码 this.status = options.status || null // HTTP状态码
this.errorCode = options.errorCode || null // 业务错误码 (如: 6010) this.errorCode = options.errorCode || null // 业务错误码 (如: 6010)
this.errorCodeName = options.errorCodeName || null // 错误码名称 (如: "TEAM_NOT_FOUND") this.errorCodeName = options.errorCodeName || null // 错误码名称 (如: "TEAM_NOT_FOUND")
this.errorMsg = options.errorMsg || null // 业务错误消息 this.errorMsg = options.errorMsg || null // 业务错误消息
this.time = options.time || null // API响应时间戳 this.time = options.time || null // API响应时间戳
this.extValues = options.extValues || null // 扩展值 this.extValues = options.extValues || null // 扩展值
this.response = options.response || null // 原始响应数据 this.response = options.response || null // 原始响应数据
this.type = options.type || 'unknown' // 错误类型: 'http' | 'business' | 'network' this.type = options.type || 'unknown' // 错误类型: 'http' | 'business' | 'network'
} }
} }
@ -32,7 +32,7 @@ if (DEBUG_MODE) {
const token = getUserAuth() const token = getUserAuth()
// 默认公共请求头配置 // 默认公共请求头配置
let COMMON_HEADERS = { let COMMON_HEADERS = {
'authorization': `Bearer ${token}`, authorization: `Bearer ${token}`,
'req-app-intel': 'version=1.0.0;build=1;model=SM-G9550;sysVersion=9;channel=Google', 'req-app-intel': 'version=1.0.0;build=1;model=SM-G9550;sysVersion=9;channel=Google',
'req-client': 'Android', 'req-client': 'Android',
'req-imei': '8fa54d728ab449e04f9292329ed44bda', 'req-imei': '8fa54d728ab449e04f9292329ed44bda',
@ -49,7 +49,7 @@ let COMMON_HEADERS = {
export function updateCommonHeaders(headers) { export function updateCommonHeaders(headers) {
COMMON_HEADERS = { COMMON_HEADERS = {
...COMMON_HEADERS, ...COMMON_HEADERS,
...headers ...headers,
} }
console.debug('🔄 Updated common headers:', COMMON_HEADERS) console.debug('🔄 Updated common headers:', COMMON_HEADERS)
} }
@ -110,37 +110,40 @@ export { COMMON_HEADERS, ApiError }
* @returns {Promise} - 返回Promise对象 * @returns {Promise} - 返回Promise对象
*/ */
export async function request(url, options = {}) { export async function request(url, options = {}) {
const { const { method = 'GET', data = null, headers = {}, ...otherOptions } = options
method = 'GET',
data = null,
headers = {},
...otherOptions
} = options
const fullUrl = url.startsWith('http') ? url : `${API_BASE_URL}${url}` const fullUrl = url.startsWith('http') ? url : `${API_BASE_URL}${url}`
const config = { const config = {
method, method,
headers: { headers: {
'Content-Type': 'application/json',
...COMMON_HEADERS, ...COMMON_HEADERS,
...headers ...headers,
}, },
...otherOptions ...otherOptions,
} }
// 如果是POST、PUT等方法且有数据添加body // 如果是POST、PUT等方法且有数据添加body
if (data && ['POST', 'PUT', 'PATCH'].includes(method.toUpperCase())) { if (data && ['POST', 'PUT', 'PATCH'].includes(method.toUpperCase())) {
config.body = JSON.stringify(data) if (data instanceof FormData) {
// FormData类型由浏览器自动设置Content-Type
config.body = data
// 删除手动设置的Content-Type重要
if (config.headers['Content-Type']) {
delete config.headers['Content-Type']
}
} else {
// 非FormData按JSON处理
config.body = JSON.stringify(data)
config.headers['Content-Type'] = 'application/json'
}
} }
try { try {
const response = await fetch(fullUrl, config) const response = await fetch(fullUrl, config)
if (!response.ok) { if (!response.ok) {
// 尝试解析响应体获取更多错误信息 // 尝试解析响应体获取更多错误信息
let errorData = await response.json() let errorData = await response.json()
throw new ApiError(`HTTP Error: ${response.status} ${response.statusText}`, { throw new ApiError(`HTTP Error: ${response.status} ${response.statusText}`, {
type: 'http', type: 'http',
@ -149,7 +152,7 @@ export async function request(url, options = {}) {
errorCode: errorData.errorCode, errorCode: errorData.errorCode,
errorCodeName: errorData.errorCodeName, errorCodeName: errorData.errorCodeName,
errorMsg: errorData.errorMsg, errorMsg: errorData.errorMsg,
time: errorData.time time: errorData.time,
}) })
} }
@ -180,7 +183,7 @@ export async function request(url, options = {}) {
throw new ApiError(error.message || 'Network or unknown error', { throw new ApiError(error.message || 'Network or unknown error', {
type: 'network', type: 'network',
originalError: error, originalError: error,
code: 'NETWORK_ERROR' code: 'NETWORK_ERROR',
}) })
} }

View File

@ -1399,15 +1399,19 @@ const getWinners = async () => {
} }
} }
// //
const receiveTaskReward = async (code) => { const receiveTaskReward = async (code) => {
let data = { let data = {
taskCode: code, taskCode: code,
} }
const resReceive = await receiveTickets(data) try {
if (resReceive.status && resReceive.body) { const resReceive = await receiveTickets(data)
getTaskList() // if (resReceive.status && resReceive.body) {
getTickets() // getTaskList() //
getTickets() //
}
} catch (error) {
showError(error.errorMsg)
} }
} }

View File

@ -70,7 +70,9 @@
<div v-else style="width: 100%; display: flex; flex-direction: column; gap: 8px"> <div v-else style="width: 100%; display: flex; flex-direction: column; gap: 8px">
<!-- 图片 --> <!-- 图片 -->
<div style="display: flex; justify-content: space-between; align-items: center"> <div style="display: flex; justify-content: space-between; align-items: center">
<span style="font-weight: 510; font-size: 1em">Passport/ID card:(3/3)</span> <span style="font-weight: 510; font-size: 1em"
>Passport/ID card:({{ bankCardInfo.cardImages.length }}/3)</span
>
<img <img
src="../../../assets/icon/arrow.png" src="../../../assets/icon/arrow.png"
alt="" alt=""
@ -79,28 +81,24 @@
/> />
</div> </div>
<div style="display: flex; align-items: center; gap: 4px"> <div style="display: flex; align-items: center; gap: 4px">
<div <img
style="width: 10vw; aspect-ratio: 1/1; border-radius: 4px; background: #ededed" :src="imgUrl"
></div> alt=""
<div v-for="imgUrl in bankCardInfo.cardImages"
style="width: 10vw; aspect-ratio: 1/1; border-radius: 4px; background: #ededed" style="width: 10vw; aspect-ratio: 1/1; border-radius: 4px"
></div> />
<div
style="width: 10vw; aspect-ratio: 1/1; border-radius: 4px; background: #ededed"
></div>
</div> </div>
<!-- 卡号 --> <!-- 卡号 -->
<div style="font-weight: 500">Contact number:</div> <div style="font-weight: 500">Contact number:</div>
<div style="color: rgba(0, 0, 0, 0.4); font-weight: 600; font-size: 0.8em"> <div style="color: rgba(0, 0, 0, 0.4); font-weight: 600; font-size: 0.8em">
123-456-789-1234 {{ bankCardInfo.contactNumber }}
</div> </div>
<!-- 备注信息 --> <!-- 备注信息 -->
<div style="font-weight: 500">Other description:</div> <div style="font-weight: 500">Other description:</div>
<div style="color: rgba(0, 0, 0, 0.4); font-weight: 600; font-size: 0.8em"> <div style="color: rgba(0, 0, 0, 0.4); font-weight: 600; font-size: 0.8em">
asdfasdfasdfasdf asfasfsaasdasdfasdfasdfa sfasfasfasfasfsdasdfasdfasf asdfafdsa asdf {{ bankCardInfo.description }}
asdasddsasdsadads
</div> </div>
</div> </div>
</div> </div>
@ -192,7 +190,9 @@ import GeneralHeader from '@/components/GeneralHeader.vue'
import maskLayer from '@/components/MaskLayer.vue' import maskLayer from '@/components/MaskLayer.vue'
import { import {
withdrawableAmount, // withdrawableAmount, //
withdrawApply, //
} from '@/api/lottery' } from '@/api/lottery'
import { setApplyInfo, getApplyInfo } from '@/utils/applyStore.js'
import { ref, onMounted, computed } from 'vue' import { ref, onMounted, computed } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
const router = useRouter() const router = useRouter()
@ -200,9 +200,7 @@ const router = useRouter()
const maskLayerShow = ref(false) const maskLayerShow = ref(false)
const currentAmount = ref(0) // const currentAmount = ref(0) //
const bankCardInfo = ref({ const bankCardInfo = ref({})
// id: '',
})
const hasBankCard = computed(() => { const hasBankCard = computed(() => {
return Object.keys(bankCardInfo.value).length > 0 return Object.keys(bankCardInfo.value).length > 0
}) })
@ -220,6 +218,15 @@ const gotoKYC = () => {
// //
const CashOut = () => { const CashOut = () => {
if (hasBankCard.value) { if (hasBankCard.value) {
try {
const resApply = withdrawApply(getApplyInfo())
if (resApply.status) {
setApplyInfo()
router.go(-1)
}
} catch (error) {
showError(error.errorMsg)
}
} else { } else {
maskLayerShow.value = true maskLayerShow.value = true
} }
@ -242,6 +249,8 @@ const getDrawableAmount = async () => {
onMounted(() => { onMounted(() => {
getDrawableAmount() // getDrawableAmount() //
bankCardInfo.value =
{ ...getApplyInfo(), cardImages: JSON.parse(getApplyInfo().cardImages) } || {}
}) })
</script> </script>

View File

@ -61,19 +61,27 @@
<!-- 上传图片 --> <!-- 上传图片 -->
<div style="font-weight: 600">Passport/lD card:</div> <div style="font-weight: 600">Passport/lD card:</div>
<div style="display: flex; align-items: center"> <div style="display: flex; align-items: center">
<img <div style="position: relative">
src="/src/assets/icon/addImg.png" <img
alt="" src="/src/assets/icon/addImg.png"
style="display: block; width: 15vw; aspect-ratio: 1/1; object-fit: cover" alt=""
/> style="display: block; width: 15vw; aspect-ratio: 1/1; object-fit: cover"
/>
<input
type="file"
@change="handleFileChange"
style="position: absolute; inset: 0; opacity: 0"
/>
</div>
<img <img
src="/src/assets/icon/direction.png" src="/src/assets/icon/direction.png"
alt="" alt=""
style="display: block; width: 5vw; aspect-ratio: 1/1; object-fit: cover" style="display: block; width: 5vw; aspect-ratio: 1/1; object-fit: cover"
/> />
<div style="position: relative"> <div v-for="(imgUrl, index) in previewUrls" style="position: relative">
<img <img
src="/src/assets/icon/addImg.png" :src="imgUrl"
alt="" alt=""
style="display: block; width: 15vw; aspect-ratio: 1/1; object-fit: cover" style="display: block; width: 15vw; aspect-ratio: 1/1; object-fit: cover"
/> />
@ -89,6 +97,7 @@
aspect-ratio: 1/1; aspect-ratio: 1/1;
object-fit: cover; object-fit: cover;
" "
@click="handleCancel(index)"
/> />
</div> </div>
</div> </div>
@ -119,11 +128,10 @@
<script setup> <script setup>
import GeneralHeader from '@/components/GeneralHeader.vue' import GeneralHeader from '@/components/GeneralHeader.vue'
import { ref, onMounted, computed } from 'vue' import { ref, onMounted, computed } from 'vue'
import {
withdrawApply, //
} from '@/api/lottery'
import { showError, showWarning, showInfo, showSuccess } from '@/utils/toast.js' import { showError, showWarning, showInfo, showSuccess } from '@/utils/toast.js'
import { setApplyInfo, getApplyInfo } from '@/utils/applyStore.js'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { uploadImg } from '@/api/upload.js'
const router = useRouter() const router = useRouter()
@ -131,22 +139,77 @@ const cardNum = ref('')
const description = ref('') const description = ref('')
const count = computed(() => description.value.length) const count = computed(() => description.value.length)
const previewUrls = ref([]) //
const tempFiles = ref([]) //
const resImgUrls = ref([]) //
//
//
const handleFileChange = (e) => {
//3
if (previewUrls.value.length < 3) {
const fileList = e.target.files //
// 1.
if (!fileList || fileList.length === 0) {
console.error('未选择文件')
return
}
// 2.
const file = fileList[0]
if (!file.type || !file.type.startsWith('image/')) {
console.error(`不支持的文件类型: ${file.type || '未知'}`)
return
}
// 3.
console.log('文件名:', file.name) // defaultAvatar.png
console.log('文件类型:', file.type) // image/png
console.log('文件大小:', (file.size / 1024).toFixed(2), 'KB')
//
previewUrls.value.push(URL.createObjectURL(file))
//
tempFiles.value.push(file)
// uploadImgUrl()
} else {
showWarning('you can upload up to 3 pictures at most')
}
}
const handleCancel = (index) => {
previewUrls.value.splice(index, 1)
tempFiles.value.splice(index, 1)
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.errorMsg)
}
}
}
// //
const Submit = () => { const Submit = async () => {
await uploadImgUrl()
let data = { let data = {
amount: 10, amount: 10,
contactNumber: cardNum.value, contactNumber: cardNum.value,
description: description.value, description: description.value,
cardImages: '', cardImages: JSON.stringify(resImgUrls.value),
}
try {
const resApply = withdrawApply(data)
if (resApply.status) {
router.go(-1)
}
} catch (error) {
showError(error.errorMsg)
} }
setApplyInfo(data)
router.go(-1)
} }
</script> </script>