入金货币,二维码生成

This commit is contained in:
Songsl 2026-05-07 15:58:01 +08:00
parent 47c2ae7d42
commit 0e195a5434
9 changed files with 401 additions and 93 deletions

View File

@ -28,6 +28,7 @@
"element-plus": "^2.13.2",
"file-saver": "^2.0.5",
"pinia": "^3.0.4",
"qrcode": "^1.5.4",
"vue": "^3.5.29",
"vue-router": "^5.0.3",
"xlsx": "^0.18.5"

View File

@ -0,0 +1,18 @@
import { request } from '@/api'
// 获取入金货币列表
export const getCurrencyList = () => {
return request.get('/currency/getCurrencyList')
}
// 创建入金货币
export const createCurrency = (params: any) => {
return request.post('/currency/createCurrency', { ...params })
}
// 编辑入金货币
export const editCurrency = (params: any) => {
return request.post('/currency/editCurrency', { ...params })
}
// 删除入金货币
export const delCurrency = (params: any) => {
return request.post('/currency/delCurrency', { ...params })
}

View File

@ -3,8 +3,14 @@
<div class="header">
<h4>{{ title }}</h4>
<div class="header-actions">
<el-button v-for="(btn, index) in buttons" :key="btn.key || index" :type="btn.type || 'default'"
:icon="btn.icon" @click="handleButtonClick(btn)" :disabled="btn.disabled">
<el-button
v-for="(btn, index) in buttons"
:key="btn.key || index"
:type="btn.type || 'default'"
:icon="btn.icon"
@click="handleButtonClick(btn)"
:disabled="btn.disabled"
>
{{ btn.label }}
</el-button>
</div>
@ -43,3 +49,21 @@ const handleButtonClick = (button: HeaderButton) => {
emit('button-click', button)
}
</script>
<style scoped lang="scss">
.header {
display: flex;
align-items: center;
margin-bottom: 10px;
h4 {
margin: 0 40px 0 0;
font-size: 24px;
font-weight: 500;
}
.header-actions {
display: flex;
align-items: center;
gap: 10px;
}
}
</style>

View File

@ -1,7 +1,162 @@
<script setup lang="ts"></script>
<template>
<h1>入金货币</h1>
<!-- 搜索 -->
<FundDetailHeader title="入金货币" :buttons="buttonsOptions" @button-click="handleButtonClick">
</FundDetailHeader>
<!-- 表格 -->
<FundDetailTable
:table-data="tableData"
:columns="tableColumnsOptions"
@row-click="handleRowClick"
row-key="id"
/>
<FundDetailDialog
:visible="visible"
:title="dialogTitle"
@close="visible = false"
@cancel="visible = false"
@confirm="handleConfirm"
>
<FundDetailForm
:formData="formData"
:formItems="formItems"
:formRules="formRules"
:optionsMap="optionsMap"
></FundDetailForm>
</FundDetailDialog>
</template>
<script setup lang="ts">
import FundDetailHeader from '@/components/common/header.vue'
import FundDetailTable from '@/components/common/Table.vue'
import FundDetailDialog from '@/components/common/Dialog.vue'
import FundDetailForm from '@/components/common/Form.vue'
import type { ButtonProps } from 'element-plus'
import type { Component } from 'vue'
import { buttons, tableColumns, form, rules } from './options'
import {
getCurrencyList,
createCurrency,
delCurrency,
editCurrency,
} from '@/api/modules/capital/currency.ts'
//
interface HeaderButton {
label: string
type?: ButtonProps['type']
icon?: Component
disabled?: boolean
key?: string | number //
}
const buttonsOptions = ref(buttons)
const formItems = ref(form)
const tableData = ref([])
const tableColumnsOptions = ref(tableColumns)
const dialogTitle = ref('')
const formRules = ref(rules)
const formData = ref({
name: '',
symbol: '',
rate: null,
type: 1,
status: 1,
})
const optionsMap = {
type: [
{ label: '法币', value: 1 },
{ label: '数字货币', value: 2 },
],
status: [
{ label: '正常', value: 1 },
{ label: '禁用', value: 2 },
],
}
let rowData: any = {}
const visible = ref<boolean>(false)
const getTableList = async () => {
const data = await getCurrencyList()
tableData.value = (data || []).map((item: any) => {
return {
...item,
typeStr: ['', '法币', '数字货币'][item.type],
statusStr: ['', '正常', '禁用'][item.status],
}
})
console.log('tableData.value', tableData.value)
}
const handleRowClick = (row: any) => {
console.log(row)
rowData = row
}
const resetForm = () => {
formData.value = {
name: '',
symbol: '',
rate: null,
type: 1,
status: 1,
}
}
const handleButtonClick = (button: HeaderButton) => {
console.log(button)
switch (button.key) {
case 'add':
handleAdd()
break
case 'edit':
if (!rowData.id) return ElMessage.warning('请选择数据')
handleEdit()
break
case 'delete':
ElMessageBox.confirm('你确定要删除此数据').then(async () => {
await delCurrency({ id: rowData.id })
ElMessage.success('删除成功')
await getTableList()
})
break
}
}
const handleAdd = () => {
dialogTitle.value = '添加'
visible.value = true
}
const handleEdit = () => {
dialogTitle.value = '编辑'
visible.value = true
formData.value = {
name: rowData.name,
symbol: rowData.symbol,
rate: rowData.rate,
type: rowData.type,
status: rowData.status,
}
}
const handleConfirm = async () => {
if (dialogTitle.value === '添加') {
await createCurrency(formData.value)
} else {
await editCurrency({ ...formData.value, id: rowData.id })
}
visible.value = false
resetForm()
await getTableList()
}
onMounted(() => {
getTableList()
})
</script>
<style scoped lang="scss"></style>

View File

@ -0,0 +1,32 @@
/**
* @description:
*/
export const buttons = [
{ label: '添加', type: 'primary', key: 'add' },
{ label: '编辑', type: 'primary', key: 'edit' },
{ label: '删除', type: 'danger', key: 'delete' },
]
export const tableColumns = [
{ prop: 'name', label: '币名' },
{ prop: 'symbol', label: '货币符号' },
{ prop: 'rate', label: '汇率' },
{ prop: 'typeStr', label: '货币类型' },
{ prop: 'statusStr', label: '状态' },
{ prop: 'created_at', label: '创建时间' },
]
export const form = [
{ prop: 'name', label: '币名', type: 'input', placeholder: '请输入币名' },
{ prop: 'symbol', label: '货币符号', type: 'input', placeholder: '请输入货币符号' },
{ prop: 'rate', label: '汇率', type: 'number', placeholder: '请输入汇率' },
{ prop: 'type', label: '货币类型', type: 'radio', placeholder: '请输入货币类型' },
{ prop: 'status', label: '状态', type: 'radio', placeholder: '请输入状态' },
]
export const rules = {
name: [{ required: true, message: '请输入币名', trigger: ['change'] }],
symbol: [{ required: true, message: '请输入货币符号', trigger: ['change'] }],
rate: [{ required: true, message: '请输入汇率', trigger: ['change'] }],
type: [{ required: true, message: '请输入货币类型', trigger: ['change'] }],
status: [{ required: true, message: '请输入状态', trigger: ['change'] }],
}

View File

@ -154,7 +154,7 @@
</div>
</div>
</el-card>
<el-card class="qr-code">
<el-card class="qr-code" v-if="userInfo.type * 1 === 2 && userInfo.role_id * 1 === 5">
<h3>
推广
<span>统计起始时间:2026-3-20</span>
@ -172,10 +172,7 @@
<div class="item">
<span>推广二维码</span>
<div class="text">
<img :src="qrCode" alt="推广二维码" />
<el-icon @click="handleCopyQrCode">
<DocumentCopy />
</el-icon>
<canvas ref="qrCanvas"></canvas>
</div>
</div>
</div>
@ -215,8 +212,6 @@
<script setup lang="ts">
import { CircleCheckFilled, EditPen, Setting, DocumentCopy } from '@element-plus/icons-vue'
import qrCodeImg from '@/assets/images/qrCode.png'
//
import AgentDialog from '@/components/common/Dialog.vue'
import AgentForm from '@/components/common/Form.vue'
@ -241,6 +236,8 @@ import { useUserStore } from '@/stores/modules/user.ts'
const userStore = useUserStore()
import QRCode from 'qrcode'
/**
* @description: dialog数据
*/
@ -303,8 +300,8 @@ const inputRef = ref<HTMLInputElement>()
/*
* @description: 复制推广二维码和推广链接数据
*/
const copyText = ref('3123123213123123123')
const qrCode = ref(qrCodeImg)
const copyText = ref(userInfo.invite_url)
const qrCanvas = ref()
/**
* @description: 定义公共请求数据方法
@ -322,6 +319,16 @@ const getLeverOptions = async () => {
}
}
const generateBasic = async () => {
const userInfo = getStorage('userInfo')
if (!userInfo?.invite_url || !qrCanvas.value) return
await QRCode.toCanvas(qrCanvas.value, userInfo.invite_url, {
width: 200,
margin: 2,
})
}
/**
* @description: 页面加载完成需要请求的数据
*/
@ -329,6 +336,7 @@ onMounted(() => {
getLeverOptions()
getAccoundList()
getWalletCapitaInfo()
generateBasic()
})
/**
@ -424,36 +432,52 @@ const handleSetting = (id: string) => {
* @description: 复制推广二维码和推广链接方法
*/
const handleCopyUrl = async () => {
try {
await navigator.clipboard.writeText(copyText.value)
ElMessage.success('复制成功!')
} catch (err) {
console.error('复制失败:', err)
ElMessage.error('复制失败,请重试')
const success = await copyToClipboard(copyText.value)
if (success) {
alert('复制成功')
} else {
alert('复制失败,请手动复制')
}
}
const handleCopyQrCode = async () => {
try {
// 1. Blob
const response = await fetch(qrCode.value)
const blob = await response.blob()
// 2. png/jpeg
await navigator.clipboard.write([
new ClipboardItem({
[blob.type]: blob,
}),
])
ElMessage.success('图片复制成功!')
async function copyToClipboard(text: string) {
// 1: 使 Clipboard API
if (navigator.clipboard && navigator.clipboard.writeText) {
try {
await navigator.clipboard.writeText(text)
return true
} catch (err) {
console.error('复制图片失败:', err)
ElMessage.error('图片复制失败,请检查图片格式或权限')
console.warn('Clipboard API 失败,尝试降级方案', err)
}
}
// 2: document.execCommand HTTP
const textarea = document.createElement('textarea')
textarea.value = text
textarea.style.position = 'fixed'
textarea.style.left = '-9999px'
textarea.style.top = '0'
document.body.appendChild(textarea)
textarea.focus()
textarea.select()
try {
const success = document.execCommand('copy')
document.body.removeChild(textarea)
return success
} catch (err) {
document.body.removeChild(textarea)
console.error('复制失败:', err)
return false
}
}
</script>
<style scoped lang="scss">
.agent-container {
width: 100%;
height: 100%;
overflow-y: auto;
.el-card {
margin-bottom: 20px;

View File

@ -68,7 +68,6 @@
import type { FormInstance } from 'element-plus'
import { Plus } from '@element-plus/icons-vue'
//
import MenuHeader from '@/components/common/Header.vue'
import MenuTable from '@/components/common/Table.vue'
import MenuDialog from '@/components/common/Dialog.vue'
import MenuForm from '@/components/common/Form.vue'

View File

@ -104,7 +104,6 @@ import type { FormInstance } from 'element-plus'
import { Plus } from '@element-plus/icons-vue'
//
import RoleHeader from '@/components/common/Header.vue'
import RoleTable from '@/components/common/Table.vue'
import RoleDialog from '@/components/common/Dialog.vue'
import RoleForm from '@/components/common/Form.vue'

View File

@ -5,41 +5,93 @@
<UserTabs v-model="activeTab" :tab-list="tabList" />
<el-form class="user-form" ref="registerFormRef" :model="registerForm" :rules="registerRules">
<Transition name="form-switch" mode="out-in">
<el-form-item class="captcha-item" v-if="activeTab === 'email'" key="form-email-item" prop="email">
<el-form-item
class="captcha-item"
v-if="activeTab === 'email'"
key="form-email-item"
prop="email"
>
<el-input v-model="registerForm.email" placeholder="请输入邮箱" clearable size="large" />
<el-button type="primary" class="captcha-btn" @click="handleSendCode" :disabled="emailIsCounting">{{
emailIsCounting ? `${emailCountdown}s后重新获取` : '发送验证码' }}</el-button>
<el-button
type="primary"
class="captcha-btn"
@click="handleSendCode"
:disabled="emailIsCounting"
>{{ emailIsCounting ? `${emailCountdown}s后重新获取` : '发送验证码' }}</el-button
>
</el-form-item>
<el-form-item class="phone-item captcha-item" prop="phone" v-else-if="activeTab === 'phone'"
key="form-phone-item">
<el-select v-model="registerForm.countryCode" :teleported="false" value-key="code" placeholder="国家"
size="large">
<el-option v-for="item in countryOptions" :key="item.code" :label="item.code" :value="item.code">
<el-form-item
class="phone-item captcha-item"
prop="phone"
v-else-if="activeTab === 'phone'"
key="form-phone-item"
>
<el-select
v-model="registerForm.countryCode"
:teleported="false"
value-key="code"
placeholder="国家"
size="large"
>
<el-option
v-for="item in countryOptions"
:key="item.code"
:label="item.code"
:value="item.code"
>
<div class="country-option">
<span class="country-name">{{ item.name }}</span>
<span class="country-code">{{ item.code }}</span>
</div>
</el-option>
</el-select>
<el-input v-model="registerForm.phone" placeholder="请输入手机号" clearable size="large" />
<el-button type="primary" class="captcha-btn" @click="handleSendCode" :disabled="phoneIsCounting">{{
phoneIsCounting ? `${phoneCountdown}s后重新获取` : '发送验证码' }}</el-button>
<el-input
v-model="registerForm.phone"
placeholder="请输入手机号"
clearable
size="large"
/>
<el-button
type="primary"
class="captcha-btn"
@click="handleSendCode"
:disabled="phoneIsCounting"
>{{ phoneIsCounting ? `${phoneCountdown}s后重新获取` : '发送验证码' }}</el-button
>
</el-form-item>
</Transition>
<UserCaptcha v-model="registerForm.code" />
<el-form-item label="设置密码(6-12位密码包含特殊符号以及大小写)" label-position="top" prop="password">
<el-input placeholder="请输入密码" clearable show-password size="large" type="password"
v-model="registerForm.password" />
<el-form-item
label="设置密码(6-12位密码包含特殊符号以及大小写)"
label-position="top"
prop="password"
>
<el-input
placeholder="请输入密码"
clearable
show-password
size="large"
type="password"
v-model="registerForm.password"
/>
</el-form-item>
<el-form-item label="邀请链接/邀请码(选填)" label-position="top" prop="inviteCode">
<el-input placeholder="请输入邀请链接/邀请码" clearable size="large" type="text" v-model="registerForm.inviteCode" />
<el-input
placeholder="请输入邀请链接/邀请码"
clearable
size="large"
type="text"
v-model="registerForm.inviteCode"
/>
</el-form-item>
<el-form-item>
<el-checkbox label="同意注册协议">我已阅读并同意 隐私条例 退款政策 AMLCTF政策<br> 执行政策 网站条款和条件</el-checkbox>
<el-checkbox label="同意注册协议"
>我已阅读并同意 隐私条例 退款政策 AMLCTF政策<br />
执行政策 网站条款和条件</el-checkbox
>
</el-form-item>
<el-form-item class="form-submit">
<el-button type="primary" class="form-btn" @click="handleRegister">注册</el-button>
@ -66,16 +118,18 @@ import { sendCaptcha, initCaptchaState, clearCaptchaTimer } from '@/utils/sendCa
//
import { countryOptions } from '@/views/user/config/mock'
import { passwordRules, emailRules, phoneRules, codeRules } from './config/rules'
import { useRoute } from 'vue-router'
//store
const userStore = useUserStore()
const route = useRoute()
// UserText
const userText = ref({
backText: '',
title: '注册',
text: '',
});
})
//tabs
const tabList = ref([
@ -92,7 +146,7 @@ const registerForm = ref({
phone: '',
password: '',
code: '',
inviteCode: ''
inviteCode: '',
})
const registerRules = ref<FormRules>({
email: emailRules().email,
@ -102,14 +156,13 @@ const registerRules = ref<FormRules>({
})
//
const emailCaptchaState = initCaptchaState('email'); //
const phoneCaptchaState = initCaptchaState('phone'); //
const emailCaptchaState = initCaptchaState('email') //
const phoneCaptchaState = initCaptchaState('phone') //
//
const { isCounting: emailIsCounting, countdown: emailCountdown } = emailCaptchaState
//
const { isCounting: phoneIsCounting, countdown: phoneCountdown } = phoneCaptchaState
//
//
@ -119,7 +172,7 @@ const sendCodeApi = async () => {
email: registerForm.value.email,
mobile: registerForm.value.phone,
reg_type: activeTab.value === 'email' ? 1 : 2,
type:'resetPass_code'
type: 'reg_code',
}
return userStore.sendCode(sendParams)
}
@ -134,26 +187,26 @@ const handleSendCode = async () => {
}
//
const type: string = activeTab.value === 'phone' ? 'phone' : 'email';
const receiver = type === 'phone' ? registerForm.value.phone : registerForm.value.email;
const currentCaptchaState = type === 'phone' ? phoneCaptchaState : emailCaptchaState;
const sendFunc = type === 'phone' ? "sendPhoneCaptcha" : "sendEmailCaptcha";
const type: string = activeTab.value === 'phone' ? 'phone' : 'email'
const receiver = type === 'phone' ? registerForm.value.phone : registerForm.value.email
const currentCaptchaState = type === 'phone' ? phoneCaptchaState : emailCaptchaState
const sendFunc = type === 'phone' ? 'sendPhoneCaptcha' : 'sendEmailCaptcha'
//
await sendCaptcha(
type,
receiver,
currentCaptchaState, //
() => sendCodeApi() // Promise
);
() => sendCodeApi(), // Promise
)
}
//
const handleRegister = async () => {
if (!registerFormRef.value) return;
if (!registerFormRef.value) return
try {
//
await registerFormRef.value.validate();
await registerFormRef.value.validate()
//
const registerParams = {
@ -162,25 +215,28 @@ const handleRegister = async () => {
reg_type: activeTab.value === 'email' ? 1 : 2,
invite_code: registerForm.value.inviteCode,
code: registerForm.value.code,
password: registerForm.value.password
};
password: registerForm.value.password,
}
//
await userStore.register(registerParams);
await userStore.register(registerParams)
} catch (error) {
console.error('表单校验失败:', error);
console.error('表单校验失败:', error)
}
}
//
onUnmounted(() => {
clearCaptchaTimer(emailCaptchaState);
clearCaptchaTimer(phoneCaptchaState);
});
clearCaptchaTimer(emailCaptchaState)
clearCaptchaTimer(phoneCaptchaState)
})
onMounted(()=>{
registerForm.value.inviteCode = route.query.invite_code
})
</script>
<style scoped lang="scss">
.user-form {
.phone-item,
.captcha-item {
:deep(.el-form-item__content) {