实战指南:Vue 2基座 + Vue 3 + Vite + TypeScript子应用+vue2微前端架构实现动态菜单与登录共享
导读:
在当今的前端开发中,微前端架构已经成为了一种流行的架构模式。本文将介绍如何结合Vue 2基座
、Vue 3子应用
、Vite构建工具和TypeScript语言
,利用qiankun
微前端框架实现动态菜单和登录共享功能的实战指南。
效果:
引言
当前项目架构,vue+ruoyi+elementUI
作为基座,vue3+recoDesign+vite+ts
作为子应用,基座只用于登录鉴权,动态菜单功能,尽量少在基座写其他业务,子应用分为业务子应用,系统子应用,其他业务子应用,结合npm私服组件库
进行组件抽取,供各个子应用使用,基座登录后,将token
及其其他子应用需要的参数通过props进行传递比如最直接的【按钮权限,token
】。
技术栈介绍
- vue2全家桶+ruoyi脚手架进行基座改造。
- vue3全家桶+arcoDesign中台后台脚手架进行子应用改造。
vue2子应用改造,vue3子应用改造
http://t.csdnimg.cn/U7a3p,vue2+qiankun项目实战
http://t.csdnimg.cn/4UFDs,vue3+qiankun项目实战
- npm私服组件库的打包上传拉取使用:http://t.csdnimg.cn/5Tgax。
创建vue2基座
- 安装qiankun
npm i qiankun -S
- 设置需要将子应用的页面嵌入到主应用的某个
div
中(子应用在主应用上的渲染出口)<div id="subapp-container">
- 在主应用(基座)中注册子应用
整体代码:
import Vue from 'vue'
import Cookies from 'js-cookie'
import Element from 'element-ui'
import './assets/styles/element-variables.scss'
import { registerMicroApps, start, setDefaultMountApp } from 'qiankun';
import "ant-design-vue/dist/antd.less"
import 'default-passive-events'
import '@/assets/styles/index.scss' // global css
import '@/assets/styles/ruoyi.scss' // ruoyi css
import App from './App'
import store from './store'
import router from './router'
import directive from './directive' // directive
import plugins from './plugins' // plugins
import { download } from '@/utils/request'
import './assets/icons' // icon
import './permission' // permission control
import { getDicts } from "@/api/system/dict/data";
import { getConfigKey } from "@/api/system/config";
import { parseTime, resetForm, addDateRange, selectDictLabel, selectDictLabels, handleTree } from "@/utils/ruoyi";
// 分页组件
import Pagination from "@/components/Pagination";
// 自定义表格工具组件
import RightToolbar from "@/components/RightToolbar"
// 富文本组件
import Editor from "@/components/Editor"
// 文件上传组件
import FileUpload from "@/components/FileUpload"
// 图片上传组件
import ImageUpload from "@/components/ImageUpload"
// 图片预览组件
import ImagePreview from "@/components/ImagePreview"
// 字典标签组件
import DictTag from '@/components/DictTag'
// 头部标签组件
import VueMeta from 'vue-meta'
// 字典数据组件
import DictData from '@/components/DictData'
//局部使用antDesign-vue中的tree组件
import { Tree } from 'ant-design-vue';
import { Table } from 'ant-design-vue';
import { Icon } from 'ant-design-vue';
// import a from "hskCommApi"
Vue.config.productionTip = false;
// 全局方法挂载
Vue.prototype.getDicts = getDicts
Vue.prototype.getConfigKey = getConfigKey
Vue.prototype.parseTime = parseTime
Vue.prototype.resetForm = resetForm
Vue.prototype.addDateRange = addDateRange
Vue.prototype.selectDictLabel = selectDictLabel
Vue.prototype.selectDictLabels = selectDictLabels
Vue.prototype.download = download
Vue.prototype.handleTree = handleTree
// 全局组件挂载
Vue.component('DictTag', DictTag)
Vue.component('Pagination', Pagination)
Vue.component('RightToolbar', RightToolbar)
Vue.component('Editor', Editor)
Vue.component('FileUpload', FileUpload)
Vue.component('ImageUpload', ImageUpload)
Vue.component('ImagePreview', ImagePreview)
Vue.component('ATree', Tree)
Vue.component('ATable', Table)
Vue.component('AIcon', Icon)
Vue.use(directive)
Vue.use(plugins)
Vue.use(VueMeta)
DictData.install()
Vue.use(Element, {
size: Cookies.get('size') || 'medium' // set element-ui default size
})
// 1. 注册微应用
registerMicroApps([
{
name: 'son',
entry: process.env.VUE_APP_BUSINESS, // 子应用页面访问入口
container: '#subapp-container', // 子应用渲染的出口
activeRule: '/vision-web/business-module-vue2', // 路径匹配规则
sandbox: {
strictStyleIsolation: true, // 开启样式隔离
},
props: { sharedStore: store, baseName: '/vision-web/business-module-vue2' }
},
{
name: 'son2',
entry: process.env.VUE_APP_SYSTEM_URL, // 子应用页面访问入口
container: '#subapp-container', // 子应用渲染的出口
activeRule: '/vision-web/system-module-vue2', // 路径匹配规则
sandbox: {
strictStyleIsolation: true, // 开启样式隔离
},
props: { sharedStore: store, baseName: '/vision-web/system-module-vue2' }
},
{
name: 'business-module-vue3', // 微应用package.json的name字段
entry: '//192.168.80.15:8010/business-module-vue3/', // 微应用访问地址,默认加载这个html页面并解析其中的js动态执行
container: '#subapp-container', // 子应用渲染的出口
// return location.pathname.includes('/vite-vue3-app2')
activeRule: '/vision-web/business-module-vue3',// 激活路径,微应用路由
sandbox: {
strictStyleIsolation: false, // 开启样式隔离
},
props: { sharedStore: store, baseName: '/vision-web/business-module-vue3' }
},
])
// 判断subapp-container是否已加载,如果未加载就延迟
function ensureContainerAndStartMicroApps() {
if (document.getElementById('subapp-container')) {
// 容器存在,可以注册微应用并启动
// registerMicroApps([...]); // 注册微应用的代码
setDefaultMountApp('/'); // 默认打开的子应用
start({
sandbox: {
// strictStyleIsolation: true,
experimentalStyleIsolation: true
}
}); // 启动 qiankun
} else {
// 容器尚不存在,稍后重试
setTimeout(ensureContainerAndStartMicroApps, 100); // 100毫秒后再次尝试
}
}
// 确保 DOMContentLoaded 事件触发后再执行
document.addEventListener('DOMContentLoaded', ensureContainerAndStartMicroApps);
Vue.config.productionTip = false
new Vue({
el: '#app',
router,
store,
render: h => h(App)
})
- 基座改造完成后,进行子应用的改造
在根目录下创建一个子应用,,子应用最好与在基座主应用main.js中配置的名称一致,这样可以直接使用package.json中的name作为output。
vue.config.js,devServer的端口改为与主应用配置的一致,且加上跨域headers和output配置。
配置子应用支持跨域
进行微应用打包成UMD库格式
设置vue.config.js
中的publicPath
,防止出现主应用引入子应用的时候出现样式,图片访问不到情况
新增src/public-path.js
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
src/router/index.js
改为只暴露routes
, new Router
改到 main.js
中声明,并改造main.js
,并引入src
下创建的public-path.js
,改写render
,添加生命周期函数,最终结果如下⬇,当前是子应用的时候根据主应用传递过来的baseName
进行子应用路由的base
及其mode
的改造。
import './public-path';
import Vue from 'vue'
import Cookies from 'js-cookie'
import Element from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css';
import './assets/styles/element-variables.scss'
// import 'ant-design-vue/dist/antd.css';
import "ant-design-vue/dist/antd.less"
import '@/assets/styles/index.scss' // global css
import '@/assets/styles/ruoyi.scss' // ruoyi css
import App from './App'
import store from './store'
import router, {constantRoutes} from './router'
import directive from './directive' // directive
import plugins from './plugins' // plugins
//引入hsk组件
import hskui from "hsk-ui"
import "hsk-ui/styles/hskui.css"
//引入hsk方法
import { hskMsgbox } from 'hsk-ui/commonUtils'
import { download } from '@/utils/request'
import './assets/icons' // icon
import './permission' // permission control
import { getDicts } from "@/api/system/dict/data";
import { getConfigKey } from "@/api/system/config";
import { parseTime, resetForm, addDateRange, selectDictLabel, selectDictLabels, handleTree } from "@/utils/ruoyi";
// 分页组件
import Pagination from "@/components/Pagination";
// Vue.prototype.hskMsgbox = hskui.hskMsgbox.hskMsgbox
// 自定义表格工具组件
import RightToolbar from "@/components/RightToolbar"
// 富文本组件
import Editor from "@/components/Editor"
// 文件上传组件
import FileUpload from "@/components/FileUpload"
// 图片上传组件
import ImageUpload from "@/components/ImageUpload"
// 图片预览组件
import ImagePreview from "@/components/ImagePreview"
// 字典标签组件
import DictTag from '@/components/DictTag'
// 头部标签组件
import VueMeta from 'vue-meta'
// 字典数据组件
import DictData from '@/components/DictData'
// import action from '../src/action'
//局部使用antDesign-vue中的tree组件
import { Tree } from 'ant-design-vue';
import { Table } from 'ant-design-vue';
import { Icon } from 'ant-design-vue';
import Router from "vue-router";
// 全局方法挂载
Vue.prototype.hskMsgbox = hskMsgbox
Vue.prototype.getDicts = getDicts
Vue.prototype.getConfigKey = getConfigKey
Vue.prototype.parseTime = parseTime
Vue.prototype.resetForm = resetForm
Vue.prototype.addDateRange = addDateRange
Vue.prototype.selectDictLabel = selectDictLabel
Vue.prototype.selectDictLabels = selectDictLabels
Vue.prototype.download = download
Vue.prototype.handleTree = handleTree
// 全局组件挂载
Vue.component('DictTag', DictTag)
Vue.component('Pagination', Pagination)
Vue.component('RightToolbar', RightToolbar)
Vue.component('Editor', Editor)
Vue.component('FileUpload', FileUpload)
Vue.component('ImageUpload', ImageUpload)
Vue.component('ImagePreview', ImagePreview)
Vue.component('ATree', Tree)
Vue.component('ATable', Table)
Vue.component('AIcon', Icon)
Vue.use(directive)
Vue.use(plugins)
Vue.use(VueMeta)
DictData.install()
/**
* If you don't want to use mock-server
* you want to use MockJs for mock api
* you can execute: mockXHR()
*
* Currently MockJs will be used in the production environment,
* please remove it before going online! ! !
*/
Vue.use(Element, {
size: Cookies.get('size') || 'medium' // set element-ui default size
})
Vue.use(hskui)
let instance = null
Cookies.set("client_id","admin")
async function render(props={}){
const { container } = props;
instance = new Vue({
router,
store,
render: h => h(App),
beforeCreate(){
if (window.__POWERED_BY_QIANKUN__) {
store.state.user = props.sharedStore.state.user
}
}
}).$mount(
container
?
container.querySelector('#app') //渲染到主应用的入口
:'#app' //独立运行的时候
)
}
// 在被qiankun引用时 修改运行时的 `publicPath`
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
//如果独立运行的时候,判断是否是独立运行
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
/**
* 子应用建议使用qiankun的规则来接入不需要安装任何依赖,
* 只需要再三个入口到二u三个必须的钩子函数给qiankun主应用使用
* 钩子函数必须返回promise(启动的时候调用)
*/
export async function bootstrap() {
// console.log('[vue] vue app bootstraped');
}
// 从生命周期 mount 中获取通信方法,props默认会有onGlobalStateChange和setGlobalState两个api
export async function mount(props) {
// console.log('乾坤子应用容器加载完成,开始渲染 child',props)
if (window.__POWERED_BY_QIANKUN__) {
if(router.options.base !== props.baseName){
const { container } = props;
// 获取容器元素,用于后续操作或设置环境变量等
let rootRoute = new Router({
mode: 'history', // 去掉url中的#
base: props.baseName,
scrollBehavior: () => ({y: 0}),
routes: constantRoutes
})
instance = new Vue({
router:rootRoute,
store,
render: h => h(App),
beforeCreate(){
if (window.__POWERED_BY_QIANKUN__) {
store.state.user = props.sharedStore.state.user
}
}
}).$mount(
container
?
container.querySelector('#app') //渲染到主应用的入口
:'#app' //独立运行的时候
)
} else {
render(props);
}
} else {
render(props);
}
}
/**
* 可选生命周期钩子,仅使用 loadMicroApp 方式加载微应用时生效
*/
export async function update(props) {
}
export async function unmount() {
instance.$destroy();
instance.$el.innerHTML = '';
instance = null;
}
export default instance;
上面完成进行基座的路由配置,使其主应用能够通过路由访问子应用。
{
path: '/system-module-vue2/system',
component: Layout,
hidden: false,
redirect: 'noredirect',
meta: {
title: "系统设置",
noCache: false,
link: null,
icon: "system"
},
children: [
{
path: 'user',
name: 'user',
meta: { title: '用户管理', icon: 'user', "link": null }
}, {
name: "role",
path: "role",
meta: {
"title": "角色管理",
"icon": 'tree',
"noCache": false,
"link": null
}
}, {
name: "codeManagement",
path: "codeManagement",
meta: {
"title": "编码管理",
"icon": 'tree',
"noCache": false,
"link": null
}
},
{
name: "log",
path: "log",
meta: {
title: "操作日志",
icon: 'log',
link: null
}
},
{
name: "dictionaryMiddle",
path: "dictionaryMiddle",
meta: {
title: "数据字典管理",
icon: 'component',
link: null
}
}, {
path: 'configInformation',
hidden: true,
name: 'configInformation',
meta: { title: '配置信息', icon: '', noCache: true }
}, {
name: "templateMiddle",
path: "templateMiddle",
meta: {
title: "消息模板管理",
icon: 'message',
link: null
}
}, {
name: "serverLog",
path: "serverLog",
meta: {
title: "服务调用日志",
icon: 'log',
link: null
}
}, {
name: "serverLogDetail",
path: "serverLogDetail",
hidden: true,
meta: {
title: "服务使用情况",
icon: 'log',
link: null
}
}
]
},
效果:其实配置最难的地方就是路由的配置。
主应用登陆后通过qiankun
自带的props
将token
传递给子应用,子应用在qiankun
的mount
生命周期中设置token
进行响应的判断设置。我当前主子应用用的都是ruoyi
脚手架搭建的,我直接将store
传递个子应用使用,不需要做太多的操作即可。
配置动态路由:我当前是ruoyi
脚手架搭建的后台项目,配置动态路由在permission.js
中设置,和后端约定好后,调用接口,通过后端返回进行路由设置。
动态菜单全部代码,目前是写死的,后期根据getRouters()
方法向后端发送请求进行动态配置,注意:路由基本上前端进行配置,不然很容易出现404报错现象。
下面是permission.js
文件代码,其中当前后端还未有接口,目前先使用adminId
代表不同系统显示不同路由
import auth from '@/plugins/auth'
import router, { constantRoutes, dynamicRoutes } from '@/router'
import { getRouters } from '@/api/menu'
import Layout from '@/layout/index'
import ParentView from '@/components/ParentView'
import InnerLink from '@/layout/components/InnerLink'
import store from '../../store'
const permission = {
state: {
routes: [],
addRoutes: [],
defaultRoutes: [],
topbarRouters: [],
// sidebarRouters: []
sidebarRouters: [],
permissions: [],
},
mutations: {
SET_PERMISSIONS: (state, permissions) => {
state.permissions = permissions
},
SET_ROUTES: (state, routes) => {
state.addRoutes = routes
state.routes = constantRoutes.concat(routes)
},
SET_DEFAULT_ROUTES: (state, routes) => {
state.defaultRoutes = constantRoutes.concat(routes)
},
SET_TOPBAR_ROUTES: (state, routes) => {
state.topbarRouters = routes
},
SET_SIDEBAR_ROUTERS: (state, routes) => {
state.sidebarRouters = routes
},
},
actions: {
// 生成路由
GenerateRoutes({ commit }) {
return new Promise(resolve => {
// // 向后端请求路由数据
// getRouters().then(res => {
const res1 = {
"msg": "操作成功",
"code": 200,
"data": [
{
"path": "/business-module-vue2/equipment",
"redirect": "noRedirect",
"component": "Layout",
"meta": {
"title": "检测设备管理",
"noCache": false,
"icon": 'zhgl',
"link": null
},
"children": [
{
"name": "equipment",
"path": "/business-module-vue2/equipment/equipment",
"hidden": false,
"redirect": "noRedirect",
"meta": {
"title": "检测设备管理",
"icon": 'yygl',
"noCache": false,
"link": null
}
},
]
}
]
}
const res2 = {
"msg": "操作成功",
"code": 200,
"data": [
{
"path": "/business-module-vue2",
"redirect": "noRedirect",
"component": "Layout",
"meta": {
"title": "业务系统",
"noCache": false,
"icon": 'zhgl',
"link": null
},
"children": [
{
"name": "zhanghao",
"path": "/business-module-vue2/zhanghao",
"hidden": false,
"redirect": "noRedirect",
"meta": {
"title": "账号管理",
"icon": 'zhgl',
"noCache": false,
"link": null
}
},
{
"name": "tenant",
"path": "/business-module-vue2/tenant",
"hidden": false,
"redirect": "noRedirect",
"meta": {
"title": "企业管理",
"icon": 'zhgl',
"noCache": false,
"link": null
}
},
{
"name": "shenhe",
"path": "/business-module-vue2/shenhe",
"hidden": false,
"redirect": "noRedirect",
"meta": {
"title": "认证审核",
"icon": 'zhgl',
"noCache": false,
"link": null
}
},
]
},
{
"path": "/business-module-vue2/productListA",
"redirect": "noRedirect",
"component": "Layout",
"meta": {
"title": "资源中心",
"noCache": false,
"icon": 'zhgl',
"link": null
},
"children": [
{
"name": "productList",
"path": "/business-module-vue2/productList",
"hidden": false,
"redirect": "noRedirect",
"meta": {
"title": "产品列表",
"icon": 'zhgl',
"noCache": false,
"link": null
}
},
{
"name": "resourceList",
"path": "/business-module-vue2/resourceList",
"hidden": false,
"redirect": "noRedirect",
"meta": {
"title": "资源列表",
"icon": 'zhgl',
"noCache": false,
"link": null
}
},
{
"name": "viewProductDetail",
"path": "/business-module-vue2/viewProductDetail",
"hidden": true,
"redirect": "noRedirect",
"meta": {
"title": "产品详情",
"icon": 'zhgl',
"noCache": false,
"link": null
}
},
{
"name": "productDetail",
"path": "/business-module-vue2/productDetail",
"hidden": true,
"redirect": "noRedirect",
"meta": {
"title": "产品详情",
"icon": 'zhgl',
"noCache": false,
"link": null
}
},
]
},
{
"path": "/system-module-vue2/system",
"redirect": "noRedirect",
"component": "Layout",
"meta": {
"title": "系统设置",
"noCache": false,
"icon": 'zhgl',
"link": null
},
"children": [
{
"name": "user",
"path": "/system-module-vue2/system/user",
"hidden": false,
"redirect": "noRedirect",
"meta": {
"title": "用户管理",
"icon": 'zhgl',
"noCache": false,
"link": null
}
},
{
"name": "role",
"path": "/system-module-vue2/system/role",
"hidden": false,
"redirect": "noRedirect",
"meta": {
"title": "角色管理",
"icon": 'zhgl',
"noCache": false,
"link": null
}
},
{
"name": "codeManagement",
"path": "/system-module-vue2/system/codeManagement",
"hidden": false,
"redirect": "noRedirect",
"meta": {
"title": "编码管理",
"icon": 'zhgl',
"noCache": false,
"link": null
}
},
{
"name": "log",
"path": "/system-module-vue2/system/log",
"hidden": false,
"redirect": "noRedirect",
"meta": {
"title": "操作日志",
"icon": 'zhgl',
"noCache": false,
"link": null
}
},{
"name": "dictionaryMiddle",
"path": "/system-module-vue2/system/dictionaryMiddle",
"hidden": false,
"redirect": "noRedirect",
"meta": {
"title": "数据字典管理",
"icon": 'zhgl',
"noCache": false,
"link": null
}
},{
"name": "configInformation",
"path": "/system-module-vue2/system/configInformation",
"hidden": false,
"redirect": "noRedirect",
"meta": {
"title": "配置信息",
"icon": 'zhgl',
"noCache": false,
"link": null
}
},{
"name": "templateMiddle",
"path": "/system-module-vue2/system/templateMiddle",
"hidden": false,
"redirect": "noRedirect",
"meta": {
"title": "消息模板管理",
"icon": 'zhgl',
"noCache": false,
"link": null
}
},
{
"name": "serverLog",
"path": "/system-module-vue2/system/serverLog",
"hidden": false,
"redirect": "noRedirect",
"meta": {
"title": "服务调用日志",
"icon": 'zhgl',
"noCache": false,
"link": null
}
},{
"name": "serverLogDetail",
"path": "/system-module-vue2/system/serverLogDetail",
"hidden": false,
"redirect": "noRedirect",
"meta": {
"title": "服务使用情况",
"icon": 'zhgl',
"noCache": false,
"link": null
}
},
]
},{
"path": "/business-module-vue2/gatewayAdministration",
"redirect": "noRedirect",
"component": "Layout",
"meta": {
"title": "物联网中心",
"noCache": false,
"icon": 'zhgl',
"link": null
},
"children": [
{
"name": "gatewayAdministration",
"path": "/business-module-vue2/gatewayAdministration",
"hidden": false,
"redirect": "noRedirect",
"meta": {
"title": "网关管理",
"icon": 'zhgl',
"noCache": false,
"link": null
}
},
]
},
]
}
console.log("store",store.getters.adminID)
let res = {}
if(localStorage.getItem('adminId') === '1'){
res = res2
}else{
res = res1
}
//遍历菜单树,将菜单树下的所有按钮权限拿到,并使用v-permissions方法比对是否有按钮权限
// commit('SET_PERMISSIONS', getAllPermissions(res.data,[]))
const sdata = JSON.parse(JSON.stringify(res.data))
const rdata = JSON.parse(JSON.stringify(res.data))
const sidebarRoutes = filterAsyncRouter(sdata)
const rewriteRoutes = filterAsyncRouter(rdata, false, true)
const asyncRoutes = filterDynamicRoutes(dynamicRoutes);
rewriteRoutes.push({ path: '*', redirect: '/404', hidden: true })
router.addRoutes(asyncRoutes);
commit('SET_ROUTES', rewriteRoutes)
commit('SET_SIDEBAR_ROUTERS', constantRoutes.concat(sidebarRoutes))
commit('SET_DEFAULT_ROUTES', sidebarRoutes)
commit('SET_TOPBAR_ROUTES', sidebarRoutes)
resolve(rewriteRoutes)
// })
})
}
}
}
function getAllPermissions(tree, result) {
//遍历树 获取id数组
for (let i = 0; i < tree.length; i++) {
if (tree[i].meta.permission !== null) {
result.push(...tree[i].meta.permission)
}
if (typeof (tree[i].children) !== "undefined" && tree[i].children !== null && tree[i].children.length > 0) {
getAllPermissions(tree[i].children, result);
}
}
return result;
}
// 遍历后台传来的路由字符串,转换为组件对象
function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) {
return asyncRouterMap.filter(route => {
if (type && route.children) {
route.children = filterChildren(route.children)
}
if (route.component) {
// Layout ParentView 组件特殊处理
if (route.component === 'Layout') {
route.component = Layout
} else if (route.component === 'ParentView') {
route.component = ParentView
} else if (route.component === 'InnerLink') {
route.component = InnerLink
} else {
route.component = loadView(route.component)
}
}
if (route.children != null && route.children && route.children.length) {
route.children = filterAsyncRouter(route.children, route, type)
} else {
delete route['children']
delete route['redirect']
}
return true
})
}
function filterChildren(childrenMap, lastRouter = false) {
var children = []
childrenMap.forEach((el, index) => {
if (el.children && el.children.length) {
if (el.component === 'ParentView' && !lastRouter) {
console.log("~~~~~~~~~",c)
el.children.forEach(c => {
c.path = el.path + '/' + c.path
if (c.children && c.children.length) {
children = children.concat(filterChildren(c.children, c))
return
}
children.push(c)
})
return
}
}
if (lastRouter) {
el.path = lastRouter.path + '/' + el.path
}
children = children.concat(el)
})
return children
}
// 动态路由遍历,验证是否具备权限
export function filterDynamicRoutes(routes) {
const res = []
routes.forEach(route => {
if (route.permissions) {
if (auth.hasPermiOr(route.permissions)) {
res.push(route)
}
} else if (route.roles) {
if (auth.hasRoleOr(route.roles)) {
res.push(route)
}
}
})
return res
}
export const loadView = (view) => {
if (process.env.NODE_ENV === 'development') {
return (resolve) => require([`@/views/${view}`], resolve)
} else {
// 使用 import 实现生产环境的路由懒加载
return () => import(`@/views/${view}`)
}
}
export default permission