目录
一.项目介绍
1.技术栈
- 前端技术
- Vue 前端框架
- Vuex 全局状态管理框架
- ElementUI 前端UI框架
- Axios 前端HTTP框架
- vue-element-admin 项目脚手架
- 后端技术
- Springboot 容器+MVC框架
- MyBatis ORM框架
- MyBatis-Plus Mybatis增强工具
- Redis 非关系型数据库
2.页面原型
登陆页面
管理界面
二.创建数据库
用户表
角色表
菜单表
用户角色映射表
角色菜单映射表
1.用户表
CREATE TABLE `x_user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL,
`password` varchar(100) DEFAULT NULL,
`email` varchar(50) DEFAULT NULL,
`phone` varchar(20) DEFAULT NULL,
`status` int(1) DEFAULT NULL,
`avatar` varchar(200) DEFAULT NULL,
`deleted` INT(1) DEFAULT 0,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
insert into `x_user` (`id`, `username`, `password`, `email`, `phone`, `status`, `avatar`, `deleted`) values('1','admin','123456','[email protected]','18677778888','1','https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif','0');
insert into `x_user` (`id`, `username`, `password`, `email`, `phone`, `status`, `avatar`, `deleted`) values('2','zhangsan','123456','[email protected]','13966667777','1','https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif','0');
insert into `x_user` (`id`, `username`, `password`, `email`, `phone`, `status`, `avatar`, `deleted`) values('3','lisi','123456','[email protected]','13966667778','1','https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif','0');
insert into `x_user` (`id`, `username`, `password`, `email`, `phone`, `status`, `avatar`, `deleted`) values('4','wangwu','123456','[email protected]','13966667772','1','https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif','0');
insert into `x_user` (`id`, `username`, `password`, `email`, `phone`, `status`, `avatar`, `deleted`) values('5','zhaoer','123456','[email protected]','13966667776','1','https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif','0');
insert into `x_user` (`id`, `username`, `password`, `email`, `phone`, `status`, `avatar`, `deleted`) values('6','songliu','123456','[email protected]','13966667771','1','https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif','0');
2.角色表
CREATE TABLE `x_role` (
`role_id` int(11) NOT NULL AUTO_INCREMENT,
`role_name` varchar(50) DEFAULT NULL,
`role_desc` varchar(100) DEFAULT NULL,
PRIMARY KEY (`role_id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4;
insert into `x_role` (`role_id`, `role_name`, `role_desc`) values('1','admin','超级管理员');
insert into `x_role` (`role_id`, `role_name`, `role_desc`) values('2','hr','人事专员');
insert into `x_role` (`role_id`, `role_name`, `role_desc`) values('3','normal','普通员工');
3.菜单表
CREATE TABLE `x_menu` (
`menu_id` int(11) NOT NULL AUTO_INCREMENT,
`component` varchar(100) DEFAULT NULL,
`path` varchar(100) DEFAULT NULL,
`redirect` varchar(100) DEFAULT NULL,
`name` varchar(100) DEFAULT NULL,
`title` varchar(100) DEFAULT NULL,
`icon` varchar(100) DEFAULT NULL,
`parent_id` int(11) DEFAULT NULL,
`is_leaf` varchar(1) DEFAULT NULL,
`hidden` tinyint(1) DEFAULT NULL,
PRIMARY KEY (`menu_id`)
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4;
insert into `x_menu`(`menu_id`,`component`,`path`,`redirect`,`name`,`title`,`icon`,`parent_id`,`is_leaf`,`hidden`) values (1,'Layout','/user','/user/list','userManage','用户管理','userManage',0,'N',0),(2,'user/user','list',NULL,'userList','用户列表','userList',1,'Y',0),(3,'user/role','role',NULL,'roleList','角色列表','role',1,'Y',0),(4,'user/permission','permission',NULL,'permissionList','权限列表','permission',1,'Y',0);
4.用户角色映射表
CREATE TABLE `x_user_role` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) DEFAULT NULL,
`role_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4;
insert into `x_user_role` (`id`, `user_id`, `role_id`) values('1','1','1');
5.角色菜单映射表
CREATE TABLE `x_role_menu` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`role_id` int(11) DEFAULT NULL,
`menu_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4;
三.前端环境搭建
1.Node环境搭建
版本不要超过16.12.0,因为vue-admin-template不支持最新版本的Node18
2.创建项目
打开模板vue-admin-template,修改为自己的项目名字 ,这个项目会为你生成一个完整的开发框架
3.项目初始化
(1).安装依赖
安装淘宝镜像
npm config set registry http://registry.npm.taobao.org/
安装依赖
npm install
(2).测试是否正常启动
npm run dev
4.配置修改
(1).修改首页名称项
- vue.config.js 修改端口、语法校验、浏览器默认开关
- src\settings.js 修改项目展示标题
- scr\router\Index.js 修改项目页面标题
vue.config.js
- port:项目启动占用的端口
- lintOnsave:语法校验,建议关闭,不然有时候多写个空格逗号也会莫名报错
- open:自动打开页面,可关可不关
settings.js
在搜索框中搜索Vue Admin Template,然后替换,也可以直接去settings.js文件中修改
修改完之后是这个样子
对应这里
router\Index.js
对应这里
(2).修改登录项
在src\views\login\index.vue中修改
我们可以看到以下属性
- Login Form
- username
- password
- login
- 校验提示
(3).最终效果
(4).个性化定制 (可省略这一步)
如何查看组件的样式选择器是什么?
右键检查,点击右上角
可以看到背景板的样式选择器是.login-container
我们首先将准备好的图片放入assets中,然后再选择器下面加上其URL路径
background-image: url('../../assets/DragonBall.jpg');
//整张平铺100%
background-size: 100%;
效果:
修改一下登录框的位置,并给它来个背景板框起来,让他整齐一点
display: flex;
align-items: center;
.login-form {
position: relative;
width: 520px;
max-width: 100%;
padding: 35px 35px 0;
margin: 0 auto;
overflow: hidden;
//为登录框部分添加一个背景色板
background-color: #283443;
//将四角改为圆弧,好看一点
border-radius: 8px;
//添加一点透明度
opacity: 0.9;
}
效果
这部分的完整代码如下
.login-container {
min-height: 100%;
width: 100%;
background-color: $bg;
overflow: hidden;
background-image: url('../../assets/DragonBall.jpg');
//整张平铺100%
background-size: 100%;
display: flex;
align-items: center;
.login-form {
position: relative;
width: 520px;
max-width: 100%;
padding: 35px 35px 0;
margin: 0 auto;
overflow: hidden;
//为登录框部分添加一个背景色板
background-color: #283443;
//将四角改为圆弧,好看一点
border-radius: 8px;
//添加一点透明度
opacity: 0.9;
}
}
5.下划菜单修改
src\layout\components\Navbar.vue
6.更改图标
- 在src\views目录下创建sys模块目录,test模块目录
- 在sys下创建user.vue,role.vue两个组件文件
- 在test下创建test1.vue,test2.vue,test3.vue
将自己准备好的图标放在src\icons\svg目录下
然后再router中修改
效果
以此为基础,创建多个让页面好看点,
这部分的全部路由代码为
{
path: '/login',
component: () => import('@/views/login/index'),
hidden: true
},
{
path: '/404',
component: () => import('@/views/404'),
hidden: true
},
{
path: '/',
component: Layout,
redirect: '/dashboard',
children: [{
path: 'dashboard',
name: 'Dashboard',
component: () => import('@/views/dashboard/index'),
meta: { title: '首页', icon: 'dashboard' }
}]
},
{
path: '/sys',
component: Layout,
redirect: '/sys/user',
name: 'sysManage',
meta: { title: '系统管理', icon: 'sys' },
//这个是二级路由
children: [
{
path: 'user',
name: 'user',
component: () => import('@/views/sys/user'),
meta: { title: '用户管理', icon: 'roleManage' }
},
{
path: 'role',
name: 'role',
component: () => import('@/views/sys/role'),
meta: { title: '角色管理', icon: 'roleManage' }
}
]
},
{
path: '/test',
component: Layout,
redirect: '/test/test1',
name: 'test',
meta: { title: '测试模块', icon: 'form' },
//这个是二级路由
children: [
{
path: 'test1',
name: 'test1',
component: () => import('@/views/test/test1'),
meta: { title: '功能1', icon: 'el-icon-s-help' }
},
{
path: 'test2',
name: 'test2',
component: () => import('@/views/test/test2'),
meta: { title: '功能2', icon: 'el-icon-s-help' }
},
{
path: 'test3',
name: 'test3',
component: () => import('@/views/test/test3'),
meta: { title: '功能3', icon: 'el-icon-s-help' }
},
]
},
// 404页面必须写在最后面!!!
{ path: '*', redirect: '/404', hidden: true }
但是这里有一个小缺陷,这里居然不是首页
我们在src\components\Breadcrumb\index.vue下搜索
四.后端环境搭建
1.创建项目
引入依赖
<!-- mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
<!-- 代码生成器-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.3.1</version>
</dependency>
<!-- 代码生成器要用到的freemarker-->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
</dependency>
<!-- redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
修改配置
将application的后缀名改为yml格式
server:
port: 8888
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql:///an1ong
# 不需要配置连接池,springboot有自带的连接池
# 也不需要配置驱动类driver-class-name,会默认配置,除非想要覆盖
redis:
port: 6379
host: localhost
#打印日志信息
logging:
level:
com.wal: debug
测试启动类
启动成功
2.接口梳理
接口 | url | method |
登录 | /user/login | POST |
获取用户信息 | /user/info | GET |
注销 | /user/logout | POST |
3.创建代码生成器
我可以不用,但是我不能没有
注意不要放在java文件夹下,要放在test文件夹下
package com.wal;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import java.util.Collections;
public class CodeGenerator {
public static void main(String[] args) {
//使用时修改这个模块名就行了
String moduleName = "sys";
String mapperLocation = "D:\\JAVA_project\\hyrule-admin\\src\\main\\resources\\mapper\\" + moduleName;
// 设置你需要的表对应的实体类名
String tables = "x_user,x_role,x_menu,x_user_role,x_role_menu";
FastAutoGenerator.create("jdbc:mysql:///an1ong",
"root",
"123456")
.globalConfig(builder -> {
builder.author("20101403wal") // 设置作者
// .enableSwagger() // 开启 swagger 模式
// .fileOverride() // 覆盖已生成文件
.outputDir("D:\\JAVA_project\\hyrule-admin\\src\\main\\java"); // 指定输出目录
})
.packageConfig(builder -> {
builder.parent("com.wal") // 设置父包名
.moduleName(moduleName) // 设置父包模块名
.pathInfo(Collections.singletonMap(OutputFile.xml, mapperLocation)); // 设置mapperXml生成路径
})
.strategyConfig(builder -> {
builder.addInclude(tables) // 设置需要生成的表名
.addTablePrefix("x_"); // 设置过滤表前缀
})
.templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
.execute();
}
}
运行查看结果
最后一步:
- 在启动类上加上@MapperScan注解
因为生成的Mapper类上没有@Mapper注解,也可以在Mapper类上直接加,但是直接在启动类上使用@MapperScan就可以一劳永逸了
在测试类中测试一下
测试select * from user
mybatis-plus把此类的crud封装了起来,所以可以直接使用
@SpringBootTest
class HyruleAdminApplicationTests {
@Resource
private UserMapper userMapper;
@Test
void testMapper() {
List<User> users = userMapper.selectList(null);
users.forEach(System.out::println);
}
}
可以在日志中看到其对应的查询语句
完成初始化创建
4.公共响应类
这个类是为了前后端分离之后的前后端交互统一结果的响应
这里注意,因为我们使用的vue-element-admin模板,前端接受的返回值code就是20000和20001等,如果有自己的需求就去前端处自己更改,否则返回值code就用20000
import lombok.Data;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
//这个类是为了前后端分离之后的前后端交互统一结果的相应
@Data
//这个注解可以自动为属性生成get&set方法
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> {
private Integer code; //响应码 20000-成功 20001失败
private String message; //响应信息 描述字符串
private T data; //返回的数据
public static <T> Result<T> success(){
//增删改 成功响应
return new Result<>(20000,"success",null);
}
public static <T> Result<T> success(T data){
//查询 成功响应
return new Result<>(20000,"success",data);
}
public static <T> Result<T> success(T data,String message){
return new Result<>(20000,message,data);
}
public static <T> Result<T> success(String message){
return new Result<>(20000,message,null);
}
public static<T> Result<T> error(){
return new Result<>(20001,"fail",null);
}
public static<T> Result<T> error(Integer code){
return new Result<>(code,"fail",null);
}
public static<T> Result<T> error(Integer code, String message){
return new Result<>(code,message,null);
}
public static<T> Result<T> error(String message){
return new Result<>(20001,message,null);
}
}
使用测试
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private IUserService userService;
@GetMapping("/all")
public Result<List<User>> getAll(){
List<User> users = userService.list();
return Result.success(users);
}
}
五.登录页接口实现
1.登录接口
从数据库查询是否有该用户,若该用户存在,则生成token返回Result,并将个人信息存入Redis
Controller层
@PostMapping("/login")
//这个user接收过来时是一个json格式,需要用@RequestMapping转换为实体类
public Result<Map<String,Object>> login(@RequestBody User user){
Map<String,Object> data = userService.login(user);
if (data != null){
return Result.success(data);
}
return Result.error(0,"用户名或密码错误");
}
Service层
package com.wal.sys.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.wal.sys.entity.User;
import com.wal.sys.mapper.UserMapper;
import com.wal.sys.service.IUserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* <p>
* 服务实现类
* </p>
*
* @author 20101403wal
* @since 2023-05-28
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Autowired
private RedisTemplate redisTemplate;
@Override
public Map<String, Object> login(User user) {
//根据用户名与密码查询结果
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getUsername,user.getUsername());
wrapper.eq(User::getPassword,user.getPassword());
User loginUser = this.baseMapper.selectOne(wrapper);
//若结果不为空,则生成token,并将用户信息存入redis
if (loginUser!=null){
//暂时用UUID,终极方案应为jwt
String key = "user:" + UUID.randomUUID();
//存入redis
loginUser.setPassword(null); //密码不要存进去
//默认这是永久有效的,要设置timeout时间
redisTemplate.opsForValue().set(key,loginUser,30, TimeUnit.MINUTES);
//返回数据
Map<String,Object> data = new HashMap<>();
data.put("token",key);
return data;
}
return null;
}
}
这里没有@AutoWired UserMapper是因为Mybatis-plus生成的mapper没有@Mapper注解,你可以使用老办法在Mapper类中加上注解然后在这里引入,后面的例子会展示到
也可以像这样使用通用Service
然后这样使用引入的mapper
我们这里使用redis来存储用户的登录信息
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
创建redis的配置类
package com.wal.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.text.SimpleDateFormat;
import java.util.TimeZone;
@Configuration
public class MyRedisConfig {
@Autowired
private RedisConnectionFactory factory;
@Bean
public RedisTemplate redisTemplate(){
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
//连接redis
redisTemplate.setConnectionFactory(factory);
//处理你之前做的序列化处理
redisTemplate.setKeySerializer(new StringRedisSerializer());
Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
redisTemplate.setValueSerializer(serializer);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL,JsonAutoDetect.Visibility.ANY);
om.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
om.setTimeZone(TimeZone.getDefault());
om.configure(MapperFeature.USE_ANNOTATIONS,false);
om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,false);
om.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS,false);
om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
om.setSerializationInclusion(JsonInclude.Include.NON_NULL);
serializer.setObjectMapper(om);
return redisTemplate;
}
}
测试
我们在postman中测试接口
查看redis中的信息
2.获取用户信息接口
接口属性 | 值 |
url | /user/info?token=xxx |
method | get |
请求参数 | token |
返回参数 | { "code":1, "message":"success", "data":{ role:[ ], "name":“admin” "avatar":"" } } |
Controller层
@GetMapping("/info")
//参数是在url后面的,用@RequestParam可以获取到
public Result<Map<String,Object>> getUserInfo(@RequestParam("token") String token){
//根据token从redis中获取用户信息
Map<String, Object> data = userService.getUserInfo(token);
if (data != null){
return Result.success(data);
}
return Result.error(0,"用户名或密码错误");
}
Service层
需要引入依赖fastjson来反序列化
<!-- fastjson-->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.25</version>
</dependency>
在这里实现的功能主要是获取token后对其反序列化返回给controller
@Override
public Map<String,Object> getUserInfo(String token){
//根据token在redis中获取用户信息,
Object obj = redisTemplate.opsForValue().get(token);
// 对obj处理成json
//因为是序列化存储的,使用时要反序列化
if (obj != null){
User loginUser = JSON.parseObject(JSON.toJSONString(obj),User.class);
//取数据
Map<String,Object> data = new HashMap<>();
//这个参数中的名字要与前端对应上
data.put("name",loginUser.getUsername());
data.put("avatar",loginUser.getAvatar());
//得到角色列表
List<String> roleList = this.baseMapper.getRoleNameByUserId(loginUser.getId());
data.put("roles",roleList);
return data;
}
return null;
}
Mapper层
public interface UserMapper extends BaseMapper<User> {
public List<String> getRoleNameByUserId(Integer userId);
}
在xml文件中配置sql语句,因为这个功能mybatis中没有,需要自己定义
- id要与方法名对应一致
- parame要与参数类型对应一直
- 动态sql参数userId要与方法参数userId对应
- 返回值类型String要与List<>中的类型一致
<mapper namespace="com.wal.sys.mapper.UserMapper">
<select id="getRoleNameByUserID" parameterType="Integer" resultType="String">
select b.'role_name'
from x_user_role a , x_role b
where
a.role_id = b.role_id
and a.user_id = #{userID}
</select>
</mapper>
测试
将post返回的token通过get请求提交
成功运行
3.注销接口
退出登录,从redis中清除token
接口属性 | 值 |
url | /user/login |
method | post |
请求参数 | token |
返回参数 | { "code":1, "message":"success", "data":null } |
Contoller层
@PostMapping("/logout")
//传入的是token,要用RequestHeader注解接收
public Result<?> logout(@RequestHeader("X-Token") String token){
userService.logout(token);
return Result.success();
}
Service层
@Override
public void logout(String token){
//从redis中删除
redisTemplate.delete(token);
}
测试
六.前后端对接
我们来到vue中修改前端接口代码,将接口url与后端接口对应
修改配置
- 在本地直接部署就在development中修改配置
- 打包部署在production中修改配置
- 两配置文件中内容是一样的,仅仅是环境不一样
base_api中修改根端口,调用接口的时候会用这一节拼接上这个base api
在vue.config中关闭mock服务
跨域
我们启动项目,发现出现了跨域问题
解决方法看这里:解决跨域问题
直接使用cors配置类
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
public class MyCorsConfig {
@Bean
public CorsFilter corsFilter(){
CorsConfiguration config = new CorsConfiguration();
//允许谁来异步访问
// config.addAllowedOrigin("*"); //允许所有人访问,不安全不推荐
config.addAllowedOrigin("http://localhost:7777"); //这里写允许访问的前端服务器
config.setAllowCredentials(true); //传递cookie
config.addAllowedMethod("*"); //允许哪些方法访问(*是全部方法)
// config.addAllowedMethod("OPTIONS");
// config.addAllowedMethod("HEAD");
// config.addAllowedMethod("GET");
// config.addAllowedMethod("PUT");
// config.addAllowedMethod("POST");
// config.addAllowedMethod("DELETE");
// config.addAllowedMethod("PATCH");
// 允许的头信息
config.addAllowedHeader("*");
//过滤资源
UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource();
configSource.registerCorsConfiguration("/**",config);
return new CorsFilter(configSource);
}
}
页面成功跳转,前后端对接成功
七.管理页面
1.用户管理布局
搜索栏
我们使用element标签来创建页面 组件 | Element
使用什么样式就直接在里面找就可以了,不需要死记
<template>
<div>
<!-- 搜索栏 -->
<el-input v-model="input" placeholder="用户名"></el-input>
<el-input v-model="input" placeholder="电话"></el-input>
<el-button type="primary" round>查询</el-button>
</div>
</template>
<script>
export default {
}
</script>
<style>
</style>
为了美观,我们使用card样式将其套起来
但是此时卡片的边界与左栏上栏默认的贴在了一起,依旧不美观,我们可以直接修改一下卡片外边距,但是并不推荐这种做法,因为之后还有别的组件需要修改边距,我们这里修改整个区域的css
整个区域指的这里
我们打开F12查看此区域叫什么名字
我们在根组件App.vue中设置style,定义一个全局边距
<style>
.app-main{
/* 边距 */
padding: 10px;
}
</style>
再来修改一下框的宽度、右边距以及查询按钮的图案,这里仅修改这个地方的框大小就行了,没必要设置全局属性
<template>
<div>
<!-- 搜索栏 -->
<el-card id="search">
<el-input v-model="input" placeholder="用户名"></el-input>
<el-input v-model="input" placeholder="电话"></el-input>
<el-button type="primary" round icon="el-icon-search">查询</el-button>
</el-card>
</div>
</template>
<script>
export default {
}
</script>
<style>
#search .el-input{
width: 200px;
margin-right: 10px;
}
</style>
十分甚至九分的美观啊(喜
若是想在这卡片末尾添加一个小按钮,我们可以使用Layout布局页面
这部分的完整代码如下
<template>
<div>
<!-- 搜索栏 -->
<el-card id="search">
<el-row>
<el-col :span="20">
<el-input v-model="input" placeholder="用户名"></el-input>
<el-input v-model="input" placeholder="电话"></el-input>
<!-- icon是图标,可以修改为自己的 -->
<el-button type="primary" round icon="el-icon-search">查询</el-button>
</el-col>
<!-- align是对齐,right就是右对齐 -->
<el-col :span="4" align="right">
<el-button type="primary" icon="el-icon-plus" circle></el-button>
</el-col>
</el-row>
</el-card>
</div>
</template>
<script>
export default {
}
</script>
<style>
#search .el-input{
width: 200px;
margin-right: 10px;
}
</style>
结果栏
我们同样将结果列表放入card组件,就是为了美观,不放也行
测试发现两个卡片距离太近,我们可以依照上面那样在根组件App.vue中设置全局底边距,这样别的该种类组件也能用,如果只是想在本组件生效那就写在本类文件的style中
先查看该组件css名称
.el-card{
/* 底部边距 */
margin-bottom: 10px ;
}
我们来使用表格替换掉xxx
直接使用的话会报错,因为数据绑定不正常
我们先在script中创建空数据,因为data中返回的searchModel是对象类型,所以调用的时候要使用“对象.属性”的形式,而userList是数组,直接使用即可
<script>
export default {
data(){
return{
searchModel: {},
userList: []
}
}
};
</script>
修改我们之前的代码绑定的数据
添加/修改我们需要的字段,对应数据库字段即可
添加分页组件,在element中查找分页组件组件 | Element
<el-pagination
方法1
@size-change="handleSizeChange"
方法2
@current-change="handleCurrentChange"
参数1:当前页
:current-page="currentPage4"
参数2:展示数
:page-sizes="[100, 200, 300, 400]"
参数3:页数,不要写死,用变量代替
:page-size="100"
layout="total, sizes, prev, pager, next, jumper"
参数4:总记录数:这个是由后端传来的数据,不能写常量
:total="400">
</el-pagination>
这里面绑定了两个方法4个参数,所以不能直接使用,要先在script中定义出来,不然会报错
<script>
export default {
data(){
return{
total:0,
searchModel: {
pageNo:1,
pageSize:10
},
userList: []
}
},
methods:{
handleSizeChange(){
},
handleCurrentChange(){
}
}
};
</script>
更改一下分页展示的语言
最终效果
这部分的全部代码
<template>
<div>
<!-- 搜索栏 -->
<el-card id="search">
<el-row>
<el-col :span="20">
<el-input v-model="searchModel.username" placeholder="用户名"></el-input>
<el-input v-model="searchModel.phone" placeholder="电话"></el-input>
<!-- icon是图标,可以修改为自己的 -->
<el-button type="primary" round icon="el-icon-search">查询</el-button>
</el-col>
<!-- align是对齐,right就是右对齐 -->
<el-col :span="4" align="right">
<el-button type="primary" icon="el-icon-plus" circle></el-button>
</el-col>
</el-row>
</el-card>
<!-- 结果栏 -->
<el-card>
<el-table :data="userList" stripe style="width: 100%">
<el-table-column type="index" label="#" width="180">
</el-table-column>
<el-table-column prop="id" label="ID" width="180">
</el-table-column>
<el-table-column prop="username" label="用户名" width="180">
</el-table-column>
<el-table-column prop="phone" label="电话" width="180">
</el-table-column>
<el-table-column prop="mail" label="电子邮箱">
</el-table-column>
<el-table-column label="操作" width="180">
</el-table-column>
</el-table>
<!-- 分页组件 -->
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="searchModel.pageNo"
:page-sizes="[5, 10, 15, 20]"
:page-size="searchModel.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="total">
</el-pagination>
</el-card>
</div>
</template>
<script>
export default {
data(){
return{
total:0,
searchModel: {
pageNo:1,
pageSize:10
},
userList: []
}
},
methods:{
handleSizeChange(){
},
handleCurrentChange(){
}
}
};
</script>
<style>
#search .el-input {
width: 200px;
margin-right: 10px;
}
</style>
2.用户管理接口
我们首先要实现分页查询,具体可以查看mybatis的官网插件主体 | MyBatis-Plus (baomidou.com)
创建分页插件拦截器
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MpConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
我们使用mybatis自带的page方法,发现需要传入wrapper对象
Controller层
@GetMapping("/list")
// required表示查询时Params不必带上该参数
public Result<Map<String,Object>> getUserList(@RequestParam(value = "username",required = false) String username,
@RequestParam(value = "phone",required = false) String phone,
@RequestParam("pageNo") Long pageNo,
@RequestParam("pageSize") Long pageSize){
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
//判断长度是否大于零,其实判断你username是不是非空
wrapper.eq(StringUtils.hasLength(username),User::getUsername,username);
wrapper.eq(StringUtils.hasLength(phone),User::getPhone,phone);
Page<User> page = new Page<>(pageNo, pageSize);
userService.page(page,wrapper);
Map<String,Object> data = new HashMap<>();
data.put("total",page.getTotal());
data.put("rows",page.getRecords());
return Result.success(data);
}
测试
3.接口对接
我们若是想要对接,首先在vue中需要定义api,前面做登录接口时,模板已经帮我们定义好了登录接口部分,现在我们需要自己来实现
一个模块一个js文件,我们在api文件夹下创建userManage.js
import request from '@/utils/request'
export default {
// searchModel是前端传来的数据
getUserList(searchModel){
return request({
url: '/user/list',
method: 'get',
// post上传的是json格式,get上传的params
params:{
pageNo: searchModel.pageNo,
pageSize: searchModel.pageSize,
username: searchModel.username,
phone: searchModel.phone
}
});
},
// 有别的方法在这里写
}
在user.vue中引入
我们为查询按钮绑定一下事件
查看效果
此时我们看到并没有1页展示5条数据,是因为没有定义这两个方法,当变量发生改变时调用方法
handleSizeChange(pageSize){
this.searchModel.pageSize = pageSize;
this.getUserList();
},
handleCurrentChange(pageNo){
this.searchModel.pageNo = pageNo;
this.getUserList();
},