@zphhpzzph/vue-route-gen 1.0.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +709 -36
- package/dist/extract-meta.d.ts +33 -0
- package/dist/extract-meta.d.ts.map +1 -0
- package/dist/extract-meta.js +137 -0
- package/dist/extract-meta.js.map +1 -0
- package/dist/hooks.d.ts +45 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +30 -0
- package/dist/hooks.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +327 -24
- package/dist/index.js.map +1 -1
- package/dist/vite.d.ts +51 -0
- package/dist/vite.d.ts.map +1 -0
- package/dist/vite.js +242 -0
- package/dist/vite.js.map +1 -0
- package/package.json +27 -7
package/README.md
CHANGED
|
@@ -1,22 +1,32 @@
|
|
|
1
|
-
# @
|
|
1
|
+
# @zphhpzzph/vue-route-gen
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> [English](./README_en.md) | 简体中文
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Vue 3 基于文件系统的路由生成器,为 Vue Router 提供完整的类型推断支持。
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
- Layout support (`layout.vue` or `layout/index.vue`)
|
|
9
|
-
- Dynamic route parameters (`$param` or `[param]`)
|
|
10
|
-
- Cache mechanism for fast rebuilds
|
|
11
|
-
- TypeScript support with generated types
|
|
7
|
+
## 特性
|
|
12
8
|
|
|
13
|
-
|
|
9
|
+
- 从文件结构自动发现路由
|
|
10
|
+
- 支持布局文件 (`layout.vue` 或 `layout/index.vue`)
|
|
11
|
+
- 动态路由参数 (`$param` 或 `[param]`)
|
|
12
|
+
- 缓存机制,快速重建
|
|
13
|
+
- TypeScript 支持并生成完整类型
|
|
14
|
+
- **类型安全的路由 Hooks**(`useRoute` 和 `useRouter` 提供完整类型推断)
|
|
15
|
+
- **自动从动态路由提取参数类型**
|
|
16
|
+
- **`<route>` 自定义块支持** - 在 SFC 中定义路由元数据,零运行时开销
|
|
17
|
+
- **精确的字面量类型推断** - 为路由元数据提供编译时类型安全,详见 [字面量类型推断文档](./docs/LiteralTypes.md)
|
|
18
|
+
|
|
19
|
+
## 安装
|
|
14
20
|
|
|
15
21
|
```bash
|
|
16
|
-
npm install @
|
|
22
|
+
npm install @zphhpzzph/vue-route-gen
|
|
23
|
+
# 或
|
|
24
|
+
pnpm install @zphhpzzph/vue-route-gen
|
|
25
|
+
# 或
|
|
26
|
+
yarn add @zphhpzzph/vue-route-gen
|
|
17
27
|
```
|
|
18
28
|
|
|
19
|
-
##
|
|
29
|
+
## 使用方法
|
|
20
30
|
|
|
21
31
|
### CLI
|
|
22
32
|
|
|
@@ -24,57 +34,720 @@ npm install @signlab/vue-route-gen
|
|
|
24
34
|
vue-route-gen
|
|
25
35
|
```
|
|
26
36
|
|
|
27
|
-
###
|
|
37
|
+
### 编程方式
|
|
28
38
|
|
|
29
39
|
```typescript
|
|
30
|
-
import { generateRoutes } from '@
|
|
40
|
+
import { generateRoutes } from '@zphhpzzph/vue-route-gen';
|
|
31
41
|
|
|
32
|
-
//
|
|
42
|
+
// 使用默认选项生成路由
|
|
33
43
|
generateRoutes();
|
|
34
44
|
|
|
35
|
-
//
|
|
45
|
+
// 或指定自定义目录
|
|
36
46
|
generateRoutes({
|
|
37
47
|
pagesDir: './src/pages',
|
|
38
48
|
outFile: './src/router/route.gen.ts'
|
|
39
49
|
});
|
|
40
50
|
```
|
|
41
51
|
|
|
42
|
-
##
|
|
52
|
+
## 文件结构
|
|
43
53
|
|
|
44
54
|
```
|
|
45
55
|
src/pages/
|
|
46
|
-
├── layout.vue #
|
|
47
|
-
├── index.vue #
|
|
48
|
-
├── about.vue #
|
|
56
|
+
├── layout.vue # 根布局
|
|
57
|
+
├── index.vue # 首页 (/)
|
|
58
|
+
├── about.vue # 关于页面 (/about)
|
|
49
59
|
├── users/
|
|
50
|
-
│ ├── layout.vue #
|
|
51
|
-
│ ├── index.vue #
|
|
52
|
-
│ └── [id].vue #
|
|
53
|
-
└── $slug.vue #
|
|
60
|
+
│ ├── layout.vue # 用户布局 (/users)
|
|
61
|
+
│ ├── index.vue # 用户列表 (/users)
|
|
62
|
+
│ └── [id].vue # 用户详情 (/users/:id)
|
|
63
|
+
└── $slug.vue # 通配路由 (/:slug)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## 配置选项
|
|
67
|
+
|
|
68
|
+
### Vite 配置
|
|
69
|
+
|
|
70
|
+
`@zphhpzzph/vue-route-gen` 提供了两个 Vite 插件:
|
|
71
|
+
|
|
72
|
+
#### 1. `routeBlockPlugin()` - 处理路由自定义块(必需)
|
|
73
|
+
|
|
74
|
+
必须添加,用于移除 `<route>` 自定义块和 `defineRoute()` 宏:
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
// vite.config.ts
|
|
78
|
+
import { defineConfig } from 'vite';
|
|
79
|
+
import vue from '@vitejs/plugin-vue';
|
|
80
|
+
import { routeBlockPlugin } from '@zphhpzzph/vue-route-gen/vite';
|
|
81
|
+
|
|
82
|
+
export default defineConfig({
|
|
83
|
+
plugins: [
|
|
84
|
+
routeBlockPlugin(), // 处理 <route> 自定义块和 defineRoute()
|
|
85
|
+
vue(),
|
|
86
|
+
],
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**注意**:`routeBlockPlugin` 会移除 `<route>` 自定义块,因为这些块已经在构建时被 `vue-route-gen` 提取并合并到路由配置中,不需要在运行时处理。
|
|
91
|
+
|
|
92
|
+
#### 2. `routeGenPlugin()` - 自动生成路由(推荐)
|
|
93
|
+
|
|
94
|
+
**开发体验优化**:自动监听文件变化并重新生成路由,无需手动运行命令。
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
// vite.config.ts
|
|
98
|
+
import { defineConfig } from 'vite';
|
|
99
|
+
import vue from '@vitejs/plugin-vue';
|
|
100
|
+
import { routeBlockPlugin, routeGenPlugin } from '@zphhpzzph/vue-route-gen/vite';
|
|
101
|
+
|
|
102
|
+
export default defineConfig({
|
|
103
|
+
plugins: [
|
|
104
|
+
routeBlockPlugin(), // 1. 最先执行
|
|
105
|
+
routeGenPlugin(), // 2. 自动生成路由
|
|
106
|
+
vue(), // 3. Vue 插件
|
|
107
|
+
],
|
|
108
|
+
});
|
|
54
109
|
```
|
|
55
110
|
|
|
56
|
-
|
|
111
|
+
**功能特性**:
|
|
112
|
+
|
|
113
|
+
- ✅ 开发服务器启动时自动生成路由
|
|
114
|
+
- ✅ 监听 `pages` 目录的文件变化
|
|
115
|
+
- ✅ 智能判断是否需要重新生成(避免不必要的重建)
|
|
116
|
+
- ✅ 自动触发 HMR 更新
|
|
117
|
+
- ✅ 支持自定义配置
|
|
118
|
+
|
|
119
|
+
**自定义配置**:
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
routeGenPlugin({
|
|
123
|
+
pagesDir: './src/pages', // 可选,默认 'src/pages'
|
|
124
|
+
outFile: './src/router/route.gen.ts', // 可选,默认 'src/router/route.gen.ts'
|
|
125
|
+
})
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**插件顺序**:`routeBlockPlugin()` → `routeGenPlugin()` → `vue()`
|
|
129
|
+
|
|
130
|
+
详细文档:[Vite 插件使用指南](./docs/VitePlugin.md)
|
|
57
131
|
|
|
58
|
-
###
|
|
132
|
+
### 生成器选项
|
|
59
133
|
|
|
60
|
-
- `pagesDir`:
|
|
61
|
-
- `outFile`:
|
|
134
|
+
- `pagesDir`: 页面目录路径(默认:`src/pages`)
|
|
135
|
+
- `outFile`: 输出文件路径(默认:`src/router/route.gen.ts`)
|
|
62
136
|
|
|
63
|
-
###
|
|
137
|
+
### 自动排除的目录
|
|
64
138
|
|
|
65
|
-
|
|
139
|
+
以下目录会被自动排除:
|
|
66
140
|
- `components`
|
|
67
141
|
- `hooks`
|
|
68
|
-
- `
|
|
142
|
+
- `services`
|
|
143
|
+
- `types`
|
|
144
|
+
- `constants`
|
|
145
|
+
- `utils`
|
|
146
|
+
|
|
147
|
+
## 生成内容
|
|
148
|
+
|
|
149
|
+
生成器会创建:
|
|
150
|
+
|
|
151
|
+
1. `ROUTE_NAME` - 路由名称常量
|
|
152
|
+
2. `ROUTE_PATH` - 路由路径常量
|
|
153
|
+
3. `ROUTE_PATH_BY_NAME` - 按名称查找路径
|
|
154
|
+
4. `RouteParams` - 每个路由的参数类型接口
|
|
155
|
+
5. `RouteParamsByName<T>` - 根据路由名称获取参数类型的工具类型
|
|
156
|
+
6. `RouteMetaMap` - 每个路由的元数据类型接口(从 `<route>` 块提取)
|
|
157
|
+
7. `RouteMetaByName<T>` - 根据路由名称获取元数据类型的工具类型
|
|
158
|
+
8. `routes` - Vue Router 路由记录数组
|
|
159
|
+
9. `useRoute()` - 类型安全的路由访问 Hook,提供参数和元数据类型推断
|
|
160
|
+
10. `useRouter()` - 类型安全的路由导航 Hook,提供参数验证
|
|
161
|
+
|
|
162
|
+
## 使用 `<route>` 自定义块
|
|
163
|
+
|
|
164
|
+
`@zphhpzzph/vue-route-gen` 支持在 Vue SFC 文件中使用 `<route>` 自定义块来定义路由元数据。这些元数据会在**构建时**被提取并合并到生成的路由配置中,**零运行时开销**。
|
|
165
|
+
|
|
166
|
+
### 基础用法
|
|
167
|
+
|
|
168
|
+
在 Vue 组件中添加 `<route>` 自定义块:
|
|
169
|
+
|
|
170
|
+
```vue
|
|
171
|
+
<template>
|
|
172
|
+
<div>
|
|
173
|
+
<h1>用户列表</h1>
|
|
174
|
+
</div>
|
|
175
|
+
</template>
|
|
176
|
+
|
|
177
|
+
<script setup lang="ts">
|
|
178
|
+
// 组件逻辑
|
|
179
|
+
</script>
|
|
180
|
+
|
|
181
|
+
<route>
|
|
182
|
+
{
|
|
183
|
+
"title": "用户列表",
|
|
184
|
+
"layout": "admin",
|
|
185
|
+
"requiresAuth": true,
|
|
186
|
+
"roles": ["admin", "moderator"]
|
|
187
|
+
}
|
|
188
|
+
</route>
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### 支持的元数据属性
|
|
192
|
+
|
|
193
|
+
| 属性 | 类型 | 说明 |
|
|
194
|
+
|------|------|------|
|
|
195
|
+
| `title` | `string` | 页面标题,用于 document.title 和面包屑 |
|
|
196
|
+
| `layout` | `string \| false` | 布局组件名称,或 `false` 禁用布局 |
|
|
197
|
+
| `keepAlive` | `boolean` | 是否缓存页面组件 |
|
|
198
|
+
| `requiresAuth` | `boolean` | 是否需要认证 |
|
|
199
|
+
| `roles` | `string[]` | 允许访问的用户角色 |
|
|
200
|
+
| `redirect` | `string \| { name: string }` | 重定向配置 |
|
|
201
|
+
| `icon` | `string` | 菜单图标(自定义属性) |
|
|
202
|
+
| `hidden` | `boolean` | 是否隐藏菜单(自定义属性) |
|
|
203
|
+
| `*` | `any` | 支持任何自定义属性 |
|
|
204
|
+
|
|
205
|
+
### JSON 和 JavaScript 对象语法
|
|
206
|
+
|
|
207
|
+
`<route>` 块支持两种语法:
|
|
208
|
+
|
|
209
|
+
#### 1. JSON 语法(推荐)
|
|
210
|
+
|
|
211
|
+
```vue
|
|
212
|
+
<route>
|
|
213
|
+
{
|
|
214
|
+
"title": "用户详情",
|
|
215
|
+
"layout": "admin",
|
|
216
|
+
"requiresAuth": true,
|
|
217
|
+
"roles": ["admin"]
|
|
218
|
+
}
|
|
219
|
+
</route>
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
#### 2. JavaScript 对象语法
|
|
223
|
+
|
|
224
|
+
```vue
|
|
225
|
+
<route>
|
|
226
|
+
{
|
|
227
|
+
title: '用户详情',
|
|
228
|
+
layout: 'admin',
|
|
229
|
+
requiresAuth: true,
|
|
230
|
+
roles: ['admin', 'moderator']
|
|
231
|
+
}
|
|
232
|
+
</route>
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### 完整示例
|
|
236
|
+
|
|
237
|
+
#### 用户列表页面
|
|
238
|
+
|
|
239
|
+
```vue
|
|
240
|
+
<!-- src/pages/users/index.vue -->
|
|
241
|
+
<template>
|
|
242
|
+
<div class="users-page">
|
|
243
|
+
<h1>用户列表</h1>
|
|
244
|
+
<UserList />
|
|
245
|
+
</div>
|
|
246
|
+
</template>
|
|
247
|
+
|
|
248
|
+
<script setup lang="ts">
|
|
249
|
+
import UserList from './components/UserList.vue';
|
|
250
|
+
</script>
|
|
251
|
+
|
|
252
|
+
<route>
|
|
253
|
+
{
|
|
254
|
+
"title": "用户列表",
|
|
255
|
+
"layout": "admin",
|
|
256
|
+
"requiresAuth": true,
|
|
257
|
+
"roles": ["admin"],
|
|
258
|
+
"icon": "User",
|
|
259
|
+
"keepAlive": true
|
|
260
|
+
}
|
|
261
|
+
</route>
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
#### 用户详情页面
|
|
265
|
+
|
|
266
|
+
```vue
|
|
267
|
+
<!-- src/pages/users/[id].vue -->
|
|
268
|
+
<template>
|
|
269
|
+
<div class="user-detail">
|
|
270
|
+
<h1>用户详情</h1>
|
|
271
|
+
<UserInfo :user-id="userId" />
|
|
272
|
+
</div>
|
|
273
|
+
</template>
|
|
274
|
+
|
|
275
|
+
<script setup lang="ts">
|
|
276
|
+
import { computed } from 'vue';
|
|
277
|
+
import { useRoute } from '@/router/route.gen';
|
|
278
|
+
import UserInfo from './components/UserInfo.vue';
|
|
279
|
+
|
|
280
|
+
const route = useRoute<'users-[id]>();
|
|
281
|
+
const userId = computed(() => route.params.id);
|
|
282
|
+
</script>
|
|
283
|
+
|
|
284
|
+
<route>
|
|
285
|
+
{
|
|
286
|
+
"title": "用户详情",
|
|
287
|
+
"layout": "admin",
|
|
288
|
+
"requiresAuth": true,
|
|
289
|
+
"roles": ["admin", "moderator"],
|
|
290
|
+
"icon": "UserProfile"
|
|
291
|
+
}
|
|
292
|
+
</route>
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
#### 公开页面(无需认证)
|
|
296
|
+
|
|
297
|
+
```vue
|
|
298
|
+
<!-- src/pages/about.vue -->
|
|
299
|
+
<template>
|
|
300
|
+
<div class="about-page">
|
|
301
|
+
<h1>关于我们</h1>
|
|
302
|
+
<p>这是关于页面</p>
|
|
303
|
+
</div>
|
|
304
|
+
</template>
|
|
305
|
+
|
|
306
|
+
<script setup lang="ts">
|
|
307
|
+
// 组件逻辑
|
|
308
|
+
</script>
|
|
309
|
+
|
|
310
|
+
<route>
|
|
311
|
+
{
|
|
312
|
+
"title": "关于我们",
|
|
313
|
+
"layout": "default",
|
|
314
|
+
"requiresAuth": false,
|
|
315
|
+
"keepAlive": false
|
|
316
|
+
}
|
|
317
|
+
</route>
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### 自定义元数据属性
|
|
321
|
+
|
|
322
|
+
你可以在 `<route>` 块中添加任何自定义属性,并通过 TypeScript 模块扩展来获得类型支持:
|
|
323
|
+
|
|
324
|
+
```typescript
|
|
325
|
+
// types/route-meta.d.ts
|
|
326
|
+
declare module '@zphhpzzph/vue-route-gen/runtime' {
|
|
327
|
+
interface RouteMeta {
|
|
328
|
+
icon?: string;
|
|
329
|
+
hidden?: boolean;
|
|
330
|
+
order?: number;
|
|
331
|
+
badge?: string | number;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
然后在组件中使用:
|
|
337
|
+
|
|
338
|
+
```vue
|
|
339
|
+
<route>
|
|
340
|
+
{
|
|
341
|
+
"title": "仪表盘",
|
|
342
|
+
"icon": "Dashboard",
|
|
343
|
+
"hidden": false,
|
|
344
|
+
"order": 1,
|
|
345
|
+
"badge": "New"
|
|
346
|
+
}
|
|
347
|
+
</route>
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
### 构建时提取
|
|
351
|
+
|
|
352
|
+
路由元数据在构建时被提取,零运行时开销:
|
|
353
|
+
|
|
354
|
+
```typescript
|
|
355
|
+
// 生成的 route.gen.ts
|
|
356
|
+
export const routes = [
|
|
357
|
+
{
|
|
358
|
+
path: "/users/:id",
|
|
359
|
+
name: "users-[id]",
|
|
360
|
+
component: () => import("../pages/users/[id].vue"),
|
|
361
|
+
meta: {
|
|
362
|
+
title: "用户详情",
|
|
363
|
+
layout: "admin",
|
|
364
|
+
requiresAuth: true,
|
|
365
|
+
roles: ["admin", "moderator"],
|
|
366
|
+
icon: "UserProfile"
|
|
367
|
+
},
|
|
368
|
+
children: [],
|
|
369
|
+
}
|
|
370
|
+
] satisfies RouteRecordRaw[];
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
### 在路由守卫中使用元数据
|
|
374
|
+
|
|
375
|
+
```typescript
|
|
376
|
+
// router/guards.ts
|
|
377
|
+
import { router } from './router';
|
|
378
|
+
|
|
379
|
+
router.beforeEach((to, from, next) => {
|
|
380
|
+
const meta = to.meta;
|
|
381
|
+
|
|
382
|
+
// 检查认证
|
|
383
|
+
if (meta.requiresAuth && !isAuthenticated()) {
|
|
384
|
+
return next({ name: 'login' });
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// 检查角色权限
|
|
388
|
+
if (meta.roles && !hasRole(meta.roles)) {
|
|
389
|
+
return next({ name: 'forbidden' });
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// 设置页面标题
|
|
393
|
+
if (meta.title) {
|
|
394
|
+
document.title = `${meta.title} - My App`;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
next();
|
|
398
|
+
});
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
### 与导航菜单结合
|
|
402
|
+
|
|
403
|
+
```vue
|
|
404
|
+
<!-- components/Sidebar.vue -->
|
|
405
|
+
<script setup lang="ts">
|
|
406
|
+
import { routes } from '@/router/route.gen';
|
|
407
|
+
|
|
408
|
+
const menuItems = routes
|
|
409
|
+
.filter(route => route.meta && !route.meta.hidden)
|
|
410
|
+
.map(route => ({
|
|
411
|
+
title: route.meta?.title,
|
|
412
|
+
icon: route.meta?.icon,
|
|
413
|
+
path: route.path,
|
|
414
|
+
order: route.meta?.order ?? 999,
|
|
415
|
+
}))
|
|
416
|
+
.sort((a, b) => a.order - b.order);
|
|
417
|
+
</script>
|
|
418
|
+
|
|
419
|
+
<template>
|
|
420
|
+
<nav>
|
|
421
|
+
<router-link
|
|
422
|
+
v-for="item in menuItems"
|
|
423
|
+
:key="item.path"
|
|
424
|
+
:to="item.path"
|
|
425
|
+
>
|
|
426
|
+
<Icon :name="item.icon" />
|
|
427
|
+
{{ item.title }}
|
|
428
|
+
</router-link>
|
|
429
|
+
</nav>
|
|
430
|
+
</template>
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
### 类型安全扩展
|
|
434
|
+
|
|
435
|
+
通过 TypeScript 模块扩展,让你的自定义元数据属性类型安全:
|
|
436
|
+
|
|
437
|
+
```typescript
|
|
438
|
+
// types/route-meta.d.ts
|
|
439
|
+
import '@zphhpzzph/vue-route-gen/runtime';
|
|
440
|
+
|
|
441
|
+
declare module '@zphhpzzph/vue-route-gen/runtime' {
|
|
442
|
+
interface RouteMeta {
|
|
443
|
+
// 页面权限
|
|
444
|
+
permissions?: string[];
|
|
445
|
+
// 页面描述
|
|
446
|
+
description?: string;
|
|
447
|
+
// SEO 关键词
|
|
448
|
+
keywords?: string[];
|
|
449
|
+
// 是否在标签页中打开
|
|
450
|
+
openInTab?: boolean;
|
|
451
|
+
// 自定义中间件
|
|
452
|
+
middleware?: string[];
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
## 类型安全的路由
|
|
458
|
+
|
|
459
|
+
### 自动生成的参数类型
|
|
460
|
+
|
|
461
|
+
对于 `/users/[id].vue` 这样的路由,生成器会自动提取 `id` 参数:
|
|
462
|
+
|
|
463
|
+
```typescript
|
|
464
|
+
// 在 route.gen.ts 中生成
|
|
465
|
+
export interface RouteParams {
|
|
466
|
+
'users-[id]': {
|
|
467
|
+
id: string;
|
|
468
|
+
};
|
|
469
|
+
// ... 其他路由
|
|
470
|
+
}
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
### 使用 useRoute
|
|
474
|
+
|
|
475
|
+
生成的 `useRoute` Hook 为路由参数和元数据提供完整的类型推断:
|
|
476
|
+
|
|
477
|
+
```typescript
|
|
478
|
+
import { useRoute, ROUTE_NAME } from '@/router/route.gen';
|
|
479
|
+
|
|
480
|
+
const route = useRoute();
|
|
481
|
+
|
|
482
|
+
// 类型安全的参数访问
|
|
483
|
+
if (route.name === ROUTE_NAME.USERS_ID) {
|
|
484
|
+
console.log(route.params.id); // 类型为 string ✅
|
|
485
|
+
|
|
486
|
+
// 类型安全的元数据访问
|
|
487
|
+
console.log(route.meta.title); // 类型为 string ✅
|
|
488
|
+
console.log(route.meta.layout); // 类型为 string ✅
|
|
489
|
+
console.log(route.meta.requiresAuth); // 类型为 boolean ✅
|
|
490
|
+
console.log(route.meta.roles); // 类型为 string[] ✅
|
|
491
|
+
|
|
492
|
+
// ❌ TypeScript 报错:属性不存在
|
|
493
|
+
// console.log(route.meta.wrongProp);
|
|
494
|
+
}
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
**Meta 类型自动推导**:
|
|
498
|
+
- 从 `<route>` 块中提取的元数据会自动生成对应的 TypeScript 类型
|
|
499
|
+
- 不同路由有不同的 meta 类型
|
|
500
|
+
- 访问不存在的 meta 属性时 TypeScript 会报错
|
|
501
|
+
|
|
502
|
+
**获取特定路由的 Meta 类型**:
|
|
503
|
+
|
|
504
|
+
```typescript
|
|
505
|
+
import type { RouteMetaByName } from '@/router/route.gen';
|
|
506
|
+
|
|
507
|
+
// 获取特定路由的 meta 类型
|
|
508
|
+
type UsersIdMeta = RouteMetaByName<typeof ROUTE_NAME.USERS_ID>;
|
|
509
|
+
// 类型为:
|
|
510
|
+
// {
|
|
511
|
+
// title: string;
|
|
512
|
+
// layout: string;
|
|
513
|
+
// requiresAuth: true;
|
|
514
|
+
// roles: string[];
|
|
515
|
+
// } & RouteMeta
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
### 使用 useRouter
|
|
519
|
+
|
|
520
|
+
生成的 `useRouter` Hook 提供类型安全的导航:
|
|
521
|
+
|
|
522
|
+
```typescript
|
|
523
|
+
import { useRouter, ROUTE_NAME } from '@/router/route.gen';
|
|
524
|
+
|
|
525
|
+
const router = useRouter();
|
|
526
|
+
|
|
527
|
+
// 类型安全的导航 - TypeScript 会验证参数
|
|
528
|
+
router.push({
|
|
529
|
+
name: ROUTE_NAME.USERS_ID,
|
|
530
|
+
params: { id: '123' } // 必需的参数会被检查
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
// 错误:类型 '{ id: string; }' 不能赋值给类型 'Record<string, never>'
|
|
534
|
+
router.push({
|
|
535
|
+
name: ROUTE_NAME.HOME, // HOME 路由没有参数
|
|
536
|
+
params: { id: '123' } // TypeScript 报错!
|
|
537
|
+
});
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
## 完整示例
|
|
541
|
+
|
|
542
|
+
### 项目结构
|
|
543
|
+
|
|
544
|
+
```
|
|
545
|
+
src/
|
|
546
|
+
├── pages/
|
|
547
|
+
│ ├── index.vue # 首页
|
|
548
|
+
│ ├── users/
|
|
549
|
+
│ │ ├── [id].vue # 用户详情页
|
|
550
|
+
│ │ └── index.vue # 用户列表页
|
|
551
|
+
│ └── posts/
|
|
552
|
+
│ └── $slug.vue # 文章详情页
|
|
553
|
+
└── router/
|
|
554
|
+
└── route.gen.ts # 自动生成
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
### 生成的类型定义
|
|
558
|
+
|
|
559
|
+
```typescript
|
|
560
|
+
// route.gen.ts (自动生成)
|
|
561
|
+
export const ROUTE_NAME = {
|
|
562
|
+
INDEX: "index",
|
|
563
|
+
USERS_INDEX: "users-index",
|
|
564
|
+
USERS_ID: "users-[id]",
|
|
565
|
+
POSTS_SLUG: "posts-$slug",
|
|
566
|
+
} as const;
|
|
567
|
+
|
|
568
|
+
export interface RouteParams {
|
|
569
|
+
index: Record<string, never>;
|
|
570
|
+
'users-index': Record<string, never>;
|
|
571
|
+
'users-[id]': {
|
|
572
|
+
id: string;
|
|
573
|
+
};
|
|
574
|
+
'posts-$slug': {
|
|
575
|
+
slug: string;
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
export function useRoute<TName extends RouteName = RouteName>(
|
|
580
|
+
name?: TName
|
|
581
|
+
): /* 类型增强的路由对象 */;
|
|
582
|
+
|
|
583
|
+
export function useRouter(): /* 类型增强的路由对象 */;
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
### 在组件中使用
|
|
587
|
+
|
|
588
|
+
```vue
|
|
589
|
+
<script setup lang="ts">
|
|
590
|
+
import { useRoute, useRouter, ROUTE_NAME } from '@/router/route.gen';
|
|
591
|
+
|
|
592
|
+
const route = useRoute();
|
|
593
|
+
const router = useRouter();
|
|
594
|
+
|
|
595
|
+
// 访问路由参数(完全类型安全)
|
|
596
|
+
if (route.name === ROUTE_NAME.USERS_ID) {
|
|
597
|
+
const userId = route.params.id; // 类型为 string
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// 导航到其他路由(参数类型受保护)
|
|
601
|
+
function navigateToUser(userId: string) {
|
|
602
|
+
router.push({
|
|
603
|
+
name: ROUTE_NAME.USERS_ID,
|
|
604
|
+
params: { id: userId } // TypeScript 会检查参数类型
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
</script>
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
## 高级用法
|
|
611
|
+
|
|
612
|
+
### 类型工具(从 `@zphhpzzph/vue-route-gen` 导入)
|
|
613
|
+
|
|
614
|
+
包中提供了一些高级类型工具用于自定义类型安全的路由 hooks:
|
|
615
|
+
|
|
616
|
+
```typescript
|
|
617
|
+
import {
|
|
618
|
+
createTypedUseRoute,
|
|
619
|
+
createTypedUseRouter,
|
|
620
|
+
type TypedRoute,
|
|
621
|
+
type TypedRouter,
|
|
622
|
+
type TypedRouteLocation,
|
|
623
|
+
} from '@zphhpzzph/vue-route-gen';
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
#### 创建自定义类型安全的 useRoute Hook
|
|
627
|
+
|
|
628
|
+
如果需要为特定路由创建类型更精确的 hook:
|
|
629
|
+
|
|
630
|
+
```typescript
|
|
631
|
+
import { createTypedUseRoute } from '@zphhpzzph/vue-route-gen';
|
|
632
|
+
|
|
633
|
+
// 创建针对特定路由的 hook
|
|
634
|
+
const useUserDetailRoute = createTypedUseRoute<'users-[id]', { id: string }>();
|
|
635
|
+
|
|
636
|
+
// 在组件中使用
|
|
637
|
+
const route = useUserDetailRoute();
|
|
638
|
+
console.log(route.params.id); // 类型为 string
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
#### 创建类型安全的 Router
|
|
642
|
+
|
|
643
|
+
```typescript
|
|
644
|
+
import { createTypedUseRouter } from '@zphhpzzph/vue-route-gen';
|
|
645
|
+
|
|
646
|
+
const useRouter = createTypedUseRouter();
|
|
647
|
+
const router = useRouter();
|
|
648
|
+
|
|
649
|
+
// 导航时提供类型检查
|
|
650
|
+
router.push({
|
|
651
|
+
name: 'users-[id]',
|
|
652
|
+
params: { id: '123' },
|
|
653
|
+
});
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
**注意**:大多数情况下,你应该使用生成的 `useRoute()` 和 `useRouter()` hooks(从 `route.gen.ts` 导入),它们已经提供了完整的类型安全。这些底层类型工具主要用于高级自定义场景。
|
|
657
|
+
|
|
658
|
+
> 💡 **深入阅读**:更多高级用法和完整文档,请参阅[文档索引](./docs/README.md)
|
|
659
|
+
|
|
660
|
+
### 获取特定路由的参数类型
|
|
661
|
+
|
|
662
|
+
```typescript
|
|
663
|
+
import type { RouteParamsByName } from '@/router/route.gen';
|
|
664
|
+
|
|
665
|
+
type UserDetailParams = RouteParamsByName<'users-[id]'>;
|
|
666
|
+
// 类型为:{ id: string }
|
|
667
|
+
|
|
668
|
+
function fetchUserData(params: UserDetailParams) {
|
|
669
|
+
// params.id 的类型为 string
|
|
670
|
+
return api.get(`/users/${params.id}`);
|
|
671
|
+
}
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
### 布局嵌套
|
|
675
|
+
|
|
676
|
+
```
|
|
677
|
+
src/pages/
|
|
678
|
+
├── layout.vue # 根布局
|
|
679
|
+
├── index.vue # / (使用根布局)
|
|
680
|
+
├── admin/
|
|
681
|
+
│ ├── layout.vue # /admin 布局
|
|
682
|
+
│ ├── index.vue # /admin (使用 admin 布局)
|
|
683
|
+
│ └── users/
|
|
684
|
+
│ └── [id].vue # /admin/users/:id (使用 admin 布局)
|
|
685
|
+
```
|
|
686
|
+
|
|
687
|
+
## 最佳实践
|
|
688
|
+
|
|
689
|
+
1. **始终使用生成的常量**:使用 `ROUTE_NAME` 而不是硬编码字符串
|
|
690
|
+
2. **利用类型推断**:让 TypeScript 为你检查路由参数
|
|
691
|
+
3. **组合使用 Hooks**:`useRoute` 和 `useRouter` 提供完整的类型安全
|
|
692
|
+
4. **使用 `<route>` 块定义元数据**:在组件中直接定义路由元数据,便于维护
|
|
693
|
+
|
|
694
|
+
## 📚 文档
|
|
695
|
+
|
|
696
|
+
查看完整文档:
|
|
697
|
+
- **[文档索引](./docs/README.md)** - 所有文档���导航目录
|
|
698
|
+
- **[更新日志](./CHANGELOG.md)** - 版本更新记录和迁移指南
|
|
699
|
+
- **[路由元数据字面量类型推断](./docs/LiteralTypes.md)** - 精确的类型推断系统详解
|
|
700
|
+
- **[Vite 插件使用指南](./docs/VitePlugin.md)** - 自动路由生成和智能更新
|
|
701
|
+
|
|
702
|
+
## 发布新版本(维护者)
|
|
703
|
+
|
|
704
|
+
### 快速发布
|
|
705
|
+
|
|
706
|
+
```bash
|
|
707
|
+
# 补丁版本 (1.0.0 -> 1.0.1)
|
|
708
|
+
pnpm run release:patch
|
|
709
|
+
|
|
710
|
+
# 次要版本 (1.0.0 -> 1.1.0)
|
|
711
|
+
pnpm run release:minor
|
|
712
|
+
|
|
713
|
+
# 主要版本 (1.0.0 -> 2.0.0)
|
|
714
|
+
pnpm run release:major
|
|
715
|
+
```
|
|
716
|
+
|
|
717
|
+
### 预发布版本
|
|
718
|
+
|
|
719
|
+
```bash
|
|
720
|
+
# 预发布补丁 (1.0.0 -> 1.0.1-0)
|
|
721
|
+
pnpm run release:pre
|
|
722
|
+
```
|
|
723
|
+
|
|
724
|
+
### 手动发布流程
|
|
725
|
+
|
|
726
|
+
```bash
|
|
727
|
+
# 1. 更新版本号
|
|
728
|
+
npm version patch|minor|major
|
|
729
|
+
|
|
730
|
+
# 2. 构建
|
|
731
|
+
pnpm run build
|
|
732
|
+
|
|
733
|
+
# 3. 发布到 npm
|
|
734
|
+
npm publish --access public --registry https://registry.npmjs.org/
|
|
735
|
+
```
|
|
69
736
|
|
|
70
|
-
|
|
737
|
+
### 发布脚本说明
|
|
71
738
|
|
|
72
|
-
|
|
739
|
+
- `release:patch` - 自动更新补丁版本并发布(bug 修复)
|
|
740
|
+
- `release:minor` - 自动更新次要版本并发布(新功能)
|
|
741
|
+
- `release:major` - 自动更新主要版本并发布(破坏性变更)
|
|
742
|
+
- `release:pre` - 发布预发布版本(alpha/beta/rc)
|
|
743
|
+
- `release` - 仅发布(不更新版本号)
|
|
744
|
+
- `pre-release` - 发布预发布版本(不更新版本号)
|
|
73
745
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
746
|
+
这些脚本会自动:
|
|
747
|
+
1. 更新 package.json 中的版本号
|
|
748
|
+
2. 创建 git tag
|
|
749
|
+
3. 构建 TypeScript 代码
|
|
750
|
+
4. 发布到 npm registry
|
|
78
751
|
|
|
79
752
|
## License
|
|
80
753
|
|