459 lines
13 KiB
Vue
459 lines
13 KiB
Vue
<template>
|
|
<div class="market-maker-dashboard">
|
|
|
|
<div class="stats-wrapper">
|
|
<div class="stats-grid">
|
|
<div
|
|
v-for="(item, index) in cardData"
|
|
:key="index"
|
|
class="stat-box"
|
|
:class="`stat-box--${index % 7}`"
|
|
>
|
|
<div class="stat-box__icon">
|
|
<el-icon :size="28">
|
|
<component :is="item.icon" />
|
|
</el-icon>
|
|
</div>
|
|
<div class="stat-box__info">
|
|
<span class="stat-box__value">
|
|
<AnimatedNumber :value="Number(item.value) || 0" :decimals="2" />
|
|
</span>
|
|
<span class="stat-box__label">{{ item.label }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="content-area">
|
|
<div class="info-panel">
|
|
<div class="panel-header">
|
|
<el-icon><User /></el-icon>
|
|
<span>当前用户</span>
|
|
</div>
|
|
<div class="panel-content">
|
|
<div class="user-info">
|
|
<div class="user-avatar">
|
|
<el-icon :size="32"><UserFilled /></el-icon>
|
|
</div>
|
|
<div class="user-details">
|
|
<span class="user-name">{{ userStore.userInfo?.username || '-' }}</span>
|
|
<el-tag size="small" :type="tagType">
|
|
{{ roleNameComputed }}
|
|
</el-tag>
|
|
</div>
|
|
</div>
|
|
<div class="info-row">
|
|
<span class="info-label">用户ID</span>
|
|
<span class="info-value">{{ userStore.userInfo?.id || '-' }}</span>
|
|
</div>
|
|
<div class="info-row">
|
|
<span class="info-label">最后登录</span>
|
|
<span class="info-value">{{ userStore.userInfo?.last_login || '-' }}</span>
|
|
</div>
|
|
<div class="info-row">
|
|
<span class="info-label">账户类型</span>
|
|
<span class="info-value">{{ roleNameComputed }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="info-panel">
|
|
<div class="panel-header">
|
|
<el-icon><Operation /></el-icon>
|
|
<span>快捷操作</span>
|
|
</div>
|
|
<div class="panel-content">
|
|
<div class="action-list">
|
|
<router-link
|
|
v-for="action in quickActions"
|
|
:key="action.name"
|
|
:to="action.path"
|
|
class="action-item"
|
|
>
|
|
<el-icon :size="18">
|
|
<component :is="action.icon" />
|
|
</el-icon>
|
|
<span>{{ action.name }}</span>
|
|
<el-icon class="arrow"><Right /></el-icon>
|
|
</router-link>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { onMounted, ref, computed, markRaw } from 'vue'
|
|
import {
|
|
UserFilled,
|
|
User,
|
|
Money,
|
|
Wallet,
|
|
Coin,
|
|
Timer,
|
|
Operation,
|
|
Right,
|
|
List,
|
|
Goods,
|
|
Document,
|
|
Setting,
|
|
TrendCharts,
|
|
DataAnalysis,
|
|
Trophy,
|
|
Lock
|
|
} from '@element-plus/icons-vue'
|
|
import AnimatedNumber from '@/views/agent/user/AnimatedNumber.vue'
|
|
import { getWalletCapital } from '@/api/modules/capital/withdraw'
|
|
import { useUserStore } from '@/stores/modules/user'
|
|
import { ROLE_MAP, type RoleType } from '@/api/types/agent.d'
|
|
|
|
const props = defineProps<{
|
|
roleId: number
|
|
}>()
|
|
|
|
defineOptions({
|
|
name: 'MarketMakerDashboard'
|
|
})
|
|
|
|
const userStore = useUserStore()
|
|
|
|
// 角色名称从 ROLE_MAP 获取
|
|
const roleNameComputed = computed(() => ROLE_MAP[props.roleId as RoleType] || '-')
|
|
|
|
const formattedDate = computed(() => {
|
|
const now = new Date()
|
|
return `${now.getFullYear()}/${String(now.getMonth() + 1).padStart(2, '0')}/${String(now.getDate()).padStart(2, '0')}`
|
|
})
|
|
|
|
// 标签类型(不同角色显示不同颜色)
|
|
const tagType = computed<'danger' | 'success' | 'warning' | 'primary'>(() => {
|
|
if (props.roleId === 2) return 'warning'
|
|
if (props.roleId === 3) return 'danger'
|
|
if (props.roleId === 4) return 'success'
|
|
return 'primary'
|
|
})
|
|
|
|
const cardData = ref([
|
|
{ label: '总金额', value: '0', icon: markRaw(Money) },
|
|
{ label: '盈利', value: '0', icon: markRaw(Trophy) },
|
|
{ label: '手续费佣金', value: '0', icon: markRaw(Coin) },
|
|
{ label: '可提现点差佣金', value: '0', icon: markRaw(Wallet) },
|
|
{ label: '冻结点差佣金', value: '0', icon: markRaw(Lock) },
|
|
{ label: '服务费', value: '0', icon: markRaw(DataAnalysis) },
|
|
{ label: '保证金', value: '0', icon: markRaw(TrendCharts) }
|
|
])
|
|
|
|
const quickActions = ref<{ name: string; path: string; icon: any }[]>([])
|
|
|
|
const iconMap: Record<string, any> = {
|
|
user: User,
|
|
list: List,
|
|
goods: Goods,
|
|
document: Document,
|
|
setting: Setting,
|
|
money: Money,
|
|
trendcharts: TrendCharts,
|
|
wallet: Wallet,
|
|
coin: Coin,
|
|
timer: Timer
|
|
}
|
|
|
|
const initQuickActions = () => {
|
|
const actions: { name: string; path: string; icon: any }[] = []
|
|
const menus = userStore.menusInfo || []
|
|
|
|
let count = 0
|
|
for (const menu of menus) {
|
|
if (count >= 4) break
|
|
|
|
if (menu.son && menu.son.length > 0) {
|
|
for (const child of menu.son) {
|
|
if (count >= 4) break
|
|
if (child.path === '/dashboard') continue
|
|
actions.push({
|
|
name: child.name || menu.name,
|
|
path: child.path || '/',
|
|
icon: markRaw(iconMap[child.icon || ''] || List)
|
|
})
|
|
count++
|
|
}
|
|
} else {
|
|
if (menu.path === '/dashboard') continue
|
|
actions.push({
|
|
name: menu.name,
|
|
path: menu.path || '/',
|
|
icon: markRaw(iconMap[menu.icon || ''] || List)
|
|
})
|
|
count++
|
|
}
|
|
}
|
|
|
|
quickActions.value = actions.slice(0, 4)
|
|
}
|
|
|
|
const fetchWalletData = async () => {
|
|
try {
|
|
const res: any = await getWalletCapital()
|
|
if (res) {
|
|
cardData.value = [
|
|
{ label: '总金额', value: res.amount ?? 0, icon: markRaw(Money) },
|
|
{ label: '盈利', value: res.closing_profit ?? 0, icon: markRaw(Trophy) },
|
|
{ label: '手续费佣金', value: res.commission_fee_handling ?? 0, icon: markRaw(Coin) },
|
|
{ label: '可提现点差佣金', value: ((res.commission_point_diff || 0) - (res.freeze || 0)) , icon: markRaw(Wallet) },
|
|
{ label: '冻结点差佣金', value: res.freeze ?? 0, icon: markRaw(Lock) },
|
|
{ label: '服务费', value: res.service ?? 0, icon: markRaw(DataAnalysis) },
|
|
{ label: '保证金', value: res.bail ?? 0, icon: markRaw(TrendCharts) }
|
|
]
|
|
}
|
|
} catch (error) {
|
|
console.error('获取钱包资金失败:', error)
|
|
}
|
|
}
|
|
|
|
onMounted(() => {
|
|
initQuickActions()
|
|
fetchWalletData()
|
|
})
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
.market-maker-dashboard {
|
|
padding: 20px;
|
|
}
|
|
|
|
.page-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 24px;
|
|
padding-bottom: 16px;
|
|
border-bottom: 1px solid var(--el-border-color-light);
|
|
|
|
h1 {
|
|
font-size: 22px;
|
|
font-weight: 600;
|
|
color: var(--el-text-color-primary);
|
|
margin: 0;
|
|
}
|
|
|
|
.date {
|
|
font-size: 14px;
|
|
color: var(--el-text-color-secondary);
|
|
}
|
|
}
|
|
|
|
.stats-wrapper {
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
.stats-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(4, 1fr);
|
|
gap: 20px;
|
|
|
|
@media (max-width: 1200px) {
|
|
grid-template-columns: repeat(3, 1fr);
|
|
}
|
|
|
|
@media (max-width: 900px) {
|
|
grid-template-columns: repeat(2, 1fr);
|
|
}
|
|
|
|
@media (max-width: 600px) {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
|
|
.stat-box {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 16px;
|
|
padding: 20px 24px;
|
|
background: #fff;
|
|
border-radius: 12px;
|
|
border: 1px solid var(--el-border-color-lighter);
|
|
transition: all 0.25s ease;
|
|
|
|
&:hover {
|
|
border-color: var(--el-color-primary-light-5);
|
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.06);
|
|
transform: translateY(-2px);
|
|
}
|
|
|
|
&__icon {
|
|
width: 52px;
|
|
height: 52px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
border-radius: 12px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
&__info {
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-width: 0;
|
|
overflow: hidden;
|
|
}
|
|
|
|
&__value {
|
|
font-size: 24px;
|
|
font-weight: 700;
|
|
color: var(--el-text-color-primary);
|
|
line-height: 1.2;
|
|
max-width: 100%;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
&__label {
|
|
font-size: 14px;
|
|
color: var(--el-text-color-secondary);
|
|
margin-top: 6px;
|
|
}
|
|
}
|
|
|
|
.stat-box--0 .stat-box__icon { background: linear-gradient(135deg, #ecf5ff, #d9ecff); color: #409eff; }
|
|
.stat-box--1 .stat-box__icon { background: linear-gradient(135deg, #f0f9eb, #e1f3d8); color: #67c23a; }
|
|
.stat-box--2 .stat-box__icon { background: linear-gradient(135deg, #fdf6ec, #fbeccb); color: #e6a23c; }
|
|
.stat-box--3 .stat-box__icon { background: linear-gradient(135deg, #fef0f0, #fad7d7); color: #f56c6c; }
|
|
.stat-box--4 .stat-box__icon { background: linear-gradient(135deg, #f4f4f5, #e4e4e7); color: #909399; }
|
|
.stat-box--5 .stat-box__icon { background: linear-gradient(135deg, #fdf5f6, #f9d7e3); color: #c71585; }
|
|
.stat-box--6 .stat-box__icon { background: linear-gradient(135deg, #f0f9ff, #d6ecff); color: #0089e5; }
|
|
|
|
.content-area {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 20px;
|
|
|
|
@media (max-width: 768px) {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
|
|
.info-panel {
|
|
background: #fff;
|
|
border-radius: 12px;
|
|
border: 1px solid var(--el-border-color-lighter);
|
|
overflow: hidden;
|
|
}
|
|
|
|
.panel-header {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
padding: 16px 20px;
|
|
background: var(--el-fill-color-light);
|
|
border-bottom: 1px solid var(--el-border-color-lighter);
|
|
font-size: 15px;
|
|
font-weight: 600;
|
|
color: var(--el-text-color-primary);
|
|
|
|
.el-icon {
|
|
color: var(--el-color-primary);
|
|
}
|
|
}
|
|
|
|
.panel-content {
|
|
padding: 20px;
|
|
}
|
|
|
|
.user-info {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 16px;
|
|
padding-bottom: 16px;
|
|
margin-bottom: 16px;
|
|
border-bottom: 1px dashed var(--el-border-color-lighter);
|
|
|
|
.user-avatar {
|
|
width: 52px;
|
|
height: 52px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background: linear-gradient(135deg, var(--el-color-primary-light-9), var(--el-color-primary-light-7));
|
|
border-radius: 50%;
|
|
color: var(--el-color-primary);
|
|
}
|
|
|
|
.user-details {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 6px;
|
|
|
|
.user-name {
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
color: var(--el-text-color-primary);
|
|
}
|
|
}
|
|
}
|
|
|
|
.info-row {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 12px 0;
|
|
border-bottom: 1px dashed var(--el-border-color-lighter);
|
|
|
|
&:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.info-label {
|
|
font-size: 14px;
|
|
color: var(--el-text-color-secondary);
|
|
}
|
|
|
|
.info-value {
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
color: var(--el-text-color-primary);
|
|
}
|
|
}
|
|
|
|
.action-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
}
|
|
|
|
.action-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
padding: 12px 14px;
|
|
background: var(--el-fill-color-light);
|
|
border-radius: 10px;
|
|
font-size: 14px;
|
|
color: var(--el-text-color-regular);
|
|
cursor: pointer;
|
|
transition: all 0.2s ease;
|
|
text-decoration: none;
|
|
|
|
&:hover {
|
|
background: var(--el-color-primary-light-9);
|
|
color: var(--el-color-primary);
|
|
|
|
.arrow {
|
|
opacity: 1;
|
|
}
|
|
}
|
|
|
|
.el-icon {
|
|
color: var(--el-color-primary);
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.arrow {
|
|
margin-left: auto;
|
|
font-size: 12px;
|
|
opacity: 0.4;
|
|
transition: opacity 0.2s ease;
|
|
}
|
|
}
|
|
</style> |