登錄
萬事開頭難,作什麼事都要有個起點,後面才能更好的進行下去,所以我選擇的起點就是最爲直觀的登錄頁面 /login/index.vue前端
/src/views/login/index
去除那些無關的東西,好比什麼 rules 校驗啊,默認的帳號密碼之類的東西,直接看核心登錄方法 handleLoginvue
handleLogin() {
this.$refs.loginForm.validate(valid => {
if (valid) {
this.loading = true
# 請求 store 中的方法
this.$store.dispatch('LoginByUsername', this.loginForm).then(() => {
this.loading = false
this.$router.push({ path: this.redirect || '/' })
}).catch((errorMsg) => {
this.loading = false
this.$message({
type: 'error',
message: errorMsg
})
})
} else {
console.log('error submit!!')
return false
}
})
},
store 主要事作一些緩存之類的處理,這裏調用了 store 中的 LoginByUsername 把表單內容傳過去,這裏我就改了點 catch 把我本身須要的錯誤信息進行提示,其餘東西不須要改動redis
/src/store/modules/user
上面登錄中請求的 LoginByUsername 正是在這數據庫
// 用戶名登陸
LoginByUsername({ commit }, userInfo) {
const username = userInfo.username.trim()
return new Promise((resolve, reject) => {
// 請求後臺登錄
loginByUsername(username, userInfo.password).then(response => {
const data = response.data
if (response.data.errorCode !== 200) {
// 登錄失敗,回傳提示信息
reject(data.errorMsg)
}
// 設置 token,做爲用戶已登錄的前端標識,存在 cookie 中
commit('SET_TOKEN', data.retData)
setToken(data.retData)
resolve()
}).catch(error => {
reject(error)
})
})
},
setToken() 方法會把 token 保存到 cookie 裏,很重要api
下面有個 GetUserInfo 方法,在你登錄的時候會去獲取你的權限數據數組
// 獲取用戶信息
GetUserInfo({ commit, state }) {
return new Promise((resolve, reject) => {
// 請求獲取權限
getUserInfo(state.token).then(response => {
if (response.status !== 200) { // 因爲mockjs 不支持自定義狀態碼只能這樣hack
reject('error')
}
const data = response.data
if (data.retData.module && data.retData.module.length > 0) { // 驗證返回的roles是不是一個非空數組
commit('SET_ROLES', data.retData.module)
} else {
// 當用戶無任何權限時設置
commit('SET_ROLES', ['普通用戶'])
}
commit('SET_NAME', data.retData.username)
commit('SET_AVATAR', 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif')
commit('SET_INTRODUCTION', data.retData.username)
resolve(response)
}).catch(error => {
reject(error)
})
})
},
不須要知道那個 new Promise 啥的幹啥用,反正我不知道,只要知道 getUserInfo 這個方法就好了,這個方法會以上面以前保存的 token 爲參數去請求獲取你的用戶權限,原邏輯是沒有權限就跳登錄頁面,我這系統須要,沒權限也有個首頁可看,因此
SET_ROLES 參數給了個「普通用戶」,反正什麼值無所謂有值,沒權限就行。
下面 commit 裏的參數,分別爲,用戶名,頭像,介紹,保留就好,也可自定義。
PS:在每次頁面跳轉時候頁面都會走一個 GetUserInfo ,以此來判斷用戶有沒有失效,所以,個人作法是直接把用戶信息包括權限保存在 redis 裏取,不要每次都從庫裏查緩存
/src/api/login
接口的請求都會從這裏去請求後臺,就很少贅述,基本只須要改動下請求路徑就ok了。
登出 logout 也是,請求保證後臺註銷,前端處理部分也是在 store/modules/user 裏的 LogOut 方法,基本不須要改動。cookie
權限
大多數系統都有根據用戶權限,或者說角色,展現相應頁面的要求框架
/src/router/index
結合框架來實現目錄的控制,若是是傳統項目,通常是把目錄結構整個存在後臺數據庫裏,而後前端循環展現出來,可是這個在這裏不須要這麼麻煩,只須要把每一個頁面對應的權限保存起來就好了異步
目錄在分爲兩個部分 constantRouterMap 與 asyncRouterMap
constantRouterMap:主要是通用部分,每一個用戶都有的頁面
asyncRouterMap:須要進行權限過濾的頁面
因此像首頁這種都丟在 constantRouterMap 裏,而像權限管理頁面這種放在 asyncRouterMap 裏
在目錄上加上 permission 標識,這個是我自定義的惟一標識,用於區分每一個頁面的權限,我這裏直接以頁面的路徑爲值,由於路徑確定惟一
{
path: '',
component: Layout,
alwaysShow: true,
redirect: '/xxx/xxx',
name: 'xxx',
meta: {
title: 'xxx',
icon: 'xxx'
},
children: [
{
path: '/xxx/xxx',
component: () => import('@/views/xxx/xxx'),
name: 'xxx',
meta: {
title: 'xxx',
icon: 'message',
# 權限標識,每一個目錄惟一
permission: '/xxx/xxx',
noCache: false }
}
]
},
而後就只須要在加載目錄時對目錄進行判斷就能夠了
/src/permission
這個文件就是整個路由邏輯在這
從這裏的邏輯能夠看到,登錄後,如今判斷了用戶權限,若是沒權限就會進入以前說到的 GetUserInfo 方法去獲取權限,因爲要對目錄進行控制,因此在 GetUserInfo 裏咱們也須要獲取到目錄的權限列表,只須要獲取到有的就好了,沒有權限的目錄就不須要獲取。
在 GetUserInfo 的最後經過 resolve 方法把返回值返回這個頁面,截圖中 module 就是我獲取到的有權限的目錄列表,而後經過
GenerateRoutes 來生成要加載的目錄,接下來就是改這了
/src/store/modules/permission
找到 GenerateRoutes
const permission = {
state: {
routers: constantRouterMap,
addRouters: []
},
mutations: {
SET_ROUTERS: (state, routers) => {
state.addRouters = routers
state.routers = constantRouterMap.concat(routers)
}
},
actions: {
GenerateRoutes({ commit }, data) {
return new Promise(resolve => {
const { roles } = data
// 權限對列表進行過濾
const accessedRouters = filterAsyncRouter(asyncRouterMap, roles)
commit('SET_ROUTERS', accessedRouters)
resolve(http://www.my516.com)
})
}
}
}
對於通用目錄咱們不須要管,在原邏輯中,這裏經過判斷若是用戶是管理員就直接把整個權限目錄都加載,若是不是再進行過濾,由於咱們這徹底就經過後臺控制,包括管理員因此就把判斷去掉了,直接對目錄進行過濾
找到 filterAsyncRouter 方法
/**
* 遞歸過濾異步路由表,返回符合用戶角色權限的路由表
* @param routes asyncRouterMap
* @param roles
*/
function filterAsyncRouter(routes, roles) {
const res = []
routes.forEach(route => {
const tmp = { ...route }
if (hasPermission(roles, tmp)) {
// 是否存在子節點,存在子節點說明當前節點爲展開欄,並不是頁面
if (tmp.children) {
tmp.children = filterAsyncRouter(tmp.children, roles)
// forCheck() 方法遞歸判斷該節點下是否存在頁面,不存在則隱藏
// true:不存在,false:存在
tmp.hidden = forCheck(tmp.children)
}
res.push(tmp)
}
})
return res
}
/**
* 遞歸子節點,判斷是否存在展現頁面,存在返回 false,不存在返回 true
* @param routes
*/
function forCheck(routes) {
// 設置默認爲隱藏
let isHidden = true
// 判斷是否存頁面,不存在說明該節點下不存在頁面
if (routes.length > 0) {
// 循環子目錄,若是子目錄中不存在須要權限頁面
// 說明子頁面全是展開欄,隱藏
for (const route of routes) {
// 存在 permission 說明爲頁面,不存在說明爲展開欄,將子頁面列表繼續遞歸
if (route.meta && route.meta.permission) {
isHidden = false
return
} else {
isHidden = forCheck(route.children)
}
}
}
return isHidden
}
經過循環權限目錄經過 hasPermission 方法進行判斷
forCheck 是我本身封裝的方法,由於項目中不僅存在二級目錄,因此,經過遞歸的方式,判斷子節點下是否有展現頁,也就是帶 permission 的頁面,若是有,返回 false,沒有返回 true
/** * 經過meta.role判斷是否與當前用戶權限匹配 * @param roles * @param route */function hasPermission(roles, route) { if (route.meta && route.meta.permission) { return roles.some(role => route.meta.permission.includes(role.permission)) } else { return true }}hasPermission 方法,這裏,咱們以前加的 permission 參數就起到做用了若是有 permission 就進行判斷, roles 參數就是 /src/permission 裏傳過來的 module 目錄。經過 roles.some 循環 roles 別名 role,若是目錄裏 includes(包含)這個目錄權限,就返回true,不然 false,爲 true 的會被顯示,固然若是直接沒有 permission 就說明不須要後臺管控,就直接 true,這樣目錄就被加載出來了。---------------------