Dashboard.vue 组件详细分析
一、组件概览
这是一个用户管理系统的仪表盘组件,使用Vue 3 Composition API + Element Plus UI库构建,主要功能是展示系统的统计数据和用户信息。
二、模板结构分析
2.1 整体布局
<div class="dashboard">
<!-- 使用Element Plus的栅格系统 -->
<el-row :gutter="20">
<!-- 统计卡片区域 -->
</el-row>
<el-row :gutter="20">
<!-- 图表和表格区域 -->
</el-row>
<!-- 欢迎信息区域 -->
<el-card>
</div>
2.2 四个核心区域
A. 统计卡片区域
<el-col :span="6" v-for="item in statsCards" :key="item.title">
<el-card class="stats-card">
<div class="stats-content">
<div class="stats-icon">
<el-icon><component :is="item.icon" /></el-icon>
</div>
<div class="stats-info">
<div class="stats-value">{{ item.value }}</div>
<div class="stats-title">{{ item.title }}</div>
</div>
</div>
</el-card>
</el-col>
- 使用24栅格系统,每个卡片占6格(共4个卡片)
- 动态组件渲染图标:
<component :is="item.icon" />
- 展示:总用户数、活跃用户、禁用用户、平均分数
B. 用户分数分布图
<el-progress
v-for="item in scoreDistribution"
:percentage="item.percentage"
:color="item.color"
:text-inside="true"
:stroke-width="26"
>
<template #default="{ percentage }">
<span>{{ item.label }}:{{ item.count }}人 ({{ percentage }}%)</span>
</template>
</el-progress>
- 使用进度条组件模拟柱状图
- 自定义插槽显示详细信息
- 四个分数段:优秀(90-100)、良好(70-89)、及格(60-69)、不及格(0-59)
C. 最近用户表格
<el-table :data="recentUsers" height="300">
<el-table-column prop="username" label="用户名" />
<el-table-column prop="email" label="邮箱" />
<el-table-column prop="createTime" label="注册时间">
<template #default="{ row }">
{{ formatDate(row.createTime) }}
</template>
</el-table-column>
</el-table>
- 固定高度300px的表格
- 自定义时间格式化
D. 欢迎信息区域
<div class="welcome-section">
<h2>欢迎回来,{{ user.username }}!</h2>
<p>您的角色是:{{ role === "admin" ? "管理员" : "普通用户" }}</p>
<p>上次登录时间:{{ new Date().toLocaleString() }}</p>
</div>
三、脚本逻辑分析
3.1 依赖导入和状态管理
import { ref, computed, onMounted } from "vue";
import { useStore } from "vuex";
import { getAllUsers } from "@/api/user";
const store = useStore();
const user = computed(() => store.getters.user);
const role = computed(() => store.getters.role);
- 使用Composition API
- 通过Vuex获取用户信息和角色
- 导入用户API
3.2 数据结构定义
统计卡片配置
const statsCards = ref([
{
title: "总用户数",
value: 0,
icon: "User", // Element Plus图标名
color: "#409eff", // 主题色
},
// ... 其他三个卡片
]);
分数分布配置
const scoreDistribution = ref([
{
label: "优秀(90-100)",
count: 0,
percentage: 0,
color: "#67c23a"
},
// ... 其他分数段
]);
3.3 核心业务逻辑
数据加载函数
const loadDashboardData = async () => {
try {
const res = await getAllUsers();
const users = res.data;
// 1. 计算基础统计
statsCards.value[0].value = users.length;
statsCards.value[1].value = users.filter(u => u.status === 1).length;
statsCards.value[2].value = users.filter(u => u.status === 0).length;
// 2. 计算平均分数
const avgScore = users.reduce((sum, u) => sum + (u.score || 0), 0) / users.length;
statsCards.value[3].value = Math.round(avgScore);
// 3. 分数分布统计
// 4. 获取最近用户
} catch (error) {
console.error("加载仪表盘数据失败", error);
}
};
分数分布算法
const distribution = [
{ range: [90, 100], index: 0 },
{ range: [70, 89], index: 1 },
{ range: [60, 69], index: 2 },
{ range: [0, 59], index: 3 },
];
users.forEach((user) => {
const score = user.score || 0;
const item = distribution.find(
(d) => score >= d.range[0] && score <= d.range[1]
);
if (item) {
scoreDistribution.value[item.index].count++;
}
});
最近用户排序
recentUsers.value = users
.sort((a, b) => new Date(b.createTime) - new Date(a.createTime))
.slice(0, 5);
四、样式设计分析
4.1 统计卡片样式
.stats-card {
.stats-content {
display: flex;
align-items: center;
.stats-icon {
width: 60px;
height: 60px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
color: white;
margin-right: 20px;
}
.stats-info {
flex: 1;
.stats-value {
font-size: 24px;
font-weight: bold;
color: #333;
}
}
}
}
- 使用Flexbox布局
- 图标容器固定尺寸,圆角设计
- 数值突出显示(24px,粗体)
4.2 欢迎区域样式
.welcome-section {
text-align: center;
padding: 20px;
h2 {
color: #333;
margin-bottom: 10px;
}
p {
color: #666;
margin: 5px 0;
}
}
- Vue 3 Composition API:更好的逻辑复用和类型推导
- Element Plus:成熟的企业级UI组件库
- SCSS:CSS预处理器,支持嵌套和变量
5.2 设计模式
- 响应式数据:使用ref和computed进行状态管理
- 组件化:模块化的卡片设计
- 数据驱动:通过配置数组驱动UI渲染
5.3 用户体验
- 可视化展示:使用进度条展示分数分布
- 实时数据:页面加载时获取最新数据
- 友好界面:清晰的布局和适当的间距
// 可以添加loading状态
const loading = ref(false);
// 可以添加错误处理UI
const error = ref(null);
// 添加数据刷新功能
const refreshData = () => {
loadDashboardData();
};
// 添加时间范围筛选
const dateRange = ref([]);
<!-- 添加aria标签 -->
<div class="stats-icon" :aria-label="item.title">
这是一个设计良好的仪表盘组件,具有以下特点:
- 结构清晰:模块化的布局设计
- 数据丰富:多维度的统计信息展示
- 交互友好:直观的可视化组件
- 技术现代:使用最新的Vue 3特性
- 可维护性高:良好的代码组织和命名规范
<!-- src/views/Dashboard.vue -->
<template>
<div class="dashboard">
<el-row :gutter="20">
<!-- 统计卡片 -->
<el-col :span="6" v-for="item in statsCards" :key="item.title">
<el-card class="stats-card">
<div class="stats-content">
<div class="stats-icon" :style="{ backgroundColor: item.color }">
<el-icon :size="30">
<component :is="item.icon" />
</el-icon>
</div>
<div class="stats-info">
<div class="stats-value">{{ item.value }}</div>
<div class="stats-title">{{ item.title }}</div>
</div>
</div>
</el-card>
</el-col>
</el-row>
<el-row :gutter="20" style="margin-top: 20px">
<!-- 用户分数分布 -->
<el-col :span="12">
<el-card>
<template #header>
<div class="card-header">
<span>用户分数分布</span>
</div>
</template>
<div class="chart-container">
<el-progress
v-for="item in scoreDistribution"
:key="item.label"
:text-inside="true"
:stroke-width="26"
:percentage="item.percentage"
:color="item.color"
style="margin-bottom: 20px"
>
<template #default="{ percentage }">
<span
>{{ item.label }}:{{ item.count }}人 ({{
percentage
}}%)</span
>
</template>
</el-progress>
</div>
</el-card>
</el-col>
<!-- 最近用户 -->
<el-col :span="12">
<el-card>
<template #header>
<div class="card-header">
<span>最近注册用户</span>
</div>
</template>
<el-table :data="recentUsers" height="300">
<el-table-column prop="username" label="用户名" />
<el-table-column prop="email" label="邮箱" />
<el-table-column prop="createTime" label="注册时间">
<template #default="{ row }">
{{ formatDate(row.createTime) }}
</template>
</el-table-column>
</el-table>
</el-card>
</el-col>
</el-row>
<!-- 欢迎信息 -->
<el-card style="margin-top: 20px">
<div class="welcome-section">
<h2>欢迎回来,{{ user.username }}!</h2>
<p>您的角色是:{{ role === "admin" ? "管理员" : "普通用户" }}</p>
<p>上次登录时间:{{ new Date().toLocaleString() }}</p>
</div>
</el-card>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from "vue";
import { useStore } from "vuex";
import { getAllUsers } from "@/api/user";
const store = useStore();
const user = computed(() => store.getters.user);
const role = computed(() => store.getters.role);
// 统计卡片数据
const statsCards = ref([
{
title: "总用户数",
value: 0,
icon: "User",
color: "#409eff",
},
{
title: "活跃用户",
value: 0,
icon: "UserFilled",
color: "#67c23a",
},
{
title: "禁用用户",
value: 0,
icon: "Warning",
color: "#e6a23c",
},
{
title: "平均分数",
value: 0,
icon: "TrendCharts",
color: "#f56c6c",
},
]);
// 分数分布
const scoreDistribution = ref([
{ label: "优秀(90-100)", count: 0, percentage: 0, color: "#67c23a" },
{ label: "良好(70-89)", count: 0, percentage: 0, color: "#409eff" },
{ label: "及格(60-69)", count: 0, percentage: 0, color: "#e6a23c" },
{ label: "不及格(0-59)", count: 0, percentage: 0, color: "#f56c6c" },
]);
// 最近用户
const recentUsers = ref([]);
const formatDate = (dateStr) => {
if (!dateStr) return "";
return new Date(dateStr).toLocaleDateString();
};
// 加载数据
const loadDashboardData = async () => {
try {
const res = await getAllUsers();
const users = res.data;
// 计算统计数据
statsCards.value[0].value = users.length;
statsCards.value[1].value = users.filter((u) => u.status === 1).length;
statsCards.value[2].value = users.filter((u) => u.status === 0).length;
const avgScore =
users.reduce((sum, u) => sum + (u.score || 0), 0) / users.length;
statsCards.value[3].value = Math.round(avgScore);
// 计算分数分布
const distribution = [
{ range: [90, 100], index: 0 },
{ range: [70, 89], index: 1 },
{ range: [60, 69], index: 2 },
{ range: [0, 59], index: 3 },
];
users.forEach((user) => {
const score = user.score || 0;
const item = distribution.find(
(d) => score >= d.range[0] && score <= d.range[1]
);
if (item) {
scoreDistribution.value[item.index].count++;
}
});
// 计算百分比
scoreDistribution.value.forEach((item) => {
item.percentage = Math.round((item.count / users.length) * 100);
});
// 获取最近5个用户
recentUsers.value = users
.sort((a, b) => new Date(b.createTime) - new Date(a.createTime))
.slice(0, 5);
} catch (error) {
console.error("加载仪表盘数据失败", error);
}
};
onMounted(() => {
loadDashboardData();
});
</script>
<style scoped lang="scss">
.dashboard {
.stats-card {
.stats-content {
display: flex;
align-items: center;
.stats-icon {
width: 60px;
height: 60px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
color: white;
margin-right: 20px;
}
.stats-info {
flex: 1;
.stats-value {
font-size: 24px;
font-weight: bold;
color: #333;
}
.stats-title {
color: #999;
margin-top: 5px;
}
}
}
}
.card-header {
font-weight: bold;
}
.welcome-section {
text-align: center;
padding: 20px;
h2 {
color: #333;
margin-bottom: 10px;
}
p {
color: #666;
margin: 5px 0;
}
}
}
</style>