2025山东大学软件学院创新项目实训博客6

工作内容

修改用户管理功能bug
更正仪表盘饼图表现

具体内容

1. 修改内容概述

本次优化主要针对用户管理功能进行了以下改进:

  1. 用户编辑功能优化

    • 限制编辑时只能修改用户类型
    • 其他字段(姓名、账号、密码)保持只读
    • 确保请求体包含所有必要字段
  2. 界面布局优化

    • 统一操作区域布局
    • 优化表单元素宽度和间距
    • 改进用户类型下拉框显示效果

2. 具体修改内容

2.1 用户编辑功能

修改前
  • 编辑时可以修改所有用户信息
  • 密码字段可以修改
  • 请求体只包含修改的字段
修改后
  • 编辑时只能修改用户类型
  • 其他字段(姓名、账号、密码)保持只读
  • 请求体包含所有用户字段,确保数据完整性

2.2 界面布局

修改前
  • "添加用户"按钮单独一行
  • 用户类型下拉框宽度不足
  • 表单元素间距不统一
修改后
  • 所有操作元素在同一行
  • 用户类型下拉框宽度调整为120px
  • 输入框宽度统一为180px
  • 优化了按钮间距和对齐方式

3. 修改成果

  1. 功能改进

    • 提高了数据安全性,防止误修改用户关键信息
    • 确保与后端API的数据一致性
    • 优化了用户操作体验
  2. 界面优化

    • 布局更加紧凑和美观
    • 操作更加直观
    • 视觉层次更加清晰
  3. 数据获取和展示的优化

    • 修改了 fetchSystemStats 函数,确保正确获取和展示用户统计数据
    • 添加了可选链操作符 ?. 来安全访问数据,防止出现 undefined
    • 为统计数据添加了默认值,确保即使后端返回空数据也能正常显示
  4. 师生比例计算的改进:

    • 修改了 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. 总结

本次优化主要从功能完整性和用户体验两个方面进行了改进:

  1. 通过限制编辑权限,提高了数据安全性
  2. 通过优化界面布局,提升了操作体验
  3. 通过完善请求数据,确保了与后端的数据一致性

这些改进使得仪表盘和用户管理功能更加完善和易用,同时也提高了系统的安全性和稳定性。

### 山东大学软件学院实训项目与课程安排 #### 1. 实训项目的概述 山东大学软件学院实训项目旨在通过实际操作和项目驱动的方式提升学生的实践能力和综合技能。例如,在暑期实训中,研究生管理系统开发是一个典型的案例[^3]。该项目涉及多个功能模块的设计与实现,其中包括学生组的“我的考试”界面以及教师组的“我的监考”界面。 以下是该系统的部分核心功能展示: ```html <div> <p>课程号:{{course.courseNameId}} {{course.courseName}}</p> <p>开始时间:{{formatDate(course.exmStartTime)}}</p> <p>结束时间:{{formatDate(course.exmOverTime)}}</p> <p>考试地点:{{course.exmPlace}}</p> <p>   考试时间:{{course.exmTime}}</p> <p>考试方式:{{course.teachMethod}}</p> <button type="primary" class="select-button" @click="exm_detail(index)" style="background-color:#18B566">查看考试要求</button> </div> ``` #### 2. 课程安排的具体内容 在第一周的实训过程中,学生可以通过系统查询本学期所选课程的考试安排信息,这些信息通常包括但不限于课序号、考试名称、考试相关的时间安排以及详细的考试介绍[^2]。具体的内容可能如下所示: | 字段 | 描述 | |--------------|--------------------------| | 课序号 | 唯一标识每门课程 | | 考试名称 | 明确考试科目 | | 开始时间 | 考试起始时刻 | | 结束时间 | 考试终止时刻 | | 考试介绍 | 提供考试形式及其他细节 | 这种结构化的数据呈现不仅方便了学生查阅个人考试计划,还帮助教师更好地管理监考任务。 #### 3. 物联网技术的应用前景 除了传统的软件开发类实训外,随着物联网技术的发展,职业教育领域也在积极探索如何将其融入教学实践中。例如,某些实验实训室已经引入了基于物联网的技术平台,用于培养学生的创新思维和技术应用能力[^4]。这表明未来山东大学软件学院可能会进一步拓展此类新兴领域的培训方向。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值