oracle查询用户权限和角色_手把手教你搭建 Vue 后台管理系统框架(二)多项目用户、角色、权限篇...

导读

用户管理、角色管理、权限管理等,是后台管理系统中常用的几个模块。本篇文章主要基于上一篇文章中的框架基础,增加系统管理模块功能,主要包括“用户管理”、“角色管理”、“权限管理”等功能模块,并封装了“分页组件”和“前端分页”、“前端导出”等方法等。也优化了上一套框架功能,增加了项目列表界面,增加了用户信息状态管理 userModule 模块,增加了组件嵌套缓存子组件功能,增加了动态渲染左侧菜单功能等。

整体系统的最新效果已发布,点击链接预览:

https://www.ctrlcv.vip

d97f0a80a539bb43cc100a3fe6f813d5.png

源码地址:

链接:https://pan.baidu.com/s/1Du1aEcs0AWywzLTDag6uxg

提取码:cvxb

下载源码后请先阅读下载文件中的 TXT 文档。

实现多项目系统模块功能主要思路

其实系统管理模块主要思路是在后端实现的,后端逻辑结合数据库结构设计就可实现对应的用户、角色、权限等之间的关系,前端只需根据当前登录用户的角色获取该角色拥有的菜单权限,渲染在左侧菜单栏即可。

本 Chat 只讨论菜单权限,如果针对操作按钮权限,在前端一般也只是对操作按钮通过权限做显示隐藏操作,为了安全起见,后台最好也需要做权限判断。

多项目系统模块的用户、角色、权限等功能,主要搞清楚它们之间的关系就好办了,一般来说,主要根据它们之间的关系建数据库表结构即可,下面列一下主要数据库表,及它们之间的关系:

表名称说明
Menu系统菜单表
Project项目表
User系统用户表
Role系统角色表
Permission权限表
ProjectUser项目用户表
ProjectRole项目角色表
ProjectUserRole项目用户角色表
ProjectRolePermission项目角色表

这几张表基本能满足多项目中用户,角色,权限之间的关系。

主要是用户有角色,角色控制权限,自然用户就有权限了,再结合项目,就可以在不同项目中,用户会有不同的角色和权限了。对于项目中用户多角色,权限就取多角色中角色权限的并集即可。

准备工作

1. 下载《框架一》源码。

2. 在 views 根目录下面新建 Project.vue 文件,作为项目列表界面,在 views 目录下新建 system 文件夹,并新建 Role.vue 和 User.vue 两个文件,分别作为角色管理界面和用户管理界面,如下图:

9f30418eccedb1e153b66bab0b6268cc.png

3. 在 router > index.js 路由管理中,添加 project、user 和 role 三个路由,如下图:

080e9b1f536bab0bb6a0f2cbe49ae983.png

4. 在 components 目录下新建 Pagination.vue 文件作为公用的分页组件,如下图:

66aaeb48603f0ed665cc43b8ea81e812.png

系统管理模块核心代码

用户管理

099a282269ae50316be6053c4a70f6a2.png

用户管理界面主要采用左右列表结构,左边显示该项目下用户列表,右面该项目下角色列表,点击每行用户,会查询当前项目下该用户所拥有的角色,并在右侧角色列表中对应选中。如果在右侧角色列表中添加选中或取消选中,即是给当前选中用户增加或取消对应角色。

利用 Element-UI 框架的 table 表格组件渲染列表,再结合 Pagination 分页组件,实现分页列表。其中 Pagination 分页组件被我抽取出来,放在 components 目录下作为公用组件了,需要分页的界面调用该组件即可。下面是部分 HTML 代码,JS 代码可参考下面角色管理模块,或者直接参考源码:

      <el-tableid="out-table"ref="singleTable"stripe:data="userList"highlight-current-rowstyle="width:100%;"border
@current-change="handleCurrentChange">
<el-table-column align="left" label="用户名称">
<template slot-scope="scope">{{ scope.row.UserName }}template>
el-table-column>
<el-table-column align="left" label="昵称">
<template slot-scope="scope">{{ scope.row.DisplayName }}template>
el-table-column>
<el-table-column label="操作">
<template slot-scope="scope">
<i class="el-icon-edit" @click="handleEditOrAdd(scope)">i>
<i class="el-icon-delete" @click="handleDelete(scope)">i>
template>
el-table-column>
el-table>
<Pagination :options="options" @getPagingResult="getPagingResult" ref="pagination">Pagination>
角色管理

51ce3b5f653d53826f6f78c87121a29a.png

与用户管理界面风格类似,左侧显示该项目下的角色列表,右侧显示系统下权限列表(如果需要显示不同项目下不同的权限列表数据,需要增加一个“项目权限”,与“系统用户(角色)”类似即可),点击每行角色,会查询当前项目下该角色所拥有的权限,并在右侧权限列表中对应选中。如果在右侧权限列表中添加选中或取消选中,即是给当前选中角色增加或取消对应权限。

这块代码与用户管理模块类似,HTML 代码可参考用户模块,或者直接参考源码。这里列出核心 JS 代码:

   //通过分页组件方法, 获取当前项目下的角色列表
getPagingResult (result) {
this.roleList = result
this.$refs.singleTable.setCurrentRow(this.roleList[0]) // 默认选中第一行
},
// 获取系统权限列表
getPermissions () {
this.$axios.get(`/api/User/GetPermissionInfos`).then(res => {
this.serviceData = res
this.actionList = res
this.total = res.length
this.pagingResult = this.$utils.jsPagination(
this.currpageIndex,
this.pagesize,
this.actionList
)
})
},
// 选中左侧某一行角色,查看当前角色的所有权限
handleCurrentChange (row) {
if (!row) {
return
}
this.currRow = row
this.$axios
.get(
`/api/User/GetRoleAssignPermission?roleId=${row.ID}&projectId=${localStorage.eleProjectId}`
)
.then(res => {
let newaction = this.actionList.map(action => {
let newr = res.filter(r => {
if (r.PermissionID == action.ID) {
return r
}
})
if (newr.length > 0) {
action.Statu = true
} else {
action.Statu = false
}
return action
})
this.actionList = newaction
this.pageChange(1)
})
},
分页组件封装

结合 Element-UI 的分页组件,将其封装成一个公用组件,在其他界面调用时只需要将分页组件作为该界面的子组件,引用即可。

结合用户管理中调用的分页组件来看:

   "options" @getPagingResult="getPagingResult" ref="pagination">Pagination>

options 对象是需要传给分页组件的参数:

  • serachText:查询参数

  • servicePage:是否为服务器端分页(服务器端分页需要在后台写分页查询的方法)

  • serachField:需要搜索的字段数组

  • url:后台 API 路径,可带条件

getPagingResult 是分页组件向界面传递请求到的列表数据的事件方法。

父组件向子组件传值 -> 属性传值;子组件向负组件传值 -> 事件传值,然后在界面的 methods 中定义 getPagingResult 方法,接收分页请求的数据,赋值给列表 data,如下面代码:

  //通过分页组件方法, 获取当前项目下的角色列表
getPagingResult (result) {
console.log(result)
this.userList = result
this.$refs.singleTable.setCurrentRow(this.userList[0]) // 默认选中第一行
},
前端导出 EXCEL 方法

正常前端导出方法只能导出当前界面 table 表格中所有条数数据,但是我们做了分页操作,一般每页显示 10 条,如果不做处理,只会导出当前界面上的 10 条数据,所以,这里导出前先默认前端每页显示查询前 1000 条数据,即可导出 1000 条数据到 Excel,导出后立即还原当前每页显示条数。

//点击 “导出” 按钮,执行下面方法
exportExcel () {
// 更改每页显示条数,默认最多导出1000条数据
this.$refs.pagination.export_pagesize(1000, '#out-table', '用户列表')
},

上面方法利用 this.$refs.pagination 会调用到子组件(分页组件)中的 export_pagesize 方法,并改变当前分页显示条数 1000。

  // 前端导出excel时,改变当前每页显示条数
export_pagesize (val, a, b) {
// 如果总条数小于等于当前每页显示条数,则直接下载
if (this.currtotal <= this.currpageSize) {
this.$utils.exportExcel(a, b)
return
}
console.log('超过')
this.currpageSize = val
this.pageChange(1)
// 模拟异步导出
setTimeout(() => {
this.$utils.exportExcel(a, b)
// 还原每页显示条数
this.currpageSize = 10
this.pageChange(1)
}, 1)
}

this.$utils.exportExcel(a, b) 方法封装在公用方法中,请继续往下看。

封装公用方法

前端导出 Excel 方法:

// 定义导出Excel表格事件
const exportExcel = (tableSelector, name) => {
if (!name) {
name = '表格'
}
/* 从表生成工作簿对象 */
var wb = XLSX.utils.table_to_book(document.querySelector(tableSelector))
/* 获取二进制字符串作为输出 */
var wbout = XLSX.write(wb, {
bookType: 'xlsx',
bookSST: true,
type: 'array'
})
try {
FileSaver.saveAs(
// Blob 对象表示一个不可变、原始数据的类文件对象。
// Blob 表示的不一定是JavaScript原生格式的数据。
// File 接口基于Blob,继承了 blob 的功能并将其扩展使其支持用户系统上的文件。
// 返回一个新创建的 Blob 对象,其内容由参数中给定的数组串联组成。
new Blob([wbout], { type: 'application/octet-stream' }),
// 设置导出文件名称
`${name}.xlsx`
)
} catch (e) {
if (typeof console !== 'undefined') console.log(e, wbout)
}
return wbout
}

前端分页方法:

    // js分页方法
const jsPagination = (pageNo, pageSize, array) => {
var offset = (pageNo - 1) * pageSize
return offset + pageSize >= array.length
? array.slice(offset, array.length)
: array.slice(offset, offset + pageSize)
}

编辑功能时,拷贝数据方法:

// 拷贝方法
var deepClone = function (source) {
if (!source && typeof source !== 'object') {
throw new Error('error arguments', 'deepClone')
}
const targetObj = source.constructor === Array ? [] : {}
Object.keys(source).forEach(keys => {
if (source[keys] && typeof source[keys] === 'object') {
targetObj[keys] = deepClone(source[keys])
} else {
targetObj[keys] = source[keys]
}
})
return targetObj
}

相同对象赋值方法:

// 两个相同的对象,将一个对象的属性值复制给另一个对象对应的属性(getScopeRow:需要赋值的对象,getTSData:数据源对象)
// 对于table编辑,两个对象直接“等于”赋值,table行内容不会对应被修改(需刷新界面),需要对应属性赋值,界面才有效果
const ObjCloning = () => {
for (let item in store.getters.getTSData) {
store.getters.getScopeRow[item] = store.getters.getTSData[item]
}
}

这些方法放在了 utils.js 公用 JS 中,通过 export default 暴露出去,在 main.js 中,将 utils.js 中暴露出来的所有方法绑定到 Vue 原型链上去 Vue.prototype.$utils = utils,然后通过 this.$utils.方法名 调用。也可将自己写的公用方法用同样的方式暴露,供全局调用。

Bug 偶遇

1. 刷新时以当前路由做为 tab 加入 tabs

必须放在菜单加载完成之后执行,否则 menu 为空,报错:

Error in created hook: "TypeError: Cannot read property 'name' of undefined"

导致这个错误主要原因是因为当初为了做刷新整个界面时,以当前路由作为 tab 加入到 tabs 中,代码原先写在了 Home.vue 的 mounted 生命周期中,现在菜单是请求服务器异步加载的,所以当菜单还未请求完成时,就执行了状态管理中 clickMenuItem 方法。

clickMenuItem 方法中,menu 还未更新到数据,所以

let newTab = {label: menu.name,index: menu.index,closable: true, includeCom: menu.includeCom }

会报这个错误。如果这里判断一下,防止报这个错误,这个治标不治本的,因为这样,当前路由不会作为 tab 加入到 tabs 中,界面上就会出现当前界面都出现在“首页”的 tab 下。

为了解决这个问题,只需要让 clickMenuItem 方法放在菜单加载完成之后执行即可, 将原先写在了 Home.vue 的 mounted 生命周期中的 this.clickMenuItem() 方法,放到 LeftNav.vue 中的 GetUserMenu 方法请求成功之后执行,如下:

   //获取当前用户有权限的菜单
GetUserMenu(){
this.$axios
.get( `/api/User/GetUserMenu?projectId=${localStorage.eleProjectId}` )
.then(res => {
//将获取的菜单存入状态管理中
res.System_Menu.unshift({ name: '首页', hasChilder: false, index: 'index', children: [], includeCom: 'Index' })
this.getMenu(res.System_Menu);
// 刷新时以当前路由做为tab加入tabs
this.clickMenuItem(this.$route.name)
});
},

2. 刷新界面后,状态管理 user 模块中的 userInfo 信息会丢失

其实当整个界面刷新后,store 里面的数据都会被清空掉,我们只需要在刷新前,将需要保存的信息存放至浏览器的 localStorage中,然后在界面刷新完成后将 localStorage 中的数据重新存储到 store 中,再将 localStorage 中的数据删掉即可神不知鬼不觉的保存 store 中的数据。

在 main.js 中添加界面刷新前将 store 中的数据填写到 localStorage 中的代码:

// 界面刷新前,将store里面的数据暂存在localStorage里面
window.addEventListener('beforeunload', () => {
localStorage.setItem('userInfo', JSON.stringify(store.state.user.userInfo))
})
在TopBar.vue中的created 生命周期中还原store中的数据:
created () {
// 当界面F5刷新后,还原store
if (localStorage.getItem('userInfo')) {
// 重新赋值用户信息
this.initUserInfo(JSON.parse(localStorage.getItem('userInfo')))
// 赋值还原后清除localStorage的store
localStorage.removeItem('userInfo')
}
},

总结

这篇 Chat 写到这里,其实还有些功能没有拿出来说明,大家可结合源码去理解。当然,也有些功能未去实现,比如“项目管理”,目前项目列表界面有三个项目,看源码可知,其实是静态界面的三条数据,但是数据库中确实只有一条项目数据,且 id 为 3,所以,我在点击项目进入后台时,默认将项目 id 为3 存储在浏览器缓存中 localStorage.setItem('eleProjectId', 3),考虑到项目在哪里创建、哪里管理,大家可结合项目需求去实现。大家可以在这套框架中去优化,去升级,真正打造成属于自己的后台管理系统。

好啦,大家看完文章后,再结合源码自己去把每一个功能,每一个界面,每一个模块走一遍,有疑问的可以结合源码调试,思考,源码主要代码都有注释。大家可就以框架一和框架二整合的代码作为自己的基础框架,后面开发重心就可以放在业务逻辑那快了。当然,框架二中最主要的逻辑是在后台 API,小伙伴们可以跟做后台的同事商量、讨论,如果自己也会后台,那就最好了。(全栈工程师,妥妥的~~)如果既不会后台,又不想跟后台同事商量,那就期待一下我后面的文章,将现在的系统模块功能用 Node.js + MongoDB 实现。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值