前言:
在開(kāi)發(fā)后臺(tái)管理項(xiàng)目時(shí),多用戶(hù)多角色不同權(quán)限的場(chǎng)景可以說(shuō)是非常普遍的。從零開(kāi)始手寫(xiě)一個(gè)后臺(tái),要考慮的東西很多,這里直接拿網(wǎng)上大家比較熟悉的vue-admin-template后臺(tái)模版來(lái)進(jìn)行改造。
也可以看比較完整的前端開(kāi)發(fā)http://m.itdecent.cn/p/12ef029e3ab2
對(duì)應(yīng)視頻教程
先來(lái)看下vue-admin-template這個(gè)模版的代碼目錄結(jié)構(gòu)
├── build # 構(gòu)建相關(guān)
├── mock # 項(xiàng)目mock 模擬數(shù)據(jù)
├── public # 靜態(tài)資源
│ │── favicon.ico # favicon圖標(biāo)
│ └── index.html # html模板
├── src # 源代碼
│ ├── api # 所有請(qǐng)求
│ ├── assets # 主題 字體等靜態(tài)資源
│ ├── components # 全局公用組件
│ ├── icons # 項(xiàng)目所有 svg icons
│ ├── layout # 全局 layout
│ ├── router # 路由
│ ├── store # 全局 store管理
│ ├── styles # 全局樣式
│ ├── utils # 全局公用方法
│ ├── views # views 所有頁(yè)面
│ ├── App.vue # 入口頁(yè)面
│ ├── main.js # 入口文件 加載組件 初始化等
│ └── permission.js # 權(quán)限管理
├── tests # 測(cè)試
├── .env.xxx # 環(huán)境變量配置
├── .eslintrc.js # eslint 配置項(xiàng)
├── .babelrc # babel-loader 配置
├── .travis.yml # 自動(dòng)化CI配置
├── vue.config.js # vue-cli 配置
├── postcss.config.js # postcss 配置
└── package.json # package.json
該模版使用了mock數(shù)據(jù),可能對(duì)于不會(huì)mock的人有點(diǎn)生澀,比如作者本人
所以,我打算把mock刪了,自己模擬數(shù)據(jù),就是那種自己寫(xiě)json數(shù)據(jù),寫(xiě)死,后端接口好了,直接請(qǐng)求接口把這些假數(shù)據(jù)替換下來(lái)就ok了。接下來(lái)的教程可以比較啰嗦,各位挑著看吧,不喜勿噴,畢竟不是專(zhuān)業(yè)做教程的。
第一步,我們先來(lái)看看最后的成果展示
login.png
image.png
image.png
image.png





比如我們用財(cái)務(wù)這個(gè)用戶(hù)去登錄一下后臺(tái),(先給財(cái)務(wù)這個(gè)角色分配一下權(quán)限)
image.png
比如給他分配培訓(xùn)認(rèn)證全部的權(quán)限
image.png
然后財(cái)務(wù)管理員登錄后臺(tái),只渲染所擁有的權(quán)限動(dòng)態(tài)渲染了側(cè)邊欄。
image.png
補(bǔ)充一下:這里是按鈕權(quán)限的設(shè)置,全局配置一個(gè)指令,到時(shí)候
v-permission='['add']'就可以實(shí)現(xiàn)按鈕是否顯示。
image.png
基本上就是這么個(gè)東西,先
添加菜單,在新建一個(gè)角色,再給這個(gè)角色分配一下權(quán)限,然后在新建個(gè)用戶(hù),賬號(hào)密碼配置一下,再給這個(gè)用戶(hù)分配一個(gè)角色,最后這個(gè)用戶(hù)登錄后臺(tái),就展示所擁有的權(quán)限,動(dòng)態(tài)去渲染側(cè)邊欄
第二步 : 我們先來(lái)啟動(dòng)一下vue-admin-template這個(gè)模版,安裝依賴(lài)
cnpm install啟動(dòng):npm run dev
image.png
image.png
可以看到,全是英文的,我是受不了,(英文爛的一逼,看不懂),側(cè)邊欄是手寫(xiě)的英文,刪掉就好了,element-ui 是英文的,這個(gè)要改一下配置。
修改路徑 src/main.js
// Vue.use(ElementUI, { locale })
// 如果想要中文版 element-ui,按如下方式聲明
Vue.use(ElementUI)
這樣就好了image.png
當(dāng)然要先實(shí)現(xiàn)正常登錄,我這里用下真實(shí)的登錄接口,修改一下
src/api/user.js
image.png
.env.development
image.png
vue.config.js配置反向代理,然后重啟項(xiàng)目

箭頭標(biāo)注的根據(jù)實(shí)際情況修改
這樣實(shí)際是觸發(fā)登錄接口了,為啥沒(méi)登錄進(jìn)去呢,因?yàn)榈卿洺晒?,?huì)立即觸發(fā)getinfo 這個(gè)接口,這個(gè)接口請(qǐng)求出錯(cuò),就會(huì)清除token且又回到登錄頁(yè)了。
所以接下來(lái)要完善getinfo這個(gè)接口,先來(lái)看下邏輯
1.執(zhí)行完登錄請(qǐng)求后,會(huì)走permission.js中的邏輯
image.png
可以看到getinfo,所以就要完善getinfo這個(gè)接口,同樣換成真實(shí)的。
image.png
store/modules/user.js根據(jù)實(shí)際情況修改下getinfo這個(gè)方法,這里看自己公司要求,我們只是取到昵稱(chēng)和頭像存起來(lái),也可以在這個(gè)方法直接把該用戶(hù)所擁有的權(quán)限拿到并保存到vuex,建議在起一個(gè)接口,模擬的話(huà)我先在這個(gè)方法里把路由信息寫(xiě)死,然后在定一個(gè)存儲(chǔ)到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 => {
//用戶(hù)信息是根據(jù)token返回的,
//我們把token放到header里自動(dòng)帶過(guò)去了,這里就把state.token刪掉了
const { data } = response
if (!data) {
return reject('Verification failed, please Login again.')
}
const { nickname, avatar } = data
// 模擬請(qǐng)求數(shù)據(jù)
const menus = [
{
"path": "/system",
"redirect": "/menu",
"component": "Layout",
"meta": {
"title": "系統(tǒng)管理",
"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": "用戶(hù)管理",
"icon": "table"
}
}
]
}
]
//如果需要404 頁(yè)面,請(qǐng)?jiān)诖颂幪砑? 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) // 觸發(fā)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里已經(jīng)把昵稱(chēng)和頭像和路由表信息存進(jìn)去了。
image.png
下面把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' }
}]
}
]
image.png
繼續(xù),可以看到,后端返給我們的是
"component": "Layout",是一個(gè)字符串,我們要把過(guò)濾下這段路由表信息,并且addrouter到路由里去,并全局掛載一個(gè)global.antRouter ,渲染的時(shí)候會(huì)用到
先在router這個(gè)目錄下新建兩個(gè)js文件,開(kāi)發(fā)環(huán)境和生產(chǎn)環(huán)境導(dǎo)入組件的方式略有不同
_import_development.js
// 開(kāi)發(fā)環(huán)境導(dǎo)入組件
module.exports = file => require('@/views/' + file + '.vue').default // vue-loader at least v13.0.0+
_import_production.js
// 生產(chǎn)環(huán)境導(dǎo)入組件
module.exports = file => () => import('@/views/' + file + '.vue')
然后在permission.js中引入剛創(chuàng)建的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') // 請(qǐng)求獲取用戶(hù)信息
if (store.getters.menus.length < 1) {
global.antRouter = []
next()
}
const menus = filterAsyncRouter(store.getters.menus) // 1.過(guò)濾路由
console.log(menus);
router.addRoutes(menus) // 2.動(dòng)態(tài)添加路由
global.antRouter = menus // 3.將路由數(shù)據(jù)傳遞給全局變量,做側(cè)邊欄菜單渲染工作
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()
})
// // 遍歷后臺(tái)傳來(lái)的路由字符串,轉(zhuǎn)換為組件對(duì)象
function filterAsyncRouter(asyncRouterMap) {
const accessedRouters = asyncRouterMap.filter(route => {
if (route.component) {
if (route.component === 'Layout') {
route.component = Layout
} else {
route.component = _import(route.component) // 導(dǎo)入組件
}
}
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進(jìn)去
},
這時(shí)候登錄成功會(huì)報(bào)錯(cuò)
image.png
原因就是 沒(méi)有提前建好頁(yè)面。新建一下路由表內(nèi)的幾個(gè)頁(yè)面就行了
然后就成功了 有木有??!
image.png
下面我打算換成真實(shí)的接口數(shù)據(jù),真正實(shí)現(xiàn)開(kāi)篇的成果展示,下面的打算錄制成視頻,對(duì)應(yīng)視頻教程,點(diǎn)我去看視頻


















