SpringBoot3+Vue3实战(Vue3快速开发登录注册页面并对接后端接口)(4)
目录
一、SpringBoot3+Vue3实现基本增删改查。前后端通信交互、配置后端跨域请求。数据批量删除。(博客链接)
二、SpringBoot3+Vue3快速开发登录、注册页面并实现对接。
(1)操作数据表employee(员工信息表)。
<1>修改employee表的字段组成。
<2>填补对应数据库数据。
(2)修改Employee实体类。
<1>新增对应成员属性。
<2>新增对应getter、setter方法。
<3>修改之前的toString()方法。
(3)修改mapper层代码。
<1>修改新增SQL。
<2>修改更新SQL。
<3>EmployeeMapper接口代码示例。
(4)员工信息(Employee.vue)页面。
<1>前端代码示例。
<2>员工信息的页面渲染效果。
<3>对话框—新增—某些字段设为必填项。(表单校验)
表单标签(el-form)添加ref。
保存按钮绑定事件函数save中触发校验。
表单标签(el-form)绑定校验规则。(:rules)
针对username设置校验规则。(required、message、trigger)
在对话框的对应需校验表单项添加prop属性。
给名称(name)、工号(no)也绑定校验“必填项”。
关闭对话框销毁旧对话框对象。(属性destroy-on-close)
(5)新建登录页面。(Login.vue)
<1>登录页面基本框架。
配置路由。(不是嵌套子路由)
导航栏—退出登录。(index)
<2>登录页面代码示例。
前端登录页面渲染效果图示。
表单项(username、password)校验。(属性ref、:rules)
表单项对应规则。(属性prop)
表单项校验效果图示。(鼠标未输入、且失焦)
登录绑定login函数。(若校验成功—>post请求后端接口"/login")
(6)后端代码编写。(登录)
<1>controller层登录接口。
<2>service层的登录业务处理。
自定义异常。(CustomerException)
封装返回前端数据结果类。(Result)
全局异常捕获处理器。(GlobalExceptionHandler)
前端处理请求(request)和响应(response)的工具类。
配置前后端跨域配置类。
service层登录逻辑代码示例。
<3>mapper层SQL。
<4>登录页面进行登录操作图示。
<5>获取后端返回的员工对象。(实时渲染右上角用户名称)
存储用户信息。(localStorage.setItem(xxx))
主页面(Manager.vue)获取登录时存储的用户信息。
(7)新建注册页面。(Register.vue)
一、SpringBoot3+Vue3实现基本增删改查。前后端通信交互、配置后端跨域请求。数据批量删除。(博客链接)
- SpringBoot3实战(SpringBoot3+Vue3基本增删改查、前后端通信交互、配置后端跨域请求、数据批量删除(超详细))(3)-CSDN博客
- 注意:关于本篇博客的案例《员工信息的前端页面代码、后端接口(controller)、service、mapper层代码》基本都在上面那篇博客中已实现。需要的可以自取。
- 这篇博客的主要内容:学习《使用Vue3快速开发登录、注册页面并对接后台》。前端Vue页面与后端SpringBoot服务器进行对接。实现员工登录或注册进入系统的功能。
二、SpringBoot3+Vue3快速开发登录、注册页面并实现对接。
(1)操作数据表employee(员工信息表)。
<1>修改employee表的字段组成。
- employee表字段原组成。
- 添加3个字段:用户名(username)、密码(password)、角色(role)。
<2>填补对应数据库数据。
(2)修改Employee实体类。
<1>新增对应成员属性。
package 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方法。
public 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()方法。
//toString方法@Overridepublic 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 version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapperPUBLIC "-//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 version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapperPUBLIC "-//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接口代码示例。
package 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>前端代码示例。
<el-table-column label="用户名" prop="username"/>
<el-table-column label="角色" prop="role"><template #default="scope"><span v-if="scope.row.role === 'EMP'">员工</span></template> </el-table-column>
<2>员工信息的页面渲染效果。
- 一般员工信息页面只会显示用户名、角色。而密码将不会直接显示。
<3>对话框—新增—某些字段设为必填项。(表单校验)
<el-form-item label="用户名"><el-input v-model="data.form.name" autocomplete="off" placeholder="请输入用户名"/> </el-form-item>
表单标签(el-form)添加ref。
<el-form ref="formRef" :model="data.form" label-width="80px" style="padding-right: 50px;padding-top: 20px">
import {reactive,ref} from "vue";const formRef = ref();
保存按钮绑定事件函数save中触发校验。
//对话框的保存按钮事件 const save = () =>{formRef.value.validate((valid)=>{//通过校验才能点击更新或新增的保存操作if(valid){//有id进行更新操作。无id进行新增操作data.form.id ? update() : add()}}) }
表单标签(el-form)绑定校验规则。(:rules)
<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表示在输入框失去焦点时触发验证。
import {reactive,ref} from "vue";const formRef = ref();const data = reactive({rules:{username:[{ required:true , message: '请输入用户名' , trigger:'blur'}]} })
在对话框的对应需校验表单项添加prop属性。
<el-form-item label="用户名" prop="username"><el-input v-model="data.form.username" autocomplete="off" placeholder="请输入用户名"/> </el-form-item>
给名称(name)、工号(no)也绑定校验“必填项”。
<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>
import {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)
<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>登录页面代码示例。
<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)
<el-form ref="formRef" :rules="data.rules" :model="data.form" style="width: 350px">
import {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)
<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")
<div style="margin-top: 20px;margin-bottom: 20px"><el-button size="large" style="width: 100%;" type="primary" @click="login">登 录</el-button> </div>
import {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层登录接口。
package 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 {@Resourceprivate 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)
package 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)
package 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)
package 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)的工具类。
import { 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-8config.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
配置前后端跨域配置类。
package 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容器中*/@Beanpublic 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层登录逻辑代码示例。
public 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。
@Select("select * from `employee` where username = #{username}")Employee selectByUserName(String username);
<4>登录页面进行登录操作图示。
<5>获取后端返回的员工对象。(实时渲染右上角用户名称)
存储用户信息。(localStorage.setItem(xxx))
//登录按钮操作方法 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)获取登录时存储的用户信息。
import {reactive} from "vue";const data = reactive({//记得将拿到的JSON字符串转换成JSON对象。这样才能获取到员工名称:data.user.nameuser: JSON.parse(localStorage.getItem('xm-pro-user')) })
<!-- 右半部分-头像 --><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)
- 后面补充。!!!!