目录
一、SpringBoot3+Vue3实现基本增删改查。前后端通信交互、配置后端跨域请求。数据批量删除。(博客链接)
二、SpringBoot3+Vue3快速开发登录、注册页面并实现对接。
<3>对话框---新增---某些字段设为必填项。(表单校验)
针对username设置校验规则。(required、message、trigger)
关闭对话框销毁旧对话框对象。(属性destroy-on-close)
表单项(username、password)校验。(属性ref、:rules)
登录绑定login函数。(若校验成功--->post请求后端接口"/login")
全局异常捕获处理器。(GlobalExceptionHandler)
前端处理请求(request)和响应(response)的工具类。
存储用户信息。(localStorage.setItem(xxx))
一、SpringBoot3+Vue3实现基本增删改查。前后端通信交互、配置后端跨域请求。数据批量删除。(博客链接)
- 注意:关于本篇博客的案例《员工信息的前端页面代码、后端接口(controller)、service、mapper层代码》基本都在上面那篇博客中已实现。需要的可以自取。
- 这篇博客的主要内容:学习《使用Vue3快速开发登录、注册页面并对接后台》。前端Vue页面与后端SpringBoot服务器进行对接。实现员工登录或注册进入系统的功能。
二、SpringBoot3+Vue3快速开发登录、注册页面并实现对接。
(1)操作数据表employee(员工信息表)。
<1>修改employee表的字段组成。
- employee表字段原组成。
- 添加3个字段:用户名(username)、密码(password)、角色(role)。
<2>填补对应数据库数据。
(2)修改Employee实体类。
<1>新增对应成员属性。
javapackage com.hyl.entity; public class Employee { private Integer id; private String username; private String password; private String role; private String name; private String sex; private String no; private Integer age; private String description; //数据库字段:department_id (下划线命名) //实体类属性:departmentId (驼峰命名) private Integer departmentId; }
<2>新增对应getter、setter方法。
javapublic String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getRole() { return role; } public void setRole(String role) { this.role = role; }
<3>修改之前的toString()方法。
java//toString方法 @Override public String toString() { return "Employee{" + "id=" + id + ", username='" + username + '\'' + ", password='" + password + '\'' + ", role='" + role + '\'' + ", name='" + name + '\'' + ", sex='" + sex + '\'' + ", no='" + no + '\'' + ", age=" + age + ", description='" + description + '\'' + ", departmentId=" + departmentId + '}'; }
(3)修改mapper层代码。
<1>修改新增SQL。
XML<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.hyl.mapper.EmployeeMapper"> <insert id="insert" parameterType="com.hyl.entity.Employee"> insert into `employee`(username,password,role,name,sex,no,age,description,department_id) values (#{username},#{password},#{role},#{name},#{sex},#{no},#{age},#{description},#{departmentId}) </insert> </mapper>
<2>修改更新SQL。
XML<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.hyl.mapper.EmployeeMapper"> <update id="updateById" parameterType="com.hyl.entity.Employee"> update `employee` set username = #{username},password = #{password},role = #{role}, name = #{name},sex=#{sex},no=#{no},age=#{age}, description=#{description},department_id=#{departmentId} where id = #{id} </update> </mapper>
<3>EmployeeMapper接口代码示例。
javapackage com.hyl.mapper; import com.hyl.entity.Employee; import org.apache.ibatis.annotations.Delete; import org.apache.ibatis.annotations.Select; import java.util.List; public interface EmployeeMapper { //通过注解形式完成SQL语句的查询 //简单SQL可以直接使用注解形式。动态SQL最好使用XML文件形式 @Select("select * from `employee` where id = #{id}") Employee selectById(Integer id); List<Employee> selectAll(Employee employee); void insert(Employee employee); void updateById(Employee employee); @Delete("delete from `employee` where id = #{id}") void deleteById(Integer id); }
(4)员工信息(Employee.vue)页面。
<1>前端代码示例。
html<el-table-column label="用户名" prop="username"/>
html<el-table-column label="角色" prop="role"> <template #default="scope"> <span v-if="scope.row.role === 'EMP'">员工</span> </template> </el-table-column>
<2>员工信息的页面渲染效果。
- 一般员工信息页面只会显示用户名、角色。而密码将不会直接显示。
<3>对话框---新增---某些字段设为必填项。(表单校验)
html<el-form-item label="用户名"> <el-input v-model="data.form.name" autocomplete="off" placeholder="请输入用户名"/> </el-form-item>
表单标签(el-form)添加ref。
html<el-form ref="formRef" :model="data.form" label-width="80px" style="padding-right: 50px;padding-top: 20px">
javascriptimport {reactive,ref} from "vue"; const formRef = ref();
保存按钮绑定事件函数save中触发校验。
javascript//对话框的保存按钮事件 const save = () =>{ formRef.value.validate((valid)=>{ //通过校验才能点击更新或新增的保存操作 if(valid){ //有id进行更新操作。无id进行新增操作 data.form.id ? update() : add() } }) }
表单标签(el-form)绑定校验规则。(:rules)
html<el-form ref="formRef" :rules="data.rules" :model="data.form" label-width="80px" style="padding-right: 50px;padding-top: 20px">
针对username设置校验规则。(required、message、trigger)
- 设置了required为true表示该字段必填。message是当未满足必填条件时显示的提示信息 "请输入用户名" 。trigger为blur表示在输入框失去焦点时触发验证。
javascriptimport {reactive,ref} from "vue"; const formRef = ref(); const data = reactive({ rules:{ username:[ { required:true , message: '请输入用户名' , trigger:'blur'} ] } })
在对话框的对应需校验表单项添加prop属性。
html<el-form-item label="用户名" prop="username"> <el-input v-model="data.form.username" autocomplete="off" placeholder="请输入用户名"/> </el-form-item>
给名称(name)、工号(no)也绑定校验"必填项"。
html<el-form-item label="名称" prop="name"> <el-input v-model="data.form.name" autocomplete="off" placeholder="请输入名称"/> </el-form-item> <el-form-item label="工号" prop="no"> <el-input v-model="data.form.no" autocomplete="off" placeholder="请输入工号"/> </el-form-item>
javascriptimport {reactive,ref} from "vue"; const data = reactive({ rules:{ username:[ { required:true , message: '请输入用户名' , trigger:'blur'} ], name:[ { required:true , message: '请输入名称' , trigger:'blur'} ], no:[ { required:true , message: '请输入工号' , trigger:'blur'} ] } })
- 页面渲染效果。
关闭对话框销毁旧对话框对象。(属性destroy-on-close)
html<el-dialog title="员工信息" v-model="data.formVisible" width="500" destroy-on-close> <el-form ref="formRef" :rules="data.rules" :model="data.form" label-width="80px" style="padding-right: 50px;padding-top: 20px"> <el-form-item label="用户名" prop="username"> <el-input v-model="data.form.username" autocomplete="off" placeholder="请输入用户名"/> </el-form-item> <el-form-item label="名称" prop="name"> <el-input v-model="data.form.name" autocomplete="off" placeholder="请输入名称"/> </el-form-item> <el-form-item label="性别"> <el-radio-group v-model="data.form.sex"> <el-radio value="男">男</el-radio> <el-radio value="女">女</el-radio> </el-radio-group> </el-form-item> <el-form-item label="工号" prop="no"> <el-input v-model="data.form.no" autocomplete="off" placeholder="请输入工号"/> </el-form-item> <!-- 设置最小年龄18 最大年龄100 --> <el-form-item label="年龄"> <el-input-number v-model="data.form.age" :min="18" :max="100" style="width: 250px" autocomplete="off" placeholder="年龄>=18与年龄<=100"/> </el-form-item> <!-- 设置类型:文本域 :rows设置默认显示三行 --> <el-form-item label="个人简介"> <el-input type="textarea" :rows="3" v-model="data.form.description" autocomplete="off" placeholder="请输入个人简介"/> </el-form-item> </el-form> <template #footer> <div class="dialog-footer"> <el-button @click="data.formVisible = false">取消</el-button> <el-button type="primary" @click="save"> 保存 </el-button> </div> </template> </el-dialog>
- 这样点击"取消"关闭某次对话框时,下次新对话框就不会存留着上次的校验失败提示。
(5)新建登录页面。(Login.vue)
<1>登录页面基本框架。
配置路由。(不是嵌套子路由)
导航栏---退出登录。(index)
<2>登录页面代码示例。
html<template> <div> <div class="login-container"> <!-- 登录表单容器 --> <div class="login-box"> <div style="padding: 40px 30px;background-color: #fbfcf8;margin-left: 200px;border-radius: 5px"> <div style="margin-bottom: 30px;font-weight: bold;font-size: 24px;color: #514343;text-align: center" > 欢迎登录hyl信息管理系统 </div> <el-form ref="formRef" :model="data.form" style="width: 350px"> <el-form-item> <el-input v-model="data.form.username" placeholder="请输入用户名" prefix-icon="User"></el-input> </el-form-item> <el-form-item> <el-input v-model="data.form.password" placeholder="请输入密码" prefix-icon="Lock" show-password></el-input> </el-form-item> <div style="margin-top: 20px;margin-bottom: 20px"> <el-button size="large" style="width: 100%;" type="primary">登 录</el-button> </div> <div style="margin-bottom: 20px"> <el-button size="large" style="width: 100%;" type="primary">注 册</el-button> </div> <!-- 可以使用text-decoration:none 让链接不显示下划线 --> <div style="text-align: right">没有账号?请 <a style="color: #1967e3;" href="/register">注册</a></div> </el-form> </div> </div> </div> </div> </template> <script setup> import {reactive} from "vue"; const data = reactive({ form:{}, }) </script> <style scoped> .login-container { /*占满可视高度*/ height: 100vh; /*隐藏超出高度*/ overflow: hidden; /*背景图*/ background-image: url("@/assets/bimage2.jpeg"); /*背景图片大小*/ background-size: cover; /*设置背景图移动*/ background-position: -450px -120px; } .login-box { /*绝对定位*/ position: absolute; width: 50%; height: 100vh; /*靠右*/ right: 0; /*flex布局*/ display: flex; align-items: center; background-color: #f1ebe2; } </style>
前端登录页面渲染效果图示。
表单项(username、password)校验。(属性ref、:rules)
html<el-form ref="formRef" :rules="data.rules" :model="data.form" style="width: 350px">
javascriptimport {reactive,ref} from "vue"; const formRef = ref() const data = reactive({ form:{}, rules:{ username :[ { required: true ,message:'请输入用户名',trigger:'blur'} ], password :[ { required: true ,message:'请输入密码',trigger:'blur'} ], }, })
表单项对应规则。(属性prop)
html<el-form-item prop="username"> <el-input v-model="data.form.username" placeholder="请输入用户名" prefix-icon="User"></el-input> </el-form-item> <el-form-item prop="password"> <el-input v-model="data.form.password" placeholder="请输入密码" prefix-icon="Lock" show-password></el-input> </el-form-item>
表单项校验效果图示。(鼠标未输入、且失焦)
登录绑定login函数。(若校验成功--->post请求后端接口"/login")
html<div style="margin-top: 20px;margin-bottom: 20px"> <el-button size="large" style="width: 100%;" type="primary" @click="login">登 录</el-button> </div>
javascriptimport {reactive,ref} from "vue"; import request from "@/utils/request.js"; import {ElMessage} from "element-plus"; const formRef = ref() //登录按钮操作方法 const login = () =>{ //开始校验 formRef.value.validate((valid)=>{ if(valid){ //表单项(字段)验证通过 request.post("/login",data.form).then(res=>{ if(res.code === '200'){ //登录成功 ElMessage.success('登录成功') //登录成功后,跳转主页。等500ms进行跳转 setTimeout(()=>{ location.href = '/manager/home' },500) }else { //登录失败 ElMessage.error(res.msg) } }) } }) }
(6)后端代码编写。(登录)
<1>controller层登录接口。
javapackage com.hyl.controller; import com.hyl.common.Result; import com.hyl.entity.Employee; import com.hyl.service.EmployeeService; import jakarta.annotation.Resource; 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; //表示对外提供可访问接口的类 @RestController public class WebController { @Resource private EmployeeService employeeService; //测试接口(不必理会) @GetMapping("/hello") public Result hello(){ return Result.success("hello"); } //登录接口 //返回登录成功后的员工对象通过@RestController响应成JSON对象给前端 //前端(res.data)再将JSON对象转换成JSON字符串临时存储用户信息 @PostMapping("/login") public Result login(@RequestBody Employee employee){ Employee dbEmployee = employeeService.login(employee); return Result.success(dbEmployee); } }
<2>service层的登录业务处理。
自定义异常。(CustomerException)
javapackage com.hyl.exception; public class CustomerException extends RuntimeException{ private String code; private String msg; public CustomerException(String code, String msg) { this.code = code; this.msg = msg; } //getter|setter方法 public String getCode() { return code; } public void setCode(String code) { this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } }
封装返回前端数据结果类。(Result)
javapackage com.hyl.common; /** * 统一后端返回的数据结果类型 */ public class Result { //请求状态码 private String code; //成功或错误信息 private String msg; //返回数据结果 private Object data; //常用静态方法 //请求成功且无数据返回 public static Result success(){ Result result = new Result(); result.setCode("200"); result.setMsg("请求成功"); return result; } //请求成功且有数据返回。 public static Result success(Object data) { //直接调用静态方法设置状态码、请求成功信息 Result result = Result.success(); result.setData(data); return result; } //请求失败且无数据返回 public static Result error(){ Result result = new Result(); result.setCode("500"); result.setMsg("系统出错了!"); return result; } //请求失败且无数据返回。适配自定义异常(传递code、msg) public static Result error(String code,String msg){ Result result = new Result(); result.setCode(code); result.setMsg(msg); return result; } //getter、setter方法 public String getCode() { return code; } public void setCode(String code) { this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public Object getData() { return data; } public void setData(Object data) { this.data = data; } }
全局异常捕获处理器。(GlobalExceptionHandler)
javapackage com.hyl.exception; import com.hyl.common.Result; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; //标识全局异常处理器并设置捕获路径 //因为异常产生最终都会归宿到controller中,所有异常统一在controller包中接口中处理。 @ControllerAdvice("com.hyl.controller") public class GlobalExceptionHandler { @ExceptionHandler(Exception.class) //捕获所有系统异常 @ResponseBody//返回json串 public Result error(Exception e) { //更清晰看到后端控制台报的错误 e.printStackTrace(); //返回Result的error()方法设置的错误信息(code、msg)给前端 return Result.error(); } @ExceptionHandler(CustomerException.class) //捕获自定义异常 @ResponseBody//返回json串 public Result error(CustomerException e) { //自定义异常的参数code、msg //更清晰看到后端控制台报的错误 e.printStackTrace(); //Result的error(String code,String Msg)方法设置获取到的错误信息(code、msg) return Result.error(e.getCode(),e.getMsg()); } }
前端处理请求(request)和响应(response)的工具类。
javascriptimport { ElMessage } from 'element-plus' import axios from "axios"; const request = axios.create({ //设置后台请求地址 baseURL: 'http://localhost:9090', timeout: 30000 // 后台接口超时时间设置 }) // request 拦截器(数据请求) // 可以自请求发送前对请求做一些处理 request.interceptors.request.use(config => { //设置统一的数据传输格式json、数据传输编码utf-8 config.headers['Content-Type'] = 'application/json;charset=utf-8'; return config }, error => { return Promise.reject(error) }); // response 拦截器 // 可以在接口响应后统一处理结果 request.interceptors.response.use( response => { //响应对象response中提取实际数据部分,存储在变量res中 let res = response.data; // 兼容服务端返回的字符串数据 //如果res是字符串且不为空字符串,则使用JSON.parse方法将其解析为JavaScript对象; //如果 res 为空字符串,则保持原样。 if (typeof res === 'string') { res = res ? JSON.parse(res) : res } return res; }, error => { if(error.response.status === 404){ //404 状态码表示请求的资源未找到,通常意味着请求的接口不存在 ElMessage.error('未找到请求接口') }else if(error.response.status === 500){ //500:之前后端设置的全局系统异常处理捕获 //500 状态码表示服务器内部错误,通常是由于后端代码出现异常 ElMessage.error('系统异常,请查看后端控制台报错') }else{ //其它情况统一打印错误信息 console.error(error.message) } //将错误继续抛出,以便后续的代码可以继续处理该错误 return Promise.reject(error) } ) export default request
配置前后端跨域配置类。
javapackage com.hyl.common; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.CorsFilter; /** * 跨域配置类。用于解决前端和后端由于不同源(协议、域名、端口不同)导致的跨域请求问题 * @Configuration 让该配置类注入到Spring容器中并能够扫描到其下类下注解 */ @Configuration public class CorsConfig { /** * 创建并注册CorsFilter bean,用于处理跨域请求 * @return CorsFilter实例,Spring会在请求处理过程中使用该过滤器处理跨域请求 * @Bean 注解会让该方法的返回值注入到Spring容器中 */ @Bean public CorsFilter corsFilter() { // 创建一个基于URL的跨域配置源对象,用于存储和管理不同URL路径的跨域配置 UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); // 创建一个跨域配置对象,用于设置具体的跨域规则 CorsConfiguration corsConfiguration = new CorsConfiguration(); // 设置允许访问的源地址,这里使用通配符"*",表示允许所有源地址访问 corsConfiguration.addAllowedOrigin("*"); // 设置允许的请求头,"*"表示允许所有请求头,即前端可以在请求中携带任意请求头 corsConfiguration.addAllowedHeader("*"); // 设置允许的请求方法,"*"表示允许所有的HTTP请求方法,如GET、POST、PUT、DELETE等 corsConfiguration.addAllowedMethod("*"); // 将跨域配置应用到所有的接口路径上,"/**"表示匹配所有路径 source.registerCorsConfiguration("/**", corsConfiguration); // 创建并返回CorsFilter实例,传入配置源对象,Spring会使用该过滤器处理跨域请求 return new CorsFilter(source); } }
service层登录逻辑代码示例。
javapublic Employee login(Employee employee){ //用户名 String username = employee.getUsername(); //先查询数据库中是否有该用户名 Employee dbEmployee = employeeMapper.selectByUserName(username); if(Objects.isNull(dbEmployee)){ //未查询到用户 //返回自定义异常。交给全局异常捕获器处理 //将错误状态码返回前端,前端捕获返回的res.code。提示登录失败与错误信息:用户不存在 throw new CustomerException("500","用户不存在"); } //用户存在 //判断密码是否正确 String password = employee.getPassword(); if(!dbEmployee.getPassword().equals(password)){ //用户输入的密码与数据库的密码不匹配 //返回自定义异常。交给全局异常捕获器处理 //将错误状态码返回前端,前端捕获返回的res.code。提示登录失败与错误信息:账号或密码错误 throw new CustomerException("500","账号或密码错误"); } //所有逻辑判断没有问题,返回数据库的Employee对象 return dbEmployee; }
<3>mapper层SQL。
java@Select("select * from `employee` where username = #{username}") Employee selectByUserName(String username);
<4>登录页面进行登录操作图示。
<5>获取后端返回的员工对象。(实时渲染右上角用户名称)
存储用户信息。(localStorage.setItem(xxx))
javascript//登录按钮操作方法 const login = () =>{ //开始校验 formRef.value.validate((valid)=>{ if(valid){ //表单项(字段)验证通过 request.post("/login",data.form).then(res=>{ if(res.code === '200'){ //登录成功 ElMessage.success('登录成功') //登录成功后,跳转主页。等500ms进行跳转 setTimeout(()=>{ location.href = '/manager/home' },500) //存储后台返回的员工对象信息 //把后端传来的JSON对象转换成JSON字符串存储 localStorage.setItem('xm-pro-user',JSON.stringify(res.data)) }else { //登录失败 ElMessage.error(res.msg) } }) } }) }
主页面(Manager.vue)获取登录时存储的用户信息。
javascriptimport {reactive} from "vue"; const data = reactive({ //记得将拿到的JSON字符串转换成JSON对象。这样才能获取到员工名称:data.user.name user: JSON.parse(localStorage.getItem('xm-pro-user')) })
html<!-- 右半部分-头像 --> <div style="width: 150px;display: flex;align-items: center"> <img src="https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png" alt="" style="width: 40px;height: 40px"> <span style="margin-left: 5px;color: white">{{data.user.name}}</span> </div>
(7)新建注册页面。(Register.vue)
- 后面补充。!!!!




















