import type { AppRouteModule , AppRouteRecordRaw } from '@/router/types'
import { getParentLayout , LAYOUT , EXCEPTION_COMPONENT } from '@/router/constant'
import type { Router , RouteRecordNormalized } from 'vue-router'
import { createRouter , createWebHashHistory } from 'vue-router'
import { cloneDeep , omit } from 'lodash-es'
import { warn } from '@/utils/log'
import { isUrl } from '@/utils/is'
export type LayoutMapKey = 'LAYOUT'
const IFRAME = ( ) = > import ( '@/views/base/iframe/FrameBlank.vue' )
const URL_HASH_TAB = ` __AGWE4H__HASH__TAG__PWHRG__ `
const LayoutMap = new Map < string , ( ) = > Promise < typeof import ( ' * .vue ' ) > > ( )
LayoutMap . set ( 'LAYOUT' , LAYOUT )
LayoutMap . set ( 'IFRAME' , IFRAME )
let dynamicViewsModules : Record < string , ( ) = > Promise < Recordable > >
// Dynamic introduction
function asyncImportRoute ( routes : AppRouteRecordRaw [ ] | undefined ) {
dynamicViewsModules = dynamicViewsModules || import . meta . glob ( '../../views/**/*.{vue,tsx}' )
if ( ! routes ) return
routes . forEach ( ( item ) = > {
if ( /^\/?http(s)?/ . test ( item . component as string ) ) {
item . component = item . component . substring ( 1 , item . component . length )
}
if ( /^http(s)?/ . test ( item . component as string ) ) {
if ( item . meta ? . internalOrExternal ) {
item . path = item . component
item . path = item . path . replace ( '#' , URL_HASH_TAB )
} else {
item . meta . frameSrc = item . component
}
delete item . component
}
if ( ! item . component && item . meta ? . frameSrc ) {
item . component = 'IFRAME'
}
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 ( )
}
const meta = item . meta || { }
meta . title = item . name
meta . icon = item . icon
item . meta = meta
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 (
// eslint-disable-next-line max-len
'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
}
}
// Turn background objects into routing objects
// 将背景对象变成路由对象
export function transformObjToRoute < T = AppRouteModule > ( routeList : AppRouteModule [ ] ) : T [ ] {
routeList . forEach ( ( route ) = > {
if ( route . children && route . parentId == 0 ) {
route . component = 'LAYOUT'
} else if ( ! route . children ) {
route . component = route . component as string
}
if ( isUrl ( route . path ) ) {
route . component = 'IFRAME'
const path = route . path
route . path = '/' + route . name
const childRoute = [
{
path : path ,
name : route.name ,
component : 'IFRAME' ,
meta : {
title : route.name ,
icon : route.icon
}
}
]
route . children = childRoute
}
const component = route . component as string
if ( component && ! isUrl ( route . path ) ) {
if ( component . toUpperCase ( ) === 'LAYOUT' ) {
route . component = LayoutMap . get ( 'LAYOUT' . toUpperCase ( ) )
const meta = route . meta || { }
meta . title = route . name
meta . icon = route . icon
route . meta = meta
} else if ( component . toUpperCase ( ) === 'IFRAME' ) {
route . component = LayoutMap . get ( 'IFRAME' . toUpperCase ( ) )
const meta = route . meta || { }
meta . title = route . name
meta . icon = route . icon
meta . frameSrc = route . path
route . meta = meta
route . path = '/' + route . name
} else {
//处理顶级非目录路由
const meta = route . meta || { }
meta . title = route . name
meta . icon = route . icon
meta . single = true
route . children = [ cloneDeep ( route ) ]
route . component = LAYOUT
route . name = ` ${ route . name } Parent `
route . path = ''
route . meta = meta
}
} else {
warn ( '请正确配置路由:' + route ? . name + '的component属性' )
}
route . children && asyncImportRoute ( route . children )
} )
return routeList as unknown as T [ ]
}
/ * *
* Convert multi - level routing to level 2 routing
* 将 多 级 路 由 转 换 为 2 级 路 由
* /
export function flatMultiLevelRoutes ( routeModules : AppRouteModule [ ] ) {
const module s : AppRouteModule [ ] = cloneDeep ( routeModules )
for ( let index = 0 ; index < module s.length ; index ++ ) {
const routeModule = module s [ index ]
// 判断级别是否 多级 路由
if ( ! isMultipleRoute ( routeModule ) ) {
// 声明终止当前循环, 即跳过此次循环,进行下一轮
continue
}
// 路由等级提升
promoteRouteLevel ( routeModule )
}
return module s
}
// Routing level upgrade
// 路由等级提升
function promoteRouteLevel ( routeModule : AppRouteModule ) {
// Use vue-router to splice menus
// 使用vue-router拼接菜单
// createRouter 创建一个可以被 Vue 应用程序使用的路由实例
let router : Router | null = createRouter ( {
routes : [ routeModule as unknown as RouteRecordNormalized ] ,
history : createWebHashHistory ( )
} )
// getRoutes: 获取所有 路由记录的完整列表。
const routes = router . getRoutes ( )
// 将所有子路由添加到二级路由
addToChildren ( routes , routeModule . children || [ ] , routeModule )
router = null
// omit lodash的函数 对传入的item对象的children进行删除
routeModule . children = routeModule . children ? . map ( ( item ) = > omit ( item , 'children' ) )
}
// Add all sub-routes to the secondary route
// 将所有子路由添加到二级路由
function addToChildren ( routes : RouteRecordNormalized [ ] , children : AppRouteRecordRaw [ ] , routeModule : AppRouteModule ) {
for ( let index = 0 ; index < children . length ; index ++ ) {
const child = children [ index ]
const route = routes . find ( ( item ) = > item . name === child . name )
if ( ! route ) {
continue
}
routeModule . children = routeModule . children || [ ]
if ( ! routeModule . children . find ( ( item ) = > item . name === route . name ) ) {
routeModule . children ? . push ( route as unknown as AppRouteModule )
}
if ( child . children ? . length ) {
addToChildren ( routes , child . children , routeModule )
}
}
}
// Determine whether the level exceeds 2 levels
// 判断级别是否超过2级
function isMultipleRoute ( routeModule : AppRouteModule ) {
// Reflect.has 与 in 操作符 相同, 用于检查一个对象(包括它原型链上)是否拥有某个属性
if ( ! routeModule || ! Reflect . has ( routeModule , 'children' ) || ! routeModule . children ? . length ) {
return false
}
const children = routeModule . children
let flag = false
for ( let index = 0 ; index < children . length ; index ++ ) {
const child = children [ index ]
if ( child . children ? . length ) {
flag = true
break
}
}
return flag
}