This commit is contained in:
parent
d85f6d9cb1
commit
e3875ebcfb
|
|
@ -43,6 +43,7 @@
|
|||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/file-saver": "^2.0.7",
|
||||
"@types/node": "^24.10.13",
|
||||
"@types/qrcode": "^1.5.6",
|
||||
"@types/xlsx": "^0.0.36",
|
||||
"@vitejs/plugin-basic-ssl": "^2.3.0",
|
||||
"@vitejs/plugin-vue": "^6.0.4",
|
||||
|
|
|
|||
|
|
@ -84,6 +84,9 @@ importers:
|
|||
'@types/node':
|
||||
specifier: ^24.10.13
|
||||
version: 24.10.15
|
||||
'@types/qrcode':
|
||||
specifier: ^1.5.6
|
||||
version: 1.5.6
|
||||
'@types/xlsx':
|
||||
specifier: ^0.0.36
|
||||
version: 0.0.36
|
||||
|
|
@ -967,6 +970,9 @@ packages:
|
|||
'@types/offscreencanvas@2019.3.0':
|
||||
resolution: {integrity: sha512-esIJx9bQg+QYF0ra8GnvfianIY8qWB0GBx54PK5Eps6m+xTj86KLavHv6qDhzKcu5UUOgNfJ2pWaIIV7TRUd9Q==}
|
||||
|
||||
'@types/qrcode@1.5.6':
|
||||
resolution: {integrity: sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==}
|
||||
|
||||
'@types/readable-stream@4.0.23':
|
||||
resolution: {integrity: sha512-wwXrtQvbMHxCbBgjHaMGEmImFTQxxpfMOR/ZoQnXxB1woqkUbdLGFDgauo00Py9IudiaqSeiBiulSV9i6XIPig==}
|
||||
|
||||
|
|
@ -3403,6 +3409,10 @@ snapshots:
|
|||
|
||||
'@types/offscreencanvas@2019.3.0': {}
|
||||
|
||||
'@types/qrcode@1.5.6':
|
||||
dependencies:
|
||||
'@types/node': 24.10.15
|
||||
|
||||
'@types/readable-stream@4.0.23':
|
||||
dependencies:
|
||||
'@types/node': 24.10.15
|
||||
|
|
|
|||
|
|
@ -1,10 +1,15 @@
|
|||
import axios from 'axios'
|
||||
import type { AxiosResponse, AxiosError, InternalAxiosRequestConfig } from 'axios'
|
||||
import type { AxiosResponse, AxiosError, AxiosRequestConfig } from 'axios'
|
||||
import type { ApiResponse } from '@/api/types'
|
||||
import { ElNotification } from 'element-plus'
|
||||
import { getStorage, setStorage, removeStorage } from '@/utils/storage'
|
||||
import router from '../router'
|
||||
|
||||
// 扩展 AxiosRequestConfig,添加 showMessage 属性
|
||||
export interface RequestConfig<T = unknown> extends AxiosRequestConfig<T> {
|
||||
showMessage?: boolean
|
||||
}
|
||||
|
||||
// 记录当前请求数量,用于控制顶部进度条
|
||||
let requestCount = 0
|
||||
|
||||
|
|
@ -44,14 +49,14 @@ const service = axios.create({
|
|||
})
|
||||
|
||||
// 3. 请求拦截器:直接使用 axios 内置类型
|
||||
const requestInterceptor = (config: InternalAxiosRequestConfig) => {
|
||||
const requestInterceptor = (config: RequestConfig) => {
|
||||
console.log('请求配置', config)
|
||||
|
||||
// 显示顶部进度条
|
||||
showLoading()
|
||||
// 添加token到请求头
|
||||
const token = getStorage('token')
|
||||
if (token) {
|
||||
if (token && config.headers) {
|
||||
config.headers.token = token
|
||||
}
|
||||
return config
|
||||
|
|
@ -75,7 +80,7 @@ service.interceptors.request.use(requestInterceptor, requestErrorInterceptor)
|
|||
const responseInterceptor = <T = unknown>(response: AxiosResponse<ApiResponse<T>>) => {
|
||||
//关闭loading(无论成功失败都要关)
|
||||
hideLoading()
|
||||
const config = response.config
|
||||
const config = response.config as RequestConfig
|
||||
// axios 会将响应头规范化为小写,同时尝试大小写版本以兼容不同服务器
|
||||
const newToken = response.headers['new-token'] || response.headers['New-Token']
|
||||
if (newToken) {
|
||||
|
|
@ -176,7 +181,7 @@ const responseErrorInterceptor = (error: AxiosError) => {
|
|||
errMsg = error.message || '服务器错误'
|
||||
}
|
||||
|
||||
if (error.config?.showMessage !== false) {
|
||||
if ((error.config as RequestConfig)?.showMessage !== false) {
|
||||
ElNotification({
|
||||
message: errMsg,
|
||||
type: 'error',
|
||||
|
|
@ -191,13 +196,13 @@ service.interceptors.response.use(responseInterceptor, responseErrorInterceptor)
|
|||
|
||||
// 5. 封装通用请求方法
|
||||
export const request = {
|
||||
get<T = unknown>(url: string, params?: unknown, config?: InternalAxiosRequestConfig): Promise<T> {
|
||||
return service.get<ApiResponse<T>>(url, { params, ...config,showMessage:false }).then((res) => {
|
||||
get<T = unknown>(url: string, params?: unknown, config?: RequestConfig): Promise<T> {
|
||||
return service.get<ApiResponse<T>>(url, { params, ...config }).then((res) => {
|
||||
return res as T;
|
||||
});
|
||||
},
|
||||
post<T = unknown>(url: string, data?: unknown, config?: InternalAxiosRequestConfig): Promise<T> {
|
||||
return service.post<ApiResponse<T>>(url, data, { ...config, showMessage: true,}).then((res) => {
|
||||
post<T = unknown>(url: string, data?: unknown, config?: RequestConfig): Promise<T> {
|
||||
return service.post<ApiResponse<T>>(url, data, config).then((res) => {
|
||||
return res as T;
|
||||
});
|
||||
},
|
||||
|
|
|
|||
|
|
@ -26,3 +26,24 @@ export const getRechargeChannelList = () => {
|
|||
export const rechargeApi = (params: any) => {
|
||||
return request.post('/user/recharge', { ...params })
|
||||
}
|
||||
|
||||
// 获取支付申请引用(扫码支付前必须调用)
|
||||
export const getApplyCustomerRef = () => {
|
||||
return request.post('/delphiPay/getApplyCustomerRef', undefined, { showMessage: false })
|
||||
}
|
||||
|
||||
// 获取KYC详情
|
||||
export const getKycDetail = () => {
|
||||
return request.post('/delphiPay/getKycDetail', undefined, { showMessage: false })
|
||||
}
|
||||
|
||||
// 创建买入订单
|
||||
export const createBuyEx = (data: {
|
||||
channel_id: number
|
||||
amount: string
|
||||
payment_method: string
|
||||
bh?: string
|
||||
pass?: string
|
||||
}) => {
|
||||
return request.post('/delphiPay/createBuyEx', data, { showMessage: false })
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 68 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 73 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
|
|
@ -4,7 +4,8 @@
|
|||
<div class="page-title">存款</div>
|
||||
|
||||
<!-- 存款渠道列表:每行提供一个"存款"按钮 -->
|
||||
<RechangeTable :table-data="channelList" :columns="tableColumns" :highlight-current-row="false" :loading="loading">
|
||||
<RechangeTable :table-data="channelList" :columns="tableColumns" :highlight-current-row="false"
|
||||
:loading="loading">
|
||||
<template #action="{ row }">
|
||||
<el-button type="primary" size="small" @click="handleOpenDialog(row)">
|
||||
存款
|
||||
|
|
@ -14,109 +15,241 @@
|
|||
</div>
|
||||
|
||||
<!-- 存款弹窗 -->
|
||||
<RechangeDialog class="rechange-pay-dialog" :visible="dialogVisible" title="存款" width="600px" :show-footer="false"
|
||||
<RechangeDialog class="rechange-pay-dialog" :visible="dialogVisible" title="存款" width="500px" :show-footer="false"
|
||||
@cancel="handleCancel" @close="handleClose">
|
||||
<el-form ref="rechangeFormRef" :model="rechangeForm" :rules="rechangeFormRules" label-position="top">
|
||||
<!-- 步骤1:选择金额和支付方式 -->
|
||||
<template v-if="payStep === 1">
|
||||
<!-- 渠道信息 -->
|
||||
<div class="channel-card">
|
||||
<div class="channel-info">
|
||||
<div class="channel-name">{{ rechangeForm.channelName }}</div>
|
||||
<div class="channel-meta">
|
||||
<span></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="channel-meta">支持货币: {{ rechangeForm.currencySymbol }}</div>
|
||||
<div class="channel-meta">支持货币: AUD (澳元)</div>
|
||||
</div>
|
||||
|
||||
<!-- 银行卡信息(channel_code = BANK 时显示) -->
|
||||
<template v-if="rechangeForm.channelCode === 'BANK'">
|
||||
<div class="info-section">
|
||||
<div class="section-title">收款信息</div>
|
||||
<div class="info-grid">
|
||||
<div class="info-item">
|
||||
<span class="label">账户持有人姓名</span>
|
||||
<span class="value">{{ rechangeForm.beneficiaryName }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">银行名称</span>
|
||||
<span class="value">{{ rechangeForm.beneficiaryBankName }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">银行识别代码</span>
|
||||
<span class="value">{{ rechangeForm.swiftBicCode }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">收款人银行账号</span>
|
||||
<span class="value">{{ rechangeForm.beneficiaryBankAccount }}</span>
|
||||
</div>
|
||||
<div class="info-item full-width">
|
||||
<span class="label">银行地址</span>
|
||||
<span class="value">{{ rechangeForm.beneficiaryBankAddress }}</span>
|
||||
</div>
|
||||
<!-- 快捷金额选择 -->
|
||||
<div class="quick-amount">
|
||||
<div class="section-title">选择充值金额</div>
|
||||
<div class="amount-list">
|
||||
<div v-for="(amount, index) in quickAmounts" :key="index" class="amount-item"
|
||||
:class="{ active: rechangeForm.depositAmount === amount.toString() }"
|
||||
@click="handleQuickAmount(amount)">
|
||||
{{ amount }} AUD
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- 钱包地址信息(其他渠道显示) -->
|
||||
<template v-else>
|
||||
<div class="info-section">
|
||||
<div class="section-title">收款信息</div>
|
||||
<div class="info-item full-width">
|
||||
<span class="label">钱包收款地址</span>
|
||||
<span class="value address-value">{{ rechangeForm.address }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 充值金额 -->
|
||||
<el-form-item label="充值金额" prop="depositAmount">
|
||||
<el-input v-model="rechangeForm.depositAmount" placeholder="请输入充值金额" size="large" @input="handleAmountInput">
|
||||
<!-- 自定义金额 -->
|
||||
<el-form-item label="自定义金额">
|
||||
<el-input v-model="rechangeForm.depositAmount" placeholder="请输入充值金额" size="large"
|
||||
@input="handleAmountInput">
|
||||
<template #suffix>
|
||||
<span class="currency-suffix">{{ rechangeForm.currencySymbol }}</span>
|
||||
<span class="currency-suffix">AUD</span>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 上传凭证 -->
|
||||
<el-form-item label="上传凭证" prop="voucherUrl">
|
||||
<el-upload class="voucher-uploader" action="" :show-file-list="false"
|
||||
:http-request="handleUpload" :before-upload="beforeAvatarUpload" :loading="uploadLoading">
|
||||
<img v-if="voucherUrl" :src="voucherUrl" class="voucher-image" />
|
||||
<div v-else class="upload-placeholder">
|
||||
<el-icon class="upload-icon">
|
||||
<Plus />
|
||||
</el-icon>
|
||||
<span class="upload-text">点击上传凭证</span>
|
||||
<!-- 支付方式选择 -->
|
||||
<div class="payment-methods">
|
||||
<div class="section-title">选择支付方式</div>
|
||||
<div class="method-list">
|
||||
<div v-for="method in paymentMethods" :key="method.code" class="method-item"
|
||||
:class="{ active: selectedMethod === method.code }" @click="handleSelectMethod(method.code)">
|
||||
<div class="method-icon">
|
||||
<img :src="method.icon" :alt="method.name" />
|
||||
</div>
|
||||
<span class="method-name">{{ method.name }}</span>
|
||||
<div class="method-check" v-if="selectedMethod === method.code">
|
||||
<el-icon>
|
||||
<Check />
|
||||
</el-icon>
|
||||
</div>
|
||||
</div>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 按钮区 -->
|
||||
<div class="dialog-actions">
|
||||
<el-button size="large" @click="handleCancel">取消</el-button>
|
||||
<el-button type="primary" size="large" :loading="submitLoading" @click="handleSubmit">
|
||||
确认充值
|
||||
</el-button>
|
||||
</div>
|
||||
<!-- 按钮区 -->
|
||||
<div class="dialog-actions">
|
||||
<el-button size="large" @click="handleCancel">取消</el-button>
|
||||
<el-button type="primary" size="large" :loading="submitLoading"
|
||||
:disabled="!rechangeForm.depositAmount || !selectedMethod" @click="handleCreateOrder">
|
||||
确认支付
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 步骤2:支付中/跳转 -->
|
||||
<template v-else-if="payStep === 2">
|
||||
<!-- 移动端:直接跳转 -->
|
||||
<template v-if="isMobileDevice">
|
||||
<div class="loading-section">
|
||||
<el-icon class="is-loading" size="48">
|
||||
<Loading />
|
||||
</el-icon>
|
||||
<div class="loading-text">正在跳转支付页面...</div>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 桌面端:显示二维码 -->
|
||||
<template v-else>
|
||||
<div class="qrcode-section">
|
||||
<div class="section-title">请使用{{ getMethodName(selectedMethod) }}扫码支付</div>
|
||||
<div class="qrcode-container">
|
||||
<div v-show="qrcodeLoading">
|
||||
<el-icon class="is-loading" size="48">
|
||||
<Loading />
|
||||
</el-icon>
|
||||
</div>
|
||||
<div v-show="!qrcodeLoading">
|
||||
<img v-if="qrcodeImageUrl" :src="qrcodeImageUrl" alt="支付二维码" class="qrcode-image" />
|
||||
<div v-else ref="qrcodeRef" class="qrcode"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="qrcode-tip">请使用{{ getMethodName(selectedMethod) }}扫描上方二维码完成支付</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</RechangeDialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Plus } from '@element-plus/icons-vue'
|
||||
import { Check, Loading } from '@element-plus/icons-vue'
|
||||
|
||||
defineOptions({
|
||||
name: 'Rechange'
|
||||
})
|
||||
import type { FormInstance, FormRules, UploadProps } from 'element-plus'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { nextTick, onMounted, ref } from 'vue'
|
||||
import { nextTick, onMounted, ref, computed } from 'vue'
|
||||
import { useUserStore } from '@/stores/modules/user'
|
||||
import RechangeTable from '@/components/common/Table.vue'
|
||||
import RechangeDialog from '@/components/common/Dialog.vue'
|
||||
import { usePageLoading } from '@/composables/useLoading'
|
||||
import { rechangeTableColumns } from './options'
|
||||
import { uploadImageApi } from '@/api/modules/upload'
|
||||
import { getRechargeChannelList, rechargeApi } from '@/api/modules/capital/rechangeChanl'
|
||||
import { getRechargeChannelList, getApplyCustomerRef, getKycDetail, createBuyEx } from '@/api/modules/capital/rechangeChanl'
|
||||
import QRCode from 'qrcode'
|
||||
import alipayIcon from '@/assets/images/zfb.png'
|
||||
import wechatIcon from '@/assets/images/wx.png'
|
||||
import unionpayIcon from '@/assets/images/ylzf.png'
|
||||
|
||||
// 获取用户信息
|
||||
const userStore = useUserStore()
|
||||
const userInfo = userStore.userInfo as any
|
||||
const bhVal = ref(userInfo?.username || '')
|
||||
const passVal = ref('')
|
||||
|
||||
// 支付方式类型
|
||||
interface PaymentMethod {
|
||||
code: string
|
||||
name: string
|
||||
icon: string
|
||||
}
|
||||
|
||||
// 支付方式列表
|
||||
const paymentMethods = ref<PaymentMethod[]>([
|
||||
{ code: 'alipay', name: '支付宝', icon: alipayIcon },
|
||||
{ code: 'wechat', name: '微信支付', icon: wechatIcon },
|
||||
{ code: 'unionpay', name: '银联支付', icon: unionpayIcon },
|
||||
])
|
||||
|
||||
// 获取支付方式名称
|
||||
const getMethodName = (code: string): string => {
|
||||
const method = paymentMethods.value.find(m => m.code === code)
|
||||
return method?.name || ''
|
||||
}
|
||||
|
||||
// 快捷金额列表
|
||||
const quickAmounts = ref<number[]>([3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000])
|
||||
|
||||
// 支付步骤:1-选择金额和支付方式,2-支付中
|
||||
const payStep = ref(1)
|
||||
const selectedMethod = ref('')
|
||||
|
||||
// 表格列配置
|
||||
const tableColumns = ref(rechangeTableColumns)
|
||||
|
||||
// 检测是否为移动设备
|
||||
const isMobileDevice = computed(() => {
|
||||
// console.log('navigator.userAgent:', navigator.userAgent);
|
||||
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)
|
||||
})
|
||||
|
||||
// 二维码容器引用
|
||||
const qrcodeRef = ref<HTMLElement | null>(null)
|
||||
|
||||
// 二维码加载状态
|
||||
const qrcodeLoading = ref(false)
|
||||
|
||||
// 二维码图片URL
|
||||
const qrcodeImageUrl = ref('')
|
||||
|
||||
// 生成二维码
|
||||
const generateQRCode = async (url: string) => {
|
||||
await nextTick()
|
||||
if (qrcodeRef.value) {
|
||||
try {
|
||||
const dataUrl = await QRCode.toDataURL(url, {
|
||||
width: 200,
|
||||
margin: 2,
|
||||
})
|
||||
qrcodeImageUrl.value = dataUrl
|
||||
} catch (error) {
|
||||
console.error('生成二维码失败:', error)
|
||||
ElMessage.error('生成二维码失败')
|
||||
} finally {
|
||||
qrcodeLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 弹窗基础状态
|
||||
const dialogVisible = ref(false)
|
||||
const rechangeFormRef = ref<FormInstance>()
|
||||
const submitLoading = ref(false)
|
||||
|
||||
const { loading, startLoading, stopLoading } = usePageLoading()
|
||||
// 渠道列表数据
|
||||
const channelList = ref<ChannelRow[]>([])
|
||||
|
||||
// 弹窗表单数据
|
||||
interface RechangeFormData {
|
||||
id: number
|
||||
channelName: string
|
||||
channelCode: string
|
||||
currencySymbol: string
|
||||
channelFee: string
|
||||
depositAmount: string
|
||||
beneficiaryName: string
|
||||
beneficiaryBankName: string
|
||||
swiftBicCode: string
|
||||
beneficiaryBankAddress: string
|
||||
beneficiaryBankAccount: string
|
||||
address: string
|
||||
}
|
||||
|
||||
const createDefaultForm = (): RechangeFormData => ({
|
||||
id: 0,
|
||||
channelName: '',
|
||||
channelCode: '',
|
||||
currencySymbol: '',
|
||||
channelFee: '0',
|
||||
depositAmount: '',
|
||||
beneficiaryName: '',
|
||||
beneficiaryBankName: '',
|
||||
swiftBicCode: '',
|
||||
beneficiaryBankAddress: '',
|
||||
beneficiaryBankAccount: '',
|
||||
address: '',
|
||||
})
|
||||
|
||||
const rechangeForm = ref<RechangeFormData>(createDefaultForm())
|
||||
|
||||
// 表单校验规则
|
||||
const rechangeFormRules = ref<FormRules<RechangeFormData>>({
|
||||
depositAmount: [
|
||||
{ required: true, message: '请输入充值金额', trigger: ['blur', 'change'] },
|
||||
{ pattern: /^\d+$/, message: '请输入整数金额', trigger: ['blur', 'change'] },
|
||||
],
|
||||
})
|
||||
|
||||
// 渠道数据接口
|
||||
interface ChannelCurrency {
|
||||
|
|
@ -140,68 +273,6 @@ interface ChannelRow {
|
|||
address?: string
|
||||
}
|
||||
|
||||
// 表格列配置
|
||||
const tableColumns = ref(rechangeTableColumns)
|
||||
|
||||
// 弹窗基础状态
|
||||
const dialogVisible = ref(false)
|
||||
const rechangeFormRef = ref<FormInstance>()
|
||||
const submitLoading = ref(false)
|
||||
|
||||
// 上传凭证相关
|
||||
const voucherUrl = ref('')
|
||||
const uploadLoading = ref(false)
|
||||
|
||||
const { loading, startLoading, stopLoading } = usePageLoading()
|
||||
// 渠道列表数据
|
||||
const channelList = ref<ChannelRow[]>([])
|
||||
|
||||
// 弹窗表单数据
|
||||
interface RechangeFormData {
|
||||
id: number
|
||||
channelName: string
|
||||
channelCode: string
|
||||
currencySymbol: string
|
||||
channelFee: string
|
||||
depositAmount: string
|
||||
voucherUrl: string
|
||||
beneficiaryName: string
|
||||
beneficiaryBankName: string
|
||||
swiftBicCode: string
|
||||
beneficiaryBankAddress: string
|
||||
beneficiaryBankAccount: string
|
||||
address: string
|
||||
}
|
||||
|
||||
const createDefaultForm = (): RechangeFormData => ({
|
||||
id: 0,
|
||||
channelName: '',
|
||||
channelCode: '',
|
||||
currencySymbol: '',
|
||||
channelFee: '0',
|
||||
depositAmount: '',
|
||||
voucherUrl: '',
|
||||
beneficiaryName: '',
|
||||
beneficiaryBankName: '',
|
||||
swiftBicCode: '',
|
||||
beneficiaryBankAddress: '',
|
||||
beneficiaryBankAccount: '',
|
||||
address: '',
|
||||
})
|
||||
|
||||
const rechangeForm = ref<RechangeFormData>(createDefaultForm())
|
||||
|
||||
// 表单校验规则
|
||||
const rechangeFormRules = ref<FormRules<RechangeFormData>>({
|
||||
depositAmount: [
|
||||
{ required: true, message: '请输入充值金额', trigger: ['blur', 'change'] },
|
||||
{ pattern: /^\d+$/, message: '请输入整数金额', trigger: ['blur', 'change'] },
|
||||
],
|
||||
voucherUrl: [
|
||||
{ required: true, message: '请上传充值凭证', trigger: ['change'] },
|
||||
],
|
||||
})
|
||||
|
||||
// 获取渠道列表
|
||||
const fetchChannelList = async () => {
|
||||
startLoading()
|
||||
|
|
@ -221,10 +292,9 @@ const handleOpenDialog = async (row: ChannelRow) => {
|
|||
id: row.id,
|
||||
channelName: row.name,
|
||||
channelCode: row.channel_code,
|
||||
currencySymbol: row.currency.symbol,
|
||||
currencySymbol: 'AUD',
|
||||
channelFee: row.channel_fee || '0',
|
||||
depositAmount: '',
|
||||
voucherUrl: '',
|
||||
beneficiaryName: row.beneficiary_name || '',
|
||||
beneficiaryBankName: row.beneficiary_bank_name || '',
|
||||
swiftBicCode: row.swift_bic_code || '',
|
||||
|
|
@ -232,52 +302,18 @@ const handleOpenDialog = async (row: ChannelRow) => {
|
|||
beneficiaryBankAccount: row.beneficiary_bank_account || '',
|
||||
address: (row as any).address || '',
|
||||
}
|
||||
voucherUrl.value = ''
|
||||
// 重置支付状态
|
||||
payStep.value = 1
|
||||
selectedMethod.value = ''
|
||||
|
||||
dialogVisible.value = true
|
||||
await nextTick()
|
||||
rechangeFormRef.value?.clearValidate()
|
||||
}
|
||||
|
||||
// 上传凭证相关
|
||||
const handleAvatarSuccess: UploadProps['onSuccess'] = (response: any) => {
|
||||
if (response.code === 0) {
|
||||
voucherUrl.value = response.url
|
||||
rechangeForm.value.voucherUrl = response.url
|
||||
ElMessage.success('上传成功')
|
||||
} else {
|
||||
ElMessage.error(response.msg || '上传失败')
|
||||
}
|
||||
uploadLoading.value = false
|
||||
}
|
||||
|
||||
const beforeAvatarUpload: UploadProps['beforeUpload'] = (rawFile) => {
|
||||
if (rawFile.type !== 'image/jpeg' && rawFile.type !== 'image/png') {
|
||||
ElMessage.error('图片格式必须为 JPG 或 PNG!')
|
||||
return false
|
||||
} else if (rawFile.size / 1024 / 1024 > 2) {
|
||||
ElMessage.error('图片大小不能超过 2MB!')
|
||||
return false
|
||||
}
|
||||
uploadLoading.value = true
|
||||
return true
|
||||
}
|
||||
|
||||
// 自定义上传方法
|
||||
const handleUpload = async (options: any) => {
|
||||
const { file, onSuccess, onError } = options
|
||||
try {
|
||||
const res: any = await uploadImageApi(file)
|
||||
voucherUrl.value = res.url
|
||||
rechangeForm.value.voucherUrl = res.url
|
||||
onSuccess(res)
|
||||
// 上传成功后手动清除该字段的验证状态
|
||||
nextTick(() => {
|
||||
rechangeFormRef.value?.clearValidate('voucherUrl')
|
||||
})
|
||||
} catch (error) {
|
||||
onError(error)
|
||||
uploadLoading.value = false
|
||||
}
|
||||
// 选择快捷金额
|
||||
const handleQuickAmount = (amount: number) => {
|
||||
rechangeForm.value.depositAmount = amount.toString()
|
||||
}
|
||||
|
||||
// 金额输入处理:只允许整数
|
||||
|
|
@ -286,21 +322,60 @@ const handleAmountInput = (value: string) => {
|
|||
rechangeForm.value.depositAmount = filtered
|
||||
}
|
||||
|
||||
// 提交充值
|
||||
const handleSubmit = async () => {
|
||||
// 选择支付方式
|
||||
const handleSelectMethod = (code: string) => {
|
||||
selectedMethod.value = code
|
||||
}
|
||||
|
||||
// 创建支付订单并跳转支付页面
|
||||
const handleCreateOrder = async () => {
|
||||
try {
|
||||
await rechangeFormRef.value?.validate()
|
||||
submitLoading.value = true
|
||||
await rechargeApi({
|
||||
qrcodeLoading.value = true
|
||||
payStep.value = 2
|
||||
|
||||
// 第一步:调用支付申请引用接口
|
||||
await getApplyCustomerRef()
|
||||
|
||||
// 第二步:获取KYC详情
|
||||
const kycD: any = await getKycDetail()
|
||||
if (kycD.status !== 'approved') {
|
||||
ElMessage.error('订单创建失败')
|
||||
return
|
||||
}
|
||||
// 第三步:创建买入订单获取跳转URL
|
||||
const res: any = await createBuyEx({
|
||||
channel_id: rechangeForm.value.id,
|
||||
amount: rechangeForm.value.depositAmount,
|
||||
img: rechangeForm.value.voucherUrl,
|
||||
payment_method: selectedMethod.value,
|
||||
bh: bhVal.value,
|
||||
})
|
||||
ElMessage.success('充值提交成功')
|
||||
dialogVisible.value = false
|
||||
fetchChannelList()
|
||||
|
||||
// 获取跳转URL并打开支付页面
|
||||
const payUrl = res.payment_urls[0]
|
||||
if (payUrl) {
|
||||
if (isMobileDevice.value) {
|
||||
// 移动端:直接跳转支付页面
|
||||
ElMessage.success('正在跳转支付页面...')
|
||||
setTimeout(() => {
|
||||
window.location.href = payUrl
|
||||
// 重置状态
|
||||
handleCancel()
|
||||
}, 500)
|
||||
} else {
|
||||
// 桌面端:生成二维码
|
||||
ElMessage.success('正在生成支付二维码...')
|
||||
await generateQRCode(payUrl)
|
||||
}
|
||||
} else {
|
||||
ElMessage.error('获取支付链接失败')
|
||||
payStep.value = 1
|
||||
}
|
||||
} catch (error: any) {
|
||||
ElMessage.error(error?.msg || '充值提交失败')
|
||||
console.log('error:', error);
|
||||
ElMessage.error(error?.msg || '创建支付订单失败')
|
||||
payStep.value = 1
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
|
|
@ -311,13 +386,15 @@ const handleCancel = () => {
|
|||
dialogVisible.value = false
|
||||
rechangeFormRef.value?.resetFields()
|
||||
rechangeForm.value = createDefaultForm()
|
||||
qrcodeImageUrl.value = ''
|
||||
qrcodeLoading.value = false
|
||||
payStep.value = 1
|
||||
selectedMethod.value = ''
|
||||
}
|
||||
|
||||
// 关闭弹窗
|
||||
const handleClose = () => {
|
||||
dialogVisible.value = false
|
||||
rechangeFormRef.value?.resetFields()
|
||||
rechangeForm.value = createDefaultForm()
|
||||
handleCancel()
|
||||
}
|
||||
|
||||
// 页面加载时获取渠道列表
|
||||
|
|
@ -329,7 +406,8 @@ onMounted(() => {
|
|||
<style scoped lang="scss">
|
||||
.rechange-page {
|
||||
max-height: 100%;
|
||||
height: 100%;
|
||||
height: 100%;
|
||||
|
||||
.page-title {
|
||||
margin-bottom: 16px;
|
||||
font-size: 28px;
|
||||
|
|
@ -362,20 +440,10 @@ height: 100%;
|
|||
opacity: 0.9;
|
||||
}
|
||||
}
|
||||
|
||||
.channel-fee {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
padding: 8px 16px;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.info-section {
|
||||
background: #f5f7fa;
|
||||
border-radius: 10px;
|
||||
padding: 16px;
|
||||
// 快捷金额选择
|
||||
.quick-amount {
|
||||
margin-bottom: 20px;
|
||||
|
||||
.section-title {
|
||||
|
|
@ -383,94 +451,133 @@ height: 100%;
|
|||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid #e4e7ed;
|
||||
}
|
||||
|
||||
.info-grid {
|
||||
.amount-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 12px;
|
||||
|
||||
.info-item {
|
||||
.amount-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 12px;
|
||||
border: 2px solid #e4e7ed;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: #409eff;
|
||||
background: #f0f7ff;
|
||||
}
|
||||
|
||||
&.active {
|
||||
border-color: #409eff;
|
||||
background: #ecf5ff;
|
||||
color: #409eff;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 支付方式样式
|
||||
.payment-methods {
|
||||
margin-bottom: 20px;
|
||||
|
||||
.section-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.method-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 12px;
|
||||
|
||||
.method-item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 16px;
|
||||
border: 2px solid #e4e7ed;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&.full-width {
|
||||
grid-column: span 2;
|
||||
&:hover {
|
||||
border-color: #409eff;
|
||||
background: #f0f7ff;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 13px;
|
||||
color: #909399;
|
||||
&.active {
|
||||
border-color: #409eff;
|
||||
background: #ecf5ff;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-size: 14px;
|
||||
color: #303133;
|
||||
word-break: break-all;
|
||||
.method-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&.address-value {
|
||||
font-family: monospace;
|
||||
background: #fff;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
||||
.method-name {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.method-check {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: #409eff;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.currency-suffix {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #606266;
|
||||
}
|
||||
// 加载状态
|
||||
.loading-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 60px 20px;
|
||||
text-align: center;
|
||||
|
||||
// 上传凭证样式
|
||||
.voucher-uploader {
|
||||
width: 100%;
|
||||
height: 160px;
|
||||
border: 1px dashed var(--el-border-color);
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
transition: var(--el-transition-duration-fast);
|
||||
|
||||
&:hover {
|
||||
border-color: var(--el-color-primary);
|
||||
.el-icon {
|
||||
margin-bottom: 20px;
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
:deep(.el-upload) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.voucher-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.upload-placeholder {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
color: #909399;
|
||||
|
||||
.upload-icon {
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
.upload-text {
|
||||
font-size: 14px;
|
||||
}
|
||||
.loading-text {
|
||||
font-size: 16px;
|
||||
color: #606266;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -506,4 +613,51 @@ height: 100%;
|
|||
:deep(.el-form-item__label) {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.currency-suffix {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
// 二维码样式
|
||||
.qrcode-section {
|
||||
text-align: center;
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.qrcode-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin: 24px 0;
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.qrcode-image {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.qrcode {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
canvas {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.qrcode-tip {
|
||||
color: #909399;
|
||||
font-size: 14px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@
|
|||
<template #floatProfitLoss="{ row }">
|
||||
<span>{{ getMarketFloatProfit(row.id) }}</span>
|
||||
</template>
|
||||
<template #s="{ row }">
|
||||
<template #closeProfitLoss="{ row }">
|
||||
<span>{{ getMarketCloseProfit(row.id) }}</span>
|
||||
</template>
|
||||
<template #pointSpread="{ row }">
|
||||
|
|
|
|||
Loading…
Reference in New Issue