菜单路由动态加载 ook

This commit is contained in:
骑着蜗牛追导弹 2024-02-05 19:59:04 +08:00
parent fb0c0a7565
commit 7e1edc0905
8 changed files with 247 additions and 159 deletions

View File

@ -12,6 +12,7 @@ type Menu struct {
RouterName string `gorm:"not null" json:"routerName"` RouterName string `gorm:"not null" json:"routerName"`
RouterAuth uint `gorm:"not null" json:"routerAuth"` RouterAuth uint `gorm:"not null" json:"routerAuth"`
RouterHidden uint `gorm:"not null" json:"routerHidden"` RouterHidden uint `gorm:"not null" json:"routerHidden"`
RouterCache uint `gorm:"not null" json:"routerCache"`
RouterComponentPath string `gorm:"not null" json:"routerComponentPath"` RouterComponentPath string `gorm:"not null" json:"routerComponentPath"`
} }

View File

@ -8,9 +8,9 @@ import d2Admin from '@/plugin/d2admin'
import store from '@/store/index' import store from '@/store/index'
// 菜单和路由设置 // 菜单和路由设置
import router from './router' import router, { constantRoutes } from './router'
import { menuHeader, menuAside } from '@/menu' import api from '@/api'
import { frameInRoutes } from '@/router/routes' import { flushMenus } from '@/menu'
// 核心插件 // 核心插件
Vue.use(d2Admin) Vue.use(d2Admin)
@ -20,15 +20,23 @@ new Vue({
store, store,
i18n, i18n,
render: h => h(App), render: h => h(App),
methods: {
async loadMenuRouter () {
// 设置路由菜单
const menuTree = await api.queryAllMenus({})
const menuData = flushMenus(menuTree) || []
// 处理路由 得到每一级的路由设置
this.$store.commit('d2admin/page/init', constantRoutes)
// 设置顶栏菜单
// this.$store.commit('d2admin/menu/headerSet', menuData)
// 设置侧边栏菜单
this.$store.commit('d2admin/menu/asideSet', menuData)
// 初始化菜单搜索功能
this.$store.commit('d2admin/search/init', menuData)
}
},
created () { created () {
// 处理路由 得到每一级的路由设置 this.loadMenuRouter()
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 () { mounted () {
// 展示系统信息 // 展示系统信息

View File

@ -5,7 +5,7 @@ import { uniqueId } from 'lodash'
* @description https://github.com/d2-projects/d2-admin/issues/209 * @description https://github.com/d2-projects/d2-admin/issues/209
* @param {Array} menu 原始的菜单数据 * @param {Array} menu 原始的菜单数据
*/ */
function supplementPath (menu) { export function supplementPath (menu) {
return menu.map(e => ({ return menu.map(e => ({
...e, ...e,
path: e.path || uniqueId('d2-menu-empty-'), path: e.path || uniqueId('d2-menu-empty-'),
@ -15,19 +15,68 @@ function supplementPath (menu) {
})) }))
} }
const menuData = [ /**
{ path: '/index', title: '首页', icon: 'home' }, * 设置菜单
{ * @returns {{path: string, icon: string, title: string}[]}
title: '页面', */
icon: 'folder-o', export function flushMenus (menuTree = []) {
children: [ let menuData = [{ path: '/index', title: '首页', icon: 'home' }]
{ path: '/page1', title: '页面 1' }, try {
{ path: '/page2', title: '页面 2' }, // 权限菜单
{ path: '/page3', title: '页面 3' } const d2adminMenus = []
] if (menuTree && menuTree.length > 0) {
for (const menuItem of menuTree) {
let menuTemp
// 一级
if (menuItem.menuPath) {
menuTemp = { id: menuItem.id, title: menuItem.menuTitle, ico: menuItem.menuIcon, path: menuItem.menuPath }
} else {
// path不能为undefined会报错, 详情看源码'src/layout/header-aside/components/libs/util.menu.js'
menuTemp = { id: menuItem.id, title: menuItem.menuTitle, ico: menuItem.menuIcon, path: '' }
}
// 二级
const children = menuItem.children
if (children && children.length > 0) {
for (const childrenMenuItem of children) {
const childrenMenuTemp = {
id: menuItem.id,
title: childrenMenuItem.menuTitle,
ico: childrenMenuItem.menuIcon,
path: childrenMenuItem.menuPath
}
if (menuTemp.children && menuTemp.children.length > 0) {
menuTemp.children.push(childrenMenuTemp)
} else {
menuTemp.children = []
menuTemp.children.push(childrenMenuTemp)
}
}
}
d2adminMenus.push(menuTemp)
}
}
// 拼合菜单
menuData = menuData.concat(d2adminMenus)
return menuData
} catch (e) {
console.error(e)
return menuData
} }
] }
export const menuHeader = supplementPath(menuData) // const menuData = [
// { path: '/index', title: '首页', icon: 'home' },
export const menuAside = supplementPath(menuData) // {
// title: '页面',
// icon: 'folder-o',
// children: [
// { path: '/page1', title: '页面 1' },
// { path: '/page2', title: '页面 2' },
// { path: '/page3', title: '页面 3' }
// ]
// }
// ]
//
// export const menuHeader = supplementPath(menuData)
//
// export const menuAside = supplementPath(menuData)

View File

@ -7,9 +7,9 @@ import 'nprogress/nprogress.css'
import store from '@/store/index' import store from '@/store/index'
import util from '@/libs/util.js' import util from '@/libs/util.js'
import layoutHeaderAside from '@/layout/header-aside'
// 路由数据 // 由于懒加载页面太多的话会造成webpack热更新太慢所以开发环境不使用懒加载只有生产环境使用懒加载
import routes from './routes' const _import = require('@/libs/util.import.' + process.env.NODE_ENV)
// fix vue-router NavigationDuplicated // fix vue-router NavigationDuplicated
const VueRouterPush = VueRouter.prototype.push const VueRouterPush = VueRouter.prototype.push
@ -23,22 +23,107 @@ VueRouter.prototype.replace = function replace (location) {
Vue.use(VueRouter) Vue.use(VueRouter)
// 导出路由 在 main.js 里使用 export function buildRouter (menuItem) {
const router = new VueRouter({ return {
path: menuItem.routerPath,
name: menuItem.routerName,
meta: {
title: menuItem.menuTitle,
auth: menuItem.routerAuth === 1,
hidden: menuItem.routerHidden === 1,
cache: menuItem.routerHidden === 1
},
component: _import(menuItem.routerComponentPath)
}
}
export function buildRouterList (menuTree) {
// 权限路由, 全是一级路由
const routerData = []
for (const menuItem of menuTree) {
const children = menuItem.children
if (children && children.length > 0) {
for (const childMenuItem of children) {
if (childMenuItem.routerPath && childMenuItem.routerComponentPath) {
routerData.push(buildRouter(childMenuItem))
}
}
}
}
return routerData
}
/**
* @description 创建在 layout 中显示的路由设置
* @param {Array} routes 动态路由设置
*/
export function createRoutesInLayout (routes = []) {
return [
{
path: '/',
redirect: { name: 'index' },
component: layoutHeaderAside,
children: [
{ path: 'index', name: 'index', meta: { title: '首页', auth: true }, component: _import('system/index') },
{ path: 'log', name: 'log', meta: { title: '前端日志', auth: true }, component: _import('system/log') },
...routes
]
}
]
}
// 在 layout 之外显示的路由
export const routesOutLayout = [
// 刷新页面 必须保留
{ path: '/refresh', name: 'refresh', component: _import('system/function/refresh'), hidden: true },
// 页面重定向 必须保留
{ path: '/redirect/:route*', name: 'redirect', component: _import('system/function/redirect'), hidden: true },
// 登陆页面 必须保留
{ path: '/login', name: 'login', component: _import('system/login'), hidden: true },
{ path: '*', name: '404', component: _import('system/error/404'), hidden: true }
]
// 默认的路由
export const constantRoutes = createRoutesInLayout().concat(routesOutLayout)
/**
* @description 创建路由
* @param {Array} routes 路由设置
*/
const createRouter = (routes = []) => new VueRouter({
scrollBehavior: () => ({ y: 0 }),
routes routes
}) })
// 导出路由 在 main.js 里使用
const router = createRouter(constantRoutes)
/**
* @description 重新设置路由
* @param {Array} routes 额外追加的路由
*/
export function resetRouter (routes = []) {
router.matcher = createRouter(routes).matcher
}
/** /**
* 路由拦截 * 路由拦截
* 权限验证 * 权限验证
*/ */
router.beforeEach(async (to, from, next) => { router.beforeEach(async (to, from, next) => {
// 进度条
NProgress.start()
console.log('即将进入的路由:', to);
console.log('当前离开的路由:', from);
// 获取路由参数或查询参数
console.log('路由参数:', to.params);
console.log('查询参数:', to.query);
// 确认已经加载多标签页数据 https://github.com/d2-projects/d2-admin/issues/201 // 确认已经加载多标签页数据 https://github.com/d2-projects/d2-admin/issues/201
await store.dispatch('d2admin/page/isLoaded') await store.dispatch('d2admin/page/isLoaded')
// 确认已经加载组件尺寸设置 https://github.com/d2-projects/d2-admin/issues/198 // 确认已经加载组件尺寸设置 https://github.com/d2-projects/d2-admin/issues/198
await store.dispatch('d2admin/size/isLoaded') await store.dispatch('d2admin/size/isLoaded')
// 进度条 // 加载动态路由 内部已经做了对登录状态和是否已经加载动态路由的判断
NProgress.start() await store.dispatch('d2admin/router/load', { to: to.fullPath })
// 关闭搜索面板 // 关闭搜索面板
store.commit('d2admin/search/set', false) store.commit('d2admin/search/set', false)
// 验证当前路由所有的匹配中是否需要有登录验证的 // 验证当前路由所有的匹配中是否需要有登录验证的

View File

@ -1,113 +0,0 @@
import layoutHeaderAside from '@/layout/header-aside'
// 由于懒加载页面太多的话会造成webpack热更新太慢所以开发环境不使用懒加载只有生产环境使用懒加载
const _import = require('@/libs/util.import.' + process.env.NODE_ENV)
console.log('================= 刷新会导致路由重新加载')
/**
* 在主框架内显示
*/
const frameIn = [
{
path: '/',
redirect: { name: 'index' },
component: layoutHeaderAside,
children: [
// 首页
{
path: 'index',
name: 'index',
meta: {
auth: true
},
component: _import('system/index')
},
// 系统 前端日志
{
path: 'log',
name: 'log',
meta: {
title: '前端日志',
auth: true
},
component: _import('system/log')
},
// 刷新页面 必须保留
{
path: 'refresh',
name: 'refresh',
hidden: true,
component: _import('system/function/refresh')
},
// 页面重定向 必须保留
{
path: 'redirect/:route*',
name: 'redirect',
hidden: true,
component: _import('system/function/redirect')
},
// 演示页面
{
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')
}
]
}
]
/**
* 在主框架之外显示
*/
const frameOut = [
// 登录
{
path: '/login',
name: 'login',
component: _import('system/login')
}
]
/**
* 错误页面
*/
const errorPage = [
{
path: '*',
name: '404',
component: _import('system/error/404')
}
]
// 导出需要显示菜单的
export const frameInRoutes = frameIn
// 重新组织后导出
export default [
...frameIn,
...frameOut,
...errorPage
]

View File

@ -5,6 +5,10 @@ import api from '@/api'
export default { export default {
namespaced: true, namespaced: true,
state: {
// 用户登录状态
isLogged: !!util.cookies.get('token')
},
actions: { actions: {
/** /**
* @description 登录 * @description 登录
@ -13,10 +17,7 @@ export default {
* @param {Object} payload password {String} 密码 * @param {Object} payload password {String} 密码
* @param {Object} payload route {Object} 登录成功后定向的路由对象 任何 vue-router 支持的格式 * @param {Object} payload route {Object} 登录成功后定向的路由对象 任何 vue-router 支持的格式
*/ */
async login ({ dispatch }, { async login ({ commit, dispatch }, { username = '', password = '', to = '/' } = {}) {
username = '',
password = ''
} = {}) {
const res = await api.login({ username, password }) const res = await api.login({ username, password })
// 设置 cookie 一定要存 uuid 和 token 两个 cookie // 设置 cookie 一定要存 uuid 和 token 两个 cookie
// 整个系统依赖这两个数据进行校验和存储 // 整个系统依赖这两个数据进行校验和存储
@ -27,10 +28,10 @@ export default {
util.cookies.set('username', res.username) util.cookies.set('username', res.username)
util.cookies.set('name', res.name) util.cookies.set('name', res.name)
util.cookies.set('token', res.token) util.cookies.set('token', res.token)
// 设置路由菜单 // 设置用户已经登陆
console.log('========= 设置路由菜单') commit('isLoggedSet', true)
const menuTree = await api.queryAllMenus({}) // 加载路由权限
console.log('========= ', menuTree) await dispatch('d2admin/router/load', { focus: true, to }, { root: true })
// 设置 vuex 用户信息 // 设置 vuex 用户信息
await dispatch('d2admin/user/set', { name: res.name }, { root: true }) await dispatch('d2admin/user/set', { name: res.name }, { root: true })
// 用户登录后从持久化数据加载一系列的设置 // 用户登录后从持久化数据加载一系列的设置

View File

@ -0,0 +1,57 @@
import router, { buildRouterList, createRoutesInLayout, resetRouter, routesOutLayout } from '@/router'
import api from '@/api'
import { flushMenus, supplementPath } from '@/menu'
export default {
namespaced: true,
state: {
// 是否已经加载
isLoaded: false,
// 用户权限
permissions: []
},
actions: {
/**
* @description 用户登录后从持久化数据加载一系列的设置
* @param {Object} context
*/
async load ({ state, rootState, commit, dispatch }, { focus = false, to = '', data }) {
// 取消请求 - 没有登录
if (!data && !rootState.d2admin.account.isLogged) return
// 取消请求 - 已经加载过动态路由
if (!focus && state.isLoaded) return
// 获取接口原始数据
const menuTree = await api.queryAllMenus({})
console.log('=================== loadXXXXXX')
// [ 菜单 ] 计算菜单
const menuData = flushMenus(menuTree)
const menus = supplementPath(menuData)
// [ 菜单 ] 设置侧栏菜单
commit('d2admin/menu/asideSet', menus, { root: true })
// [ 路由 ] 计算路由
const routes = createRoutesInLayout(buildRouterList(menuTree)).concat(routesOutLayout)
// [ 路由 ] 重新设置路由
resetRouter(routes)
// [ 路由 ] 重新设置多标签页池
commit('d2admin/page/init', routes, { root: true })
// [ 标签页 ] 重新计算多标签页数据
dispatch('d2admin/page/openedLoad', { filter: true }, { root: true })
// [ 搜索 ] 初始化搜索数据
commit('d2admin/search/init', menus, { root: true })
// [ 路由 ] 重新访问
if (to) router.replace(to)
// 标记已经加载过动态路由
commit('isLoadedSet', true)
}
},
mutations: {
/**
* @description 设置动态路由加载状态
* @param {Object} state state
* @param {Boolean} value 是否已经加载动态路由
*/
isLoadedSet (state, value) {
state.isLoaded = value
}
}
}

View File

@ -175,12 +175,12 @@ export default {
// //
this.login({ this.login({
username: this.formLogin.username, username: this.formLogin.username,
password: this.formLogin.password password: this.formLogin.password,
to: this.$route.query.redirect || '/'
}).then(() => {
//
this.$router.replace(this.$route.query.redirect || '/')
}) })
.then(() => {
//
this.$router.replace(this.$route.query.redirect || '/')
})
} else { } else {
// //
this.$message.error('表单校验失败,请检查') this.$message.error('表单校验失败,请检查')