Router
介绍
工程中的Router 会根据src/views
目录结构自动生成路由配置,路由结构与文件系统结构保持一致,不需要手动编写routes = [...]
,使得项目结构更加直观和易于理解。
配置
配置文件:
router
└─ index.ts
ts
import {createRouter, createWebHistory, RouteRecordRaw} from "vue-router";
const ps = import.meta.glob("../views/**/page.ts", {eager: true, import: 'default'})
const cs = import.meta.glob("../views/**/index.vue")
const re: string[] = []
function createChildRoutes(basePath: string): RouteRecordRaw[] {
const pObjs = Object.entries(ps)
const routes: RouteRecordRaw[] = pObjs.filter(([path]) => {
return path.startsWith(basePath) && path.split('/').length === basePath.split('/').length + 1;
}).map(([path, params]) => {
return createRoutes(path, params, (path) => {
path = path.replace('../views', '').replace('/page.ts', '').replace('[', ':').replace(']', '')
const name = path.replace(':', '').split('/').filter(Boolean).join('-') || 'index'
const pathArr = path.split('/')
const routePath = pathArr[pathArr.length - 1]
re.push(name)
return [routePath, name]
})
})
return routes;
}
function createRoutes(path: string, params: Record<string, any> | CustomRouterParams, getRoutePath: ((path: string) => string[])) {
const compKey = path.replace('page.ts', 'index.vue')
const [routePath, name] = getRoutePath(path)
const data = params as Record<string, any> as CustomRouterParams
const chs: RouteRecordRaw[] = [];
if (routePath !== '/') {
chs.push(...createChildRoutes(path.replace('page.ts', '')))
}
if (chs.filter((child) => child.path.startsWith('_')).length > 1) {
throw new Error(`${path} 默认路由配置错误`)
}
const cd = chs.find((child) => child.path.startsWith('_'))
if (cd) {
const defaultRoute: RouteRecordRaw = {...cd, path: ''}
chs.push(defaultRoute)
cd.path = cd.path.replace('_', '')
if (cd.name) {
cd.name = (cd.name as string).replace('_', '')
}
}
const source = {
...data,
...(chs.length > 0 && { children: chs })
};
return {
...source,
path: routePath,
name: name,
component: cs[compKey],
} as RouteRecordRaw;
}
const routers: RouteRecordRaw[] = Object.entries(ps).map(([path, params]) => {
return createRoutes(path, params, (path) => {
const routePath = path.replace('../views', '').replace('/page.ts', '').replace('[', ':').replace(']', '') || '/'
const name = routePath.replace(':', '').split('/').filter(Boolean).join('-') || 'index'
return [routePath, name]
})
})
const finalRouters = routers.filter(r => !re.includes(r.name as string))
export const router = createRouter({history: createWebHistory(), routes: finalRouters})
配置文件负责根据目录结构自动生成路由配置,目前支持的功能如下:
- 生成基础配置项:
path、name、component
- 支持动态路由
- 支持嵌套路由
- 支持嵌套路由默认显示某个子路由
- 支持重定向
- 支持props传递给路由组件
- 支持自定义路由元信息
- 暂不支持命名视图和路由别名
只需关注目录结构,不需要关注此配置文件。
目录结构约定
即views
目录约定规范
基础约定
一个路由以index.vue
和page.ts
文件组成,二者缺一不可,index.vue
为路由组件,page.ts
为该路由的配置项。每个文件夹只存放一个路由。
views
├─ user
| └─ message
| ├─ index.vue
| └─ pages.ts
├─ index.vue
└─ pages.ts
以上文件目录示例生成路由为:
ts
const routers = [
{
path: '/',
component: () => import('@/views/index.vue'),
name: 'index',
// 其他配置项从pages中取出
},
{
path: '/user/message',
component: () => import('@/views/message/index.vue'),
name: 'user-message',
// 其他配置项从pages中取出
},
]
- 路由组件文件的路径即为path属性值,
views
为根目录'/'
,如上示例中目录结构views/user/message/index.vue
,转path为:/user/message
。(如果被判定为嵌套路由则path: message
,详见[嵌套路由](#(如果被判定为嵌套路由则path: message,详见嵌套路由))) path
属性值去掉'/'
并以'-'
相连形成路由名称,如上示例中目录结构views/user/message/index.vue
,转name为:user-message
,根路径默认name:'index'
。
动态路由
动态路由以[words]
形式命名文件夹,如:文件夹名称[id]
。
views
└─ user
└─ [id]
├─ index.vue
└─ pages.ts
以上文件目录示例生成路由为:
ts
const routers = [
{
path: '/user/:id',
component: () => import('@/views/user/[id]/index.vue'),
name: 'user-id',
// 其他配置项从pages中取出
},
]
- 如上示例中目录结构
views/user/[id]/index.vue
,转path为:/user/:id
。(如果被判定为嵌套路由则path: ':id'
,详见[嵌套路由](#(如果被判定为嵌套路由则path: ':id',详见嵌套路由))) - 如上示例中目录结构
views/user/[id]/index.vue
,转name为:user-id
。
嵌套路由
嵌套路由判定规则:当前目录下存在路由组件文件时,下一级的所有路由组件文件都判定为子路由。('/
'除外)
views
└─ user
├─ [id]
| ├─ index.vue
| └─ pages.ts
├─ index.vue
└─ pages.ts
以上文件目录示例生成路由为:
ts
const routers = [
{
path: '/user',
component: () => import('@/views/user/index.vue'),
name: 'user',
// 其他配置项从pages中取出
children: [
{
path: ':id',
component: () => import('@/views/user/[id]/index.vue'),
name: 'user-id',
// 其他配置项从pages中取出
}
]
},
]
- 如上示例中目录结构,
[id]
文件夹下的路由作为子路由填充到父路由的children
属性。 - 如上示例中目录结构,子路由的
path
属性为:id
,即父路由的相对路径。
嵌套路由默认子路由
在子路由文件夹名称以单个下划线'_'
开头为默认子路由。
views
└─ user
├─ _u1
| ├─ index.vue
| └─ pages.ts
├─ u2
| ├─ index.vue
| └─ pages.ts
├─ index.vue
└─ pages.ts
以上文件目录示例生成路由为:
ts
const routers = [
{
path: '/user',
component: () => import('@/views/user/index.vue'),
name: 'user',
childern: [
{
path: '',
component: () => import('@/views/user/_u1/index.vue'),
name: 'user-_u1',
},
{
path: 'u1',
component: () => import('@/views/user/_u1/index.vue'),
name: 'user-u1',
},
{
path: 'u2',
component: () => import('@/views/user/_u2/index.vue'),
name: 'user-u2',
},
]
// 其他配置项从pages中取出
},
]
- 默认子路由的name属性值会保留
'_'
下划线与区别其他路由,防止路由名称重复。 - 最多只允许设置一个默认子路由,如果设置数量大于1,会抛出异常。异常信息:
${path} 路由配置错误,'_'开头的默认子路由只能有一个
设置配置项
vue_template
├─ src
├─ ...
└─ global.d.ts
global.d.ts
文件是TypeScript
项目中的一个全局类型声明文件。它用于声明全局变量、模块、接口或其他类型,使得这些声明在整个项目中都可以被访问,而无需显式导入。global.d.ts
文件中声明了可以对路由设置的配置项,如下:
ts
interface CustomRouterParams {
meta: {
title: string
menuOrder?: number
}
props?: boolean | Record<string, unknown> | ((route: any) => Record<string, unknown>)
redirect?: string | Record<string, unknown> | ((to: any) => string | Record<string, unknown>)
}
meta
:路由元信息,可根据项目需求增加meta
中的属性props
: 路由组件的props传递redirect
: 重定向
目录结构:
views
├─ user
| ├─ index.vue
| └─ pages.ts
├─ index.vue
└─ pages.ts
views/pages.ts
文件:
ts
const pageConfig: CustomRouterParams = {
meta: {
title: 'home',
},
props: {name: 'tom', age: 20},// props: true | props: {name: 'tom', age: 20} | props: route => ({ id: route.params.id })
redirect: '/user' // redirect: '/user' | redirect: {name: user} | redirect: to => {path: '/user', query: {q: to.query.search}} | ......
};
export default pageConfig;
生成的路由如下:
ts
const routers = [
{
path: '/',
component: () => import('@/views/index.vue'),
name: 'index',
meta: {title: 'home'},
props: {name: 'tom', age: 20}, // props: true | props: {name: 'tom', age: 20} | props: route => ({ id: route.params.id })
redirect: '/user' // redirect: '/user' | redirect: {name: user} | redirect: to => {path: '/user', query: {q: to.query.search}} | ......
},
{
path: '/user',
//.......
//.......
},
]