在前后端分离中,当权限是由后端接口动态生成的时候,此时我们需要将后端配置的页面组件与前端的页面做一个关联。 我们看下通常后端返回的数据结构格式: result是可被访问的菜单,其中component属性就是对应的页面组件。我们可以看到component的值为一个字符串路径,这个在vue-router中是不能正确解析出组件的,因为它仅仅是一个字符串。 在vue-router中,我们可以使用component: () => import('/@/views/sys/login/Login.vue')动态导入组件,以后端返回的菜单为例,当"component"的值对应的是"/demo/system/role/index"字符串时,他对应的组件就应该通过() => import('/@/views/demo/system/role/index.vue)导入。 () => import('/@/views/demo/system/role/index.vue) 是一种动态导入组件的方式,它结合了箭头函数和 import() 方法。 具体而言,这段代码表示在 /@/views/demo/system/role/index.vue 组件被需要时才会加载。通过使用动态导入组件的方式,可以将组件分割成更小的代码块,只有当用户访问到该组件时才会加载。所以我们需要将我们开发好的页面通过import动态导入,并形成一个对应的映射表。 由于页面是不断增加的,所以我们不可能一个个去写这个映射关系。构建工具为我们提供了很方便的方法,在webpack中使用require.context,在vite中使用import.meta.glob。 import.meta.glob 是一个用于动态导入模块的方法,它可以根据指定的匹配模式,自动加载满足条件的所有模块,并返回一个对象,该对象包含了所有已加载模块的键值对。 例如,以下代码就是使用 import.meta.glob 方法来加载一个文件夹下的所有 Vue 组件: 在上述代码中,import.meta.glob 方法会通过 ./components/*.vue 匹配模式来加载 ./components 文件夹中所有以 .vue 结尾的文件。然后,通过遍历加载的模块,将其作为 Vue 的全局组件进行注册。 以vite为例,我们使用import.meta.glob来得到一个组件映射对象 我们可以提供一个方法来处理component属性 通过循环传入的routes,我们取出每个路由的component属性值,通过属性值找到对应的映射组件并重新赋值。在上面方法中dynamicImport用于找出对应的映射关系。{
"code": 0,
"result": [
{
"path": "/dashboard",
"name": "Dashboard",
"component": "LAYOUT",
"redirect": "/dashboard/analysis",
"meta": {
"title": "routes.dashboard.dashboard",
"hideChildrenInMenu": true,
"icon": "bx:bx-home"
},
"children": [
{
"path": "analysis",
"name": "Analysis",
"component": "/dashboard/analysis/index",
"meta": {
"hideMenu": true,
"hideBreadcrumb": true,
"title": "routes.dashboard.analysis",
"currentActiveMenu": "/dashboard",
"icon": "bx:bx-home"
}
},
{
"path": "workbench",
"name": "Workbench",
"component": "/dashboard/workbench/index",
"meta": {
"hideMenu": true,
"hideBreadcrumb": true,
"title": "routes.dashboard.workbench",
"currentActiveMenu": "/dashboard",
"icon": "bx:bx-home"
}
}
]
},
{
"path": "/system",
"name": "System",
"component": "LAYOUT",
"redirect": "/system/account",
"meta": {
"icon": "ion:settings-outline",
"title": "routes.demo.system.moduleName"
},
"children": [
{
"path": "account",
"name": "AccountManagement",
"meta": {
"title": "routes.demo.system.account",
"ignoreKeepAlive": true
},
"component": "/demo/system/account/index"
},
{
"path": "account_detail/:id",
"name": "AccountDetail",
"meta": {
"hideMenu": true,
"title": "routes.demo.system.account_detail",
"ignoreKeepAlive": true,
"showMenu": false,
"currentActiveMenu": "/system/account"
},
"component": "/demo/system/account/AccountDetail"
},
{
"path": "role",
"name": "RoleManagement",
"meta": {
"title": "routes.demo.system.role",
"ignoreKeepAlive": true
},
"component": "/demo/system/role/index"
},
{
"path": "dept",
"name": "DeptManagement",
"meta": {
"title": "routes.demo.system.dept",
"ignoreKeepAlive": true
},
"component": "/demo/system/dept/index"
},
{
"path": "changePassword",
"name": "ChangePassword",
"meta": {
"title": "routes.demo.system.password",
"ignoreKeepAlive": true
},
"component": "/demo/system/password/index"
}
]
}
],
"message": "ok",
"type": "success"
}
{
"/demo/system/role/index":() => import('/@/views/demo/system/role/index.vue)
}
const modules = import.meta.glob('./components/*.vue')
Object.keys(modules).forEach((path) => {
const name = path.replace(/^\.\/components\/(.*)\.\w+$/, '$1')
Vue.component(name, () => modules[path])
})
dynamicViewsModules = import.meta.glob('../../views/**/*.{vue,tsx}')
function asyncImportRoute(routes: AppRouteRecordRaw[] | undefined) {
dynamicViewsModules = dynamicViewsModules || import.meta.glob('../../views/**/*.{vue,tsx}')
if (!routes) return
routes.forEach((item) => {
const { component, name } = item
const { children } = item
if (component) {
const layoutFound = LayoutMap.get(component.toUpperCase())
//判断是否是布局组件
if (layoutFound) {
item.component = layoutFound
} else {
item.component = dynamicImport(dynamicViewsModules, component as string)
}
} else if (name) {
item.component = getParentLayout()
}
children && asyncImportRoute(children)
})
}
function dynamicImport(
dynamicViewsModules: Record<string, () => Promise<Recordable>>,
component: string,
) {
const keys = Object.keys(dynamicViewsModules)
const matchKeys = keys.filter((key) => {
const k = key.replace('../../views', '')
const startFlag = component.startsWith('/')
const endFlag = component.endsWith('.vue') || component.endsWith('.tsx')
const startIndex = startFlag ? 0 : 1
const lastIndex = endFlag ? k.length : k.lastIndexOf('.')
return k.substring(startIndex, lastIndex) === component
})
if (matchKeys?.length === 1) {
const matchKey = matchKeys[0]
return dynamicViewsModules[matchKey]
} else if (matchKeys?.length > 1) {
warn(
'Please do not create `.vue` and `.TSX` files with the same file name in the same hierarchical directory under the views folder. This will cause dynamic introduction failure',
)
return
} else {
warn('在src/views/下找不到`' + component + '.vue` 或 `' + component + '.tsx`, 请自行创建!')
return EXCEPTION_COMPONENT
}
}
总结