基于Springboot+Vue3的学生成绩管理系统

👇🏻 精选专栏推荐收藏订阅👇🏻
🎀Java项目精选实战案例《600套+》😘
https://blog.csdn.net/rucoding/category_12319634.html

1、开发环境

类型

内容

开发语言

Java

框架

Springboot

前端

Vue3 + Element-plus

JDK版本

Jdk1.8

数据库

Mysql5.7

数据库工具

Navicat16

数据库框架

Mybatis

开发软件

IDEA

Maven包

Maven3.6.1

浏览器

Edge

2、功能介绍

3、脚手架介绍

Vue

  • Vue3基础脚手架

  • request http请求封装

  • 支持换主题色

  • 页面标题+icon

  • 页面主体框架(菜单+路由+主页)

SpringBoot

  • SpringBoot基础脚手架

  • 内置基础查询的Hello接口

  • 配置Mybatis

  • 集成hutool工具类

  • 统一接口返回类Result

  • 统一跨域处理

  • 统一异常处理

学习网站

Spring Initial 脚手架:https://start.spring.io/

Vue: https://cn.vuejs.org/

Element-Plus: https://element-plus.org/

国内网站:https://element-plus.gitee.io/

hutool官网:https://doc.hutool.cn/pages/index/

Vue启动

npm install
npm run dev

4、部分代码展示

登录功能前端代码

<template>
  <div>
    <div class="login-container">
     <div style="width: 350px" class="login-box">
       <div style="font-weight: bold;font-size: 24px;text-align: center;margin-bottom: 30px;">登 录</div>
       <el-form :model="data.form" ref="ruleFormRef" :rules="rules">
         <el-form-item prop="username">
           <el-input prefix-icon="User" v-model="data.form.username" placeholder="请输入账号"/>
         </el-form-item>
         <el-form-item prop="password">
           <el-input show-password prefix-icon="Lock" v-model="data.form.password" placeholder="请输入密码"/>
         </el-form-item>
         <el-form-item>
           <label style="margin-right: 15px;">角色选择:</label>
           <div class="mb-2 flex items-center text-sm">
             <el-radio-group v-model="data.form.role" class="ml-4">
               <el-radio label="1" size="large">管理员</el-radio>
               <el-radio label="0" size="large">学生</el-radio>
             </el-radio-group>
           </div>
         </el-form-item>
         <el-form-item>
           <el-button type="primary" style="width: 100%;" @click="login">登 录</el-button>
         </el-form-item>
       </el-form>
       <div style="margin-top: 30px;text-align: right">
         还没有账号?请<a href="/register">注册</a>
       </div>
     </div>
    </div>
  </div>
</template>

<!--vue3语法糖-->
<script setup>
import {reactive,ref} from "vue";
import request from "@/utils/request";
import {ElMessage} from "element-plus"; //导入{ElMessage}、ElMessage区别
import router from "@/router";

const data = reactive({
    form: {role: '0'}
})

//表单校验
const rules = reactive({
  username: [
    { required: true, message: '请输入账号', trigger: 'blur' }
  ],
  password: [
    { required: true, message: '请输入密码', trigger: 'blur' }
  ]
})

const ruleFormRef = ref()

const login = () => {
  ruleFormRef.value.validate((valid) => {
    if (valid) {
      //表单校验通过,请求登录接口
      // console.log(valid)
      request.post('/login',data.form).then(res => {
        if(res.code === '200'){
          localStorage.setItem("student-user",JSON.stringify(res.data))
          ElMessage.success("登录成功")
          // location.href = '/home' //登录成功,跳转主页
          router.push('/home') //路由跳转
        }else{
          ElMessage.error(res.msg)
        }
      })
    } else {
      console.log('error submit!')
    }
  })
}

</script>

<style scoped>
.login-container {
  min-height: 100vh;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  background-image: url("@/assets/imgs/bg3.png");
  background-size: cover;
}

.login-box {
  /*background-color: rgba(255,255,255,.5);*/
  background-color: #fff;
  box-shadow: 0 0 10px rgba(0,0,0,0.1);
  padding: 30px;
  border-radius: 10px;
}
</style>

登录功能后端代码

package com.example.controller;

import com.example.common.Result;
import com.example.common.RoleEnum;
import com.example.entity.Account;
import com.example.entity.Admin;
import com.example.exception.CustomException;
import com.example.service.AdminService;
import com.example.service.StudentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.Optional;

@RestController
public class WebController {

    @Resource
    AdminService adminService;

    @Resource
    StudentService studentService;

    /**
     * 默认请求接口
     */
    @GetMapping("/")
    public Result hello() {
        return Result.success();
    }


    /**
     * 登录接口
     * @param account
     * @return
     */
    @PostMapping("/login")
    public Result login(@RequestBody Account account) {
        Account dbAccount;
        String role = Optional.ofNullable(account)
                .map(acc -> acc.getRole() == 1 ? "ADMIN" : acc.getRole() == 0 ? "STUDENT" : "UNDEFINED")
                .orElse("UNDEFINED");
        if(RoleEnum.ADMIN.name().equals(role)){ //管理员
            dbAccount = adminService.login(account);
        }else if(RoleEnum.STUDENT.name().equals(role)){ //学生
            dbAccount = studentService.login(account);
        }else {
            return Result.error("角色错误");
        }
        return Result.success(dbAccount);
    }

    /**
     * 注册接口
     * @param account
     * @return
     */
    @PostMapping("/register")
    public Result register(@RequestBody Account account) {
        studentService.register(account);
        return Result.success();
    }

}

文件上传下载代码

package com.example.controller;

import cn.hutool.core.io.FileUtil;
import com.example.common.Result;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.net.URLEncoder;

@RestController
@RequestMapping("/files")
public class FilesController {

    // System.getProperty("user.dir"): Java 运行环境的工作目录(即用户当前所在的目录)
    private static final String ROOT_PATH  = System.getProperty("user.dir") + "/files";

    @Value("${server.port}")
    String port;

    @Value("${accessIp}")
    String accessIp;


    /**
     * 上传文件
     * @param file
     * @return
     * @throws IOException
     */
    @PostMapping("/upload")
    public Result upload(MultipartFile file) throws IOException {
        //获取上传的源文件名称
        String originalFilename = file.getOriginalFilename();
        //随机重命名文件名,避免重复
        long preFlag = System.currentTimeMillis();
        String fileName = preFlag + "_" + originalFilename;
        File finalFile = new File(ROOT_PATH + "/" + fileName); //最终存到磁盘的文件对象
        if(!finalFile.getParentFile().exists()){ //判断父级目录是否存在,不存在则创建
            finalFile.getParentFile().mkdirs();
        }
        //将 MultipartFile 对象表示的file文件内容写入到指定的 finalFile 对象中(即目标文件)
        file.transferTo(finalFile);
        //返回url
        String url = "http://" + accessIp +":" + port +"/files/download?fileName=" + fileName;
        return Result.success(url);
    }

    /**
     * 下载文件
     * @param fileName
     * @param response
     * @throws IOException
     */
    @GetMapping("/download")
    public void download(String fileName, HttpServletResponse response) throws IOException {
        File file = new File(ROOT_PATH + "/" + fileName); //文件在存盘存储的对象
        ServletOutputStream os = response.getOutputStream();
        //设置 HTTP 响应头
        /*
         * Content-Disposition 是 HTTP 响应头的一部分,用于指示浏览器如何显示附加的文件。
         * attachment 表示将文件作为附件下载。
         * filename 指定下载文件的名称。URLEncoder.encode(fileName, "UTF-8") 用于对文件名进行 UTF-8 编码,以确保文件名中的特殊字符不会破坏 HTTP 头的格式。
         */
        response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
        /*
         * Content-Type 是 HTTP 响应头的一部分,指示浏览器处理响应体的数据类型。
         * application/octet-stream 是一种通用的二进制流数据类型。它告诉浏览器不要尝试解析响应体数据,而是按照二进制流进行处理。
         */
        response.setContentType("application/octet-stream");
//        os.write(FileUtil.readBytes(file));
        FileUtil.writeToStream(file,os);
        os.flush();
        os.close();
    }

}

5、系统部分截图

登录界面截图

课程管理界面截图

学生成绩界面截图

学生管理界面截图

学生个人资料截图

6、开发过程问题汇总

①、vue 判断

== 用于比较两者是否相等,忽略数据类型。

=== 用于更严谨的比较,值和值的数据类型都需要同时比较。

②、el-radio 反显默认值不显示

<el-form-item label="性别" prop="sex"
  <el-radio-group v-model="data.stuForm.sex" class="ml-4"
    <el-radio label="1" size="large"男</el-radio>
    <el-radio label="0" size="large"女</el-radio>
  </el-radio-group>
</el-form-item>

参考解决:在label前加了冒号

③、vue使用Element-plus的Image预览时样式崩乱

参考解决:增加preview-teleported="true"属性。

<el-image v-if="scope.row.avatar"
preview-teleported="true"
:src="scope.row.avatar"
:preview-src-list="[scope.row.avatar]"
style="width: 40px; height: 40px; border-radius: 5px">
</el-image>

④、Invalid bound statement

org.apache.ibatis.binding.BindingException:
Invalid bound statement (not found): 
com.example.mapper.StudentCourseMapper.selectStuCourseList

解决方式:配置yml文件

  mapper-locations: classpath:mapper/*.xml

⑤、 Column 'name' in where clause is ambiguous

Cause: java.sql.SQLIntegrityConstraintViolationException: 
Column 'name' in where clause is ambiguous

⑥、Request method 'PUT' not supported

类型

操作

示例

查询接口

@GetMapping

@GetMapping("/selectPage")

新增接口

@PostMapping

@PostMapping("/add")

更新接口

@PutMapping

@PutMapping("/update")

删除接口

@DeleteMapping

@DeleteMapping("/delete/{id}")

示例代码:

package com.example.controller;

import com.example.common.Result;
import com.example.entity.Grade;
import com.example.service.GradeService;
import com.github.pagehelper.PageInfo;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;

@RestController
@RequestMapping("/grade")
public class GradeController {

    @Resource
    GradeService gradeService;


    /**
     * 分页条件查询课程
     */
    @GetMapping("/selectPage")
    public Result selectPage(@RequestParam(defaultValue = "1") Integer pageNum,
                             @RequestParam(defaultValue = "10") Integer pageSize,
                             Grade grade) {
        PageInfo<Grade> pageInfo = gradeService.selectPage(pageNum, pageSize, grade);
        return Result.success(pageInfo);
    }

    /**
     * 打分
     * @param grade
     * @return
     */
    @PostMapping("/add")
    public Result add(@RequestBody Grade grade){
        gradeService.add(grade);
        return Result.success();
    }

    /**
     * 更新
     * @param grade
     * @return
     */
    @PutMapping("/update")
    public Result update(@RequestBody Grade grade){
        gradeService.update(grade);
        return Result.success();
    }

    /**
     * 删除成绩
     */
    @DeleteMapping("/delete/{id}")
    public Result delete(@PathVariable Integer id) {
        gradeService.deleteById(id);
        return Result.success();
    }

}

⑦、编辑操作赋值

注意data.courseForm = row,共用相同内存地址,意味着修改中,表格数据跟着修改变化。

//编辑操作
const handleEdit = (row) => {
  //清空表单校验规则
  const formRef = ruleCourseFormRef.value;
  if (formRef) {
    formRef.clearValidate();
  }
  // data.courseForm = row //获取数据设置到表单里   这种赋值方式引用相同的内存地址
  data.courseForm = JSON.parse(JSON.stringify(row)) //获取数据设置到表单里
  data.dialogFormVisible = true
}