feat: 动态菜单路由
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
export default ({ service, request, serviceForMock, requestForMock, mock, faker, tools }) => ({
|
||||
/**
|
||||
* @description 获取菜单与路由
|
||||
*/
|
||||
SYS_MENU () {
|
||||
// 模拟数据
|
||||
mock
|
||||
.onAny('/menu')
|
||||
.reply(() => {
|
||||
const aside = [
|
||||
{
|
||||
title: '百度一下',
|
||||
icon: 'folder-o',
|
||||
path: 'https://www.baidu.com'
|
||||
},
|
||||
{
|
||||
title: '单页面',
|
||||
icon: 'folder-o',
|
||||
path: '/page1'
|
||||
},
|
||||
{
|
||||
title: '二级目录',
|
||||
icon: 'folder-o',
|
||||
children: [
|
||||
{ path: '/page2', title: '页面 2' },
|
||||
{ path: '/page3', title: '页面 3' },
|
||||
{ path: '/page4', title: '页面 4' }
|
||||
]
|
||||
}
|
||||
]
|
||||
const header = aside
|
||||
const routes = [
|
||||
{ path: 'page1', name: 'page1', meta: { title: '单页面', auth: true }, component: 'demo/page1' },
|
||||
{ path: 'page2', name: 'page2', meta: { title: '页面 2', auth: true }, component: 'demo/page2' },
|
||||
{ path: 'page3', name: 'page3', meta: { title: '页面 3', auth: true }, component: 'demo/page3' },
|
||||
{ path: 'page4', name: 'page4', meta: { title: '页面 4', auth: true }, component: 'demo/page4' }
|
||||
]
|
||||
return tools.responseSuccess({ header, aside, routes })
|
||||
})
|
||||
// 接口请求
|
||||
return requestForMock({
|
||||
url: '/menu',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -4,5 +4,7 @@ import d2Container from './d2-container'
|
||||
|
||||
// 注意 有些组件使用异步加载会有影响
|
||||
Vue.component('d2-container', d2Container)
|
||||
Vue.component('d2-icon', () => import('./d2-icon'))
|
||||
Vue.component('d2-icon-svg', () => import('./d2-icon-svg/index.vue'))
|
||||
import d2Icon from './d2-icon'
|
||||
import d2IconSvg from './d2-icon-svg/index.vue'
|
||||
Vue.component('d2-icon', d2Icon)
|
||||
Vue.component('d2-icon-svg', d2IconSvg)
|
||||
|
||||
+3
-7
@@ -9,9 +9,11 @@ import store from '@/store/index'
|
||||
|
||||
// 菜单和路由设置
|
||||
import router from './router'
|
||||
import { menuHeader, menuAside } from '@/menu'
|
||||
import { frameInRoutes } from '@/router/routes'
|
||||
|
||||
// 全局提前加载字体图标样式,避免首次渲染闪烁
|
||||
import '@/components/d2-icon/font-awesome-4.7.0/css/font-awesome.min.css'
|
||||
|
||||
// 核心插件
|
||||
Vue.use(d2Admin)
|
||||
|
||||
@@ -23,12 +25,6 @@ new Vue({
|
||||
created () {
|
||||
// 处理路由 得到每一级的路由设置
|
||||
this.$store.commit('d2admin/page/init', frameInRoutes)
|
||||
// 设置顶栏菜单
|
||||
this.$store.commit('d2admin/menu/headerSet', menuHeader)
|
||||
// 设置侧边栏菜单
|
||||
this.$store.commit('d2admin/menu/asideSet', menuAside)
|
||||
// 初始化菜单搜索功能
|
||||
this.$store.commit('d2admin/search/init', menuHeader)
|
||||
},
|
||||
mounted () {
|
||||
// 展示系统信息
|
||||
|
||||
+3
-30
@@ -1,10 +1,5 @@
|
||||
import { uniqueId } from 'lodash'
|
||||
|
||||
/**
|
||||
* @description 给菜单数据补充上 path 字段
|
||||
* @description https://github.com/d2-projects/d2-admin/issues/209
|
||||
* @param {Array} menu 原始的菜单数据
|
||||
*/
|
||||
function supplementPath (menu) {
|
||||
return menu.map(e => ({
|
||||
...e,
|
||||
@@ -15,28 +10,6 @@ function supplementPath (menu) {
|
||||
}))
|
||||
}
|
||||
|
||||
export const menuHeader = supplementPath([
|
||||
{ path: '/index', title: '首页', icon: 'home' },
|
||||
{
|
||||
title: '页面',
|
||||
icon: 'folder-o',
|
||||
children: [
|
||||
{ path: '/page1', title: '页面 1' },
|
||||
{ path: '/page2', title: '页面 2' },
|
||||
{ path: '/page3', title: '页面 3' }
|
||||
]
|
||||
}
|
||||
])
|
||||
|
||||
export const menuAside = supplementPath([
|
||||
{ path: '/index', title: '首页', icon: 'home' },
|
||||
{
|
||||
title: '页面',
|
||||
icon: 'folder-o',
|
||||
children: [
|
||||
{ path: '/page1', title: '页面 1' },
|
||||
{ path: '/page2', title: '页面 2' },
|
||||
{ path: '/page3', title: '页面 3' }
|
||||
]
|
||||
}
|
||||
])
|
||||
// 启动时不再使用本地菜单,改为登录后从服务端获取
|
||||
export const menuHeader = supplementPath([])
|
||||
export const menuAside = supplementPath([])
|
||||
|
||||
+43
-7
@@ -10,6 +10,7 @@ import util from '@/libs/util.js'
|
||||
|
||||
// 路由数据
|
||||
import routes from './routes'
|
||||
import api from '@/api'
|
||||
|
||||
// fix vue-router NavigationDuplicated
|
||||
const VueRouterPush = VueRouter.prototype.push
|
||||
@@ -23,6 +24,8 @@ VueRouter.prototype.replace = function replace (location) {
|
||||
|
||||
Vue.use(VueRouter)
|
||||
|
||||
let dynamicAdded = false
|
||||
|
||||
// 导出路由 在 main.js 里使用
|
||||
const router = new VueRouter({
|
||||
routes
|
||||
@@ -41,27 +44,60 @@ router.beforeEach(async (to, from, next) => {
|
||||
NProgress.start()
|
||||
// 关闭搜索面板
|
||||
store.commit('d2admin/search/set', false)
|
||||
// 如果已登录且尚未注入动态菜单与路由,先从服务端获取并注入,避免刷新后菜单丢失
|
||||
const token = util.cookies.get('token')
|
||||
if (token && token !== 'undefined' && !dynamicAdded) {
|
||||
try {
|
||||
// 重要:避免当前是 /login 时注入导致无限跳转
|
||||
if (to.name === 'login') {
|
||||
next()
|
||||
return
|
||||
}
|
||||
const menuRes = await api.SYS_MENU()
|
||||
const { header = [], aside = [], routes = [] } = menuRes
|
||||
store.commit('d2admin/menu/headerSet', header)
|
||||
store.commit('d2admin/menu/asideSet', aside)
|
||||
store.commit('d2admin/search/init', header)
|
||||
const _import = require('@/libs/util.import.' + process.env.NODE_ENV)
|
||||
const layoutHeaderAside = require('@/layout/header-aside').default
|
||||
const dynamicChildren = routes.map(r => ({
|
||||
path: r.path,
|
||||
name: r.name,
|
||||
meta: r.meta || {},
|
||||
component: _import(r.component)
|
||||
}))
|
||||
if (dynamicChildren.length) {
|
||||
router.addRoutes([{ path: '/', component: layoutHeaderAside, children: dynamicChildren }])
|
||||
const { frameInRoutes } = require('@/router/routes')
|
||||
const dynamicFrameIn = JSON.parse(JSON.stringify(frameInRoutes))
|
||||
if (dynamicFrameIn && dynamicFrameIn[0] && Array.isArray(dynamicFrameIn[0].children)) {
|
||||
dynamicFrameIn[0].children = dynamicFrameIn[0].children.concat(dynamicChildren)
|
||||
}
|
||||
store.commit('d2admin/page/init', dynamicFrameIn)
|
||||
dynamicAdded = true
|
||||
// 关键:重新进入当前路由,确保新注册的路由生效,避免刷新后 404
|
||||
next({ path: to.fullPath, replace: true })
|
||||
return
|
||||
}
|
||||
dynamicAdded = true
|
||||
} catch (e) {
|
||||
// 注入失败直接继续,避免阻塞
|
||||
}
|
||||
}
|
||||
// 验证当前路由所有的匹配中是否需要有登录验证的
|
||||
if (to.matched.some(r => r.meta.auth)) {
|
||||
// 这里暂时将cookie里是否存有token作为验证是否登录的条件
|
||||
// 请根据自身业务需要修改
|
||||
const token = util.cookies.get('token')
|
||||
if (token && token !== 'undefined') {
|
||||
next()
|
||||
} else {
|
||||
// 没有登录的时候跳转到登录界面
|
||||
// 携带上登陆成功之后需要跳转的页面完整路径
|
||||
next({
|
||||
name: 'login',
|
||||
query: {
|
||||
redirect: to.fullPath
|
||||
}
|
||||
})
|
||||
// https://github.com/d2-projects/d2-admin/issues/138
|
||||
NProgress.done()
|
||||
}
|
||||
} else {
|
||||
// 不需要身份校验 直接通过
|
||||
next()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -21,34 +21,6 @@ const frameIn = [
|
||||
},
|
||||
component: _import('system/index')
|
||||
},
|
||||
// 演示页面
|
||||
{
|
||||
path: 'page1',
|
||||
name: 'page1',
|
||||
meta: {
|
||||
title: '页面 1',
|
||||
auth: true
|
||||
},
|
||||
component: _import('demo/page1')
|
||||
},
|
||||
{
|
||||
path: 'page2',
|
||||
name: 'page2',
|
||||
meta: {
|
||||
title: '页面 2',
|
||||
auth: true
|
||||
},
|
||||
component: _import('demo/page2')
|
||||
},
|
||||
{
|
||||
path: 'page3',
|
||||
name: 'page3',
|
||||
meta: {
|
||||
title: '页面 3',
|
||||
auth: true
|
||||
},
|
||||
component: _import('demo/page3')
|
||||
},
|
||||
// 系统 前端日志
|
||||
{
|
||||
path: 'log',
|
||||
|
||||
@@ -13,7 +13,7 @@ export default {
|
||||
* @param {Object} payload password {String} 密码
|
||||
* @param {Object} payload route {Object} 登录成功后定向的路由对象 任何 vue-router 支持的格式
|
||||
*/
|
||||
async login ({ dispatch }, {
|
||||
async login ({ dispatch, commit }, {
|
||||
username = '',
|
||||
password = ''
|
||||
} = {}) {
|
||||
@@ -29,6 +29,31 @@ export default {
|
||||
await dispatch('d2admin/user/set', { name: res.name }, { root: true })
|
||||
// 用户登录后从持久化数据加载一系列的设置
|
||||
await dispatch('load')
|
||||
// 登录成功后,从服务端获取菜单与路由并设置
|
||||
const menuRes = await api.SYS_MENU()
|
||||
const { header = [], aside = [], routes = [] } = menuRes
|
||||
// 设置菜单与搜索池
|
||||
await dispatch('d2admin/menu/asideCollapseSet', false, { root: true })
|
||||
commit('d2admin/menu/headerSet', header, { root: true })
|
||||
commit('d2admin/menu/asideSet', aside, { root: true })
|
||||
commit('d2admin/search/init', header, { root: true })
|
||||
// 动态注册路由(仅注册框架内的子路由)
|
||||
const _import = require('@/libs/util.import.' + process.env.NODE_ENV)
|
||||
const layoutHeaderAside = require('@/layout/header-aside').default
|
||||
const dynamicChildren = routes.map(r => ({
|
||||
path: r.path,
|
||||
name: r.name,
|
||||
meta: r.meta || {},
|
||||
component: _import(r.component)
|
||||
}))
|
||||
router.addRoutes([{ path: '/', component: layoutHeaderAside, children: dynamicChildren }])
|
||||
// 重新初始化 page 池,确保标签页与新路由一致
|
||||
const { frameInRoutes } = require('@/router/routes')
|
||||
const dynamicFrameIn = JSON.parse(JSON.stringify(frameInRoutes))
|
||||
if (dynamicFrameIn && dynamicFrameIn[0] && Array.isArray(dynamicFrameIn[0].children)) {
|
||||
dynamicFrameIn[0].children = dynamicFrameIn[0].children.concat(dynamicChildren)
|
||||
}
|
||||
commit('d2admin/page/init', dynamicFrameIn, { root: true })
|
||||
},
|
||||
/**
|
||||
* @description 注销用户并返回登录页面
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<template>
|
||||
<d2-container>
|
||||
<template slot="header">Page 4 header</template>
|
||||
Hello World
|
||||
</d2-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'page4'
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user