前端动态菜单原理-Node版
菜单
二开基座,通过在widgets的index.ts,读取其配置,最后生成一份menu.ts文件,会存放于src/config/menu.ts。

export const menu: Record<string,any>[] = [
{
"name": "追溯报表",
"path": "/information-base/TraceManagement",
"patchName": "TraceManagement",
"icon": "t1",
"notPage": false
},
{
"name": "系统管理",
"path": "/information-base/SystemManagement",
"patchName": "SystemManagement",
"icon": "system",
"notPage": false
}
]
具体细节如下:
import { glob } from 'glob'
import fse from 'fs-extra/esm'
import { readFileSync, writeFileSync } from 'fs'
import { resolve } from 'path'
const regExp = /export default [\s\S]*?;/
const regExpObj = /\{[\s\S]*?;/
import babel from '@babel/core'
import pkg from '../package.json' assert { type: 'json' }
const { ensureDirSync } = fse
const isWin = process.platform === 'win32'
/**
* 根据widgets下的组件,自动生成菜单数据,用于对外引用
*/
async function start() {
const tsFiles = await glob(resolve(process.cwd(), 'src/widgets/*/index.ts'), {
ignore: 'node_modules/**',
windowsPathsNoEscape: true,
})
const menu = []
const menuMap = {}
const errorKey = ' is not defined'
tsFiles.forEach((filePath) => {
const spl = !isWin ? filePath.split('/') : filePath.split('\\')
const patchName = spl[spl.length - 2]
const file = readFileSync(filePath, { encoding: 'utf8' })
const { code } = babel.transformSync(file)
const exportDefaultRegion = code.match(regExp)
const exportDefaultContent = exportDefaultRegion[0]
if (exportDefaultContent) {
const v = exportDefaultContent.match(regExpObj)
const canvasView = exportDefaultContent.match(/canvasView: ([^,]+),/)
let canvasViewValue = canvasView ? canvasView[0] : ''
canvasViewValue = !canvasViewValue.includes(')')
? canvasViewValue.replace(',', '),')
: canvasViewValue
const c = v[0].replace(canvasViewValue, '')
let setViewMatch = c.match(/settingsView:\s*(.*?)(?=\s*[,}])/)
let newCode = ''
if (setViewMatch[0]) {
newCode = c.replace(setViewMatch[0], '').replace(';', '')
}
if (newCode.includes('canvasView')) {
newCode = newCode.replace(
/canvasView\s*:\s*.*?(\{.*?\}|\(.*?\)|[^\s,]+)\s*,?\s*(?=\n|$)/gs,
''
)
}
const codeRun = (code) => {
const fn = new Function(`return ${code}`)
const widgetInfo = fn()
const row = {
name: widgetInfo.name,
path: `/${pkg.name}/` + patchName,
patchName: patchName,
icon: widgetInfo.icon,
notPage: !!widgetInfo.notPage,
}
menu.push(row)
menuMap[patchName] = row
}
try {
codeRun(newCode)
} catch (error) {
if (error.message.includes(errorKey)) {
const iconKey = error.message.split(errorKey)
if (iconKey.length > 1) {
const iconName = iconKey[0]
const code = newCode.replaceAll(iconName, `"${iconName}"`)
codeRun(code)
}
} else {
console.error(error.message)
}
}
}
})
const data = `export const menu: Record<string,any>[] = ${JSON.stringify(
menu,
null,
2
)};\nexport const menuMap: Record<string,any> = ${JSON.stringify(
menuMap,
null,
2
)};`
ensureDirSync(resolve(process.cwd(), './src/config/'))
writeFileSync(resolve(process.cwd(), './src/config/menu.ts'), data, {
encoding: 'utf-8',
})
}
const startTime = performance.now()
start()
console.log('执行时间: ', Math.ceil(performance.now() - startTime), 'ms')