通用权限管理系统PermissionAdmin
基于SpringBoot+Vue前后端分离通用权限管理后台系统PermissionAdmin
一、技术架构
总体采用前后端分离架构
后端:
主要开发框架:SpringBoot2x+MyBatisPlus3.4.1
开发语言:Java
开发技术:java技术、SpringBoot技术、MyBatisPlus代码生成器、MyBatisPlus分页技术、MyBatisPlus查询技术、Lombok技术、文件上传技术、screw数据库文档生成技术、kaptcha技术、mail邮箱验证码技术、Sa-token权限技术等
数据库:MySQL5.7
数据库测试工具:Navicat Premium 12
前端:
主要开发框架:Node.js16+Vue2x
开发语言:vue2
开发技术:vue技术、vue-router技术、axios技术、vuex技术、路由守卫技术、element-ui技术、echarts技术、邮箱验证码技术等
二、数据库搭建
1.数据库设计文档
数据库名: permissionadmin
文档版本: 1.0.0
文档描述: 数据库设计文档生成
表名 | 说明 |
---|---|
menu | 菜单表 |
role | 角色表 |
role_menu | 角色-菜单表 |
user | 用户表 |
user_role | 用户-角色表 |
表名: menu
说明:
数据列:
序号 | 名称 | 数据类型 | 长度 | 小数位 | 允许空值 | 主键 | 默认值 | 说明 |
---|---|---|---|---|---|---|---|---|
1 | id | bigint | 20 | 0 | N | Y | 菜单ID | |
2 | parent_id | bigint | 20 | 0 | Y | N | 父菜单ID,一级菜单为0 | |
3 | name | varchar | 64 | 0 | N | N | 菜单名称 | |
4 | path | varchar | 255 | 0 | Y | N | 菜单URL | |
5 | perms | varchar | 255 | 0 | Y | N | 授权(多个用逗号分隔,如:user:list,user:create) | |
6 | component | varchar | 255 | 0 | Y | N | 菜单组件 | |
7 | type | int | 10 | 0 | N | N | 类型0:目录1:菜单2:按钮 | |
8 | icon | varchar | 32 | 0 | Y | N | 菜单图标 | |
9 | orderNum | int | 10 | 0 | Y | N | 排序 | |
10 | status | int | 10 | 0 | N | N | 1 | 菜单状态 |
表名: role
说明:
数据列:
序号 | 名称 | 数据类型 | 长度 | 小数位 | 允许空值 | 主键 | 默认值 | 说明 |
---|---|---|---|---|---|---|---|---|
1 | id | bigint | 20 | 0 | N | Y | 角色ID | |
2 | name | varchar | 64 | 0 | N | N | 角色名称 | |
3 | code | varchar | 64 | 0 | N | N | 角色代码 | |
4 | remark | varchar | 64 | 0 | Y | N | 备注 | |
5 | created | datetime | 19 | 0 | Y | N | CURRENT_TIMESTAMP | 角色创建时间 |
6 | status | int | 10 | 0 | N | N | 角色状态 |
表名: role_menu
说明:
数据列:
序号 | 名称 | 数据类型 | 长度 | 小数位 | 允许空值 | 主键 | 默认值 | 说明 |
---|---|---|---|---|---|---|---|---|
1 | id | bigint | 20 | 0 | N | Y | 角色菜单关联ID | |
2 | role_id | bigint | 20 | 0 | N | N | 角色ID | |
3 | menu_id | bigint | 20 | 0 | N | N | 菜单ID |
表名: user
说明:
数据列:
序号 | 名称 | 数据类型 | 长度 | 小数位 | 允许空值 | 主键 | 默认值 | 说明 |
---|---|---|---|---|---|---|---|---|
1 | id | int | 10 | 0 | N | Y | 用户ID | |
2 | username | varchar | 50 | 0 | N | N | 用户名 | |
3 | password | varchar | 50 | 0 | N | N | 用户密码 | |
4 | avatar | varchar | 255 | 0 | N | N | avatar3 | 用户头像 |
5 | varchar | 64 | 0 | Y | N | 用户邮箱 | ||
6 | created | datetime | 19 | 0 | N | N | CURRENT_TIMESTAMP | 用户注册时间 |
7 | status | int | 10 | 0 | N | N | 1 | 账号状态,注销为0,正常为1 |
表名: user_role
说明:
数据列:
序号 | 名称 | 数据类型 | 长度 | 小数位 | 允许空值 | 主键 | 默认值 | 说明 |
---|---|---|---|---|---|---|---|---|
1 | id | bigint | 20 | 0 | N | Y | 用户角色关联ID | |
2 | user_id | bigint | 20 | 0 | N | N | 用户ID | |
3 | role_id | bigint | 20 | 0 | N | N | 角色ID |
2.建表sql
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`username` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户名',
`password` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户密码',
`avatar` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'avatar3' COMMENT '用户头像',
`email` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '用户邮箱',
`created` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '用户注册时间',
`status` int(255) NOT NULL DEFAULT 1 COMMENT '账号状态,注销为0,正常为1',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `UK_USERNAME`(`username`) USING BTREE
);
CREATE TABLE `role` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '角色ID',
`name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '角色名称',
`code` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '角色代码',
`remark` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '备注',
`created` datetime(0) DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '角色创建时间',
`status` int(5) NOT NULL COMMENT '角色状态',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `name`(`name`) USING BTREE,
UNIQUE INDEX `code`(`code`) USING BTREE
);
CREATE TABLE `menu` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '菜单ID',
`parent_id` bigint(20) DEFAULT NULL COMMENT '父菜单ID,一级菜单为0',
`name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '菜单名称',
`path` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '菜单URL',
`perms` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '授权(多个用逗号分隔,如:user:list,user:create)',
`component` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '菜单组件',
`type` int(5) NOT NULL COMMENT '类型 0:目录 1:菜单 2:按钮',
`icon` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '菜单图标',
`orderNum` int(11) DEFAULT NULL COMMENT '排序',
`status` int(5) NOT NULL DEFAULT 1 COMMENT '菜单状态',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `name`(`name`) USING BTREE
) ;
CREATE TABLE `user_role` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户角色关联ID',
`user_id` bigint(20) NOT NULL COMMENT '用户ID',
`role_id` bigint(20) NOT NULL COMMENT '角色ID',
PRIMARY KEY (`id`) USING BTREE
);
CREATE TABLE `role_menu` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '角色菜单关联ID',
`role_id` bigint(20) NOT NULL COMMENT '角色ID',
`menu_id` bigint(20) NOT NULL COMMENT '菜单ID',
PRIMARY KEY (`id`) USING BTREE
);
三、后端搭建
1.新建空项目PermissionAdmin
2.新建后端springboot模块springboot-permission-admin
3.添加pom依赖
<!-- Spring Boot的Web启动器,用于创建基于Spring MVC的Web应用程序。-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MySQL Connector/J驱动,用于连接MySQL数据库。-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.44</version>
<scope>runtime</scope>
</dependency>
<!-- MyBatis Plus Generator,用于自动生成MyBatis的Mapper、Service等代码。-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.1</version>
</dependency>
<!-- Lombok,用于简化Java实体类的编写,自动生成Getters、Setters等方法。-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Spring Boot的测试启动器,用于编写单元测试。-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- MyBatis Plus的Spring Boot集成,简化了MyBatis Plus的配置。-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
<!-- FreeMarker模板引擎,用于生成动态HTML页面。-->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.30</version>
</dependency>
<!-- Swagger,用于生成API文档。-->
<dependency>
<groupId>com.spring4all</groupId>
<artifactId>spring-boot-starter-swagger</artifactId>
<version>1.5.1.RELEASE</version>
</dependency>
<!-- Apache Commons IO,提供了常用的IO操作工具类。-->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
<!-- Apache Commons FileUpload,用于处理文件上传。-->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
<!-- Screw,用于生成数据库文档。-->
<dependency>
<groupId>cn.smallbun.screw</groupId>
<artifactId>screw-core</artifactId>
<version>1.0.5</version>
</dependency>
<!-- Kaptcha,用于生成验证码图片。-->
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>2.3.2</version>
</dependency>
<!-- Spring Boot的邮件发送功能启动器,用于发送电子邮件。-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<!-- hutool 工具类 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.8</version>
</dependency>
<!-- Sa-Token 权限认证, 在线文档:http://sa-token.dev33.cn/ -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>1.30.0</version>
</dependency>
4.编写配置类
(1)MyBatis Plus的配置类,配置MyBatis Plus的分页插件
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 MybatisPlusConfig {
/**
* 配置MyBatis Plus的分页插件
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 创建PaginationInnerInterceptor对象,并设置数据库类型为MySQL
PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
// 将PaginationInnerInterceptor对象添加为MybatisPlusInterceptor的内部拦截器
interceptor.addInnerInterceptor(paginationInterceptor);
return interceptor;
}
}
(2)跨域请求配置类
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class CorsConfig implements WebMvcConfigurer {
/**
* 配置跨域请求
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
// 是否发送Cookie
.allowCredentials(true)
// 放行哪些原始域
.allowedOriginPatterns("*")
// 允许的请求方法
.allowedMethods(new String[]{"GET", "POST", "PUT", "DELETE"})
// 允许的请求头
.allowedHeaders("*");
}
}
(3)验证码生成器配置类
import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Properties;
@Configuration
public class KaptchaConfig {
/**
* 配置验证码生成器
*/
@Bean
DefaultKaptcha producer() {
// 创建属性对象,用于配置验证码生成器
Properties properties = new Properties();
properties.put("kaptcha.border", "no"); // 设置边框样式为无边框
properties.put("kaptcha.textproducer.font.color", "black"); // 设置验证码文字颜色为黑色
properties.put("kaptcha.textproducer.char.space", "4"); // 设置验证码字符间距为4个像素
properties.put("kaptcha.image.height", "40"); // 设置验证码图片高度为40像素
properties.put("kaptcha.image.width", "120"); // 设置验证码图片宽度为120像素
properties.put("kaptcha.textproducer.font.size", "30"); // 设置验证码文字大小为30像素
// 创建配置对象,使用上述属性初始化
Config config = new Config(properties);
// 创建默认的验证码生成器
DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
defaultKaptcha.setConfig(config); // 将配置对象设置到验证码生成器中
return defaultKaptcha; // 返回配置好的验证码生成器
}
}
5.编写工具类
(1)代码生成器
import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class CodeGenerator {
/**
* 读取控制台内容
*/
public static String scanner(String tip) {
Scanner scanner = new Scanner(System.in);
StringBuilder help = new StringBuilder();
help.append("请输入" + tip + ":");
System.out.println(help.toString());
if (scanner.hasNext()) {
String ipt = scanner.next();
if (StringUtils.isNotBlank(ipt)) {
return ipt;
}
}
throw new MybatisPlusException("请输入正确的" + tip + "!");
}
public static void main(String[] args) {
// 代码生成器
AutoGenerator mpg = new AutoGenerator();
// 全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir")+"/springboot-permission-admin";
gc.setOutputDir(projectPath + "/src/main/java");
gc.setAuthor("longyi");
gc.setOpen(false);
gc.setSwagger2(true); // 实体属性 Swagger2 注解
gc.setBaseResultMap(true);// XML ResultMap
gc.setBaseColumnList(true);// XML columList
gc.setServiceName("%sService"); // 去掉service接口首字母的I, 如DO为User则叫UserService
mpg.setGlobalConfig(gc);
// 数据源配置
DataSourceConfig dsc = new DataSourceConfig();
// 修改数据源
dsc.setUrl("jdbc:mysql://localhost:3306/permissionadmin?useUnicode=true&characterEncoding=UTF8&useSSL=false");
dsc.setDriverName("com.mysql.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("root");
mpg.setDataSource(dsc);
// 包配置
PackageConfig pc = new PackageConfig();
// 模块配置
pc.setParent("com.longyi.springbootpermissionadmin")
.setEntity("entity")
.setMapper("mapper")
.setService("service")
.setServiceImpl("service.impl")
.setController("controller");
mpg.setPackageInfo(pc);
// 自定义配置
InjectionConfig cfg = new InjectionConfig() {
@Override
public void initMap() {
// do nothing
}
};
String templatePath = "templates/mapper.xml.ftl";
// 自定义输出配置
List<FileOutConfig> focList = new ArrayList<>();
// 自定义配置会被优先输出
focList.add(new FileOutConfig(templatePath) {
@Override
public String outputFile(TableInfo tableInfo) {
// 自定义输出文件名,如果你的Entity设置了前后缀,此处注意xml的名称会跟着发生变化!
return projectPath + "/src/main/resources/mapper/" + pc.getModuleName()
+ "/" + tableInfo.getEntityName() + "Mapper" +
StringPool.DOT_XML;
}
});
cfg.setFileOutConfigList(focList);
mpg.setCfg(cfg);
// 配置模板
TemplateConfig templateConfig = new TemplateConfig();
templateConfig.setXml(null); // 不生成xml文件
mpg.setTemplate(templateConfig);
// 策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
strategy.setEntityLombokModel(true);
strategy.setRestControllerStyle(true);
System.out.println("user,role,menu,user_role,role_menu");
strategy.setInclude(scanner("表名,多个英文逗号分割").split(","));
strategy.setControllerMappingHyphenStyle(true);
mpg.setStrategy(strategy);
mpg.setTemplateEngine(new FreemarkerTemplateEngine());
mpg.execute();
}
}
(2)数据库文档生成器
import cn.smallbun.screw.core.Configuration;import cn.smallbun.screw.core.engine.EngineConfig;
import cn.smallbun.screw.core.engine.EngineFileType;
import cn.smallbun.screw.core.engine.EngineTemplateType;
import cn.smallbun.screw.core.execute.DocumentationExecute;import cn.smallbun.screw.core.process.ProcessConfig;import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import javax.sql.DataSource;import java.util.ArrayList;
public class ScrewGenerator {
public static void main(String[] args){
documentGeneration();
}
static void documentGeneration() {
//数据源
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setDriverClassName("com.mysql.jdbc.Driver");
hikariConfig.setJdbcUrl("jdbc:mysql://localhost:3306/permissionadmin?useUnicode=true&characterEncoding=UTF8&useSSL=false");
hikariConfig.setUsername("root");
hikariConfig.setPassword("root");
String fileOutputDir = System.getProperty("user.dir");
//设置可以获取tables remarks信息
hikariConfig.addDataSourceProperty("useInformationSchema", "true");
hikariConfig.setMinimumIdle(2);
hikariConfig.setMaximumPoolSize(5);
DataSource dataSource = new HikariDataSource(hikariConfig);
// 生成配置
EngineConfig engineConfig =
EngineConfig.builder()
// 生成文件路径
.fileOutputDir(fileOutputDir)
// 打开目录
.openOutputDir(true)
// 文件类型
.fileType(EngineFileType.MD)
// 生成模板实现
.produceType(EngineTemplateType.freemarker)
// 自定义文件名称
// .fileName("基于springboot+vue的共享单车时空地图综合管理平台系统数据库文档")
.fileName("PermissionAdmin通用权限管理系统数据库文档")
.build();
//忽略表
ArrayList<String> ignoreTableName = new ArrayList<>();
ignoreTableName.add("test_user");
ignoreTableName.add("test_group");
//忽略表前缀
ArrayList<String> ignorePrefix = new ArrayList<>();
ignorePrefix.add("test_");
//忽略表后缀
ArrayList<String> ignoreSuffix = new ArrayList<>();
ignoreSuffix.add("_test");
ProcessConfig processConfig = ProcessConfig.builder()
//指定生成逻辑、当存在指定表、指定表前缀、指定表后缀时,将生成指定表,其余表不生成、并跳过忽略表配置
//根据名称指定表生成
.designatedTableName(new ArrayList<>())
//根据表前缀生成
.designatedTablePrefix(new ArrayList<>())
//根据表后缀生成
.designatedTableSuffix(new ArrayList<>())
//忽略表名
.ignoreTableName(ignoreTableName)
//忽略表前缀
.ignoreTablePrefix(ignorePrefix)
//忽略表后缀
.ignoreTableSuffix(ignoreSuffix).build();
//配置
Configuration config = Configuration.builder()
//版本
.version("1.0.0")
//描述
.description("数据库设计文档生成")
//数据源
.dataSource(dataSource)
//生成配置
.engineConfig(engineConfig)
//生成配置
.produceConfig(processConfig)
.build();
//执行生成
new DocumentationExecute(config).execute();
}
}
(3)路径获取
import java.io.IOException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.stereotype.Component;
@Component
public class GetPath {
@Autowired
private ApplicationContext applicationContext;
/**
* 获取项目路径
*
* @return 项目路径
* @throws IOException 当获取路径时发生IO异常
*/
public String getPath() throws IOException {
ResourceLoader resourceLoader = applicationContext;
Resource resource = resourceLoader.getResource("classpath:");
String path =
resource.getFile().getParentFile().getParentFile().getParentFile().getAbsolutePath();
return path;
}
/**
* 获取项目中静态资源的绝对路径
*
* @return 静态资源绝对路径
* @throws IOException 当获取路径时发生IO异常
*/
public String getProjectAbsolutePath() throws IOException {
return getPath() + "\\springboot-permission-admin\\src\\main\\resources\\static";
}
}
(4)分页查询参数的封装
import lombok.Data;
import java.util.HashMap;
@Data
public class QueryPageParam {
// 默认每页数量
private static int PAGE_SIZE=20;
// 默认页码
private static int PAGE_NUM=1;
// 每页数量
private int pageSize=PAGE_SIZE;
// 页码
private int pageNum=PAGE_NUM;
// 查询参数
private HashMap<String, Object> param = new HashMap<>();
}
(5)返回结果集的封装
import lombok.Data;
@Data
public class Result {
private int code; // 编码 200/400
private String msg; // 消息 成功/失败
private Long total; // 总记录数
private Object data; // 数据
/**
* 创建一个表示失败的Result对象
*
* @return Result对象
*/
public static Result fail() {
return result(400, "失败", 0L, null);
}
/**
* 创建一个表示失败的Result对象,并指定消息
*
* @param msg 失败消息
* @return Result对象
*/
public static Result fail(String msg) {
return result(400, msg, 0L, null);
}
/**
* 创建一个表示成功的Result对象
*
* @return Result对象
*/
public static Result suc() {
return result(200, "成功", 0L, null);
}
/**
* 创建一个表示成功的Result对象,并指定数据
*
* @param data 成功返回的数据
* @return Result对象
*/
public static Result suc(Object data) {
return result(200, "成功", 0L, data);
}
/**
* 创建一个表示成功的Result对象,并指定数据和总记录数
*
* @param data 成功返回的数据
* @param total 总记录数
* @return Result对象
*/
public static Result suc(Object data, Long total) {
return result(200, "成功", total, data);
}
/**
* 根据指定参数创建Result对象
*
* @param code 编码
* @param msg 消息
* @param total 总记录数
* @param data 数据
* @return Result对象
*/
private static Result result(int code, String msg, Long total, Object data) {
Result res = new Result();
res.setData(data);
res.setMsg(msg);
res.setCode(code);
res.setTotal(total);
return res;
}
}
(6)随机验证码的生成
import java.util.Random;
public class CodeCreate {
/**
* 生成随机字符验证码
*
* @param length 验证码长度
* @return 随机字符验证码
*/
public static String RandomStringCode(int length) {
String characters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
Random random = new Random();
StringBuilder code = new StringBuilder();
for (int i = 0; i < length; i++) {
int index = random.nextInt(characters.length());
code.append(characters.charAt(index));
}
return code.toString();
}
/**
* 生成随机数字验证码
*
* @param length 验证码长度
* @return 随机数字验证码
*/
public static String RandomNumberCode(int length) {
String characters = "0123456789";
Random random = new Random();
StringBuilder code = new StringBuilder();
for (int i = 0; i < length; i++) {
int index = random.nextInt(characters.length());
code.append(characters.charAt(index));
}
return code.toString();
}
}
6.yml的配置
server: # 配置服务器相关信息
port: 8090 # 配置端口号
spring: # 配置Spring框架相关信息
datasource: # 配置数据源
url: jdbc:mysql://localhost:3306/permissionadmin?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8 # 数据库连接URL
driver-class-name: com.mysql.jdbc.Driver # 数据库驱动
username: root # 数据库用户名
password: root # 数据库密码
mail: # 配置邮件发送相关信息
default-encoding: UTF-8 # 邮件编码方式
host: smtp.qq.com # 邮箱SMTP服务器地址
username: # 发件人邮箱
password: # 发件人邮箱授权码
servlet: # 配置Servlet相关信息
multipart: # 配置Spring Boot的文件上传功能
enabled: true # 是否启用文件上传功能
max-file-size: 100MB # 允许上传单个文件的最大大小
max-request-size: 100MB # 允许上传多个文件的总大小限制
http: # 配置HTTP相关信息
multipart: # 启用HTTP的文件上传功能
enabled=true # 是否启用文件上传功能
logging: # 配置日志相关信息
level: # 配置包的日志等级
com.longyi.springboot-permission-admin: debug # 为com.longyi.springboot-permission-admin包开启debug级别日志输出
## Sa-Token配置
sa-token:
## token 名称 (同时也是cookie名称)
token-name: token
## token 有效期,单位s 默认30天, -1代表永不过期
timeout: 2592000
## token 临时有效期 (指定时间内无操作就视为token过期) 单位: 秒
activity-timeout: -1
## 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
is-concurrent: true
## 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
is-share: false
## token风格
token-style: uuid
## 是否输出操作日志
is-log: true
7.QQ邮箱的授权码获取
(1)登录进自己的qq邮箱
(2)进入设置
(3)在常规或者账号中找到并开启第三方服务IMAP/SMTP服务
(4)完成验证后可以获得邮箱授权码,记得保存下来
四、前端搭建
1.打开命令行,创建vue2项目vue-permission-admin
2.用vscode打开项目,安装相关依赖
安装vue-router
npm i vue-router@3.5.4
安装axios
npm install axios --save
安装vuex
npm i vuex@3.0.0
npm i vuex-persistedstate
安装element-ui
npm i element-ui -S
安装echarts
npm install echarts --save
3.配置main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import Element from "element-ui" // 导入 element-ui 组件库
import "element-ui/lib/theme-chalk/index.css" // 引入其样式文件
import axios from 'axios' // 导入axios
Vue.prototype.$axios = axios
Vue.prototype.$url='http://localhost:8090'
Vue.config.productionTip = false
Vue.use(Element) // 使用 element-ui 组件库
// 创建根 Vue 实例
new Vue({
router, // 路由实例
store, // 状态管理实例
render: h => h(App) // 渲染根组件 App
}).$mount('#app')
4.配置router.js
创建Index.vue,Home.vue,Login.vue和Register.vue页面
空模板代码
<template>
<div>
</div>
</template>
<script>
export default {
// eslint-disable-next-line vue/multi-word-component-names
name: "Index",
}
</script>
<style>
</style>
router.js代码
import Vue from "vue";
import VueRouter from "vue-router";
import Index from "../components/Index/Index.vue";
import Home from "../components/Home/Home.vue";
Vue.use(VueRouter);
// 定义路由配置
const routes = [
{
path: "/",
name: "Index",
component: Index,
children: [
{
path: "/Home",
name: "Home",
meta: { title: "系统首页" },
component: Home,
},
// 可以根据需要添加其他子路由
],
},
{
path: "/login",
name: "Login",
component: () => import("@/components/Login.vue"),
},
{
path: "/Register",
name: "Register",
component: () => import("@/components/Register.vue"),
},
];
// 创建 Vue Router 实例
const router = new VueRouter({
mode: "history", // 路由模式为 history 模式,去掉 URL 中的 #
base: process.env.BASE_URL, // 根据配置设置基本 URL
routes, // 路由配置
});
export default router;
5.配置store.js
创建模块 menus
配置modules/menus.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default {
state: {
menuList: [], // 菜单列表
permList: [], // 权限列表
hasRoutes: false, // 是否存在路由
editableTabsValue: 'Home', // 可编辑标签页的值,默认为系统首页
editableTabs: [{
title: '系统首页',
name: 'Home',
}] // 可编辑标签页的数组,初始只包含系统首页
},
mutations: {
setMenuList(state, menus) {
state.menuList = menus // 更新菜单列表
},
setPermList(state, perms) {
state.permList = perms // 更新权限列表
},
changeRouteStatus(state, hasRoutes) {
state.hasRoutes = hasRoutes // 更新是否存在路由的状态
},
addTab(state, tab) {
// 添加标签页
let index = state.editableTabs.findIndex(e => e.name === tab.name)
if (index === -1) { // 若该标签页不存在,则添加
state.editableTabs.push({
title: tab.title,
name: tab.name,
});
}
state.editableTabsValue = tab.name; // 将当前选中的标签页值更新为添加的标签页
},
resetState: (state) => {
// 重置状态
state.menuList = [] // 清空菜单列表
state.permList = [] // 清空权限列表
state.hasRoutes = false // 设置是否存在路由的状态为 false
state.editableTabsValue = 'Home' // 将当前选中的标签页值重置为系统首页
state.editableTabs = [{
title: '系统首页',
name: 'Home',
}] // 重置可编辑标签页数组,只包含系统首页
// 移除本地存储数据
localStorage.removeItem("LocalUser")
}
},
actions: {},
}
配置vuex
import Vue from 'vue'
import Vuex from 'vuex'
import menus from "./modules/menus";
Vue.use(Vuex)
export default new Vuex.Store({
state: {
LocalUser: '' // 定义状态属性 LocalUser,用于存储本地用户信息,默认为空字符串
},
mutations: {
SETUSER: (state, LocalUser) => {
state.LocalUser = LocalUser // 更新状态属性 LocalUser 的值为传入的 LocalUser
localStorage.setItem("LocalUser", LocalUser) // 将 LocalUser 存储到 localStorage 中
},
},
actions: {},
modules: {
menus // 注册模块 menus
}
})
6.修改App.vue
<template>
<div id="app">
<router-view/>
</div>
</template>
<script>
export default {
name: "App",
}
</script>
<style>
html, body, #app {
font-family: 'Helvetica Neue', 'Hiragino Sans GB', 'WenQuanYi Micro Hei', 'Microsoft Yahei', sans-serif;
height: 100%;
padding: 0;
margin: 0;
font-size: 15px;
}
</style>
7.运行
五、后端Menu功能的实现
Controller层
1.注入服务
@Resource
private MenuService menuService; // 菜单服务类
@Resource
private RoleMenuService roleMenuService; // 角色菜单服务类
2.菜单列表
// 菜单列表
@GetMapping("/list")
public Result list() {
List<Menu> menuList = menuService.list(); // 获取菜单列表
return Result.suc(menuList); // 返回成功结果和菜单列表
}
3.新增菜单
// 新增菜单
@PostMapping("/save")
public Result save(@Validated @RequestBody Menu menu) {
return menuService.save(menu)?Result.suc(menu):Result.fail(); // 返回成功结果和保存的菜单
}
4.修改菜单
// 修改菜单
@PostMapping("/update")
public Result update(@Validated @RequestBody Menu menu) {
return menuService.updateById(menu)?Result.suc(menu):Result.fail(); // 返回成功结果和更新的菜单
}
5.删除菜单
// 删除菜单
@PostMapping("/delete/{id}")
public Result delete(@PathVariable("id") Long id) {
int count = menuService.count(new QueryWrapper<Menu>().eq("parent_id", id)); // 统计子菜单数量
if (count > 0) {
return Result.fail("请先删除子菜单"); // 存在子菜单时返回错误结果
}
menuService.removeById(id); // 删除菜单
// 同步删除中间关联表
roleMenuService.remove(new QueryWrapper<RoleMenu>().eq("menu_id", id)); // 删除角色菜单关联表中的记录
return Result.suc("删除菜单成功"); // 返回成功结果
}
6.获取菜单信息
// 获取菜单信息
@GetMapping("/info/{id}")
public Result info(@PathVariable(name = "id") Long id) {
return Result.suc(menuService.getById(id)); // 返回成功结果和指定id的菜单信息
}
7.获取菜单树形结构列表
// 获取菜单树形结构列表
@GetMapping("/tree")
public Result tree() {
List<Menu> menuList = menuService.tree(); // 获取菜单树形结构列表
return Result.suc(menuList); // 返回成功结果和菜单树形结构列表
}
entity实体类
@Data
@EqualsAndHashCode(callSuper = false)
@ApiModel(value="Menu对象", description="")
public class Menu implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
private Long id;
@ApiModelProperty(value = "父菜单ID,一级菜单为0")
private Long parentId;
private String name;
@ApiModelProperty(value = "菜单URL")
private String path;
@ApiModelProperty(value = "授权(多个用逗号分隔,如:user:list,user:create)")
private String perms;
private String component;
@ApiModelProperty(value = "类型 0:目录 1:菜单 2:按钮")
private Integer type;
@ApiModelProperty(value = "菜单图标")
private String icon;
@ApiModelProperty(value = "排序")
@TableField("orderNum")
private Integer ordernum;
private Integer status;
@TableField(exist = false)
private List<Menu> children = new ArrayList<>();
}
Service层
List<Menu> tree();
MenuServiceImpl
注入mapper
@Resource UserMapper userMapper;
@Resource MenuMapper menuMapper;
@Override
public List<Menu> tree() {
// 获取所有菜单信息
List<Menu> menuList = this.list(new QueryWrapper<Menu>().orderByAsc("orderNum"));
// 转成树状结构
return buildTreeMenu(menuList);
}
/**
* 构建菜单树形结构
*
* @param menus 菜单信息列表
* @return 构建好的菜单信息树形结构
*/
private List<Menu> buildTreeMenu(List<Menu> menus) {
// 存放构建好的菜单信息树形结构
List<Menu> finalMenus = new ArrayList<>();
// 遍历每个菜单信息
for (Menu menu : menus) {
// 将当前菜单的子节点添加到其父节点的children属性中
for (Menu e : menus) {
if (menu.getId() == e.getParentId()) {
menu.getChildren().add(e);
}
}
// 如果当前菜单没有父菜单,则认为是顶级菜单,将其添加到结果集中
if (menu.getParentId() == 0L) {
finalMenus.add(menu);
}
}
// 返回构造完成的菜单信息树形结构
return finalMenus;
}
8.获取导航栏信息
// 获取导航栏信息
@PostMapping("/nav")
public Result nav(@RequestBody User user) {
// 获取导航栏信息
List<MenuDto> navs = menuService.getCurrentUserNav(user); // 根据用户获取当前用户的导航栏信息
return Result.suc(MapUtil.builder()
.put("auth", user.getRoles()) // 设置返回结果中的权限信息
.put("nav", navs) // 设置返回结果中的导航栏信息
.map()
);
}
entity实体类;需要需要创建MenuDto实体来存放导航栏信息
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
public class MenuDto implements Serializable {
private Long id;
private String name;
private String title;
private String icon;
private String path;
private String component;
private Integer type;
@ApiModelProperty(value = "父菜单ID,一级菜单为0")
private Long parentId;
private List<MenuDto> children = new ArrayList<>();
}
User实体类
@Data
@EqualsAndHashCode(callSuper = false)
@ApiModel(value="User对象", description="")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "用户ID")
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
@ApiModelProperty(value = "用户名")
private String username;
@ApiModelProperty(value = "用户密码")
private String password;
@ApiModelProperty(value = "用户头像")
private String avatar;
@ApiModelProperty(value = "用户邮箱")
private String email;
@ApiModelProperty(value = "用户注册时间")
private LocalDateTime created;
@ApiModelProperty(value = "账号状态,注销为0,正常为1")
private Integer status;
@TableField(exist = false)
private Role roles;
}
MenuService
List<MenuDto> getCurrentUserNav(User user);
MenuServiceImpl
@Override
public List<MenuDto> getCurrentUserNav(User user) {
// 获取当前用户的导航菜单ID列表
List<Long> menuIds = userMapper.getNavMenuIds(Long.valueOf(user.getId()));
// 根据导航菜单ID列表查找对应的菜单信息
List<Menu> menus = new ArrayList<>();
for (int i = 0; i < menuIds.size(); i++) {
menus.add(menuMapper.selectById(menuIds.get(i)));
}
// 构建菜单树形结构
List<Menu> menuTree = buildTreeMenu(menus);
// 将菜单树形结构转换为DTO对象
return convert(menuTree);
}
/**
* 将菜单信息构建成树形结构
*
* @param menuTree 菜单信息列表
* @return 构建好的菜单树形结构
*/
private List<MenuDto> convert(List<Menu> menuTree) {
// 存放转换后的MenuDto信息
List<MenuDto> menuDtos = new ArrayList<>();
// 遍历每个菜单信息
menuTree.forEach(
m -> {
// 新建一个MenuDto对象
MenuDto dto = new MenuDto();
// 将当前菜单信息的属性值赋值给MenuDto对象的对应属性
dto.setId(m.getId());
dto.setName(m.getPerms());
dto.setTitle(m.getName());
dto.setComponent(m.getComponent());
dto.setPath(m.getPath());
dto.setType(m.getType());
dto.setIcon(m.getIcon());
dto.setParentId(m.getParentId());
// 如果当前菜单有子节点,则递归调用当前方法进行再次转换
if (m.getChildren().size() > 0) {
dto.setChildren(convert(m.getChildren()));
}
// 将转换后的MenuDto对象添加到结果集中
menuDtos.add(dto);
});
// 返回构造完成的菜单信息树形结构
return menuDtos;
}
UserMapper
List<Long> getNavMenuIds(Long valueOf);
UserMapper.xml
<select id="getNavMenuIds" resultType="java.lang.Long">
SELECT
DISTINCT rm.menu_id
FROM
user_role ur
LEFT JOIN role_menu rm ON ur.role_id = rm.role_id
WHERE ur.user_id = #{userId}
</select>
9.插入数据
INSERT INTO `menu` VALUES (1, 0, '系统首页', '/Home', 'Home', 'Home/Home', 1, 'el-icon-s-home', 1, 1);
INSERT INTO `menu` VALUES (2, 0, '系统管理', '', 'sys:manage', '', 0, 'el-icon-s-operation', 2, 1);
INSERT INTO `menu` VALUES (3, 2, '角色管理', '/System/Role', 'sys:role:list', 'System/Role', 1, 'el-icon-rank', 3, 1);
INSERT INTO `menu` VALUES (4, 2, '菜单管理', '/System/Menu', 'sys:menu:list', 'System/Menu', 1, 'el-icon-menu', 4, 1);
INSERT INTO `menu` VALUES (5, 0, '用户管理', '', 'sys:user', 'User/User', 0, 'el-icon-user-solid', 5, 1);
INSERT INTO `menu` VALUES (6, 5, '用户信息管理', '/user/User', 'sys:user:list', 'User/User', 1, 'el-icon-user', 6, 1);
INSERT INTO `menu` VALUES (7, 5, '用户权限管理', '/user/Role', 'sys:role', 'User/Role', 1, 'el-icon-s-tools', 7, 1);
INSERT INTO `menu` VALUES (8, 0, '个人中心', '/account/Account', 'sys:account', 'Account/Account', 1, 'el-icon-menu', 8, 1);
INSERT INTO `menu` VALUES (9, 3, '添加角色', '', 'sys:role:save', '', 2, '', 1, 1);
INSERT INTO `menu` VALUES (10, 3, '修改角色', NULL, 'sys:role:update', NULL, 2, NULL, 2, 1);
INSERT INTO `menu` VALUES (11, 3, '删除角色', NULL, 'sys:role:delete', NULL, 2, NULL, 3, 1);
INSERT INTO `menu` VALUES (12, 3, '分配权限', NULL, 'sys:role:perm', NULL, 2, NULL, 4, 1);
INSERT INTO `menu` VALUES (13, 4, '添加菜单', NULL, 'sys:menu:save', NULL, 2, NULL, 1, 1);
INSERT INTO `menu` VALUES (14, 4, '修改菜单', NULL, 'sys:menu:update', NULL, 2, NULL, 2, 1);
INSERT INTO `menu` VALUES (15, 4, '删除菜单', NULL, 'sys:menu:delete', NULL, 2, NULL, 3, 1);
INSERT INTO `menu` VALUES (16, 6, '添加用户', NULL, 'sys:user:save', NULL, 2, NULL, 1, 1);
INSERT INTO `menu` VALUES (17, 6, '修改用户', NULL, 'sys:user:update', NULL, 2, NULL, 2, 1);
INSERT INTO `menu` VALUES (18, 6, '删除用户', NULL, 'sys:user:delete', NULL, 2, NULL, 3, 1);
INSERT INTO `menu` VALUES (19, 7, '用户控制', NULL, 'sys:user:on', NULL, 2, NULL, 1, 1);
INSERT INTO `menu` VALUES (20, 7, '重置密码', NULL, 'sys:user:repass', NULL, 2, NULL, 2, 1);
INSERT INTO `menu` VALUES (21, 7, '分配角色', NULL, 'sys:user:role', NULL, 2, NULL, 3, 1);
INSERT INTO `role` VALUES (1, '超级管理员', 'admin', '系统默认最高权限,不可以编辑和任意修改', '2021-01-16 13:29:03', 1);
INSERT INTO `role_menu` VALUES (1, 1, 1);
INSERT INTO `role_menu` VALUES (2, 1, 2);
INSERT INTO `role_menu` VALUES (3, 1, 3);
INSERT INTO `role_menu` VALUES (4, 1, 9);
INSERT INTO `role_menu` VALUES (5, 1, 10);
INSERT INTO `role_menu` VALUES (6, 1, 11);
INSERT INTO `role_menu` VALUES (7, 1, 12);
INSERT INTO `role_menu` VALUES (8, 1, 4);
INSERT INTO `role_menu` VALUES (9, 1, 13);
INSERT INTO `role_menu` VALUES (10, 1, 14);
INSERT INTO `role_menu` VALUES (11, 1, 15);
INSERT INTO `role_menu` VALUES (12, 1, 5);
INSERT INTO `role_menu` VALUES (13, 1, 6);
INSERT INTO `role_menu` VALUES (14, 1, 16);
INSERT INTO `role_menu` VALUES (15, 1, 17);
INSERT INTO `role_menu` VALUES (16, 1, 18);
INSERT INTO `role_menu` VALUES (17, 1, 7);
INSERT INTO `role_menu` VALUES (18, 1, 19);
INSERT INTO `role_menu` VALUES (19, 1, 20);
INSERT INTO `role_menu` VALUES (20, 1, 21);
INSERT INTO `role_menu` VALUES (21, 1, 8);
INSERT INTO `user` VALUES (1, 'admin', '123456', 'avatar1', '1158842161@qq.com', '2023-09-27 23:27:12', 1);
INSERT INTO `user_role` VALUES (1, 1, 1);
10.测试
六、后端Role功能的实现
Controller层
1.注入服务
@Resource private RoleService roleService;
@Resource private RoleMenuService roleMenuService;
2.角色列表
@GetMapping("/list")
public List<Role> list() {
return roleService.list();
}
3.新增角色
@PostMapping("/save")
public Result save(@RequestBody Role role) {
return roleService.save(role) ? Result.suc() : Result.fail();
}
4.修改角色
@PostMapping("/update")
public Result update(@RequestBody Role role) {
return roleService.updateById(role) ? Result.suc() : Result.fail();
}
5.删除角色
@PostMapping("/delete/{id}")
public Result delete(@PathVariable("id") Long id) {
return roleService.removeById(id) ? Result.suc() : Result.fail();
}
6.逻辑删除角色
@PostMapping("/deleteLogical/{id}")
public Result deleteLogical(@PathVariable("id") Long id) {
// 获取用户数据
Role role = roleService.getById(id);
// 修改用户状态
role.setStatus(0);
// 更新数据
return roleService.updateById(role) ? Result.suc() : Result.fail();
}
7.禁用启用角色
@PostMapping("/control/{id}")
public Result control(@PathVariable("id") Long id) {
Role role = roleService.getById(id);
if (role.getStatus() == 1) {
role.setStatus(0);
} else {
role.setStatus(1);
}
return roleService.updateById(role) ? Result.suc() : Result.fail();
}
8.批量删除角色
@Transactional
@PostMapping("/deleteBatch")
public Result deleteBatch(@RequestBody Long[] ids) {
roleService.removeByIds(Arrays.asList(ids));
return Result.suc("批量删除成功");
}
9.获取角色信息
@GetMapping("/info/{id}")
public Result info(@PathVariable("id") Long id) {
Role role = roleService.getById(id);
// 获取角色相关联的菜单id
List<RoleMenu> roleMenus = roleMenuService.list(new QueryWrapper<RoleMenu>().eq("role_id", id));
List<Long> menuIds = roleMenus.stream().map(p -> p.getMenuId()).collect(Collectors.toList());
role.setMenuIds(menuIds);
//把角色的权限信息带过去,好做角色权限分配
return Result.suc(role);
}
Role实体类
@Data
@EqualsAndHashCode(callSuper = false)
@ApiModel(value="Role对象", description="")
public class Role implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "角色ID")
@TableId(value = "id", type = IdType.AUTO)
private Long id;
@ApiModelProperty(value = "角色名称")
private String name;
@ApiModelProperty(value = "角色代码")
private String code;
@ApiModelProperty(value = "备注")
private String remark;
@ApiModelProperty(value = "角色创建时间")
private LocalDateTime created;
@ApiModelProperty(value = "角色状态")
private Integer status;
@TableField(exist = false)
private List<Long> menuIds = new ArrayList<>();
}
10.分配角色权限
@Transactional
@PostMapping("/perm/{roleId}")
public Result info(@PathVariable("roleId") Long roleId, @RequestBody Long[] menuIds) {
List<RoleMenu> roleMenus = new ArrayList<>();
// 查询角色权限
Arrays.stream(menuIds)
.forEach(
menuId -> {
RoleMenu roleMenu = new RoleMenu();
roleMenu.setMenuId(menuId);
roleMenu.setRoleId(roleId);
roleMenus.add(roleMenu);
});
// 先删除原来的记录,再保存新的
roleMenuService.remove(new QueryWrapper<RoleMenu>().eq("role_id", roleId));
roleMenuService.saveBatch(roleMenus);
return Result.suc(menuIds);
}
11.分页查询角色
@PostMapping("/listPage")
public Result listPage(@RequestBody QueryPageParam query) {
HashMap param = query.getParam();
// 查询条件
String name = (String) param.get("name");
String status = (String) param.get("status");
// 引入分页组件
Page<Role> page = new Page();
page.setCurrent(query.getPageNum());
page.setSize(query.getPageSize());
LambdaQueryWrapper<Role> lambdaQueryWrapper = new LambdaQueryWrapper();
if (StringUtils.isNotBlank(status) && !"null".equals(status)) {
lambdaQueryWrapper.eq(Role::getStatus, status);
}
if (StringUtils.isNotBlank(name) && !"null".equals(name)) {
lambdaQueryWrapper.like(Role::getName, name);
}
IPage result = roleService.listPage(page, lambdaQueryWrapper);
return Result.suc(result.getRecords(), result.getTotal());
}
RoleService
IPage listPage(IPage<Role> page, Wrapper wrapper);
RoleServiceImpl
@Resource
private RoleMapper roleMapper;
@Override
public IPage listPage(IPage<Role> page, Wrapper wrapper) {
return roleMapper.listPage(page,wrapper);
}
RoleMapper
IPage listPage(IPage<Role> page, @Param(Constants.WRAPPER) Wrapper wrapper);
RoleMapper.xml
<select id="listPage" resultType="com.longyi.springbootpermissionadmin.entity.Role">
select * from role ${ew.customSqlSegment}
</select>
12.测试
七、后端User基本功能的实现
Controller层
1.注入服务
@Resource private UserService userService;
@Resource private RoleService roleService;
@Resource private UserRoleService userRoleService;
2.用户列表
@GetMapping("/list")
public List<User> list() {
return userService.list();
}
3.新增用户
@Transactional
@PostMapping("/save")
public Result save(@RequestBody User user) {
userService.save(user);//保存用户信息
User user1 = userService.lambdaQuery().eq(User::getUsername, user.getUsername()).list().get(0);//查询用户信息
UserRole userRole = new UserRole();//创建userole对象
userRole.setUserId(Long.valueOf(user1.getId()));//设置userole对象
userRole.setRoleId(2L);//默认新用户都是普通用户,也就是第二个role
return userRoleService.save(userRole) ? Result.suc(user1) : Result.fail();//保存userrole对象
}
4.修改用户
@PostMapping("/update")
public Result update(@RequestBody User user) {
return userService.updateById(user) ? Result.suc() : Result.fail();
}
5.删除用户
@PostMapping("/delete/{id}")
public Result delete(@PathVariable("id") Long id) {
return userService.removeById(id) ? Result.suc() : Result.fail();
}
6.逻辑删除用户
@PostMapping("/deleteLogical/{id}")
public Result deleteLogical(@PathVariable("id") Long id) {
// 获取用户数据
User user = userService.getById(id);
// 修改用户状态
user.setStatus(0);
// 更新数据
return userService.updateById(user) ? Result.suc() : Result.fail();
}
7.禁用启用用户
@PostMapping("/control/{id}")
public Result control(@PathVariable("id") Long id) {
User user = userService.getById(id);
if (user.getStatus() == 1) {
user.setStatus(0);
} else {
user.setStatus(1);
}
return userService.updateById(user) ? Result.suc() : Result.fail();
}
8.批量删除用户
@Transactional
@PostMapping("/deleteBatch")
public Result deleteBatch(@RequestBody Long[] ids) {
userService.removeByIds(Arrays.asList(ids));
return Result.suc("批量删除成功");
}
9.获取用户信息
@GetMapping("/info/{id}")
public Result info(@PathVariable("id") Long id) {
User user = userService.getById(id);
if (user.getStatus() == 0) {
return Result.fail("该账号已注销");
} else {
Role role = roleService.listRolesByUserId(id);
user.setRoles(role);
return Result.suc(user);
}
}
RoleService
Role listRolesByUserId(Long id);
RoleServiceImpl
@Override
public Role listRolesByUserId(Long id) {
return roleMapper.listRolesByUserId(id);
}
RoleMapper
Role listRolesByUserId(Long id);
RoleMapper.xml
<select id="listRolesByUserId" resultType="com.longyi.springbootpermissionadmin.entity.Role">
SELECT role.*
FROM role, user_role
WHERE user_role.user_id = #{id} AND role.id = user_role.role_id
</select>
10.获取用户权限
@GetMapping("/auth/{id}")
public List<String> auth(@PathVariable("id") Long id) {
return userService.getUserAuthorityInfo(id);
}
UserService
List<String> getUserAuthorityInfo(Long id);
UserServiceImpl
注入
@Resource private UserMapper userMapper;
@Resource private RoleService roleService;
@Resource private MenuService menuService;
@Override
public List<String> getUserAuthorityInfo(Long id) {
List<String> authority = new ArrayList<>();
// 获取角色编码
Role roles = roleService.listRolesByUserId(id);
authority.add(roles.getCode());
// 获取菜单操作编码
List<Long> menuIds = userMapper.getNavMenuIds(id);
if (menuIds.size() > 0) {
List<Menu> menuList= menuService.listByIds(menuIds);
for (int i=0;i<menuList.size();i++){
authority.add(menuList.get(i).getPerms());
}
}
return authority;
}
11.分配用户角色
@Transactional
@PostMapping("/role")
public Result rolePerm(@RequestParam Long userId, Long roleId) {
List<UserRole> userRoles = userRoleService.lambdaQuery().eq(UserRole::getUserId, userId).list();
UserRole userRole = userRoles.get(0);
userRole.setRoleId(roleId);
return userRoleService.updateById(userRole) ? Result.suc() : Result.fail();
}
12.分页查询用户
@PostMapping("/listPage")
public Result listPage(@RequestBody QueryPageParam query) {
HashMap param = query.getParam();
String status = (String) param.get("status");
String username = (String) param.get("username");
String email = (String) param.get("email");
Page<User> page = new Page();
page.setCurrent(query.getPageNum());
page.setSize(query.getPageSize());
LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper();
if (StringUtils.isNotBlank(status) && !"null".equals(status)) {
lambdaQueryWrapper.eq(User::getStatus, status);
}
if (StringUtils.isNotBlank(username) && !"null".equals(username)) {
lambdaQueryWrapper.like(User::getUsername, username);
}
if (StringUtils.isNotBlank(email) && !"null".equals(email)) {
lambdaQueryWrapper.like(User::getEmail, email);
}
IPage result = userService.listPage(page, lambdaQueryWrapper);
List<User> userList = new ArrayList<>();
for (int i = 0; i < result.getRecords().size(); i++) {
User user = (User) result.getRecords().get(i);
user.setRoles(roleService.listRolesByUserId(Long.valueOf(user.getId())));
userList.add(i, user);
}
result.setRecords(userList);
return Result.suc(result.getRecords(), result.getTotal());
}
UserService
IPage listPage(IPage<User> page, Wrapper wrapper);
UserServiceImpl
@Override
public IPage listPage(IPage<User> page, Wrapper wrapper) {
return userMapper.listPage(page, wrapper);
}
UserMapper
IPage listPage(IPage<User> page, @Param(Constants.WRAPPER) Wrapper wrapper);
UserMapper.xml
<select id="listPage" resultType="com.longyi.springbootpermissionadmin.entity.User">
select * from user ${ew.customSqlSegment}
</select>
13.测试
八、头像上传下载功能的实现
1.创建存放目录文件夹
static/avatar
static/file
2.编写代码
获取文件扩展名
头像上传接口
头像下载接口
文件下载接口
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.util.List;
import com.longyi.springbootpermissionadmin.common.GetPath;
import com.longyi.springbootpermissionadmin.common.Result;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
@RestController
@RequestMapping("/upload")
public class UploadController {
@Resource
private GetPath getPath;
// 注入 GetPath 对象,用于获取项目文件夹路径
// 获取文件扩展名
private String getFileExtension(String fileName) {
int index = fileName.lastIndexOf(".");
if (index > 0 && index < fileName.length() - 1) {
return fileName.substring(index + 1);
}
return "";
}
// 头像上传接口
@PostMapping("/avatar")
public Result avatarUpload(String fileName, @RequestParam MultipartFile file) {
if (file == null) {
return Result.fail("文件为空,请上传文件");
}
synchronized (UploadController.class) {
// 获取当前时间戳作为文件名前缀
String flag = System.currentTimeMillis() + "";
String extension = getFileExtension(fileName); // 获取文件扩展名
try {
// 如果没有 avatar 文件夹,会在项目根目录下创建一个 avatar 文件夹
if (!FileUtil.isDirectory(getPath.getProjectAbsolutePath())) {
FileUtil.mkdir(getPath.getProjectAbsolutePath());
}
// 文件存储形式:时间戳-文件名.扩展名
FileUtil.writeBytes(
file.getBytes(),
getPath.getProjectAbsolutePath() + "/avatar/" + flag + "." + extension);
System.out.println(fileName + "--上传成功");
return Result.suc(flag); // 返回上传成功信息和文件名前缀
} catch (Exception e) {
System.err.println(fileName + "--文件上传失败");
return Result.fail("文件上传失败");
}
}
}
// 头像下载接口
@GetMapping("/avatar/{flag}")
public void avatarPath(@PathVariable String flag, HttpServletResponse response)
throws IOException {
String filePath = getPath.getProjectAbsolutePath() + "/avatar/";
if (!FileUtil.isDirectory(filePath)) {
FileUtil.mkdir(filePath);
}
OutputStream os;
List<String> fileNames = FileUtil.listFileNames(filePath);
// 筛选出符合特定标识符 flag 的文件名
String targetFileName = fileNames.stream()
.filter(name -> name.contains(flag))
.findAny()
.orElse("");
try {
if (StrUtil.isNotEmpty(targetFileName)) {
response.addHeader("Content-Disposition",
"attachment;filename=" + URLEncoder.encode(targetFileName, "UTF-8"));
response.setContentType("application/octet-stream");
byte[] bytes = FileUtil.readBytes(filePath + targetFileName);
os = response.getOutputStream();
os.write(bytes);
os.flush();
os.close();
}
} catch (Exception e) {
System.out.println("文件下载失败");
}
}
// 文件下载接口
@GetMapping("/file/{flag}")
public void filePath(@PathVariable String flag, HttpServletResponse response) throws IOException {
String filePath = getPath.getProjectAbsolutePath() + "/file/";
if (!FileUtil.isDirectory(filePath)) {
FileUtil.mkdir(filePath);
}
OutputStream os;
List<String> fileNames = FileUtil.listFileNames(filePath);
// 筛选出符合特定标识符 flag 的文件名
String targetFileName = fileNames.stream()
.filter(name -> name.contains(flag))
.findAny()
.orElse("");
try {
if (StrUtil.isNotEmpty(targetFileName)) {
response.addHeader("Content-Disposition",
"attachment;filename=" + URLEncoder.encode(targetFileName, "UTF-8"));
response.setContentType("application/octet-stream");
byte[] bytes = FileUtil.readBytes(filePath + targetFileName);
os = response.getOutputStream();
os.write(bytes);
os.flush();
os.close();
}
} catch (Exception e) {
System.out.println("文件下载失败");
}
}
}
九、前端注册功能的实现
1.搭建注册页面基本架构
<div class="loginBody">
<div class="loginDiv">
<div class="login-content">
<h1 class="login-title">账号注册</h1>
</div>
</div>
</div>
.loginBody {
position: absolute;
width: 100%;
height: 100%;
background-color: darkgrey;
background-size: 100% 100%;
}
.loginDiv {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 500px;
height: 500px;
background: rgba(255, 255, 255, 0.5);
border-radius: 5%;
display: flex;
text-align: center;
justify-content: center;
align-items: center;
backdrop-filter: blur(5px);
/* 添加毛玻璃效果,模糊值可根据需要进行调整 */
}
.login-title {
text-align: center;
}
.login-content {
width: 500px;
height: 450px;
position: absolute;
}
2.form表单
<el-form
:model="registerForm"
label-width="100px"
:rules="rules"
ref="registerForm"
>
<el-row style="margin-top: 20px; height: 40px">
</el-row>
<el-row style="margin-top: 20px; height: 40px">
</el-row>
<el-row style="margin-top: 20px; height: 40px">
</el-row>
<el-row style="margin-top: 20px; height: 40px">
</el-row>
<el-row style="margin-top: 20px; height: 40px">
</el-row>
<el-row style="margin-top: 20px; height: 40px">
<el-button
type="primary"
@click="register"
:disabled="confirm_disabled"
style="width: 150px"
>注册</el-button
>
<el-button
type="success"
@click="login"
style="width: 150px;margin-left:100px"
>登录</el-button
>
</el-row>
</el-form>
3.配置数据和校验规则
data() {
var validatePass = (rule, value, callback) => {
if (value === "") {
callback(new Error("请再次输入密码"));
} else if (value !== this.registerForm.password) {
callback(new Error("两次输入密码不一致!"));
} else {
callback();
}
};
var validateCode = (rule, value, callback) => {
if (value === "") {
callback(new Error("请再次输入验证码"));
} else if (value !== this.registerForm.rcode) {
callback(new Error("验证码不正确!"));
} else {
callback();
}
};
return {
confirm_disabled: false,
registerForm: {
username: "",
password: "",
rpassword: "",
email: "",
code: "",
rcode: "",
},
rules: {
username: [
{ required: true, message: "请输入姓名", trigger: "blur" },
{ min: 2, max: 10, message: "2-10个字符", trigger: "blur" },
],
email: [
{ required: true, message: "请输入邮箱", trigger: "blur" },
{
pattern:
/^[A-Za-z0-9\u4e00-\u9fa5]+@[a-zA-Z0-9_-]+(.[a-zA-Z0-9_-]+)+$/,
message: "邮箱格式不正确",
},
],
code: [
{ required: true, validator: validateCode, trigger: "blur" },
{
min: 6,
max: 6,
message: "请输入6位数字邮箱验证码",
trigger: "blur",
},
],
password: [
{ required: true, message: "请输入密码", trigger: "blur" },
{ min: 6, max: 16, message: "6-16个字符", trigger: "blur" },
],
rpassword: [
{ required: true, validator: validatePass, trigger: "blur" },
{ min: 6, max: 16, message: "6-16个字符", trigger: "blur" },
],
},
};
},
4.编写form表单项
<el-form-item label="用户名" prop="username">
<el-input
style="width: 300px"
type="text"
v-model="registerForm.username"
autocomplete="off"
size="small"
></el-input> </el-form-item
>
<el-form-item label="邮箱" prop="email">
<el-input
v-model="registerForm.email"
style="width: 300px"
@change="emailchange"
placeholder="请输入邮箱"
></el-input>
</el-form-item>
<el-form-item prop="code" label="验证码">
<div style="display: flex;margin-left:50px;"> <el-input
v-model.lazy.trim="registerForm.code"
placeholder="请输入验证码"
style="width: 150px"
></el-input>
<el-button
class="code-btn"
size="medium"
:disabled="sendCodeDisabled"
style="width: 120px; margin-left: 25px; font-size: 8px"
@click.stop.prevent.native="handleGetCode"
>{{ sendCodeText }}</el-button
>
</div>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input
style="width: 300px"
type="password"
v-model="registerForm.password"
show-password
autocomplete="off"
size="small"
@keyup.enter.native="register"
></el-input> </el-form-item
>
<el-form-item label="重复密码" prop="password">
<el-input
style="width: 300px"
type="password"
v-model="registerForm.rpassword"
show-password
autocomplete="off"
size="small"
@keyup.enter.native="register"
></el-input>
</el-form-item>
5.配置邮箱验证码数据
sendCodeText: '获取验证码',
sendCodeDisabled: false,
countDown: 60,
6.登录跳转方法
methods: {
login() {
this.$router.replace('/login');
},
}
7.获取邮箱验证码
handleGetCode() {
// 校验邮箱格式是否正确
if (!this.validateEmail(this.registerForm.email)) {
this.$message({
message: "请输入正确的邮箱地址",
type: "error",
});
return;
}
// 设置发送验证码按钮
this.sendCodeDisabled = true;
this.sendCodeText = `${this.countDown}s 后重新获取`;
// 开启定时器
this.timer = setInterval(() => {
if (this.countDown === 1) {
clearInterval(this.timer);
this.countDown = 60;
this.sendCodeDisabled = false;
this.sendCodeText = "获取验证码";
return;
}
this.countDown -= 1;
this.sendCodeText = `${this.countDown}s 后重新获取`;
}, 1000);
// 向后端发送请求,往用户邮箱发送验证码
this.$axios
.get(this.$url + "/email/bind?email=" + this.registerForm.email)
.then((res) => res.data)
.then((res) => {
console.log(res);
if (res.code == 200) {
this.$message({
message: "邮箱验证码发送成功!",
type: "success",
});
this.registerForm.rcode = res.data;
} else {
this.$message({
message: res.msg,
type: "error",
});
this.sendCodeDisabled = false;
this.registerForm.rcode = "";
this.sendCodeText = "获取验证码";
}
});
},
validateEmail(email) {
// 正则表达式匹配邮箱格式
const emailRegex = /^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/;
return emailRegex.test(email);
},
// 当用户修改邮箱时,重置发送按钮状态
emailchange() {
clearInterval(this.timer);
this.countDown = 60;
this.sendCodeDisabled = false;
this.sendCodeText = "获取验证码";
this.registerForm.rcode = "";
this.registerForm.code = "";
},
8.邮箱验证码后端接口
import com.longyi.springbootpermissionadmin.common.CodeCreate;
import com.longyi.springbootpermissionadmin.common.Result;
import com.longyi.springbootpermissionadmin.entity.User;
import com.longyi.springbootpermissionadmin.service.UserService;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.List;
@RestController
@RequestMapping("/email")
public class EmailController {
@Resource
JavaMailSender javamail;
/**
* 普通文本邮件发送
*
* @return
*/
@GetMapping("/bind")
public Result bind(@RequestParam String email) {
try {
SimpleMailMessage message = new SimpleMailMessage();
String rcode = CodeCreate.RandomNumberCode(6);
message.setFrom("1158842161@qq.com");//换成自己的邮箱
message.setTo(email);
message.setSubject("邮箱验证码");
message.setText("尊敬的客户,您好!欢迎使用龙毅的通用权限管理平台PermissionAdmin,您此次的邮箱验证码为:" + rcode);
javamail.send(message); //发送邮件
System.out.println("邮件已发送。"+rcode);
return Result.suc(rcode);
} catch (Exception e) {
System.out.println("发送邮件时发生异常了!" + e.getMessage());
return Result.fail("邮件发送失败");
}
}
}
9.注册方法
register() {
this.confirm_disabled = true;
this.$refs.registerForm.validate((valid) => {
console.log(valid);
if (valid) {
this.$axios
.post(this.$url + "/user/register", this.registerForm)
.then((res) => res.data)
.then((res) => {
console.log(res);
if (res.code == 200) {
this.$router.replace("/login");
} else {
this.confirm_disabled = false;
this.$message({
message: res.msg,
type: "error",
});
return false;
}
});
} else {
this.confirm_disabled = false;
this.$message({
message: "校验失败",
type: "error",
});
return false;
}
});
},
10.后端注册接口
@Transactional
@PostMapping("/register")
public Result register(@RequestBody User user) {
List list = userService.lambdaQuery().eq(User::getUsername, user.getUsername()).list();
if (list.size() > 0) {
System.out.println(list);
return Result.fail("账号已被注册");
} else {
list = userService.lambdaQuery().eq(User::getEmail, user.getEmail()).list();
if (list.size() > 0) {
System.out.println(list);
return Result.fail("邮箱已被注册");
} else {
user.setAvatar("avatar3");
userService.save(user);
User user1 =
userService.lambdaQuery().eq(User::getUsername, user.getUsername()).list().get(0);
UserRole userRole = new UserRole();
userRole.setUserId(Long.valueOf(user1.getId()));
userRole.setRoleId(2L);
return userRoleService.save(userRole) ? Result.suc(user1) : Result.fail();
}
}
}
11.测试
十、前端登录功能的实现
1.搭建登录页面基本架构
<div class="loginBody">
<div class="loginDiv">
<div class="login-content">
<h1 class="login-title">账号登录</h1>
</div>
</div>
</div>
.loginBody {
position: absolute;
width: 100%;
height: 100%;
background-color:chocolate;
background-size: 100% 100%;
}
.loginDiv {
position: absolute;
top: 50%;
left: 50%;
/* 居中 */
width: 500px;
height: 400px;
/* 注册盒子宽高 */
transform: translate(-50%, -50%);
background: rgba(255, 255, 255, 0.5);
backdrop-filter: blur(5px);
/* 添加毛玻璃效果,模糊值可根据需要进行调整 */
border-radius: 5%;
display: flex;
text-align: center;
justify-content: center;
align-items: center;
}
.login-title {
text-align: center;
}
.login-content {
width: 500px;
height: 400px;
position: absolute;
}
2.form表单
<el-form
:model="loginForm"
label-width="100px"
:rules="rules"
ref="loginForm"
>
<el-row style="margin-top: 20px; height: 60px">
<el-form-item label="账号" prop="username">
<el-input
style="width: 300px"
type="text"
v-model="loginForm.username"
autocomplete="off"
size="small"
></el-input>
</el-form-item>
</el-row>
<el-row style="margin-top: 20px; height: 60px">
<el-form-item label="密码" prop="password">
<el-input
style="width: 300px"
type="password"
v-model="loginForm.password"
show-password
autocomplete="off"
size="small"
@keyup.enter.native="confirm"
></el-input>
</el-form-item>
</el-row>
<el-row style="margin-top: 20px;height: 60px">
<el-form-item label="验证码" prop="code" >
<div style="display: flex;margin-left:50px;">
<el-input
v-model="loginForm.code"
style="width: 150px"
></el-input>
<el-image
:src="captchaImg"
class="captcha-img"
@click="getCaptcha"
lazy
style="width: 120px; margin-left: 25px"
></el-image>
</div>
</el-form-item>
</el-row>
<el-row style="margin-top: 20px; height: 60px">
<el-button
type="primary"
@click="login"
:disabled="confirm_disabled"
style="width: 150px"
>登 录</el-button
>
<el-button
type="success"
@click="register"
style="width: 150px; margin-left: 100px"
>注 册</el-button
>
</el-row>
</el-form>
3.配置数据和校验规则
data() {
var validateCode = (rule, value, callback) => {
if (value === '') {
callback(new Error('请再次输入验证码'))
} else if (value !== this.loginForm.rcode) {
callback(new Error('验证码不正确!'))
} else {
callback()
}
}
return {
confirm_disabled: false,
captchaImg: null,
loginForm: {
username: '',
password: '',
code: '',
rcode: ''
},
rules: {
username: [
{ required: true, message: '请输入账号', trigger: 'blur' }
],
password: [
{ required: true, message: '请输密码', trigger: 'blur' }
],
code: [
{ required: true, validator: validateCode, trigger: 'blur' },
{ min: 4, max: 4, message: '长度为 4 个字符', trigger: 'blur' }
],
}
}
},
4.注册跳转方法
methods: {
register() {
this.$router.replace('/Register');
},
}
5.获取验证码
getCaptcha() {
this.$axios.get(this.$url + '/captcha').then(res => {
this.captchaImg = res.data.data.captchaImg
this.loginForm.rcode = res.data.data.rcode
this.loginForm.code = ''
})
},
初始化获取
mounted() {
this.getCaptcha()
}
6.验证码后端接口
import cn.hutool.core.map.MapUtil;
import com.google.code.kaptcha.Producer;
import com.longyi.springbootpermissionadmin.common.CodeCreate;
import com.longyi.springbootpermissionadmin.common.Result;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import sun.misc.BASE64Encoder;
import javax.annotation.Resource;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@RestController
public class KaptchaController {
@Resource
Producer producer;
@GetMapping("/captcha")
public Result captcha() throws IOException {
String code = CodeCreate.RandomStringCode(4);
BufferedImage image = producer.createImage(code);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ImageIO.write(image, "jpg", outputStream);
BASE64Encoder encoder = new BASE64Encoder();
String str = "data:image/jpeg;base64,";
String base64Img = str + encoder.encode(outputStream.toByteArray());
System.out.println(code);
return Result.suc(
MapUtil.builder()
.put("captchaImg", base64Img)
.put("rcode",code)
.build()
);
}
}
7.登录方法
login() {
this.confirm_disabled = true;
this.$refs.loginForm.validate((valid) => {
if (valid) { //valid成功为true,失败为false
//去后台验证用户名密码
this.$axios.post(this.$url + '/user/login', this.loginForm).then(res => res.data).then(res => {
// console.log(res)
if (res.code == 200) {
//存储
this.$store.commit("SETUSER", JSON.stringify(res.data))
this.$router.push("/Home")
} else {
this.confirm_disabled = false;
this.$message({
message: res.msg,
type: 'error'
});
return false;
}
});
} else {
this.confirm_disabled = false;
this.$message({
message: '登录信息不正确!',
type: 'error'
});
return false;
}
});
},
8.后端登录接口
// 登录
@PostMapping("/login")
public Result login(@RequestBody User user) {
if (StpUtil.isLogin()) {
User usertail =
userService
.lambdaQuery()
.eq(User::getUsername, user.getUsername())
.eq(User::getPassword, user.getPassword())
.list()
.get(0);
Role roleList = roleService.listRolesByUserId(Long.valueOf(usertail.getId()));
usertail.setRoles(roleList);
StpUtil.login(usertail.getId());
return Result.suc(usertail);
} else {
List list =
userService
.lambdaQuery()
.eq(User::getUsername, user.getUsername())
.eq(User::getPassword, user.getPassword())
.list();
if (list.size() > 0) {
User usertail = (User) list.get(0);
if (usertail.getStatus() == 0) {
return Result.fail("该账号已注销");
} else {
Role roleList = roleService.listRolesByUserId(Long.valueOf(usertail.getId()));
usertail.setRoles(roleList);
StpUtil.login(usertail.getId());
return Result.suc(usertail);
}
}
list =
userService
.lambdaQuery()
.eq(User::getEmail, user.getUsername())
.eq(User::getPassword, user.getPassword())
.list();
if (list.size() > 0) {
User usertail = (User) list.get(0);
if (usertail.getStatus() == 0) {
return Result.fail("该账号已注销");
} else {
Role roleList = roleService.listRolesByUserId(Long.valueOf(usertail.getId()));
usertail.setRoles(roleList);
StpUtil.login(usertail.getId());
return Result.suc(usertail);
}
}
return Result.fail("账号不存在或密码错误");
}
}
11.测试
十一、前端首页布局以及路由守卫功能
1.首页布局
创建Header.vue SideMenu.vue TabMenu.vue
<el-container>
<el-aside :width=aside_witdh>
<SideMenu :isCollapse="isCollapse"></SideMenu>
</el-aside>
<el-container>
<el-header
style="text-align: right; font-size: 12px;height: 8%;border-bottom: rgba(168,168,168,0.3) 1px solid;">
<Header @doCollapse="doCollapse" :icon="icon">
</Header>
</el-header>
<el-main>
<TabMenu></TabMenu>
<div style="margin: 0 15px;">
<router-view />
</div>
</el-main>
<el-footer style="text-align: center;height: 8%;">
@龙毅开发 2023.10.01
</el-footer>
</el-container>
</el-container>
引入组件
import SideMenu from "./SideMenu";
import TabMenu from "./TabMenu";
import Header from "./Header";
export default {
// eslint-disable-next-line vue/multi-word-component-names
name: "Home",
components: {
SideMenu, TabMenu, Header,
},
}
2.首页样式
.el-container {
padding: 0;
margin: 0;
height: 100%;
}
.header-avatar {
float: right;
width: 210px;
display: flex;
justify-content: space-around;
align-items: center;
}
.el-dropdown-link {
cursor: pointer;
}
.el-header {
background-color: #17B3A3;
color: #333;
text-align: center;
line-height: 60px;
}
.el-aside {
background-color: #D3DCE6;
color: #333;
line-height: 200px;
}
.el-main {
color: #333;
padding: 0;
}
a {
text-decoration: none;
}
.el-footer {
background-color: #17B3A3;
color: #333;
text-align: center;
line-height: 60px;
}
3.配置数据项
data() {
return {
user: {},
isCollapse: false,
aside_witdh: '200px',
icon: 'el-icon-s-fold'
}
},
4.侧边栏伸缩方法
methods: {
doCollapse() {
this.isCollapse = !this.isCollapse
if (!this.isCollapse) {// 展开
this.aside_witdh = '200px'
this.icon = 'el-icon-s-fold'
} else {//关起、关闭、收起
this.aside_witdh = '64px'
this.icon = 'el-icon-s-unfold'
}
},
}
5.初始化获取用户登录信息
created() {
this.getUser()
},
getUser() {
this.user = JSON.parse(localStorage.getItem("LocalUser"))
},
6.配置路由守卫
import store from "../store";
// router.beforeEach是Vue.js的路由守卫,在用户访问任何页面之前都会执行这个函数。
router.beforeEach((to, from, next) => {
// 获取Store中menus里的hasRoutes属性和localStorage中的LocalUser属性
let hasRoute = store.state.menus.hasRoutes;
let token = localStorage.getItem("LocalUser");
// 如果访问路径为/login或/Register,则直接跳转路由
if (to.path == "/login" || to.path == "/Register") {
next();
// 如果LocalStorage中没有LocalUser,则跳转至登录界面
} else if (!token) {
next({ path: "/login" });
// 如果LocalStorage中有LocalUser且Store中menus没有路由信息,则通过Api获取菜单信息并动态加载路由
} else if (token && !hasRoute) {
Vue.prototype.$axios
.post("http://localhost:8090/menu/nav", localStorage.getItem("LocalUser"),{headers: {
'Content-Type': 'application/json;charset=UTF-8'
}})
.then((res) => {
// 打印获取到的菜单和权限信息
console.log(res.data.data);
// 将菜单列表和权限列表保存到Store中
store.commit("setMenuList", res.data.data.nav);
store.commit("setPermList", res.data.data.auth);
console.log(store.state.menus.permList);
// 将菜单信息转为路由,并动态绑定到router实例的routes属性中
let newRoutes = router.options.routes;
res.data.data.nav.forEach((menu) => {
let route = menuToRoute(menu);
if (route) {
newRoutes[0].children.push(route);
}
if (menu.children) {
menu.children.forEach((e) => {
let route = menuToRoute(e);
if (route) {
newRoutes[0].children.push(route);
}
});
}
});
console.log("newRoutes");
console.log(newRoutes);
router.addRoutes(newRoutes);
// 修改Store中menus的hasRoutes为true
hasRoute = true;
store.commit("changeRouteStatus", hasRoute);
});
}
// 继续执行下一个路由
next();
});
7.菜单转换路由
// 导航转成路由函数,将菜单转换为路由对象
const menuToRoute = (menu) => {
if (!menu.component) {
return null;
}
let route = {
name: menu.name,
path: menu.path,
meta: {
icon: menu.icon,
title: menu.title,
},
};
// 路由懒加载,根据菜单信息动态加载组件
route.component = () => import("@/components/" + menu.component + ".vue");
console.log(route);
return route;
};
十二、前端首页头部功能
1.头部布局
<div style="line-height: 60px;">
<div style="margin-top: 8px;float: left;">
<i :class="icon" style="font-size: 20px;cursor: pointer;" @click="collapse"></i>
</div>
<div style="text-align: center;">
<strong>通用权限管理系统</strong>
<div class="header-avatar">
<el-avatar size="medium" :src="'http://localhost:8090/upload/avatar/' + user.avatar"></el-avatar>
<el-dropdown>
<span class="el-dropdown-link">
{{ user.username }}<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item @click.native="toAccount">个人中心
</el-dropdown-item>
<el-dropdown-item @click.native="logout">退出登录</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<el-link href="https://github.com/shengguanglongyi/PermissionAdmin.git" target="_blank">网站</el-link>
<el-link href="https://www.bilibili.com/video/BV1bH4y1Z7jM/"
target="_blank">B站</el-link>
</div>
</div>
</div>
.header-avatar {
float: right;
width: 210px;
display: flex;
justify-content: space-around;
align-items: center;
}
.el-dropdown-link {
cursor: pointer;
}
.el-header {
background-color: #17B3A3;
color: #333;
text-align: center;
line-height: 60px;
}
a {
text-decoration: none;
}
2.准备EventBus.js
import Vue from 'vue'
export default new Vue()
3.导入EventBus.js
import EventBus from '../../assets/EventBus.js';
4.配置数据
data() {
return {
//获取用户信息
user: JSON.parse(localStorage.getItem('LocalUser')),
}
},
// 接收父组件传递的icon属性
props: {
icon: String
},
5.监听用户更新数据
mounted() {
// 监听全局事件,用于更新当前用户信息
EventBus.$on('update-cur-user', this.updateCurUser);
},
methods: {
// 更新当前用户信息的方法
updateCurUser(user) {
this.user = JSON.parse(user)
},
}
6.监听导航标签页
computed: {
// 通过computed属性绑定this.$store.state.menus.editableTabs到editableTabs属性上
editableTabs: {
get() {
return this.$store.state.menus.editableTabs
},
set(val) {
this.$store.state.menus.editableTabs = val
}
},
// 通过computed属性绑定this.$store.state.menus.editableTabsValue到editableTabsValue属性上
editableTabsValue: {
get() {
return this.$store.state.menus.editableTabsValue
},
set(val) {
this.$store.state.menus.editableTabsValue = val
}
}
},
7.跳转个人中心
// 跳转至个人中心页面的方法
toAccount() {
let activeName = this.editableTabsValue;
if ("sys:account" != activeName && "sys:account" !== this.$route.name) {
// 如果当前激活的标签页不是个人中心页,则将激活的标签页设置为个人中心页,并跳转路由至个人中心页
let tab = this.$store.state.menus.menuList.find((e) => e.name === "sys:account");
// 先获取对应个人中心的菜单数据
this.$store.commit("addTab", tab);
// 然后再添加标签栏
this.$router.push({ name: "sys:account" });
// 最后跳转路由
} else if (
"sys:account" === activeName &&
"sys:account" !== this.$route.name
) {
// 如果当前激活的标签页是个人中心页且不是当前路由页,则直接跳转路由至个人中心页
this.$router.push({ name: "sys:account" });
}
},
8.退出登录
// 退出登录的方法
logout() {
// 弹出确认框,确认退出登录
this.$confirm('您确定要退出登录吗?', '提示', {
confirmButtonText: '确定', // 确认按钮的文字显示
type: 'warning',
center: true, // 文字居中显示
})
.then(() => {
// 发送退出登录请求
this.$axios.get(this.$url + '/user/logout').then(res => res.data).then(res => {
// console.log(res)
if (res.code == 200) {
// 重置全局状态
this.$store.commit("resetState")
// 跳转至登录页面
this.$router.push("/login")
} else {
// 提示错误消息
this.$message({
message: res.msg,
type: 'error'
});
}
});
})
.catch(() => {
// 提示取消退出登录信息
this.$message({
type: 'info',
message: '已取消退出登录'
})
})
},
9.退出登录后端接口
@GetMapping("/logout")
public SaResult logout(){
if(StpUtil.isLogin()){
StpUtil.logout();
}
return SaResult.ok();
}
10.折叠菜单方法
// 折叠展开菜单的方法,通过事件向父组件发送折叠命令
collapse() {
this.$emit('doCollapse')
}
十三、前端首页侧边菜单栏功能
1.菜单布局
<el-menu :default-active="this.$store.state.menus.editableTabsValue" class="el-menu-vertical-demo"
background-color="#545c64" text-color="#fff" :collapse="isCollapse" :collapse-transition="false"
:unique-opened="true" active-text-color="#ffd04b">
<el-menu-item>
<i>
<el-image style="width: 32px; height: 32px" :src="'http://localhost:8090/upload/file/logo'"></el-image>
</i>
<span slot="title">PermissionAdmin</span>
</el-menu-item>
<template v-for="menu in menuList">
<template v-if="menu.type == 1 && menu.parentId == 0">
<router-link :to="menu.path" :key="menu.id">
<el-menu-item :to="menu.path" @click="selectMenu(menu)" :index="menu.name">
<i :class="menu.icon"></i>
<span slot="title">{{ menu.title }}</span>
</el-menu-item>
</router-link>
</template>
<template v-if="menu.type == 0 && menu.parentId == 0">
<el-submenu :index="menu.name" :key="menu.id">
<template slot="title">
<i :class="menu.icon"></i>
<span>{{ menu.title }}</span>
</template>
<router-link :to="item.path" v-for="item in menu.children" :key="item.id">
<el-menu-item :index="item.name" @click="selectMenu(item)">
<template slot="title">
<i :class="item.icon"></i>
<span slot="title">{{ item.title }}</span>
</template>
</el-menu-item>
</router-link>
</el-submenu>
</template>
</template>
</el-menu>
.el-menu-vertical-demo {
height: 100%;
}
2.更新菜单
// 计算属性
computed: {
// menuList绑定到this.$store.state.menus.menuList上,当menuList发生变化时,会自动更新
menuList: {
get() {
console.log(this.$store.state.menus.menuList);
return this.$store.state.menus.menuList;
}
}
},
3.折叠菜单
// 属性定义,接收是否折叠的属性
props: {
isCollapse: Boolean
}
4.添加标签导航方法
methods: {
// 选择菜单项
selectMenu(item) {
// 调用$store.commit方法来将item添加到标签页中
this.$store.commit("addTab", item);
}
},
十四、前端首页导航标签栏功能
1.导航标签布局
<el-tabs v-model="editableTabsValue" type="card" closable @tab-remove="removeTab" @tab-click="clickTab">
<el-tab-pane v-for="(item) in editableTabs" :key="item.name" :label="item.title" :name="item.name">
</el-tab-pane>
</el-tabs>
2.更新导航标签
// 计算属性
computed: {
// 将this.$store.state.menus.editableTabs绑定到editableTabs上,并在修改时同步更新vuex中的状态
editableTabs: {
get() {
return this.$store.state.menus.editableTabs;
},
set(val) {
this.$store.state.menus.editableTabs = val;
}
},
// 将this.$store.state.menus.editableTabsValue绑定到editableTabsValue上,并在修改时同步更新vuex中的状态
editableTabsValue: {
get() {
return this.$store.state.menus.editableTabsValue;
},
set(val) {
this.$store.state.menus.editableTabsValue = val;
}
}
},
3.删除标签
// 删除标签页
removeTab(targetName) {
// 获取可编辑标签页列表
let tabs = this.editableTabs;
// 获取当前激活的标签页名称
let activeName = this.editableTabsValue;
console.log(tabs);
// 如果当前激活的标签页名称为"Home",则不能删除
if (activeName === "Home") {
return;
}
// 如果要删除的标签页名称为"Home",则跳转至"Home"页
if (targetName === "Home") {
this.$router.push({ name: targetName });
return;
}
// 遍历所有可编辑标签页
if (targetName === activeName) {
tabs.forEach((tab, index) => {
// 判断当前tab是否为要删除的tab
if (tab.name === activeName) {
console.log(activeName);
// 如果当前tab为最后一个tab,则激活前一个tab
if (index === tabs.length - 1) {
activeName = tabs[index - 1].name;
console.log(activeName);
tabs.splice(index, 1); // 删除tab
this.editableTabsValue = activeName;
this.editableTabs = tabs;
this.$router.push({ name: activeName });
} else { // 否则激活后一个tab
activeName = tabs[index + 1].name;
console.log(activeName);
tabs.splice(index, 1); // 删除tab
this.editableTabsValue = activeName;
this.editableTabs = tabs;
this.$router.push({ name: activeName });
}
}
});
} else {
tabs.forEach((tab, index) => {
if (tab.name === targetName) {
tabs.splice(index, 1); // 删除tab
console.log(activeName);
}
});
}
},
4.点击标签
// 点击标签页
clickTab(target) {
console.log(target);
// 如果点击的标签页名称不是当前路由名称,则跳转至该标签页
if (target.name !== this.$route.name) {
console.log(target.name);
this.$router.push({ name: target.name });
}
},
5.默认初始化标签
// 组件挂载后,调用clickFirstTab方法
mounted() {
this.clickFirstTab();
}
// 点击第一个标签页
clickFirstTab() {
let activeName = this.editableTabsValue;
// 如果当前激活的标签页是"Home",且当前路由不是"Home",则跳转至"Home"页
if ("Home" === activeName && "Home" !== this.$route.name) {
this.$router.push({ name: "Home" });
}
}
6.查看效果
创建Account/Account.vue System/Menu.vue System/Role.vue User/User.vue User/Role.vue
运行查看效果
十五、前端菜单管理的实现
1.按钮栏
<div style=" height: 50px;margin: 5px;float: right;">
<el-button type="success" @click="getMenuTree">
<i class="el-icon-refresh"></i></el-button>
<el-button type="primary" style="margin-left: 5px;" @click="dialogVisible = true"
> <i class="el-icon-plus"></i></el-button>
</div>
2.表格部分
<el-table :data="tableData" style="width: 100%;margin-bottom: 20px;" row-key="id" border stripe default-expand-all
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }">
<el-table-column prop="name" label="名称" sortable width="180">
</el-table-column>
<el-table-column prop="perms" label="权限编码" sortable width="180">
</el-table-column>
<el-table-column prop="icon" label="图标">
</el-table-column>
<el-table-column prop="type" label="类型">
<template slot-scope="scope">
<el-tag size="small" v-if="scope.row.type === 0" type="primary">目录</el-tag>
<el-tag size="small" v-else-if="scope.row.type === 1" type="success">菜单</el-tag>
<el-tag size="small" v-else-if="scope.row.type === 2" type="info">按钮</el-tag>
</template>
</el-table-column>
<el-table-column prop="path" label="菜单URL">
</el-table-column>
<el-table-column prop="component" label="菜单组件">
</el-table-column>
<el-table-column prop="ordernum" label="排序号">
</el-table-column>
<el-table-column prop="status" label="状态">
<template slot-scope="scope">
<el-tag size="small" v-if="scope.row.status === 1" type="success">正常</el-tag>
<el-tag size="small" v-else-if="scope.row.status === 0" type="danger">禁用</el-tag>
</template>
</el-table-column>
<el-table-column prop="icon" label="操作">
<template slot-scope="scope">
<el-button type="text" @click="editHandle(scope.row.id)"
>编辑</el-button>
<el-divider direction="vertical"></el-divider>
<template>
<el-popconfirm title="这是一段内容确定删除吗?" @confirm="delHandle(scope.row.id)"
>
<el-button type="text" slot="reference">删除</el-button>
</el-popconfirm>
</template>
</template>
</el-table-column>
</el-table>
3.增改表单
<el-dialog title="菜单表单" :visible.sync="dialogVisible" width="600px" :before-close="handleClose">
<el-form :model="editForm" :rules="editFormRules" ref="editForm" label-width="100px" class="demo-editForm">
<el-form-item label="上级菜单" prop="parentId">
<el-select v-model="editForm.parentId" placeholder="请选择上级菜单">
<template v-for="item in tableData">
<el-option :label="item.name" :value="item.id" :key="item.id"></el-option>
<template v-for="child in item.children">
<el-option :label="child.name" :value="child.id" :key="child.id">
<span>{{ "- " + child.name }}</span>
</el-option>
</template>
</template>
</el-select>
</el-form-item>
<el-form-item label="菜单名称" prop="name" label-width="100px">
<el-input v-model="editForm.name" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="权限编码" prop="perms" label-width="100px">
<el-input v-model="editForm.perms" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="图标" prop="icon" label-width="100px">
<el-input v-model="editForm.icon" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="菜单URL" prop="path" label-width="100px">
<el-input v-model="editForm.path" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="菜单组件" prop="component" label-width="100px">
<el-input v-model="editForm.component" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="类型" prop="type" label-width="100px">
<el-radio-group v-model="editForm.type">
<el-radio :label=0>目录</el-radio>
<el-radio :label=1>菜单</el-radio>
<el-radio :label=2>按钮</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="状态" prop="status" label-width="100px">
<el-radio-group v-model="editForm.status">
<el-radio :label=0>禁用</el-radio>
<el-radio :label=1>正常</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="排序号" prop="ordernum" label-width="100px">
<el-input-number v-model="editForm.ordernum" :min="1" label="排序号">1</el-input-number>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm('editForm')">确定</el-button>
<el-button @click="resetForm('editForm')">重置</el-button>
<el-button type="danger" @click="dialogVisible=false">取消</el-button>
</el-form-item>
</el-form>
</el-dialog>
4.表单数据配置
data() {
return {
dialogVisible: false,
editForm: {},
editFormRules: {
parentId: [
{ required: true, message: '请选择上级菜单', trigger: 'blur' }
],
name: [
{ required: true, message: '请输入名称', trigger: 'blur' }
],
perms: [
{ required: true, message: '请输入权限编码', trigger: 'blur' }
],
type: [
{ required: true, message: '请选择状态', trigger: 'blur' }
],
ordernum: [
{ required: true, message: '请填入排序号', trigger: 'blur' }
],
status: [
{ required: true, message: '请选择状态', trigger: 'blur' }
]
},
}
},
5.表格数据配置
tableData: [],
created() {
this.getMenuTree()
},
methods: {
getMenuTree() {
this.$axios.get(this.$url + "/menu/tree").then(res => {
this.tableData = res.data.data
})
},
}
6.删除菜单
delHandle(id) {
this.$axios.post(this.$url + "/menu/delete/" + id).then(res => {
console.log(res)
this.$message({
showClose: true,
message: '恭喜你,操作成功',
type: 'success',
});
this.getMenuTree()
})
}
7.增改菜单
submitForm(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
this.$axios.post(this.$url + '/menu/' + (this.editForm.id ? 'update' : 'save'), this.editForm)
.then(res => {
console.log(res)
this.$message({
showClose: true,
message: '恭喜你,操作成功',
type: 'success',
});
this.getMenuTree()
this.dialogVisible = false
})
} else {
return false;
}
});
},
editHandle(id) {
this.$axios.get(this.$url + '/menu/info/' + id).then(res => {
this.editForm = res.data.data
this.dialogVisible = true
})
},
resetForm(formName) {
this.$refs[formName].resetFields();
this.dialogVisible = false
this.editForm = {}
},
handleClose() {
this.resetForm('editForm')
},
十六、前端角色管理的实现
1.搜索栏
<div style=" height: 50px;margin: 5px;">
<el-input v-model="name" placeholder="名称" suffix-icon="el-icon-search" style="width: 200px;"
@keyup.enter.native="getRoleList"></el-input>
<el-select v-model="status" filterable placeholder="请选择权限状态" style="margin-left: 5px;">
<el-option v-for="item in statusmap" :key="item.value" :label="item.label" :value="item.value">
</el-option>
</el-select>
<el-button type="primary" style="margin-left: 5px;" @click="getRoleList"> <i
class="el-icon-search"></i></el-button>
<el-button type="success" @click="resetParam">
<i class="el-icon-refresh"></i></el-button>
<el-button type="primary" style="margin-left: 5px;" @click="dialogVisible = true"
> <i class="el-icon-plus"></i></el-button>
<el-popconfirm title="这是确定批量删除吗?" @confirm="delHandle(null)" >
<el-button type="danger" slot="reference" :disabled="delBtlStatu">批量删除</el-button>
</el-popconfirm>
</div>
2.表格部分
<el-table ref="multipleTable" :data="tableData" tooltip-effect="dark" style="width: 100%" border stripe
@selection-change="handleSelectionChange">
<el-table-column type="selection" width="55">
</el-table-column>
<el-table-column prop="name" label="名称" width="120">
</el-table-column>
<el-table-column prop="code" label="唯一编码" show-overflow-tooltip>
</el-table-column>
<el-table-column prop="remark" label="描述" show-overflow-tooltip>
</el-table-column>
<el-table-column prop="status" label="状态">
<template slot-scope="scope">
<el-tag size="small" v-if="scope.row.status === 1" type="success">正常</el-tag>
<el-tag size="small" v-else-if="scope.row.status === 0" type="danger">禁用</el-tag>
</template>
</el-table-column>
<el-table-column prop="icon" label="操作">
<template slot-scope="scope">
<el-button type="text" @click="permHandle(scope.row.id)">分配权限</el-button>
<el-divider direction="vertical"></el-divider>
<el-button type="text" @click="editHandle(scope.row.id)">编辑</el-button>
<el-divider direction="vertical"></el-divider>
<template>
<el-popconfirm title="你确定删除吗?" @confirm="delHandle(scope.row.id)">
<el-button type="text" slot="reference">删除</el-button>
</el-popconfirm>
</template>
</template>
</el-table-column>
</el-table>
3.分页部分
<el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="pageNum"
:page-sizes="[5, 10, 20, 30]" :page-size="pageSize" layout="total, sizes, prev, pager, next, jumper"
:total="total">
</el-pagination>
<style scoped>
.el-pagination {
text-align: center;
margin-top: 22px;
}
</style>
4.增改表单
<el-dialog title="角色表单" :visible.sync="dialogVisible" width="600px" :before-close="handleClose">
<el-form :model="editForm" :rules="editFormRules" ref="editForm" label-width="100px" class="demo-editForm">
<el-form-item label="角色名称" prop="name" label-width="100px">
<el-input v-model="editForm.name" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="唯一编码" prop="code" label-width="100px">
<el-input v-model="editForm.code" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="描述" prop="remark" label-width="100px">
<el-input v-model="editForm.remark" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="状态" prop="status" label-width="100px">
<el-radio-group v-model="editForm.status">
<el-radio :label=0>禁用</el-radio>
<el-radio :label=1>正常</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm('editForm')">确定</el-button>
<el-button @click="resetForm('editForm')">重置</el-button>
<el-button type="danger" @click="dialogVisible=false">取消</el-button>
</el-form-item>
</el-form>
</el-dialog>
5.分配权限表单
<el-dialog title="分配权限" :visible.sync="permDialogVisible" width="600px">
<el-form :model="permForm">
<el-tree :data="permTreeData" show-checkbox ref="permTree" :default-expand-all=true node-key="id"
:check-strictly=true :props="defaultProps">
</el-tree>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="permDialogVisible = false">取 消</el-button>
<el-button type="primary" @click="submitPermFormHandle('permForm')">确 定</el-button>
</span>
</el-dialog>
6.数据配置
data() {
return {
//表格数据
tableData: [],
//分页配置
pageSize: 10,
pageNum: 1,
total: 0,
//条件搜索
name: '',
status: '',
statusmap: [
{
value: 1,
label: '正常'
}, {
value: 0,
label: '禁用'
},
],
//多选删除
delBtlStatu: true,
multipleSelection: [],
//增改表单
dialogVisible: false,
editForm: {},
editFormRules: {
name: [
{ required: true, message: '请输入角色名称', trigger: 'blur' }
],
code: [
{ required: true, message: '请输入唯一编码', trigger: 'blur' }
],
status: [
{ required: true, message: '请选择状态', trigger: 'blur' }
]
},
//权限树数据
permTreeData: [],
defaultProps: {
children: 'children',
label: 'name'
},
权限分配表单
permDialogVisible: false,
permForm: {},
}
},
7.初始化数据获取
created() {
this.getRoleList()
this.getpermTree()
},
methods: {
getpermTree(){
this.$axios.get(this.$url + '/menu/tree').then(res => {
this.permTreeData = res.data.data
})
},
//重置条件搜索框
resetParam() {
this.name = "";
this.status = "";
},
getRoleList() {
this.$axios.post(this.$url + "/role/listPage", {
pageSize: this.pageSize,
pageNum: this.pageNum,
param: {
name: this.name,
status: this.status + ''
}
}).then(res => {
console.log(res)
this.tableData = res.data.data
this.total = res.data.total
})
},
}
8.多选删除
toggleSelection(rows) {
if (rows) {
rows.forEach(row => {
this.$refs.multipleTable.toggleRowSelection(row);
});
} else {
this.$refs.multipleTable.clearSelection();
}
},
handleSelectionChange(val) {
this.multipleSelection = val;
this.delBtlStatu = val.length == 0
},
delHandle(id) {
var ids = [];
if (id) {
ids.push(id);
} else {
this.multipleSelection.forEach((row) => {
ids.push(row.id);
});
}
this.$axios.post(this.$url + "/role/deleteBatch", ids).then((res) => {
console.log(res);
this.$message({
showClose: true,
message: "恭喜你,操作成功",
type: "success",
});
this.getRoleList();
});
},
8.增改角色
resetForm(formName) {
this.$refs[formName].resetFields();
this.dialogVisible = false;
this.editForm = {};
},
handleClose() {
this.resetForm("editForm");
},
submitForm(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
this.$axios
.post(
this.$url + "/role/" + (this.editForm.id ? "update" : "save"),
this.editForm
)
.then((res) => {
console.log(res);
this.$message({
showClose: true,
message: "恭喜你,操作成功",
type: "success",
});
this.getRoleList();
this.dialogVisible = false;
this.resetForm(formName);
});
} else {
return false;
}
});
},
editHandle(id) {
this.$axios.get(this.$url + "/role/info/" + id).then((res) => {
this.editForm = res.data.data;
this.dialogVisible = true;
});
},
9.分页函数
handleSizeChange(val) {
console.log(`每页 ${val} 条`);
this.size = val;
this.getRoleList();
},
handleCurrentChange(val) {
console.log(`当前页: ${val}`);
this.current = val;
this.getRoleList();
},
10.权限分配
permHandle(id) {
this.permDialogVisible = true;
this.$axios.get(this.$url + "/role/info/" + id).then((res) => {
this.$refs.permTree.setCheckedKeys(res.data.data.menuIds);
this.permForm = res.data.data;
});
},
submitPermFormHandle() {
var menuIds = this.$refs.permTree.getCheckedKeys();
this.$axios
.post(this.$url + "/role/perm/" + this.permForm.id, menuIds)
.then((res) => {
console.log(res);
this.$message({
showClose: true,
message: "恭喜你,操作成功",
type: "success",
});
this.getRoleList();
this.permDialogVisible = false;
});
},
11.测试
添加用户角色,并分配权限
十七、前端用户管理的实现(上)
1.搜索栏
<div style=" height: 50px;margin: 5px;">
<el-input v-model="username" placeholder="请输入用户姓名" suffix-icon="el-icon-search" style="width: 200px;"
@keyup.enter.native="loadPost"></el-input>
<el-input v-model="email" placeholder="请输入用户邮箱" suffix-icon="el-icon-search" style="width: 200px;"
@keyup.enter.native="loadPost"></el-input>
<el-select v-model="status" filterable placeholder="请选择用户状态" style="margin-left: 5px;">
<el-option v-for="item in statusmap" :key="item.value" :label="item.label" :value="item.value">
</el-option>
</el-select>
<el-button type="primary" style="margin-left: 5px;" @click="loadPost">
<i class="el-icon-search"></i></el-button>
<el-button type="success" @click="resetParam"> <i class="el-icon-refresh"></i></el-button>
<el-button type="primary" style="margin-left: 5px;" @click="add" >
<i class="el-icon-plus"></i></el-button>
<el-popconfirm title="这是确定批量删除吗?" @confirm="delHandle(null)">
<el-button type="danger" slot="reference" :disabled="delBtlStatu">批量删除</el-button>
</el-popconfirm>
</div>
2.表格部分
<el-table ref="multipleTable" :data="tableData" tooltip-effect="dark" style="width: 100%" border stripe
@selection-change="handleSelectionChange">
<el-table-column type="selection" width="55">
</el-table-column>
<el-table-column label="头像" width="50">
<template slot-scope="scope">
<el-avatar size="small" :src="'http://localhost:8090/upload/avatar/' + scope.row.avatar"
v-if="scope.row.avatar"></el-avatar>
</template>
</el-table-column>
<el-table-column prop="username" label="用户名" width="120">
</el-table-column>
<el-table-column prop="Roles" label="角色名称">
<template slot-scope="scope">
<el-tag size="small" type="info">{{ scope.row.roles.name
}}</el-tag>
</template>
</el-table-column>
<el-table-column prop="email" label="邮箱" width="200">
</el-table-column>
<el-table-column prop="status" label="状态">
<template slot-scope="scope">
<el-tag size="small" v-if="scope.row.status === 1" type="success">正常</el-tag>
<el-tag size="small" v-else-if="scope.row.status === 0" type="danger">禁用</el-tag>
</template>
</el-table-column>
<el-table-column prop="created" width="200" label="注册时间" :formatter="formatDate">
</el-table-column>
<el-table-column prop="icon" width="260px" label="操作">
<template slot-scope="scope">
<el-button type="text" @click="mod(scope.row.id)">编辑</el-button>
<el-divider direction="vertical"></el-divider>
<el-popconfirm title="确定删除吗?" @confirm="dele(scope.row.id)" >
<el-button type="text" slot="reference">删除</el-button>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
3.分页部分
参上
4.增改表单
<el-dialog title="用户表单" :visible.sync="centerDialogVisible" width="50%" center>
<el-form :model="form" :rules="rules" ref="form" label-width="100px" style="width: 800px; margin: 10px auto;"
method="post" enctype="multipart/form-data">
<el-row>
<el-col :span="12">
<el-form-item label="用户姓名" prop="username">
<el-input v-model="form.username"></el-input>
</el-form-item>
<el-form-item label="用户密码" prop="password">
<el-input type="password" v-model="form.password"></el-input>
</el-form-item>
<el-form-item label="用户邮箱" prop="email">
<el-input v-model="form.email" autocomplete="off"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="用户头像" prop="avater">
<div class="img-show" v-if="imgUrl">
<img :src="imgUrl" class="avatar" v-if="!ismod">
<img :src="'http://localhost:8090/upload/avatar/' + form.avatar" class="avatar"
v-if="ismod">
<span class="actions">
<!-- 删除 -->
<span class="item">
<i class="el-icon-delete" @click="del()"></i>
</span>
</span>
</div>
<!-- 图片上传 -->
<el-upload v-else action="#" class="uploader-avatar" list-type="picture" :auto-upload="false"
:show-file-list="false" :on-change="imgPreview">
<i class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
</el-form-item>
</el-col>
</el-row>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="centerDialogVisible = false">取 消</el-button>
<el-button type="primary" @click="save">确 定</el-button>
</span>
</el-dialog>
5.头像上传部分样式
.uploader-avatar {
display: flex;
/* 添加flex属性 */
justify-content: center;
/* 水平居中 */
align-items: center;
/* 垂直居中 */
background-color: #fbfdff;
border: 1px dashed #c0ccda;
border-radius: 6px;
box-sizing: border-box;
width: 200px;
height: 200px;
cursor: pointer;
line-height: 300px;
vertical-align: top;
margin-bottom: 10px;
overflow: hidden;
}
.img-show {
position: relative;
border: 1px solid #c0ccda;
border-radius: 6px;
box-sizing: border-box;
width: 200px;
height: 200px;
cursor: pointer;
overflow: hidden;
}
.uploader-avatar:hover {
border-color: #409EFF;
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
}
.avatar {
width: 200px;
height: 200px;
display: block;
}
.actions {
position: absolute;
width: 100%;
height: 100%;
line-height: 300px;
left: 0;
top: 0;
cursor: default;
text-align: center;
color: #fff;
opacity: 0;
font-size: 20px;
background-color: rgba(0, 0, 0, .5);
transition: opacity .3s;
}
.actions:hover {
opacity: 1;
}
.actions:hover span {
display: inline-block;
}
.actions span {
display: none;
margin: 0 16px;
cursor: pointer;
}
6.数据配置
data() {
return {
//表格数据
tableData: [],
//分页配置
pageSize: 5,
pageNum: 1,
total: 0,
//条件查询
username: '',
status: '',
statusmap: [
{
value: 1,
label: '正常'
}, {
value: 0,
label: '注销'
},
],
email: '',
//多选删除
delBtlStatu: true,
multipleSelection: [],
//增改表单
centerDialogVisible: false,
form: {
id: '',
username: '',
password: '',
email: '',
avatar: '',
},
rules: {
username: [
{ required: true, message: '请输入用户姓名', trigger: 'blur' }
],
password: [
{ required: true, message: '请输入用户密码', trigger: 'blur' }
],
roleId: [
{ required: true, message: '请选择用户角色', trigger: 'blur' }
],
email: [
{ required: true, message: '请输入邮箱', trigger: 'blur' }
],
status: [
{ required: true, message: '请选择状态', trigger: 'blur' }
]
},
// 头像数据
imgUrl: '',
dialogUrl: '',
ismod: false,
dialogVisible: false,
file: null,
}
},
7.分页条件查询
mounted() {
this.loadPost();
},
methods: {
//格式化时间显示
formatDate(row, column) {
let data = row[column.property];
if (data == null) {
return null;
}
let dt = new Date(data);
return (
dt.getFullYear() +
"-" +
(dt.getMonth() + 1) +
"-" +
dt.getDate() +
" " +
dt.getHours() +
":" +
dt.getMinutes() +
":" +
dt.getSeconds()
);
},
// 重置条件搜索
resetParam() {
this.username = ''
this.status = ''
this.email=''
},
loadPost() {
this.$axios.post(this.$url + '/user/listPage', {
pageSize: this.pageSize,
pageNum: this.pageNum,
param: {
username: this.username,
status: this.status + '',
email:this.email }
}).then(res => res.data).then(res => {
console.log(res)
if (res.code == 200) {
this.tableData = res.data
this.total = res.total
} else {
alert('获取数据失败')
}
})
},
}
8.头像上传部分代码
imgPreview: function (file) {
//生成临时缩略图
this.imgUrl = URL.createObjectURL(file.raw);
this.file = file
},
del: function () {
this.imgUrl = '';
this.form.avatar = ''
this.ismod = false
},
submitUpload() {
if (this.file == null) {
this.$message({
message: '请先选择用户头像',
type: 'error'
});
return false;
} else {
const config = {
headers: {
'Content-Type': 'multipart/form-data'
}
};
if (this.beforeUpload(this.file.name)) {
let fd = new FormData();
fd.append("file", this.file.raw);
fd.append("fileName", this.file.name)
console.log(this.file.raw)
this.$axios.post(this.$url + "/upload/avatar", fd, config)
.then(res => {
console.log(res);
if (res.status === 200) {
this.form.avatar = res.data.data
this.$message({
message: '上传成功!',
type: 'success'
});
} else {
this.$message({
message: '未上传成功!',
type: 'error'
});
}
})
} else {
return false;
}
}
},
handleSuccess(response) {
console.log(response)
this.$message({
message: '上传成功',
type: 'success',
});
return true;
},
beforeUpload(fileName) {
const isStl = this.checkImgType(fileName)
if (!isStl) {
this.$message.error('只能上传图片文件');
}
return isStl;
},
checkImgType(fileName) {
//用文件名name后缀判断文件类型,可用size属性判断文件大小不能超过500k , 前端直接判断的好处,免去服务器的压力。
console.log(fileName)
if (!/\.(jpg|jpeg|png)$/.test(fileName)) {
return false;
} else {
return true;
}
},
9.多选删除
参上
10.增改用户
resetForm() {
this.form = {
id: "",
username: "",
password: "",
email: "",
avatar: "",
};
(this.imgUrl = ""),
(this.dialogUrl = ""),
(this.ismod = false),
(this.dialogVisible = false),
(this.file = null),
(this.dialogVisible = false);
},
handleClose() {
this.resetForm();
},
add() {
this.centerDialogVisible = true;
this.$nextTick(() => {
this.resetForm();
});
},
mod(id) {
this.$axios.get(this.$url + "/user/info/" + id).then((res) => {
this.form = res.data.data;
console.log(this.form);
if (this.form.avatar != undefined) {
this.imgUrl = true;
this.ismod = true;
}
this.centerDialogVisible = true;
});
},
async save() {
if (this.form.avatar == "" || this.form.avatar == null) {
await this.submitUpload();
}
setTimeout(() => {
this.$refs.form.validate((valid) => {
if (valid) {
if (this.form.id) {
this.doMod();
} else {
this.doSave();
}
} else {
console.log("error submit!!");
return false;
}
});
}, 500);
},
doSave() {
this.$axios
.post(this.$url + "/user/save", this.form)
.then((res) => res.data)
.then((res) => {
console.log(res);
if (res.code == 200) {
this.$message({
message: "添加成功!",
type: "success",
});
this.centerDialogVisible = false;
//关闭弹窗
this.loadPost();
//重新加载数据
this.resetForm("form");
//重置表单数据
} else {
this.$message({
message: "添加失败!",
type: "error",
});
}
});
},
doMod() {
this.$axios
.post(this.$url + "/user/update", this.form)
.then((res) => res.data)
.then((res) => {
console.log(res);
if (res.code == 200) {
this.$message({
message: "修改成功!",
type: "success",
});
this.centerDialogVisible = false;
this.loadPost();
this.resetForm("form");
} else {
this.$message({
message: "修改失败!",
type: "error",
});
}
});
},
11.删除用户
dele(id) {
this.$axios
.get(this.$url + "/user/delete?id=" + id)
.then((res) => res.data)
.then((res) => {
console.log(res);
if (res.code == 200) {
this.$message({
message: "删除成功!",
type: "success",
});
this.loadPost();
} else {
this.$message({
message: "删除失败!",
type: "error",
});
}
});
},
十八、前端用户权限管理的实现(下)
1.搜索栏
<div style=" height: 50px;margin: 5px;">
<el-input v-model="username" placeholder="请输入用户姓名" suffix-icon="el-icon-search" style="width: 200px;"
@keyup.enter.native="loadPost"></el-input>
<el-select v-model="status" filterable placeholder="请选择用户状态" style="margin-left: 5px;">
<el-option v-for="item in statusmap" :key="item.value" :label="item.label" :value="item.value">
</el-option>
</el-select>
<el-button type="primary" style="margin-left: 5px;" @click="loadPost"> <i class="el-icon-search"></i></el-button>
<el-button type="success" @click="resetParam"> <i class="el-icon-refresh"></i></el-button>
</div>
2.表格部分
<el-table
:data="tableData"
tooltip-effect="dark"
style="width: 100%;text-align:center;"
border
>
<el-table-column prop="username" label="用户名" width="120">
</el-table-column>
<el-table-column label="头像" >
<template slot-scope="scope">
<el-avatar
size="small"
:src="'http://localhost:8090/upload/avatar/' + scope.row.avatar"
v-if="scope.row.avatar"
></el-avatar>
</template>
</el-table-column>
<el-table-column prop="Roles" label="角色名称">
<template slot-scope="scope">
<el-tag size="small" type="info">{{ scope.row.roles.name }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="email" label="邮箱" width="200"> </el-table-column>
<el-table-column prop="status" label="状态">
<template slot-scope="scope">
<el-tag size="small" v-if="scope.row.status === 1" type="success"
>正常</el-tag
>
<el-tag size="small" v-else-if="scope.row.status === 0" type="danger"
>禁用</el-tag
>
</template>
</el-table-column>
<el-table-column
prop="created"
width="200"
label="注册时间"
:formatter="formatDate"
>
</el-table-column>
<el-table-column prop="icon" width="400px" label="操作">
<template slot-scope="scope">
<el-button
type="text"
@click="roleHandle(scope.row.id)"
>分配角色</el-button
>
<el-divider direction="vertical"></el-divider>
<el-switch
:value="scope.row.status === 1"
active-text="启用"
inactive-text="禁用"
style="margin-right: 20px"
@change="modStatus(scope.row.id)"
></el-switch>
<el-divider direction="vertical"></el-divider>
<el-button
type="text"
@click="repassHandle(scope.row.id, scope.row.username)"
>重置密码</el-button
>
<el-divider direction="vertical"></el-divider>
</template>
</el-table-column>
</el-table>
3.分页部分
参上
4.分配角色表单
<el-dialog
title="分配角色"
:visible.sync="roleDialogFormVisible"
width="600px"
>
<el-radio-group v-model="roleId" size="mini">
<div
v-for="role in roleData"
:key="role.id"
style="width: 100%; padding: 10px"
>
<el-radio :label="role.id" border>{{ role.name }}</el-radio>
</div>
</el-radio-group>
<div slot="footer" class="dialog-footer">
<el-button @click="roleDialogFormVisible = false">取 消</el-button>
<el-button type="primary" @click="submitRoleHandle()">确 定</el-button>
</div>
</el-dialog>
5.数据配置
data() {
return {
//表格数据
tableData: [],
//角色数据
roleData: [],
//分页数据
pageSize: 5,
pageNum: 1,
total: 0,
//条件搜索
username: "",
status: "",
statusmap: [
{
value: 1,
label: "正常",
},
{
value: 0,
label: "注销",
},
],
//角色分配表单
roleId: "",
roleDialogFormVisible: false,
};
},
6.初始化数据
mounted() {
this.loadPost();
this.getRole();
},
methods: {
getRole() {
this.$axios.get(this.$url + "/role/list").then((res) => {
this.roleData = res.data;
});
},
formatDate(row, column) {
let data = row[column.property];
if (data == null) {
return null;
}
let dt = new Date(data);
return (
dt.getFullYear() +
"-" +
(dt.getMonth() + 1) +
"-" +
dt.getDate() +
" " +
dt.getHours() +
":" +
dt.getMinutes() +
":" +
dt.getSeconds()
);
},
resetParam() {
this.name = "";
this.status = "";
},
loadPost() {
this.$axios
.post(this.$url + "/user/listPage", {
pageSize: this.pageSize,
pageNum: this.pageNum,
param: {
name: this.name,
status: this.status + "",
},
})
.then((res) => res.data)
.then((res) => {
console.log(res);
if (res.code == 200) {
this.tableData = res.data;
this.total = res.total;
} else {
alert("获取数据失败");
}
});
},
},
7.禁用/启用用户
modStatus(id) {
this.$axios
.post(this.$url + "/user/control/" + id)
.then((res) => res.data)
.then((res) => {
console.log(res);
if (res.code == 200) {
this.loadPost();
} else {
this.$message({
message: "操作失败!",
type: "error",
});
}
});
},
8.分配角色
roleHandle(id) {
this.roleDialogFormVisible = true;
this.$axios.get(this.$url + "/user/info/" + id).then((res) => {
this.roleId = res.data.data.roles.id;
});
},
submitRoleHandle() {
this.$axios
.post(
this.$url +
"/user/role?userId=" +
this.user.id +
"&roleId=" +
this.roleId
)
.then((res) => {
console.log(res);
this.$message({
showClose: true,
message: "恭喜你,操作成功",
type: "success"
});
this.loadPost();
this.roleDialogFormVisible = false;
});
},
9.重置密码
repassHandle(id, username) {
this.$confirm("将重置用户【" + username + "】的密码, 是否继续?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(() => {
this.$axios
.post(this.$url + "/user/repass", id, {
headers: {
"Content-Type": "application/json;charset=UTF-8",
},
})
.then((res) => {
console.log(res);
this.$message({
showClose: true,
message: "恭喜你,操作成功",
type: "success",
});
})
.catch((err) => {
console.error(err);
this.$message({
showClose: true,
message: "操作失败,请重试",
type: "error",
});
});
})
.catch(() => {
this.$message({
showClose: true,
message: "已取消操作",
type: "info",
});
});
},
十九、按钮级别权限控制
1.配置数据项
//用户信息
user: JSON.parse(localStorage.getItem("LocalUser")),
//用户权限信息
Authority: "",
2.获取权限数据
mounted() {
this.getAuthority();
},
methods: {
getAuthority() {
this.$axios.get(this.$url + "/user/auth/" + this.user.id).then((res) => {
this.Authority = res.data;
});
},
}
3.权限判断函数
hasAuth(auth) {
return this.Authority.includes(auth);
},
4.权限控制
menu部分
<el-button type="primary" style="margin-left: 5px;" @click="dialogVisible = true"
v-if="hasAuth('sys:menu:save')"> <i class="el-icon-plus"></i></el-button>
<el-button type="text" @click="editHandle(scope.row.id)"
v-if="hasAuth('sys:menu:update')">编辑</el-button>
<el-popconfirm title="这是一段内容确定删除吗?" @confirm="delHandle(scope.row.id)"
v-if="hasAuth('sys:menu:delete')">
<el-button type="text" slot="reference">删除</el-button>
</el-popconfirm>
role部分
<el-button
type="primary"
style="margin-left: 5px"
@click="dialogVisible = true"
v-if="hasAuth('sys:role:save')"
>
<i class="el-icon-plus"></i
></el-button>
<el-popconfirm
title="这是确定批量删除吗?"
@confirm="delHandle(null)"
v-if="hasAuth('sys:role:delete')"
>
<el-button type="danger" slot="reference" :disabled="delBtlStatu"
>批量删除</el-button
>
</el-popconfirm>
<el-button type="text" @click="permHandle(scope.row.id)" v-if="hasAuth('sys:role:perm')"
>分配权限</el-button
>
<el-divider direction="vertical"></el-divider>
<el-button type="text" @click="editHandle(scope.row.id)" v-if="hasAuth('sys:role:edit')"
>编辑</el-button
>
<el-divider direction="vertical"></el-divider>
<template>
<el-popconfirm v-if="hasAuth('sys:role:delete')"
title="你确定删除吗?"
@confirm="delHandle(scope.row.id)"
>
<el-button type="text" slot="reference">删除</el-button>
</el-popconfirm>
</template>
user这一块自行补充
二十、个人中心功能的实现(上)
1.个人中心布局
三行布局
第一行4列个人信息
第二行3列修改个人信息功能
第三行2列系统功能
<div style="text-align: center;background-color: #f1f1f3;height: 100%;padding: 0px;margin: 0px;">
<el-row :gutter="12" class="card-row">
<el-col :span="6">
</el-col>
<el-col :span="6">
</el-col>
<el-col :span="6">
</el-col>
<el-col :span="6">
</el-col>
</el-row>
<el-row :gutter="12" class="card-row">
<el-col :span="8">
</el-col>
<el-col :span="8">
</el-col>
<el-col :span="8">
</el-col>
</el-row>
<el-row :gutter="12" class="card-row">
<el-col :span="12">
</el-col>
<el-col :span="12">
</el-col>
</el-row>
</div>
2.Card组件样式
<style scoped>
.box-card {
height: 150px;
text-align: center;
margin: 20px;
}
.card-row {
display: flex;
justify-content: center;
}
.centered-card {
display: flex;
justify-content: center;
align-items: center;
}
</style>
3.自定义card组件颜色
.red {
background-color: rgb(231, 9, 9);
}
.green {
background-color: rgb(152, 246, 11);
}
.purple {
background-color: rgb(135, 53, 212);
}
.blue {
background-color: rgb(10, 138, 207);
}
.orange {
background-color: chocolate;
}
.grey {
background-color: grey;
}
.pink {
background-color: blanchedalmond;
}
.gold {
background-color: goldenrod;
}
4.card组件的应用
第一行:
<el-card shadow="hover" class="box-card centered-card">
<div>
<i class="el-icon-s-custom"></i>
用户名
<el-tag type="success">
{{ user.username }}
</el-tag>
</div>
</el-card>
<el-card shadow="hover" class="box-card centered-card" style="display: flex; align-items: center;">
<div>
<el-avatar size="large" :src="'http://localhost:8090/upload/avatar/' + user.avatar" fit="cover"
style="width:100px;height:100px"></el-avatar>
</div>
<el-button type="success" size="small" @click="modAvatar">更换头像</el-button>
</el-card>
<el-card shadow="hover" class="box-card centered-card">
<div>
<i class="el-icon-s-promotion"></i>
角色
<el-tag disable-transitions
:type="user.roles.id == 1 ? 'success' : user.roles.id == 1 ? 'primary' : 'danger'">
{{ user.roles.name
}}
</el-tag>
</div>
</el-card>
<el-card shadow="hover" class="box-card centered-card">
<div>
<i class="el-icon-s-promotion"></i>
邮箱
<el-tag disable-transitions>
{{ user.email }}
</el-tag>
</div>
</el-card>
第二行:
<el-card shadow="hover" class="box-card centered-card">
<div>
<el-button class="el-icon-edit-outline" type="success" @click="name()">修改昵称</el-button>
</div>
</el-card>
<el-card shadow="hover" class="box-card centered-card">
<div>
<el-button class="el-icon-lock" type="success" @click="pwd()"> 修改密码</el-button>
</div>
</el-card>
<el-card shadow="hover" class="box-card centered-card" v-if="user.email">
<div>
<el-button class="el-icon-message" type="success" @click="email()"> 修改邮箱</el-button>
</div>
</el-card>
<el-card shadow="hover" class="box-card centered-card" v-if="!user.email">
<div>
<el-button class="el-icon-message" type="success" @click="email()"> 绑定邮箱</el-button>
</div>
</el-card>
第三行:
<el-card shadow="hover" class="box-card centered-card">
<div>
<el-button class="el-icon-switch-button" type="danger" @click="logout">退出登录</el-button>
</div>
</el-card>
<el-card shadow="hover" class="box-card centered-card">
<div>
<el-button class="el-icon-delete" type="danger" @click="delUser">注销账号</el-button>
</div>
</el-card>
给card加上自定义颜色
5.数据配置
data() {
return {
user: {},
dialogNameVisible: false,
dialogPwdVisible: false,
dialogEmailVisible: false,
roleData: [],
}
},
methods: {
init() {
this.user = JSON.parse(localStorage.getItem('LocalUser'))
},
pwd() {
this.dialogPwdVisible = true
},
name() {
this.dialogNameVisible = true
},
email() {
this.dialogEmailVisible = true
},
delUser() {
this.$confirm('您确定要注销账号吗?', '提示', {
confirmButtonText: '确定', //确认按钮的文字显示
type: 'warning',
center: true, //文字居中显示
})
.then(() => {
this.$axios.post(this.$url + '/user/deleteLogical/' + this.user.id).then(res => res.data).then(res => {
console.log(res)
if (res) {
this.$message({
message: '注销账号成功!',
type: 'success'
});
this.$axios.get(this.$url + '/user/logout').then(res => res.data).then(res => {
if (res.code == 200) {
this.$store.commit("resetState")
this.$message({
type: 'success',
message: '退出登录成功'
})
this.$router.push("/login")
} else {
this.$message({
message: res.msg,
type: 'error'
});
}
})
} else {
this.$message({
message: '注销失败!',
type: 'error'
});
}
})
})
.catch(() => {
this.$message({
type: 'info',
message: '已取消注销账号'
})
})
},
logout() {
//退出登录
this.$confirm('您确定要退出登录吗?', '提示', {
confirmButtonText: '确定', //确认按钮的文字显示
type: 'warning',
center: true, //文字居中显示
})
.then(() => {
this.$axios.get(this.$url + '/user/logout').then(res => res.data).then(res => {
// console.log(res)
if (res.code == 200) {
this.$store.commit("resetState")
this.$message({
type: 'success',
message: '退出登录成功'
})
this.$router.push("/login")
} else {
this.$message({
message: res.msg,
type: 'error'
});
}
});
})
.catch(() => {
this.$message({
type: 'info',
message: '已取消退出登录'
})
})
},
loadRole() {
this.$axios.get(this.$url + '/role/list').then(res => res.data).then(res => {
console.log(res)
this.roleData = res
})
},
getUserById() {
this.$axios.get(this.$url + '/user/info/' + this.user.id).then(res => res.data).then(res => {
console.log(res)
if (res) {
sessionStorage.setItem("LocalUser", JSON.stringify(res.data))
this.$store.commit("SETUSER", JSON.stringify(res.data))
this.user = res.data
} else {
this.$message({
message: res.msg,
type: 'error'
});
}
})
}
},
created() {
this.init()
this.getUserById()
this.loadRole()
}
6.退出登录后端接口
@GetMapping("/logout")
public SaResult logout(){
if(StpUtil.isLogin()){
StpUtil.logout();
}
return SaResult.ok();
}
7.修改头像方法
//注入EventBus.js
import EventBus from '../../assets/EventBus.js';
modAvatar() {
const input = document.createElement('input');
input.type = 'file';
input.accept = 'image/*';
input.onchange = async () => {
const file = input.files[0];
if (file) {
// 使用 FormData 构造表单数据
let fd = new FormData();
fd.append("file", file); // 修改这行代码
fd.append("fileName", file.name);
// 发送 POST 请求将表单数据提交到服务器
try {
const config = {
headers: {
'Content-Type': 'multipart/form-data'
}
};
await this.$axios.post(this.$url + "/upload/avatar", fd, config)
.then(res => {
console.log(res);
if (res.status === 200) {
this.modUserAvatar(res.data.data);
this.$message({
message: '修改成功!',
type: 'success'
});
} else {
this.$message({
message: '未上传成功!',
type: 'error'
});
}
});
} catch (err) {
console.error(err);
// 提示用户上传失败
this.$message.error('修改失败,请重试');
}
} else {
// 用户没有选择文件
this.$message.error('未选择文件');
}
};
input.click();
},
modUserAvatar(avatar) {
this.$axios.get(this.$url + '/user/modAvatar?id=' + this.user.id + "&avatar=" + avatar).then(res => res.data).then(res => {
console.log(res)
if (res.code == 200) {
this.getUserById(this.user.id)
EventBus.$emit('update-cur-user', JSON.stringify(res.data))
} else {
this.$message({
message: res.msg,
type: 'error'
});
}
})
},
8.修改头像后端接口
@Transactional
@GetMapping("/modAvatar")
public Result modAvatar(@RequestParam Integer id, String avatar) {
User user = userService.getById(id);
user.setAvatar(avatar);
userService.updateById(user);
User usertail = userService.lambdaQuery().eq(User::getId, id).list().get(0);
Role roleList = roleService.listRolesByUserId(Long.valueOf(usertail.getId()));
usertail.setRoles(roleList);
System.out.println(usertail);
return Result.suc(usertail);
}
二十一、个人中心功能的实现(下)
1.修改昵称表单
<el-dialog title="修改昵称" :visible.sync="dialogNameVisible" width="30%" center>
<el-form :model="modNameForm" label-width="100px" :rules="namerules" ref="modNameForm">
<el-form-item label="账号昵称">
<el-input style="width: 200px" type="text" :placeholder="user.username" size="small"
disabled></el-input>
</el-form-item>
<el-form-item label="新昵称" prop="username">
<el-input style="width: 200px" type="text" v-model="modNameForm.username" autocomplete="off"
size="small" @keyup.enter.native="modName"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button type="danger" @click="dialogNameVisible = false">取消修改</el-button>
<el-button type="success" @click="modName">开始修改</el-button>
</span>
</el-dialog>
2.昵称表单数据项配置
modNameForm: {
username: '',
},
namerules: {
username: [
{ required: true, message: '请输入账号', trigger: 'blur' },
{ min: 2, max: 10, message: '2-10个字符', trigger: 'blur' },
],
},
3.修改昵称方法
modName() {
this.$refs.modNameForm.validate((valid) => {
if (valid) {
this.$axios.get(this.$url + '/user/modName?id=' + this.user.id + "&username=" + this.modNameForm.username).then(res => res.data).then(res => {
console.log(res)
if (res.code == 200) {
this.$message({
message: '修改成功',
type: 'success'
});
this.dialogNameVisible = false
this.getUserById(this.user.id)
EventBus.$emit('update-cur-user', JSON.stringify(res.data))
} else {
this.$message({
message: res.msg,
type: 'error'
});
}
})
}
else {
this.$message({
message: "新账号昵称不符合规范",
type: 'warning'
});
return false;
}
})
},
4.修改昵称后端接口
@Transactional
@GetMapping("/modName")
public Result modName(@RequestParam Integer id, String username) {
User user = userService.getById(id);
user.setUsername(username);
List list =
userService.lambdaQuery().eq(User::getUsername, username).ne(User::getId, id).list();
if (list.size() > 0) return Result.fail("账号已被注册,请换一个名字");
else {
userService.updateById(user);
User usertail = userService.lambdaQuery().eq(User::getUsername, username).list().get(0);
Role roleList = roleService.listRolesByUserId(Long.valueOf(usertail.getId()));
usertail.setRoles(roleList);
return Result.suc(usertail);
}
}
5.修改密码表单
<el-dialog title="修改密码" :visible.sync="dialogPwdVisible" width="30%" center>
<el-form :model="modPwdForm" label-width="100px" :rules="rules" ref="modPwdForm">
<el-form-item label="原密码" prop="password">
<el-input style="width: 200px" type="text" v-model="modPwdForm.password" show-password
autocomplete="off" size="small"></el-input>
</el-form-item>
<el-form-item label="新密码" prop="newpassword">
<el-input style="width: 200px" type="password" v-model="modPwdForm.newpassword" show-password
autocomplete="off" size="small"></el-input>
</el-form-item>
<el-form-item label="重复密码" prop="rpassword">
<el-input style="width: 200px" type="password" v-model="modPwdForm.rpassword" show-password
autocomplete="off" size="small" @keyup.enter.native="modPwd"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button type="danger" @click="dialogPwdVisible = false">取消修改</el-button>
<el-button type="success" @click="modPwd">开始修改</el-button>
</span>
</el-dialog>
6.密码表单数据项配置
var validatePass = (rule, value, callback) => {
if (value === '') {
callback(new Error('请再次输入密码'))
} else if (value !== this.modPwdForm.newpassword) {
callback(new Error('两次输入密码不一致!'))
} else {
callback()
}
}
modPwdForm: {
password: '',
newpassword: '',
rpassword: '',
},
rules: {
password: [
{ required: true, message: '请输入原密码', trigger: 'blur' },
{ min: 6, max: 16, message: '6-16个字符', trigger: 'blur' },
],
newpassword: [
{ required: true, message: '请输入新密码', trigger: 'blur' },
{ min: 6, max: 16, message: '6-16个字符', trigger: 'blur' },
],
rpassword: [
{ required: true, validator: validatePass, trigger: 'blur' },
{ min: 6, max: 16, message: '6-16个字符', trigger: 'blur' },
],
},
7.修改密码方法
modPwd() {
this.$refs.modPwdForm.validate((valid) => {
if (valid) {
this.$axios.get(this.$url + '/user/modPwd?id=' + this.user.id + "&password=" + this.modNameForm.newpassword).then(res => res.data).then(res => {
console.log(res)
if (res.code == 200) {
this.$axios.get(this.$url + '/user/logout').then(res => res.data).then(res => {
if (res.code == 200) {
this.$store.commit("resetState")
this.$message({
message: '修改成功',
type: 'success'
});
this.dialogPwdVisible = false,
this.$message({
type: 'success',
message: '退出登录成功'
})
this.$router.push("/login")
} else {
this.$message({
message: res.msg,
type: 'error'
});
}
});
} else {
this.$message({
message: "修改失败",
type: 'error'
});
}
})
} else {
this.$message({
message: "请检查密码",
type: 'warning'
});
return false;
}
})
},
后端接口就自己思考一下喽
8.修改邮箱表单
<el-dialog title="修改邮箱" :visible.sync="dialogEmailVisible" width="30%" center>
<el-form :model="emailForm" :rules="emailRules" ref="emailForm" :validate-on-rule-change="false">
<el-row style="margin-top:20px;height:40px;">
<el-col :span="18" :offset="3">
<el-form-item>
<el-input v-model="emailForm.email" placeholder="请输入邮箱" @change="emailchange"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row style="margin-top:30px;height:45px;">
<el-col :span="18" :offset="3" style="height:40px;">
<el-form-item prop="code">
<el-row>
<el-col :span="16">
<el-input v-model.lazy.trim="emailForm.code" placeholder="请输入验证码"
@keyup.enter.native="modEmail"></el-input>
</el-col>
<el-col :span="4">
<el-button class="code-btn" size="medium" :disabled="sendCodeDisabled"
@click.stop.prevent.native="handleGetCode">{{ sendCodeText
}}</el-button>
</el-col>
</el-row>
</el-form-item>
</el-col>
</el-row>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button type="danger" @click="dialogEmailVisible = false">取消修改</el-button>
<el-button type="success" @click="modEmail">修改邮箱</el-button>
</span>
</el-dialog>
9.邮箱表单数据项配置
var validateCode = (rule, value, callback) => {
if (value === '') {
callback(new Error('请再次输入验证码'))
} else if (value !== this.loginForm.rcode) {
callback(new Error('验证码不正确!'))
} else {
callback()
}
}
sendCodeText: '获取验证码',
sendCodeDisabled: false,
countDown: 60,
emailForm: {
email: "",
code: "",
rcode: ''
},
emailRules: {
email: [{ required: true, message: '请输入邮箱', trigger: 'blur' },
{ pattern: /^[A-Za-z0-9\u4e00-\u9fa5]+@[a-zA-Z0-9_-]+(.[a-zA-Z0-9_-]+)+$/, message: '邮箱格式不正确' }
],
code: [{ required: true, validator: validateCode, trigger: 'blur' },
{ min: 6, max: 6, message: '请输入6位数字邮箱验证码', trigger: 'blur' },
],
},
10.修改邮箱方法
emailchange() {
clearInterval(this.timer);
this.countDown = 60
this.sendCodeDisabled = false
this.sendCodeText = '获取验证码'
this.emailForm.rcode = ''
this.emailForm.code = ''
},
handleGetCode() {
if (!this.validateEmail(this.emailForm.email)) {
this.$message({
message: '请输入正确的邮箱地址',
type: 'error'
})
return
}
this.sendCodeDisabled = true
this.sendCodeText = `${this.countDown}s 后重新获取`
this.timer = setInterval(() => {
if (this.countDown === 1) {
clearInterval(this.timer)
this.countDown = 60
this.sendCodeDisabled = false
this.sendCodeText = '获取验证码'
return
}
this.countDown -= 1
this.sendCodeText = `${this.countDown}s 后重新获取`
}, 1000)
this.$axios.get(this.$url + '/email/bind?email=' + this.emailForm.email).then(res => res.data).then(res => {
console.log(res)
if (res.code == 200) {
this.$message({
message: '邮箱验证码发送成功!',
type: 'success'
});
this.emailForm.rcode = res.data
} else {
this.$message({
message: res.msg,
type: 'error'
});
this.sendCodeDisabled = false
this.emailForm.rcode = ''
this.sendCodeText = '获取验证码'
}
})
},
validateEmail(email) {
// 正则表达式匹配邮箱格式
const emailRegex = /^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/
return emailRegex.test(email)
},
后端接口自行思考
二十二、系统首页的设计与基本实现(上)
1.系统首页布局
三行布局
第一行4列,为首页仪表盘
第二行2列,为系统介绍内容
第三行2列,为两张可视化图表
<div style="text-align: center;background-color: #f1f1f3;height: 100%;padding: 0px;margin: 0px;">
<el-row class="card-row">
<el-col :span="6">
</el-col>
<el-col :span="6">
</el-col>
<el-col :span="6">
</el-col>
<el-col :span="6">
</el-col>
</el-row>
<el-row class="card-row">
<el-col :span="16">
</el-col>
<el-col :span="8">
</el-col>
</el-row>
<el-row class="card-row">
<el-col :span="12">
</el-col>
<el-col :span="12">
</el-col>
</el-row>
</div>
2.Card组件样式
参上;自定义组件样式同参上
3.自定义仪表盘代码
用户数量:
<el-card shadow="hover" class="box-card centered-card red">
<div>
<i class="el-icon-s-custom"></i>
用户数量:
<el-tag type="success">
{{ userCount }}
</el-tag>
</div>
</el-card>
角色数量:
<el-card shadow="hover" class="box-card centered-card green">
<div>
<i class="el-icon-s-promotion"></i>
角色数量:
<el-tag disable-transitions>{{ roleCount }}</el-tag>
</div>
</el-card>
菜单数量:
<el-card shadow="hover" class="box-card centered-card purple">
<div>
<i class="el-icon-s-promotion"></i>
菜单数量
<el-tag disable-transitions>
{{ MenuCount }}
</el-tag>
</div>
</el-card>
联系开发者:
<el-card shadow="hover" class="box-card centered-card grey">
<el-button @click="showDialog = true">点击联系开发者</el-button>
</el-card>
4.仪表盘数据项配置
data() {
return {
userCount: 0,
roleCount: 0,
MenuCount: 0,
group1show: false,
group2show: false,
showDialog: false,
}
},
5.仪表盘数据获取函数
methods: {
getUserRole() {
this.$axios.get(this.$url + '/role/pie1').then(res => res.data).then(res => {
this.drawChartPie1(res)
})
},
getRoleMenu() {
this.$axios.get(this.$url + '/role/pie2').then(res => res.data).then(res => {
console.log(res)
this.drawChartPie2(res)
})
},
getUserCount() {
this.$axios.get(this.$url + '/user/count').then(res => res.data).then(res => {
this.userCount = res
})
},
},
created() {
this.getMenuCount()
this.getRoleCount()
this.getRoleMenu()
}
6.仪表盘后端接口
用户:
@GetMapping("/count")
public Integer count() {
return userService.list().size();
}
角色:
@GetMapping("/count")
public Integer count() {
return roleService.list().size();
}
菜单:
@GetMapping("/count")
public Integer count() {
System.out.println(menuService.list().size()); // 打印菜单列表的大小
return menuService.list().size(); // 返回菜单列表的大小
}
7.联系开发者对话框
<el-dialog :visible.sync="showDialog" width="30%" center>
<el-image :src="'http://localhost:8090/upload/file/qq'"></el-image>
<div slot="footer"></div>
</el-dialog>
<el-dialog :visible.sync="group1show" width="30%" center>
<el-image :src="'http://localhost:8090/upload/file/group1'"></el-image>
<div slot="footer"></div>
</el-dialog>
<el-dialog :visible.sync="group2show" width="30%" center>
<el-image :src="'http://localhost:8090/upload/file/group2'"></el-image>
<div slot="footer"></div>
</el-dialog>
二十三、系统首页的设计与基本实现(下)
1.系统技术架构介绍
<el-card shadow="hover" class="box-card centered-card gold">
<h2>基于SpringBoot+Vue前后端分离通用权限管理后台系统PermissionAdmin</h2>
<h3>技术架构:总体采用前后端分离架构</h3>
<h4>后端:</h4>
<el-row style="text-align:left">
<el-col :span="24">主要开发框架:
<el-tag type="primary" style="margin:2px">SpringBoot2x</el-tag>
<el-tag type="success" style="margin:2px">MyBatisPlus3.4.1</el-tag>
</el-col>
</el-row>
<el-row style="text-align:left">
<el-col :span="24">开发语言:
<el-tag type="primary">Java</el-tag>
</el-col>
</el-row>
<el-row style="text-align:left">
开发技术:
<el-col :span="24">
<el-tag type="primary" style="margin:2px">java技术</el-tag>
<el-tag type="success" style="margin:2px">SpringBoot技术</el-tag>
<el-tag type="warning" style="margin:2px">MyBatisPlus代码生成器</el-tag>
<el-tag type="danger" style="margin:2px">MyBatisPlus分页技术</el-tag>
<el-tag type="success" style="margin:2px">MyBatisPlus查询技术</el-tag>
<el-tag type="primary" style="margin:2px">Lombok技术</el-tag>
<el-tag type="success" style="margin:2px">文件上传技术</el-tag>
<el-tag type="warning" style="margin:2px">screw数据库文档生成技术</el-tag>
<el-tag type="danger" style="margin:2px">kaptcha技术</el-tag>
<el-tag type="success" style="margin:2px">mail邮箱验证码技术</el-tag>
<el-tag type="primary" style="margin:2px">Sa-token权限技术</el-tag>
</el-col>
</el-row>
<h4>数据库:MySQL5.7</h4>
<h4>前端:</h4>
<el-row style="text-align:left">
<el-col :span="24">主要开发框架:
<el-tag type="primary" style="margin:2px">Node.js16</el-tag>
<el-tag type="success" style="margin:2px">Vue2x</el-tag>
</el-col>
</el-row>
<el-row style="text-align:left">
<el-col :span="24">开发语言:
<el-tag type="primary">vue2</el-tag>
</el-col>
</el-row>
<el-row style="text-align:left"> 开发技术:
<el-col :span="24">
<el-tag type="primary" style="margin:2px">vue技术</el-tag>
<el-tag type="success" style="margin:2px">vue-router技术</el-tag>
<el-tag type="warning" style="margin:2px">axios技术</el-tag>
<el-tag type="danger" style="margin:2px">vuex技术</el-tag>
<el-tag type="success" style="margin:2px">路由守卫技术</el-tag>
<el-tag type="primary" style="margin:2px">element-ui技术</el-tag>
<el-tag type="success" style="margin:2px">echarts技术</el-tag>
<el-tag type="warning" style="margin:2px">邮箱验证码技术</el-tag>
</el-col>
</el-row>
</el-card>
2.系统作者介绍
<el-card shadow="hover" class="box-card centered-card orange">
<div>
<p>作者:龙毅</p>
<p>QQ群</p>
<el-row>
<el-col :span="24" >
龙毅前后端技术交流1群 <el-link type="primary" @click.native="group1show = true">398414828</el-link>
<el-tag type="success" @click.native="group1show = true">前后端分离系统技术交流群兼哔站项目资料群</el-tag>
</el-col>
<el-col :span="24" @click.native="group2show = true">
龙毅前后端技术交流2群<el-link type="primary" @click.native="group2show = true"> 930942770</el-link>
<el-tag type="success" @click.native="group2show = true">通用权限管理系统PermissionAdmin群</el-tag>
</el-col>
<el-col :span="24" @click.native="group2show = true">
龙毅前后端技术交流3群<el-link type="primary"> 477183730</el-link>
<el-tag type="success" >前后端分离系统技术交流群兼资料分享群</el-tag>
</el-col>
</el-row>
<p>QQ号:群主</p>
<p>加群时请备注【已三连关注 + 自己B站名字】</p>
<p>“未三连 + 关注的”【不通过】</p>
<p>“未三连 + 关注的”【不通过】</p>
<p>“未三连 + 关注的”【不通过】</p>
<p>哔站:<el-link type="primary" href="https://b23.tv/F7yFuQK" target="_blank">龙毅代码的个人空间</el-link></p>
<p>GitHub:<el-link type="primary" href="https://github.com/shengguanglongyi/PermissionAdmin.git"
target="_blank">
聖光龙毅的GitHub
</el-link></p>
</div>
</el-card>
二十四、系统首页可视化图表的实现
1.准备可视化容器
<el-card shadow="hover" class="box-card centered-card blue">
<div id="container1" style="height:320px;width: 500px;"></div>
<div class="text"> 用户角色比例</div>
</el-card>
<el-card shadow="hover" class="box-card centered-card pink">
<div id="container2" style="height:320px;width: 500px;"></div>
<div class="text"> 角色权限比例</div>
</el-card>
2.引入Echarts
import * as echarts from 'echarts'
3.编写初始化函数
getUserRole() {
this.$axios.get(this.$url + '/role/pie1').then(res => res.data).then(res => {
this.drawChartPie1(res)
})
},
getRoleMenu() {
this.$axios.get(this.$url + '/role/pie2').then(res => res.data).then(res => {
console.log(res)
this.drawChartPie2(res)
})
},
this.getRoleMenu()
this.getUserRole()
4.编写后端接口
1.测试sql
SELECT r.name , COUNT(ur.user_id) AS value
FROM role r
LEFT JOIN user_role ur ON r.id = ur.role_id
GROUP BY r.id
SELECT r.name , COUNT(ur.menu_id) AS value
FROM role r
LEFT JOIN role_menu ur ON r.id = ur.role_id
GROUP BY r.id
2.编写实体类
import lombok.Data;
@Data
public class RoleDto {
private String name;
private Integer value;
}
3.编写Controller层
@GetMapping("/pie1")
public List<RoleDto> pie1() {
List<RoleDto> roleDtoList = roleService.pie1();
return roleDtoList;
}
@GetMapping("/pie2")
public List<RoleDto> pie2() {
List<RoleDto> roleDtoList = roleService.pie2();
return roleDtoList;
}
4.编写service层,serverimpl层,mapper层
5.编写xml代码
<select id="pie1" resultType="com.longyi.springbootpermissionadmin.entity.Dto.RoleDto">
SELECT r.name , COUNT(ur.user_id) AS value
FROM role r
LEFT JOIN user_role ur ON r.id = ur.role_id
GROUP BY r.id
</select>
<select id="pie2" resultType="com.longyi.springbootpermissionadmin.entity.Dto.RoleDto">
SELECT r.name , COUNT(ur.menu_id) AS value
FROM role r
LEFT JOIN role_menu ur ON r.id = ur.role_id
GROUP BY r.id
</select>
5.编写前端实例化代码
drawChartPie1(data) {
var chartDom = document.getElementById('container1');
// 判断是否已经初始化
if (echarts.getInstanceByDom(chartDom)) {
// 已初始化,销毁实例
echarts.getInstanceByDom(chartDom).dispose();
}
var myChart = echarts.init(chartDom);
var option = {
title: {
text: '角色用户数量',
subtext: '角色用户比例',
left: 'center'
},
tooltip: {
trigger: 'item'
},
legend: {
orient: 'vertical',
left: 'left'
},
series: [
{
name: '用户数',
type: 'pie',
radius: '50%',
data:data,
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
};
myChart.setOption(option);
},
drawChartPie2(data) {
var chartDom = document.getElementById('container2');
// 判断是否已经初始化
if (echarts.getInstanceByDom(chartDom)) {
// 已初始化,销毁实例
echarts.getInstanceByDom(chartDom).dispose();
}
var myChart = echarts.init(chartDom);
var option = {
title: {
text: '角色权限数量',
subtext: '角色权限比例',
left: 'center'
},
tooltip: {
trigger: 'item'
},
legend: {
orient: 'vertical',
left: 'left'
},
series: [
{
name: '权限数',
type: 'pie',
radius: '50%',
data:data,
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
};
myChart.setOption(option);
},