Merge branch 'master' into deploy
This commit is contained in:
commit
0e9f32f25a
|
@ -0,0 +1,38 @@
|
||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
export function getRoutes() {
|
||||||
|
return request({
|
||||||
|
url: '/routes',
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getRoles() {
|
||||||
|
return request({
|
||||||
|
url: '/roles',
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteRole(id) {
|
||||||
|
return request({
|
||||||
|
url: `/roles/${id}`,
|
||||||
|
method: 'delete'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addRole(data) {
|
||||||
|
return request({
|
||||||
|
url: '/roles',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateRole(key, data) {
|
||||||
|
return request({
|
||||||
|
url: `/roles/${key}`,
|
||||||
|
method: 'put',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
|
@ -33,8 +33,8 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
routers() {
|
routes() {
|
||||||
return this.$store.getters.permission_routers
|
return this.$store.getters.permission_routes
|
||||||
},
|
},
|
||||||
lang() {
|
lang() {
|
||||||
return this.$store.getters.language
|
return this.$store.getters.language
|
||||||
|
@ -42,10 +42,10 @@ export default {
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
lang() {
|
lang() {
|
||||||
this.searchPool = this.generateRouters(this.routers)
|
this.searchPool = this.generateRoutes(this.routes)
|
||||||
},
|
},
|
||||||
routers() {
|
routes() {
|
||||||
this.searchPool = this.generateRouters(this.routers)
|
this.searchPool = this.generateRoutes(this.routes)
|
||||||
},
|
},
|
||||||
searchPool(list) {
|
searchPool(list) {
|
||||||
this.initFuse(list)
|
this.initFuse(list)
|
||||||
|
@ -59,7 +59,7 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.searchPool = this.generateRouters(this.routers)
|
this.searchPool = this.generateRoutes(this.routes)
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
click() {
|
click() {
|
||||||
|
@ -100,10 +100,10 @@ export default {
|
||||||
},
|
},
|
||||||
// Filter out the routes that can be displayed in the sidebar
|
// Filter out the routes that can be displayed in the sidebar
|
||||||
// And generate the internationalized title
|
// And generate the internationalized title
|
||||||
generateRouters(routers, basePath = '/', prefixTitle = []) {
|
generateRoutes(routes, basePath = '/', prefixTitle = []) {
|
||||||
let res = []
|
let res = []
|
||||||
|
|
||||||
for (const router of routers) {
|
for (const router of routes) {
|
||||||
// skip hidden router
|
// skip hidden router
|
||||||
if (router.hidden) { continue }
|
if (router.hidden) { continue }
|
||||||
|
|
||||||
|
@ -125,11 +125,11 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// recursive child routers
|
// recursive child routes
|
||||||
if (router.children) {
|
if (router.children) {
|
||||||
const tempRouters = this.generateRouters(router.children, data.path, data.title)
|
const tempRoutes = this.generateRoutes(router.children, data.path, data.title)
|
||||||
if (tempRouters.length >= 1) {
|
if (tempRoutes.length >= 1) {
|
||||||
res = [...res, ...tempRouters]
|
res = [...res, ...tempRoutes]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -146,6 +146,19 @@ export default {
|
||||||
this.selcetRecursion(child, select, children)
|
this.selcetRecursion(child, select, children)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
updateTreeNode(item) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const { _id, _parent } = item
|
||||||
|
const index = _id.split('-').slice(-1)[0] // get last index
|
||||||
|
if (_parent) {
|
||||||
|
_parent.children.splice(index, 1, item)
|
||||||
|
resolve(this.data)
|
||||||
|
} else {
|
||||||
|
this.data.splice(index, 1, item)
|
||||||
|
resolve(this.data)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
|
||||||
|
import { addResizeListener, removeResizeListener } from 'element-ui/src/utils/resize-event'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How to use
|
||||||
|
* <el-table height="100px" v-el-height-adaptive-table="{bottomOffset: 30}">...</el-table>
|
||||||
|
* el-table height is must be set
|
||||||
|
* bottomOffset: 30(default) // The height of the table from the bottom of the page.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const doResize = (el, binding, vnode) => {
|
||||||
|
const { componentInstance: $table } = vnode
|
||||||
|
|
||||||
|
const { value } = binding
|
||||||
|
|
||||||
|
if (!$table.height) {
|
||||||
|
throw new Error(`el-$table must set the height. Such as height='100px'`)
|
||||||
|
}
|
||||||
|
const bottomOffset = (value && value.bottomOffset) || 30
|
||||||
|
|
||||||
|
if (!$table) return
|
||||||
|
|
||||||
|
const height = window.innerHeight - el.getBoundingClientRect().top - bottomOffset
|
||||||
|
$table.layout.setHeight(height)
|
||||||
|
$table.doLayout()
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
bind(el, binding, vnode) {
|
||||||
|
el.resizeListener = () => {
|
||||||
|
doResize(el, binding, vnode)
|
||||||
|
}
|
||||||
|
|
||||||
|
addResizeListener(el, el.resizeListener)
|
||||||
|
},
|
||||||
|
inserted(el, binding, vnode) {
|
||||||
|
doResize(el, binding, vnode)
|
||||||
|
},
|
||||||
|
unbind(el) {
|
||||||
|
removeResizeListener(el, el.resizeListener)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
|
||||||
|
import adaptive from './adaptive'
|
||||||
|
|
||||||
|
const install = function(Vue) {
|
||||||
|
Vue.directive('el-height-adaptive-table', adaptive)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.Vue) {
|
||||||
|
window['el-height-adaptive-table'] = adaptive
|
||||||
|
Vue.use(install); // eslint-disable-line
|
||||||
|
}
|
||||||
|
|
||||||
|
adaptive.install = install
|
||||||
|
export default adaptive
|
|
@ -1,14 +1,18 @@
|
||||||
import './waves.css'
|
import './waves.css'
|
||||||
|
|
||||||
export default{
|
const context = '@@wavesContext'
|
||||||
bind(el, binding) {
|
|
||||||
el.addEventListener('click', e => {
|
function handleClick(el, binding) {
|
||||||
|
function handle(e) {
|
||||||
const customOpts = Object.assign({}, binding.value)
|
const customOpts = Object.assign({}, binding.value)
|
||||||
const opts = Object.assign({
|
const opts = Object.assign(
|
||||||
|
{
|
||||||
ele: el, // 波纹作用元素
|
ele: el, // 波纹作用元素
|
||||||
type: 'hit', // hit 点击位置扩散 center中心点扩展
|
type: 'hit', // hit 点击位置扩散 center中心点扩展
|
||||||
color: 'rgba(0, 0, 0, 0.15)' // 波纹颜色
|
color: 'rgba(0, 0, 0, 0.15)' // 波纹颜色
|
||||||
}, customOpts)
|
},
|
||||||
|
customOpts
|
||||||
|
)
|
||||||
const target = opts.ele
|
const target = opts.ele
|
||||||
if (target) {
|
if (target) {
|
||||||
target.style.position = 'relative'
|
target.style.position = 'relative'
|
||||||
|
@ -25,18 +29,45 @@ export default{
|
||||||
}
|
}
|
||||||
switch (opts.type) {
|
switch (opts.type) {
|
||||||
case 'center':
|
case 'center':
|
||||||
ripple.style.top = (rect.height / 2 - ripple.offsetHeight / 2) + 'px'
|
ripple.style.top = rect.height / 2 - ripple.offsetHeight / 2 + 'px'
|
||||||
ripple.style.left = (rect.width / 2 - ripple.offsetWidth / 2) + 'px'
|
ripple.style.left = rect.width / 2 - ripple.offsetWidth / 2 + 'px'
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
ripple.style.top = (e.pageY - rect.top - ripple.offsetHeight / 2 - document.documentElement.scrollTop || document.body.scrollTop) + 'px'
|
ripple.style.top =
|
||||||
ripple.style.left = (e.pageX - rect.left - ripple.offsetWidth / 2 - document.documentElement.scrollLeft || document.body.scrollLeft) + 'px'
|
(e.pageY - rect.top - ripple.offsetHeight / 2 - document.documentElement.scrollTop ||
|
||||||
|
document.body.scrollTop) + 'px'
|
||||||
|
ripple.style.left =
|
||||||
|
(e.pageX - rect.left - ripple.offsetWidth / 2 - document.documentElement.scrollLeft ||
|
||||||
|
document.body.scrollLeft) + 'px'
|
||||||
}
|
}
|
||||||
ripple.style.backgroundColor = opts.color
|
ripple.style.backgroundColor = opts.color
|
||||||
ripple.className = 'waves-ripple z-active'
|
ripple.className = 'waves-ripple z-active'
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}, false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!el[context]) {
|
||||||
|
el[context] = {
|
||||||
|
removeHandle: handle
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
el[context].removeHandle = handle
|
||||||
|
}
|
||||||
|
|
||||||
|
return handle
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
bind(el, binding) {
|
||||||
|
el.addEventListener('click', handleClick(el, binding), false)
|
||||||
|
},
|
||||||
|
update(el, binding) {
|
||||||
|
el.removeEventListener('click', el[context].removeHandle, false)
|
||||||
|
el.addEventListener('click', handleClick(el, binding), false)
|
||||||
|
},
|
||||||
|
unbind(el) {
|
||||||
|
el.removeEventListener('click', el[context].removeHandle, false)
|
||||||
|
el[context] = null
|
||||||
|
delete el[context]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ export default {
|
||||||
guide: 'Guide',
|
guide: 'Guide',
|
||||||
permission: 'Permission',
|
permission: 'Permission',
|
||||||
pagePermission: 'Page Permission',
|
pagePermission: 'Page Permission',
|
||||||
|
rolePermission: 'Role Permission',
|
||||||
directivePermission: 'Directive Permission',
|
directivePermission: 'Directive Permission',
|
||||||
icons: 'Icons',
|
icons: 'Icons',
|
||||||
components: 'Components',
|
components: 'Components',
|
||||||
|
@ -87,9 +88,14 @@ export default {
|
||||||
github: 'Github Repository'
|
github: 'Github Repository'
|
||||||
},
|
},
|
||||||
permission: {
|
permission: {
|
||||||
|
addRole: 'New Role',
|
||||||
|
editPermission: 'Edit Permission',
|
||||||
roles: 'Your roles',
|
roles: 'Your roles',
|
||||||
switchRoles: 'Switch roles',
|
switchRoles: 'Switch roles',
|
||||||
tips: 'In some cases it is not suitable to use v-permission, such as element Tab component or el-table-column and other asynchronous rendering dom cases which can only be achieved by manually setting the v-if.'
|
tips: 'In some cases it is not suitable to use v-permission, such as element Tab component or el-table-column and other asynchronous rendering dom cases which can only be achieved by manually setting the v-if.',
|
||||||
|
delete: 'Delete',
|
||||||
|
confirm: 'Confirm',
|
||||||
|
cancel: 'Cancel'
|
||||||
},
|
},
|
||||||
guide: {
|
guide: {
|
||||||
description: 'The guide page is useful for some people who entered the project for the first time. You can briefly introduce the features of the project. Demo is based on ',
|
description: 'The guide page is useful for some people who entered the project for the first time. You can briefly introduce the features of the project. Demo is based on ',
|
||||||
|
|
|
@ -5,6 +5,7 @@ export default {
|
||||||
documentation: 'Documentación',
|
documentation: 'Documentación',
|
||||||
guide: 'Guía',
|
guide: 'Guía',
|
||||||
permission: 'Permisos',
|
permission: 'Permisos',
|
||||||
|
rolePermission: 'Permisos de rol',
|
||||||
pagePermission: 'Permisos de la página',
|
pagePermission: 'Permisos de la página',
|
||||||
directivePermission: 'Permisos de la directiva',
|
directivePermission: 'Permisos de la directiva',
|
||||||
icons: 'Iconos',
|
icons: 'Iconos',
|
||||||
|
@ -87,9 +88,14 @@ export default {
|
||||||
github: 'Repositorio Github'
|
github: 'Repositorio Github'
|
||||||
},
|
},
|
||||||
permission: {
|
permission: {
|
||||||
|
addRole: 'Nuevo rol',
|
||||||
|
editPermission: 'Permiso de edición',
|
||||||
roles: 'Tus permisos',
|
roles: 'Tus permisos',
|
||||||
switchRoles: 'Cambiar permisos',
|
switchRoles: 'Cambiar permisos',
|
||||||
tips: 'In some cases it is not suitable to use v-permission, such as element Tab component or el-table-column and other asynchronous rendering dom cases which can only be achieved by manually setting the v-if.'
|
tips: 'In some cases it is not suitable to use v-permission, such as element Tab component or el-table-column and other asynchronous rendering dom cases which can only be achieved by manually setting the v-if.',
|
||||||
|
delete: 'Borrar',
|
||||||
|
confirm: 'Confirmar',
|
||||||
|
cancel: 'Cancelar'
|
||||||
},
|
},
|
||||||
guide: {
|
guide: {
|
||||||
description: 'The guide page is useful for some people who entered the project for the first time. You can briefly introduce the features of the project. Demo is based on ',
|
description: 'The guide page is useful for some people who entered the project for the first time. You can briefly introduce the features of the project. Demo is based on ',
|
||||||
|
|
|
@ -5,6 +5,7 @@ export default {
|
||||||
documentation: '文档',
|
documentation: '文档',
|
||||||
guide: '引导页',
|
guide: '引导页',
|
||||||
permission: '权限测试页',
|
permission: '权限测试页',
|
||||||
|
rolePermission: '角色权限',
|
||||||
pagePermission: '页面权限',
|
pagePermission: '页面权限',
|
||||||
directivePermission: '指令权限',
|
directivePermission: '指令权限',
|
||||||
icons: '图标',
|
icons: '图标',
|
||||||
|
@ -87,9 +88,14 @@ export default {
|
||||||
github: 'Github 地址'
|
github: 'Github 地址'
|
||||||
},
|
},
|
||||||
permission: {
|
permission: {
|
||||||
|
addRole: '新增角色',
|
||||||
|
editPermission: '编辑权限',
|
||||||
roles: '你的权限',
|
roles: '你的权限',
|
||||||
switchRoles: '切换权限',
|
switchRoles: '切换权限',
|
||||||
tips: '在某些情况下,不适合使用 v-permission。例如:Element-UI 的 Tab 组件或 el-table-column 以及其它动态渲染 dom 的场景。你只能通过手动设置 v-if 来实现。'
|
tips: '在某些情况下,不适合使用 v-permission。例如:Element-UI 的 Tab 组件或 el-table-column 以及其它动态渲染 dom 的场景。你只能通过手动设置 v-if 来实现。',
|
||||||
|
delete: '删除',
|
||||||
|
confirm: '确定',
|
||||||
|
cancel: '取消'
|
||||||
},
|
},
|
||||||
guide: {
|
guide: {
|
||||||
description: '引导页对于一些第一次进入项目的人很有用,你可以简单介绍下项目的功能。本 Demo 是基于',
|
description: '引导页对于一些第一次进入项目的人很有用,你可以简单介绍下项目的功能。本 Demo 是基于',
|
||||||
|
|
|
@ -3,6 +3,7 @@ import loginAPI from './login'
|
||||||
import articleAPI from './article'
|
import articleAPI from './article'
|
||||||
import remoteSearchAPI from './remoteSearch'
|
import remoteSearchAPI from './remoteSearch'
|
||||||
import transactionAPI from './transaction'
|
import transactionAPI from './transaction'
|
||||||
|
import roleAPI from './role'
|
||||||
|
|
||||||
// 修复在使用 MockJS 情况下,设置 withCredentials = true,且未被拦截的跨域请求丢失 Cookies 的问题
|
// 修复在使用 MockJS 情况下,设置 withCredentials = true,且未被拦截的跨域请求丢失 Cookies 的问题
|
||||||
// https://github.com/nuysoft/Mock/issues/300
|
// https://github.com/nuysoft/Mock/issues/300
|
||||||
|
@ -23,6 +24,13 @@ Mock.mock(/\/login\/login/, 'post', loginAPI.loginByUsername)
|
||||||
Mock.mock(/\/login\/logout/, 'post', loginAPI.logout)
|
Mock.mock(/\/login\/logout/, 'post', loginAPI.logout)
|
||||||
Mock.mock(/\/user\/info\.*/, 'get', loginAPI.getUserInfo)
|
Mock.mock(/\/user\/info\.*/, 'get', loginAPI.getUserInfo)
|
||||||
|
|
||||||
|
// 角色相关
|
||||||
|
Mock.mock(/\/routes/, 'get', roleAPI.getRoutes)
|
||||||
|
Mock.mock(/\/roles/, 'get', roleAPI.getRoles)
|
||||||
|
Mock.mock(/\/roles$/, 'post', roleAPI.addRole)
|
||||||
|
Mock.mock(/\/roles\/[A-Za-z0-9]+/, 'put', roleAPI.updateRole)
|
||||||
|
Mock.mock(/\/roles\/[A-Za-z0-9]+/, 'delete', roleAPI.deleteRole)
|
||||||
|
|
||||||
// 文章相关
|
// 文章相关
|
||||||
Mock.mock(/\/article\/list/, 'get', articleAPI.getList)
|
Mock.mock(/\/article\/list/, 'get', articleAPI.getList)
|
||||||
Mock.mock(/\/article\/detail/, 'get', articleAPI.getArticle)
|
Mock.mock(/\/article\/detail/, 'get', articleAPI.getArticle)
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
import Mock from 'mockjs'
|
||||||
|
import { deepClone } from '@/utils'
|
||||||
|
import { filterAsyncRoutes } from '@/store/modules/permission'
|
||||||
|
import { asyncRoutes, constantRoutes } from '@/router'
|
||||||
|
|
||||||
|
const routes = deepClone([...constantRoutes, ...asyncRoutes])
|
||||||
|
|
||||||
|
const roles = [
|
||||||
|
{
|
||||||
|
key: 'admin',
|
||||||
|
name: 'admin',
|
||||||
|
description: 'Super Administrator. Have access to view all pages.',
|
||||||
|
routes: routes
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'editor',
|
||||||
|
name: 'editor',
|
||||||
|
description: 'Normal Editor. Can see all pages except permission page',
|
||||||
|
routes: filterAsyncRoutes(routes, ['editor'])
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'visitor',
|
||||||
|
name: 'visitor',
|
||||||
|
description: 'Just a visitor. Can only see the home page and the document page',
|
||||||
|
routes: [{
|
||||||
|
path: '',
|
||||||
|
redirect: 'dashboard',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'dashboard',
|
||||||
|
name: 'Dashboard',
|
||||||
|
meta: { title: 'dashboard', icon: 'dashboard' }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
export default {
|
||||||
|
getRoutes() {
|
||||||
|
return routes
|
||||||
|
},
|
||||||
|
getRoles() {
|
||||||
|
return roles
|
||||||
|
},
|
||||||
|
addRole() {
|
||||||
|
return Mock.mock('@integer(300, 5000)')
|
||||||
|
},
|
||||||
|
updateRole() {
|
||||||
|
const res = {
|
||||||
|
data: 'success'
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
},
|
||||||
|
deleteRole() {
|
||||||
|
const res = {
|
||||||
|
data: 'success'
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,13 +3,13 @@ import store from './store'
|
||||||
import { Message } from 'element-ui'
|
import { Message } from 'element-ui'
|
||||||
import NProgress from 'nprogress' // progress bar
|
import NProgress from 'nprogress' // progress bar
|
||||||
import 'nprogress/nprogress.css' // progress bar style
|
import 'nprogress/nprogress.css' // progress bar style
|
||||||
import { getToken } from '@/utils/auth' // getToken from cookie
|
import { getToken } from '@/utils/auth' // get token from cookie
|
||||||
|
|
||||||
NProgress.configure({ showSpinner: false }) // NProgress Configuration
|
NProgress.configure({ showSpinner: false }) // NProgress Configuration
|
||||||
|
|
||||||
// permission judge function
|
// permission judge function
|
||||||
function hasPermission(roles, permissionRoles) {
|
function hasPermission(roles, permissionRoles) {
|
||||||
if (roles.indexOf('admin') >= 0) return true // admin permission passed directly
|
if (roles.includes('admin')) return true // admin permission passed directly
|
||||||
if (!permissionRoles) return true
|
if (!permissionRoles) return true
|
||||||
return roles.some(role => permissionRoles.indexOf(role) >= 0)
|
return roles.some(role => permissionRoles.indexOf(role) >= 0)
|
||||||
}
|
}
|
||||||
|
@ -18,20 +18,28 @@ const whiteList = ['/login', '/auth-redirect']// no redirect whitelist
|
||||||
|
|
||||||
router.beforeEach((to, from, next) => {
|
router.beforeEach((to, from, next) => {
|
||||||
NProgress.start() // start progress bar
|
NProgress.start() // start progress bar
|
||||||
if (getToken()) { // determine if there has token
|
if (getToken()) {
|
||||||
|
// determine if there has token
|
||||||
|
|
||||||
/* has token*/
|
/* has token*/
|
||||||
if (to.path === '/login') {
|
if (to.path === '/login') {
|
||||||
next({ path: '/' })
|
next({ path: '/' })
|
||||||
NProgress.done() // if current page is dashboard will not trigger afterEach hook, so manually handle it
|
NProgress.done() // if current page is dashboard will not trigger afterEach hook, so manually handle it
|
||||||
} else {
|
} else {
|
||||||
if (store.getters.roles.length === 0) { // 判断当前用户是否已拉取完user_info信息
|
if (store.getters.roles.length === 0) {
|
||||||
store.dispatch('GetUserInfo').then(res => { // 拉取user_info
|
// 判断当前用户是否已拉取完user_info信息
|
||||||
const roles = res.data.roles // note: roles must be a array! such as: ['editor','develop']
|
store
|
||||||
store.dispatch('GenerateRoutes', { roles }).then(() => { // 根据roles权限生成可访问的路由表
|
.dispatch('GetUserInfo')
|
||||||
router.addRoutes(store.getters.addRouters) // 动态添加可访问路由表
|
.then(res => {
|
||||||
|
// 拉取user_info
|
||||||
|
const roles = res.data.roles // note: roles must be a object array! such as: [{id: '1', name: 'editor'}, {id: '2', name: 'developer'}]
|
||||||
|
store.dispatch('GenerateRoutes', { roles }).then(accessRoutes => {
|
||||||
|
// 根据roles权限生成可访问的路由表
|
||||||
|
router.addRoutes(accessRoutes) // 动态添加可访问路由表
|
||||||
next({ ...to, replace: true }) // hack方法 确保addRoutes已完成 ,set the replace: true so the navigation will not leave a history record
|
next({ ...to, replace: true }) // hack方法 确保addRoutes已完成 ,set the replace: true so the navigation will not leave a history record
|
||||||
})
|
})
|
||||||
}).catch((err) => {
|
})
|
||||||
|
.catch(err => {
|
||||||
store.dispatch('FedLogOut').then(() => {
|
store.dispatch('FedLogOut').then(() => {
|
||||||
Message.error(err)
|
Message.error(err)
|
||||||
next({ path: '/' })
|
next({ path: '/' })
|
||||||
|
@ -49,7 +57,8 @@ router.beforeEach((to, from, next) => {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
/* has no token*/
|
/* has no token*/
|
||||||
if (whiteList.indexOf(to.path) !== -1) { // 在免登录白名单,直接进入
|
if (whiteList.indexOf(to.path) !== -1) {
|
||||||
|
// 在免登录白名单,直接进入
|
||||||
next()
|
next()
|
||||||
} else {
|
} else {
|
||||||
next(`/login?redirect=${to.path}`) // 否则全部重定向到登录页
|
next(`/login?redirect=${to.path}`) // 否则全部重定向到登录页
|
||||||
|
|
|
@ -33,7 +33,7 @@ import nestedRouter from './modules/nested'
|
||||||
affix: true if true, the tag will affix in the tags-view
|
affix: true if true, the tag will affix in the tags-view
|
||||||
}
|
}
|
||||||
**/
|
**/
|
||||||
export const constantRouterMap = [
|
export const constantRoutes = [
|
||||||
{
|
{
|
||||||
path: '/redirect',
|
path: '/redirect',
|
||||||
component: Layout,
|
component: Layout,
|
||||||
|
@ -81,7 +81,6 @@ export const constantRouterMap = [
|
||||||
{
|
{
|
||||||
path: '/documentation',
|
path: '/documentation',
|
||||||
component: Layout,
|
component: Layout,
|
||||||
redirect: '/documentation/index',
|
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: 'index',
|
path: 'index',
|
||||||
|
@ -109,10 +108,10 @@ export const constantRouterMap = [
|
||||||
export default new Router({
|
export default new Router({
|
||||||
// mode: 'history', // require service support
|
// mode: 'history', // require service support
|
||||||
scrollBehavior: () => ({ y: 0 }),
|
scrollBehavior: () => ({ y: 0 }),
|
||||||
routes: constantRouterMap
|
routes: constantRoutes
|
||||||
})
|
})
|
||||||
|
|
||||||
export const asyncRouterMap = [
|
export const asyncRoutes = [
|
||||||
{
|
{
|
||||||
path: '/permission',
|
path: '/permission',
|
||||||
component: Layout,
|
component: Layout,
|
||||||
|
@ -141,6 +140,15 @@ export const asyncRouterMap = [
|
||||||
title: 'directivePermission'
|
title: 'directivePermission'
|
||||||
// if do not set roles, means: this page does not require permission
|
// if do not set roles, means: this page does not require permission
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'role',
|
||||||
|
component: () => import('@/views/permission/role'),
|
||||||
|
name: 'RolePermission',
|
||||||
|
meta: {
|
||||||
|
title: 'rolePermission',
|
||||||
|
roles: ['admin']
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
@ -12,8 +12,8 @@ const getters = {
|
||||||
status: state => state.user.status,
|
status: state => state.user.status,
|
||||||
roles: state => state.user.roles,
|
roles: state => state.user.roles,
|
||||||
setting: state => state.user.setting,
|
setting: state => state.user.setting,
|
||||||
permission_routers: state => state.permission.routers,
|
permission_routes: state => state.permission.routes,
|
||||||
addRouters: state => state.permission.addRouters,
|
addRoutes: state => state.permission.addRoutes,
|
||||||
errorLogs: state => state.errorLog.logs
|
errorLogs: state => state.errorLog.logs
|
||||||
}
|
}
|
||||||
export default getters
|
export default getters
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { asyncRouterMap, constantRouterMap } from '@/router'
|
import { asyncRoutes, constantRoutes } from '@/router'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通过meta.role判断是否与当前用户权限匹配
|
* 通过meta.role判断是否与当前用户权限匹配
|
||||||
|
@ -15,17 +15,17 @@ function hasPermission(roles, route) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 递归过滤异步路由表,返回符合用户角色权限的路由表
|
* 递归过滤异步路由表,返回符合用户角色权限的路由表
|
||||||
* @param routes asyncRouterMap
|
* @param routes asyncRoutes
|
||||||
* @param roles
|
* @param roles
|
||||||
*/
|
*/
|
||||||
function filterAsyncRouter(routes, roles) {
|
export function filterAsyncRoutes(routes, roles) {
|
||||||
const res = []
|
const res = []
|
||||||
|
|
||||||
routes.forEach(route => {
|
routes.forEach(route => {
|
||||||
const tmp = { ...route }
|
const tmp = { ...route }
|
||||||
if (hasPermission(roles, tmp)) {
|
if (hasPermission(roles, tmp)) {
|
||||||
if (tmp.children) {
|
if (tmp.children) {
|
||||||
tmp.children = filterAsyncRouter(tmp.children, roles)
|
tmp.children = filterAsyncRoutes(tmp.children, roles)
|
||||||
}
|
}
|
||||||
res.push(tmp)
|
res.push(tmp)
|
||||||
}
|
}
|
||||||
|
@ -36,27 +36,27 @@ function filterAsyncRouter(routes, roles) {
|
||||||
|
|
||||||
const permission = {
|
const permission = {
|
||||||
state: {
|
state: {
|
||||||
routers: [],
|
routes: [],
|
||||||
addRouters: []
|
addRoutes: []
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
SET_ROUTERS: (state, routers) => {
|
SET_ROUTES: (state, routes) => {
|
||||||
state.addRouters = routers
|
state.addRoutes = routes
|
||||||
state.routers = constantRouterMap.concat(routers)
|
state.routes = constantRoutes.concat(routes)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
GenerateRoutes({ commit }, data) {
|
GenerateRoutes({ commit }, data) {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
const { roles } = data
|
const { roles } = data
|
||||||
let accessedRouters
|
let accessedRoutes
|
||||||
if (roles.includes('admin')) {
|
if (roles.includes('admin')) {
|
||||||
accessedRouters = asyncRouterMap
|
accessedRoutes = asyncRoutes
|
||||||
} else {
|
} else {
|
||||||
accessedRouters = filterAsyncRouter(asyncRouterMap, roles)
|
accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
|
||||||
}
|
}
|
||||||
commit('SET_ROUTERS', accessedRouters)
|
commit('SET_ROUTES', accessedRoutes)
|
||||||
resolve()
|
resolve(accessedRoutes)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -136,7 +136,8 @@ export function param2Obj(url) {
|
||||||
decodeURIComponent(search)
|
decodeURIComponent(search)
|
||||||
.replace(/"/g, '\\"')
|
.replace(/"/g, '\\"')
|
||||||
.replace(/&/g, '","')
|
.replace(/&/g, '","')
|
||||||
.replace(/=/g, '":"') +
|
.replace(/=/g, '":"')
|
||||||
|
.replace(/\+/g, ' ') +
|
||||||
'"}'
|
'"}'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
:collapse-transition="false"
|
:collapse-transition="false"
|
||||||
mode="vertical"
|
mode="vertical"
|
||||||
>
|
>
|
||||||
<sidebar-item v-for="route in permission_routers" :key="route.path" :item="route" :base-path="route.path"/>
|
<sidebar-item v-for="route in permission_routes" :key="route.path" :item="route" :base-path="route.path"/>
|
||||||
</el-menu>
|
</el-menu>
|
||||||
</el-scrollbar>
|
</el-scrollbar>
|
||||||
</template>
|
</template>
|
||||||
|
@ -23,7 +23,7 @@ export default {
|
||||||
components: { SidebarItem },
|
components: { SidebarItem },
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters([
|
...mapGetters([
|
||||||
'permission_routers',
|
'permission_routes',
|
||||||
'sidebar'
|
'sidebar'
|
||||||
]),
|
]),
|
||||||
variables() {
|
variables() {
|
||||||
|
|
|
@ -45,8 +45,8 @@ export default {
|
||||||
visitedViews() {
|
visitedViews() {
|
||||||
return this.$store.state.tagsView.visitedViews
|
return this.$store.state.tagsView.visitedViews
|
||||||
},
|
},
|
||||||
routers() {
|
routes() {
|
||||||
return this.$store.state.permission.routers
|
return this.$store.state.permission.routes
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
@ -93,7 +93,7 @@ export default {
|
||||||
return tags
|
return tags
|
||||||
},
|
},
|
||||||
initTags() {
|
initTags() {
|
||||||
const affixTags = this.affixTags = this.filterAffixTags(this.routers)
|
const affixTags = this.affixTags = this.filterAffixTags(this.routes)
|
||||||
for (const tag of affixTags) {
|
for (const tag of affixTags) {
|
||||||
// Must have tag name
|
// Must have tag name
|
||||||
if (tag.name) {
|
if (tag.name) {
|
||||||
|
|
|
@ -0,0 +1,267 @@
|
||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<el-button type="primary" @click="handleAddRole">{{ $t('permission.addRole') }}</el-button>
|
||||||
|
|
||||||
|
<el-table :data="rolesList" style="width: 100%;margin-top:30px;" border>
|
||||||
|
<el-table-column align="center" label="Role Key" width="220">
|
||||||
|
<template slot-scope="scope">{{ scope.row.key }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column align="center" label="Role Name" width="220">
|
||||||
|
<template slot-scope="scope">{{ scope.row.name }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column align="header-center" label="Description">
|
||||||
|
<template slot-scope="scope">{{ scope.row.description }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column align="center" label="Operations">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-button type="primary" size="small" @click="handleEdit(scope)">{{ $t('permission.editPermission') }}</el-button>
|
||||||
|
<el-button type="danger" size="small" @click="handleDelete(scope)">{{ $t('permission.delete') }}</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<el-dialog :visible.sync="dialogVisible" :title="dialogType==='edit'?'Edit Role':'New Role'">
|
||||||
|
<el-form :model="role" label-width="80px" label-position="left" >
|
||||||
|
<el-form-item label="Name">
|
||||||
|
<el-input v-model="role.name" placeholder="Role Name"/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="Desc">
|
||||||
|
<el-input
|
||||||
|
v-model="role.description"
|
||||||
|
:autosize="{ minRows: 2, maxRows: 4}"
|
||||||
|
type="textarea"
|
||||||
|
placeholder="Role Description"/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="Menus">
|
||||||
|
<el-tree ref="tree" :check-strictly="checkStrictly" :data="routesData" :props="defaultProps" show-checkbox node-key="path" class="permission-tree"/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<div style="text-align:right;">
|
||||||
|
<el-button type="danger" @click="dialogVisible=false">{{ $t('permission.cancel') }}</el-button>
|
||||||
|
<el-button type="primary" @click="confirmRole">{{ $t('permission.confirm') }}</el-button>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import path from 'path'
|
||||||
|
import { deepClone } from '@/utils'
|
||||||
|
import { getRoutes, getRoles, addRole, deleteRole, updateRole } from '@/api/role'
|
||||||
|
import i18n from '@/lang'
|
||||||
|
|
||||||
|
const defaultRole = {
|
||||||
|
key: '',
|
||||||
|
name: '',
|
||||||
|
description: '',
|
||||||
|
routes: []
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
role: Object.assign({}, defaultRole),
|
||||||
|
routes: [],
|
||||||
|
rolesList: [],
|
||||||
|
dialogVisible: false,
|
||||||
|
dialogType: 'new',
|
||||||
|
checkStrictly: false,
|
||||||
|
defaultProps: {
|
||||||
|
children: 'children',
|
||||||
|
label: 'title'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
routesData() {
|
||||||
|
return this.routes
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
// Mock: get all routes and roles list from server
|
||||||
|
this.getRoutes()
|
||||||
|
this.getRoles()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async getRoutes() {
|
||||||
|
const res = await getRoutes()
|
||||||
|
this.serviceRoutes = res.data
|
||||||
|
const routes = this.generateRoutes(res.data)
|
||||||
|
this.routes = this.i18n(routes)
|
||||||
|
},
|
||||||
|
async getRoles() {
|
||||||
|
const res = await getRoles()
|
||||||
|
this.rolesList = res.data
|
||||||
|
},
|
||||||
|
i18n(routes) {
|
||||||
|
const app = routes.map(route => {
|
||||||
|
route.title = i18n.t(`route.${route.title}`)
|
||||||
|
if (route.children) {
|
||||||
|
route.children = this.i18n(route.children)
|
||||||
|
}
|
||||||
|
return route
|
||||||
|
})
|
||||||
|
return app
|
||||||
|
},
|
||||||
|
// Reshape the routes structure so that it looks the same as the sidebar
|
||||||
|
generateRoutes(routes, basePath = '/') {
|
||||||
|
const res = []
|
||||||
|
|
||||||
|
for (let route of routes) {
|
||||||
|
// skip some route
|
||||||
|
if (route.hidden) { continue }
|
||||||
|
|
||||||
|
const onlyOneShowingChild = this.onlyOneShowingChild(route.children, route)
|
||||||
|
|
||||||
|
if (route.children && onlyOneShowingChild && !route.alwaysShow) {
|
||||||
|
route = onlyOneShowingChild
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
path: path.resolve(basePath, route.path),
|
||||||
|
title: route.meta && route.meta.title
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// recursive child routes
|
||||||
|
if (route.children) {
|
||||||
|
data.children = this.generateRoutes(route.children, data.path)
|
||||||
|
}
|
||||||
|
res.push(data)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
},
|
||||||
|
generateArr(routes) {
|
||||||
|
let data = []
|
||||||
|
routes.forEach(route => {
|
||||||
|
data.push(route)
|
||||||
|
if (route.children) {
|
||||||
|
const temp = this.generateArr(route.children)
|
||||||
|
if (temp.length > 0) {
|
||||||
|
data = [...data, ...temp]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return data
|
||||||
|
},
|
||||||
|
handleAddRole() {
|
||||||
|
this.role = Object.assign({}, defaultRole)
|
||||||
|
if (this.$refs.tree) {
|
||||||
|
this.$refs.tree.setCheckedNodes([])
|
||||||
|
}
|
||||||
|
this.dialogType = 'new'
|
||||||
|
this.dialogVisible = true
|
||||||
|
},
|
||||||
|
handleEdit(scope) {
|
||||||
|
this.dialogType = 'edit'
|
||||||
|
this.dialogVisible = true
|
||||||
|
this.checkStrictly = true
|
||||||
|
this.role = deepClone(scope.row)
|
||||||
|
this.$nextTick(() => {
|
||||||
|
const routes = this.generateRoutes(this.role.routes)
|
||||||
|
this.$refs.tree.setCheckedNodes(this.generateArr(routes))
|
||||||
|
// set checked state of a node not affects its father and child nodes
|
||||||
|
this.checkStrictly = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
handleDelete({ $index, row }) {
|
||||||
|
this.$confirm('Confirm to remove the role?', 'Warning', {
|
||||||
|
confirmButtonText: 'Confirm',
|
||||||
|
cancelButtonText: 'Cancel',
|
||||||
|
type: 'warning'
|
||||||
|
})
|
||||||
|
.then(async() => {
|
||||||
|
await deleteRole(row.id)
|
||||||
|
this.rolesList.splice($index, 1)
|
||||||
|
this.$message({
|
||||||
|
type: 'success',
|
||||||
|
message: 'Delete succed!'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(err => { console.error(err) })
|
||||||
|
},
|
||||||
|
generateTree(routes, basePath = '/', checkedKeys) {
|
||||||
|
const res = []
|
||||||
|
|
||||||
|
for (const route of routes) {
|
||||||
|
const routePath = path.resolve(basePath, route.path)
|
||||||
|
|
||||||
|
// recursive child routes
|
||||||
|
if (route.children) {
|
||||||
|
route.children = this.generateTree(route.children, routePath, checkedKeys)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (checkedKeys.includes(routePath) || (route.children && route.children.length >= 1)) {
|
||||||
|
res.push(route)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
},
|
||||||
|
async confirmRole() {
|
||||||
|
const isEdit = this.dialogType === 'edit'
|
||||||
|
|
||||||
|
const checkedKeys = this.$refs.tree.getCheckedKeys()
|
||||||
|
this.role.routes = this.generateTree(deepClone(this.serviceRoutes), '/', checkedKeys)
|
||||||
|
|
||||||
|
if (isEdit) {
|
||||||
|
await updateRole(this.role.key, this.role)
|
||||||
|
for (let index = 0; index < this.rolesList.length; index++) {
|
||||||
|
if (this.rolesList[index].key === this.role.key) {
|
||||||
|
this.rolesList.splice(index, 1, Object.assign({}, this.role))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const { data } = await addRole(this.role)
|
||||||
|
this.role.key = data
|
||||||
|
this.rolesList.push(this.role)
|
||||||
|
}
|
||||||
|
|
||||||
|
const { description, key, name } = this.role
|
||||||
|
this.dialogVisible = false
|
||||||
|
this.$notify({
|
||||||
|
title: 'Success',
|
||||||
|
dangerouslyUseHTMLString: true,
|
||||||
|
message: `
|
||||||
|
<div>Role Key: ${key}</div>
|
||||||
|
<div>Role Nmae: ${name}</div>
|
||||||
|
<div>Description: ${description}</div>
|
||||||
|
`,
|
||||||
|
type: 'success'
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// reference: src/view/layout/components/Sidebar/SidebarItem.vue
|
||||||
|
onlyOneShowingChild(children = [], parent) {
|
||||||
|
let onlyOneChild = null
|
||||||
|
const showingChildren = children.filter(item => !item.hidden)
|
||||||
|
|
||||||
|
// When there is only one child route, the child route is displayed by default
|
||||||
|
if (showingChildren.length === 1) {
|
||||||
|
onlyOneChild = showingChildren[0]
|
||||||
|
onlyOneChild.path = path.resolve(parent.path, onlyOneChild.path)
|
||||||
|
return onlyOneChild
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show parent if there are no child route to display
|
||||||
|
if (showingChildren.length === 0) {
|
||||||
|
onlyOneChild = { ... parent, path: '', noShowingChildren: true }
|
||||||
|
return onlyOneChild
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.app-container {
|
||||||
|
.roles-table {
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
.permission-tree {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -127,17 +127,8 @@ export default {
|
||||||
this.tempItem = Object.assign({}, row)
|
this.tempItem = Object.assign({}, row)
|
||||||
this.dialogFormVisible = true
|
this.dialogFormVisible = true
|
||||||
},
|
},
|
||||||
updateItem() {
|
async updateItem() {
|
||||||
const data = this.$refs.TreeTable.getData()
|
await this.$refs.TreeTable.updateTreeNode(this.tempItem)
|
||||||
const { _id } = this.tempItem
|
|
||||||
|
|
||||||
for (let i = 0; i < data.length; i++) {
|
|
||||||
if (data[i]._id === _id) {
|
|
||||||
data.splice(i, 1, Object.assign({}, this.tempItem))
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.dialogFormVisible = false
|
this.dialogFormVisible = false
|
||||||
},
|
},
|
||||||
addMenuItem(row, type) {
|
addMenuItem(row, type) {
|
||||||
|
|
Loading…
Reference in New Issue