參考https://blog.51cto.com/hequan/2383130
後端部分
1.model
// TODO 後續可能需要擴展一些字段
/**類型*/
@ApiModelProperty(value = "類型",dataType = "String")
private String type;
/**名稱*/
@ApiModelProperty(value = "名稱",dataType = "String")
private String name;
/**代碼*/
@ApiModelProperty(value = "代碼",dataType = "String")
private String code;
/**後臺訪問路徑*/
@ApiModelProperty(value = "uri",dataType = "String")
private String uri;
/**序號*/
@ApiModelProperty(value = "sort",dataType = "Integer")
private Integer sort;
/**父菜單ID*/
@ApiModelProperty(value="parentId",dataType="String")
private String parentId;
/**圖標*/
@ApiModelProperty(value="圖標",dataType="String")
private String metaIcon;
/**權限標記*/
@ApiModelProperty(value="權限標記",dataType="String")
private String permission;
/** 菜單path */
@ApiModelProperty(value="菜單path",dataType="String")
private String localDirPath;
/** 菜單title */
@ApiModelProperty(value="菜單title",dataType="String")
private String metaTitle;
/** 菜單component路徑 */
@ApiModelProperty(value="菜單component路徑",dataType="String")
private String localFilePath;
@ApiModelProperty(hidden = true)
@TableField(exist = false)
private List<Permission> children = new ArrayList<Permission>();
2.service
@Override
public List<Permission> bulidTree(List<Permission> permissions) {
List<Permission> returnList = new ArrayList<Permission>();
for(Iterator<Permission> iterator = permissions.iterator(); iterator.hasNext();) {
Permission t = (Permission) iterator.next();
if("0".equals(t.getParentId())) {
recursionFn(permissions, t);
returnList.add(t);
}
}
if (returnList.isEmpty()) {
returnList = permissions;
}
return returnList;
}
/** 遞歸列表 */
private void recursionFn(List<Permission> list, Permission t) {
List<Permission> childList = getChildList(list, t);
t.setChildren(childList);
for (Permission tchild : childList) {
if ( hasChild(list, tchild)) {
Iterator<Permission> it = childList.iterator();
while(it.hasNext()) {
Permission p = (Permission) it.next();
recursionFn(list, p);
}
}
}
}
/** 根據子節點獲取列表 */
private List<Permission> getChildList(List<Permission> list, Permission t) {
List<Permission> tlist = new ArrayList<Permission>();
Iterator<Permission> it = list.iterator();
while(it.hasNext()) {
Permission p = (Permission) it.next();
if (p.getParentId().equals(t.getId())) {
tlist.add(p);
}
}
return tlist;
}
/** 判斷是否有子節點 */
private boolean hasChild(List<Permission> list, Permission p) {
return getChildList(list, p).size() > 0 ? true : false;
}
3.controller
@RequestMapping(value = { "menu" }, method = { RequestMethod.GET })
@ApiOperation(value = "菜單列表", notes = "返回菜單樹")
protected ResponseData<List<Permission>> menu(@Validated Permission entity) {
ResponseData<List<Permission>> json = new ResponseData<List<Permission>>();
List<Permission> sourcelist = (List<Permission>)this.service.findList(entity);
json.setMsg("獲取樹型菜單失敗");
if (null != entity) {
json.setData(this.service.bulidTree(sourcelist));
json.setSuccess(true);
json.setMsg("獲取樹型菜單成功");
}
return json;
}
前端部分
1.src/api/setting.js 添加獲取菜單樹axios
//chenlf 獲取左側菜單數
export const getMenuData = () => {
return axios.request({
url: '/permission/menu',
method: 'post'
})
}
2.src/libs 新建工具 router-util.js
/**
*
* @title 定義初始化菜單
* @Author chenglf
* @Date 2020/4/15
**/
// import axios from 'axios'
//import config from '@/config'
import Main from '@/components/main' // Main 是架構組件,不在後臺返回,在文件裏單獨引入
import parentView from '@/components/parent-view' // 獲取組件的方法
import store from '@/store' // parentView 是二級架構組件,不在後臺返回,在文件裏單獨引入
import { hasChild, getToken, storageSave, storageRead } from '@/libs/util'
import { lazyLoadingCop, forEach } from '@/libs/tools'
import { getMenuData } from '@/api/setting' //獲取菜單樹
// eslint-disable-next-line no-unused-vars
const _import = require('@/router/_import_' + process.env.NODE_ENV)
//路由localstorage的key
export const MENU_DYNAMIC_ROUTER = 'dynamicRouter'
//清除菜單
export const clearMenu = () => {
localStorage.removeItem(MENU_DYNAMIC_ROUTER)
}
//讀取菜單
export const readMenu = () => {
const menu = localStorage.getItem(MENU_DYNAMIC_ROUTER)
if (menu) return menu
else return false
}
//保存菜單
export const saveMenu = (data) => {
localStorage.setItem(MENU_DYNAMIC_ROUTER, JSON.stringify(data))
}
var gotRouter
// 初始化路由
export const initRouter = () => {
if (!getToken()) {
return
}
var routerData
if (!gotRouter) {
getMenuData().then(res => {
routerData = res.data.data // 後臺拿到路由
//TODO 判斷是否獲取到後臺數據
saveMenu(routerData) // 存儲路由到localStorage
gotRouter = filterAsyncRouter(routerData) // 過濾路由,路由組件轉換
store.commit('updateMenuList', gotRouter)
dynamicRouterAdd()
})
} else {
gotRouter = dynamicRouterAdd()
}
return gotRouter
}
// 加載路由菜單,從localStorage拿到路由,在創建路由時使用
export const dynamicRouterAdd = () => {
let dynamicRouter = []
let data = storageRead(MENU_DYNAMIC_ROUTER)
if (!data) {
return dynamicRouter
}
dynamicRouter = filterAsyncRouter(JSON.parse(data))
return dynamicRouter
}
// 遍歷格式化菜單
export const filterAsyncRouter = asyncRouterMap => {
let res = []
forEach(asyncRouterMap, item => {
let obj = {
path: item.localDirPath,
name: item.name
}
obj.meta = {
icon: item.metaIcon,
title: item.metaTitle
}
if (item.parentId === '0') {
obj.component = Main
} else {
if (item.localFilePath !=null && item.localFilePath != '') {
obj.component = lazyLoadingCop(item.localFilePath)
}
}
if (hasChild(item)) {
obj.children = filterAsyncRouter(item.children)
}
res.push(obj)
})
return res
}
3.src/libs/tools.js 中添加 引入.vue組件的封裝函數
/**
* chenlf .vue組件的封裝函數
* @param file 組件路徑
*/
export const lazyLoadingCop = file => require('@/view/standard' + file + '.vue').default
4.src/router 新增_import_development.j s和_import_production.js 爲引入.vue組件的封裝
/**
* @title 開發環境 引入.vue組件的封裝
* @Author chenglf
**/
module.default = file => require('@/view/standard' + file + '.vue').default // vue-loader at least v13.0.0+
/**
* @title 生產環境 引入.vue組件的封裝
* @Author chenglf
**/
module.exports = file => () => import('@/view/standard' + file + '.vue')
5.src/store/module/app.js :vux部分updateMenuList更新菜單數據。
1)mutations添加 updateMenuList
2)state添加 menuList:[]
3) getters修改 menuList: (state, getters, rootState) =>
getMenuByRouter(state.menuList, rootState.user.access)
import {
getBreadCrumbList,
setTagNavListInLocalstorage,
getMenuByRouter,
getTagNavListFromLocalstorage,
getHomeRoute,
getNextRoute,
routeHasExist,
routeEqual,
getRouteTitleHandled,
localSave,
localRead
} from '@/libs/util'
import { saveErrorLogger } from '@/api/data'
import router from '@/router'
import config from '@/config'
const { homeName } = config
const closePage = (state, route) => {
const nextRoute = getNextRoute(state.tagNavList, route)
state.tagNavList = state.tagNavList.filter(item => {
return !routeEqual(item, route)
})
router.push(nextRoute)
}
export default {
state: {
menuList: [],//菜單
breadCrumbList: [],
tagNavList: [],
homeRoute: {},
local: localRead('local'),
errorList: [],
hasReadErrorPage: false
},
getters: {
menuList: (state, getters, rootState) =>
getMenuByRouter(state.menuList, rootState.user.access),
errorCount: state => state.errorList.length
},
mutations: {
updateMenuList (state, routes) {//chenlf 添接受前臺數組,刷新菜單
router.addRoutes(routes) // 動態添加路由
state.menuList = routes
},
setBreadCrumb (state, route) {
state.breadCrumbList = getBreadCrumbList(route, state.homeRoute)
},
setHomeRoute (state, routes) {
state.homeRoute = getHomeRoute(routes, homeName)
},
setTagNavList (state, list) {
let tagList = []
if (list) {
tagList = [...list]
} else tagList = getTagNavListFromLocalstorage() || []
if (tagList[0] && tagList[0].name !== homeName) tagList.shift()
let homeTagIndex = tagList.findIndex(item => item.name === homeName)
if (homeTagIndex > 0) {
let homeTag = tagList.splice(homeTagIndex, 1)[0]
tagList.unshift(homeTag)
}
state.tagNavList = tagList
setTagNavListInLocalstorage([...tagList])
},
closeTag (state, route) {
let tag = state.tagNavList.filter(item => routeEqual(item, route))
route = tag[0] ? tag[0] : null
if (!route) return
closePage(state, route)
},
addTag (state, { route, type = 'unshift' }) {
let router = getRouteTitleHandled(route)
if (!routeHasExist(state.tagNavList, router)) {
if (type === 'push') state.tagNavList.push(router)
else {
if (router.name === homeName) state.tagNavList.unshift(router)
else state.tagNavList.splice(1, 0, router)
}
setTagNavListInLocalstorage([...state.tagNavList])
}
},
setLocal (state, lang) {
localSave('local', lang)
state.local = lang
},
addError (state, error) {
state.errorList.push(error)
},
setHasReadErrorLoggerStatus (state, status = true) {
state.hasReadErrorPage = status
}
},
actions: {
addErrorLog ({ commit, rootState }, info) {
if (!window.location.href.includes('error_logger_page')) commit('setHasReadErrorLoggerStatus', false)
const { user: { token, userId, userName } } = rootState
let data = {
...info,
time: Date.parse(new Date()),
token,
userId,
userName
}
saveErrorLogger(info).then(() => {
commit('addError', data)
})
}
}
}
6.src/router/routers.js 左側菜單加入
import Main from '@/components/main'
import { dynamicRouterAdd } from '@/libs/router-util' // chenlf 引入加載菜單
// 不作爲Main組件的子頁面展示的頁面單獨寫
export const otherRouter = [
{
path: '/login',
name: 'login',
meta: {
title: 'Login - 登錄',
hideInMenu: true
},
component: () => import('@/view/login/login.vue')
},
{
path: '/401',
name: 'error_401',
meta: {
hideInMenu: true
},
component: () => import('@/view/error-page/401.vue')
},
{
path: '/500',
meta: {
title: '500-服務端錯誤'
},
name: 'error_500',
component: () => import('@/view/error-page/500.vue')
}
]
// 作爲Main組件的子頁面展示但是不在左側菜單顯示的路由寫在mainRouter裏
export const mainRouter = [
{
path: '/',
name: '_home',
redirect: '/home',
component: Main,
meta: {
hideInMenu: true,
notCache: true
},
children: [
{
path: '/home',
name: 'home',
meta: {
hideInMenu: true,
title: '首頁',
notCache: true,
icon: 'md-home'
},
component: () => import('@/view/single-page/home')
}
]
}
]
// 作爲Main組件的子頁面展示並且在左側菜單顯示的路由寫在appRouter裏
export const appRouter = [...dynamicRouterAdd()]
export const routes = [ ...otherRouter, ...mainRouter, ...appRouter ]
// 所有上面定義的路由都要寫在下面輸出
export default routes
7.src/router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import routes from './routers'
import store from '@/store'
import iView from 'view-design'
import config from '@/config'
import { hasChild, storageRead, storageSave, setToken, getToken, canTurnTo, setTitle } from '@/libs/util'
import { dynamicRouterAdd, filterAsyncRouter, initRouter, saveMenu } from '@/libs/router-util'
import { getMenuData } from '@/api/setting' //獲取菜單樹
const { homeName } = config
Vue.use(Router)
const router = new Router({
routes,
//base: '/standard',
mode: 'history',
scrollBehavior: () => ({ y: 0 })
})
const LOGIN_PAGE_NAME = 'login'
/*const turnTo = (to, access, next) => {
//chenlf
if (canTurnTo(to.name, access, [...routes, ...dynamicRouterAdd()])) next()
//if (canTurnTo(to.name, access, routes)) next() // 有權限,可訪問
else next({ replace: true, name: 'error_401' }) // 無權限,重定向到401頁面
}*/
//全局的router.beforeEach()是優先於頁面中的beforeRouterEnter()
router.beforeEach((to, from, next) => {
iView.LoadingBar.start()
const token = getToken()
if (!token && to.name !== LOGIN_PAGE_NAME) {
// 未登錄且要跳轉的頁面不是登錄頁
next({
name: LOGIN_PAGE_NAME // 跳轉到登錄頁
})
} else if (!token && to.name === LOGIN_PAGE_NAME) {
// 未登陸且要跳轉的頁面是登錄頁
next() // 跳轉
} else if (token && to.name === LOGIN_PAGE_NAME) {
// 已登錄且要跳轉的頁面是登錄頁
next({
name: homeName // 跳轉到homeName頁
})
} else {
/*initRouter()
if (store.state.user.hasGetInfo) {
turnTo(to, store.state.user.access, next)
} else {
store.dispatch('getUserInfo').then(user => {
// 拉取用戶信息,通過用戶權限和跳轉的頁面的name來判斷是否有權限訪問;access必須是一個數組,如:['super_admin'] ['super_admin', 'admin']
turnTo(to, user.access, next)
}).catch(() => {
setToken('')
next({name: 'login'})
})
}*/
const menu = initRouter()//獲取菜單樹
store.dispatch('getUserInfo').then(user => {
// 如果本地不存在路由數據則動態獲取
if (!menu || menu.length === 0) {
getMenuData().then(res => {
var list = []
var menuData = res.data.data
saveMenu(menuData)
// 格式化菜單
list = filterAsyncRouter(menuData)
// 將404路由動態注入,防止第一次沒有路由數據跳轉到404,
list.push({
path: '*',
name: 'error_404',
meta: {
hideInMenu: true
},
component: () => import('@/view/error-page/404.vue')
})
// 刷新界面菜單
store.commit('updateMenuList', list)
next()
})
} else {
next()
}
}).catch(() => {
setToken('')
next({
name: 'login'
})
})
}
})
router.afterEach(to => {
setTitle(to, router.app)
iView.LoadingBar.finish()
window.scrollTo(0, 0)
})
export default router
8.store/module/user.js 增加initRouter()初始化路由
import { initRouter } from '@/libs/router-util' // ①新增 引入動態菜單渲染
handleLogin ({ commit }, { userName, password }) {
userName = userName.trim()
return new Promise((resolve, reject) => {
login({
userName,
password
})
.then(res => {
const data = res.data
commit('setToken', data.token)
console.log('token', getToken())
initRouter()
resolve()
})
.catch(err => {
reject(err)
})
})
},
// 退出登錄
handleLogOut ({ state, commit }) {
return new Promise((resolve, reject) => {
logout(state.token)
.then(res => {
console.log('退出', res)
commit('setToken', '')
commit('setAccess', [])
localSave('dynamicRouter', []) // 主要修改這裏 清空本地路由
resolve()
})
.catch(err => {
reject(err)
})
// 如果你的退出登錄無需請求接口,則可以直接使用下面三行代碼而無需使用logout調用接口
// commit('setToken', '')
// commit('setAccess', [])
// resolve()
})
},