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