工作内容
修改用户管理功能bug
更正仪表盘饼图表现
具体内容
1. 修改内容概述
本次优化主要针对用户管理功能进行了以下改进:
-
用户编辑功能优化
- 限制编辑时只能修改用户类型
- 其他字段(姓名、账号、密码)保持只读
- 确保请求体包含所有必要字段
-
界面布局优化
- 统一操作区域布局
- 优化表单元素宽度和间距
- 改进用户类型下拉框显示效果
2. 具体修改内容
2.1 用户编辑功能
修改前
- 编辑时可以修改所有用户信息
- 密码字段可以修改
- 请求体只包含修改的字段
修改后
- 编辑时只能修改用户类型
- 其他字段(姓名、账号、密码)保持只读
- 请求体包含所有用户字段,确保数据完整性
2.2 界面布局
修改前
- "添加用户"按钮单独一行
- 用户类型下拉框宽度不足
- 表单元素间距不统一
修改后
- 所有操作元素在同一行
- 用户类型下拉框宽度调整为120px
- 输入框宽度统一为180px
- 优化了按钮间距和对齐方式
3. 修改成果
-
功能改进
- 提高了数据安全性,防止误修改用户关键信息
- 确保与后端API的数据一致性
- 优化了用户操作体验
-
界面优化
- 布局更加紧凑和美观
- 操作更加直观
- 视觉层次更加清晰
-
数据获取和展示的优化
- 修改了 fetchSystemStats 函数,确保正确获取和展示用户统计数据
- 添加了可选链操作符 ?. 来安全访问数据,防止出现 undefined
- 为统计数据添加了默认值,确保即使后端返回空数据也能正常显示
-
师生比例计算的改进:
- 修改了 teacherStudentStats 的计算逻辑
- 确保在计算师生比例时正确处理分母为 0 的情况
- 使用 toFixed(2) 格式化比例显示
4. 页面展示
仪表盘页面
5. 修改代码清单
5.1 UserManagement.vue 修改
<!-- 顶部操作栏 -->
<div class="management-header">
<div class="search-section">
<el-form :inline="true" :model="searchForm" class="search-form">
<el-form-item label="姓名">
<el-input v-model="searchForm.name" placeholder="请输入姓名" clearable />
</el-form-item>
<el-form-item label="账号">
<el-input v-model="searchForm.account" placeholder="请输入账号" clearable />
</el-form-item>
<el-form-item label="用户类型">
<el-select v-model="searchForm.type" placeholder="请选择用户类型" clearable style="width: 120px">
<el-option label="管理员" :value="0" />
<el-option label="学生" :value="1" />
<el-option label="教师" :value="2" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="searchUsers">搜索</el-button>
<el-button @click="resetSearch">重置</el-button>
<el-button type="primary" @click="showAddDialog">添加用户</el-button>
</el-form-item>
</el-form>
</div>
</div>
<style scoped>
.search-section {
display: flex;
justify-content: space-between;
align-items: center;
}
.search-form {
display: flex;
align-items: center;
flex-wrap: nowrap;
width: 100%;
}
:deep(.el-form--inline .el-form-item) {
margin-right: 20px;
margin-bottom: 0;
display: flex;
align-items: center;
}
:deep(.el-form-item__content) {
display: flex;
align-items: center;
}
:deep(.el-select) {
width: 120px;
}
:deep(.el-input) {
width: 180px;
}
:deep(.el-button) {
margin-left: 10px;
}
:deep(.el-button + .el-button) {
margin-left: 10px;
}
</style>
5.2 AdminAPI.js 修改
async updateUser(user, token) {
try {
if (!user.id) {
throw new Error('用户ID不能为空');
}
// 构建请求体,包含所有字段
const requestBody = {
id: user.id,
name: user.name,
account: user.account,
type: user.type,
password: user.password
};
const response = await this.axiosUtil.put('/dao/user', requestBody, null, {
'Authorization': token
});
if (response.data.code === 0) {
return response.data;
}
throw new Error(response.data.message || '更新用户失败');
} catch (error) {
throw error;
}
}
5.3 DashBoard.vue 修改
<template>
<div class="dashboard-container">
<el-row :gutter="20">
<!-- 系统概览 -->
<el-col :span="24">
<el-card class="box-card" shadow="hover">
<template #header>
<div class="card-header">
<div class="header-left">
<el-icon class="header-icon"><Monitor /></el-icon>
<span>系统概览</span>
</div>
</div>
</template>
<el-row :gutter="20">
<el-col :span="6" v-for="(stat, index) in systemStatsList" :key="index">
<div class="stat-card" :class="stat.class">
<div class="stat-icon">
<el-icon><component :is="stat.icon" /></el-icon>
</div>
<div class="stat-content">
<div class="stat-title">{{ stat.title }}</div>
<div class="stat-value">{{ stat.value }}</div>
</div>
</div>
</el-col>
</el-row>
</el-card>
</el-col>
<!-- 作业统计 -->
<el-col :span="24" style="margin-top: 20px;">
<el-card class="box-card" shadow="hover">
<template #header>
<div class="card-header">
<div class="header-left">
<el-icon class="header-icon"><Document /></el-icon>
<span>作业统计</span>
</div>
</div>
</template>
<el-row :gutter="20">
<el-col :span="8" v-for="(stat, index) in homeworkStatsList" :key="index">
<div class="stat-card" :class="stat.class">
<div class="stat-icon">
<el-icon><component :is="stat.icon" /></el-icon>
</div>
<div class="stat-content">
<div class="stat-title">{{ stat.title }}</div>
<div class="stat-value">{{ stat.value }}</div>
</div>
</div>
</el-col>
</el-row>
</el-card>
</el-col>
<!-- 教师-学生关系 -->
<el-col :span="24" style="margin-top: 20px;">
<el-card class="box-card" shadow="hover">
<template #header>
<div class="card-header">
<div class="header-left">
<el-icon class="header-icon"><User /></el-icon>
<span>教师-学生关系</span>
</div>
</div>
</template>
<el-row :gutter="20">
<el-col :span="12" v-for="(stat, index) in teacherStudentStatsList" :key="index">
<div class="stat-card" :class="stat.class">
<div class="stat-icon">
<el-icon><component :is="stat.icon" /></el-icon>
</div>
<div class="stat-content">
<div class="stat-title">{{ stat.title }}</div>
<div class="stat-value">{{ stat.value }}</div>
</div>
</div>
</el-col>
</el-row>
</el-card>
</el-col>
</el-row>
<!-- 图表区域 -->
<div class="chart-section">
<el-card class="chart-card" shadow="hover">
<template #header>
<div class="card-header">
<div class="header-left">
<el-icon class="header-icon"><PieChart /></el-icon>
<span>用户类型分布</span>
</div>
</div>
</template>
<div class="chart-container">
<v-chart :option="userTypeChart" style="height: 300px;" />
</div>
</el-card>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, computed } from 'vue';
import {
Monitor, Document, User, PieChart,
UserFilled, Reading, Collection, DataLine, Connection
} from '@element-plus/icons-vue';
import { useUserStore } from '@/store/modules/user';
import AdminAPI from '@/api/AdminAPI';
import TeacherAPI from '@/api/TeacherAPI';
import AxiosUtil from '@/utils/AxiosUtil';
import * as echarts from 'echarts';
import { VueEcharts } from 'vue3-echarts';
import { ElMessage } from 'element-plus';
const userStore = useUserStore();
const adminAPI = new AdminAPI(AxiosUtil);
const teacherAPI = new TeacherAPI(AxiosUtil);
// 系统统计数据
const systemStats = ref({
totalUsers: 0,
totalAdmins: 0,
totalTeachers: 0,
totalStudents: 0,
totalHomework: 0
});
// 作业统计数据
const homeworkStats = ref({
totalHomework: 0,
totalSubmissions: 0,
averageScore: 0
});
// 教师-学生关系统计数据
const teacherStudentStats = ref({
totalTeachers: 0,
totalStudents: 0,
teacherStudentRatio: 0
});
// 用户类型分布图表配置
const userTypeChart = ref({
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c} ({d}%)'
},
legend: {
orient: 'vertical',
left: 10,
data: ['管理员', '学生', '教师']
},
series: [
{
name: '用户类型',
type: 'pie',
radius: ['50%', '70%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2
},
label: {
show: true,
formatter: '{b}: {c}人'
},
emphasis: {
label: {
show: true,
fontSize: '16',
fontWeight: 'bold'
}
},
data: [
{ value: 0, name: '管理员' },
{ value: 0, name: '学生' },
{ value: 0, name: '教师' }
]
}
]
});
// 系统统计数据列表
const systemStatsList = computed(() => [
{
title: '总用户数',
value: systemStats.value.totalUsers,
icon: 'UserFilled',
class: 'stat-card-blue'
},
{
title: '管理员数量',
value: systemStats.value.totalAdmins || 0,
icon: 'UserFilled',
class: 'stat-card-purple'
},
{
title: '教师数量',
value: systemStats.value.totalTeachers,
icon: 'Reading',
class: 'stat-card-green'
},
{
title: '学生数量',
value: systemStats.value.totalStudents,
icon: 'Collection',
class: 'stat-card-orange'
}
]);
// 作业统计数据列表
const homeworkStatsList = computed(() => [
{
title: '总作业数',
value: homeworkStats.value.totalHomework,
icon: 'Document',
class: 'stat-card-blue'
},
{
title: '已提交作业数',
value: homeworkStats.value.totalSubmissions,
icon: 'Collection',
class: 'stat-card-green'
},
{
title: '平均得分',
value: homeworkStats.value.averageScore,
icon: 'DataLine',
class: 'stat-card-orange'
}
]);
// 教师-学生关系统计数据列表
const teacherStudentStatsList = computed(() => [
{
title: '教师数量',
value: teacherStudentStats.value.totalTeachers,
icon: 'Reading',
class: 'stat-card-blue'
},
{
title: '学生数量',
value: teacherStudentStats.value.totalStudents,
icon: 'Collection',
class: 'stat-card-green'
},
{
title: '师生比例',
value: teacherStudentStats.value.teacherStudentRatio.toFixed(2),
icon: 'DataLine',
class: 'stat-card-orange'
}
]);
// 获取系统统计数据
const fetchSystemStats = async () => {
try {
const token = userStore.token;
if (!token) {
ElMessage.error('未登录或登录已过期');
return;
}
const response = await adminAPI.getSystemOverview(token);
if (response.code === 0) {
const { userStatistics, homeworkStatistics, teachingStatistics } = response.data;
// 更新系统统计
const typeDistribution = userStatistics?.typeDistribution || {};
systemStats.value = {
totalUsers: userStatistics?.total || 0,
totalAdmins: typeDistribution['0'] || 0,
totalTeachers: typeDistribution['2'] || 0,
totalStudents: typeDistribution['1'] || 0,
totalHomework: homeworkStatistics?.totalHomeworks || 0
};
// 更新用户类型分布图表
userTypeChart.value = {
...userTypeChart.value,
series: [{
...userTypeChart.value.series[0],
data: [
{ value: typeDistribution['0'] || 0, name: '管理员' },
{ value: typeDistribution['1'] || 0, name: '学生' },
{ value: typeDistribution['2'] || 0, name: '教师' }
]
}]
};
console.log('系统统计:', response.data);
} else {
console.error('获取系统统计失败:', response.message);
}
} catch (error) {
console.error('获取系统统计失败:', error);
}
};
// 获取作业统计数据
const fetchHomeworkStats = async () => {
try {
const token = userStore.token;
if (!token) {
ElMessage.error('未登录或登录已过期');
return;
}
const response = await adminAPI.getAllHomeworkStatistics(token);
if (response.code === 0) {
const homeworkList = response.data;
// 计算总体统计
const stats = homeworkList.reduce((acc, curr) => {
return {
totalHomework: acc.totalHomework + 1,
totalSubmissions: acc.totalSubmissions + (curr.status === 1 ? 1 : 0),
totalScore: acc.totalScore + (curr.average || 0)
};
}, {
totalHomework: 0,
totalSubmissions: 0,
totalScore: 0
});
homeworkStats.value = {
totalHomework: stats.totalHomework,
totalSubmissions: stats.totalSubmissions,
averageScore: stats.totalHomework > 0 ? (stats.totalScore / stats.totalHomework).toFixed(2) : 0
};
console.log('作业统计:', response.data);
} else {
console.error('获取作业统计失败:', response.message);
}
} catch (error) {
console.error('获取作业统计失败:', error);
}
};
// 获取教师-学生关系统计数据
const fetchTeacherStudentStats = async () => {
try {
const token = userStore.token;
if (!token) {
ElMessage.error('未登录或登录已过期');
return;
}
const response = await adminAPI.getSystemOverview(token);
if (response.code === 0) {
const { teachingStatistics } = response.data;
teacherStudentStats.value = {
totalTeachers: teachingStatistics?.teacherCount || 0,
totalStudents: teachingStatistics?.studentCount || 0,
teacherStudentRatio: teachingStatistics?.teacherStudentRatio || 0
};
console.log('教师-学生关系统计:', teachingStatistics);
} else {
console.error('获取教师-学生关系统计失败:', response.message);
}
} catch (error) {
console.error('获取教师-学生关系统计失败:', error);
}
};
// 获取所有统计数据
const fetchAllStats = async () => {
try {
await Promise.all([
fetchSystemStats(),
fetchHomeworkStats(),
fetchTeacherStudentStats()
]);
} catch (error) {
console.error('获取所有统计数据失败:', error);
ElMessage.error('获取所有统计数据失败');
}
};
onMounted(() => {
fetchAllStats();
});
</script>
<style scoped>
.dashboard-container {
padding: 20px;
background: #f5f7fa;
min-height: calc(100vh - 60px);
}
.box-card {
margin-bottom: 20px;
transition: all 0.3s;
}
.box-card:hover {
transform: translateY(-5px);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.header-left {
display: flex;
align-items: center;
gap: 8px;
}
.header-icon {
font-size: 20px;
color: #409EFF;
}
.stat-card {
display: flex;
align-items: center;
padding: 20px;
border-radius: 8px;
transition: all 0.3s;
height: 100px;
}
.stat-card:hover {
transform: translateY(-3px);
}
.stat-icon {
display: flex;
align-items: center;
justify-content: center;
width: 48px;
height: 48px;
border-radius: 8px;
margin-right: 16px;
}
.stat-icon .el-icon {
font-size: 24px;
color: #fff;
}
.stat-content {
flex: 1;
}
.stat-title {
font-size: 14px;
color: #909399;
margin-bottom: 8px;
}
.stat-value {
font-size: 24px;
font-weight: bold;
color: #303133;
}
/* 统计卡片颜色主题 */
.stat-card-blue {
background: linear-gradient(135deg, #1890ff 0%, #36cfc9 100%);
}
.stat-card-green {
background: linear-gradient(135deg, #52c41a 0%, #73d13d 100%);
}
.stat-card-orange {
background: linear-gradient(135deg, #fa8c16 0%, #ffa940 100%);
}
.stat-card-purple {
background: linear-gradient(135deg, #722ed1 0%, #b37feb 100%);
}
.stat-card-blue .stat-icon {
background: rgba(255, 255, 255, 0.2);
}
.stat-card-green .stat-icon {
background: rgba(255, 255, 255, 0.2);
}
.stat-card-orange .stat-icon {
background: rgba(255, 255, 255, 0.2);
}
.stat-card-purple .stat-icon {
background: rgba(255, 255, 255, 0.2);
}
.stat-card .stat-title,
.stat-card .stat-value {
color: #fff;
}
.chart-section {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
gap: 20px;
margin-bottom: 20px;
}
.chart-card {
height: 100%;
}
.chart-container {
padding: 20px;
}
</style>
6. 总结
本次优化主要从功能完整性和用户体验两个方面进行了改进:
- 通过限制编辑权限,提高了数据安全性
- 通过优化界面布局,提升了操作体验
- 通过完善请求数据,确保了与后端的数据一致性
这些改进使得仪表盘和用户管理功能更加完善和易用,同时也提高了系统的安全性和稳定性。