自动生成vue路由树和对应的目录树和.vue文件模板

1,132 阅读4分钟

自动生成vue路由树和对应的目录树和.vue文件模板

痛点

每次新增路由的时候,都有几个固定的步骤

  1. 找到一个文件目录位置
    1. 创建一个新的.vue文件, 并写入.vue文件的基本代码
  2. 需要改路由的配置文件,找一个层级位置,添加对应的新路由映射
    1. 还需把新的.vue文件引入 路由的配置文件(路径要对)
  3. 路由的层级结构 和 .vue文件的目录层级结构 很难保证一一对应的映射。希望达到绝对的一一映射,这样找文件也更快,更方便,看起来也舒服

每次新增路由都要重复性的走一遍流程,还要去确保层级结构的一一对应,非常麻烦。考虑自动化实现,只需一行命令,就能自动化做完上面的所有事情

实现目标

通过一行命令,比如fe route

  1. 自动化完成新建一个路由 需要的所有操作。比如:路由树内对应增加一行 包含文件引入。文件树内,对应的目录创建对应的.vue文件
  2. 路由树和文件树的层级结构保证一一对应

技术实现设计

根据一个配置文件(对象—树),实现目标。例如:

// 配置文件 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,此处又需要用到我们的老朋友 前端工具平台了:内部前端工具平台搭建

生成的文件如下, 特点是

  1. 生成的.vue文件,会在src/view目录下,并且层级结构会和配置文件保持一致
  2. 会生成 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) })
}

码字不易,点赞鼓励