feat: 动态菜单路由
This commit is contained in:
@@ -1,24 +1,11 @@
|
|||||||
[D2Admin](https://github.com/d2-projects/d2-admin) is a fully open source and free enterprise back-end product front-end integration solution, using the latest front-end technology stack, javascript files loading of local first screen less than 60kb, has prepared most of the project preparations, and with a lot of sample code to help the management system agile development.
|
## 安装依赖
|
||||||
|
```shell
|
||||||
[中文](https://github.com/d2-projects/d2-admin-start-kit/blob/master/README.zh.md) | **English**
|
nvm use 14
|
||||||
|
npm config set strict-ssl false
|
||||||
## Preview
|
npm install
|
||||||
|
npm config set strict-ssl true
|
||||||

|
```
|
||||||
[](https://app.netlify.com/sites/d2-admin-start-kit/deploys)
|
### 运行服务
|
||||||
|
```shell
|
||||||
The following access addresses are built and deployed by the latest master branch code at the same time. The access effect is completely consistent. Please select the appropriate access link according to your own network situation.
|
npm start
|
||||||
|
```
|
||||||
| server | link | server |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| d2.pub | [d2.pub/d2-admin-start-kit/preview](https://d2.pub/d2-admin-start-kit/preview) | China server |
|
|
||||||
| cdn.d2.pub | [cdn.d2.pub/d2-admin-start-kit/preview](https://cdn.d2.pub/d2-admin-start-kit/preview) | qiniu CDN |
|
|
||||||
| github | [d2-projects.github.io/d2-admin-start-kit](https://d2-projects.github.io/d2-admin-start-kit) | GitHub pages |
|
|
||||||
| netlify | [d2-admin-start-kit.netlify.com](https://d2-admin-start-kit.netlify.com) | Netlify CDN |
|
|
||||||
|
|
||||||
## Other synchronous repositories
|
|
||||||
|
|
||||||
| type | link |
|
|
||||||
| --- | --- |
|
|
||||||
| gitee | [https://gitee.com/d2-projects/d2-admin](https://gitee.com/d2-projects/d2-admin) |
|
|
||||||
| coding | [https://d2-projects.coding.net/p/d2-projects/d/d2-admin/git](https://d2-projects.coding.net/p/d2-projects/d/d2-admin/git) |
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
[D2Admin](https://github.com/d2-projects/d2-admin) 是一个完全 **开源免费** 的企业中后台产品前端集成方案,使用最新的前端技术栈,小于 60kb 的本地首屏 js 加载,已经做好大部分项目前期准备工作,并且带有大量示例代码,助力管理系统敏捷开发。
|
|
||||||
|
|
||||||
**中文** | [English](https://github.com/d2-projects/d2-admin-start-kit)
|
|
||||||
|
|
||||||
## 预览
|
|
||||||
|
|
||||||

|
|
||||||
[](https://app.netlify.com/sites/d2-admin-start-kit/deploys)
|
|
||||||
|
|
||||||
下列访问地址均由最新的 master 分支代码同时构建部署,访问效果完全一致,请根据自身网络情况选择合适的访问链接。
|
|
||||||
|
|
||||||
| 位置 | 链接 | 部署位置 |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| d2.pub | [preview](https://d2.pub/d2-admin-start-kit/preview) | 中国服务器 |
|
|
||||||
| cdn.d2.pub | [preview](https://cdn.d2.pub/d2-admin-start-kit/preview) | 七牛云 CDN |
|
|
||||||
| github | [preview](https://d2-projects.github.io/d2-admin-start-kit) | GitHub pages |
|
|
||||||
| netlify | [preview](https://d2-admin-start-kit.netlify.com) | Netlify CDN |
|
|
||||||
|
|
||||||
## 其它同步仓库
|
|
||||||
|
|
||||||
| 位置 | 链接 |
|
|
||||||
| --- | --- |
|
|
||||||
| 码云 | [https://gitee.com/d2-projects/d2-admin-start-kit](https://gitee.com/d2-projects/d2-admin-start-kit) |
|
|
||||||
| coding | [https://d2-projects.coding.net/p/d2-projects/d/d2-admin-start-kit/git](https://d2-projects.coding.net/p/d2-projects/d/d2-admin-start-kit/git) |
|
|
||||||
|
|
||||||
> 如果您在 github 仓库下载很慢,可以尝试使用我们的码云仓库克隆代码
|
|
||||||
+1
-3
@@ -2,9 +2,7 @@
|
|||||||
"name": "d2-admin",
|
"name": "d2-admin",
|
||||||
"version": "1.20.1",
|
"version": "1.20.1",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"serve": "vue-cli-service serve --open",
|
"start": "vue-cli-service serve",
|
||||||
"start": "npm run serve",
|
|
||||||
"dev": "npm run serve",
|
|
||||||
"build": "vue-cli-service build",
|
"build": "vue-cli-service build",
|
||||||
"build:preview": "NODE_OPTIONS=--max_old_space_size=4096 vue-cli-service build --mode preview",
|
"build:preview": "NODE_OPTIONS=--max_old_space_size=4096 vue-cli-service build --mode preview",
|
||||||
"lint": "vue-cli-service lint --fix",
|
"lint": "vue-cli-service lint --fix",
|
||||||
|
|||||||
@@ -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-container', d2Container)
|
||||||
Vue.component('d2-icon', () => import('./d2-icon'))
|
import d2Icon from './d2-icon'
|
||||||
Vue.component('d2-icon-svg', () => import('./d2-icon-svg/index.vue'))
|
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 router from './router'
|
||||||
import { menuHeader, menuAside } from '@/menu'
|
|
||||||
import { frameInRoutes } from '@/router/routes'
|
import { frameInRoutes } from '@/router/routes'
|
||||||
|
|
||||||
|
// 全局提前加载字体图标样式,避免首次渲染闪烁
|
||||||
|
import '@/components/d2-icon/font-awesome-4.7.0/css/font-awesome.min.css'
|
||||||
|
|
||||||
// 核心插件
|
// 核心插件
|
||||||
Vue.use(d2Admin)
|
Vue.use(d2Admin)
|
||||||
|
|
||||||
@@ -23,12 +25,6 @@ new Vue({
|
|||||||
created () {
|
created () {
|
||||||
// 处理路由 得到每一级的路由设置
|
// 处理路由 得到每一级的路由设置
|
||||||
this.$store.commit('d2admin/page/init', frameInRoutes)
|
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 () {
|
||||||
// 展示系统信息
|
// 展示系统信息
|
||||||
|
|||||||
+3
-30
@@ -1,10 +1,5 @@
|
|||||||
import { uniqueId } from 'lodash'
|
import { uniqueId } from 'lodash'
|
||||||
|
|
||||||
/**
|
|
||||||
* @description 给菜单数据补充上 path 字段
|
|
||||||
* @description https://github.com/d2-projects/d2-admin/issues/209
|
|
||||||
* @param {Array} menu 原始的菜单数据
|
|
||||||
*/
|
|
||||||
function supplementPath (menu) {
|
function supplementPath (menu) {
|
||||||
return menu.map(e => ({
|
return menu.map(e => ({
|
||||||
...e,
|
...e,
|
||||||
@@ -15,28 +10,6 @@ function supplementPath (menu) {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
export const menuHeader = supplementPath([
|
// 启动时不再使用本地菜单,改为登录后从服务端获取
|
||||||
{ path: '/index', title: '首页', icon: 'home' },
|
export const menuHeader = supplementPath([])
|
||||||
{
|
export const menuAside = supplementPath([])
|
||||||
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' }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
])
|
|
||||||
|
|||||||
+43
-7
@@ -10,6 +10,7 @@ import util from '@/libs/util.js'
|
|||||||
|
|
||||||
// 路由数据
|
// 路由数据
|
||||||
import routes from './routes'
|
import routes from './routes'
|
||||||
|
import api from '@/api'
|
||||||
|
|
||||||
// fix vue-router NavigationDuplicated
|
// fix vue-router NavigationDuplicated
|
||||||
const VueRouterPush = VueRouter.prototype.push
|
const VueRouterPush = VueRouter.prototype.push
|
||||||
@@ -23,6 +24,8 @@ VueRouter.prototype.replace = function replace (location) {
|
|||||||
|
|
||||||
Vue.use(VueRouter)
|
Vue.use(VueRouter)
|
||||||
|
|
||||||
|
let dynamicAdded = false
|
||||||
|
|
||||||
// 导出路由 在 main.js 里使用
|
// 导出路由 在 main.js 里使用
|
||||||
const router = new VueRouter({
|
const router = new VueRouter({
|
||||||
routes
|
routes
|
||||||
@@ -41,27 +44,60 @@ router.beforeEach(async (to, from, next) => {
|
|||||||
NProgress.start()
|
NProgress.start()
|
||||||
// 关闭搜索面板
|
// 关闭搜索面板
|
||||||
store.commit('d2admin/search/set', false)
|
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)) {
|
if (to.matched.some(r => r.meta.auth)) {
|
||||||
// 这里暂时将cookie里是否存有token作为验证是否登录的条件
|
|
||||||
// 请根据自身业务需要修改
|
|
||||||
const token = util.cookies.get('token')
|
|
||||||
if (token && token !== 'undefined') {
|
if (token && token !== 'undefined') {
|
||||||
next()
|
next()
|
||||||
} else {
|
} else {
|
||||||
// 没有登录的时候跳转到登录界面
|
|
||||||
// 携带上登陆成功之后需要跳转的页面完整路径
|
|
||||||
next({
|
next({
|
||||||
name: 'login',
|
name: 'login',
|
||||||
query: {
|
query: {
|
||||||
redirect: to.fullPath
|
redirect: to.fullPath
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
// https://github.com/d2-projects/d2-admin/issues/138
|
|
||||||
NProgress.done()
|
NProgress.done()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 不需要身份校验 直接通过
|
|
||||||
next()
|
next()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -21,34 +21,6 @@ const frameIn = [
|
|||||||
},
|
},
|
||||||
component: _import('system/index')
|
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',
|
path: 'log',
|
||||||
|
|||||||
@@ -13,7 +13,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 ({ dispatch, commit }, {
|
||||||
username = '',
|
username = '',
|
||||||
password = ''
|
password = ''
|
||||||
} = {}) {
|
} = {}) {
|
||||||
@@ -29,6 +29,31 @@ export default {
|
|||||||
await dispatch('d2admin/user/set', { name: res.name }, { root: true })
|
await dispatch('d2admin/user/set', { name: res.name }, { root: true })
|
||||||
// 用户登录后从持久化数据加载一系列的设置
|
// 用户登录后从持久化数据加载一系列的设置
|
||||||
await dispatch('load')
|
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 注销用户并返回登录页面
|
* @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