基于Springboot+Vue3+Mybatis学生成绩管理系统
基于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/
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
}