Skip to content

Latest commit

 

History

History
3552 lines (2367 loc) · 116 KB

项目报告.md

File metadata and controls

3552 lines (2367 loc) · 116 KB

项目报告

[TOC]

第一部分 项目简介

1.系统总体功能介绍

(1)需求

总体功能需求:

  1. 用户管理:用户注册、忘记密码(通过邮件发送验证码)、重置密码(检查验证码)、用户信息维护(修改密码、个人信息、邮箱等)。支持管理员、教师、学生三种角色。

  2. 前端展示框架:支持二级菜单导航、系统logo、个人头像,版权说明分区布局显示

  3. 基本信息维护:学生和教师网页中相关展示的个人信息的维护

  4. 课程中心:课程管理、选课管理、成绩管理等。

  5. 学工管理:社会实践、课外活动、成果奖励、学生互评

  6. 学生主页:个人简介、照片、博客、用户画像(数据统计和行为分析)。

  7. 教师主页:个人简介、照片、研究方向、讲授课程、论文著作等。

非功能性需求:

  1. 用户体验:界面美观、布局协调、导航便捷(多级导航)、操作流畅。可用CSS样式、UI框架(如Element-UI)、图片、动画、音乐、视频、表格插件、富文本编辑插件等方式完善设计。
  2. 程序质量:具备输入校验(必填、数值/文本类型、格式)、系统运行稳定(遇到异常不崩溃、有反馈)、会话管理正确(有登录状态和权限校验,刷新页面后能够保持会话)、验证码。
  3. 数据设计:表和字段命名规范、约束健全(主键、唯一、非空等)、关系完备(通过外键实现,可导出E-R图)。
  4. 文档质量:实验报告包括项目简介、需求分析、架构设计、数据设计、使用说明等内容。

(2)时序图

学生端:

时序图2(删,以成就界面为例)

时序图3(查,以成就管理界面为例)

时序图1(增或改,以成就界面为例)

(3)类图

类图

2.开发环境介绍

语言开发集成环境:

  • IntelliJ IDEA
  • VS code

硬件环境:

  • Windows 11 X64环境

3.开发技术介绍

(1)前端:

  • Vue

    Vue 是一种用于构建用户界面的渐进式JavaScript框架。它易于上手且灵活,特别适合单页面应用(SPA)。Vue 强调声明式渲染和组件化架构,这意味着你可以通过简单地描述界面应该呈现的状态来构建复杂的界面。此外,Vue 的生态系统提供了路由、状态管理等强大的功能,使得开发大型应用变得更加高效。

  • Element-plus

    Element-plus 是一款基于 Vue 3 的桌面端组件库,它提供了一整套丰富的、可重用的组件,如表格、对话框、菜单等,极大地加速了基于 Vue 的界面开发过程。Element-plus 的设计风格简洁、现代,且易于自定义,能够帮助开发者快速构建美观且功能丰富的应用界面。

  • JavaScript

    JavaScript 是一种高级编程语言,用于网页开发、服务器端开发等多个领域。作为一种解释型语言,JavaScript 主要用于添加交互性和动态效果到网页上。它是万维网的三大核心技术之一,与HTML和CSS共同工作。JavaScript 支持面向对象、命令式及函数式编程风格,灵活性和广泛的应用使其成为开发者的首选。

  • TypeScript

    TypeScript 是 JavaScript 的一个超集,它在 JavaScript 的基础上添加了静态类型检查。这意味着开发者可以在代码中定义变量和函数的类型,这样就可以在编译时捕获许多潜在的错误。TypeScript 的类型系统是可选的和灵活的,可以为大型项目带来更高的稳定性和可维护性。它完全兼容JavaScript,并且可以利用JavaScript的丰富生态系统。

  • Echarts

    Echarts 是一个使用 JavaScript 实现的开源可视化库。它提供了丰富的图表类型,如折线图、柱状图、散点图等,非常适合于数据的图形化表示。Echarts 易于上手,支持高度自定义,并且能够轻松地嵌入到网页中。其优秀的交互性和出色的性能使其成为数据可视化项目的优选工具。

  • v-md-editor

    v-md-editor 是一款基于 Vue 的 Markdown 编辑器,它为用户提供了一个易于使用的界面来编写和预览Markdown文档。v-md-editor 支持多种扩展和自定义配置,例如代码高亮、主题更换等,能够满足多样化的编辑需求。此外,它的响应式设计保证了在不同设备上的良好体验。

  • Axios

    Axios 是一个基于 Promise 的 HTTP 客户端,用于浏览器和 node.js。它使得发送 HTTP 请求变得简单和灵活。Axios 具有广泛的特性,包括从浏览器中创建 XMLHttpRequests 和从 node.js 发出 http 请求、支持 Promise API、拦截请求和响应、转换请求和响应数据等。

(2)后端:

  • Spring Boot

    项目使用Spring Boot作为后端开发框架,Spring Boot是由Pivotal团队提供的一套开源框架,可以简化spring应用的创建及部署。作为一个强大的开发框架,它不仅提供了快速启动器和自动配置,还整合了大量的开发工具和库。这使得开发者能够更专注于业务逻辑的实现,而不必花费大量时间在配置和集成上。同时,Spring Boot 的内置容器简化了应用程序的部署流程,使得团队在项目开发时更加便捷。

  • Spring Data Jpa

    JPA(Java Persistence API)即java持久化API,是spring提供的一款对于数据访问层(Dao层)的框架,为数据库操作提供了一种简单的抽象方式,将 Java 对象与数据库表之间建立映射关系。使用Spring Data JPA,只需要按照框架的规范提供dao接口,不需要实现类就可以完成数据库的增删改查、分页查询等方法的定义,不仅提供了更加面向对象的数据库操作方式,还简化了数据持久化的代码编写,减少了手动编写 SQL 查询语句的工作量。

  • Spring Security

    Spring Security 是一个功能强大的安全框架,为应用程序提供了全方位的安全性保障。它支持身份验证、授权、攻击防范等功能,能够有效地保护系统免受各种恶意攻击,并管理用户的权限和访问控制。

  • Redis

    Redis 是一种高性能的键值存储数据库,广泛用于临时数据的存储和缓存。它不仅具有出色的读写性能,还支持多种数据结构,如字符串、哈希、列表等。在本项目中,利用 Redis 存储邮箱验证码信息,并设置有效期为5分钟,为用户身份验证提供了便捷而高效的解决方案。

  • Lombok

    Lombok 是一款方便实用的 Java 库,通过注解来消除样板代码,例如生成 getter、setter、toString 等方法。这种自动生成代码的方式极大地简化了开发流程,让团队成员可以更专注于业务逻辑的实现,而不必为常规的代码编写而烦恼。

4.团队分工

项目组共有4人,权重均为1,分工如下:

  • 王春雨(202200300043):
    • 全部主体功能的后端实现(用户登陆注册,密码修改,找回密码;学生社会实践、课外活动、成果奖励、学生互评;选课管理;教师论文;博客管理)
    • 前后端图片验证码及邮箱验证码的实现。
    • 数据库设计
    • 部分接口在postman中的测试使用
    • 前端注册、忘记密码、修改密码的实现,登录失败提示,登录成功及注册成功换页,支持三种角色的登录,两种角色的注册
    • 前端社会实践管理、成就奖励管理的实现
    • 前端密码格式、邮箱格式、手机号格式等部分格式校验的实现
    • 部分实验报告的书写及实验报告的排版
  • 高圆源(202200300300)
    • 项目初期结构图的绘制,视觉稿的设计,确定项目大致风格
    • 前端社会实践功能的实现
    • 项目查询和分页的实现
    • 学生端与教师端个人主页设计与实现
    • 学生端博客系统的增删查看功能的实现
    • 前端富文本编辑和预览的实现,包括查看个人简介、博客内容等
    • 多个弹窗组件页面(如社会实践等)即组件间传参的实现
    • 学生行为分析图表的实现
  • 黄婉姗(202200300095)
    • 负责前端框架的搭建,完成路由管理router、状态管理pinia、网络请求axios的配置。
    • 完成了整体页面的布局,使用element-ui的布局容器。
    • 进行整体样式的设计,比如主题色的更改、标签样式的初始化。
    • 前端管理员端的学科管理、课程管理。
    • 前端教师端的课程管理、成绩管理。
    • 前端学生端的课程管理(选课系统、查看课表、查看成绩)。
    • 前端基础的登陆页面。
  • 许新鑫(202200300219)
    • 前端教师端教师论文界面
    1. 前端管理员端教师管理、学生管理界面
    2. 前端管理员端互评管理的界面,学生端学生互评实现查看并评价本班同学、查看我的评论以及历史评论的界面的实现
    3. 前端学生端课外活动的界面
    4. 前端管理员端班级管理界面的实现、以及查看没有班级的同学并对及分配班级的功能
    5. 项目报告中部分图表的绘制

第二部分 需求分析

需求描述

通用功能

  1. 登录注册

​ 三种角色的登录,登录成功换页,登录失败提示

​ 教师与学生注册,通过邮箱发送验证码,密码强度判断,注册成功后跳转至登录页面,失败提示

​ 找回密码(邮件验证码),重置密码

​ 前端框架展示:二级菜单导航,系统logo,个人头像,版权说明,分区布局显示

  1. 基本信息维护

    密码,邮箱,手机,地址、身份证号,个人简介

    * 密码复杂性的要求

    *身份证号合法性判断

    *手机号是否合法

    *个人简介可Markdown编辑

学生端

  1. 个人主页

    点击照片可进行修改(上传、缩放、存储方法)

    基本信息展示

    数据统计和行为分析:根据学生GPA以及分数数据进行分析并绘制图表

    奖励/实践清单:分栏展示该学生经审核通过的成果与社会实践

    个人简介:使用Markdown预览查看该学生简介

  2. 课程中心

    • 选课系统

      选课开启时间内可选择课程及退课操作

      冲突检测:上课时间、剩余容量是否冲突

      条件筛选:通过课程名、课序号、教师、类型、节次进行筛选

    • 查看课表

      将已选课程呈现在学生课表上,按类型赋予不同颜色

      悬停显示课程详细信息

    • 查看成绩

      根据教师端后台赋分计算总成绩并展示

      查看该课程在班级内的排名

  3. 学工管理

    • 社会实践(主题、名称、时间、地点、成果、审核状态)

      可对时间以及审核状态进行排序或筛选

      编辑后会重新递交管理员审核

    • 课外活动(主题、时间、地点、介绍)

    • 成果奖励(名称、类别、级别、时间、内容、审核状态)

      可对时间以及审核状态进行排序或筛选

      编辑后会重新递交管理员审核

    • 学生互评

      根据班级查看本班同学名单

      可查看对应同学主页,并对其进行匿名评价

      查看历史评论列表(查看内容和删除)

      查看对我的评论(仅显示时间和内容)

  4. 博客系统

    显示所有文章(标题、摘要、发布时间、作者)

    查看我发布的文章(同上)

    点击文章可查看详细内容(Markdown渲染)

    对我的文章进行修改和删除操作

    发布新文章(Markdown编辑、标题、摘要、标签)

教师端

  1. 个人主页

    点击照片可进行修改修改(上传、缩放、存储方法)

    基本信息展示

    发布论文列表

    个人简介:使用Markdown预览查看该教师简介

  2. 课程中心

    • 查看课程

      查看该教师任教课程信息

    • 成绩管理

      查看选课学生名单并对其成绩进行编辑操作(可对学生及课程进行筛选)

  3. 论文管理(名称、作者、发表时间)

    实现基本增删改查

管理端

  1. 学科管理

    对学科进行统筹管理

    包括:开设单位、年级、名称、课序号、学时、学分、类型、介绍

    实现基本增删改查

  2. 课程管理

    通过课序号关联课程与教师,实现同一科目多人任教

    包括:教师工号、教师名称、课容量、上课时间、地点

    实现基本增删改查

    选课状态按钮管理

  3. 人员管理

    • 学生管理(姓名、学号、性别等基本信息)

      实现基本增删改查

    • 教师管理(姓名、工号、性别等基本信息)

      实现基本增删改查

    • 班级管理

      班级基本信息(名称、年级、学院)并实现增删改查

      查看班级学生列表(学号、姓名、性别等基本信息)

      查看未分配班级的同学(基本信息)并为其分配现有班级

  4. 学工管理

    • 社会实践(学生姓名、学号、主题、名称、时间、地点、成果、审核状态)

      可对时间以及审核状态进行排序或筛选

      对社会实践条目进行审核

    • 成果奖励(学生姓名、学号、名称、类别、级别、时间、内容、审核状态)

      可对时间以及审核状态进行排序或筛选

      对社会实践条目进行审核

    • 学生互评(评价人与被评价人学号、姓名、评价内容、评价时间)

      对其进行查看和删除操作

产品功能信息结构图

mmexport1701692218428(1)

第三部分 架构设计

设计系统的总体架构

系统中采用的开发框架是Spring Boot。Spring Boot遵循一个分层的体系结构,其中每个层都与其直接在其下方或上方的层(层次结构)进行通信。

本系统基于web的B/S架构设计完成,前端使用vue.js+ElementPlus进行前后端分离,前端框架采用了比较流行的渐进式javaScript框架Vue.js。使用Vue-Router和Vuex实现动态路由和全局状态管理,Axios实现前后端通信,Element Plus组件库使页面快速成型。主要编程语言为nodejs和mysql。

采用Spring Boot 开发框架,可以通过大量的自动配置来简化开发,可以更加专注于业务逻辑部分代码的开发,减少了工作量,同时加快了开发速度。对于追求效率和简洁方便的开发者来说如虎添翼。同时也摆脱了复杂的配置工作以及依赖的管理工作,避免了大量的样板代码和配置,减少出错和延误。

系统采用前后端分离,通过前端views层和后端controller层的连接,将前端界面对应的方法传到后端,后端仅返回前端所需的数据,前端负责渲染HTML页面,后端不再控制前端的效果,用户看到什么样的效果,从后端请求的数据如何加载到前端中,都由前端自己决定,后端仅仅需要提供一套逻辑对外提供数据即可,并且前端与后端的耦合度相对较低,在这种模式中,我们通常将后端开发的每个视图都成为一个接口,或者API,前端通过访问接口来对数据进行增删改查。后台负责提供数据,前端负责数据展示,职责分离,分工明确。

同时面向接口的编程思想始终贯穿前后,实际开发出来的代码十分的简洁,而且逻辑清晰明确,也提高了功能类的代码复用率。

Views层:

Views层负责展示模型数据,接收客户请求,并将业务处理最后的结果呈现给用户。基于渐进式框架Vue.Js的实现,通过调整页面结构和命名匹配规则,将接收到的数据在经过描述渲染后,最终展示页面给用户浏览。由于Views的开发框架自动完成,所以只需不断调整,就可以做到对页面结构的不断调整。

Controller层:

Controller层,负责具体的业务模块流程的控制,控制的配置也同样是在Spring的配置文件里进行,针对具体的业务流程,会有不同的控制器。

作为Model和Views之间的沟通桥梁,Controller层用于响应用户请求,委托Model处理,选择Views,并把Model返回的数据递交给Views。由于该信息管理系统的业务逻辑处理相对简单,所以在controller层一并实现,而不再设计service层。而且该层也解决了页面代码与业务逻辑代码之间耦合的问题,通过对用户请求进行拦截,然后分别在相应的Controllers类中进行处理,将处理结果返映给Model层,对数据库中数据进行CURD,最后将结果返回页面。

我们具体的设计过程可以将流程进行抽象归纳,设计出可以重复利用的子单元流程模块。这样不仅使程序结构变得清晰,也大大减少了代码量。

Model层:

Model层属于数据层,用于做数据库的操作,主要是增删查改,在基础的mvc划分中,model层还需要处理数据验证使用JPA对数据库进行数据持久化操作,而且 JPA的API定义的规范提供了数据库的基本操作,不必再纠结于复杂的SQL代码的开发,同时JPA约定了面向对象的查询语言,将以往面向数据库表的查询,改变为面向实体类进行的数据查询。

Model层中存放有实体类,其中的对象与数据库中的属性值基本保持一致,实现了数据库中字段与对象属性之间的映射,因此只要在开发中定义好实体类之间的关系,数据库中表与表之间的关系也就确定了。

而且Model中还包括了成员变量的get/set方法等功能。

系统工作流程

基于以上的系统架构,可以这样描述该系统的工作流程:

前端模块

登录页面由一个表单构成,输入框输入账号和密码并选择相对应的登录身份,输入成功后跳转到信息管理页面。信息管理页面分为三个,分别是学生端个人主页,教师端个人主页,管理员端信息管理。

信息管理页面可以对学生教师等信息进行管理,并可以通过姓名学号查询,包含基本信息以及课程、日志管理、活动、竞赛、学生荣誉等信息。

后端模块

表现层用来接受数据和返回数据。采用RestFul接口风格,通过接口的类别区分接口的功能。

服务层是具体业务实现的地方。对表现层的数据进行处理,并调用持久层的接口,对数据进行增删查改以及进行相关的级联操作。

因为数据库方面采用的是逻辑外键,需要在服务层对表与表之间的关联进行相关操作。

持久层是访问数据的地方。采用了MyBatis框架,对数据库的访问进行了封装,通过关联数据库表相关的实体,完成对象关系映射,简化了数据库的操作。

各层概要设计

在Views层中,利用基于渐进式框架Vue.Js描述的页面结构构建形成相应的页面,以实现用户与系统之间的交互界面。利用该页面传送用户的操作请求request和接受系统返回的响应response。

​ 在系统接收到操作请求后,不同的请求在controllers中相应的控制器中进行处理,并且根据controllers处理后返回的数据,影响对应的响应页面。

​ 在Controller层中,controllers内构造的方法负责提供实体对象和数据,将其反映给前端界面,同时controllers中负责分派用户请求,然后调用对应的repository接口进行业务处理实现对相应数据的增删改查操作,最后把结果返回到前端界面。

​ 在Model层中,利用JPA的对象——关系表的映射关系,实现实体对象与数据库表之间的映射,并将运行期的实体对象持久化到数据库中,通过继承JpaRepository,使用其中的增删改查,分页等的方法,实现与数据库之间的交互,同时通过接口返回相应的操作结果。

​ 利用上述的系统架构进行开发,可以将views,controller,model之间进行较好的分离,三层之间全都面向接口。

而也是因为接口的实现,不同层之间不会相互产生影响,这提高了系统的可复用性,以及开发的灵活性。同时耦合度小也有利于我们的小组成员选择自己擅长的开发方向进行设计,大幅度提高了团队开发的效率。

第四部分 数据设计

Er图

java

数据库表

Achievement

学生奖励成就

image-20231231143817131

package org.fatmansoft.teach.models.student;

import lombok.*;
import org.fatmansoft.teach.models.teacher.Teacher;

import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(	name = "achievement",
        uniqueConstraints = {
        })
public class Achievement {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer achievementId;

    @ManyToOne
    @JoinColumn(name = "student_id")
    private Student student;

    @NotBlank
    @Size(max = 60)
    private String name;
    //奖项名称

    private String level;
    //等级 国家级 省级 市级 校级 院级

    @Size(max = 40)
    private String type;

    @Size(max = 200)
    private String content;
    //内容

    private String time;

    private Integer status=0;
    // 2为未通过 1为已通过审核 0为待审核

}

Activity

学生活动

image-20231231144033884

package org.fatmansoft.teach.models.student;

import lombok.*;

import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(	name = "activity",
        uniqueConstraints = {
        })

public class Activity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer activityId;

    @ManyToOne
    @JoinColumn(name = "student_id")
    private Student student;

    private String day;//活动日期

    @Size(max = 200)
    private String introduction;//活动介绍

    @Size(max = 60)
    private String location;//活动地点

    @NotBlank
    @Size(max = 30)
    private String title;//活动主题

}

Blog

博客

image-20231231144256226

image-20231231144313929

package org.fatmansoft.teach.models.student;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import java.time.LocalDateTime;
import java.util.Date;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(	name = "blog",
        uniqueConstraints = {
        })
public class Blog {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer blogId;

    @ManyToOne
    @JoinColumn(name = "student_id")
    private Student student;

    @Size(max = 50)
    private String title; //题目

    @Size(max = 15)
    private String tag; //标签

    private String createTime; //创建时间(即发布时间)

    private String updateTime; // 设定是作者可以在任意时间修改

    private String content; //文章内容

    private String digest; //摘要
}

Campus

学院

image-20231231144405231

package org.fatmansoft.teach.models.student;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.*;
import javax.validation.constraints.Size;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(	name = "campus",
        uniqueConstraints = {
        })
public class Campus {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer campusId; //学院

    @Size(max = 50)
    private String name;
}

Clazz

班级

image-20231231144504759

package org.fatmansoft.teach.models.student;


import lombok.*;

import javax.persistence.*;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(	name = "clazz",
        uniqueConstraints = {
        })
public class Clazz {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer clazzId;

    private String clazzName;

    @ManyToOne
    @JoinColumn(name = "grade_id")
    private Grade grade;

    @OneToOne
    @JoinColumn(name="capmus_id")
    private Campus campus; //学院
}

Course

image-20231231144554380

package org.fatmansoft.teach.models.student;

import lombok.*;

import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;

/**
 * Course 课程表实体类  保存课程的的基本信息信息,
 * Integer courseId 人员表 course 主键 course_id
 * String num 课程编号
 * String name 课程名称
 * Integer credit 学分
 * Course preCourse 前序课程 pre_course_id 关联前序课程的主键 course_id
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(	name = "course",
        uniqueConstraints = {
        })
public class Course {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer courseId;

    @Size(max = 50)
    private String name;

    @NotBlank
    @Size(max = 20)
    private String num; //课序号

    private Integer hour; //总课时

    private Integer credit; //学分

    private Integer type; //必修 限选 任选  0 1 2 type

    @OneToOne
    @JoinColumn(name="capmus_id")
    private Campus campus; //学院

    @ManyToOne
    @JoinColumn(name = "grade_id")
    private Grade grade; //开课年级

    private String introduction; //课程介绍

}

Evaluation

image-20231231144636074

package org.fatmansoft.teach.models.student;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import java.util.Date;

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@Table(	name = "evaluation",
        uniqueConstraints = {
        })
public class Evaluation {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer evaluationId;

    @ManyToOne
    @JoinColumn(name = "evaluator_id")
    private Student evaluator; //评估者

    @ManyToOne
    @JoinColumn(name = "student_id")
    private Student student; //被评估者

    @NotBlank
    @Size(max = 200)
    private String eval; //评价内容

    @NotBlank
    private String evalTime; //评价时间

}

Grade

年级

image-20231231144718226

package org.fatmansoft.teach.models.student;


import lombok.Getter;
import lombok.Setter;
import javax.persistence.*;

@Entity
@Table(	name = "grade",
        uniqueConstraints = {
        })
@Getter
@Setter
public class Grade {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer gradeId;

    private String gradeName;
}

Person

image-20231231144945057

image-20231231145005003

package org.fatmansoft.teach.models.system;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.*;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import java.util.Date;
/**
 * Person人员表实体类 保存人员的基本信息信息, 账户、学生和教师都关联人员,
 * Integer personId 人员表 person 主键 person_id
 * String num 人员编号
 * String name 人员名称
 * String type 人员类型  0管理员 1学生 2教师
 * String dept 学院
 * String card 身份证号
 * String gender 性别  1 男 2 女
 * String birthday 出生日期
 * String email 邮箱
 * String phone 电话
 * String address 地址
 * String introduce 个人简介
 */
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@Table(	name = "person",
        uniqueConstraints = {
                @UniqueConstraint(columnNames = "num"),   //人员表中的编号 唯一
        })
public class Person {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer personId;

    @NotBlank    // 字段非空
    @Size(max = 20)   //字段长度最长为20
    private String num;

    @Size(max = 50)
    private String name;

    @Size(max = 2)
    private String type;

    @Size(max = 50)
    private String dept; //专业

    @Size(max = 20)
    private String card;

    @Size(max = 2)
    private String gender;//性别

    private String birthday;

    @Size(max = 60)
    @Email
    private String email;

    @Size(max = 20)
    private String phone;

    @Size(max = 20)
    private String address;

    @Size(max = 1000)
    private String introduce;

}

ScientificPayoffs

教师论文

image-20231231145207721

package org.fatmansoft.teach.models.teacher;

import lombok.*;

import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import java.io.File;
/**
 * 科研成果
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(	name = "scientific_payoffs",
        uniqueConstraints = {
        })
public class ScientificPayoffs {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer scientificPayoffsId;

    @ManyToOne
    @JoinColumn(name = "teacher_id")
    private Teacher teacher;

    @Size(max = 100)
    private String paperName; //论文


    private String day; // 发布时间

    @NotBlank
    @Size(max = 60)
    private String authors; //作者
}

Score

分数

image-20231231145256681

package org.fatmansoft.teach.models.student;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.fatmansoft.teach.models.teacher.TeacherCourse;

import javax.persistence.*;

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@Table(	name = "score",
        uniqueConstraints = {
        })
public class Score {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer scoreId;

    @ManyToOne
    @JoinColumn(name = "student_id")
    private Student student;

    @ManyToOne
    @JoinColumn(name = "teacher_course_id")
    private TeacherCourse teacherCourse;

    //平时分+期末考试分 = 总成绩(前端计算求和)

    private Integer commonMark; //平时成绩

    private Integer finalMark; //期末考试成级

    private Integer isResult; //成绩是否公布  1为公布 0为不公布

}

Social

学生社会实践

image-20231231145452905

package org.fatmansoft.teach.models.student;

import lombok.*;

import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(	name = "social",
        uniqueConstraints = {
        })
public class Social {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer socialId;

    @ManyToOne
    @JoinColumn(name = "student_id")
    private Student student;

    private Integer auditStatus; //审核状态(审核中0、已通过1、未通过2)

    private String day; // 时间

    private String location;

    @Size(max = 20)
    private String groupName;  // 团队名称

    @Size(max = 50)
    private String theme;  // 实践主题

    @Size(max = 200)
    private String digest; // 摘要

    private String harvest; // 成果


}

StaticValue

静态值表 存储选课开启与否的控制变量

image-20231231145535215

package org.fatmansoft.teach.models.system;

import lombok.*;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;

@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
@Table(	name = "static_value",
        uniqueConstraints = {
        })
public class StaticValue implements Serializable {

    @Id
    private String key;

    @Column(name = "value")
    private String value;

    @Column(name = "comment")
    private String comment;

    private static final long serialVersionUID = 1L;
}

Student

image-20231231145643534

package org.fatmansoft.teach.models.student;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.fatmansoft.teach.models.system.Person;

import javax.persistence.*;

/**
 * Student学生表实体类 保存每个学生的信息,
 * Integer studentId 用户表 student 主键 student_id
 * Person person 关联到该用户所用的Person对象,账户所对应的人员信息 person_id 关联 person 表主键 person_id
 * String class 班级
 *
 */
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@Table(	name = "student",
        uniqueConstraints = {
        })
public class Student {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer studentId;

    @OneToOne
    @JoinColumn(name = "person_id")
    private Person person;

    @ManyToOne
    @JoinColumn(name = "clazz_id")
    private Clazz clazz;

}

Teacher

image-20231231145720300

package org.fatmansoft.teach.models.teacher;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.fatmansoft.teach.models.student.Campus;
import org.fatmansoft.teach.models.system.Person;

import javax.persistence.*;
import javax.validation.constraints.Size;

    @Entity
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    @Table(	name = "teacher",
            uniqueConstraints = {
            })
    public class Teacher {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Integer teacherId;

        @OneToOne
        @JoinColumn(name="person_id")
        private Person person;

        @Size(max = 20)
        private String title;

        @Size(max = 50)
        private String degree;

        @Size(max = 100)
        private String direction; //研究方向

    }

TeacherCourse

teacher和course多对多关系的中间表

实现一门学科有多个教师授课

image-20231231145750499

package org.fatmansoft.teach.models.teacher;


import lombok.*;
import org.fatmansoft.teach.models.student.Course;

import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@Table(	name = "teacher_course",
        uniqueConstraints = {
        })
public class TeacherCourse {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    @ManyToOne
    @JoinColumn(name = "teacher_id")
    private Teacher teacher;

    @ManyToOne
    @JoinColumn(name = "course_id")
    private Course course;

    private Integer selectedCount = 0; //选课人数

    private Integer courseCapacity; //课容量

    //上课时间
    private Integer day; //星期x   1 2 3 4 5 6 7

    private Integer timeOrder; //第x节 1 2 3 4 5

    private String place; //上课地点
}

User

用户信息

密码加密存储

image-20231231145900763

image-20231231145924988

package org.fatmansoft.teach.models.system;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import java.util.Date;
/**
 * User用户表实体类 保存每个允许登录的信息人员的账号信息,
 * Integer userId 用户表 user 主键 user_id
 * UserType userType 关联到用户类型对象
 * Person person 关联到该用户所用的Person对象,账户所对应的人员信息 person_id 关联 person 表主键 person_id
 * String userName 登录账号 和Person 中的num属性相同
 * String password 用户密码 非对称加密,这能加密,无法解码
 *
 */

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@Table(	name = "user",
        uniqueConstraints = {
                @UniqueConstraint(columnNames = "userName"),
        })
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer userId;

    @ManyToOne()
    @JoinColumn(name = "user_type_id")
    private UserType userType;

    @OneToOne
    @JoinColumn(name="person_id")
    private Person person;

    @NotBlank
    @Size(max = 20)
    private String userName;

    @NotBlank
    @Size(max = 60)
    private String password;

    private Integer loginCount;

    @Size(max = 20)
    private String lastLoginTime;

    @Size(max = 20)
    private String createTime;

    private Integer creatorId;

}

UserType

用户三种身份选择 管理员 学生 教师

image-20231231150036670

第五部分 使用说明

邮箱注册及找回密码

验证码存储在本地运行的redis中(设置5min有效期,5min后验证码会失效),若要使用该功能,需在本地运行redis,请设置端口号及密码如下:

port: 6379
password: 

redis使用教程:

  1. 安装并运行redis (可以在本地直接安装,也可以使用docker运行redis)

    在docker中使用redis:

    docker pull redis:latest
    docker run -itd --name redis -p 6379:6379 redis
  2. 修改密码:

CONFIG SET requirepass "your_new_password"

若使用docker:

docker exec -it redis redis-cli
CONFIG SET requirepass "your_new_password"
  1. 运行该项目即可

若需查看redis存储内容,可使用如Another Redis Desktop等可视化工具

也可以修改后端项目application.yml文件中的redis配置ip地址为自己的服务器ip,redis端口号,密码无

详细界面说明

1)登录与注册界面

登录页面涉及学生、教师、管理三端,注册页面仅涉及学生和教师端

image-20231231143822190

在对应框中输入用户名和密码,并输入正确的验证码,点击登录,即可跳转至账号对应身份的主页

反馈信息

image-20231231144054991

image-20231231144125228

点击红框中的忘记密码,即可跳转到对应页面

image-20231231144255971

image-20231231144403217

在对应输入框中输入信息,即可成功修改密码

image-20231231144548647

点击注册,跳转到注册页面

image-20231231144735211

选择对应身份,输入信息,并填写正确的邮箱验证码即可注册成功

2) 人员管理模块

​ 本部分只涉及管理员端。

  1. 用户管理界面:

    该界面主要实现对于整个管理系统后台管理平台的注册的用户(学生或教师)进行展示,通过表格展示所有用户的详细数据,此处以学生端管理为例,教师同理。

image-20231231145104419

image-20231231145254661

​ 通过红框内输入学工号或者姓名,可对整个用户表进行搜索并展示出对应的内容,如图

image-20231231145326196

​ 通过表单中的详细信息,可以实现对用户基础信息的详细查看和编辑

image-20231231145437814

image-20231231145507875

新增

​ 对于新增一个用户信息,可以采用左上角的新增按钮,并填入相应信息,对于红星所表示的信息为必须填入,成功后可点击保 存按钮进行新增操作。

image-20231231145820248

image-20231231145905049

​ 格式校验

image-20231231150015534

在显示新增成功后可在表格末尾看到新增的用户

image-20231231150106393

image-20231231150138549

删除

​ 可通过表格中的删除按钮将该行信息进行删除

image-20231231150247937

image-20231231150312750

  1. 班级管理界面

    该界面主要实现对于整个管理系统后台管理平台的班级进行展示,通过表格展示所有班级的详细数据。

    image-20231231150521674

    新增

    点击左上角新增按钮,即可跳出弹窗

    image-20231231150646173

    点击每一行末尾的删除按钮,如图,最后一行班级被删除

    image-20231231150759745

    image-20231231150821932

    查询操作,在下拉框中选择学院和年级,点击查询,即可获得对应班级表格

    image-20231231150927975

    image-20231231150949904

    也可通过右侧搜索框对班级名称进行搜索

    image-20231231151103645

    点击按钮查看未分配班级同学

    image-20231231151145359

    可以对其进行分班操作

    image-20231231151212180

    image-20231231151229081

    点击确定,即可在新班级名单下看见该同学

    image-20231231151317470

3) 课程管理模块

  • 管理员端

    1. 学科管理界面

      image-20231231151429376

      点击左上角新增,即可新增一门学科

      image-20231231151535191

      image-20231231151548355

      点击确定,即可在表格末尾看见新增的学科

      image-20231231151637207

      点击每一行的删除按钮,即可删除该学科

      点击编辑,即可查看学科信息并对其进行修改

      image-20231231151754840

      image-20231231151811546

      右上角输入对应课程名或课序号,选择课程类型,即可进行查询操作

      image-20231231151841098

      image-20231231151924388

    2. 课程管理界面

      右上角按钮控制选课的开关

      image-20231231152008274

      输入对应信息即可进行查询

      image-20231231152037749

      点击左上角新增,弹出弹窗

      image-20231231152104836

      image-20231231152118174

      新增成功后即可查看新增的课程

      点击编辑按钮,即可查看课程具体信息,并对其进行修改

      image-20231231152214630

      点击每一行的删除按钮,即可删除该学科

  • 学生端

    1. 选课系统界面

      image-20231231154500923

      在此页面,学生可以查看所有课程名单,并实现选退课操作

      若不在选课时间内,点击选课系统会弹出提示

      顶部可确定筛选条件,对课程列表进行查询

      image-20231231154650136

      对未选课程可进行选课,若与已选课程时间冲突,会弹出提示;同时若课容量不足,也会提示

      image-20231231154930271

      image-20231231154809990

      image-20231231154834992

      image-20231231154901366

      同时可以进行退课操作

      image-20231231154943977

      image-20231231154959373

    2. 课表界面

      完成选课后,点击我的课表,即可查看对应课表

      image-20231231155059607

    3. 查看成绩界面

      选课后,每一门已选课程都与成绩相关联,成绩由授课教师在后台进行编辑和上传,呈现在学生端

      image-20231231155210282

      点击查看排名,即可获取自己在该门课所有选课学生中的名次

      image-20231231155249915

      image-20231231155305468

  • 教师端

    1. 查看课程界面

      教师可以在这个界面查看自己任教的课程

      image-20231231161918197

    2. 成绩管理界面

      对选课学生进行成绩上传

      image-20231231162058572

      左上角输入学生学号或选择任教课程进行查询

      image-20231231162137371

      点击编辑按钮,对学生成绩进行修改

      image-20231231162201016

      image-20231231162212578

      若成绩不符合规范,会弹出提示

      image-20231231162237711

4) 学生互评管理模块

  • 管理端

    互评管理可以查看所有评论以及双方信息,管理仅可对评论进行查看和删除操作

    image-20231231152419855

    输入对应信息即可进行查询

    image-20231231152530007

    点击查看,可查看每条评论的具体信息

    image-20231231152633511

    image-20231231152650474

    点击删除,可删除该评论,以避免不良言论的出现

    image-20231231152705839

    image-20231231152717495

  • 学生端

    学生能查看本班所有同学的列表

    image-20231231155414981

    点击查看主页,即可跳转至对应同学的主页,查看他的详细信息

    image-20231231155451451

    image-20231231155508916

    点击评价,弹出评价弹窗,编辑评价信息即可

    image-20231231155546510

    image-20231231155602673

    image-20231231155615740

    点击查看历史评论按钮,即可查看我发出的评论历史

    image-20231231155650855

    image-20231231155707085

    在此处可以对评论进行删除操作

    image-20231231155852546

    image-20231231155904854

    点击查看我的评论按钮,即可查看别人对我的所有评论(匿名)

    image-20231231155739132

    image-20231231155805658

5) 学工管理/论文管理模块

  • 管理端

    管理需要对社会实践以及成果奖励两部分进行审核,此处以社会实践为例

    image-20231231152904579

    输入对应信息即可进行查询

    image-20231231152935614

    也可以通过表格的筛选功能,对审核状态进行筛选

    image-20231231153019911

    点击查看,可查看每条社会实践的具体信息

    image-20231231153145694

    在详细信息页面,可以对其进行审核操作

    image-20231231153205208

    image-20231231153242975

    弹窗关闭,即可看见审核状态的更新

    点击删除,可删除该社会实践

    image-20231231153331866

    成果奖励管理页面同理,此处仅做展示

    image-20231231153907761

  • 学生端

    学生可以在该模块对社会实践、课外活动、成果奖励三部分进行增删改查的操作,此处以社会实践为例,其他模块类似

    image-20231231160039970

    点击左上角新增按钮,弹出新增弹窗

    image-20231231160110569

    image-20231231160124237

    输入查询内容,即可对社会实践栏目进行筛选

    image-20231231160204041

    点击查看按钮,可对现有社会实践进行查看和编辑操作,注意,若编辑已通过的社会实践,则审核状态将变为待审核

    image-20231231160250912

    image-20231231160303318

    删除社会实践

    image-20231231160332676

    image-20231231160353699

  • 教师端

    教师端论文管理,与学生端相似,此处仅做图片展示

    image-20231231161833054

6) 个人主页与模块

本页面仅涉及学生与教师端,且学生端内容包含教师端的功能,所以仅对学生端进行具体阐述

  • 学生端

    image-20231231154123442

    image-20231231154259682

    如图所示,在个人主页界面,首先呈现个人基本信息与头像;而后是相关的教育背景,例如绩点、成绩区间等;再往后是个人荣誉的展示,最后还有个人简介。

    可以在账号安全/修改个人信息页面中对基本信息和个人简介进行修改。修改成功后主页展示内容也会相应变更

    image-20231231154348499

  • 教师端

    image-20231231161804437

7)博客系统模块

此模块仅涉及学生端。

  1. 查看所有文章界面

    该页面以时间倒序显示所有博客,右侧展示学生信息和评论

    image-20231231160544965

    在搜索框中输入内容,可对博客进行搜索

    image-20231231160618152

    点击博客卡片,可以跳转至博客详情

    image-20231231160655261

    image-20231231160711872

    点击右侧查看文章按钮,可以进入我的文章页面

    image-20231231160810486

  2. 我的文章界面

    image-20231231160840336

    点击博客卡片中的编辑按钮,可对博客进行编辑

    image-20231231161037074

    image-20231231161051580

    点击删除操作,可删除对应博客

    image-20231231161138469

    点击写新文章按钮,即可进入发布新文章界面

    image-20231231161411532

  3. 发布新文章界面

    image-20231231161506024

    可以用Markdown语法对文章内容进行撰写

    底部设定标签以及文章摘要

    image-20231231161703633

详细界面说明

1)登录与注册界面

登录页面涉及学生、教师、管理三端,注册页面仅涉及学生和教师端

image-20231231143822190

在对应框中输入用户名和密码,并输入正确的验证码,点击登录,即可跳转至账号对应身份的主页

反馈信息

image-20231231144054991

image-20231231144125228

点击红框中的忘记密码,即可跳转到对应页面

image-20231231144255971

image-20231231144403217

在对应输入框中输入信息,即可成功修改密码

image-20231231144548647

点击注册,跳转到注册页面

image-20231231144735211

选择对应身份,输入信息,并填写正确的邮箱验证码即可注册成功

2) 人员管理模块

​ 本部分只涉及管理员端。

  1. 用户管理界面:

该界面主要实现对于整个管理系统后台管理平台的注册的用户(学生或教师)进行展示,通过表格展示所有用户的详细数据,此处以学生端管理为例,教师同理。

image-20231231145104419

image-20231231145254661

​ 通过红框内输入学工号或者姓名,可对整个用户表进行搜索并展示出对应的内容,如图

image-20231231145326196

​ 通过表单中的详细信息,可以实现对用户基础信息的详细查看和编辑

image-20231231145437814

image-20231231145507875

新增

​ 对于新增一个用户信息,可以采用左上角的新增按钮,并填入相应信息,对于红星所表示的信息为必须填入,成功后可点击保 存按钮进行新增操作。

image-20231231145820248

image-20231231145905049

​ 格式校验

image-20231231150015534

在显示新增成功后可在表格末尾看到新增的用户

image-20231231150106393

image-20231231150138549

删除

​ 可通过表格中的删除按钮将该行信息进行删除

image-20231231150247937

image-20231231150312750

  1. 班级管理界面

    该界面主要实现对于整个管理系统后台管理平台的班级进行展示,通过表格展示所有班级的详细数据。

    image-20231231150521674

    新增

    点击左上角新增按钮,即可跳出弹窗

    image-20231231150646173

    点击每一行末尾的删除按钮,如图,最后一行班级被删除

    image-20231231150759745

    image-20231231150821932

    查询操作,在下拉框中选择学院和年级,点击查询,即可获得对应班级表格

    image-20231231150927975

    image-20231231150949904

    也可通过右侧搜索框对班级名称进行搜索

    image-20231231151103645

    点击按钮查看未分配班级同学

    image-20231231151145359

    可以对其进行分班操作

    image-20231231151212180

    image-20231231151229081

    点击确定,即可在新班级名单下看见该同学

    image-20231231151317470

3) 课程管理模块

  • 管理员端

    1. 学科管理界面

      image-20231231151429376

      点击左上角新增,即可新增一门学科

      image-20231231151535191

      image-20231231151548355

      点击确定,即可在表格末尾看见新增的学科

      image-20231231151637207

      点击每一行的删除按钮,即可删除该学科

      点击编辑,即可查看学科信息并对其进行修改

      image-20231231151754840

      image-20231231151811546

      右上角输入对应课程名或课序号,选择课程类型,即可进行查询操作

      image-20231231151841098

      image-20231231151924388

    2. 课程管理界面

      右上角按钮控制选课的开关

      image-20231231152008274

      输入对应信息即可进行查询

      image-20231231152037749

      点击左上角新增,弹出弹窗

      image-20231231152104836

      image-20231231152118174

      新增成功后即可查看新增的课程

      点击编辑按钮,即可查看课程具体信息,并对其进行修改

      image-20231231152214630

      点击每一行的删除按钮,即可删除该学科

  • 学生端

    1. 选课系统界面

      image-20231231154500923

      在此页面,学生可以查看所有课程名单,并实现选退课操作

      若不在选课时间内,点击选课系统会弹出提示

      顶部可确定筛选条件,对课程列表进行查询

      image-20231231154650136

      对未选课程可进行选课,若与已选课程时间冲突,会弹出提示;同时若课容量不足,也会提示

      image-20231231154930271

      image-20231231154809990

      image-20231231154834992

      image-20231231154901366

      同时可以进行退课操作

      image-20231231154943977

      image-20231231154959373

    2. 课表界面

      完成选课后,点击我的课表,即可查看对应课表

      image-20231231155059607

    3. 查看成绩界面

      选课后,每一门已选课程都与成绩相关联,成绩由授课教师在后台进行编辑和上传,呈现在学生端

      image-20231231155210282

      点击查看排名,即可获取自己在该门课所有选课学生中的名次

      image-20231231155249915

      image-20231231155305468

  • 教师端

    1. 查看课程界面

      教师可以在这个界面查看自己任教的课程

      image-20231231161918197

    2. 成绩管理界面

      对选课学生进行成绩上传

      image-20231231162058572

      左上角输入学生学号或选择任教课程进行查询

      image-20231231162137371

      点击编辑按钮,对学生成绩进行修改

      image-20231231162201016

      image-20231231162212578

      若成绩不符合规范,会弹出提示

      image-20231231162237711

4) 学生互评管理模块

  • 管理端

    互评管理可以查看所有评论以及双方信息,管理仅可对评论进行查看和删除操作

    image-20231231152419855

    输入对应信息即可进行查询

    image-20231231152530007

    点击查看,可查看每条评论的具体信息

    image-20231231152633511

    image-20231231152650474

    点击删除,可删除该评论,以避免不良言论的出现

    image-20231231152705839

    image-20231231152717495

  • 学生端

    学生能查看本班所有同学的列表

    image-20231231155414981

    点击查看主页,即可跳转至对应同学的主页,查看他的详细信息

    image-20231231155451451

    image-20231231155508916

    点击评价,弹出评价弹窗,编辑评价信息即可

    image-20231231155546510

    image-20231231155602673

    image-20231231155615740

    点击查看历史评论按钮,即可查看我发出的评论历史

    image-20231231155650855

    image-20231231155707085

    在此处可以对评论进行删除操作

    image-20231231155852546

    image-20231231155904854

    点击查看我的评论按钮,即可查看别人对我的所有评论(匿名)

    image-20231231155739132

    image-20231231155805658

5) 学工管理/论文管理模块

  • 管理端

    管理需要对社会实践以及成果奖励两部分进行审核,此处以社会实践为例

    image-20231231152904579

    输入对应信息即可进行查询

    image-20231231152935614

    也可以通过表格的筛选功能,对审核状态进行筛选

    image-20231231153019911

    点击查看,可查看每条社会实践的具体信息

    image-20231231153145694

    在详细信息页面,可以对其进行审核操作

    image-20231231153205208

    image-20231231153242975

    弹窗关闭,即可看见审核状态的更新

    点击删除,可删除该社会实践

    image-20231231153331866

    成果奖励管理页面同理,此处仅做展示

    image-20231231153907761

  • 学生端

    学生可以在该模块对社会实践、课外活动、成果奖励三部分进行增删改查的操作,此处以社会实践为例,其他模块类似

    image-20231231160039970

    点击左上角新增按钮,弹出新增弹窗

    image-20231231160110569

    image-20231231160124237

    输入查询内容,即可对社会实践栏目进行筛选

    image-20231231160204041

    点击查看按钮,可对现有社会实践进行查看和编辑操作,注意,若编辑已通过的社会实践,则审核状态将变为待审核

    image-20231231160250912

    image-20231231160303318

    删除社会实践

    image-20231231160332676

    image-20231231160353699

  • 教师端

    教师端论文管理,与学生端相似,此处仅做图片展示

    image-20231231161833054

6) 个人主页与模块

本页面仅涉及学生与教师端,且学生端内容包含教师端的功能,所以仅对学生端进行具体阐述

  • 学生端

    image-20231231154123442

    image-20231231154259682

    如图所示,在个人主页界面,首先呈现个人基本信息与头像;而后是相关的教育背景,例如绩点、成绩区间等;再往后是个人荣誉的展示,最后还有个人简介。

    可以在账号安全/修改个人信息页面中对基本信息和个人简介进行修改。修改成功后主页展示内容也会相应变更

    image-20231231154348499

  • 教师端

    image-20231231161804437

7)博客系统模块

此模块仅涉及学生端。

  1. 查看所有文章界面

    该页面以时间倒序显示所有博客,右侧展示学生信息和评论

    image-20231231160544965

    在搜索框中输入内容,可对博客进行搜索

    image-20231231160618152

    点击博客卡片,可以跳转至博客详情

    image-20231231160655261

    image-20231231160711872

    点击右侧查看文章按钮,可以进入我的文章页面

    image-20231231160810486

  2. 我的文章界面

    image-20231231160840336

    点击博客卡片中的编辑按钮,可对博客进行编辑

    image-20231231161037074

    image-20231231161051580

    点击删除操作,可删除对应博客

    image-20231231161138469

    点击写新文章按钮,即可进入发布新文章界面

    image-20231231161411532

  3. 发布新文章界面

    image-20231231161506024

    可以用Markdown语法对文章内容进行撰写

    底部设定标签以及文章摘要

    image-20231231161703633

第六部分 特色功能

邮箱校验及验证码

邮箱验证码

注册及找回密码收到验证码如下:

image-20231231154620267

验证码由数字、大写字小写字母随机组成6位

验证码暂存在redis,5min内有效

authService

package org.fatmansoft.teach.service.system;

import org.apache.catalina.connector.Response;
import org.fatmansoft.teach.cache.IGlobalCache;
import org.fatmansoft.teach.models.student.Student;
import org.fatmansoft.teach.models.system.EUserType;
import org.fatmansoft.teach.models.system.Person;
import org.fatmansoft.teach.models.system.User;
import org.fatmansoft.teach.models.system.UserType;
import org.fatmansoft.teach.models.teacher.Teacher;
import org.fatmansoft.teach.payload.request.DataRequest;
import org.fatmansoft.teach.payload.response.DataResponse;
import org.fatmansoft.teach.payload.response.JwtResponse;
import org.fatmansoft.teach.repository.student.StudentRepository;
import org.fatmansoft.teach.repository.system.PersonRepository;
import org.fatmansoft.teach.repository.system.UserRepository;
import org.fatmansoft.teach.repository.system.UserTypeRepository;
import org.fatmansoft.teach.repository.teacher.TeacherRepository;
import org.fatmansoft.teach.security.jwt.JwtUtils;
import org.fatmansoft.teach.security.services.UserDetailsImpl;
import org.fatmansoft.teach.util.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ResourceLoader;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import javax.xml.crypto.Data;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class AuthService {

    @Autowired
    AuthenticationManager authenticationManager;

    @Autowired
    UserRepository userRepository;

    @Autowired
    UserTypeRepository userTypeRepository;

    @Autowired
    PasswordEncoder encoder;

    @Autowired
    JwtUtils jwtUtils;

    @Autowired
    private ResourceLoader resourceLoader;

    @Autowired
    private StudentRepository studentRepository;

    @Autowired
    private TeacherRepository teacherRepository;

    @Autowired
    private PersonRepository personRepository;

    @Autowired
    IGlobalCache iGlobalCache;

    private static final Logger logger = LoggerFactory
            .getLogger(AuthService.class);
    @Autowired
    private JavaMailSender javaMailSender;

    @Value("${spring.mail.username}")
    String fromEmail;


    public DataResponse sendEmail(DataRequest dataRequest) {
        String email = dataRequest.getString("email");
        String subject = dataRequest.getString("subject");
        logger.info("HTML email sending start");
        MimeMessage mimeMessage = javaMailSender.createMimeMessage();
        // 生成邮件验证码
        String mailVerificationCode = EmailUtil.generateRandomString(6);
        try {
            // Set multipart mime message true
            MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage,
                    true);
            mimeMessageHelper.setFrom(fromEmail);
            mimeMessageHelper.setTo(email);
            mimeMessageHelper.setSubject(subject);

            String html = "<html>"
                    + "<head>"
                    + "    <style>"
                    + "        body {"
                    + "            font-family: Arial, sans-serif;"
                    + "            background-color: #f5f5f5;"
                    + "            padding: 20px;"
                    + "        }"
                    + "        h1 {"
                    + "            color: #333;"
                    + "            text-align: center;"
                    + "        }"
                    + "        .container {"
                    + "            text-align:center;"
                    + "            width: 90%;"
                    + "            height: 50%"
                    + "            margin: 0 auto;"
                    + "            background-color: #fff;"
                    + "            padding: 20px;"
                    + "            border-radius: 5px;"
                    + "            box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);"
                    + "        }"
                    + "        .code.b {"
                    + "            color: darkseagreen;"
                    + "            font-size: 300%;"
                    + "            border-radius: 3px;"
                    + "        }"
                    + "    </style>"
                    + "</head>"
                    + "<body>"
                    + "    <div class='container'>"
                    + "        <h1>欢迎来到学生管理系统</h1>"
                    + "        <p class='code'>验证码<b>" + mailVerificationCode + "</b> </p>"
                    + "        <p>您的操作需要您提供接收到的验证码,验证码在5分钟内有效。</p>"
                    + "        <p>如果您没有进行过获取验证码的操作,请忽略这封邮件。</p>"
                    + "        <p>祝您生活愉快!</p>"
                    + "    </div>"
                    + "</body>"
                    + "</html>";

            mimeMessageHelper.setText(html, true);

            javaMailSender.send(mimeMessage);

        } catch (MessagingException e) {
            logger.error("Exeception=>sendHTMLEmail ", e);
        }
        // 存入Redis
        String redisIKey = "MailVerificationCode-" + email;
        try {
            iGlobalCache.set(redisIKey, mailVerificationCode, Const.MAIL_VERIFICATION_CODE_EXPIRE_TIME);
        } catch (Exception e) {
            System.out.println(e.getMessage());
            return CommonMethod.getReturnMessageError("Redis存储异常");
        }
        return CommonMethod.getReturnMessageOK();
    }

   
    public DataResponse register(DataRequest dataRequest) {
        String username = dataRequest.getString("username");
        String password = dataRequest.getString("password");
        String name = dataRequest.getString("name");
        String email = dataRequest.getString("email");
        String role = dataRequest.getString("role");
        String mailVerificationCode = dataRequest.getString("mailVerificationCode");
        UserType ut = null;
        Optional<User> uOp = userRepository.findByUserName(username);
        if (uOp.isPresent()) {
            return CommonMethod.getReturnMessageError("用户已经存在,不能注册!");
        }
        if (!email.contains("@")) return CommonMethod.getReturnMessageError("邮箱格式错误!");
        // 校验邮件验证码
        String redisIKey = "MailVerificationCode-" + email;
        String trueCode = (String) iGlobalCache.get(redisIKey);
        if (trueCode == null || !trueCode.equals(mailVerificationCode)) {
            return CommonMethod.getReturnMessageError("验证码有误");
        }
        Person p = new Person();
        p.setNum(username);
        p.setName(name);
        p.setEmail(email);
        if ("ADMIN".equals(role)) {
            p.setType("0");
            ut = userTypeRepository.findByName(EUserType.ROLE_ADMIN);
        } else if ("STUDENT".equals(role)) {
            p.setType("1");
            ut = userTypeRepository.findByName(EUserType.ROLE_STUDENT);
        } else if ("TEACHER".equals(role)) {
            p.setType("2");
            ut = userTypeRepository.findByName(EUserType.ROLE_TEACHER);
        }
        personRepository.saveAndFlush(p);
        User u = new User();
        u.setPerson(p);
        u.setUserType(ut);
        u.setUserName(username);
        u.setPassword(encoder.encode(password));
        u.setCreateTime(DateTimeTool.parseDateTime(new Date()));
        u.setCreatorId(p.getPersonId());
        u.setLoginCount(0);
        userRepository.saveAndFlush(u);
        if ("STUDENT".equals(role)) {
            Student s = new Student();   // 创建实体对象
            s.setPerson(p);
            studentRepository.saveAndFlush(s);  //插入新的Student记录
        } else if ("TEACHER".equals(role)) {
            Teacher t = new Teacher();   // 创建实体对象
            t.setPerson(p);
            teacherRepository.saveAndFlush(t);  //插入新的Teacher记录
        }
        return CommonMethod.getReturnMessageOK();
    }

    public DataResponse resetPassword(DataRequest dataRequest) {
        String username = dataRequest.getString("username");
        String email = dataRequest.getString("email");
        String newPassword = dataRequest.getString("newPassword");
        String mailVerificationCode = dataRequest.getString("mailVerificationCode");
        Optional<User> op = userRepository.findByUserName(username);
        if (!op.isPresent())
            return CommonMethod.getReturnMessageError("账户不存在!");  //通知前端操作正常
        User u = op.get();
        Person p = u.getPerson();
        email = p.getEmail();
        if (!email.equals(p.getEmail())) {
            return CommonMethod.getReturnMessageError("邮箱不匹配不能重置!");
        }
        // 校验邮件验证码
        String redisIKey = "MailVerificationCode-" + email;
        String trueCode = (String) iGlobalCache.get(redisIKey);
        if (trueCode == null || !trueCode.equals(mailVerificationCode)) {
            return CommonMethod.getReturnMessageError("验证码有误");
        }
        u.setPassword(encoder.encode(newPassword));
        userRepository.save(u);
        return CommonMethod.getReturnMessageOK();  //通知前端操作正常
    }

    public DataResponse changePassword(DataRequest dataRequest) {
        String oldPassword = dataRequest.getString("oldPassword");
        String newPassword = dataRequest.getString("newPassword");
        Integer userId = CommonMethod.getUserId();
        Optional<User> uOp = userRepository.findByUserId(userId);  // 查询获得 user对象
        if (!uOp.isPresent())
            return CommonMethod.getReturnMessageError("用户不存在!");
        User u = uOp.get();
        String username = u.getUserName();
        // 验证旧密码是否匹配
        Authentication authentication = authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(username, oldPassword));

        if (authentication.isAuthenticated()) {
            // 旧密码验证通过,可以修改密码
            u.setPassword(encoder.encode(newPassword));
            userRepository.save(u);
            return CommonMethod.getReturnMessageOK("Password changed successfully!");
        } else {
            return CommonMethod.getReturnMessageError("Old password is incorrect.");
        }
    }
}

EmailUtil

package org.fatmansoft.teach.util;

import java.util.Random;

public class EmailUtil {
    /**
     * 生成一定长度的随机字符串
     * @param length 目标生成长度
     * @return 随机字符串
     */
    public static String generateRandomString(int length) {
        String characters = Const.CHARACTER_SET;
        StringBuilder randomString = new StringBuilder();

        //随机组合
        Random random = new Random();
        for (int i = 0; i < length; i++) {
            int index = random.nextInt(characters.length());
            char randomChar = characters.charAt(index);
            randomString.append(randomChar);
        }

        return randomString.toString();
    }
}

Const

package org.fatmansoft.teach.util;

public class Const {
    /*
   邮箱验证码过期时间
    */
    public static final int MAIL_VERIFICATION_CODE_EXPIRE_TIME = 5 * 60;

    /*
    随机字符集
     */
    public static final String CHARACTER_SET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";

    public static String  COURSE_SELECT_AVAILABLE = "COURSE_SELECT_AVAILABLE";

}

图片验证码

图片验证码由4位随机数字组成,样例及代码如下image-20231231154813849

package org.fatmansoft.teach.util;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.util.*;

public class LoginControlUtil {
    /** 验证码图片的宽度 */
    private int width = 54;
    /** 验证码图片的高度 */
    private int height = 20;
    /** 验证码字符个数 */
    private int codeCount = 4;
    // 字符间距
    private int x = width / (codeCount + 1);
    // 字体高度
    private int fontHeight=height - 2;
    private int codeY=height - 4;

    private char[] codeSequence = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
    private Random random = new Random();

    private Map<Integer, String> codeMap = new HashMap<Integer, String>();
    private Map<String, Object[]> msgCodeMap = new HashMap<String, Object[]>();
    private Map<String, Integer> loginCountMap = new HashMap<String, Integer>();
    private static LoginControlUtil instance = new LoginControlUtil();
    public static LoginControlUtil getInstance(){
        return instance;
    }
    public Map getValidateCodeDataMap(){
        BufferedImage buffImg = new BufferedImage(width, height,
                BufferedImage.TYPE_INT_RGB);
        Graphics2D g = buffImg.createGraphics();

        // 创建一个随机数生成器类

        // 将图像填充为白色
        g.setColor(new Color(240, 240, 240));
        g.fillRect(0, 0, width, height);

        // 创建字体,字体的大小应该根据图片的高度来定。
        Font font = new Font("Arial", Font.BOLD, fontHeight); // 修改为你想要的字体和大小
        g.setFont(font);

         //画边框。
        g.setColor(Color.BLACK);
        g.drawRect(0, 0, width - 1, height - 1);

        // 随机产生160条干扰线,使图象中的认证码不易被其它程序探测到。
        g.setColor(Color.WHITE);
        for (int i = 0; i < 160; i++) {
            int x = random.nextInt(width);
            int y = random.nextInt(height);
            int xl = random.nextInt(12);
            int yl = random.nextInt(12);
            g.drawLine(x, y, x + xl, y + yl);
        }

        // randomCode用于保存随机产生的验证码,以便用户登录后进行验证。
        StringBuffer randomCode = new StringBuffer();
        int red = 0, green = 0, blue = 0;

        // 随机产生codeCount数字的验证码。
        for (int i = 0; i < codeCount; i++) {
            // 得到随机产生的验证码数字。
            String strRand = String.valueOf(codeSequence[random.nextInt(10)]);
//             产生随机的颜色分量来构造颜色值,这样输出的每位数字的颜色值都将不同。
            red = random.nextInt(255);
            green = random.nextInt(255);
            blue = random.nextInt(255);

            // 用随机产生的颜色将验证码绘制到图像中。
            g.setColor(new Color(red, green, blue));
//            g.setColor(new Color(147, 14,20));
            g.drawString(strRand, (i + 1) * x - 6, codeY);

            // 将产生的四个随机数组合在一起。
            randomCode.append(strRand);
        }
        Integer validateCodeId = random.nextInt();
        String  validateCode = randomCode.toString();
        codeMap.put(validateCodeId, validateCode);
        Map data = new HashMap();
        try {
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            ImageIO.write(buffImg, "jpeg", out);
            out.close();
            String imgStr = "data:image/png;base64,";
            String s = new String(Base64.getEncoder().encode(out.toByteArray()));
            imgStr = imgStr + s;
            data.put("validateCodeId", validateCodeId);
            data.put("img",imgStr);
        }catch(Exception e) {
            e.printStackTrace();
        }
        return data;
    }
    public String getValidateCode(Integer validateCodeId) {
        return codeMap.get(validateCodeId);
    }
    public Integer getLoginCount(String username){
        Integer count = loginCountMap.get(username);
        if(count == null)
            count = 0;
        return count;
    }
    public void setLoginCount(String username, Integer count){
        loginCountMap.put(username,count);
    }
    public void clearLoginCount(String username){
        loginCountMap.remove(username);
    }
    public void clearData(){
        codeMap.clear();
        msgCodeMap.clear();
        loginCountMap.clear();
    }
    public  String createMessageCode(String perNum){
        String randomCode="";
        for (int i = 0; i < codeCount; i++) {
            randomCode+= String.valueOf(codeSequence[random.nextInt(10)]);
        }
        Object []o = new Object[]{randomCode,new Date()};
        msgCodeMap.put(perNum,o);
        return randomCode;
    }
    public Object [] getMessageCodeObject(String perNum){
        return msgCodeMap.get(perNum);
    }
}

学科和课程的对应关系

为了更好地配合学生端和教师端的需求,我们在管理员端进行了创新,在基础的课程管理上增设了学科管理。这种学科管理与传统意义上的课程管理有所不同,它强调的是一门学科对应多门课程的关系。这样的设计理念是基于现实逻辑的考虑,以高等数学这门学科为例,同一门高等数学可以由多位老师授课,这样一门学科就对应了多门课程。

在管理端添加课程时,若学科不存在则需要先添加学科,充分体现了学科和课程的对应关系。

以下是学科的实体类:

package org.fatmansoft.teach.models.student;

import lombok.*;
import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(	name = "course",
        uniqueConstraints = {
        })
public class Course {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer courseId;

    @Size(max = 50)
    private String name;

    @NotBlank
    @Size(max = 20)
    private String num; //课序号

    private Integer hour; //总课时

    private Integer credit; //学分

    private Integer type; //必修 限选 任选

    @OneToOne
    @JoinColumn(name="capmus_id")
    private Campus campus; //学院

    @ManyToOne
    @JoinColumn(name = "grade_id")
    private Grade grade; //开课年级

    private String introduction; //课程介绍
}

以下是课程的实体类(老师和学科的绑定关系形成课程):

package org.fatmansoft.teach.models.teacher;

import lombok.*;
import org.fatmansoft.teach.models.student.Course;
import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@Table(	name = "teacher_course",
        uniqueConstraints = {
        })
public class TeacherCourse {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    @ManyToOne
    @JoinColumn(name = "teacher_id")
    private Teacher teacher;

    @ManyToOne
    @JoinColumn(name = "course_id")
    private Course course;

    private Integer selectedCount = 0; //选课人数

    private Integer courseCapacity; //课容量

    //上课时间
    private Integer day; //星期x   

    private Integer timeOrder; //第x节 

    private String place; //上课地点
}

开启和关闭选课

为了解决管理员端开启和关闭选课的复杂性,我们在后端设置了一个全局字段。这个字段的用途是标识选课的开启状态,通过简单地设置这个字段的值,我们能够快速地控制选课的开启和关闭。这种方式大大简化了操作流程,减轻了管理员的工作负担。

同时,学生端也通过判断这个全局字段的值来判断是否可以选课。当字段标识选课为开启时,学生可以正常选课;而当字段标识选课为关闭时,学生则无法进行选课操作。这种方式不仅简化了学生的操作流程,也避免了因时间设置错误而导致的问题。

通过这种方式,我们成功地实现了管理员端开启和关闭选课的需求,同时也满足了学生端的需求。这不仅提高了系统的稳定性和可靠性,也提升了用户体验。

以下是开启和关闭选课的逻辑代码:

/**
  * 开启选课(管理员)
  * @param dataRequest    string selectAvailable  1表示选课关闭 0表示开启
  * @return
  */
@PostMapping("/changeCourseSelectAvailable")
@PreAuthorize(" hasRole('ADMIN')")
public DataResponse changeCourseSelectAvailable(@Valid @RequestBody DataRequest dataRequest){
    String select_available = dataRequest.getString("selectAvailable");
    StaticValue available = staticValueRepository.findById(Const.COURSE_SELECT_AVAILABLE).orElse(null);
    if(select_available.equals("1") || select_available.equals("0")){
        available.setValue(select_available);
    }else{
        return CommonMethod.getReturnMessageError("获取参数值未知!");
    }
    staticValueRepository.save(available);
    return CommonMethod.getReturnData(available.getValue());
}

课表设计

image-20231231155059607

为了处理后端传来的数组并渲染出合适的课表,我们采用了嵌套的3个v-for循环和1个v-if判断。这样的处理方式确保了数据能够被正确地遍历和筛选,从而准确地渲染出每一节课的信息。

为了提升用户体验,我们特别设计了当鼠标悬浮在某节课上时,会显示这节课的具体信息。这一功能为用户提供了更多的交互性和便利性,使他们能够更轻松地获取课程详情。

此外,我们还精心设计了图例,通过不同的颜色来区分必修课、限选课和任选课。这样的设计符合用户的习惯和期望,使得课程类型的区分更为直观和清晰。颜色的使用不仅美化了界面,还提高了信息的可读性。

以下是展示课表的html代码:

<ul class="legend">
  <li>
    <span class="necessary"></span>
    必修
  </li>
  <li>
    <span class="limit"></span>
    限选
  </li>
  <li>
    <span class="random"></span>
    任选
  </li>
</ul>
<table class="schedule">
  <thead>
    <tr>
      <td>星期/节次</td>
      <td v-for="item in weeks" :key="item">
        {{ item }}
      </td>
    </tr>
  </thead>
  <tbody>
    <tr v-for="(order,orderIndex) in classOrders" :key="order">
      <td class="column0">
          {{ order }}
      </td>
      <td class="columnOther" v-for="(week,weekIndex) in weeks" :key="week">
        <template v-for="item in courseList" :key="item">
          <el-popover
              class="popover"
              placement="right-start"
              :width="200"
              trigger="hover"
          >
          <h3>{{ item.courseName }}</h3>
          <hr/>
          <p>学分:{{ item.credit }}</p>
          <p>学时:{{ item.hour }}</p>
          <p>地点:{{ item.place }}</p>
          <template #reference>
            <div 
                class="lesson"
                :class="{ 'necessary': item.type==0, 'limit': item.type==1, 'random': item.type==2 }" 
                v-if="item.day==(weekIndex+1)&&item.timeOrder==(orderIndex+1)"
            >
              <p class="cName">{{ item.courseName }}</p>
              <p class="tName">教师:{{ item.teacherName }}</p>
            </div>
            </template>
          </el-popover>
        </template>
      </td>
    </tr>
  </tbody>
</table>

班级管理

学生端手动修改班级、专业、年级等固定信息,容易导致分班混乱难以控制,班级和专业将沦为单纯的字符串;同时,管理员在学生管理界面无法便捷地为每一位学生确定班级和专业,多次输入容误差概率显著提升。为了避免这些现象,将班级真正地利用起来并实现统一的管理,我们增加了班级管理这一模块。

班级Model部分如下:

@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(	name = "clazz",
        uniqueConstraints = {
        })
public class Clazz {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer clazzId;

    private String clazzName;

    @ManyToOne
    @JoinColumn(name = "grade_id")
    private Grade grade;

    @OneToOne
    @JoinColumn(name="capmus_id")
    private Campus campus; //学院
}

前端展示时,页面完成挂载后调用getClazzOptionItemListByGradeId获取班级数据,并呈现在表格中

image-20231230011237361

接口代码如下:

 public DataResponse getClazzOptionItemListByGradeId(DataRequest dataRequest) {
       Integer gradeId=dataRequest.getInteger("gradeId");
       if(gradeId == null){
           List<Clazz> sList = clazzRepository.findAll();
           return CommonMethod.getReturnData(getClazzMapList(sList));
       }
       List<Clazz> sList = clazzRepository.findClazzListByGradeGradeId(gradeId);
       return CommonMethod.getReturnData(getClazzMapList(sList));
  }

前端请求部分如下:

onMounted(async ()=> {
    updateTableData()
})

const updateTableData = async () => {
  const res = await request.post('/clazz/getClazzOptionItemListByGradeId',{
    data:{
      gradeId:'' //获取所有班级
    }
  })
  tableData.value = res.data.data
  filterTableData.value = res.data.data
}

新增班级时,通过下拉框选择班级所属学院和年级,极大降低了手动输入带来的误差,调用clazzEditSave接口完成新增操作。

image-20231230011836910

接口代码如下:

public DataResponse clazzEditSave(DataRequest dataRequest) {
        Integer clazzId = dataRequest.getInteger("clazzId");  //获取clazz_id值
        Map clazz = dataRequest.getMap("clazz");
        String clazzName = CommonMethod.getString(clazz,"clazzName");
        Integer gradeId = CommonMethod.getInteger(clazz,"gradeId");
        Integer campusId = CommonMethod.getInteger(clazz,"campusId");
        Clazz a = null;
        Optional<Clazz> op;
        if(clazzId != null) {
            op= clazzRepository.findByClazzId(clazzId);  //查询对应数据库中主键为id的值的实体对象
            if(op.isPresent()) {
                a = op.get();
            }
        }
        if(a == null){
            clazzId = getNewClazzId(); //获取Clazz新的主键
            a = new Clazz();
            a.setClazzId(clazzId);
        }
        Optional<Grade> og = gradeRepository.findByGradeId(gradeId);
        Grade grade;
        if(og.isPresent()){
            grade = og.get();
        }else{
            return CommonMethod.getReturnMessageError("年级不存在!");
        }
        a.setGrade(grade);
        a.setClazzName(clazzName);
        Optional<Campus> oc = campusRepository.findByCampusId(campusId);
        Campus campus;
        if(oc.isPresent()){
            campus = oc.get();
        }else{
            return CommonMethod.getReturnMessageError("学院不存在!");
        }
        a.setCampus(campus);
        clazzRepository.saveAndFlush(a);//插入新的Clazz记录
        return CommonMethod.getReturnData(a.getClazzId(),"修改或新增成功");  // 将ClazzId返回前端
    }

点击查看未分配班级同学按钮,即可查看暂未分班的学生(数据库中clazz_id为空),调用getNoClazzStudents接口获取学生名单,同时调用getClazzOptionItemListByGradeId接口获取现有班级列表

image-20231230012721873

为避免出现同班级名称不同年级的情况,在下拉框中将label调整为gradeNameclazzName的组合

image-20231230012939242

 <div class="dialogContent">
        <div class="clazzName">
          <p>选择班级</p>
          <el-select v-model="clazzId" value-key="id"
          placeholder="请选择班级" clearable @change="changeSelect">
              <el-option
              v-for="item in clazzData"
                :key="item.clazzId"
                :label="item.gradeName+' '+item.clazzName"
                :value="item.clazzId"
              />    
        </el-select>
       </div>
</div>

点击班级管理首页的班级名称,即可跳转至班级学生列表:

image-20231230013252572

同样,在页面挂载结束后,像后端发送请求,调用接口getStudentOptionItemListByClazzId,通过store实现不同页面间的数据传递。

删除班级时,调用clazzDelete接口,将该班级中的所有学生clazzId清空,即恢复到无班级状态

public DataResponse clazzDelete(DataRequest dataRequest) {
        Integer clazzId = dataRequest.getInteger("clazzId");  //获取clazz_id值
        Clazz a = null;
        Optional<Clazz> op;
        if(clazzId != null) {
            op= clazzRepository.findByClazzId(clazzId);  //查询对应数据库中主键为id的值的实体对象
            if(op.isPresent()) {
                a = op.get();
            }else{
                return CommonMethod.getReturnMessageError("班级不存在!");
            }
        }
        if(a != null) {
            List<Student> studentList = studentRepository.findStudentListByClazzClazzId(clazzId);
            for(Student s: studentList){
                s.setClazz(null);
            }
            clazzRepository.delete(a);//删除该班级
        }
        return CommonMethod.getReturnMessageOK("删除成功");  //通知前端操作正常
    }

通过班级管理这一功能,将学生与年级、学院有效对应,同时为后续的互评功能奠定基础,使项目逻辑更加严密。

echarts图表

为了直观展现用户动态和进行行为分析,兼顾学生主页的丰富性和美观需求,特引入echarts库进行统计图表的绘制。

  1. 配置与导入:

    通过npm获取echarts,这里获取特定版本

    npm install [email protected] --save
    

    在main.js中增加设置

    //echart
    import * as echarts from 'echarts';
    app.config.globalProperties.$echarts = echarts;
  2. Homepage.vue中使用echarts

    学生主页共有两个图表,一个是GPA占比的饼状图,另一个为成绩等级数量的玫瑰图,下面是这两张图表的js部分:

    image-20231230021119048

    //饼状图
    const userInfo = ref({
        //其他属性
        gpa:'',
    })
    const dom1 = document.getElementById('myChart');
        const myChart = echarts.init(dom1); // 初始化echarts实例
        const option1 = {
          series: [{
              color:["#6FB6C1","#e6e6e6"],
              type: 'pie',
              data: [
                  {
                  value: userInfo.value.gpa,
                  name: 'GPA:'+ userInfo.value.gpa
                  },
                  {
                  value: 5-userInfo.value.gpa,
                    name: '满分5'
                    },
                ],
                radius: '50%'
            }]
        };
      // 设置实例参数
      myChart.setOption(option1);

    image-20231230021244157

    //玫瑰图 
    const dom2 = document.getElementById('scoreChart');
        const scoreChart = echarts.init(dom2);
        const option2 = {
            series: [{
            color:["#6FB6C1","#a094c3","#ccdead","#dbc4a7","#c16f6f"],
            type: 'pie',
            data: markList.value,
            roseType: 'area'
            }
        ]};
        // 设置实例参数
        scoreChart.setOption(option2);

    由于后端所传参数markList格式与绘图所需data一致,此处便直接将其值赋给data

    markList=[
        {name: '优:2', value: 2},
        {name: '良:4', value: 4},
        {name: '中:3', value: 3},
        {name: '及格:1', value: 1},
        {name: '不及格:0', value: 0}
    ]
    

无孔不入的Markdown

为了实现富文本编辑,丰富可展示信息,以及美化界面,决定引入基于Vue开发的轻量级Markdown编辑器组件v-md-editor,下面是详细介绍。

  1. 导入和配置

    通过npm下载

    # use npm
    npm i @kangc/v-md-editor -S
    

    若出现报错依赖冲突或Uncaught TypeError: Cannot read properties of undefined (reading 'config')等,在命令行运行如下命令即可

    npm i @kangc/v-md-editor@next -S --legacy-peer-deps
    

    在vue中引入轻量版编辑器和预览组件

    // If you want to use Md-editor, import it.
    import VMdEditor from '@kangc/v-md-editor';
    import '@kangc/v-md-editor/lib/style/base-editor.css';
    import githubTheme from '@kangc/v-md-editor/lib/theme/github.js';
    import '@kangc/v-md-editor/lib/theme/style/github.css';
    
    // highlightjs
    import hljs from 'highlight.js';
    
    VMdEditor.use(githubTheme, {
      Hljs: hljs,
    });
    
    //preview
    import VMdPreview from '@kangc/v-md-editor/lib/preview';
    import '@kangc/v-md-editor/lib/style/preview.css';
    
    VMdPreview.use(githubTheme, {
      Hljs: hljs,
    });
    
    const app = createApp(/*...*/);
    
    app.use(VMdEditor);
    app.use(VMdPreview);
  2. 编辑器组件的使用

    image-20231230023756472

    <template>
      <v-md-editor v-model="markdown" height="700px" 
                placeholder="# 记录创作灵感 & 美好生活 #"></v-md-editor>
    </template>
    <scrpt>
    const markdown = ref('')
    </scrpt>
  3. 预览组件的使用

    image-20231230024209508

    <template>
    	<v-md-preview :text="text" class="content"></v-md-preview>
    </template>
    <script>
    	const text = blogInfo.value.content
    </script>

查询功能的实现(filter方法)

为了能精准快速地从数据中筛选出符合条件的内容,我们在多数页面都设置了查询功能,由于页面需求的多样性,我们查询的内容和形式也不尽相同,倘若在后端实现查询方法,前端调用接口进行筛选,可能会增加服务器负载,导致相应时间增加。同时出于安全性的考量,采用混合策略,部分筛选在前端执行以提供更快的用户体验,同时后端仍然进行额外的验证和安全性检查。

下述filter过滤方法的应用流程:

以博客部分为例

image-20231230143126714

前端将后端返回的博客数据存在blogs数组中,在搜索框内输入文章标题、作者、文章内容等,点击查询,即可对博客进行筛选。

const blogs = ref([])

const search = ref('')
const inputSearch = ref('')
const searchedBlogs = computed(() => blogs.value.filter(
  item =>   //筛选条件,进行模糊查询
    !search.value ||
    item.BlogTitle.toLowerCase().includes(search.value.toLowerCase()) ||
    item.name.toLowerCase().includes(search.value.toLowerCase()) ||
    item.content.toLowerCase().includes(search.value.toLowerCase())
))
const searchFn = () => {
  search.value = inputSearch.value   //修改search的内容
}

template标签下仅需将v-for循环的数组绑定为searchedBlogs即可。

<div class="blog" v-for="blog in searchedBlogs" :key="blog.BlogId">
       <div class="content" @click="handleClickBlog(blog)">
           <h2 class="title">{{ blog.BlogTitle }}</h2>
           <p class="date">发布于 {{blog.createTime}} | {{ blog.name }}</p>
           <p class="summary">{{ ellipsis(blog.digest,blog.content) }}</p>
       </div>
</div>

数据分页(pagination)

当数据量过多时,使用分页分解数据。由于多数页面同时伴随着查询功能,所以我们将查询后的数据再进行分页。

// 分页相关
const currentPage = ref(1)
const paginatedBlogs = computed(() => searchedBlogs.value.slice((currentPage.value - 1) * 15, currentPage.value * 15))
const handleSizeChange = () => {
  currentPage.value = 1
}

template标签下仅需将v-for循环的数组绑定为paginatedBlogs即可。

 <div class="blog" v-for="blog in paginatedBlogs" :key="blog.BlogId">
            <div class="content" @click="handleClickBlog(blog)">
                <h2 class="title">{{ blog.BlogTitle }}</h2>
                <p class="date">发布于 {{blog.createTime}} | {{ blog.name }}</p>
                <p class="summary">{{ ellipsis(blog.digest,blog.content) }}</p>
           </div>
 </div>
<!-- 对组件进行配置 -->
 <el-row class="pagination">
        <el-col>
            <el-pagination
            background
            :hide-on-single-page="false"
            v-model:current-page="currentPage"
            default-page-size = "15"
            :pager-count="7"
            layout="total, prev, pager, next, jumper"
            :total="searchedBlogs.length"
            @size-change="handleSizeChange"
            />
        </el-col>
 </el-row>

整体页面框架

我们的整体页面布局采用了固定页头和内容的布局方式。这种布局能够确保页面的整体结构保持一致,使用户在浏览内容时能够快速定位。

在内容部分,我们特别采用了侧边栏和主体的布局。侧边栏不仅为用户提供了导航菜单,还为页面提供了必要的结构。主体部分则分为标签页和内容页,用户可以通过标签页快速切换不同的内容,提高了浏览效率。

为了确保页面的兼容性和稳定性,我们采用了element-ui提供的布局容器。element-ui是一个成熟的UI组件库,其提供的布局容器经过了大量实践的验证,能够满足各种复杂场景的需求。

为了配合我们设计的系统主题,我们对element-ui组件库的默认主题色进行了修改。这种修改确保了我们的页面风格与系统主题保持一致,增强了页面的整体视觉效果。

此外,我们还重写了scss文件,以便于全局修改element-ui组件的样式。通过这种方式,我们能够根据实际需求定制化地调整组件的外观,使页面效果更加符合用户期望,从而达到不落窠臼的效果。

以下是修改样式的scss文件:

@forward '../../../node_modules/element-plus/theme-chalk/src/common/var.scss' with (
  $colors: (
    'primary': (
      'base': #6FB6C1,
    ),
  ),
  $main: (
    'padding': 0px,
  )
);
body{
    background-color: rgb(245, 245, 245);
}
.el-tabs{
    position: absolute;
    top: 0px;
    bottom: 0px;
    width: 100%;
    .el-tabs__header {
        margin: 0px;
        .el-tabs__nav{
            width: fit-content;
            .el-tabs__item{
                background-color: #f0f0f0; 
                color: #929292;  
                height: 35px;
            }
            .is-active{
                background-color: #e5f1f3;   
                color: #498f9a;
            }
        }
    }
    .el-tabs__content {
        position: absolute;
        top: 35px;
        bottom: 0px;
        width: 100%;
        background-color: white;
        overflow: auto;
        padding: 10px;
    }
}
.main {
    .el-tabs--card > .el-tabs__header{
        height: 35px;
    }
    .el-table__header thead{
        th{
            background-color: #D3E9EC;
        }
    }
}

store 传参(以班级管理为例)

点击班级管理首页班级列表中的名称,即可跳转至对应班级的学生名单页面

ClazzManage.vue

image-20231230011237361

classmate.vue

image-20231230013252572

具体实现方法如下:

// @/stores/app.ts
export const useAppStore = defineStore('app', () => {
  /* 其他数据 */
  const classmate = ref({
    clazzId:"",
    clazzName:""
  })
  return {
    classmate
  }
})
<!--  @/views/admin/ClazzManage.vue -->
<template>
   <!-- -->
    <el-table-column prop="clazzName" label="班级名称" width="auto" align="center">
              <template #default="scope">
                <a href="javascript:" @click="handleClickName(scope)">{{ scope.row.clazzName }}</a>
              </template>
    </el-table-column>
	<!-- -->
</template>
<script>
    import { useAppStore } from '../../stores/app.ts'
    import router from "~/router";
    const store = useAppStore()
	const handleClickName = (clazz) => {
      console.log(clazz.row.clazzName)
      store.classmate = clazz.row
      console.log(store.classmate)
      router.push('classmate')
    }
    
</script>
<!--  @/views/admin/classmate.vue -->
<script>
import { useAppStore } from '../../stores/app.ts'
import { storeToRefs } from 'pinia'

const store = useAppStore()

const { classmate } = storeToRefs(store);
const className = classmate.value.clazzName
const tableData = ref([])
const updateTableData = async () => {
  console.log(classmate.value.clazzName)
  const res = await request.post('/student/getStudentOptionItemListByClazzId',{
    data:{
        clazzId:classmate.value.clazzId
    }
  })
  tableData.value = res.data.data
}
</script>