本文最后更新于:1 年前
项目初始化
后端
- 前往
Gitee
下载页面(https://gitee.com/y_project/RuoYi-Vue (opens new window))下载解压到工作目录
- 创建数据库(默认使用 ry-vue 数据库,可自己修改),导入 sql 目录下的数据脚本
ry_2021xxxx.sql
,quartz.sql
- 修改项目配置文件,如果前面修改了数据库名则需要修改数据库相关配置,位于 ruoyi-admin 模块下的配置文件
application-druid.yml
的数据库配置
- 启动 admin 应用程序即可
前端
- cd 进入 ruiyi-ui,执行
npm install
安装项目依赖,如果下载太慢可指定下载源 npm install --registry=https://registry.npmmirror.com
- 安装完依赖后可修改 vue.config.js 设置页面端口默认为 80
npm run dev
启动项目
结构分析
模块结构
1,2,3,4 前四个模块是必须的基本模块,5 和 6 分别是定时任务和代码生成器模块为可选模块,使用 maven 的 package 打包后会在 ruoyi-admin 模块的 target 文件夹下生成 jar 包,可直接部署运行
表结构
- 代码生成器表和系统管理表:
2. 定时任务表:
配置结构
ruoyi-admin 的 resources 目录树形结构如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| │ application-druid.yml │ application.yml │ banner.txt │ logback-spring.xml │ ├─i18n │ messages.properties │ messages_en.properties │ ├─META-INF │ spring-devtools.properties │ ├─mybatis │ mybatis-config.xml │ └─upload └─avatar └─2023 └─12 └─04 wallhaven-1pol63_20231204211235A001.png
|
application-druid.yml
:数据库配置文件,使用 druid 数据库连接池,并开启数据库监控
application.yml
:管理后台应用程序配置文件
banner.txt
:程序启动横幅配置
logback-spring.xml
(原本为 logback.xml,修改后可从 yml 文件中读取配置参数用于自定义日志):日志自定义配置文件(将原先的 logback.xml 改成 logback-spring.xml。原因是 springboot 先读取 logback.xml,然后加载 yml/properties,再加载 logback-spring.xml)
i18n
:国际化文件夹,messages.properties 和 messages_en.properties 分别为中英文配置
spring-devtools.properties
:热启动配置文件
mybatis-config.xml
:mybatis 配置文件
upload/**
:自己配置的文件上传目录
返回值结构
RuoYi 脚手架使用的是前后端分离的版本,后端接口依旧是返回 json 格式数据,一共有 3 中返回值分别为:TableDatalnfo
(用于分页列表)、void
(用于导出/下载)、AjaxResult
(用于基本的增删改查)。
TableDatalnfo:
1 2 3 4 5 6 7 8 9 10 11 12
| public class TableDataInfo implements Serializable { private static final long serialVersionUID = 1L; private long total; private List<?> rows; private int code; private String msg; }
|
AjaxResult:
1 2 3 4 5 6 7 8 9 10
| public class AjaxResult extends HashMap<String, Object> { private static final long serialVersionUID = 1L; public static final String CODE_TAG = "code"; public static final String MSG_TAG = "msg"; public static final String DATA_TAG = "data"; }
|
核心模块刨析
1. 解读模块
首先对一个开源项目,需要对各个项目业务模块进行分析理解,充分理解各个业务模块功能和关系之后,才能以此作为基座进行二次开发,一般分析从以下四个方面考虑:
- 业务前提:了解模块功能,它在做什么,比如岗位 crud
- 技术前提:了解前后端技术栈,掌握相对应的技术,如 springboot+vue 项目,那么前端 vue 基本操作必须会,以及后端 ssm 必须会
- 开发技巧:前端会 F12 进入开发者模式进行调试,后端掌握 debug 调试
- 常规项目特点:了解各种类型项目的特点,例如:管理后台类项目特点先页面、后接口,经典厂字形布局
- web 项目组件技巧:
- ==业务 UI==:
- 一般以树形结构层级关系布局,无论多复杂的前端页面,都先将整个页面标签收起来,再层层展开剥离分析各个子标签对应的 UI 和功能,由浅入深了解整个 UI 设计
- ==业务逻辑==:
- 主线:发起请求—>接收请求—>处理请求—>响应请求
- 详细:
- 客户端发请求(关注:请求 url/请求方式/请求头/请求参数)
- 接口接收请求(关注:接口定义/参数接收/参数封装/参数校验)
- 接口处理请求(关注:业务逻辑实现)
- 接口响应请求(关注:响应数据类型/数据格式/响应头)
2. 岗位模块
2.1 岗位列表
岗位模块的业务逻辑如下图所示:
用户请求浏览器进入前端页面,进入过程中前端页面发起请求向后端请求数据,得到返回的 json 数据后将数据渲染在前端页面上呈现给用户。
其后端业务调用链路如下图所示:
2.2 岗位添加/修改
页面设计:依据表中必须填/选填字段,根据内容决定选择框架-输入框
前端代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| <!-- 添加或修改岗位对话框 --> <el-dialog :title="title" :visible.sync="open" append-to-body width="500px"> <el-form ref="form" :model="form" :rules="rules" label-width="80px"> <el-form-item label="岗位名称" prop="postName"> <el-input v-model="form.postName" placeholder="请输入岗位名称" /> </el-form-item> <el-form-item label="岗位编码" prop="postCode"> <el-input v-model="form.postCode" placeholder="请输入编码名称" /> </el-form-item> <el-form-item label="岗位顺序" prop="postSort"> <el-input-number v-model="form.postSort" :min="0" controls-position="right" /> </el-form-item> <el-form-item label="岗位状态" prop="status"> <el-radio-group v-model="form.status"> <el-radio v-for="dict in dict.type.sys_normal_disable" :key="dict.value" :label="dict.value">{{ dict.label }} </el-radio> </el-radio-group> </el-form-item> <el-form-item label="备注" prop="remark"> <el-input v-model="form.remark" placeholder="请输入内容" type="textarea" /> </el-form-item> </el-form> <div slot="footer" class="dialog-footer"> <el-button type="primary" @click="submitForm">确 定</el-button> <el-button @click="cancel">取 消</el-button> </div> </el-dialog></div> <script> /** 提交按钮 */ submitForm: function () { this.$refs["form"].validate(valid => { if (valid) { if (this.form.postId != undefined) { updatePost(this.form).then(response => { this.$modal.msgSuccess("修改成功"); this.open = false; }); } else { addPost(this.form).then(response => { this.$modal.msgSuccess("新增成功"); this.open = false; }); } this.getList(); } }); }, </script>
|
后端代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
@PreAuthorize("@ss.hasPermi('system:post:add')") @Log(title = "岗位管理", businessType = BusinessType.INSERT) @PostMapping
public AjaxResult add(@Validated @RequestBody SysPost post) { if (!postService.checkPostNameUnique(post)) { return error("新增岗位'" + post.getPostName() + "'失败,岗位名称已存在"); } else if (!postService.checkPostCodeUnique(post)) { return error("新增岗位'" + post.getPostName() + "'失败,岗位编码已存在"); } post.setCreateBy(getUsername()); return toAjax(postService.insertPost(post)); }
@PreAuthorize("@ss.hasPermi('system:post:edit')") @Log(title = "岗位管理", businessType = BusinessType.UPDATE) @PutMapping public AjaxResult edit(@Validated @RequestBody SysPost post) { if (!postService.checkPostNameUnique(post)) { return error("修改岗位'" + post.getPostName() + "'失败,岗位名称已存在"); } else if (!postService.checkPostCodeUnique(post)) { return error("修改岗位'" + post.getPostName() + "'失败,岗位编码已存在"); } post.setUpdateBy(getUsername()); return toAjax(postService.updatePost(post)); }
|
- 前端通过
data
发送 json
数据后端就要用 @RequestBody
接收
- 前端通过
Params
发送 xxx-form-url
编码的参数后端就要用 @RequestParam
接收
服务调用链路如下图所示:
2.3 岗位删除
前端代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <script> /** 删除按钮操作 */ handleDelete (row) { const postIds = row.postId || this.ids; this.$modal.confirm('是否确认删除岗位编号为"' + postIds + '"的数据项?').then(function () { // 点击确认删除后的操作 return delPost(postIds); }).then(() => { // 执行完删除后的操作 this.getList(); this.$modal.msgSuccess("删除成功"); }).catch(() => { }); }, </script>
|
后端代码:
1 2 3 4 5 6 7 8 9
|
@PreAuthorize("@ss.hasPermi('system:post:remove')") @Log(title = "岗位管理", businessType = BusinessType.DELETE) @DeleteMapping("/{postIds}") public AjaxResult remove(@PathVariable Long[] postIds) { return toAjax(postService.deletePostByIds(postIds)); }
|
2.4 Excel 导出功能
若依脚手架使用的是 POI,但是占用内存大,不适合高并发,但是定制灵活。
- 如果操作 Excel 复杂度高,建议使用 POI,编程灵活。
- 如果操作 Excel 数据量大,对性能有一定要求的情况,建议使用 EasyExcel。
- 如果操作 Excel 数据量小,而且追求编程效率,建议使用 Hutool 的 ExcelUtil。
前端代码:
1 2 3 4 5 6 7 8
| <script> /** 导出按钮操作 */ handleExport () { this.download('system/post/export', { ...this.queryParams }, `post_${new Date().getTime()}.xlsx`) } </script>
|
后端代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
@Log(title = "岗位管理", businessType = BusinessType.EXPORT) @PreAuthorize("@ss.hasPermi('system:post:export')") @PostMapping("/export") public void export(HttpServletResponse response, SysPost post) { List<SysPost> list = postService.selectPostList(post); ExcelUtil<SysPost> util = new ExcelUtil<SysPost>(SysPost.class); util.exportExcel(response, list, "岗位数据"); }
public class SysPost extends BaseEntity { private static final long serialVersionUID = 1L; @Excel(name = "岗位序号", cellType = ColumnType.NUMERIC) private Long postId; @Excel(name = "岗位编码") private String postCode; @Excel(name = "岗位名称") private String postName; @Excel(name = "岗位排序") private Integer postSort; @Excel(name = "状态", readConverterExp = "0=正常,1=停用") private String status; }
|
扩展自定义模块-客户 CURD
使用 mybatis-generator 实现
- 建表-customer:
1 2 3 4 5 6 7 8
| CREATE TABLE customer( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id', `name` varchar(255) NOT NULL COMMENT '名称', `phone` varchar(255) DEFAULT NULL COMMENT '手机号', `age` int(11) DEFAULT NULL COMMENT '年龄', PRIMARY KEY (`id`) )ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='客户表'; SET FOREIGN_KEY_CHECKS = 1;
|
- 拷贝配置文件 generatorConfig.xml 到 ruoyi-systemi 中:
==在 pom 文件中加入依赖==1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <build> <plugins> <plugin> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-maven-plugin</artifactId> <version>1.3.2</version> <configuration> <verbose>true</verbose> <overwrite>false</overwrite> </configuration> <dependencies> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.45</version> </dependency> </dependencies> </plugin> </plugins> </build>
|
- 使用插件生成 domain,mapper,xml 文件
- 添加 selectForList()方法
- 拷贝岗位的 service 和 impl 实现类和 controller,修改
- 拷贝前端界面到对应界面中
- 创建客户的菜单
代码生成器生成
导入创建的 customer 表
编辑修改生成的配置信息
3. 下载生成的代码,导入 sql 文件到数据表
- 将前后端代码复制到对应文件夹下,重新启动前后端程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| +---main | +---java | | \---com | | \---ruoyi | | \---system | | +---controller | | | CustomerController.java | | +---domain | | | Customer.java | | +---mapper | | | CustomerMapper.java | | \---service | | | ICustomerService.java | | \---impl | | CustomerServiceImpl.java | \---resources | \---mapper | \---system | CustomerMapper.xml \---vue +---api | \---system | customer.js \---views \---system \---customer index.vue
|
基本的增删改查(基本条件查询和列表查询)逻辑已经实现,后续根据业务需求修改补充对应逻辑代码
数据字典
数据字典是程序中全局经常改变的一些变量,为了方便维护管理采用数据字典进行交互式配置管理。
背景:我们在项目中会有很多的下拉框,这些下拉框都有一个特点就是键值对的存在实现下拉框方式:
- 界面写死—>有硬编码问题
- 给下拉框创建张表—>要创建很多张表
- 使用数据字典实现—>借助字典类型表和数据表实现下拉框等常用逻辑值的动态更新
概念
数据字典(data dictionary)是对于数据模型中的数据对象或者项目的描述的集合,这样做有利于程序员和其他需要参考的人。分析一个用户交换的对象系统的第一步就是去辨别每一个对象,以及它与其他对象之间的关系。这个过程称为数据建模,结果产生一个对象关系图。当每个数据对象和项目都给出了一个描述性的名字之后,它的关系再进行描述(或者是成为潜在描述关系的结构中的一部分),然后再描述数据的类型(例如文本还是图像,或者是二进制数值),列出所有可能预先定义的数值,以及提供简单的文字性描述。这个集合被组织成书的形式用来参考,就叫做数据字典。
简而言之,它是一种数据组织形式,方便使用者维护数据,管理数据。
数据字典表
脚手架中的数据字典组成数据结构由 2 张表共同维护
sys_dict_type
: 字典类型表
sys_dict_data
: 字典数据表
字典界面
通过在线维护管理数据字典数据进行实现常见数据的软编码动态更新,大大提高系统的可维护性。
逻辑实现
前端:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| export function useDict(...args) { const res = ref({}); return (() => { args.forEach((dictType, index) => { res.value[dictType] = []; const dicts = useDictStore().getDict(dictType); if (dicts) { res.value[dictType] = dicts; } else { getDicts(dictType).then(resp => { res.value[dictType] = resp.data.map(p => ({ label: p.dictLabel, value: p.dictValue, elTagType: p.listClass, elTagClass: p.cssClass })) useDictStore().setDict(dictType, res.value[dictType]); }) } }) return toRefs(res.value); })() }
app.config.globalProperties.useDict = useDict
const {proxy} = getCurrentInstance(); const {sys_normal_disable} = proxy.useDict("sys_normal_disable");
<el-form-item label="状态" prop="status"> <el-select v-model="queryParams.status" clearable placeholder="岗位状态" style="width: 200px"> <el-option v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.label" :value="dict.value" /> </el-select></el-form-item>
|
认证与授权
认证和授权采用是 SpringSecurity
实现 RBAC
,这里分析 RuoYi 是如何借助 SpringSecurity 实现认证与授权的。
认证
- 导入依赖
1 2 3 4 5
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
|
- SecurityConfig 配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
| package com.ruoyi.framework.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.logout.LogoutFilter; import org.springframework.web.filter.CorsFilter; import com.ruoyi.framework.config.properties.PermitAllUrlProperties; import com.ruoyi.framework.security.filter.JwtAuthenticationTokenFilter; import com.ruoyi.framework.security.handle.AuthenticationEntryPointImpl; import com.ruoyi.framework.security.handle.LogoutSuccessHandlerImpl;
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired private UserDetailsService userDetailsService;
@Autowired private AuthenticationEntryPointImpl unauthorizedHandler;
@Autowired private LogoutSuccessHandlerImpl logoutSuccessHandler;
@Autowired private JwtAuthenticationTokenFilter authenticationTokenFilter;
@Autowired private CorsFilter corsFilter;
@Autowired private PermitAllUrlProperties permitAllUrl;
@Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); }
@Override protected void configure(HttpSecurity httpSecurity) throws Exception { ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = httpSecurity.authorizeRequests(); permitAllUrl.getUrls().forEach(url -> registry.antMatchers(url).permitAll()); httpSecurity .csrf().disable() .headers().cacheControl().disable().and() .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() .authorizeRequests() .antMatchers("/login", "/register", "/captchaImage").permitAll() .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll() .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll() .anyRequest().authenticated() .and() .headers().frameOptions().disable(); httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler); httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class); httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class); }
@Bean public BCryptPasswordEncoder bCryptPasswordEncoder() { return new BCryptPasswordEncoder(); }
@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder()); } }
|
- 使用 jwt 认证(SysLoginService):
1 2 3 4 5 6 7 8 9 10 11 12 13
|
@PostMapping("/login") public AjaxResult login(@RequestBody LoginBody loginBody) { String token = loginService.login(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(), loginBody.getUuid()); return AjaxResult.success().put(Constants.TOKEN, token); }
|
自定义数据库验证
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
|
@Service public class UserDetailsServiceImpl implements UserDetailsService { private static final Logger log = LoggerFactory.getLogger(UserDetailsServiceImpl.class); @Autowired private ISysUserService userService; @Autowired private SysPasswordService passwordService; @Autowired private SysPermissionService permissionService; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { SysUser user = userService.selectUserByUserName(username); if (StringUtils.isNull(user)) { log.info("登录用户:{} 不存在.", username); throw new ServiceException(MessageUtils.message("user.not.exists")); } else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) { log.info("登录用户:{} 已被删除.", username); throw new ServiceException(MessageUtils.message("user.password.delete")); } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) { log.info("登录用户:{} 已被停用.", username); throw new ServiceException(MessageUtils.message("user.blocked")); } passwordService.validate(user); return createLoginUser(user); } public UserDetails createLoginUser(SysUser user) { return new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user)); } }
|
授权
若依脚手架中权限是捆绑于菜单上的,用户-——>角色—>菜单—>权限
前端在用户登录后会向后端发起请求获取用户信息,其中包括权限字符串数组,以便于鉴权。
后端接口级别鉴权
- 开启注解支持
1 2
| @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter {···}
|
- 方法上添加权限注解
1 2 3 4 5 6 7 8
| @PreAuthorize("@ss.hasPermi('system:dict:list')") @GetMapping("/list") public TableDataInfo list(SysDictType dictType) { startPage(); List<SysDictType> list = dictTypeService.selectDictTypeList(dictType); return getDataTable(list); }
|
- 验证用户权限(返回 true 表示鉴权通过)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| @Service("ss") public class PermissionService {
public boolean hasPermi(String permission) { if (StringUtils.isEmpty(permission)) { return false; } LoginUser loginUser = SecurityUtils.getLoginUser(); if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())) { return false; } PermissionContextHolder.setContext(permission); return hasPermissions(loginUser.getPermissions(), permission); }
private boolean hasPermissions(Set<String> permissions, String permission) { return permissions.contains(Constants.ALL_PERMISSION) || permissions.contains(StringUtils.trim(permission)); }
|
前端组件级别鉴权
- 为组件添加定制化属性(
v-hasPermi=['权限标签']
)1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| <el-row :gutter="10" class="mb8"> <el-col :span="1.5"> <el-button v-hasPermi="['system:post:add']" icon="Plus" plain type="primary" @click="handleAdd" >新增 </el-button> </el-col> <el-col :span="1.5"> <el-button v-hasPermi="['system:post:edit']" :disabled="single" icon="Edit" plain type="success" @click="handleUpdate" >修改 </el-button> </el-col> <el-col :span="1.5"> <el-button v-hasPermi="['system:post:remove']" :disabled="multiple" icon="Delete" plain type="danger" @click="handleDelete" >删除 </el-button> </el-col> <el-col :span="1.5"> <el-button v-hasPermi="['system:post:export']" icon="Download" plain type="warning" @click="handleExport" >导出 </el-button> </el-col> <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar> </el-row>
|
- 解析定制化权限属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
|
import useUserStore from '@/store/modules/user' export default {
mounted(el, binding, vnode) { const {value} = binding const all_permission = "*:*:*"; const permissions = useUserStore().permissions if (value && value instanceof Array && value.length > 0) { const permissionFlag = value
const hasPermissions = permissions.some(permission => { return all_permission === permission || permissionFlag.includes(permission) }) if (!hasPermissions) { el.parentNode && el.parentNode.removeChild(el) } } else { throw new Error(`请设置操作权限标签值`) } } }
|
- vue app 全局使用组件权限属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import hasRole from './permission/hasRole' import hasPermi from './permission/hasPermi' import copyText from './common/copyText' export default function directive(app){ app.directive('hasRole', hasRole) app.directive('hasPermi', hasPermi) app.directive('copyText', copyText) }
app.use(router) app.use(store) app.use(plugins) app.use(elementIcons) directive(app)
app.use(ElementPlus, { locale: locale, size: Cookies.get('size') || 'default' }) app.mount('#app')
|
参考
- 若依项目分析二次开发_哔哩哔哩_bilibili