предисловие
Ресурсы разрешений были классифицированы во внутренней главе: интерфейс API, меню маршрутизации и кнопки страницы. В этой статье основное внимание уделяется тому, как распределить эти ресурсы разрешений и представить различные функциональные меню и кнопки для унифицированной обработки разных пользователей, входящих в систему, в соответствии с разными разрешениями.
Сортировка отношений
пользователи и роли
Связь «многие ко многим»: у пользователя может быть несколько ролей, а роль содержит несколько пользователей.
Персонажи и меню
Связь «многие ко многим»: роль имеет несколько меню, и меню может быть назначено нескольким ролям.
Ресурсы ролей и разрешений
Связь «многие ко многим»: роль имеет несколько ресурсов разрешений, и ресурс разрешений может быть назначен нескольким ролям.
vue связанные
динамическая маршрутизация
Vue может динамически добавлять информацию о маршрутизации через router.addRoutes.
// 动态添加可访问路由表
router.addRoutes(accessRoutes)
пользовательская директива
Это всего лишь краткое введение, если вы хотите узнать больше, вы можете проверить информацию самостоятельно.
Определение инструкции
Vue может настроить инструкцию, взять текущий элемент DOM, а затем удалить его.Следующий el — это нативный объект DOM.
const directives = {
has: {
inserted: function(el, binding, vnode) {
if(binding.value) {
el.parentNode.removeChild(el)
}
}
}
}
export default directives
Регистрация компонента
import directive from './directives'
const importDirective = Vue => {
/**
* 权限指令
* options ===>权限字符串数组 ['admin','/sys/role/add']
*/
Vue.directive('hasPerm', directive.has)
}
export default importDirective
Использование инструкции
hasPerm — параметр hasPerm, определенный при регистрации компонента. Он используется в форме v-hasPerm. ['admin','sys:role:save'] is binding.value
<el-button v-hasPerm="['admin','sys:role:save']">添加</el-button>
Список интерфейсов
имя интерфейса | адрес интерфейса |
---|---|
Сохранить отношения ролей пользователей | /sys/rbac/saveUserRole |
Сохранить отношение меню роли | /sys/rbac/saveRoleMenu |
Сохранить отношение ресурса разрешения роли | /sys/rbac/saveRoleAccess |
удалить пользователя из роли | /sys/rbac/deleteUserRole |
список участников роли | /sys/rbac/listUserByRoleId |
Запросить список пользователей, которые не присоединились к указанной роли | /sys/rbac/listUserNoInRole |
Получить дерево ресурсов разрешений | /sys/rbac/listAccessTree |
Получить меню по идентификатору персонажа | /sys/rbac/listMenuByRoleId |
Получить текущую информацию о пользователе | /sys/user/info |
начать кодирование
Структура каталогов
├── src
├── api/sys
└── sys.rbac.service.js
├── directive
├── directives.js
└── index.js
├── layout
├── components/Sidebar
└── index.js
├── router
└── index.js
├── store
├── modules
└── permission.js
└── getters.js
├── views/modules/sys
└── role
├── drawer.vue
└── selectUser.vue
├── main.js
└── permission.js
Подробный файл
src/api/sys/sys.rbac.service.js
Определение интерфейса, связанного с управлением разрешениями
src/directive/directives.js
Инструкция разрешения кнопки определяет логическую обработку
const directives = {
has: {
inserted: function(el, binding, vnode) {
var arr = binding.value
// 判断要查询的数组(arr)是否至少有一个元素包含在目标数组(vnode.context.$store.state.user.access)中
if (!vnode.context.$store.state.user.accessList.some(_ => arr.indexOf(_) > -1)) {
vnode.context.$nextTick(() => {
if (el.parentNode) {
// 节点存在,就删除
el.parentNode.removeChild(el)
}
})
}
}
}
}
export default directives
src/directive/index.js
назначенная регистрация
import directive from './directives'
const importDirective = Vue => {
/**
* 权限指令
* options ===>权限字符串数组 ['admin','/sys/role/add']
*/
Vue.directive('hasPerm', directive.has)
}
export default importDirective
src/layout/components/Sidebar/index.js
Маршрут, добавленный addRouters, не может быть получен с помощью this.$router.options.routes, поэтому его необходимо изменить на vuex.
<sidebar-item v-for="route in permission_routes" :key="route.path" :item="route" :base-path="route.path" />
<!-- ===>修改成-->
<sidebar-item v-for="route in routes" :key="route.path" :item="route" :base-path="route.path" />
Добавлены разрешения_маршруты
computed: {
...mapGetters([
'permission_routes',
'sidebar'
]),
src/router/index.js
Удалить динамические маршруты из статических маршрутов
const createRouter = () => new Router({
mode: 'history', // require service support
scrollBehavior: () => ({ y: 0 }),
routes: [
... constantRoutes // ,
//... asyncRoutes
]
})
src/store/modules/permission.js
Здесь управление состоянием разрешений --vuex
В основном используйте menuList в информационном интерфейсе пользователя для обработки меню маршрутизации в asyncRoutes, сортировки, изменения имени, изменения значка и управления отображением.
import { asyncRoutes, constantRoutes } from '@/router'
/**
* Use meta.access to determine if the current user has permission
* @param menus
* @param route
*/
function hasPermission(menus, route) {
if (route.name) {
var currMenu = getMenu(route.name, menus)
if (currMenu !== null) {
// 设置菜单的标题、图标
if (currMenu.name) {
route.meta.title = currMenu.name
}
if (currMenu.icon) {
route.meta.icon = currMenu.icon
}
if (currMenu.sort) {
route.sort = currMenu.sort
}
} else {
route.sort = 10
}
}
if (route.meta && route.meta.access) {
return menus.some(menu => route.meta.access.includes(menu.routeName))
} else {
return true
}
}
// 根据路由名称获取菜单
function getMenu(access, menus) {
for (let i = 0; i < menus.length; i++) {
var menu = menus[i]
if (access === menu.routeName) {
return menu
}
}
return null
}
// 对菜单进行排序
function sortRouters(accessedRouters) {
for (let i = 0; i < accessedRouters.length; i++) {
var router = accessedRouters[i]
if (router.children && router.children.length > 0) {
router.children.sort(compare('sort'))
}
}
accessedRouters.sort(compare('sort'))
}
// 降序比较函数
function compare(p) {
return function(m, n) {
var a = m[p]
var b = n[p]
return b - a
}
}
/**
* Filter asynchronous routing tables by recursion
* @param routes asyncRoutes
* @param menus
*/
export function filterAsyncRoutes(routes, menus) {
const res = []
routes.forEach(route => {
const tmp = { ...route }
if (hasPermission(menus, tmp)) {
if (tmp.children) {
tmp.children = filterAsyncRoutes(tmp.children, menus)
}
res.push(tmp)
}
})
return res
}
const state = {
routes: [],
addRoutes: []
}
const mutations = {
SET_ROUTES: (state, routes) => {
state.addRoutes = routes
state.routes = constantRoutes.concat(routes)
}
}
const actions = {
generateRoutes({ commit }, menus) {
return new Promise(resolve => {
var accessedRoutes = filterAsyncRoutes(asyncRoutes, menus)
// 对菜单进行排序
sortRouters(accessedRoutes)
commit('SET_ROUTES', accessedRoutes)
resolve(accessedRoutes)
})
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
src/store/getters.js
const getters = {
sidebar: state => state.app.sidebar,
device: state => state.app.device,
token: state => state.user.token,
avatar: state => state.user.avatar,
name: state => state.user.name,
// 这里追加dictMap的get方法,可以使用mapGetters,详见src/components/m/Dict/index.vue
dictMap: state => state.dict.dictMap,
// 使用addRouter动态添加路由后,需要用vuex维护路由信息
permission_routes: state => state.permission.routes
}
export default getters
src/views/modules/sys/role/drawer.vue
Член роли управляет ящиком. index.vue был сгенерирован генератором кода. Drawer.vue необходимо временно добавить вручную, а затем настроить и изменить.
<template>
<div class="app-container">
<el-button
style="margin-bottom:10px;margin-left:10px;"
type="primary"
icon="el-icon-plus"
size="small"
@click="handleOpenSelectUser"
v-hasPerm="['admin','sys:rbac:saveUserRole']"
>新增</el-button>
<el-table :header-cell-style="{background:'#eef1f6',color:'#606266'}" v-loading="loading" :data="tableData">
<el-table-column prop="userName" label="用户名">
<template slot-scope="scope">
{{ scope.row.userName }}
</template>
</el-table-column>
<el-table-column prop="realName" label="姓名">
<template slot-scope="scope">
{{ scope.row.realName }}
</template>
</el-table-column>
<el-table-column prop="mobilePhone" label="手机号">
<template slot-scope="scope">
{{ scope.row.mobilePhone }}
</template>
</el-table-column>
<el-table-column
label="操作"
align="center">
<template slot-scope="scope">
<el-button type="text" size="small" icon="el-icon-delete" v-hasPerm="['admin','sys:rbac:deleteUserRole']" @click.native.stop="handleDeleteUserRole(scope.row.id)">移除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="recordCount>0"
:total="recordCount"
:page.sync="pageNum"
:limit.sync="pageSize"
@pagination="requestData"
/>
</div>
</template>
<script>
import { listUserByRoleId, deleteUserRole } from '@/api/sys/sys.rbac.service.js'
export default {
props: {
id: {
type: [String, Number],
default: undefined
}
},
data() {
return {
// 总记录数
recordCount: 0,
// 表格数据加载中
loading: false,
// 列表数据
tableData: [],
// 当前页
pageNum: 1,
// 每页大小
pageSize: Number(process.env.VUE_APP_PAGE_SIZE)
}
},
watch: {
id(n) {
this.requestData()
}
},
mounted() {
this.requestData()
},
methods: {
// 请求数据
requestData(page) {
if (!this.id) {
return
}
if (!page) {
page = {
page: this.pageNum,
limit: this.pageSize
}
}
this.loading = true
listUserByRoleId({
pageNum: page.page,
pageSize: page.limit,
roleId: this.id,
...this.searchForm
}).then(res => {
this.loading = false
if (res.code === 0) {
this.tableData = res.data.rows
this.recordCount = res.data.recordCount
}
}).catch(() => {
this.loading = false
})
},
// 打开弹窗--使用index.vue中的dialog,所以需要$parent.$parent selectUser -> drawer->index.vue
handleOpenSelectUser() {
this.$parent.$parent.openDialog(this.id, `选择用户`, 'selectUser', true)
},
handleDeleteUserRole(id) {
this.$confirm('此操作将永久删除该记录, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
var ids = []
if (id) {
ids.push(id)
} else {
return
}
deleteUserRole({
ids: ids,
id: this.id
}).then(res => {
if (res.code === 0) {
this.$message({
message: '删除成功',
type: 'success'
})
this.requestData()
} else {
this.$message({
message: res.msg || '删除失败',
type: 'error'
})
}
})
}).catch((e) => {
this.$message({
type: 'info',
message: '已取消删除' + e
})
})
}
}
}
</script>
<style lang="scss" scoped>
</style>
src/views/modules/sys/role/selectUser.vue
Выберите элемент для добавления, index.vue был сгенерирован генератором кода, selectUser.vue необходимо временно добавить вручную, а затем настроить и изменить.
<template>
<div class="app-container">
<el-form ref="searchForm" :model="searchForm" :inline="true">
<el-form-item label="关键字" prop="keywords">
<el-input v-model="searchForm.keywords" placeholder="请输入用户名、手机号" size="small" style="width: 240px"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="small" @click="handleSearch">查询</el-button>
<el-button icon="el-icon-refresh" size="small" @click="resetform">重置</el-button>
</el-form-item>
</el-form>
<el-table :header-cell-style="{background:'#eef1f6',color:'#606266'}" v-loading="loading" :data="tableData" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column prop="userName" label="用户名">
<template slot-scope="scope">
{{ scope.row.userName }}
</template>
</el-table-column>
<el-table-column prop="realName" label="姓名">
<template slot-scope="scope">
{{ scope.row.realName }}
</template>
</el-table-column>
<el-table-column prop="mobilePhone" label="手机号">
<template slot-scope="scope">
{{ scope.row.mobilePhone }}
</template>
</el-table-column>
</el-table>
<pagination
v-show="recordCount>0"
:total="recordCount"
:page.sync="pageNum"
:limit.sync="pageSize"
@pagination="requestData"
/>
</div>
</template>
<script>
import { listUserNoInRole, saveUserRole } from '@/api/sys/sys.rbac.service.js'
export default {
props: {
id: {
type: [String, Number],
default: undefined
}
},
data() {
return {
// 非单个禁用
single: true,
// 非多个禁用
multiple: true,
// 总记录数
recordCount: 0,
// 表格数据加载中
loading: false,
// 弹出层
open: false,
// 弹出层内容
dialogContent: 'add',
// 是否显示Ok
showOk: true,
// 当前弹出层标题
title: '',
// 列表数据
tableData: [],
// 提交按钮状态
submitLoading: false,
// 当前页
pageNum: 1,
// 每页大小
pageSize: Number(process.env.VUE_APP_PAGE_SIZE),
// 当前勾选行id
ids: [],
// 当前勾选行集合
selection: [],
searchForm: {
keywords: undefined
}
}
},
watch: {
id(n) {
this.requestData()
}
},
mounted() {
this.requestData()
},
methods: {
// 多选框选中数据
handleSelectionChange(selection) {
this.selection = selection
this.ids = selection.map(item => item.id)
this.single = selection.length !== 1
this.multiple = !selection.length
},
// 请求数据
requestData(page) {
if (!this.id) {
return
}
if (!page) {
page = {
page: this.pageNum,
limit: this.pageSize
}
}
this.loading = true
listUserNoInRole({
pageNum: page.page,
pageSize: page.limit,
roleId: this.id,
...this.searchForm
}).then(res => {
this.loading = false
if (res.code === 0) {
this.tableData = res.data.rows
this.recordCount = res.data.recordCount
}
}).catch(() => {
this.loading = false
})
},
// 查询
handleSearch() {
this.requestData()
},
// 重置
resetform(e) {
this.$refs['searchForm'].resetFields()
},
resetFields() {
this.$refs['searchForm'].resetFields()
this.requestData()
},
// 提交
submit() {
return new Promise((resolve, reject) => {
if (!this.ids.length) {
reject(new Error('id不能为空'))
return
}
saveUserRole({
ids: this.ids,
id: this.id
}).then(res => {
this.$parent.$parent.$refs.drawer.requestData()
resolve(res)
}).catch(e => {
reject(e)
})
})
}
}
}
</script>
<style lang="scss" scoped>
</style>
src/main.js
Здесь в основном для регистрации пользовательских инструкций
import importDirective from '@/directive'
/**
* 注册指令
*/
importDirective(Vue)
src/permission.js
Вот логика добавления динамической маршрутизации
// 进入页面前拦截
router.beforeEach(async(to, from, next) => {
// 进度条开始
NProgress.start()
// 重设页面标题
document.title = getPageTitle(to.meta.title)
// 获取token,判断是否已经登录
const hasToken = getToken()
if (hasToken) {
if (to.path === '/login') {
// 如果已经登录且是登录页,则重定向到首页
next({ path: '/' })
NProgress.done()
} else {
const hasGetUserInfo = store.getters.name
// 判断用户信息是否存在,如果已经存在,则可以进入页面
if (hasGetUserInfo) {
next()
} else {
try {
// 拉取用户信息
const { menuList } = await store.dispatch('user/getInfo')
// 生成可访问的路由表
const accessRoutes = await store.dispatch('permission/generateRoutes', menuList)
// 动态添加可访问路由表
router.addRoutes(accessRoutes)
router.app.$nextTick(() => {
next({ ...to, replace: true })
})
} catch (error) {
// 获取用户信息失败,则删除会话信息并跳转到登录页
await store.dispatch('user/resetToken')
Message.error(error || 'Has Error')
next(`/login?redirect=${to.path}`)
NProgress.done()
}
}
}
} else {
/* 没有token*/
if (whiteList.indexOf(to.path) !== -1) {
// 是白名单的页面,则可以进入页面
next()
} else {
// 非白名单,则跳转到登录页
next(`/login?redirect=${to.path}`)
NProgress.done()
}
}
})
визуализация
резюме
На этом глава фронтенда подошла к концу, и должны быть баги, но я буду потихоньку оптимизировать его после последующих открытий. Я собираюсь открыть новую главу - приглашаю вас играть в k8s рука об руку!
Адрес исходного кода проекта
- задняя часть
git ee.com/красивое желе/красивое о…
- внешний интерфейс
git ee.com/красивое желе/красивое о…
Статьи по Теме
Создайте фреймворк для быстрой разработки, который вам подходит - пилот
Создайте фреймворк для быстрой разработки, который подходит именно вам — фронтенд-скаффолдинг
Создайте фреймворк для быстрой разработки, который вам подходит — генератор кода для фронтенд-статей