处理首页

This commit is contained in:
2026-05-22 11:44:07 +08:00
parent b0b6c3055e
commit 99f52e0cf4
1 changed files with 361 additions and 57 deletions

View File

@ -1,45 +1,169 @@
<template>
<div class="dashboard-container" v-if="userId === 1">
<el-card v-for="(item, index) in cardData" :key="index" class="stat-card" shadow="hover">
<div class="card-content">
<h3>
<el-icon size="48" class="icon">
<component :is="item.icon" />
</el-icon>
{{ item.label }}
</h3>
<p>{{ item.value }}</p>
<div class="dashboard" v-if="userId === 1">
<div class="page-header">
<h1>数据概览</h1>
<span class="date">{{ formattedDate }}</span>
</div>
<div class="stats-wrapper">
<div class="stats-grid">
<div
v-for="(item, index) in cardData"
:key="index"
class="stat-box"
:class="`stat-box--${index}`"
>
<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="0" />
</span>
<span class="stat-box__label">{{ item.label }}</span>
</div>
</div>
</div>
</el-card>
</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="+userStore.userInfo.role_id === 1 ? 'danger' : 'primary'">
{{ userStore.userInfo.role }}
</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>
</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>
<Agent v-else />
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { UserFilled } from '@element-plus/icons-vue'
import { getStorage } from '@/utils/storage'
import { onMounted, ref, computed } from 'vue'
import { UserFilled, User, TrendCharts, Money, Wallet, Coin, Timer, Operation, Right, List, Goods, Document, Setting } from '@element-plus/icons-vue'
import Agent from './components/view/index.vue'
import AnimatedNumber from '@/views/agent/user/AnimatedNumber.vue'
import { getUserIndex } from '@/api/modules/dashboard'
import { useUserStore } from '@/stores/modules/user'
defineOptions({
name: 'Dashboard'
})
//id
const userId = getStorage('userInfo')?.id
const userStore = useUserStore()
const userId = userStore.userInfo?.id
const loginTime = ref('--')
const formattedDate = computed(() => {
const now = new Date()
return `${now.getFullYear()}/${String(now.getMonth() + 1).padStart(2, '0')}/${String(now.getDate()).padStart(2, '0')}`
})
const cardData = ref([
{ label: '代理总数', value: 0, icon: UserFilled },
{ label: '用户总数', value: 0, icon: UserFilled },
{ label: '今日新增用户', value: 0, icon: UserFilled },
{ label: '今日充值金额', value: '0', icon: UserFilled },
{ label: '今日提现金额', value: 0, icon: UserFilled },
{ label: '总充值金额', value: 0, icon: UserFilled },
{ label: '总提现金额', value: 0, icon: UserFilled }
{ label: '用户总数', value: 0, icon: User },
{ label: '今日新增用户', value: 0, icon: TrendCharts },
{ label: '今日充值金额', value: '0', icon: Money },
{ label: '今日提现金额', value: 0, icon: Wallet },
{ label: '总充值金额', value: 0, icon: Coin },
{ label: '总提现金额', value: 0, icon: Timer }
])
//
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: iconMap[child.icon || ''] || List
})
count++
}
} else {
if (menu.path === '/dashboard') continue
actions.push({
name: menu.name,
path: menu.path || '/',
icon: iconMap[menu.icon || ''] || List
})
count++
}
}
quickActions.value = actions.slice(0, 4)
}
const fetchDashboardData = async () => {
try {
const res = await getUserIndex()
@ -49,12 +173,12 @@ const fetchDashboardData = async () => {
cardData.value = [
{ label: '代理总数', value: agent_count, icon: UserFilled },
{ label: '用户总数', value: user_count, icon: UserFilled },
{ label: '今日新增用户', value: today_user_count, icon: UserFilled },
{ label: '今日充值金额', value: today_recharge_amount, icon: UserFilled },
{ label: '今日提现金额', value: today_withdraw_amount, icon: UserFilled },
{ label: '总充值金额', value: total_recharge_amount, icon: UserFilled },
{ label: '总提现金额', value: total_withdraw_amount, icon: UserFilled }
{ label: '用户总数', value: user_count, icon: User },
{ label: '今日新增用户', value: today_user_count, icon: TrendCharts },
{ label: '今日充值金额', value: today_recharge_amount, icon: Money },
{ label: '今日提现金额', value: today_withdraw_amount, icon: Wallet },
{ label: '总充值金额', value: total_recharge_amount, icon: Coin },
{ label: '总提现金额', value: total_withdraw_amount, icon: Timer }
]
}
} catch (error) {
@ -63,6 +187,8 @@ const fetchDashboardData = async () => {
}
onMounted(() => {
loginTime.value = new Date().toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })
initQuickActions()
if (userId === 1) {
fetchDashboardData()
}
@ -70,42 +196,220 @@ onMounted(() => {
</script>
<style scoped lang="scss">
.dashboard-container {
display: flex;
flex-wrap: wrap;
gap: 16px;
margin: 0 auto;
.dashboard {
padding: 24px;
}
.stat-card {
flex: 0 0 calc(25% - 12px);
min-width: 280px;
height: 180px;
box-sizing: border-box;
}
.card-content {
height: 100%;
.page-header {
display: flex;
flex-direction: column;
justify-content: center;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
padding-bottom: 16px;
border-bottom: 1px solid var(--el-border-color-light);
h3 {
margin-bottom: 10px;
display: flex;
justify-content: center;
align-items: center;
font-size: 22px;
.icon {
margin-right: 8px;
}
h1 {
font-size: 20px;
font-weight: 600;
color: var(--el-text-color-primary);
margin: 0;
}
p {
font-size: 18px;
.date {
font-size: 14px;
color: var(--el-text-color-secondary);
}
}
.stats-wrapper {
margin-bottom: 24px;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 16px;
}
.stat-box {
display: flex;
align-items: center;
gap: 16px;
padding: 20px;
background: #fff;
border-radius: 12px;
border: 1px solid var(--el-border-color-lighter);
transition: all 0.2s ease;
&:hover {
border-color: var(--el-color-primary-light-5);
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
}
&__icon {
width: 48px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 10px;
flex-shrink: 0;
}
&__info {
display: flex;
flex-direction: column;
min-width: 0;
overflow: hidden;
}
&__value {
font-size: 20px;
font-weight: 600;
color: var(--el-text-color-primary);
line-height: 1.2;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&__label {
font-size: 13px;
color: var(--el-text-color-secondary);
margin-top: 4px;
}
}
.stat-box--0 .stat-box__icon { background: #ecf5ff; color: #409eff; }
.stat-box--1 .stat-box__icon { background: #f0f9eb; color: #67c23a; }
.stat-box--2 .stat-box__icon { background: #fdf6ec; color: #e6a23c; }
.stat-box--3 .stat-box__icon { background: #fef0f0; color: #f56c6c; }
.stat-box--4 .stat-box__icon { background: #f4f4f5; color: #909399; }
.stat-box--5 .stat-box__icon { background: #fdf5f6; color: #c71585; }
.stat-box--6 .stat-box__icon { background: #f0f9ff; color: #0089e5; }
.content-area {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 16px;
}
.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: 8px;
padding: 14px 16px;
background: var(--el-fill-color-light);
border-bottom: 1px solid var(--el-border-color-lighter);
font-size: 14px;
font-weight: 500;
color: var(--el-text-color-primary);
.el-icon {
color: var(--el-color-primary);
}
}
.panel-content {
padding: 16px;
}
.user-info {
display: flex;
align-items: center;
gap: 14px;
padding-bottom: 14px;
margin-bottom: 14px;
border-bottom: 1px dashed var(--el-border-color-lighter);
.user-avatar {
width: 48px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
background: var(--el-color-primary-light-9);
border-radius: 50%;
color: var(--el-color-primary);
}
.user-details {
display: flex;
flex-direction: column;
gap: 6px;
.user-name {
font-size: 15px;
font-weight: 600;
color: var(--el-text-color-primary);
}
}
}
.info-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 0;
border-bottom: 1px dashed var(--el-border-color-lighter);
&:last-child {
border-bottom: none;
}
.info-label {
font-size: 13px;
color: var(--el-text-color-secondary);
}
.info-value {
font-size: 13px;
font-weight: 500;
color: var(--el-text-color-primary);
}
}
.action-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.action-item {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 12px;
background: var(--el-fill-color-light);
border-radius: 8px;
font-size: 13px;
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);
}
.el-icon {
color: var(--el-color-primary);
flex-shrink: 0;
}
.arrow {
margin-left: auto;
font-size: 12px;
opacity: 0.5;
}
}
</style>