自动生成vue路由和对应的目录和.vue文件
痛点
每次新增路由的时候,都有几个固定的步骤
- 找到一个文件目录位置
- 创建一个新的.vue文件, 并写入.vue文件的基本代码
- 需要改路由的配置文件,找一个层级位置,添加对应的新路由映射
- 还需把新的.vue文件引入 路由的配置文件(路径要对)
- 路由的层级结构 和 .vue文件的目录层级结构 很难保证一一对应的映射。希望达到绝对的一一映射,这样找文件也更快,更方便,看起来也舒服
每次新增路由都要重复性的走一遍流程,还要去确保层级结构的一一对应,非常麻烦。考虑自动化实现,只需一行命令,就能自动化做完上面的所有事情
实现目标
通过一行命令,比如fe route
- 自动化完成新建一个路由 需要的所有操作。比如:路由树内对应增加一行 包含文件引入。文件树内,对应的目录创建对应的.vue文件
- 路由树和文件树的层级结构保证一一对应
技术实现设计
根据一个配置文件(对象—树),实现目标。例如:
// 配置文件 fe.config.js(根目录下) (注意关注层级结构设计)
module.exports = {
route: {
login: '',
index: { // index会特殊处理成 / , 对应 { path: '/' },
children: {
index: '',
template: '',
taskList: '',
xxx: {
children: {
a: '',
b: '',
c: ''
}
}
},
redirect: '/login', // 可选配置
meta: { title: '234' } // 可选配置
},
otherPage: {
redirect: '/login', // 可选配置
meta: { title: '111111111' } // 可选配置
}
}
}
然后输入一个命令 fe route
,此处又需要用到我们的老朋友 前端工具平台了:内部前端工具平台搭建
生成的文件如下, 特点是
- 生成的.vue文件,会在src/view目录下,并且层级结构会和配置文件保持一致
- 会生成 autoRoutes.js 路由树,层级结构也会和配置文件保持一致
最终:文件树、路由树、配置文件 的层级结构,都会保持一一对应的完美情况!
- 如果要新增路由,只需在配置文件fe.config.js内,加一行。在执行
fe route
,就行了
// 目录结构:
├── fe.config.js
└── src
├── router
│ └── autoRoutes.js // 路由文件,详细如下 , 层级结构会和配置文件保持一致
└── view
├── index
│ ├── index.vue
│ ├── taskList.vue
│ ├── template.vue
│ ├── xxx
│ │ ├── a.vue
│ │ ├── b.vue
│ │ └── c.vue
│ └── xxx.vue
├── index.vue
├── login.vue
└── otherPage.vue
// autoRoutes.js 层级结构会和配置文件保持一致
export default [
{ path: '/login', name: 'login', component: () => import(/* webpackChunkName: "login" */'@/view/login.vue') },
{
path: '/', name: 'index', component: () => import(/* webpackChunkName: "index" */'@/view/index.vue'), meta: { title: '234' }, redirect: '/login',
children: [
{ path: 'index', name: 'indexIndex', component: () => import(/* webpackChunkName: "index-index" */'@/view/index/index.vue') },
{ path: 'template', name: 'indexTemplate', component: () => import(/* webpackChunkName: "index-template" */'@/view/index/template.vue') },
{ path: 'taskList', name: 'indexTaskList', component: () => import(/* webpackChunkName: "index-taskList" */'@/view/index/taskList.vue') },
{
path: 'xxx', name: 'indexXxx', component: () => import(/* webpackChunkName: "index-xxx" */'@/view/index/xxx.vue'),
children: [
{ path: 'a', name: 'indexXxxA', component: () => import(/* webpackChunkName: "index-xxx-a" */'@/view/index/xxx/a.vue') },
{ path: 'b', name: 'indexXxxB', component: () => import(/* webpackChunkName: "index-xxx-b" */'@/view/index/xxx/b.vue') },
{ path: 'c', name: 'indexXxxC', component: () => import(/* webpackChunkName: "index-xxx-c" */'@/view/index/xxx/c.vue') }
]
}
]
},
{ path: '/otherPage', name: 'otherPage', component: () => import(/* webpackChunkName: "otherPage" */'@/view/otherPage.vue'), meta: { title: '111111111' }, redirect: '/login' }
]
// 另外也会给 每个 .vue 文件 初始化一套模板, 如下
// 下面的是login.vue,里面的字符串'login',会根据.vue文件的文件名 动态变化的
<template>
<div>login</div>
</template>
<script>
export default {
name: 'login',
data () {
return {
}
},
watch: {},
computed: {},
created () {
},
methods: {
}
}
</script>
<style lang='' scoped>
</style>
贴上实现的源代码
- 难点:因为需要读取树,也要生成树形结构。所以要用到递归
- 递归要主要传入的参数,在递归内是有传递性的。还有递归要注意停止条件
// Config 示例, 实际使用时, 会去fe.config.js找
// const Config = {
// login: '',
//
// index: { // index会特殊处理成 / , 对应 { path: '/' },
// children: {
// class: '',
// template: '',
// taskList: '',
// xxx: {
// children: {
// a: '',
// b: '',
// c: ''
// }
// }
// },
// meta: { title: '234' }
// },
// otherPage: ''
// }
/**
* 根据fe.config.js内的route对象生成对应的 路由和文件结构
* @param{Config: object} fe.config.js内的route对象
* @return {无}
*/
module.exports = (Config) => {
const fse = require('fs-extra')
const Tpl = require('./tpl.js')
const createFile = (path, name) => {
const realPath = './src/view/' + path + '.vue'
fse.pathExists(realPath).then(exists => {
if (!exists) {
fse.outputFile(realPath, Tpl.vueTpl(name))
.then(_ => {
console.log(`${realPath} 生成成功!`)
})
.catch(err => { console.error(err) })
} else {
console.log(`${realPath} 已存在, 不做修改!`)
}
})
}
const importComponent = (name, parent) => {
let arr = [name]
if (parent) {
arr = [...parent.split('/'), ...arr]
}
// 里面一定要加"",不然会报错 /* webpackChunkName: "${arr.join('-')}" */
return `() => import(/* webpackChunkName: "${arr.join('-')}" */'@/view/${parent ? parent + '/' : ''}${name}.vue')`
}
// index/template => indexTemplate
const getCamel = (name) => {
const arr = name.split('/')
if (arr.length === 1) {
return name
} else {
let str = arr.shift()
for (const val of arr) {
str += val.replace(/^\S/, s => s.toUpperCase())
}
return str
}
}
const handleRootPath = (key, parent) => {
let path = key
if (!parent) { // 根路径
if (key === 'index') { // 对根路径的 index 特殊处理成 /
path = '/'
} else {
path = '/' + path
}
}
return path
}
let row = ''
const recursion = (obj, parent) => {
let content = ''
for (const key in obj) {
const val = obj[key]
if (!['[object Object]', '[object String]'].includes(Object.prototype.toString.call(val))) {
console.error('key对应的value的格式有误, 只能是string或object')
return 'key对应的value的格式有误, 只能是string或object'
} else {
if (val && val.parent) { // 如果配置parent属性, 那可以改变层级安排(为了更容易融进老项目)
parent = val.parent
}
const parentVal = parent ? `${parent}/${key}` : key
createFile(parentVal, key)
row = ` { path: '${handleRootPath(key, parent)}', name: '${getCamel(parentVal)}', component: ${importComponent(key, parent)}`
if (typeof val === 'object') { // { meta }
if (val.meta) {
row += `, meta: ${JSON.stringify(val.meta)}`
}
if (val.redirect) {
row += `, redirect: ${JSON.stringify(val.redirect)}`
}
if (val.children) {
// 如果有父, 保存父的值
row += `,
children: [
${recursion(val.children, parentVal)}
]`
}
}
content += row + ' },\n'
}
}
return content
}
const tpl = `/* 此文件是自动生成的, 请用自助用编辑器格式化 */
export default [
${recursion(Config)}]
`
const routeFile = './src/router/autoRoutes.js'
fse.outputFile(routeFile, tpl)
.then(_ => {
console.log(routeFile + '生成成功!')
})
.catch(err => { console.error(err) })
}
码字不易,点赞鼓励