【SpringBoot+Vue】x-admin管理系统跟做
技术栈
前端技术 | 说明 |
---|---|
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:8080spring:datasource:username: rootpassword: 123456url: jdbc:mysql:///my_demoredis:port: 6379host: localhostlogging:level:com.cyfy: debug# 设置逻辑删除字段
mybatis-plus:global-config:db-config:logic-delete-field: deletedlogic-delete-value: 1logic-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 {@Autowiredprivate 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 {@Resourceprivate RedisConnectionFactory factory;@Beanpublic 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 {@Beanpublic 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、POSTconfig.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.返回新的CorsFilterreturn 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 {@Beanpublic 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 {@Autowiredprivate 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 {@Resourceprivate RedisTemplate redisTemplate;@Autowiredprivate PasswordEncoder passwordEncoder;@Overridepublic 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,并将用户信息存入redisif(loginUser != null && passwordEncoder.matches(user.getPassword(),loginUser.getPassword())){// 这里暂时使用UUID,最终需要使用jwtString key = "user" + UUID.randomUUID();loginUser.setPassword(null); // 将密码设置为空,不存入redis// 存入redisredisTemplate.opsForValue().set(key,loginUser,30, TimeUnit.MINUTES);// 返回处理好的tokenMap<String,Object> data = new HashMap<>();data.put("token",key);return data;}return null;}@Overridepublic 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;}@Overridepublic void logout(String token) {// 从redis中清除指定tokenredisTemplate.delete(token);}@Overridepublic 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;}@Overridepublic boolean addUser(User user) {// 根据用户名查询LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();wrapper.eq(User::getUsername,user.getUsername());// 根据username查询是否存在对应用户User loginUser = this.baseMapper.selectOne(wrapper);// 如果用户名存在,返回falseif(loginUser != null){return false;}// 加密密码user.setPassword(passwordEncoder.encode(user.getPassword()));return this.save(user);}@Overridepublic boolean updateUser(User user) {// 加密密码user.setPassword(null);return this.updateById(user);}@Overridepublic User getUserById(Integer id) {User user = this.getById(id);user.setPassword(null);return user;}@Overridepublic 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">SELECTb.`role_name`FROM sys_user_role a, sys_role bWHEREa.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);}@Beanpublic 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-inputref="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 = truethis.$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 supportscrollBehavior: () => ({ 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-switchv-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时没找到
相关文章:
【SpringBoot+Vue】x-admin管理系统跟做
技术栈 前端技术说明Vue前端框架Vuex全局状态管理框架ElementUI前端UI框架Axios前端HTTP框架vue-element-admin项目脚手架 后端技术说明SpringBoot容器MVC框架MyBatisORM框架MyBatis-plusMyBatis增强工具Redis非关系型数据库 数据库准备 SET NAMES utf8mb4; SET FOREIGN_KE…...
计算机网络 —— HTTP 协议(详解)
前一篇文章:网页版五子棋—— WebSocket 协议_网页可以实现websocket吗-CSDN博客 目录 前言 一、HTTP 协议简介 二、HTTP 协议格式 1.抓包工具的使用 2.抓包工具的原理 3.抓包结果 4.HTTP协议格式总结 三、HTTP 请求 1. URL (1)UR…...
MacOS 配置github密钥
MacOS 配置github密钥 1. 生成GitHub的SSH密钥对 ssh-keygen -t ed25519 -C "xxxxxxx.com" -f ~/.ssh/id_ed25519_github 其中 xxxxxxxxxxx.com 是注册github、gitee和gitlab的绑定账号的邮箱 -t ed25519:生成密钥的算法为ed25519(ed25519比rsa速度快&…...
我的第一个创作纪念日 —— 梦开始的地方
前言 时光荏苒,转眼间,我已经在CSDN这片技术沃土上耕耘了365天 今天,我迎来了自己在CSDN的第1个创作纪念日,这个特殊的日子不仅是对我过去努力的肯定,更是对未来持续创作的激励 机缘 回想起初次接触CSDN,那…...
float globalMapVIsualizationLeafSize; 的中文意思是什么
1.在visual studio 中新建文件 没有包含#include <string>头文件,也可以使用 str2.append(", C");吗? 在 Visual Studio 或任何其他 C 开发环境中,即使新建的文件中没有显式包含 #include <string> 头文件,…...
合规性要求对漏洞管理策略的影响
讨论漏洞管理中持续面临的挑战,包括确定漏洞的优先级和解决修补延迟问题。 介绍合规性要求以及自动化如何简化漏洞管理流程。 您认为为什么尽管技术不断进步,但优先考虑漏洞和修补延迟等挑战仍然存在? 企业基础设施日益复杂,攻…...
【Delphi】modbus-TCP 协议库
在日常开发中,也会遇到使用modbus的部件,比如温度控制器、读卡器等等,那么使用Delphi开发,也就必须遵守modbus-TCP协议,如果自己使用TCP控件写也没有问题,不过如果有开源的三方库,别人已经调试过…...
乐橙云小程序插件接入HbuilderX
乐橙插件使用: 1.配置app.json文件,uniapp中在mainfest.json中配置 https://uniapp.dcloud.net.cn/collocation/manifest.html#mp-weixin ** 2、集成插件页面.json文件 ** uniapp在 pages.json 对应页面的 style -> usingComponents 引入组件&…...
Python世界:复制粘贴?没那么简单!浅谈深拷贝与浅拷贝
Python世界:复制粘贴?没那么简单!浅谈深拷贝与浅拷贝 问题引入切片拷贝是深还是浅?深拷贝和浅拷贝到底有啥区别?本文小结 问题引入 Python实现中,最近遇到个小问题,对其中的拷贝理解更深了些&a…...
vue3实现el-table的拖拽
我这里使用的是 sortablejs 插件; 安装命令: npm install sortablejs --save 注意点: 你的表格数据中要有id作为key去使用; <div class"draggable"><el-table row-key"id" :data"form.tableData" style"width: 100%" max-…...
Apache SSI 远程命令执行漏洞
目录 1、漏洞描述 2、访问页面 3、dirsearch工具爆破出上传目录 编辑 4、登录后台 5、上传shell.shtml一句话木马文件 6、拿到flag 1、漏洞描述 在测试任意文件上传漏洞的时候,目标服务端可能不允许上传php后缀的文件。如果目标服务器开启了SSI与CGI支持&a…...
高效集成:将聚水潭数据导入MySQL的实战案例
聚水潭数据集成到MySQL:店铺信息查询案例分享 在数据驱动的业务环境中,如何高效、准确地实现跨平台的数据集成是每个企业面临的重要挑战。本文将聚焦于一个具体的系统对接集成案例——将聚水潭的店铺信息查询结果集成到MySQL数据库中,以供BI…...
Elasticsearch面试内容整理-面试注意事项
在准备 Elasticsearch 面试时,除了掌握技术知识外,还需要注意如何有效展示你的技能和经验。以下是一些 Elasticsearch 面试的注意事项和建议: 掌握基础概念 在面试中,面试官通常会首先评估你对 Elasticsearch 基础概念的理解,包括集群架构、分片、副本、节点类型等。这些是…...
学习threejs,使用specularMap设置高光贴图
👨⚕️ 主页: gis分享者 👨⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅! 👨⚕️ 收录于专栏:threejs gis工程师 文章目录 一、🍀前言1.1 ☘️THREE.MeshPhongMaterial高…...
Cannot resolve symbol ‘ActivityThread‘ | Android 语法
背景 ActivityThread 是 Android 系统内部使用的一个类,它位于 android.app 包中,但在 Android SDK 的公共 API 中并没有公开。 由于 ActivityThread 是隐藏的内部类,因此在编写单元测试或功能开发时,无法直接引用它。可以使用反射来访问内部 API,或者使用依赖注入的方式…...
Cypress内存溢出奔溃问题汇总
内存溢出报错信息 <--- Last few GCs ---> [196:0xe58001bc000] 683925 ms: Scavenge 1870.7 (1969.9) -> 1865.6 (1969.9) MB, 6.07 / 0.00 ms (average mu 0.359, current mu 0.444) task; [196:0xe58001bc000] 683999 ms: Scavenge 1872.4 (1969.9) -> 1867.1…...
debian 11 虚拟机环境搭建过坑记录
目录 安装过程系统配置修改 sudoers 文件网络配置换源安装桌面mount nfs 挂载安装复制功能tab 无法补全其他安装 软件配置eclipse 配置git 配置老虚拟机硬盘挂载 参考 原来去 debian 官网下载了一个最新的 debian 12,安装后出现包依赖问题,搞了半天&…...
【k8s深入学习之 event 记录】初步了解 k8s event 记录机制
event 事件记录初始化 一般在控制器都会有如下的初始化函数,初始化 event 记录器等参数 1. 创建 EventBroadcaster record.NewBroadcaster(): 创建事件广播器,用于记录和分发事件。StartLogging(klog.Infof): 将事件以日志的形式输出。StartRecording…...
InterHub:为自动驾驶提供密集互动事件的自然驾驶轨迹数据集
InterHub 是一个为自动驾驶领域设计的自然驾驶轨迹数据集,它通过深入挖掘自然驾驶记录中的密集互动事件而构建。 InterHub 的特点在于其形式化的方法,这使得数据集能够精确描述和提取多智能体之间的互动事件,揭示了现有自动驾驶解决方案的局限…...
鸿蒙Next星河版基础用例
目录: 1、鸿蒙箭头函数的写法2、鸿蒙数据类型的定义3、枚举的定义以及使用4、position绝对定位及层级zIndex5、字符串的拼接转换以及数据的处理(1)字符串转数字(2)数字转字符串(3)布尔值转换情况(4)数组的增删改查 6、三元表达式7、鸿蒙for循环的几种写法7.1、基本用…...
ScribblePrompt 医学图像分割工具,三种标注方式助力图像处理
ScribblePrompt 的主要目标是简化医学图像的分割过程,这在肿瘤检测、器官轮廓描绘等应用中至关重要。相比依赖大量人工标注数据,该工具允许用户通过少量输入(例如简单的涂鸦或点位)来引导模型优化分割结果。这种方式减少了医学专家…...
PKO-LSSVM-Adaboost班翠鸟优化最小二乘支持向量机结合AdaBoost分类模型
PKO-LSSVM-Adaboost班翠鸟优化最小二乘支持向量机结合AdaBoost分类模型 目录 PKO-LSSVM-Adaboost班翠鸟优化最小二乘支持向量机结合AdaBoost分类模型效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.PKO-LSSVM-Adaboost班翠鸟优化最小二乘支持向量机结合AdaBoost分类模…...
5G学习笔记之随机接入
目录 1. 概述 2. MSG1 2.1 选择SSB 2.2 选择Preamble Index 2.3 选择发送Preamble的时频资源 2.4 确定RA-RNTI 2.5 确定发送功率 3. MSG2 4. MSG3 5. MSG4 6. 其它 6.1 切换中的随机接入 6.2 SI请求的随机接入 6.3 通过PDCCH order重新建立同步 1. 概述 随机接入…...
爬虫专栏第二篇:Requests 库实战:从基础 GET 到 POST 登录全攻略
简介:本文聚焦 Requests 库的强大功能与应用实战。首先介绍其安装步骤及版本选择要点,随后深入讲解 GET 请求,以百度页面为例,展示如何发起基本 GET 请求、巧妙添加 headers 与参数以精准搜索,以及正确设置 encoding 避…...
Android Studio更改项目使用的JDK
一、吐槽 过去,在安卓项目中配置JDK和Gradle的过程非常直观,只需要进入Android Studio的File菜单中的Project Structure即可进行设置,十分方便。 原本可以在这修改JDK: 但大家都知道,Android Studio的狗屎性能,再加…...
鸿蒙技术分享:Navigation页面管理-鸿蒙@fw/router框架源码解析(二)
本文是系列文章,其他文章见: 鸿蒙fw/router框架源码解析(一)-Router页面管理 鸿蒙fw/router框架源码解析(三)-Navigation页面容器封装 鸿蒙fw/router框架源码解析(四)-路由Hvigor插件…...
数据结构:树
树的基本定义: 树是一种数据结构,它是由n(n>1)个有限节点组成一个具有层次关系的集合。把它叫做 “树” 是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。它具有以下的特点: …...
矩阵sum,prod函数
s u m sum sum表示求和, p r o d prod prod表示求乘积 s u m sum sum函数 对于矩阵,可以对某一行或某一列求和,也可以对矩阵整体求和 s u m ( a , 1 ) sum(a,1) sum(a,1)计算每一列的和 s u m ( a , 2 ) sum(a,2) sum(a,2)计算每一行的和 计算矩阵整体…...
Cursor安装与使用,5分钟完成需求
Cursor简单介绍 Cursor是一款基于AI的代码编辑器,旨在帮助开发者更高效地编写和管理代码。它提供了智能代码补全、AI对话和跨文件编辑等创新功能。 一、安装下载 1、下载cursor:https://www.cursor.com/ 2、注册账号,直接拿自己的邮箱登录…...
嵌入式系统应用-LVGL的应用-平衡球游戏 part1
平衡球游戏 part1 1 平衡球游戏的界面设计2 界面设计2.1 背景设计2.2 球的设计2.3 移动球的坐标2.4 用鼠标移动这个球2.5 增加边框规则2.6 效果图 3 为小球增加增加动画效果3.1 增加移动效果代码3.2 具体效果图片 平衡球游戏 part2 第二部分文章在这里 1 平衡球游戏的界面设计…...
Vue基本语法
Options API 选项式/配置式api 需要在script中的export default一个对象对象中可以包含data、method、components等keydata是数据,数据必须是一个方法(如果是对象,会导致多组件的时候,数据互相影响,因为对象赋值后&…...
UIE与ERNIE-Layout:智能视频问答任务初探
内容来自百度飞桨ai社区UIE与ERNIE-Layout:智能视频问答任务初探: 如有侵权,请联系删除 1 环境准备 In [2] # 安装依赖库 !pip install paddlenlp --upgrade !pip install paddleocr --upgrade !pip install paddlespeech --upgrade In …...
Mac启动服务慢问题解决,InetAddress.getLocalHost().getHostAddress()慢问题。
项目启动5分钟,很明显有问题。像网上其他的提高jvm参数就不说了,应该不是这个问题,也就快一点。 首先找到自己的电脑名称(用命令行也行,只要能找到自己电脑名称就行,这里直接在共享里看)。 复制…...
Django 视图层
from django.shortcuts import render, HttpResponse, redirectfrom django.http import JsonResponse1. render: 渲染模板 def index(request):print(reverse(index))return render(request, "index.html")return render(request, index.html, context{name: lisi})…...
HickWall 详解
优质博文:IT-BLOG-CN 一、监控分类 【1】Tracing调用链: 【2】Logging日志: 【3】Metrics指标:在应用发布之后,会长时间存在的度量维度。某个接口的请求量、响应时间。 Metrics数据模型 二、Metirc 接入 【1】pom…...
开源的跨平台SQL 编辑器Beekeeper Studio
一款开源的跨平台 SQL 编辑器,提供 SQL 语法高亮、自动补全、数据表内容筛选与过滤、连接 Web 数据库、存储历史查询记录等功能。该编辑器支持 SQLite、MySQL、MariaDB、Postgres 等主流数据库,并兼容 Windows、macOS、Linux 等桌面操作系统。 项目地址…...
Linux应用层学习——Day4(进程处理)
system #include<stdio.h> #include<stdlib.h>int main(int argc, char const *argv[]) {//使用标准库函数创建子进程//int system (const char *__command);//const char *__command:使用linux命令直接创建一个子进程//return:成功返回0 失败返回失败编号int sys…...
起别名typedef
#include<stdio.h> //typedef int myType1; //typedef char myType2; typedef struct { int a; int b; }po; int main() { /*myType1 a5; myType2 bo; printf("%d\n",a); printf("%c\n",b);*/ po p;//不需要加struct关键…...
【Linux内核】ashmem pin/unpin
前言 在 Linux 内核的 ASHMEM(Android Shared Memory)实现中,pin 和 unpin 操作主要用于管理共享内存的生命周期和可用性。这些操作有助于确保在内存使用期间,特定的共享内存区域不会被回收或释放。 Pin 操作 定义 Pin 操作用…...
【docker】docker网络六种网络模式
Docker 网络模式总结 网络模式描述使用场景bridge默认的网络模式,容器之间通过虚拟网桥通信,容器与宿主机隔离。单机部署、本地开发、小型项目host容器与宿主机共享网络堆栈,容器直接使用宿主机的 IP 地址。高性能网络应用、日志处理、大量与…...
永磁同步电机谐波抑制算法(11)——基于矢量比例积分调节器(vector PI controller,VPI controller)的谐波抑制策略
1.前言 相比于传统的谐振调节器,矢量比例积分调节器(vector PI controller,VPI controller)多一个可调零点,能够实现电机模型的零极点对消。因此VPI调节器也被广泛应用于交流控制/谐波抑制中。 2.参考文献 [1] A. G…...
排序算法中稳定性的意义和作用
多关键字排序:当需要对数据进行多个关键字排序时,稳定性变得非常重要。例如,先按次要关键字排序,再按主要关键字排序。如果排序算法是稳定的,那么在按主要关键字排序后,次要关键字的顺序将被保留。保持关联…...
网站怎么防御https攻击
HTTPS攻击,它不仅威胁到网站的数据安全,还可能影响用户隐私和业务稳定运行。 HTTPS攻击主要分为以下几种类型: 1.SSL劫持:攻击者通过中间人攻击手段,篡改HTTPS流量,从而实现对数据的窃取或伪造。 2.中间人攻…...
gitignore 不起作用
.gitignore不起作用 文件已提交至远程仓库,已经被Git跟踪。清除缓存.gitignore位置可能不是与 .git隐藏文件夹同级目录。将文件移至同级目录缓存未清除 清除缓存 清楚git缓存步骤 进入项目路径 清除本地当前的Git缓存 git rm -r --cached . 应用.gitignore等本地…...
Hive学习基本概念
基本概念 hive是什么? Facebook 开源,用于解决海量结构化日志的数据统计。 基于Hadoop的一个数据仓库工具,可以将结构化的数据文件映射为一张表,并提供类SQL查询功能 本质是将HQL转化为MapReduce程序。 Hive处理的数据存储在H…...
在 Ubuntu 使用 fonts-noto-cjk 设置 Matplotlib 支持中文的完整教程
在 Ubuntu 使用 fonts-noto-cjk 设置 Matplotlib 支持中文的完整教程 1. 为什么需要配置中文字体?2. 安装 fonts-noto-cjk安装命令:检查字体安装是否成功 3. 配置 Matplotlib 支持中文3.1 手动加载字体3.2 设置全局字体(可选)修改…...
《C++ Primer Plus》学习笔记|第10章 对象和类 (24-12-2更新)
文章目录 10.3 类的构造函数和析构函数10.3.2 使用构造函数显式地调用构造函数隐式地调用构造函数使用对象指针 10.3.3默认构造函数10.3.4 析构函数析构函数示例 10.4 this指针三个const的作用 10.5 对象数组10.6 类作用域10.9 复习题1.什么是类?2.类如何实现抽象、…...
SpringMVC接收数据
一、访问路径设置: RequestMapping注解的作用就是将请求的URL地址和处理请求的方式(handler方法)关联起来,建立映射关系;SpringMVC接收到指定的请求,就会来找到在映射关系中对应的方法来处理这个请求 1.精准路径匹配: 在RequestMapping注解指定URL地址…...
Python数组拆分(array_split())
天行健,君子以自强不息;地势坤,君子以厚德载物。 每个人都有惰性,但不断学习是好好生活的根本,共勉! 文章均为学习整理笔记,分享记录为主,如有错误请指正,共同学习进步。…...
Git 使用总结
下载 git bash:http://git-scm.com/download/win 第一次使用 git 时,配置用户信息: git config --global user.email "your.emailexample.com" 从github仓库中下载项目到本地,修改后重新上传: git clone 项…...