springboot+vue+mybatisplus
实现连接数据库,登录之后跳转到menu页面,没有登录时只能进入登录页
基础前后端代码在‘Vue前后端交互+springboot+mybatisplus’文章里写过
跨域CrossConfig.class
写一个跨越的配置类,就不用在controller里一个一个加@CrossOrigin跨域注解
package org.example.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
@Configuration
public class CrossConfig {
@Bean
public CorsFilter corsFilter(){
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
final CorsConfiguration corsConfiguration = new CorsConfiguration();
// corsConfiguration.setAllowCredentials(true);
corsConfiguration.addAllowedHeader("*"); // 允许所有的头
corsConfiguration.addAllowedOrigin("*"); //
corsConfiguration.addAllowedMethod("*"); // * get put delete head post
source.registerCorsConfiguration("/**", corsConfiguration); // 所有的路由都能够进行跨域
return new CorsFilter(source);
}
}
工具类ResponseVo.class
package org.example.util;
public class ResponseVo {
private int id;
private String msg;
private Object data;
public ResponseVo(String msg, Object data) {
this.msg = msg;
this.data = data;
}
public ResponseVo(int id, String msg, Object data) {
this.id = id;
this.msg = msg;
this.data = data;
}
public ResponseVo(int id, String msg) {
this.id = id;
this.msg = msg;
}
public static ResponseVo SUCCESS(String msg){
return new ResponseVo(0,msg);
}
public static ResponseVo SUCCESS(String msg,Object data){
return new ResponseVo(0,msg,data);
}
public static ResponseVo FAIL(String msg){
return new ResponseVo(1,msg);
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Object getData() {
return data;
}
public void setObject(Object data) {
this.data = data;
}
}
登录后端相关
UserController.class
IUserService.interface
UserServiceImpl.class
package org.example.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.example.entity.User;
import org.example.mapper.UserMapper;
import org.example.service.IUserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* <p>
* 服务实现类
* </p>
*
* @author pan
* @since 2024-01-05
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Override
public Boolean login(User user) {
//判断前端是否把信息传过来
if (user!=null){
QueryWrapper<User> wrapper =new QueryWrapper<>();
wrapper.eq("user",user.getUser());
wrapper.eq("pwd",user.getPwd());
User one = this.getOne(wrapper);
//判断账号密码是否正确
if (one!=null){
return true;
}
}
return false;
}
}
前端相关
login.vue
输入账号密码点击登录的方法
<template>
<div class="page-container">
<el-form :model="ruleForm" status-icon ref="ruleForm" label-width="100px" class="demo-ruleForm">
<el-form-item label="账号" prop="user">
<el-input type="text" v-model="ruleForm.user" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="密码" prop="pwd">
<el-input type="password" v-model="ruleForm.pwd" autocomplete="off"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm('ruleForm')">登录</el-button>
<el-button @click="resetForm('ruleForm')">重置</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
export default {
data() {
return {
ruleForm: {
user: '',
pwd: ''
}
}
},
methods: {
submitForm() {
console.log(this.ruleForm)
//把页面输入的账号密码传到后端
this.$axios.get("/user",{params:this.ruleForm}).then(res=>{
if (res.data.data===true){
this.$router.push({name: 'menu'})
// 将登录信息以JSON字符串的形式存储在浏览器的本地存储中,使用键名"userInfo"来标识存储的数据。
localStorage.setItem("userInfo",JSON.stringify(this.ruleForm))
}
else {
alert("账号或密码错误")
}
})
},
resetForm(formName) {
this.$refs[formName].resetFields();
}
},
created() {
}
}
</script>
<style>
.el-form {
width: 400px;
}
.page-container {
display: flex;
justify-content: center;
height: 100vh;
margin-top: 100px;
}
</style>
localStorage是一个Web浏览器提供的API,它允许开发者在浏览器中存储键值对数据,并且该数据是持久化的,即使关闭了浏览器也可以保留。而setItem(key, value)方法是localStorage对象的一个方法,用于将指定的键值对数据存储到本地存储中。
通过将数据存储在本地存储中,可以在后续的浏览器会话中使用localStorage.getItem("userInfo")方法来检索该数据,并通过JSON.parse()方法将其还原为原始的对象形式,以便在应用程序中使用。
main.js(路由守卫)
import Vue from 'vue'
import router from './router'
import App from './App.vue';
Vue.config.productionTip = false
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);
import ax from "axios";
const instance=ax.create({
baseURL:'http://localhost:8080'
})
//路由守卫,对路由跳转进行拦截,判断用户是否已经登录,如果没有登录则阻止用户跳转到指定路由,并将其重定向到登录页面。
router.beforeEach((to,from,next)=>{
//判断即将要进入的是否为登录页或是否登陆成功
if (to.path=='/'||localStorage.getItem("userInfo")!=null){
//进入登录页或已经登录,可以跳转到其他页面
next()
}else {
//否则进入登录页
next('/')
}
})
// 定义成全局变量
Vue.prototype.$axios=instance;
new Vue({
router,
render: h => h(App)
}).$mount('#app')
router.beforeEach是Vue Router提供的一个全局前置守卫,它会在每次路由切换之前被调用。该守卫接收三个参数:to、from和next。
- to:即将要进入的目标路由对象。
- from:当前导航正要离开的路由对象。
- next:必须调用该方法来resolve钩子函数。执行效果依赖next方法的调用参数。
拦截器
请求拦截器
请求拦截器,也称为axios拦截器,是在发送请求之前对请求进行处理的一个函数队列。在拦截器中,可以对请求进行一些公共的处理和配置,例如添加请求头、设置请求超时时间、对请求数据进行加密等。请求拦截器可以全局配置,也可以单个请求配置。
在使用Axios库时,可以通过以下方式定义请求拦截器:
// 添加请求拦截器
axios.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
config.headers.Authorization = getToken();
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
上述代码中,axios.interceptors.request.use()方法用于添加请求拦截器。该方法接收两个参数,第一个参数为请求发送前的处理函数,第二个参数为请求出错时的处理函数。其中,请求发送前的处理函数中的config参数表示当前请求的配置对象,可以在该对象中进行一些通用的请求配置和处理,例如设置请求头、添加token等。请求出错时的处理函数中的error参数则表示请求出错的错误对象。
需要注意的是,在请求拦截器中必须要返回config或Promise.resolve(config),否则请求将被阻止。
添加请求拦截器前:
在main.js中添加请求拦截器
//请求拦截器
instance.interceptors.request.use(config=>{
// 在请求发送前拦截请求,并为请求头添加登录信息
config.headers.login=localStorage.getItem("userInfo");
return config;
})
- instance: 请求实例对象(通常是 Axios 实例)
- interceptors: 拦截器对象,用于拦截请求和响应
- request: 请求拦截器,用于拦截发送的请求
- use: 注册拦截器的方法,接收一个回调函数作为参数
- config: 请求配置对象,包含请求的相关信息
- config.headers: 请求头对象,用于设置请求头信息
- localStorage.getItem("userInfo"): 从本地存储中获取 "userInfo" 的值,通常用于存储登录信息等
- config.headers.login: 将登录信息添加到请求头中的 "login" 属性中
添加请求拦截器后:
响应拦截器
响应拦截器(Response Interceptor)是在获取到服务器响应后,对响应进行统一处理的机制。响应拦截器通常用于处理服务端返回的数据格式、异常信息等情况。
// 添加响应拦截器
axios.interceptors.response.use(function (response) {
// 2xx 范围内的状态码都会触发该函数。
// 对响应数据做点什么
return response;
}, function (error) {
// 超出 2xx 范围的状态码都会触发该函数。
// 对响应错误做点什么
return Promise.reject(error);
});
在main.js中添加响应拦截器
//响应拦截器
instance.interceptors.response.use( (response)=> {
// 在接收到响应数据之前做些什么
// 通过response.data.data获取响应数据中的data字段,然后将提取的数据返回。
let repdata = response.data.data;
return repdata;
});
拦截前
拦截后
注:在响应拦截器中必须要返回处理后的响应数据,否则请求的then回调函数将无法接收到正确的数据。
分页
单表分页
把element上分页完整功能相关代码粘进t.vue
后端
写一个分页的配置类
@Configuration
public class MybatisPlusConfig {
/**
* 添加分页插件
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));//如果配置多个插件,切记分页最后添加
//interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); 如果有多数据源可以不配具体类型 否则都建议配上具体的DbType
return interceptor;
}
}
DeptController
package org.example.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.example.entity.Dept;
import org.example.service.IDeptService;
import org.example.util.ResponseVo;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
/**
* <p>
* 前端控制器
* </p>
*
* @author pan
* @since 2024-01-05
*/
@RestController
@CrossOrigin
@RequestMapping("/dept")
public class DeptController {
@Resource
private IDeptService service;
@GetMapping
public ResponseVo getAll(Integer index,Integer size){
Page page=new Page(index,size);
Page page1 = service.page(page);
return ResponseVo.SUCCESS("成功",page1);
}
}
前端
在element里复制分页完整功能相关代码粘到t.vue里,再根据后端传过来的代码进行改写
Element - The world's most popular Vue UI framework
<template>
<div>
<!-- 表格组件 -->
<el-table
:data="tableData"
style="width: 100%"
:row-class-name="tableRowClassName">
<!-- 表格列,显示日期 -->
<el-table-column
prop="deptno"
label="日期"
width="180">
</el-table-column>
<!-- 表格列,显示部门名称 -->
<el-table-column
prop="dname"
label="部门名称"
width="180">
</el-table-column>
<!-- 表格列,显示地址 -->
<el-table-column
prop="loc"
label="地址">
</el-table-column>
</el-table>
<!-- 分页组件 -->
<div class="block">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage4"
:page-sizes="[2, 4, 6, 8]"
:page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="total">
</el-pagination>
</div>
</div>
</template>
<style>
.el-table .warning-row {
background: oldlace;
}
.el-table .success-row {
background: #f0f9eb;
}
</style>
<script>
export default {
methods: {
// 根据行和索引返回相应的类名,实现彩色背景
tableRowClassName({row, rowIndex}) {
if (rowIndex % 4 === 1) {
return 'warning-row';
} else if (rowIndex % 4 === 3) {
return 'success-row';
}
return '';
},
// 每页显示条数改变时的回调函数
handleSizeChange(val) {
console.log(`每页 ${val} 条`);
this.pageSize=val
this.query()
},
// 当前页码改变时的回调函数
handleCurrentChange(val) {
console.log(`当前页: ${val}`);
this.currentPage4=val,
this.query()
},
// 查询数据方法
query(){
this.$axios.get("/dept",{params:{index:this.currentPage4,size:this.pageSize}}).then(res => {
console.log(res)
this.total=res.total;
this.tableData = res.records;
})
}
},
// 组件的数据
data() {
return {
tableData: [], // 表格数据
currentPage4: 1, // 当前页码
pageSize:2, // 每页显示条数
total:0, // 数据总数
}
},
// 组件创建时自动调用的钩子函数
created() {
this.query() // 初始化时查询数据
}
}
</script>
t.vue
<template>
<div>
<!-- 表格组件 -->
<el-table
:data="tableData"
style="width: 100%"
:row-class-name="tableRowClassName">
<!-- 表格列,显示日期 -->
<el-table-column
prop="deptno"
label="日期"
width="180">
</el-table-column>
<!-- 表格列,显示部门名称 -->
<el-table-column
prop="dname"
label="部门名称"
width="180">
</el-table-column>
<!-- 表格列,显示地址 -->
<el-table-column
prop="loc"
label="地址">
</el-table-column>
</el-table>
<!-- 分页组件 -->
<div class="block">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage4"
:page-sizes="[2, 4, 6, 8]"
:page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="total">
</el-pagination>
</div>
</div>
</template>
<style>
.el-table .warning-row {
background: oldlace;
}
.el-table .success-row {
background: #f0f9eb;
}
</style>
<script>
export default {
methods: {
// 根据行和索引返回相应的类名,实现彩色背景
tableRowClassName({row, rowIndex}) {
if (rowIndex % 4 === 1) {
return 'warning-row';
} else if (rowIndex % 4 === 3) {
return 'success-row';
}
return '';
},
// 每页显示条数改变时的回调函数
handleSizeChange(val) {
console.log(`每页 ${val} 条`);
this.pageSize=val
this.query()
},
// 当前页码改变时的回调函数
handleCurrentChange(val) {
console.log(`当前页: ${val}`);
this.currentPage4=val,
this.query()
},
// 查询数据方法
query(){
this.$axios.get("/dept",{params:{index:this.currentPage4,size:this.pageSize}}).then(res => {
console.log(res)
this.total=res.total;
this.tableData = res.records;
})
}
},
// 组件的数据
data() {
return {
tableData: [], // 表格数据
currentPage4: 1, // 当前页码
pageSize:2, // 每页显示条数
total:0, // 数据总数
}
},
// 组件创建时自动调用的钩子函数
created() {
this.query() // 初始化时查询数据
}
}
</script>
访问
多表分页
后端
@RestController
@CrossOrigin
@RequestMapping("/dept")
public class DeptController {
@Resource
private IDeptService service;
@GetMapping
public ResponseVo getAll(Integer index,Integer size){
Page page=new Page(index,size);
Page page1 = service.page(page);
return ResponseVo.SUCCESS("成功",page1);
}
@GetMapping("/de")
public ResponseVo getAll(){
List<Dept> list = service.list();
return ResponseVo.SUCCESS("成功",list);
}
}
Emp.java
加上
@TableField(exist = false)
private Dept dept;
EmpController
@RestController
@RequestMapping("/emp")
public class EmpController {
@Resource
private IEmpService service;
@GetMapping
public ResponseVo selectAll(PageINfo pageInfo){
Page page=new Page(pageInfo.getCurrPage(),pageInfo.getPageSize());
Page page1 = service.selectAll(page);
return ResponseVo.SUCCESS("成功",page1);
}
}
Empmapper.java
public interface EmpMapper extends BaseMapper<Emp> {
Page selectAll(Page page);
}
IEmpServece接口
public interface EmpMapper extends BaseMapper<Emp> {
Page selectAll(Page page);
}
EmpServiceImpl
@Service
public class EmpServiceImpl extends ServiceImpl<EmpMapper, Emp> implements IEmpService {
@Resource
private EmpMapper mapper;
@Override
public Page selectAll(Page page) {
return mapper.selectAll(page);
}
}
Empmapper.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">
<!-- MyBatis 映射文件 -->
<mapper namespace="org.example.mapper.EmpMapper">
<!-- 定义结果映射 -->
<resultMap id="emps" type="org.example.entity.Emp" autoMapping="true">
<!-- 定义主键属性映射 -->
<id property="empno" column="empno"/>
<!-- 定义关联对象的映射 -->
<association property="dept" javaType="org.example.entity.Dept" autoMapping="true">
<id property="deptno" column="deptno"/>
</association>
</resultMap>
<!-- 定义查询语句 -->
<select id="selectAll" resultMap="emps">
select * from emp join dept on emp.deptno=dept.deptno
</select>
</mapper>
前端
emp.vue
<template>
<div>
<!-- 表格组件 -->
<el-table
:data="tableData"
style="width: 100%"
:row-class-name="tableRowClassName">
<el-table-column
prop="empno"
label="员工编号"
width="180">
</el-table-column>
<el-table-column
prop="ename"
label="姓名"
width="180">
</el-table-column>
<el-table-column
prop="job"
label="职位">
</el-table-column>
<el-table-column
prop="mgr"
label="上司编号"
width="180">
</el-table-column>
<el-table-column
prop="hiredate"
label="入职日期"
width="180">
</el-table-column>
<el-table-column
prop="sal"
label="底薪">
</el-table-column>
<el-table-column
prop="comm"
label="提成"
width="180">
</el-table-column>
<el-table-column
prop="dept.dname"
label="部门名称"
width="180">
</el-table-column>
</el-table>
<!-- 分页组件 -->
<div class="block">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="page.currPage"
:page-sizes="[5, 10, 15,20]"
:page-size="page.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="page.total">
</el-pagination>
</div>
</div>
</template>
<style>
.el-table .warning-row {
background: oldlace;
}
.el-table .success-row {
background: #f0f9eb;
}
</style>
<script>
export default {
methods: {
// 根据行和索引返回相应的类名,实现彩色背景
tableRowClassName({row, rowIndex}) {
if (rowIndex % 4 === 1) {
return 'warning-row';
} else if (rowIndex % 4 === 3) {
return 'success-row';
}
return '';
},
// 每页显示条数改变时的回调函数
handleSizeChange(val) {
this.page.pageSize=val
this.query()
},
// 当前页码改变时的回调函数
handleCurrentChange(val) {
this.page.currPage=val,
this.query()
},
// 查询数据方法
query(){
let newparam={...this.page};
this.$axios.get("emp",{params:newparam}).then(res => {
console.log(newparam)
this.page.total=res.total;
this.tableData = res.records;
})
},
},
// 组件的数据
data() {
return {
page:{
currPage:1,
pageSize:5,
total:0,
},
options: [],
value: '',
search:{
deptno:''
},
tableData: [], // 表格数据
}
},
// 组件创建时自动调用的钩子函数
created() {
this.query() // 初始化时查询数据
}
}
</script>
index.js
menu.js
访问
查询
emp.vue
<template>
<div>
<template>
<el-select v-model="search.deptno" placeholder="请选择">
<el-option
v-for="item in options"
:key="item.deptno"
:label="item.dname"
:value="item.deptno">
</el-option>
</el-select>
<el-button icon="el-icon-search" circle @click="onSubmit"></el-button>
</template>
<!-- 表格组件 -->
<el-table
:data="tableData"
style="width: 100%"
:row-class-name="tableRowClassName">
<el-table-column
prop="empno"
label="员工编号"
width="180">
</el-table-column>
<el-table-column
prop="ename"
label="姓名"
width="180">
</el-table-column>
<el-table-column
prop="job"
label="职位">
</el-table-column>
<el-table-column
prop="mgr"
label="上司编号"
width="180">
</el-table-column>
<el-table-column
prop="hiredate"
label="入职日期"
width="180">
</el-table-column>
<el-table-column
prop="sal"
label="底薪">
</el-table-column>
<el-table-column
prop="comm"
label="提成"
width="180">
</el-table-column>
<el-table-column
prop="dept.dname"
label="部门名称"
width="180">
</el-table-column>
</el-table>
<!-- 分页组件 -->
<div class="block">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="page.currPage"
:page-sizes="[5, 10, 15,20]"
:page-size="page.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="page.total">
</el-pagination>
</div>
</div>
</template>
<style>
.el-table .warning-row {
background: oldlace;
}
.el-table .success-row {
background: #f0f9eb;
}
</style>
<script>
export default {
methods: {
// 根据行和索引返回相应的类名,实现彩色背景
tableRowClassName({row, rowIndex}) {
if (rowIndex % 4 === 1) {
return 'warning-row';
} else if (rowIndex % 4 === 3) {
return 'success-row';
}
return '';
},
// 每页显示条数改变时的回调函数
handleSizeChange(val) {
this.page.pageSize=val
this.query()
},
// 当前页码改变时的回调函数
handleCurrentChange(val) {
this.page.currPage=val,
this.query()
},
// 查询数据方法
query(){
let newparam={...this.page,...this.search};
this.$axios.get("emp",{params:newparam}).then(res => {
console.log(newparam)
this.page.total=res.total;
this.tableData = res.records;
})
},
querydept(){
this.$axios.get("/dept/de").then(res => {
this.options = res;
})
},
onSubmit() {
this.query()
}
},
// 组件的数据
data() {
return {
page:{
currPage:1,
pageSize:5,
total:0,
},
options: [],
value: '',
search:{
deptno:''
},
tableData: [], // 表格数据
}
},
// 组件创建时自动调用的钩子函数
created() {
this.querydept()
this.query() // 初始化时查询数据
}
}
</script>
EmpController
@RestController
@RequestMapping("/emp")
public class EmpController {
@Resource
private IEmpService service;
@GetMapping
public ResponseVo selectAll(PageInfo pageInfo, @RequestParam(required = false) Integer deptno){
Page page=new Page(pageInfo.getCurrPage(),pageInfo.getPageSize());
Page page1 = service.selectAll(page,deptno);
return ResponseVo.SUCCESS("成功",page1);
}
}
DeptController
@RestController
@CrossOrigin
@RequestMapping("/dept")
public class DeptController {
@Resource
private IDeptService service;
@GetMapping("de")
public ResponseVo select(){
List<Dept> list = service.list();
return ResponseVo.SUCCESS("成功",list);
}
@GetMapping
public ResponseVo getAll(Integer index,Integer size){
Page page=new Page(index,size);
Page page1 = service.page(page);
return ResponseVo.SUCCESS("成功",page1);
}
}