在开发vue项目时候,需要手动创建路由指向到目录,如果是小项目,路由不多的情况手动配置没有问题,如果遇到中大项目呢,增加一个路由,未免会显得格外枯燥,后期路由的维护成本也会很高,那么可以自动生成路由就很有必要了。

搭建vue项目

vite+vue3+ts创建一个vue3项目

约定

为了避免将一个不必要的文件夹配置为路由,我们约定目录下拥有page.ts文件为路由,且入口文件均为index.vue
page.ts文件会暴露出一个对象,这个对象会添加设置为路由的meta属性
根据上边的约定一个路由最少会有如图文件

1
2
3
4
5
6
// page.ts
import {RouteMeta} from "vue-router";
const meta:RouteMeta = {
name:"test",
}
export default meta;
1
2
3
4
5
6
7
8
9
10
11
<!--index.vue-->
<template>
<div class="test">test</div>
</template>

<script setup lang="ts">

</script>

<style scoped>
</style>

是不是觉得创建这两个文件也很麻烦,没关系后边会说怎么用命令去创建这两个文件

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
// src/router/index.ts
import {createRouter, createWebHashHistory, RouteMeta, RouteRecordRaw} from "vue-router";
// 需要自己添加的添加路由写在这里 一下错误页面,一般除了根目录重定向需要在这块配置,其他重定向页面写在page.ts文件里就可以
const selfRouter:RouteRecordRaw[] = [
{
path: '/:pathMatch(.*)',
component: () => import((`../views/404/index.vue`)),
},
{
path: '/',
redirect:'/home'
}
]

// 获取到views所有的index.vue文件 这块拿到的是一个导出函数
const compModels:Record<string, any> = import.meta.glob('../views/**/index.vue');
// 获取到views下所有的page.ts文件 设置eager参数为true表示拿到导出的值 使用import参数设置导出值的key
const pageModels:Record<string, any> = import.meta.glob('../views/**/page.ts', {
eager: true,
import:'default'
});
// 将路径转为数组
const pageArr:string[][] = Object.keys(pageModels).map(val => val.split('/').slice(2, -1));
let routes:RouteRecordRaw[] = [];
/**
* @param arr 存着路径的数组
* @param fatherPath 父路由的路径
* @param children 子路由数组
* @param length 本次处理长度为length的路径
* @description 创建单个路由的方法
* */
function createNode(arr: string[][], fatherPath: string, children: RouteRecordRaw[],length:number) {
// 遍历路径数组
for (let item of arr) {
// 如果路径长度与本次处理的长度相等就开始处理
if (item.length === length) {
// 定义一个路由 存放着path和children
const node:RouteRecordRaw = {
path: `${fatherPath}/${item[length - 1]}`,// 父路径和自己文件夹名字凭借在一起就成为他的path了
children: [],
}
// 如果path正确那就继续操作 如果不进行判断 会造成路由混乱 想看看啥样子了 去掉这个 判断 再根据报错注释对应代码看看
if (node.path === '/' + item.join('/')) {
const path = '../views' + node.path;
node.meta = pageModels[path + '/page.ts'] as RouteMeta;
if(node.meta.redirect){
node.redirect = node.meta.redirect as string;
}
node.name = node.meta.name as string;
// 根据路径拿到路由的入口文件
node.component = compModels[path + '/index.vue']
// 递归调用 length+1 进行下一个长度路由的处理
createNode(arr, node.path, node.children, length + 1);
children.push(node);
// 将path转为小写
node.path = node.path.toLowerCase();
}
}
}
}
// 调用函数
createNode(pageArr,'',routes,1);
routes.push(...selfRouter)
const router = createRouter({
history: createWebHashHistory(),
routes
})
export default router;

效果

根据如上代码,会根据目录结构生成路由文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
[
{
"path":"/home",
"children":[
{
"path":"/home/user",
"children":[

],
"meta":{
"name":"user"
},
"name":"user",
"component": () => import((`../views/Home/User/index.vue`)),
}
],
"meta":{
"name":"home"
},
"name":"home",
"component": () => import((`../views/Home/index.vue`)),
},
{
"path":"/test",
"children":[

],
"meta":{
"name":"test"
},
"name":"test",
"component": () => import((`../views/Test/index.vue`)),
},
{
"path":"/:pathMatch(.*)",
"component": () => import((`../views/404/index.vue`)),
},
{
"path":"/",
"redirect":"/home"
}
]

命令生成文件、文件夹

在项目根目录创建/createFile/index.js编写如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import fs from 'fs';
import * as readline from "readline";
import {pageTemplate,indexTemplate} from "./template.js";
const read = readline.createInterface({
input: process.stdin,
output: process.stdout
});
const createFile = (dirTitle, path) => {
fs.writeFileSync(`./src/views${path}/${dirTitle}/page.ts`, pageTemplate(dirTitle));
fs.writeFileSync(`./src/views${path}/${dirTitle}/index.vue`, indexTemplate(dirTitle));
console.log('组件创建成功,文件路径为:',path + '/' + dirTitle.slice(0,1).toUpperCase() +dirTitle.slice(1).toLowerCase());
console.log(`\x1B[36m此组件路由为:${(path + '/' + dirTitle).toLowerCase()}\x1B[0m`);
read.close();
}
const createDir = (title, path) => {
title = title.slice(0, 1).toUpperCase() + title.slice(1).toLowerCase()
fs.mkdir(`./src/views${path}/${title}`, () => {
createFile(title, path)
});
}
inputName();
function inputName(){
read.question('请输入组件名称:',(title)=>{
if (!/^[a-zA-Z]+$/.test(title)) {
console.log('\x1B[31m组件名称必须为英文\x1B[0m');
inputName();
}else {
inputPath(title);
}
});
}
function inputPath(title){
read.question('请输入组件所处路径:(例如:/Home,意思为在Home下创建此组件)', (path) => {
if(!/^[a-zA-Z\/]+$/.test(path) && path[0] !== '/'){
console.log('\x1B[31m路径开头必须为“/“且必须全为英文\x1B[0m');
inputPath(title);
} else {
title = title.slice(0,1).toUpperCase() +title.slice(1).toLowerCase();
createDir(title, path)
}
});
}

在项目根目录创建/createFile/template.js编写如下代码
如下代码根据自己需要自由更改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
export const pageTemplate = (name) => {return `import {RouteMeta} from "vue-router";
const meta:RouteMeta = {
name:"${name}",
}
export default meta;`
}

export const indexTemplate = (name) => {
return `<template>
<div class="${name}">${name}</div>
</template>

<script setup lang="ts">

</script>

<style scoped>
</style>`
}

结语

没有编写动态路由等方面的代码、因为我的项目中使用的功能如上代码已经完全可以满足。文中若有错误,欢迎指正!