前言:
在开发后台管理项目时,多用户多角色不同权限的场景可以说是非常普遍的。从零开始手写一个后台,要考虑的东西很多,这里直接拿网上大家比较熟悉的vue-admin-template后台模版来进行改造。
也可以看比较完整的前端开发https://www.jianshu.com/p/12ef029e3ab2
对应视频教程
先来看下vue-admin-template这个模版的代码目录结构
├── build # 构建相关
├── mock # 项目mock 模拟数据
├── public # 静态资源
│ │── favicon.ico # favicon图标
│ └── index.html # html模板
├── src # 源代码
│ ├── api # 所有请求
│ ├── assets # 主题 字体等静态资源
│ ├── components # 全局公用组件
│ ├── icons # 项目所有 svg icons
│ ├── layout # 全局 layout
│ ├── router # 路由
│ ├── store # 全局 store管理
│ ├── styles # 全局样式
│ ├── utils # 全局公用方法
│ ├── views # views 所有页面
│ ├── App.vue # 入口页面
│ ├── main.js # 入口文件 加载组件 初始化等
│ └── permission.js # 权限管理
├── tests # 测试
├── .env.xxx # 环境变量配置
├── .eslintrc.js # eslint 配置项
├── .babelrc # babel-loader 配置
├── .travis.yml # 自动化CI配置
├── vue.config.js # vue-cli 配置
├── postcss.config.js # postcss 配置
└── package.json # package.json
该模版使用了mock数据,可能对于不会mock的人有点生涩,比如作者本人
所以,我打算把mock删了,自己模拟数据,就是那种自己写json数据,写死,后端接口好了,直接请求接口把这些假数据替换下来就ok了。接下来的教程可以比较啰嗦,各位挑着看吧,不喜勿喷,毕竟不是专业做教程的。
第一步,我们先来看看最后的成果展示
比如我们用财务这个用户去登录一下后台,(先给财务这个角色分配一下权限)
比如给他分配培训认证全部的权限
然后财务管理员登录后台,只渲染所拥有的权限动态渲染了侧边栏。
补充一下:这里是按钮权限的设置,全局配置一个指令,到时候
v-permission='['add']'
就可以实现按钮是否显示。
基本上就是这么个东西,先
添加菜单
,在新建一个角色
,再给这个角色分配一下权限
,然后在新建个用户
,账号密码配置一下,再给这个用户分配一个角色
,最后这个用户登录后台,就展示所拥有的权限
,动态去渲染侧边栏
第二步 : 我们先来启动一下vue-admin-template这个模版,安装依赖
cnpm install
启动:npm run dev
可以看到,全是英文的,我是受不了,(英文烂的一逼,看不懂),侧边栏是手写的英文,删掉就好了,element-ui 是英文的,这个要改一下配置。
修改路径 src/main.js
// Vue.use(ElementUI, { locale })
// 如果想要中文版 element-ui,按如下方式声明
Vue.use(ElementUI)
这样就好了
当然要先实现正常登录,我这里用下真实的登录接口,修改一下
src/api/user.js
.env.development
vue.config.js
配置反向代理,然后重启项目
箭头标注的根据实际情况修改
这样实际是触发登录接口了,为啥没登录进去呢,因为登录成功后,会立即触发getinfo 这个接口,这个接口请求出错,就会清除token且又回到登录页了。
所以接下来要完善getinfo这个接口,先来看下逻辑
1.执行完登录请求后,会走permission.js
中的逻辑
可以看到getinfo,所以就要完善getinfo这个接口,同样换成真实的。
store/modules/user.js
根据实际情况修改下getinfo这个方法,这里看自己公司要求,我们只是取到昵称和头像存起来,也可以在这个方法直接把该用户所拥有的权限拿到并保存到vuex,建议在起一个接口,模拟的话我先在这个方法里把路由信息写死,然后在定一个存储到vuex的方法
const getDefaultState = () => {
return {
token: getToken(),
name: '',
avatar: '',
menus: "",//新增
}
}
const mutations = {
RESET_STATE: (state) => {
Object.assign(state, getDefaultState())
},
SET_TOKEN: (state, token) => {
state.token = token
},
SET_NAME: (state, name) => {
state.name = name
},
SET_AVATAR: (state, avatar) => {
state.avatar = avatar
},
// 新增
SET_MENUS: (state, menus) => {
state.menus = menus
}
}
// get user info
getInfo({ commit, state }) {
return new Promise((resolve, reject) => {
getInfo().then(response => {
//用户信息是根据token返回的,
//我们把token放到header里自动带过去了,这里就把state.token删掉了
const { data } = response
if (!data) {
return reject('Verification failed, please Login again.')
}
const { nickname, avatar } = data
// 模拟请求数据
const menus = [
{
"path": "/system",
"redirect": "/menu",
"component": "Layout",
"meta": {
"title": "系统管理",
"icon": "form"
},
"children": [{
"path": "/menu",
"name": "menu",
"component": "menu/index",
"meta": {
"title": "菜单管理",
"icon": "table",
}
},
{
"path": "/roles",
"name": "roles",
"component": "roles/index",
"meta": {
"title": "角色管理",
"icon": "table",
}
},
{
"path": "/administrator",
"name": "administrator",
"component": "dashboard/index",
"meta": {
"title": "用户管理",
"icon": "table"
}
}
]
}
]
//如果需要404 页面,请在此处添加
menus.push({
path: "/404",
component: "404",
hidden: true
}, {
path: "*",
redirect: "/404",
hidden: true
})
commit('SET_NAME', nickname)
commit('SET_AVATAR', avatar)
commit("SET_MENUS", menus) // 触发vuex SET_MENUS 保存路由表到vuex
resolve(data)
}).catch(error => {
reject(error)
})
})
}
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,
menus: state => state.user.menus //新增
}
export default getters
登录成功后,可以看到vuex里已经把昵称和头像和路由表信息存进去了。
下面把router.js
中的路由先删掉,保留必要的路由。
export const constantRoutes = [
{
path: '/login',
component: () => import('@/views/login/index'),
hidden: true
},
{
path: '/',
component: Layout,
redirect: '/dashboard',
children: [{
path: 'dashboard',
name: 'Dashboard',
component: () => import('@/views/dashboard/index'),
meta: { title: 'Dashboard', icon: 'dashboard' }
}]
}
]
继续,可以看到,后端返给我们的是
"component": "Layout",
是一个字符串,我们要把过滤下这段路由表信息,并且addrouter到路由里去,并全局挂载一个global.antRouter ,渲染的时候会用到
先在router这个目录下新建两个js文件,开发环境和生产环境导入组件的方式略有不同
_import_development.js
// 开发环境导入组件
module.exports = file => require('@/views/' + file + '.vue').default // vue-loader at least v13.0.0+
_import_production.js
// 生产环境导入组件
module.exports = file => () => import('@/views/' + file + '.vue')
然后在permission.js中引入刚创建的js文件
const _import = require('./router/_import_' + process.env.NODE_ENV) // 获取组件的方法
permission.js改造成下面这样,不明白的地方看注释
import router from './router'
import store from './store'
import {
Message
} from 'element-ui'
import NProgress from 'nprogress' // progress bar
import 'nprogress/nprogress.css' // progress bar style
import {
getToken
} from '@/utils/auth' // get token from cookie
import getPageTitle from '@/utils/get-page-title'
import Layout from '@/layout'
const _import = require('./router/_import_' + process.env.NODE_ENV) // 获取组件的方法
NProgress.configure({
showSpinner: false
}) // NProgress Configuration
const whiteList = ['/login'] // no redirect whitelist
router.beforeEach(async (to, from, next) => {
// start progress bar
NProgress.start()
// set page title
document.title = getPageTitle(to.meta.title)
// determine whether the user has logged in
const hasToken = getToken()
if (hasToken) {
if (to.path === '/login') {
// if is logged in, redirect to the home page
next({
path: '/'
})
NProgress.done()
} else {
const hasGetUserInfo = store.getters.name
if (hasGetUserInfo) {
next()
} else {
try {
// get user info
await store.dispatch('user/getInfo') // 请求获取用户信息
if (store.getters.menus.length < 1) {
global.antRouter = []
next()
}
const menus = filterAsyncRouter(store.getters.menus) // 1.过滤路由
console.log(menus);
router.addRoutes(menus) // 2.动态添加路由
global.antRouter = menus // 3.将路由数据传递给全局变量,做侧边栏菜单渲染工作
next({
...to,
replace: true
})
// next()
} catch (error) {
// remove token and go to login page to re-login
console.log(error);
await store.dispatch('user/resetToken')
Message.error(error || 'Has Error')
next(`/login?redirect=${to.path}`)
NProgress.done()
}
}
}
} else {
/* has no token*/
if (whiteList.indexOf(to.path) !== -1) {
// in the free login whitelist, go directly
next()
} else {
// other pages that do not have permission to access are redirected to the login page.
next(`/login?redirect=${to.path}`)
NProgress.done()
}
}
})
router.afterEach(() => {
// finish progress bar
NProgress.done()
})
// // 遍历后台传来的路由字符串,转换为组件对象
function filterAsyncRouter(asyncRouterMap) {
const accessedRouters = asyncRouterMap.filter(route => {
if (route.component) {
if (route.component === 'Layout') {
route.component = Layout
} else {
route.component = _import(route.component) // 导入组件
}
}
if (route.children && route.children.length) {
route.children = filterAsyncRouter(route.children)
}
return true
})
return accessedRouters
}
下面在改造下
layout/components/Sidebar/index.vue
computed: {
...mapGetters([
'sidebar'
]),
routes() {
//return this.$router.options.routes
return this.$router.options.routes.concat(global.antRouter) //把路由concat进去
},
这时候登录成功会报错
原因就是 没有提前建好页面。新建一下路由表内的几个页面就行了
然后就成功了 有木有!!
下面我打算换成真实的接口数据,真正实现开篇的成果展示,下面的打算录制成视频,对应视频教程,点我去看视频