技术栈
前端技术 | 说明 |
---|---|
Vue | 前端框架 |
Vuex | 全局状态管理框架 |
ElementUI | 前端UI框架 |
Axios | 前端HTTP框架 |
vue-element-admin | 项目脚手架 |
后端技术 | 说明 |
---|---|
SpringBoot | 容器+MVC框架 |
MyBatis | ORM框架 |
MyBatis-plus | MyBatis增强工具 |
Redis | 非关系型数据库 |
数据库准备
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for sys_menu
-- ----------------------------
DROP TABLE IF EXISTS `sys_menu`;
CREATE TABLE `sys_menu` (
`menu_id` int(11) NOT NULL AUTO_INCREMENT,
`component` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`path` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`redirect` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`title` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`icon` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`parent_id` int(11) NULL DEFAULT NULL,
`is_leaf` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`hidden` tinyint(1) NULL DEFAULT NULL,
PRIMARY KEY (`menu_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of sys_menu
-- ----------------------------
INSERT INTO `sys_menu` VALUES (1, 'Layout', '/user', '/user/list', 'userManage', '用户管理', 'userManage', 0, 'N', 0);
INSERT INTO `sys_menu` VALUES (2, 'user/user', 'list', NULL, 'userList', '用户列表', 'userList', 1, 'Y', 0);
INSERT INTO `sys_menu` VALUES (3, 'user/role', 'role', NULL, 'roleList', '角色列表', 'role', 1, 'Y', 0);
INSERT INTO `sys_menu` VALUES (4, 'user/permission', 'permission', NULL, 'permissionList', '权限列表', 'permission', 1, 'Y', 0);
-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role` (
`role_id` int(11) NOT NULL AUTO_INCREMENT,
`role_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`role_desc` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`role_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of sys_role
-- ----------------------------
INSERT INTO `sys_role` VALUES (1, 'admin', '超级管理员');
INSERT INTO `sys_role` VALUES (2, 'hr', '人事专员');
INSERT INTO `sys_role` VALUES (3, 'normal', '普通员工');
-- ----------------------------
-- Table structure for sys_role_menu
-- ----------------------------
DROP TABLE IF EXISTS `sys_role_menu`;
CREATE TABLE `sys_role_menu` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`role_id` int(11) NULL DEFAULT NULL,
`menu_id` int(11) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of sys_role_menu
-- ----------------------------
-- ----------------------------
-- Table structure for sys_user
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`password` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`email` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`phone` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`status` int(1) NULL DEFAULT NULL,
`avater` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`deleted` int(1) NULL DEFAULT 0,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of sys_user
-- ----------------------------
INSERT INTO `sys_user` VALUES (1, 'admin1', '$2a$10$QSDIu2LNrHsj.YqC2rrwEOtTCcoZWUphwRbwe0VIKVkJXaMD2Qo8y', '123456', '12345456', 1, '', 0);
-- ----------------------------
-- Table structure for sys_user_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NULL DEFAULT NULL,
`role_id` int(11) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of sys_user_role
-- ----------------------------
INSERT INTO `sys_user_role` VALUES (1, 1, 1);
SET FOREIGN_KEY_CHECKS = 1;
后端框架装备
1.项目框架搭建
创建springboot 2.6项目
编辑pom.xml
文件导入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.cyfy</groupId>
<artifactId>x-admin-serve</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>x-admin-serve</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.7.6</spring-boot.version>
</properties>
<dependencies>
<!-- web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- mysql -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<!-- mybatis-plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.2</version>
</dependency>
<!-- freemarker -->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- fastjson -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.7</version>
</dependency>
<!-- 密码加密工具 -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>3.1.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<mainClass>com.cyfy.XAdminServeApplication</mainClass>
<skip>true</skip>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
编辑src/main/resources目录下的application.yml
配置文件(没有就创建)
server:
port:8080
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql:///my_demo
redis:
port: 6379
host: localhost
logging:
level:
com.cyfy: debug
# 设置逻辑删除字段
mybatis-plus:
global-config:
db-config:
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0
运行项目。查看是否可正常启动
2.代码生成器
在src/test/java目录下,创建CodeGenerator.java
文件,编写引用mybatis-plus代码生成器生成代码
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 url = "jdbc:mysql:///my_demo"; // 数据库链接地址
String username = "root"; // 数据库用户名
String password = "123456"; // 数据库密码
String moduleName = "sys"; // 父包模块
String project_abs_path = "E:\\项目\\神盾管理系统\\x-admin\\x-admin-serve\\src\\main\\"; // 项目绝对路径
String mapperLocation = project_abs_path + "resources\\mapper\\" + moduleName; // xxxMapper.xml文件输出地址
String tables = "sys_user,sys_role,sys_menu,sys_user_role,sys_role_menu"; // 需要生成代码的表
FastAutoGenerator.create(url, username, password)
.globalConfig(builder -> {
builder.author("cyfy") // 设置作者
// .enableSwagger() // 开启 swagger 模式
.outputDir(project_abs_path + "java"); // 指定输出目录
})
.packageConfig(builder ->
builder.parent("com.cyfy") // 设置父包名
.moduleName(moduleName) // 设置父包模块名
.pathInfo(Collections.singletonMap(OutputFile.xml, mapperLocation)) // 设置mapperXml生成路径
)
.strategyConfig(builder ->
builder.addInclude(tables) // 设置需要生成的表名
.addTablePrefix("sys_") // 设置过滤表前缀
)
.templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
.execute();
}
}
执行生成器代码后,项目新增对应代码文件
测试
编辑UserController.java
文件,编写获取用户表数据接口
import com.cyfy.sys.entity.User;
import com.cyfy.sys.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private IUserService userService;
@GetMapping("/all")
public List<User> getAllUser(){
List<User> list = userService.list();
return list;
}
}
修改项目启动器文件,增加@MapperScan
注解
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.cyfy.*.mapper")
public class XAdminServeApplication {
public static void main(String[] args) {
SpringApplication.run(XAdminServeApplication.class, args);
}
}
运行项目,访问接口是否正常
后端开发
1.公共响应类
在com/xxx目录下新建common/vo目录,并新建Result.java
文件处理返回给前端的数据
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> {
private Integer code;
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> fail(){
return new Result<>(20001,"fail",null);
}
public static<T> Result<T> fail(Integer code){
return new Result<>(code,"success",null);
}
public static<T> Result<T> fail(Integer code, String Message){
return new Result<>(code,Message,null);
}
public static<T> Result<T> fail( String Message){
return new Result<>(20001,Message,null);
}
}
测试:修改getAllUser()
方法
@GetMapping("/all")
public Result<List<User>> getAllUser(){
List<User> list = userService.list();
return Result.success(list,"查询成功");
}
运行效果
2.Redis配置
在common目录下新建tools目录,并新建MyRedisConfig.java
文件
package com.cyfy.common.tools;
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.Configurable;
import org.springframework.context.annotation.Bean;
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 javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.util.TimeZone;
@Configurable
public class MyRedisConfig {
@Resource
private RedisConnectionFactory factory;
@Bean
public RedisTemplate redisTemplate(){
RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();
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(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,false);
om.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS,false);
om.configure(MapperFeature.USE_ANNOTATIONS,false);
om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
om.setSerializationInclusion(JsonInclude.Include.NON_NULL);
serializer.setObjectMapper(om);
return redisTemplate;
}
}
3.跨域处理
在common/tools目录下,新建MyCorsConfig.java
文件,用于处理跨域请求问题
package com.cyfy.common.tools;
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(){
// 1.添加CORS配置信息
CorsConfiguration config = new CorsConfiguration();
// 1.1. 允许的域,不要写*,否则cookie就无法使用
config.addAllowedOrigin("http://localhost:8888"); // 这里填写请求的前端服务器
// 1.2. 是否发送cookie信息
config.setAllowCredentials(true);
// 1.3. 允许的请求方式,常用方式有GET、POST
config.addAllowedMethod("OPTIONS");
config.addAllowedMethod("HEAD");
config.addAllowedMethod("GET");
config.addAllowedMethod("PUT");
config.addAllowedMethod("POST");
config.addAllowedMethod("DELETE");
config.addAllowedMethod("PATCH");
// 1.4. 允许的头信息
config.addAllowedHeader("*");
// 2.添加映射路径,这里拦截一切请求
UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource();
configSource.registerCorsConfiguration("/**",config);
// 3.返回新的CorsFilter
return new CorsFilter(configSource);
}
}
也可在控制器类上加@CrossOrigin
注解处理,减少配置类
4.mybatis-plus拦截器
在common/tools目录下,新建MpConfig.java
文件
package com.cyfy.common.tools;
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;
}
}
5.用户相关接口
编写UserController.java
文件
package com.cyfy.sys.controller;
import com.cyfy.common.vo.Result;
import com.cyfy.sys.entity.User;
import com.cyfy.sys.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
/**
* 前端控制器
*/
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private IUserService userService;
/**
* 用户登录接口
*/
@PostMapping("/login")
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.fail(20002,"用户名或密码错误");
}
/**
* 用户信息查询接口
*/
@GetMapping("/info")
public Result<Map<String,Object>> getUserInfo(@RequestParam("token") String token){
Map<String,Object> data = userService.getUserInfo(token);
if(data != null){
return Result.success(data);
}
return Result.fail(20003,"登录信息已失效,请重新登录");
}
/**
* 用户注销接口
*/
@PostMapping("/logout")
public Result<?> logout(@RequestHeader("X-Token") String token){
userService.logout(token);
return Result.success();
}
/**
* 获取用户列表接口
*/
@GetMapping("/list")
public Result<Map<String,Object>> getUserList(@RequestParam(value = "username",required = false) String username,
@RequestParam(value = "phone",required = false) String phone,
@RequestParam(value = "pageNo") Long pageNo,
@RequestParam(value = "pageSize") Long pageSize){
Map<String, Object> data = userService.getUserList(username,phone,pageNo,pageSize);
return Result.success(data);
}
@PostMapping("/add")
public Result<?> addUser(@RequestBody User user){
if(userService.addUser(user)) {
return Result.success("新增用户成功");
}
return Result.fail("新增用户失败");
}
@PutMapping("/upd")
public Result<?> updateUser(@RequestBody User user){
if(userService.updateUser(user)) {
return Result.success("修改用户成功");
}
return Result.fail("修改用户失败");
}
@GetMapping("/{id}")
public Result<User> getUserById(@PathVariable("id")Integer id){
User data = userService.getUserById(id);
return Result.success(data);
}
@DeleteMapping("/{id}")
public Result<User> deleteUserById(@PathVariable("id")Integer id){
if(userService.deleteUserById(id)){
return Result.success("删除用户成功");
}
return Result.fail("删除用户失败");
}
}
编写IUserService.java
文件
package com.cyfy.sys.service;
import com.cyfy.sys.entity.User;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.Map;
/**
* 服务类
*/
public interface IUserService extends IService<User> {
// 用户登录
Map<String,Object> login(User user);
// 根据token获取用户信息
Map<String, Object> getUserInfo(String token);
// 注销登录
void logout(String token);
// 获取用户列表
Map<String, Object> getUserList(String username,String phone,Long pageNo,Long pageSize);
// 添加用户
boolean addUser(User user);
// 修改用户
boolean updateUser(User user);
// 根据id查询用户
User getUserById(Integer id);
// 根据id删除指定用户
boolean deleteUserById(Integer id);
}
编写UserServiceImpl.java
文件
package com.cyfy.sys.service.impl;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.cyfy.sys.entity.User;
import com.cyfy.sys.mapper.UserMapper;
import com.cyfy.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.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* 服务实现类
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Resource
private RedisTemplate redisTemplate;
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public Map<String, Object> login(User user) {
// 根据用户名查询
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getUsername,user.getUsername());
// 根据username查询是否存在对应用户
User loginUser = this.baseMapper.selectOne(wrapper);
System.out.println(user.getPassword());
System.out.println(loginUser.getPassword());
// 结果不为空时,且加密密码正确:生成token,并将用户信息存入redis
if(loginUser != null && passwordEncoder.matches(user.getPassword(),loginUser.getPassword())){
// 这里暂时使用UUID,最终需要使用jwt
String key = "user" + UUID.randomUUID();
loginUser.setPassword(null); // 将密码设置为空,不存入redis
// 存入redis
redisTemplate.opsForValue().set(key,loginUser,30, TimeUnit.MINUTES);
// 返回处理好的token
Map<String,Object> data = new HashMap<>();
data.put("token",key);
return data;
}
return null;
}
@Override
public Map<String, Object> getUserInfo(String token) {
// 根据Token从Redis中获取用户信息
Object obj = redisTemplate.opsForValue().get(token);
if(obj != null){
// 将字符串转为User对象
User loginUser = JSON.parseObject(JSON.toJSONString(obj),User.class);
Map<String,Object> data = new HashMap<>();
data.put("name",loginUser.getUsername());
data.put("avatar",loginUser.getAvater());
// 通过ID查询用户角色
List<String> roleList = this.baseMapper.getRoleNameByUserId(loginUser.getId());
data.put("roles",roleList);
return data;
}
return null;
}
@Override
public void logout(String token) {
// 从redis中清除指定token
redisTemplate.delete(token);
}
@Override
public Map<String, Object> getUserList(String username,String phone,Long pageNo,Long pageSize) {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(StringUtils.hasLength(username),User::getUsername,username);
wrapper.eq(StringUtils.hasLength(phone),User::getPhone,phone);
// 分页处理
Page<User> page = new Page<>(pageNo,pageSize);
this.page(page,wrapper);
Map<String,Object> data = new HashMap<>();
data.put("total",page.getTotal());
data.put("row",page.getRecords());
return data;
}
@Override
public boolean addUser(User user) {
// 根据用户名查询
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getUsername,user.getUsername());
// 根据username查询是否存在对应用户
User loginUser = this.baseMapper.selectOne(wrapper);
// 如果用户名存在,返回false
if(loginUser != null){
return false;
}
// 加密密码
user.setPassword(passwordEncoder.encode(user.getPassword()));
return this.save(user);
}
@Override
public boolean updateUser(User user) {
// 加密密码
user.setPassword(null);
return this.updateById(user);
}
@Override
public User getUserById(Integer id) {
User user = this.getById(id);
user.setPassword(null);
return user;
}
@Override
public boolean deleteUserById(Integer id) {
// 先查询用户是否存在
if(this.getById(id) != null){
return this.removeById(id);
}
return false;
}
}
编写UserMapper.java
文件
package com.cyfy.sys.mapper;
import com.cyfy.sys.entity.User;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import java.util.List;
/**
* Mapper 接口
*/
public interface UserMapper extends BaseMapper<User> {
List<String> getRoleNameByUserId(Integer id);
}
编写UserMapper.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.cyfy.sys.mapper.UserMapper">
<select id="getRoleNameByUserId" parameterType="Integer" resultType="String">
SELECT
b.`role_name`
FROM sys_user_role a, sys_role b
WHERE
a.role_id = b.`role_id` AND a.user_id = #{userId}
</select>
</mapper>
修改XAdminServeApplication.java
文件,加个加密方法
package com.cyfy;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@SpringBootApplication
@MapperScan("com.cyfy.*.mapper")
public class XAdminServeApplication {
public static void main(String[] args) {
SpringApplication.run(XAdminServeApplication.class, args);
}
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
使用Postman测试接口(记得开启redis)
前端框架准备
1.脚手架搭建
详见【Vue】vue-admin-template项目搭建,需保持node版本一致,可以使用nvm进行版本管理
2.使用VS Code打开项目
安装依赖:npm install --no-fund
运行项目:npm run dev
3.修改后端请求接口
修改.env.development
文件
# just a flag
ENV = 'development'
# base api
VUE_APP_BASE_API = 'http://localhost:8080'
修改vue.config.js
文件,注释使用模拟数据
// before: require('./mock/mock-server.js') // 模拟数据
测试:修改src/api/user.js
文件,更正请求url
import request from '@/utils/request'
export function login(data) {
return request({
url: '/user/login',
method: 'post',
data
})
}
export function getInfo(token) {
return request({
url: '/user/info',
method: 'get',
params: { token }
})
}
export function logout() {
return request({
url: '/user/logout',
method: 'post'
})
}
运行效果:可通过数据库的账号登录
前端开发
1.登录页修改
修改src/views/login目录下的index.vue
文件(没啥修改内容,就是将英文内容改为中文,稍微调整一下布局)
<template>
<div class="login-container">
<el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form" auto-complete="on" label-position="left">
<div class="title-container">
<h3 class="title">欢迎登录神盾局管理系统</h3>
</div>
<el-form-item prop="username">
<span class="svg-container">
<svg-icon icon-class="user" />
</span>
<el-input
ref="username"
v-model="loginForm.username"
placeholder="用户名"
name="username"
type="text"
tabindex="1"
auto-complete="on"
/>
</el-form-item>
<el-form-item prop="password">
<span class="svg-container">
<svg-icon icon-class="password" />
</span>
<el-input
:key="passwordType"
ref="password"
v-model="loginForm.password"
:type="passwordType"
placeholder="密码"
name="password"
tabindex="2"
auto-complete="on"
@keyup.enter.native="handleLogin"
/>
<span class="show-pwd" @click="showPwd">
<svg-icon :icon-class="passwordType === 'password' ? 'eye' : 'eye-open'" />
</span>
</el-form-item>
<el-button :loading="loading" type="primary" style="width:100%;margin-bottom:30px;" @click.native.prevent="handleLogin">登录</el-button>
</el-form>
</div>
</template>
<script>
import { validUsername } from '@/utils/validate'
export default {
name: 'Login',
data() {
const validateUsername = (rule, value, callback) => {
if (!validUsername(value)) {
callback(new Error('请输入正确的用户名'))
} else {
callback()
}
}
const validatePassword = (rule, value, callback) => {
if (value.length < 6) {
callback(new Error('密码不能少于6位'))
} else {
callback()
}
}
return {
loginForm: {
username: '',
password: ''
},
loginRules: {
username: [{ required: true, trigger: 'blur', validator: validateUsername }],
password: [{ required: true, trigger: 'blur', validator: validatePassword }]
},
loading: false,
passwordType: 'password',
redirect: undefined
}
},
watch: {
$route: {
handler: function(route) {
this.redirect = route.query && route.query.redirect
},
immediate: true
}
},
methods: {
showPwd() {
if (this.passwordType === 'password') {
this.passwordType = ''
} else {
this.passwordType = 'password'
}
this.$nextTick(() => {
this.$refs.password.focus()
})
},
handleLogin() {
this.$refs.loginForm.validate(valid => {
if (valid) {
this.loading = true
this.$store.dispatch('user/login', this.loginForm).then(() => {
this.$router.push({ path: this.redirect || '/' })
this.loading = false
}).catch(() => {
this.loading = false
})
} else {
console.log('error submit!!')
return false
}
})
}
}
}
</script>
<style lang="scss">
/* 修复input 背景不协调 和光标变色 */
/* Detail see https://github.com/PanJiaChen/vue-element-admin/pull/927 */
$bg:#283443;
$light_gray:#fff;
$cursor: #fff;
@supports (-webkit-mask: none) and (not (cater-color: $cursor)) {
.login-container .el-input input {
color: $cursor;
}
}
/* reset element-ui css */
.login-container {
.el-input {
display: inline-block;
height: 47px;
width: 85%;
input {
background: transparent;
border: 0px;
-webkit-appearance: none;
border-radius: 0px;
padding: 12px 5px 12px 15px;
color: $light_gray;
height: 47px;
caret-color: $cursor;
&:-webkit-autofill {
box-shadow: 0 0 0px 1000px $bg inset !important;
-webkit-text-fill-color: $cursor !important;
}
}
}
.el-form-item {
border: 1px solid rgba(255, 255, 255, 0.1);
background: rgba(0, 0, 0, 0.1);
border-radius: 5px;
color: #454545;
}
}
</style>
<style lang="scss" scoped>
$bg:#2d3a4b;
$dark_gray:#889aa4;
$light_gray:#eee;
.login-container {
min-height: 100%;
width: 100%;
background-color: $bg;
overflow: hidden;
display: flex; // 布局类型:弹性布局
align-items: center; // 调整元素在侧轴的对齐方式:居中
.login-form {
position: relative;
width: 520px;
max-width: 100%;
padding: 35px 50px 20px; // 内边距:上 中 下
margin: 0 auto; // 外边距
overflow: hidden;
background-color: #283443; // 设置背景色
border-radius: 8px; // 设置边框圆角
opacity: 0.9; // 设置透明度
}
.tips {
font-size: 14px;
color: #fff;
margin-bottom: 10px;
span {
&:first-of-type {
margin-right: 16px;
}
}
}
.svg-container {
padding: 6px 5px 6px 15px;
color: $dark_gray;
vertical-align: middle;
width: 30px;
display: inline-block;
}
.title-container {
position: relative;
.title {
font-size: 26px;
color: $light_gray;
margin: 0px auto 40px auto;
text-align: center;
font-weight: bold;
}
}
.show-pwd {
position: absolute;
right: 10px;
top: 7px;
font-size: 16px;
color: $dark_gray;
cursor: pointer;
user-select: none;
}
}
</style>
运行效果
2.菜单初始化
在src/views目录下创建sys模块目录、test模块目录(充数,暂不实现)
在sys模块目录下创建user/index.vue
、role/index.vue
两个组件文件,test模块目录下创建test1.vue
、test2.vue
、test3.vue
修改src/router/index.js
文件,修改路由配置
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
/* Layout */
import Layout from '@/layout'
export const constantRoutes = [
{
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: 'sysMange',
meta: { title: '系统管理', icon: 'el-icon-s-help' },
children: [
{
path: 'user',
name: 'User',
component: () => import('@/views/sys/user/index'),
meta: { title: '用户管理', icon: 'table' }
},
{
path: 'role',
name: 'Role',
component: () => import('@/views/sys/role/index'),
meta: { title: '角色管理', icon: 'tree' }
}
]
},
{
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: '功能点一', icon: 'form' }
},
{
path: 'test2',
name: 'Test2',
component: () => import('@/views/test/test2'),
meta: { title: '功能点二', icon: 'form' }
},
{
path: 'test3',
name: 'Test3',
component: () => import('@/views/test/test3'),
meta: { title: '功能点三', icon: 'form' }
},
]
},
// 404 page must be placed at the end !!!
{ path: '*', redirect: '/404', hidden: true }
]
const createRouter = () => new Router({
// mode: 'history', // require service support
scrollBehavior: () => ({ y: 0 }),
routes: constantRoutes
})
const router = createRouter()
// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
export function resetRouter() {
const newRouter = createRouter()
router.matcher = newRouter.matcher // reset router
}
export default router
运行效果
3.标签栏导航
3.1.复制对应文件到对应目录
@/layout/components/TagsView
@/store/modules/tagsView.js
@/store/modules/permission.js
3.2.修改@/layoutcomponents/AppMain.vue
文件
<template>
<section class="app-main">
<transition name="fade-transform" mode="out-in">
<!-- 加入标签栏组件 -->
<keep-alive :include="cachedViews">
<router-view :key="key" />
</keep-alive>
</transition>
</section>
</template>
<script>
export default {
name: 'AppMain',
computed: {
key() {
return this.$route.path
},
cachedViews(){
return this.$store.state.tagsView.cachedViews
}
}
}
</script>
3.3.修改@store/getters.js
文件
const getters = {
sidebar: state => state.app.sidebar,
device: state => state.app.device,
token: state => state.user.token,
avatar: state => state.user.avatar,
name: state => state.user.name,
visitedViews: state => state.tagsView.visitedViews,
cachedViews: state => state.tagsView.cachedViews,
permission_routes: state => state.permission.routes,
}
export default getters
3.4.修改@layout/components/index.js
文件
export { default as Navbar } from './Navbar'
export { default as Sidebar } from './Sidebar'
export { default as AppMain } from './AppMain'
export { default as TagsView } from './TagsView/index.vue'
3.5.修改@store/index.js
文件
import Vue from 'vue'
import Vuex from 'vuex'
import getters from './getters'
import app from './modules/app'
import settings from './modules/settings'
import user from './modules/user'
import tagsView from './modules/tagsView'
import permission from './modules/permission'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
app,
settings,
user,
tagsView,
permission
},
getters
})
export default store
3.6.Affix固钉
修改@/router/index.js
文件,在需要固定的标签页中添加affix: true
属性
{
path: '/',
component: Layout,
redirect: '/dashboard',
children: [{
path: 'dashboard',
name: 'Dashboard',
component: () => import('@/views/dashboard/index'),
meta: { title: '首页', icon: 'dashboard' ,affix: true }
}]
},
运行效果
4.用户管理页实现
编写views/sys/user/index.vue
文件
<template>
<div>
<!-- 搜索栏 -->
<el-card class="search">
<el-row>
<el-col :span="20">
<el-input v-model="searchModel.username" placeholder="用户名" clearable />
<el-input v-model="searchModel.phone" placeholder="电话" clearable />
<el-button @click="getUserList" type="primary" round icon="el-icon-search">查询</el-button>
</el-col>
<el-col :span="4" align="right">
<el-button @click="openUserVisible(null)" 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 label="#" width="80">
<template slot-scope="scope">
{{ (searchModel.pageNo - 1) * searchModel.pageSize + scope.$index + 1 }}
</template>
</el-table-column>
<el-table-column prop="id" label="用户ID" width="80" />
<el-table-column prop="username" label="用户名" width="180" />
<el-table-column prop="phone" label="电话" width="180" />
<el-table-column prop="status" label="状态" width="180" >
<template slot-scope="scope">
<el-tag v-if="scope.row.status === 1">正常</el-tag>
<el-tag v-else type="danger">禁用</el-tag>
</template>
</el-table-column>
<el-table-column prop="email" label="电子邮件" />
<el-table-column label="操作" width="180">
<template slot-scope="scope">
<el-button @click="openUserVisible(scope.row.id)" type="primary" icon="el-icon-edit" size="mini" circle />
<el-button @click="deleteUser(scope.row)" type="danger" icon="el-icon-delete" size="mini" circle />
</template>
</el-table-column>
</el-table>
</el-card>
<!-- 分页组件 -->
<el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange"
:current-page="searchModel.pageNo" :page-sizes="[5, 10, 20, 50]" :page-size="searchModel.pageSize"
layout="total, sizes, prev, pager, next, jumper" :total="total">
</el-pagination>
<!-- 用户信息编辑对话框 -->
<el-dialog @close="clearUserForm" :title="title" :visible.sync="userFormVisible">
<el-form :model="userForm" ref="userForm" :rules="rules">
<el-form-item label="用户名" prop="username" :label-width="formLabelWidth">
<el-input v-model="userForm.username" autocomplete="off"></el-input>
</el-form-item>
<el-form-item v-if="userForm.id == null || userForm.id === undefined" label="登录密码" prop="password" :label-width="formLabelWidth">
<el-input type="password" v-model="userForm.password" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="联系电话" :label-width="formLabelWidth">
<el-input v-model="userForm.phone" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="状态" :label-width="formLabelWidth">
<el-switch
v-model="userForm.status"
:active-value="1"
:inactive-value="0"
></el-switch>
</el-form-item>
<el-form-item label="电子邮件" prop="email" :label-width="formLabelWidth">
<el-input v-model="userForm.email" autocomplete="off"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="userFormVisible = false">取 消</el-button>
<el-button type="primary" @click="saveUserFrom">确 定</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import userApi from '@/api/userManage'
export default {
components: {
},
data() {
// 自定义验证规则
var checkEmail = (rule, value, callback)=>{
var reg = /^[a-zA-Z0-9]+([-_.][a-zA-Z0-9]+)*@[a-zA-Z0-9]+([-_.][a-zA-Z0-9]+)*\.[a-z]{2,}$/
if(!reg.test(value)){
return callback(new Error('电子邮箱格式错误'));
}
callback();
}
return {
total: 0,
searchModel: {
username: "",
phone: "",
pageNo: 1,
pageSize: 10,
},
userList: [],
title:"",
userFormVisible: false,
userForm:{},
formLabelWidth: '130px',
rules:{
username:[
{required:true, message: '请输入用户名', trigger: 'blur'},
{min:5,max: 12, message: '长度在5到12个字符', trigger: 'blur'},
],
password:[
{required:true, message: '请输入初始登录密码', trigger: 'blur'},
{min:6,max: 16, message: '长度在6到16个字符', trigger: 'blur'},
],
email:[
{required:true, message: '请输入电子邮箱', trigger: 'blur'},
{validator:checkEmail, trigger: 'blur'},
],
}
}
},
methods: {
// 删除用户
deleteUser(user){
this.$confirm(`你确认删除用户${user.username}?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
userApi.deleteUserById(user.id).then(response => {
this.$message({
type: 'success',
message: response.message
});
this.getUserList();
})
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
});
});
},
// 添加用户
saveUserFrom(){
// 触发表单验证
this.$refs.userForm.validate((valid)=>{
if(valid){
// 提交请求给后台
userApi.saveUser(this.userForm).then(response =>{
// 成功提示
this.$message({
message: response.message,
type: 'success'
});
// 关闭对话框
this.userFormVisible = false;
// 刷新表格
this.getUserList();
})
}else{
this.$message({
message: "数据提交错误",
type: 'error'
});
return false;
}
})
},
handleSizeChange(pageSize) {
this.searchModel.pageSize = pageSize;
this.getUserList();
},
handleCurrentChange(pageNo) {
this.searchModel.pageSize = pageNo;
this.getUserList();
},
getUserList() {
userApi.getUserList(this.searchModel).then(response => {
this.userList = response.data.row;
this.total = response.data.total;
});
},
openUserVisible(id) {
if(id === null){
this.title = "新增用户";
}else{
this.title = "修改用户";
// 根据id查询用户
userApi.getUserById(id).then(response =>{
this.userForm = response.data
})
}
this.userFormVisible = true;
},
clearUserForm(){
this.userForm = {}
this.$refs.userForm.clearValidate();
}
},
created() {
this.getUserList();
}
}
</script>
<style scoped>
.search .el-input {
width: 200px;
margin-right: 10px;
}
.el-dialog .el-input{
width: 85%;
}
</style>
在src/api目录下新建userManage.js
文件
import request from '@/utils/request'
export default{
getUserList(searchModel){
return request({
url: '/user/list',
method: 'get',
params :{
pageNo: searchModel.pageNo,
pageSize: searchModel.pageSize,
username: searchModel.username,
phone: searchModel.phone,
}
})
},
addUser(user){
return request({
url: '/user/add',
method: 'post',
data:user
})
},
saveUser(user){
if(user.id === null || user.id === undefined){
return this.addUser(user)
}else{
return this.updateUser(user);
}
},
updateUser(user){
return request({
url: '/user/upd',
method: 'put',
data: user
})
},
getUserById(id){
return request({
url: `/user/${id}`,
method: 'get'
})
},
deleteUserById(id){
return request({
url: `/user/${id}`,
method: 'delete'
})
},
}
5.用户管理页组件优化
当前页面所有内容都集中在index.vue中,代码多看起码繁杂,不适合后续拓展及优化,所有需内容按模块拆分成组件文件
在src/utils目录下新建eventbus.js
文件,实现组件之间数据交互的方法
import Vue from "vue"
const EventBus = new Vue();
Object.defineProperties(Vue.prototype,{
$bus:{
get(){
return EventBus;
}
}
})
在main.js
文件中引入eventbus
// 引入eventbus
import "./utils/eventbus"
在src/views/sys/user目录下新建components目录,用于存放当前页面的组件文件
新建SearchBar.vue
文件,用于存放搜索框部分
<template>
<!-- 搜索栏 -->
<el-card class="search">
<el-row>
<el-col :span="20">
<el-input v-model="searchContext.username" placeholder="用户名" clearable />
<el-input v-model="searchContext.phone" placeholder="电话" clearable />
<el-button @click="searchUser" type="primary" round icon="el-icon-search">查询</el-button>
</el-col>
<el-col :span="4" align="right">
<el-button @click="openUserVisible" type="primary" icon="el-icon-plus" circle></el-button>
</el-col>
</el-row>
</el-card>
</template>
<script>
export default {
data(){
return {
searchContext: {
username: "",
phone: "",
},
}
},
methods:{
searchUser(){
this.$bus.$emit("searchUser",this.searchContext)
},
openUserVisible(){
this.$bus.$emit("openUserVisible",null)
}
}
}
</script>
<style scoped>
.search .el-input {
width: 200px;
margin-right: 10px;
}
</style>
新建ResultList.vue
文件,用于存放结果展示部分
<template>
<!-- 结果列表 -->
<el-card>
<el-table :data="userList" stripe style="width:100%">
<el-table-column label="#" width="80">
<template slot-scope="scope">
{{ (searchModel.pageNo - 1) * searchModel.pageSize + scope.$index + 1 }}
</template>
</el-table-column>
<el-table-column prop="id" label="用户ID" width="80" />
<el-table-column prop="username" label="用户名" width="180" />
<el-table-column prop="phone" label="电话" width="180" />
<el-table-column prop="status" label="状态" width="180">
<template slot-scope="scope">
<el-tag v-if="scope.row.status === 1">正常</el-tag>
<el-tag v-else type="danger">禁用</el-tag>
</template>
</el-table-column>
<el-table-column prop="email" label="电子邮件" />
<el-table-column label="操作" width="180">
<template slot-scope="scope">
<el-button @click="openUserVisible(scope.row.id)" type="primary" icon="el-icon-edit" size="mini" circle />
<el-button @click="deleteUser(scope.row)" type="danger" icon="el-icon-delete" size="mini" circle />
</template>
</el-table-column>
</el-table>
</el-card>
</template>
<script>
import userApi from '@/api/userManage'
export default {
data() {
return {
userList: [],
searchModel: {
pageNo: 1,
pageSize: 10,
},
}
},
methods: {
// 删除用户
deleteUser(user) {
this.$confirm(`你确认删除用户${user.username}?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
userApi.deleteUserById(user.id).then(response => {
this.$message({
type: 'success',
message: response.message
});
this.getUserList();
})
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
});
});
},
// 获取数据
getUserList() {
userApi.getUserList(this.searchModel).then(response => {
this.userList = response.data.row;
this.$bus.$emit("getTotal",response.data.total)
});
},
// 修改用户
openUserVisible(id){
console.log(1111111111111);
this.$bus.$emit("openUserVisible",id)
}
},
mounted(){
// 查询数据
this.$bus.$on("searchUser",searchContext =>{
this.searchModel.username = searchContext.username;
this.searchModel.phone = searchContext.phone;
this.getUserList();
})
// 更新页面
this.$bus.$on("refresh",() =>{
this.getUserList();
})
// 分页
this.$bus.$on("page_turning",searchModel =>{
this.searchModel.pageNo = searchModel.pageNo;
this.searchModel.pageSize = searchModel.pageSize;
this.getUserList();
})
},
created() {
this.getUserList();
}
}
</script>
新建ResultPage.vue
文件,用于存放分页部分
<template>
<!-- 分页组件 -->
<el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange"
:current-page="searchModel.pageNo" :page-sizes="[5, 10, 20, 50]" :page-size="searchModel.pageSize"
layout="total, sizes, prev, pager, next, jumper" :total="total">
</el-pagination>
</template>
<script>
export default {
data() {
return {
total: 0,
searchModel: {
pageNo: 1,
pageSize: 10,
}
}
},
methods: {
handleSizeChange(pageSize) {
this.searchModel.pageSize = pageSize;
this.$bus.$emit("page_turning",this.searchModel)
},
handleCurrentChange(pageNo) {
this.searchModel.pageNo = pageNo;
this.$bus.$emit("page_turning",this.searchModel)
},
},
mounted(){
this.$bus.$on("getTotal",total =>{
this.total = total
})
}
}
</script>
新建UserDialog.vue
文件,用于存放用户信息编辑对话框部分
<template>
<div>
<!-- 用户信息编辑对话框 -->
<el-dialog @close="clearUserForm" :title="title" :visible.sync="userFormVisible">
<el-form :model="userForm" ref="userForm" :rules="rules">
<el-form-item label="用户名" prop="username" :label-width="formLabelWidth">
<el-input v-model="userForm.username" autocomplete="off"></el-input>
</el-form-item>
<el-form-item v-if="userForm.id == null || userForm.id === undefined" label="登录密码" prop="password"
:label-width="formLabelWidth">
<el-input type="password" v-model="userForm.password" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="联系电话" :label-width="formLabelWidth">
<el-input v-model="userForm.phone" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="状态" :label-width="formLabelWidth">
<el-switch v-model="userForm.status" :active-value="1" :inactive-value="0"></el-switch>
</el-form-item>
<el-form-item label="电子邮件" prop="email" :label-width="formLabelWidth">
<el-input v-model="userForm.email" autocomplete="off"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="userFormVisible = false">取 消</el-button>
<el-button type="primary" @click="saveUserFrom">确 定</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import userApi from '@/api/userManage'
export default {
data() {
// 自定义验证规则
var checkEmail = (rule, value, callback) => {
var reg = /^[a-zA-Z0-9]+([-_.][a-zA-Z0-9]+)*@[a-zA-Z0-9]+([-_.][a-zA-Z0-9]+)*\.[a-z]{2,}$/
if (!reg.test(value)) {
return callback(new Error('电子邮箱格式错误'));
}
callback();
}
return {
title: "",
userFormVisible: false,
userForm: {},
formLabelWidth: '130px',
rules: {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: 5, max: 12, message: '长度在5到12个字符', trigger: 'blur' },
],
password: [
{ required: true, message: '请输入初始登录密码', trigger: 'blur' },
{ min: 6, max: 16, message: '长度在6到16个字符', trigger: 'blur' },
],
email: [
{ required: true, message: '请输入电子邮箱', trigger: 'blur' },
{ validator: checkEmail, trigger: 'blur' },
],
}
}
},
methods: {
// 添加或修改用户
saveUserFrom() {
// 触发表单验证
this.$refs.userForm.validate((valid) => {
if (valid) {
// 提交请求给后台
userApi.saveUser(this.userForm).then(response => {
// 成功提示
this.$message({
message: response.message,
type: 'success'
});
// 关闭对话框
this.userFormVisible = false;
// 刷新表格
this.$bus.$emit("refresh")
})
} else {
this.$message({
message: "数据提交错误",
type: 'error'
});
return false;
}
})
},
// 根据是否存在id显示弹出信息
openUserVisible(id) {
if (id === null) {
this.title = "新增用户";
} else {
this.title = "修改用户";
// 根据id查询用户
userApi.getUserById(id).then(response => {
this.userForm = response.data
})
}
this.userFormVisible = true;
},
// 清除弹窗内容
clearUserForm() {
this.userForm = {}
this.$refs.userForm.clearValidate();
}
},
mounted(){
this.$bus.$on("openUserVisible",id =>{
console.log(22222222);
this.openUserVisible(id)
})
}
}
</script>
<style scoped>
.el-dialog .el-input{
width: 85%;
}
</style>
修改user/index.vue
文件
<template>
<div>
<SearchBar/>
<ResultList/>
<ResultPage/>
<UserDialog/>
</div>
</template>
<script>
import SearchBar from './components/SearchBar'
import ResultList from './components/ResultList'
import ResultPage from './components/ResultPage'
import UserDialog from './components/UserDialog'
export default {
components: {
SearchBar,ResultList,ResultPage,UserDialog
}
}
</script>
运行效果正常
报错处理
1.使用TagsView组件控制台报错
报错截图
报错原因
未将TagsView所依赖的组件permission组件注册到store中,导致TagsView组件在找permission.routes时没找到