foreign-admin-ui/src/views/dashboard/components/marketMaker/index.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>