This commit is contained in:
李远 2026-03-20 18:04:07 +08:00
parent 8d632dd3f8
commit 09b2d83c88
8 changed files with 684 additions and 4 deletions

View File

@ -0,0 +1,24 @@
import { request } from '@/api';
//创建子账户
export const createAgentApi = (params: any) => {
return request.post('user/createAccount', {...params})
}
//获取用户杠杆选项
export const getLeverOptionsApi = () => {
return request.get('/user/getLeverOptions')
}
//编辑账户名称
export const editAgentNameApi = (params: any) => {
return request.post('/user/editAccountName', {...params})
}
//重置密码
export const resetPasswordApi = (params: any) => {
return request.post('/user/editLoginPass', {...params})
}
//修改杠杆
export const leverageLeverApi = (params: any) => {
return request.post('/user/editLever', {...params})
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

View File

@ -37,6 +37,7 @@ body,
main {
padding: 20px;
overflow: auto;
max-height: calc(1080px - 200px);
}
}
}

View File

@ -9,7 +9,9 @@
<!-- 输入框 -->
<el-input v-if="item.type === 'input'" v-model="formData[item.prop]"
:placeholder="item.placeholder" :disabled="item.disabled" clearable />
<!-- 密码输入框 -->
<el-input v-else-if="item.type === 'password'" v-model="formData[item.prop]"
:placeholder="item.placeholder" :disabled="item.disabled" clearable show-password type="password" />
<!-- 下拉选择框 -->
<el-select v-else-if="item.type === 'select'" v-model="formData[item.prop]"
:placeholder="item.placeholder" :disabled="item.disabled" clearable style="width: 100%"
@ -76,7 +78,7 @@ import type { FormInstance, FormRules } from 'element-plus'
import type { Component } from 'vue'
interface FormItem {
type: 'input' | 'select' | 'number' | 'radio' | 'slot' | 'checkbox' | 'date' | 'switch' | 'edit' | 'time'
type: 'input' | 'select' | 'number' | 'radio' | 'slot' | 'checkbox' | 'date' | 'switch' | 'edit' | 'password' | 'time'
label: string
prop: string
placeholder?: string

View File

@ -8,8 +8,8 @@
</div>
<!-- 菜单区域 -->
<el-menu class="menu" :unique-opened="true" default-active="dashboard" router>
<template v-for="menu in staticMenuList" :key="menu.id">
<el-menu class="menu" :unique-opened="true" :default-active="activeMenu" router>
<template v-for="menu in staticAgentMenuList" :key="menu.id">
<!-- 无子菜单 -->
<el-menu-item v-if="!menu.children || menu.children.length === 0" :index="menu.path">
<el-icon>
@ -43,9 +43,15 @@ import { HomeFilled, List, UserFilled, Platform, BellFilled, GoodsFilled, Warnin
//
import { useUserStore } from '@/stores/modules/user'
const route = useRoute()
const userStore = useUserStore()
const menuList = ref<any[]>([])
// index fullPath
const activeMenu = computed(() => {
return route.fullPath // 使 path
})
//
onMounted(async () => {
menuList.value = userStore.sideMenu || []
@ -241,6 +247,18 @@ const staticMenuList = ref<ElMenuItem[]>(
}
]
)
const staticAgentMenuList = <ElMenuItem[]>(
[
//
{
id: 1,
label: '首页',
path: 'dashboard', // el-menu-item index="dashboard"
icon: 'HomeFilled' // 对应 <HomeFilled /> 图标
},
]
);
</script>
<style scoped lang="scss">

598
src/views/agent/index.vue Normal file
View File

@ -0,0 +1,598 @@
<template>
<div class="agent-container">
<el-card class="start">
<h3>如何开始</h3>
<div class="list">
<div class="item">
<el-icon color="#409eff">
<CircleCheckFilled />
</el-icon>
<span>实名认证</span>
</div>
<div class="line"></div>
<div class="item">
<el-icon color="#409eff">
<CircleCheckFilled />
</el-icon>
<span>存款</span>
</div>
<div class="line"></div>
<div class="item">
<el-icon color="#409eff">
<CircleCheckFilled />
</el-icon>
<span>交易</span>
</div>
</div>
</el-card>
<el-card class="common">
<h3>常用功能</h3>
<div class="list">
<router-link to="" class="item">
<el-icon color="#409eff">
<CircleCheckFilled />
</el-icon>
<span>存款</span>
</router-link>
<router-link to="" class="item">
<el-icon color="#409eff">
<CircleCheckFilled />
</el-icon>
<span>取款</span>
</router-link>
<router-link to="" class="item">
<el-icon color="#409eff">
<CircleCheckFilled />
</el-icon>
<span>交易</span>
</router-link>
</div>
</el-card>
<el-card>
<h3>我的钱包</h3>
<el-popover placement="right" trigger="hover" class="wallet-popover">
<div class="link">
<router-link to="" class="item">转账</router-link>
<router-link to="" class="item">取款</router-link>
<router-link to="" class="item">资金明细</router-link>
</div>
<template #reference>
<div class="wallet">
<div class="item">
<el-icon color="#409eff" size="40">
<svg t="1773987067376" class="icon" viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg" p-id="1663" width="200" height="200">
<path
d="M512 97.52381c228.912762 0 414.47619 185.563429 414.47619 414.47619s-185.563429 414.47619-414.47619 414.47619S97.52381 740.912762 97.52381 512 283.087238 97.52381 512 97.52381z m96.060952 193.584761L512 387.120762l-96.060952-96.060952-51.687619 51.73638 96.036571 96.012191L329.142857 438.857143v73.142857h146.285714v60.952381h-146.285714v73.142857h146.285714v97.52381h73.142858v-97.52381h146.285714v-73.142857h-146.285714V512h146.285714v-73.142857h-131.145143l96.060952-96.060953-51.736381-51.687619z"
p-id="1664"></path>
</svg>
</el-icon>
</div>
<div class="item">
<span>USD</span>
<span>总金额</span>
<span>可用资金</span>
<span>冻结资金</span>
</div>
<div class="item">
<span>主钱包</span>
<span>1000000</span>
<span>1000000</span>
<span>1000000</span>
</div>
</div>
</template>
</el-popover>
</el-card>
<el-card class="account">
<h3 class="account-title">
个人账户
<el-button type="primary" size="small" @click="handleAddSubAccount">添加子账户</el-button>
</h3>
<div class="list">
<div class="item item-setting">
<div class="setting-content" v-if="!isEdit">
<span>账户名称:</span>
<span>{{ accountName }}</span>
</div>
<div class="setting-content" v-else>
<span class="setting-label">账户名称:</span>
<span v-if="!isEdit">{{ accountName }}</span>
<el-input v-model="editName" @blur="handleSave" @keyup.enter="handleSave" ref="inputRef"
size="small" v-else />
</div>
<div class="setting-group">
<span>
<el-icon @click="handleEdit">
<EditPen />
</el-icon>
</span>
<span>
<el-icon @click="handleSetting">
<Setting />
</el-icon>
</span>
</div>
</div>
<div class="item">
<span>账户号</span>
<span>12345678901234567890</span>
</div>
<div class="item">
<span>净资产</span>
<span>1000000</span>
</div>
<div class="item">
<span>余额</span>
<span>1000000</span>
</div>
<div class="item">
<span>杠杆</span>
<span>1:5000</span>
</div>
</div>
</el-card>
<el-card class="qr-code">
<h3>
推广
<span>统计起始时间:2026-3-20</span>
</h3>
<div class="list">
<div class="item">
<span>推广链接</span>
<div class="text">
<span>{{ copyText }}</span>
<el-icon @click="handleCopyUrl">
<DocumentCopy />
</el-icon>
</div>
</div>
<div class="item">
<span>推广二维码</span>
<div class="text">
<img :src="qrCode" alt="推广二维码">
<el-icon @click="handleCopyQrCode">
<DocumentCopy />
</el-icon>
</div>
</div>
</div>
</el-card>
</div>
<AgentDialog :visible="dialogVisible" :title="dialogTitle" :type="dialogType" @confirm="handleSubmit"
@cancel="handleCancel" @close="handleClose">
<div class="tab" v-if="dialogType !== 'add'">
<el-button :type="dialogType === 'setting' ? 'primary' : 'default'"
@click="dialogType = 'setting'">重置密码</el-button>
<el-button :type="dialogType === 'leverage' ? 'primary' : 'default'"
@click="dialogType = 'leverage'">修改杠杆</el-button>
</div>
<AgentForm :form-data="subAccountForm" :form-rules="subAccountRules" :form-items="subAccountFormItem"
:options-map="subAccountFormOptionsMap" :row-gutter="rowGutter" :col-span="colSpan"
ref="subAccountFormRef" />
</AgentDialog>
</template>
<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'
//
import { formItem, resetPasswordFormItem, leverageFormItem } from './options'
import { rules } from './rules'
//
import { getLeverOptionsApi, createAgentApi, editAgentNameApi, resetPasswordApi, leverageLeverApi } from '@/api/modules/agent'
//
import { getStorage, setStorage } from '@/utils/storage'
/**
* @description: dialog数据
*/
const dialogVisible = ref(false)
const dialogTitle = ref('')
const dialogType = ref('add')
/**
* @description: 添加子账户表单数据
*/
const subAccountForm = ref({
password: '',
leverage: null,
//
resetPassword: '',
//
confirmPassword: ''
})
const subAccountFormRef = ref()
const subAccountRules = ref(rules(subAccountForm.value))
const subAccountFormItem = computed(() => {
if (dialogType.value === 'setting') {
return resetPasswordFormItem
} else if (dialogType.value === 'leverage') {
return leverageFormItem
} else {
return formItem
}
})
const subAccountFormOptionsMap = ref({
leverage: [],
})
const rowGutter = ref(20)
const colSpan = ref(12)
/**
* @description: 编辑账户名称数据
*/
//
const userInfo = getStorage<any>('userInfo')
const accountName = ref(userInfo?.username || '')
const editName = ref(accountName.value)
const isEdit = ref(false)
const inputRef = ref<HTMLInputElement>()
/*
* @description: 复制推广二维码和推广链接数据
*/
const copyText = ref('3123123213123123123')
const qrCode = ref(qrCodeImg)
/**
* @description: 定义公共请求数据方法
*/
const getLeverOptions = async () => {
try {
const data: any = await getLeverOptionsApi()
console.log(data)
subAccountFormOptionsMap.value.leverage = data.map((item: any) => ({
label: item.label,
value: item.value
}))
} catch (err) {
console.error('获取杠杆选项失败:', err)
}
}
/**
* @description: 页面加载完成需要请求的数据
*/
onMounted(() => {
getLeverOptions()
})
/**
* @description: 添加子账户方法
*/
const handleAddSubAccount = () => {
dialogVisible.value = true
dialogTitle.value = '添加子账户'
dialogType.value = 'add'
console.log(dialogType.value)
}
/**
* @description: dialog方法
*/
const handleSubmit = async () => {
console.log(dialogType.value)
await subAccountFormRef.value.validate()
const params = {
password: subAccountForm.value.password,
lever: subAccountForm.value.leverage
}
if (dialogType.value === 'add') {
await createAgentApi(params)
dialogVisible.value = false
} else if (dialogType.value === 'setting') {
const params = {
reg_type:userInfo?.reg_type || '',
login_password1: subAccountForm.value.resetPassword,
login_password2: subAccountForm.value.confirmPassword,
email: userInfo?.email || '',
mobile: userInfo?.mobile || '',
code: '123456'
}
await resetPasswordApi(params)
dialogVisible.value = false
} else if (dialogType.value === 'leverage') {
const params = {
account_id: userInfo?.id || '',
lever: subAccountForm.value.leverage
}
await leverageLeverApi(params)
dialogVisible.value = false
}
}
const handleClose = () => {
dialogVisible.value = false
}
const handleCancel = () => {
dialogVisible.value = false
}
/**
* @description: 编辑账户名称方法
*/
//
const handleEdit = async () => {
isEdit.value = true
// DOM
await nextTick()
inputRef.value?.focus()
}
//
const handleSave = async () => {
if (editName.value.trim()) {
await editAgentNameApi({
account_id: userInfo?.id || '',
account_name: accountName.value
})
accountName.value = editName.value.trim()
setStorage('userInfo', {
...userInfo,
username: accountName.value
})
} else {
editName.value = accountName.value //
}
isEdit.value = false
}
/**
* @description: 设置方法
*/
const handleSetting = () => {
dialogVisible.value = true
dialogTitle.value = '编辑账号'
dialogType.value = 'setting'
}
/**
* @description: 复制推广二维码和推广链接方法
*/
const handleCopyUrl = async () => {
try {
await navigator.clipboard.writeText(copyText.value)
ElMessage.success('复制成功!')
} catch (err) {
console.error('复制失败:', err)
ElMessage.error('复制失败,请重试')
}
}
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('图片复制成功!')
} catch (err) {
console.error('复制图片失败:', err)
ElMessage.error('图片复制失败,请检查图片格式或权限')
}
}
</script>
<style scoped lang="scss">
.agent-container {
.el-card {
margin-bottom: 20px;
h3 {
margin-bottom: 20px;
}
}
.start {
.list {
display: flex;
align-items: center;
.item {
display: flex;
align-items: center;
justify-content: center;
border: 1px solid #ccc;
border-radius: 5px;
padding: 10px;
span {
margin: 0 10px;
}
}
.line {
width: 100px;
height: 2px;
background-color: #409eff;
position: relative;
display: flex;
align-items: center;
&:before {
content: '';
display: block;
width: 14px;
height: 14px;
border-radius: 50%;
background-color: #409eff;
position: absolute;
left: 0;
}
&:after {
content: '';
display: block;
width: 0;
height: 0;
border-top: 10px solid transparent;
border-bottom: 10px solid transparent;
border-left: 14px solid #409eff;
position: absolute;
right: 0;
}
}
}
}
.common {
.list {
display: flex;
gap: 60px;
}
.item {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 16px 24px;
border: 1px solid #ccc;
border-radius: 5px;
text-decoration: none;
color: #303133;
.el-icon {
font-size: 28px;
}
}
}
.wallet {
display: flex;
border: 1px solid #ccc;
border-radius: 5px;
width: fit-content;
padding: 10px;
cursor: pointer;
gap: 10px;
.item {
display: flex;
flex-direction: column;
align-items: center;
}
}
.account-title {
display: flex;
align-items: center;
gap: 10px;
}
.account {
.list {
width: 300px;
border: 1px solid #ccc;
border-radius: 5px;
padding: 10px;
display: flex;
flex-direction: column;
gap: 10px;
.item {
display: flex;
align-items: center;
gap: 16px;
}
.item-setting {
justify-content: space-between;
.setting-content {
display: flex;
align-items: center;
gap: 10px;
.setting-label {
width: 120px;
}
}
.setting-group {
display: flex;
cursor: pointer;
gap: 10px;
}
}
}
}
.qr-code {
h3 {
span {
font-size: 14px;
font-weight: normal;
}
}
.list {
display: flex;
gap: 20px;
.item {
display: flex;
flex-direction: column;
gap: 10px;
.text {
.el-icon {
margin-left: 10px;
cursor: pointer;
}
img {
width: 100px;
height: 100px;
}
}
}
}
}
}
//
.el-popper {
width: 100px;
.link {
display: flex;
flex-wrap: wrap;
gap: 10px;
.item {
width: fit-content;
border: 1px solid #409eff;
border-radius: 5px;
padding: 4px;
}
.item {
text-decoration: none;
color: #303133;
}
}
}
.el-dialog {
.tab {
margin-bottom: 10px;
}
}
</style>

View File

@ -0,0 +1,15 @@
export const formItem = [
{label: '密码',prop: 'password',type: 'password' as const,placeholder: '请输入密码',},
{label: '杠杆',prop: 'leverage',type: 'select' as const,placeholder: '请选择杠杆比例',labelWidth: '80px',},
]
//重置密码formItem
export const resetPasswordFormItem = [
{label: '密码',prop: 'resetPassword',type: 'password' as const,placeholder: '请输入密码',},
{label: '确认密码',prop: 'confirmPassword',type: 'password' as const,placeholder: '请确认密码',},
]
//修改杠杆formItem
export const leverageFormItem = [
{label: '杠杆',prop: 'leverage',type: 'select' as const,placeholder: '请选择杠杆比例',labelWidth: '80px',},
]

22
src/views/agent/rules.ts Normal file
View File

@ -0,0 +1,22 @@
import type { FormRules } from 'element-plus'
export const rules = (subAccountForm: any): FormRules => {
return {
password: [
{ required: true, message: '请输入密码', trigger: ['blur'] },
{ pattern: /^[0-9]+(\.[0-9]+)?$/, message: '请输入数字' }
],
leverage: [
{ required: true, message: '请选择杠杆比例', trigger: ['change'] }
],
confirmPassword: [
{ required: true, message: '请确认密码', trigger: ['blur'] },
{ validator: (rule: any, value: string, callback: any) => {
if (value !== subAccountForm.resetPassword) {
callback(new Error('两次密码不一致'))
} else {
callback()
}
}, trigger: ['blur'] }
]
}
}