This commit is contained in:
2026-06-10 09:51:09 +08:00
parent d85f6d9cb1
commit e3875ebcfb
9 changed files with 470 additions and 279 deletions

View File

@ -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",

View File

@ -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

View File

@ -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;
});
},

View File

@ -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 })
}

BIN
src/assets/images/wx.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

BIN
src/assets/images/ylzf.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

BIN
src/assets/images/zfb.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

@ -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>

View File

@ -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 }">