diff --git a/src/api/login.js b/src/api/login.js index d6e3cf7..49165e4 100644 --- a/src/api/login.js +++ b/src/api/login.js @@ -28,4 +28,57 @@ export function logout() { url: '/admin/logout', method: 'post' }) -} \ No newline at end of file +} + +export function fetchList(params) { + return request({ + url: '/admin/list', + method: 'get', + params: params + }); +} +export function createAdmin(data) { + return request({ + url: '/admin/register', + method: 'post', + data: data + }); +} + +export function updateAdmin(id, data) { + return request({ + url: '/admin/update/' + id, + method: 'post', + data: data + }); +} + +export function updateStatus(id, params) { + return request({ + url: '/admin/updateStatus/' + id, + method: 'post', + params: params + }); +} + +export function deleteAdmin(id) { + return request({ + url: '/admin/delete/' + id, + method: 'post' + }); +} + +export function getRoleByAdmin(id) { + return request({ + url: '/admin/role/' + id, + method: 'get' + }); +} + +export function allocRole(params) { + return request({ + url: '/admin/role/update', + method: 'post', + params + }); +} diff --git a/src/api/menu.js b/src/api/menu.js index faef101..f58ba0a 100644 --- a/src/api/menu.js +++ b/src/api/menu.js @@ -6,4 +6,56 @@ export const getRouters = () => { url: '/getRouters', method: 'get' }) -} \ No newline at end of file +} +export function fetchList(parentId, params) { + return request({ + url: '/menu/list/' + parentId, + method: 'get', + params: params + }); +} + +export function deleteMenu(id) { + return request({ + url: '/menu/delete/' + id, + method: 'post' + }); +} + +export function createMenu(data) { + return request({ + url: '/menu/create', + method: 'post', + data: data + }); +} + +export function updateMenu(id, data) { + return request({ + url: '/menu/update/' + id, + method: 'post', + data: data + }); +} + +export function getMenu(id) { + return request({ + url: '/menu/' + id, + method: 'get' + }); +} + +export function updateHidden(id, params) { + return request({ + url: '/menu/updateHidden/' + id, + method: 'post', + params: params + }); +} + +export function fetchTreeList() { + return request({ + url: '/menu/treeList', + method: 'get' + }); +} diff --git a/src/api/resource.js b/src/api/resource.js new file mode 100644 index 0000000..ee2ca81 --- /dev/null +++ b/src/api/resource.js @@ -0,0 +1,39 @@ +import request from '@/utils/request' + +export function fetchList(params) { + return request({ + url: '/resource/list', + method: 'get', + params: params + }) +} + +export function createResource(data) { + return request({ + url: '/resource/create', + method: 'post', + data: data + }) +} + +export function updateResource(id, data) { + return request({ + url: '/resource/update/' + id, + method: 'post', + data: data + }) +} + +export function deleteResource(id) { + return request({ + url: '/resource/delete/' + id, + method: 'post' + }) +} + +export function fetchAllResourceList() { + return request({ + url: '/resource/listAll', + method: 'get' + }) +} diff --git a/src/api/resourceCategory.js b/src/api/resourceCategory.js new file mode 100644 index 0000000..5b69b6e --- /dev/null +++ b/src/api/resourceCategory.js @@ -0,0 +1,31 @@ +import request from '@/utils/request' + +export function listAllCate() { + return request({ + url: '/resourceCategory/listAll', + method: 'get' + }) +} + +export function createResourceCategory(data) { + return request({ + url: '/resourceCategory/create', + method: 'post', + data: data + }) +} + +export function updateResourceCategory(id, data) { + return request({ + url: '/resourceCategory/update/' + id, + method: 'post', + data: data + }) +} + +export function deleteResourceCategory(id) { + return request({ + url: '/resourceCategory/delete/' + id, + method: 'post' + }) +} diff --git a/src/api/role.js b/src/api/role.js new file mode 100644 index 0000000..35dc732 --- /dev/null +++ b/src/api/role.js @@ -0,0 +1,81 @@ +import request from '@/utils/request'; + +export function fetchList(params) { + return request({ + url: '/role/list', + method: 'get', + params: params + }); +} + +export function createRole(data) { + return request({ + url: '/role/create', + method: 'post', + data: data + }); +} + +export function updateRole(id, data) { + return request({ + url: '/role/update/' + id, + method: 'post', + data: data + }); +} + +export function updateStatus(id, params) { + return request({ + url: '/role/updateStatus/' + id, + method: 'post', + params: params + }); +} + +export function deleteRole(params) { + return request({ + url: '/role/delete', + method: 'post', + // data: + params + }); +} + +export function fetchAllRoleList() { + return request({ + url: '/role/listAll', + method: 'get' + }); +} + +export function listMenuByRole(roleId) { + return request({ + url: '/role/listMenu/' + roleId, + method: 'get' + }); +} + +export function listResourceByRole(roleId) { + return request({ + url: '/role/listResource/' + roleId, + method: 'get' + }); +} + +export function allocMenu(params) { + return request({ + url: '/role/allocMenu', + method: 'post', + // data:data + params + }); +} + +export function allocResource(params) { + return request({ + url: '/role/allocResource', + method: 'post', + // data: data, + params + }); +} diff --git a/src/permission.js b/src/permission.js index 3bbb1f3..4d19bbd 100644 --- a/src/permission.js +++ b/src/permission.js @@ -1,65 +1,52 @@ -import router from './router' -import store from './store' -import { Message } from 'element-ui' -import NProgress from 'nprogress' -import 'nprogress/nprogress.css' -import { getToken } from '@/utils/auth' - -NProgress.configure({ showSpinner: false }) - -const whiteList = ['/login', '/auth-redirect', '/bind', '/register'] +import router from './router'; +import store from './store'; +import NProgress from 'nprogress'; // Progress 进度条 +import 'nprogress/nprogress.css'; // Progress 进度条样式 +import { Message } from 'element-ui'; +import { getToken } from '@/utils/auth'; // 验权 +NProgress.configure({ showSpinner: false }); +const whiteList = ['/login', '/auth-redirect', '/bind', '/register']; router.beforeEach((to, from, next) => { - NProgress.start() + NProgress.start(); if (getToken()) { - /* has token*/ if (to.path === '/login') { - next({ path: '/' }) - NProgress.done() + next({ path: '/' }); + NProgress.done(); // if current page is dashboard will not trigger afterEach hook, so manually handle it } else { if (store.getters.roles.length === 0) { - // 判断当前用户是否已拉取完user_info信息 - store.dispatch('GetInfo').then(res => { - // 拉取user_info - // console.log(res.roles); - const roles = res.roles - store.dispatch('GenerateRoutes', { roles }).then(accessRoutes => { - // 测试 默认静态页面 - // store.dispatch('permission/generateRoutes', { roles }).then(accessRoutes => { - // 根据roles权限生成可访问的路由表 - router.addRoutes(accessRoutes) // 动态添加可访问路由表 - next({ ...to, replace: true }) // hack方法 确保addRoutes已完成 + store + .dispatch('GetInfo') + .then(res => { + // 拉取用户信息 + let menus = res.data.menus; + let username = res.data.username; + store.dispatch('GenerateRoutes', { menus, username }).then(() => { + // 生成可访问的路由表 + router.addRoutes(store.getters.addRoutes); // 动态添加可访问路由表 + next({ ...to, replace: true }); + }); }) - }) .catch(err => { store.dispatch('FedLogOut').then(() => { - Message.error(err) - next({ path: '/' }) - }) - }) + Message.error(err || 'Verification failed, please login again'); + next({ path: '/' }); + }); + }); } else { - next() - // 没有动态改变权限的需求可直接next() 删除下方权限判断 ↓ - // if (hasPermission(store.getters.roles, to.meta.roles)) { - // next() - // } else { - // next({ path: '/401', replace: true, query: { noGoBack: true }}) - // } - // 可删 ↑ + next(); } } } else { - // 没有token if (whiteList.indexOf(to.path) !== -1) { - // 在免登录白名单,直接进入 - next() + next(); } else { - next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页 - NProgress.done() + next('/login'); + NProgress.done(); } } -}) +}); router.afterEach(() => { - NProgress.done() -}) + NProgress.done(); // 结束Progress +}); diff --git a/src/router/dynamicRoutes.js b/src/router/dynamicRoutes.js index fccbf5d..e4d4fba 100644 --- a/src/router/dynamicRoutes.js +++ b/src/router/dynamicRoutes.js @@ -11,6 +11,7 @@ import customerManagement from './modules/customerManagement'; import accountManagement from './modules/accountManagement'; import customerService from './modules/customerService'; import feedback from './modules/feedback'; +import settings from './modules/settings'; export const DynamicRoutes = [ // 政策管理 @@ -26,7 +27,9 @@ export const DynamicRoutes = [ // 客服中心 customerService, // 意见反馈 - feedback + feedback, + // 系统设置 + settings ]; export default DynamicRoutes; diff --git a/src/router/index.js b/src/router/index.js index 590b175..ec58760 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -23,6 +23,7 @@ import Layout from '@/layout' breadcrumb: false // 如果设置为false,则不会在breadcrumb面包屑中显示 } */ +import DynamicRoutes from './dynamicRoutes' // 公共路由 export const constantRoutes = [ @@ -61,7 +62,7 @@ export const constantRoutes = [ path: 'index', component: (resolve) => require(['@/views/index'], resolve), name: '首页', - meta: { title: '首页', noCache: true, affix: true } + meta: { title: '首页', icon: 'dashboard', noCache: true, affix: true } } ] }, @@ -119,6 +120,7 @@ export const constantRoutes = [ ] } ] +export const asyncRouterMap = DynamicRoutes export default new Router({ mode: 'history', // 去掉url中的# diff --git a/src/router/modules/accountManagement.js b/src/router/modules/accountManagement.js index 33f6dc7..05617cf 100644 --- a/src/router/modules/accountManagement.js +++ b/src/router/modules/accountManagement.js @@ -8,8 +8,8 @@ const nestedRouter = { { path: 'account', component: resolve => require(['@/views/account/index'], resolve), - name: '账号管理', - meta: { title: '账号管理' } + name: 'account', + meta: { title: '账号管理', icon: 'user' } } ] }; diff --git a/src/router/modules/configPage.js b/src/router/modules/configPage.js index 1a0ea7a..4568bf0 100644 --- a/src/router/modules/configPage.js +++ b/src/router/modules/configPage.js @@ -6,14 +6,15 @@ const nestedRouter = { path: '/front', component: Layout, redirect: 'noRedirect', - meta: { title: '前端配置' }, + name:'front', + meta: { title: '前端配置', icon: 'swagger' }, children: [ { path: 'unscramble', component: resolve => require(['@/views/front/unscramble/index'], resolve), name: 'unscramble', - meta: { title: '政策解读' } + meta: { title: '政策解读', icon: 'list' } }, { path: 'addUnscramble', @@ -27,7 +28,7 @@ const nestedRouter = { component: resolve => require(['@/views/front/expressNews/index'], resolve), name: 'expressNews', - meta: { title: '资讯快报' } + meta: { title: '资讯快报', icon: 'list' } }, { path: 'addExpressNews', diff --git a/src/router/modules/customerManagement.js b/src/router/modules/customerManagement.js index 7b59835..e8d162a 100644 --- a/src/router/modules/customerManagement.js +++ b/src/router/modules/customerManagement.js @@ -8,8 +8,8 @@ const nestedRouter = { { path: 'customer', component: resolve => require(['@/views/customer/index'], resolve), - name: '客户管理', - meta: { title: '客户管理' } + name: 'customer', + meta: { title: '客户管理', icon: 'peoples' } }, { path: 'addCustomer', diff --git a/src/router/modules/customerService.js b/src/router/modules/customerService.js index e12ef46..4d9439d 100644 --- a/src/router/modules/customerService.js +++ b/src/router/modules/customerService.js @@ -8,8 +8,8 @@ const nestedRouter = { { path: 'service', component: resolve => require(['@/views/service/index'], resolve), - name: '客服中心', - meta: { title: '客服中心' } + name: 'service', + meta: { title: '客服中心', icon:'people' } } ] }; diff --git a/src/router/modules/feedback.js b/src/router/modules/feedback.js index 9ec1fea..3e35573 100644 --- a/src/router/modules/feedback.js +++ b/src/router/modules/feedback.js @@ -9,7 +9,7 @@ const nestedRouter = { path: 'feedback', component: resolve => require(['@/views/feedback/index'], resolve), name: 'feedback', - meta: { title: '意见反馈' } + meta: { title: '意见反馈', icon: 'message' } } ] }; diff --git a/src/router/modules/policyPage.js b/src/router/modules/policyPage.js index db27731..b84c1e7 100644 --- a/src/router/modules/policyPage.js +++ b/src/router/modules/policyPage.js @@ -6,13 +6,14 @@ const nestedRouter = { path: '/policy', component: Layout, redirect: 'noRedirect', - meta: { title: '政策管理' }, + name:'policy', + meta: { title: '政策管理', icon: 'row' }, children: [ { path: 'library', component: resolve => require(['@/views/policy/library/index'], resolve), name: 'library', - meta: { title: '政策库' } + meta: { title: '政策库', icon: 'list' } }, { path: 'add', @@ -25,7 +26,7 @@ const nestedRouter = { path: 'tag', component: resolve => require(['@/views/policy/tag/index'], resolve), name: 'tag', - meta: { title: '政策标签' } + meta: { title: '政策标签', icon: 'list' } } ] }; diff --git a/src/router/modules/settings.js b/src/router/modules/settings.js new file mode 100644 index 0000000..fca6b9c --- /dev/null +++ b/src/router/modules/settings.js @@ -0,0 +1,72 @@ +import Layout from '@/layout'; +// 账号管理 +const nestedRouter = { + path: '/ums', + component: Layout, + redirect: '/ums/admin', + name: 'ums', + meta: { title: '系统设置', icon: 'system' }, + children: [ + { + path: 'admin', + name: 'admin', + component: () => import('@/views/ums/admin/index'), + meta: { title: '用户列表', icon: 'list' } + }, + { + path: 'role', + name: 'role', + component: () => import('@/views/ums/role/index'), + meta: { title: '角色列表', icon: 'list' } + }, + { + path: 'allocMenu', + name: 'allocMenu', + component: () => import('@/views/ums/role/allocMenu'), + meta: { title: '分配菜单' }, + hidden: true + }, + { + path: 'allocResource', + name: 'allocResource', + component: () => import('@/views/ums/role/allocResource'), + meta: { title: '分配资源' }, + hidden: true + }, + { + path: 'menu', + name: 'menu', + component: () => import('@/views/ums/menu/index'), + meta: { title: '菜单列表', icon: 'list' } + }, + { + path: 'addMenu', + name: 'addMenu', + component: () => import('@/views/ums/menu/add'), + meta: { title: '添加菜单' }, + hidden: true + }, + { + path: 'updateMenu', + name: 'updateMenu', + component: () => import('@/views/ums/menu/update'), + meta: { title: '修改菜单' }, + hidden: true + }, + { + path: 'resource', + name: 'resource', + component: () => import('@/views/ums/resource/index'), + meta: { title: '资源列表', icon: 'list' } + }, + { + path: 'resourceCategory', + name: 'resourceCategory', + component: () => import('@/views/ums/resource/categoryList'), + meta: { title: '资源分类' }, + hidden: true + } + ] +} + +export default nestedRouter; diff --git a/src/router/modules/transferPage.js b/src/router/modules/transferPage.js index 7364a22..efb0f43 100644 --- a/src/router/modules/transferPage.js +++ b/src/router/modules/transferPage.js @@ -6,14 +6,15 @@ const nestedRouter = { path: '/technology', component: Layout, redirect: 'noRedirect', - meta: { title: '技术转移' }, + name:'technology', + meta: { title: '技术转移', icon: 'clipboard'}, children: [ { path: 'demand', component: resolve => require(['@/views/technology/demand/index'], resolve), name: 'demand', - meta: { title: '企业需求' } + meta: { title: '企业需求', icon: 'list' } }, { path: 'addDemand', @@ -27,7 +28,7 @@ const nestedRouter = { component: resolve => require(['@/views/technology/achievement/index'], resolve), name: 'achievement', - meta: { title: '科技成果' } + meta: { title: '科技成果', icon: 'list' } }, { path: 'addAchievement', diff --git a/src/store/getters.js b/src/store/getters.js index df51465..576b434 100644 --- a/src/store/getters.js +++ b/src/store/getters.js @@ -10,6 +10,7 @@ const getters = { introduction: state => state.user.introduction, roles: state => state.user.roles, permissions: state => state.user.permissions, - permission_routes: state => state.permission.routes -} -export default getters + permission_routes: state => state.permission.routes, + addRoutes: state => state.permission.addRoutes +}; +export default getters; diff --git a/src/store/modules/permission.js b/src/store/modules/permission.js index d034afd..05c3377 100644 --- a/src/store/modules/permission.js +++ b/src/store/modules/permission.js @@ -1,60 +1,109 @@ -import { constantRoutes } from '@/router' -// import { getRouters } from '@/api/menu' -import Layout from '@/layout/index' -import DynamicRoutes from '@/router/dynamicRoutes' +import { asyncRouterMap, constantRoutes } from '@/router/index'; -const permission = { - state: { - routes: [], - addRoutes: [] - }, - mutations: { - SET_ROUTES: (state, routes) => { - state.addRoutes = routes - state.routes = constantRoutes.concat(routes) - } - }, - actions: { - // 生成路由 - GenerateRoutes({ commit }) { - return new Promise(resolve => { - // console.log(DynamicRoutes); - const accessedRoutes = filterAsyncRouter(DynamicRoutes) - accessedRoutes.push({ path: '*', redirect: '/404', hidden: true }) - commit('SET_ROUTES', accessedRoutes) - resolve(accessedRoutes) - // 向后端请求路由数据 - // getRouters().then(res => { - // const accessedRoutes = filterAsyncRouter(res.data) - // accessedRoutes.push({ path: '*', redirect: '/404', hidden: true }) - // commit('SET_ROUTES', accessedRoutes) - // resolve(accessedRoutes) - // }) - }) +//判断是否有权限访问该菜单 +function hasPermission(menus, route) { + if (route.name) { + let currMenu = getMenu(route.name, menus); + if (currMenu != null) { + //设置菜单的标题、图标和可见性 + if (currMenu.title != null && currMenu.title !== '') { + route.meta.title = currMenu.title; + } + if (currMenu.icon != null && currMenu.title !== '') { + route.meta.icon = currMenu.icon; + } + if (currMenu.hidden != null) { + route.hidden = currMenu.hidden !== 0; + } + if (currMenu.sort != null && currMenu.sort !== '') { + route.sort = currMenu.sort; + } + return true; + } else { + route.sort = 0; + if (route.hidden !== undefined && route.hidden === true) { + return true; + } else { + return false; + } } + } else { + return true; } } -// 遍历后台传来的路由字符串,转换为组件对象 -function filterAsyncRouter(asyncRouterMap) { - return asyncRouterMap.filter(route => { - // if (route.component) { - // // Layout组件特殊处理 - // if (route.component === 'Layout') { - // route.component = Layout - // } else { - // route.component = loadView(route.component) - // } - // } - if (route.children != null && route.children && route.children.length) { - route.children = filterAsyncRouter(route.children) +//根据路由名称获取菜单 +function getMenu(name, menus) { + for (let i = 0; i < menus.length; i++) { + let menu = menus[i]; + if (name === menu.name) { + return menu; } - return true - }) + } + return null; } -export const loadView = (view) => { // 路由懒加载 - return (resolve) => require([`@/views/${view}`], resolve) +//对菜单进行排序 +function sortRouters(accessedRouters) { + for (let i = 0; i < accessedRouters.length; i++) { + let router = accessedRouters[i]; + if (router.children && router.children.length > 0) { + router.children.sort(compare('sort')); + } + } + accessedRouters.sort(compare('sort')); } -export default permission +//降序比较函数 +function compare(p) { + return function(m, n) { + let a = m[p]; + let b = n[p]; + return b - a; + }; +} + +const permission = { + state: { + routes: constantRoutes, + addRoutes: [] + }, + mutations: { + SET_ROUTERS: (state, routes) => { + state.addRoutes = routes; + state.routes = constantRoutes.concat(routes); + } + }, + actions: { + GenerateRoutes({ commit }, data) { + return new Promise(resolve => { + const { menus } = data; + const { username } = data; + const accessedRouters = asyncRouterMap.filter(v => { + //admin帐号直接返回所有菜单 + // if (username === 'admin') return true; + if (hasPermission(menus, v)) { + if (v.children && v.children.length > 0) { + v.children = v.children.filter(child => { + if (hasPermission(menus, child)) { + return child; + } + return false; + }); + return v; + } else { + return v; + } + } + return false; + }); + //对菜单进行排序 + sortRouters(accessedRouters); + commit('SET_ROUTERS', accessedRouters); + resolve(); + }); + } + } +}; + +export default permission; diff --git a/src/utils/date.js b/src/utils/date.js new file mode 100644 index 0000000..7c1a591 --- /dev/null +++ b/src/utils/date.js @@ -0,0 +1,42 @@ +// date.js +export function formatDate(date, fmt) { + if (/(y+)/.test(fmt)) { + fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length)); + } + let o = { + 'M+': date.getMonth() + 1, + 'd+': date.getDate(), + 'h+': date.getHours(), + 'm+': date.getMinutes(), + 's+': date.getSeconds() + }; + for (let k in o) { + if (new RegExp(`(${k})`).test(fmt)) { + let str = o[k] + ''; + fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? str : padLeftZero(str)); + } + } + return fmt; +} + +function padLeftZero(str) { + return ('00' + str).substr(str.length); +} + +export function str2Date(dateStr, separator) { + if (!separator) { + separator = "-"; + } + let dateArr = dateStr.split(separator); + let year = parseInt(dateArr[0]); + let month; + //处理月份为04这样的情况 + if (dateArr[1].indexOf("0") == 0) { + month = parseInt(dateArr[1].substring(1)); + } else { + month = parseInt(dateArr[1]); + } + let day = parseInt(dateArr[2]); + let date = new Date(year, month - 1, day); + return date; +} diff --git a/src/views/ums/admin/index.vue b/src/views/ums/admin/index.vue new file mode 100644 index 0000000..3734f4c --- /dev/null +++ b/src/views/ums/admin/index.vue @@ -0,0 +1,393 @@ + + + diff --git a/src/views/ums/menu/add.vue b/src/views/ums/menu/add.vue new file mode 100644 index 0000000..f7dfb71 --- /dev/null +++ b/src/views/ums/menu/add.vue @@ -0,0 +1,14 @@ + + + + + diff --git a/src/views/ums/menu/components/MenuDetail.vue b/src/views/ums/menu/components/MenuDetail.vue new file mode 100644 index 0000000..c1796bf --- /dev/null +++ b/src/views/ums/menu/components/MenuDetail.vue @@ -0,0 +1,164 @@ + + + + + diff --git a/src/views/ums/menu/index.vue b/src/views/ums/menu/index.vue new file mode 100644 index 0000000..b260db3 --- /dev/null +++ b/src/views/ums/menu/index.vue @@ -0,0 +1,196 @@ + + + + + diff --git a/src/views/ums/menu/update.vue b/src/views/ums/menu/update.vue new file mode 100644 index 0000000..3e6be07 --- /dev/null +++ b/src/views/ums/menu/update.vue @@ -0,0 +1,14 @@ + + + + + diff --git a/src/views/ums/resource/categoryList.vue b/src/views/ums/resource/categoryList.vue new file mode 100644 index 0000000..9fbce99 --- /dev/null +++ b/src/views/ums/resource/categoryList.vue @@ -0,0 +1,156 @@ + + + + + diff --git a/src/views/ums/resource/index.vue b/src/views/ums/resource/index.vue new file mode 100644 index 0000000..c361be6 --- /dev/null +++ b/src/views/ums/resource/index.vue @@ -0,0 +1,273 @@ + + + + + diff --git a/src/views/ums/role/allocMenu.vue b/src/views/ums/role/allocMenu.vue new file mode 100644 index 0000000..3b03f09 --- /dev/null +++ b/src/views/ums/role/allocMenu.vue @@ -0,0 +1,100 @@ + + + + + diff --git a/src/views/ums/role/allocResource.vue b/src/views/ums/role/allocResource.vue new file mode 100644 index 0000000..ef53912 --- /dev/null +++ b/src/views/ums/role/allocResource.vue @@ -0,0 +1,187 @@ + + + + + diff --git a/src/views/ums/role/index.vue b/src/views/ums/role/index.vue new file mode 100644 index 0000000..9d5be1f --- /dev/null +++ b/src/views/ums/role/index.vue @@ -0,0 +1,348 @@ + + +