废话不多说直接上代码:
首先一般的后台管理都是有登录页面的 我们在登陆页面实现 具体的 接口调用 获取 动态菜单和权限
这里我们就简单mock一下 上代码:
vue3.x-admin\src\mock\menu.json
[ { "path": "/home", "meta": { "title":"首页", "icon":"house", "isHideSubMenu": true, "isAuth": true }, "component" : "components/layout/Template.vue", "children" : [ { "path": "", "meta": { "title":"首页" }, "component" : "views/home/Index.vue" } ] }, { "path": "/article", "redirect": "/article/list", "meta": { "title":"文章管理", "icon":"document", "isAuth": true }, "auths": [ "article_add", "article_view", "article_delete", "article_edit" ], "component" : "components/layout/Template.vue", "children" : [ { "path": "list", "meta": { "title":"文章管理" }, "component" : "views/article/List.vue" }, { "path": "add", "meta": { "isHide": true, "title":"新增文章" }, "component" : "views/article/Add.vue" } ] }]
这是模拟的mock 菜单数据 我们在 登陆成功后 开始初始化 菜单
vue3.x-admin\src\views\login\Index.vue
<script>import { ref, watch, reactive, toRefs } from 'vue'import { ElLoading } from 'element-plus'import { useRoute, useRouter } from 'vue-router'import CookieUtil from 'cookie-tool-plug'import { useStore } from 'vuex'export default { setup(props, context) { const store = useStore() const router = useRouter() const state = reactive({ formInline: { account: 'admin', password: 'admin', } }) const onSubmit = () => { const loading = ElLoading.service({ lock: true, text: '初始化配置......', background: 'rgba(255,255,255, 1)', }) // 模拟接口 setTimeout(() => { const menus = require('@/mock/menu.json') // 初始化菜单 和 按钮权限 store.commit('global/SET_MENUS', menus) // 设置 token CookieUtil.set("token", "token"); router.push('/') loading.close() }, 2000) } return { ...toRefs(state), onSubmit } } }</script><template> <el-form :inline="true" :model="formInline" style="text-align: center;padding-top: 200px;"> <el-form-item label="账户名:"> <el-input v-model="formInline.account" placeholder="账户名"></el-input> </el-form-item> <el-form-item label="密码:"> <el-input v-model="formInline.password" type="password" show-password placeholder="密码"></el-input> </el-form-item> <el-form-item> <el-button type="primary" @click="onSubmit">登陆</el-button> </el-form-item> </el-form></template>
这里我们就把菜单缓存到了store当中了,
这时候我们的动态路由在 路由守卫 beforeEach 中实现,其中有几点需要注意,有些坑 坑了我挺长时间的:
- 就是借口返回的component 只是个字符串 如何动态拼接生成真正的模板引入呢?
- vue-router4.x 当中路由守卫的地方如何实现 只初始化一次动态路由?
- 初始化动态路由后 如何保证 页面刷新路由不丢失?
- 如何动态判断 默认跳转页面?
这些都是问题 我研究了半天 可能也是文档没看太明白!!!不多说了 上代码
vue3.x-admin\src\router\index.js
import { createRouter, createWebHashHistory } from 'vue-router'import CookieUtil from 'cookie-tool-plug'import store from '@/store'import _ from 'lodash'const routes = [ // 主模板 { path: '', name: 'app', meta: {}, component: () => import('@/views/Main.vue'), children: [ { path: '/no-auth', meta: { title:'暂无权限', isHide: true }, component: () => import('@/views/404/noAuth.vue'), }, { path: '/404', name: '404', meta: { title:'404', isHide: true }, component: () => import('@/views/404/404.vue'), }, ] }, { path: '/login', name: 'login', component: () => import('@/views/login/Index.vue'), }, { path: '/:pathMatch(.*)*', redirect: '/404' }];const router = createRouter({ history: createWebHashHistory(), routes,})// 权限检查const asyncGetAuthFun = to => { return new Promise((resolve, reject) => { if(!CookieUtil.get('token') && to.path !== '/login'){ resolve(false) } else { resolve(true) } })}// 动态路由const hasNecessaryRoute = () => { return router.getRoutes().find(v=>v.path === '')['children'].length !== 2}// 生产路由// const _import = file => require('@/' + file).default const generateRoute = () => { const menus = _.cloneDeep(store.state.global.menus) // 获取组件的方法 const getMenus = (menus) => { menus.forEach(item=>{ item.component = import('@/' + item.component) if(item.children) { getMenus(item.children) } }) } getMenus(menus) router.addRoute({path: '', meta: {}, redirect:menus[0]['path'], component: () => import('@/views/Main.vue'), name: 'app' , children: [ ...menus, ...routes[0]['children'] ]})}router.beforeEach(async (to, from) => { try { const res = await asyncGetAuthFun(to) if(!res) { return '/login' } else if(to.path !== '/login' && !hasNecessaryRoute()) { generateRoute() if(to.redirectedFrom){ return to.redirectedFrom.fullPath } else { return to.fullPath } } else { return true } } catch (error) { if (error instanceof NotAllowedError) { return false } else { throw error } }})export { router, routes }
这个地方实现了 路由拦截, 登陆权限验证, 动态添加路由 以及默认导航 页面实现
可以看到我们 使用 item.component = import('@/' + item.component)
这个来动态拼接 实现了 把字符串转换成了 实际模板,前面好像必须@/ 这个我也没细细研究,有老铁知道可以评论。
我们让动态路由只添加一次的 判断条件是 hasNecessaryRoute
这个函数 主要是根据 默认子路由数量来的 因为默认静态路由 是有两条的 如果超过两条 就代表已经添加过了。
同时我们是怎么实现 刷新重新跳转到 原来的页面的 因为刷新 默认会重定向到404 我们根据
return to.redirectedFrom.fullPath
这个来判断是否重定向 在没有动态添加的判断里面 如果有 那么添加完 动态路由就跳转回去
vue-router4.x 的路由守卫 有点饶人 建议多看几遍 不然你一不小心就陷入死循环当中了 , 嘎嘎嘎嘎
最后成功实现了 具体 面包屑 还有左侧菜单生成 可以看下仓库。就不贴了 比较麻烦!!!