iview-admin 学习 02 动态路由

参考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()
		  })
		},
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章