uni-router-enhance 1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 SanshanStreet
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,692 @@
1
+ # uni-router-enhance
2
+
3
+ 一个为 uni-app 设计的**类型安全**路由增强库,提供完整的 TypeScript 类型支持、路由守卫、动态处理函数等高级特性。
4
+
5
+ ## ✨ 特性
6
+
7
+ - 🔒 **完全类型安全** - 基于 `pages.json` 自动生成路由类型,避免路由拼写错误
8
+ - 🛡️ **导航守卫** - 支持 `beforeEach` 和 `afterEach` 全局守卫
9
+ - 🎯 **动态处理函数** - 为特定路由注册数据预加载、权限检查等处理逻辑
10
+ - 📦 **查询参数类型化** - 支持 TypeScript 类型推断的查询参数
11
+ - 🔄 **自动类型生成** - Vite 插件自动从 `pages.json` 生成路由类型
12
+ - 🎨 **灵活的页面关闭策略** - 支持 `navigateTo`、`redirectTo`、`reLaunch` 等多种跳转方式
13
+ - 💾 **路由数据缓存** - 自动缓存查询参数和处理函数返回值
14
+
15
+ ## 📦 安装
16
+
17
+ ```bash
18
+ npm install uni-router-enhance
19
+ # 或
20
+ pnpm add uni-router-enhance
21
+ # 或
22
+ yarn add uni-router-enhance
23
+ ```
24
+
25
+ ## 🚀 快速开始
26
+
27
+ ### 1. 配置 Vite 插件
28
+
29
+ 在 `vite.config.ts` 中配置自动类型生成插件:
30
+
31
+ ```typescript
32
+ import { defineConfig } from 'vite'
33
+ import uni from '@dcloudio/vite-plugin-uni'
34
+ import { routeTypesPlugin } from 'uni-router-enhance'
35
+
36
+ export default defineConfig({
37
+ plugins: [
38
+ uni(),
39
+ // 自动从 pages.json 生成路由类型
40
+ routeTypesPlugin('./types/auto-page.d.ts')
41
+ ]
42
+ })
43
+ ```
44
+
45
+ ### 2. 创建 Router 实例
46
+
47
+ 在 `src/router/index.ts` 中创建 router 实例:
48
+
49
+ ```typescript
50
+ import { createRouter } from 'uni-router-enhance'
51
+ import pagesJson from '../pages.json'
52
+ import type { ENHANCE_ROUTE_PATH } from '../../types/auto-page.d.ts'
53
+
54
+ // 创建路由实例,传入 pages.json 配置
55
+ const router = createRouter<ENHANCE_ROUTE_PATH>(pagesJson)
56
+
57
+ // 配置全局前置守卫
58
+ router.beforeEach((to, from) => {
59
+ console.log('导航前:', to, from)
60
+ // 返回 false 可以取消导航
61
+ // 返回路由名称或路由对象可以重定向
62
+ })
63
+
64
+ // 配置全局后置守卫
65
+ router.afterEach((to, from) => {
66
+ console.log('导航后:', to, from)
67
+ })
68
+
69
+ // 导出钩子函数供页面使用
70
+ const { useRouter, useRoute } = router
71
+
72
+ export {
73
+ useRouter,
74
+ useRoute,
75
+ router
76
+ }
77
+ ```
78
+
79
+ ### 3. 在页面中使用
80
+
81
+ #### 基本路由跳转
82
+
83
+ ```vue
84
+ <script setup lang="ts">
85
+ import { useRouter } from '@/router'
86
+
87
+ const { push } = useRouter()
88
+
89
+ // 简单跳转(类型安全)
90
+ const goToHome = () => {
91
+ push('home')
92
+ }
93
+
94
+ // 带查询参数跳转
95
+ const goToDetail = () => {
96
+ push({
97
+ path: 'detail',
98
+ query: {
99
+ id: '123',
100
+ name: 'Product'
101
+ }
102
+ })
103
+ }
104
+
105
+ // 带回调的跳转
106
+ const goToProfile = () => {
107
+ push('profile', {
108
+ success: (result) => {
109
+ console.log('跳转成功,handler 返回:', result)
110
+ },
111
+ fail: (error) => {
112
+ console.error('跳转失败:', error)
113
+ }
114
+ })
115
+ }
116
+ </script>
117
+ ```
118
+
119
+ #### 页面关闭策略
120
+
121
+ ```typescript
122
+ // 默认: navigateTo - 保留当前页面
123
+ push({
124
+ path: 'detail'
125
+ })
126
+
127
+ // redirectTo - 关闭当前页面
128
+ push({
129
+ path: 'login'
130
+ close: 'current'
131
+ })
132
+
133
+ // reLaunch - 关闭所有页面
134
+ push({
135
+ path: 'index',
136
+ close: 'all'
137
+ })
138
+ ```
139
+
140
+ #### 获取当前路由信息
141
+
142
+ ```vue
143
+ <script setup lang="ts">
144
+ import { useRoute } from '@/router'
145
+
146
+ const route = useRoute()
147
+
148
+ // 访问路由信息
149
+ console.log('当前路由名称:', route.name)
150
+ console.log('路由元信息:', route.meta)
151
+ console.log('查询参数:', route.query)
152
+ console.log('Handler 返回值:', route.handlerResult)
153
+ </script>
154
+
155
+ <template>
156
+ <view>
157
+ <text>当前页面: {{ route.name }}</text>
158
+ <text>参数 ID: {{ route.query.id }}</text>
159
+ </view>
160
+ </template>
161
+ ```
162
+
163
+ ## 🔧 高级功能
164
+
165
+ ### 导航守卫
166
+
167
+ #### 全局前置守卫 (beforeEach)
168
+
169
+ ```typescript
170
+ router.beforeEach((to, from) => {
171
+ console.log(`从 ${from.name} 跳转到 ${to.name}`)
172
+
173
+ // 权限检查示例
174
+ if (to.meta?.requireAuth && !isLoggedIn()) {
175
+ // 重定向到登录页
176
+ return 'login'
177
+ }
178
+
179
+ // 返回 false 取消导航
180
+ if (someCondition) {
181
+ return false
182
+ }
183
+
184
+ // 不返回或返回 true 继续导航
185
+ })
186
+ ```
187
+
188
+ #### 全局后置守卫 (afterEach)
189
+
190
+ ```typescript
191
+ router.afterEach((to, from) => {
192
+ // 页面访问统计
193
+ analytics.track('page_view', {
194
+ from: from.name,
195
+ to: to.name,
196
+ timestamp: Date.now()
197
+ })
198
+
199
+ // 设置页面标题
200
+ if (to.meta?.title) {
201
+ uni.setNavigationBarTitle({ title: to.meta.title })
202
+ }
203
+ })
204
+ ```
205
+
206
+ #### 守卫返回值
207
+
208
+ - `undefined` 或 `true`: 继续导航
209
+ - `false`: 取消导航
210
+ - 路由名称字符串: 重定向到指定路由
211
+ - 路由对象: 重定向到指定路由并携带参数
212
+
213
+ ```typescript
214
+ router.beforeEach((to, from) => {
215
+ // 简单重定向
216
+ if (needRedirect) {
217
+ return 'home'
218
+ }
219
+
220
+ // 带参数重定向
221
+ if (needRedirectWithParams) {
222
+ return {
223
+ path: 'detail',
224
+ query: { id: '123' }
225
+ }
226
+ }
227
+ })
228
+ ```
229
+
230
+ ### 路由处理函数 (Handler)
231
+
232
+ Handler 函数允许你在路由跳转时执行自定义逻辑,例如数据预加载、权限验证、埋点上报等。
233
+
234
+ #### 注册 Handler
235
+
236
+ ```typescript
237
+ import { router } from '@/router'
238
+
239
+ // 数据预加载示例
240
+ router.register('productDetail', async (payload) => {
241
+ const { query } = payload
242
+ const productId = query.id
243
+
244
+ // 预加载商品数据
245
+ const product = await fetchProduct(productId)
246
+
247
+ // 返回的数据可在目标页面通过 route.handlerResult 获取
248
+ return product
249
+ })
250
+
251
+ // 权限检查示例
252
+ router.register('adminPanel', async (payload) => {
253
+ const user = await getCurrentUser()
254
+
255
+ if (!user.isAdmin) {
256
+ throw new Error('无权限访问管理面板')
257
+ }
258
+
259
+ return { allowed: true }
260
+ })
261
+
262
+ // 埋点上报示例
263
+ router.register('orderList', async (payload) => {
264
+ analytics.track('page_view', {
265
+ page: 'orderList',
266
+ timestamp: Date.now(),
267
+ ...payload.query
268
+ })
269
+ })
270
+ ```
271
+
272
+ #### 获取 Handler 返回值
273
+
274
+ ```vue
275
+ <script setup lang="ts">
276
+ import { useRoute } from '@/router'
277
+
278
+ const route = useRoute()
279
+
280
+ // 获取 handler 返回的数据
281
+ const product = route.handlerResult as Product
282
+
283
+ onMounted(() => {
284
+ if (product) {
285
+ console.log('预加载的商品数据:', product)
286
+ }
287
+ })
288
+ </script>
289
+ ```
290
+
291
+ #### 动态管理 Handler
292
+
293
+ ```typescript
294
+ // 检查是否已注册
295
+ if (router.has('productDetail')) {
296
+ console.log('已注册 productDetail handler')
297
+ }
298
+
299
+ // 注销 handler(返回取消函数)
300
+ const unregister = router.register('temp', async () => {
301
+ // 临时逻辑
302
+ })
303
+
304
+ // 稍后移除
305
+ unregister()
306
+
307
+ // 或直接移除
308
+ router.unregister('temp')
309
+ ```
310
+
311
+ ### 路由元信息 (Meta)
312
+
313
+ 路由元信息从 `pages.json` 自动提取,包含以下字段:
314
+
315
+ ```typescript
316
+ interface RouteMeta {
317
+ /** 页面路径 */
318
+ url: string
319
+ /** 页面标题 */
320
+ navigationBarTitleText?: string
321
+ /** 是否为 tabBar 页面 */
322
+ isTabBar?: boolean
323
+ /** 页面唯一标识 */
324
+ name: string
325
+ /** 页面样式配置 */
326
+ style?: Record<string, any>
327
+ }
328
+ ```
329
+
330
+ 在 `pages.json` 中配置的页面样式会自动映射到 meta:
331
+
332
+ ```json
333
+ {
334
+ "pages": [
335
+ {
336
+ "path": "pages/home/index",
337
+ "style": {
338
+ "navigationBarTitleText": "首页",
339
+ "enablePullDownRefresh": true
340
+ }
341
+ }
342
+ ]
343
+ }
344
+ ```
345
+
346
+ 访问元信息:
347
+
348
+ ```typescript
349
+ const route = useRoute()
350
+ console.log(route.meta?.navigationBarTitleText) // "首页"
351
+ console.log(route.meta?.isTabBar) // true/false
352
+ ```
353
+
354
+ ## 📝 API 参考
355
+
356
+ ### createRouter
357
+
358
+ 创建 router 实例。
359
+
360
+ ```typescript
361
+ function createRouter<TName extends string>(
362
+ routes?: PagesConfig
363
+ ): Router<TName>
364
+ ```
365
+
366
+ **参数:**
367
+ - `routes`: pages.json 配置对象(可选)
368
+
369
+ **返回:** Router 实例
370
+
371
+ ### Router 实例方法
372
+
373
+ #### register
374
+
375
+ 注册路由处理函数。
376
+
377
+ ```typescript
378
+ register(name: TName, handler: RouteHandler): () => void
379
+ ```
380
+
381
+ **参数:**
382
+ - `name`: 路由名称
383
+ - `handler`: 处理函数,接收 payload,可返回任意值或 Promise
384
+
385
+ **返回:** 取消注册的函数
386
+
387
+ #### unregister
388
+
389
+ 移除已注册的处理函数。
390
+
391
+ ```typescript
392
+ unregister(name: TName): boolean
393
+ ```
394
+
395
+ **返回:** 是否成功移除
396
+
397
+ #### has
398
+
399
+ 检查是否已注册处理函数。
400
+
401
+ ```typescript
402
+ has(name: TName): boolean
403
+ ```
404
+
405
+ #### beforeEach
406
+
407
+ 添加全局前置守卫。
408
+
409
+ ```typescript
410
+ beforeEach(guard: NavigationGuard<TName>): () => void
411
+ ```
412
+
413
+ **返回:** 移除守卫的函数
414
+
415
+ #### afterEach
416
+
417
+ 添加全局后置守卫。
418
+
419
+ ```typescript
420
+ afterEach(guard: NavigationGuard<TName>): () => void
421
+ ```
422
+
423
+ **返回:** 移除守卫的函数
424
+
425
+ #### useRouter
426
+
427
+ 返回路由操作钩子。
428
+
429
+ ```typescript
430
+ useRouter(): RouterHookResult<TName>
431
+ ```
432
+
433
+ **返回对象:**
434
+ - `push`: 类型安全的路由跳转函数
435
+
436
+ #### useRoute
437
+
438
+ 返回当前路由信息。
439
+
440
+ ```typescript
441
+ useRoute(): RouteInfo<TName>
442
+ ```
443
+
444
+ **返回对象:**
445
+ - `name`: 当前路由名称
446
+ - `meta`: 路由元信息
447
+ - `query`: 查询参数
448
+ - `handlerResult`: Handler 返回值
449
+
450
+ ### useRouter 返回的方法
451
+
452
+ #### push
453
+
454
+ 类型安全的路由跳转。
455
+
456
+ ```typescript
457
+ push(
458
+ data: TName | RouterParams<TName>,
459
+ callbacks?: {
460
+ success?: (result?: unknown) => void
461
+ fail?: (error?: any) => void
462
+ }
463
+ ): Promise<void>
464
+ ```
465
+
466
+ **参数:**
467
+ - `data`: 路由名称字符串或路由参数对象
468
+ - `callbacks`: 可选的成功/失败回调
469
+
470
+ **RouterParams 对象:**
471
+ ```typescript
472
+ interface RouterParams<TPath extends string> {
473
+ path?: TPath // 路由名称
474
+ query?: Record<string, any> // 查询参数
475
+ close?: CloseTypes // 页面关闭策略
476
+ success?: (result?: unknown) => void // 成功回调
477
+ fail?: (error?: any) => void // 失败回调
478
+ }
479
+ ```
480
+
481
+ ### CloseTypes 枚举
482
+
483
+ 页面关闭策略。
484
+
485
+ ```typescript
486
+ enum CloseTypes {
487
+ default = 'default', // navigateTo - 保留当前页面
488
+ current = 'current', // redirectTo - 关闭当前页面
489
+ all = 'all' // reLaunch - 关闭所有页面
490
+ }
491
+ ```
492
+
493
+ ### routeTypesPlugin
494
+
495
+ Vite 插件,自动生成路由类型。
496
+
497
+ ```typescript
498
+ function routeTypesPlugin(dts: string): Plugin
499
+ ```
500
+
501
+ **参数:**
502
+ - `dts`: 类型文件输出路径
503
+
504
+ **示例:**
505
+ ```typescript
506
+ routeTypesPlugin('./types/auto-page.d.ts')
507
+ ```
508
+
509
+ 生成的类型文件示例:
510
+
511
+ ```typescript
512
+ export type ENHANCE_ROUTE_PATH =
513
+ | 'demo'
514
+ | 'home'
515
+ | 'index'
516
+ | 'profile'
517
+ ```
518
+
519
+ ## 🎯 完整示例
520
+
521
+ ### 示例 1:电商应用
522
+
523
+ ```typescript
524
+ // router/index.ts
525
+ import { createRouter } from 'uni-router-enhance'
526
+ import pagesJson from '../pages.json'
527
+ import type { ENHANCE_ROUTE_PATH } from '../../types/auto-page.d.ts'
528
+
529
+ const router = createRouter<ENHANCE_ROUTE_PATH>(pagesJson)
530
+
531
+ // 全局登录检查
532
+ router.beforeEach((to, from) => {
533
+ const needAuth = ['profile', 'order', 'cart'].includes(to.name)
534
+
535
+ if (needAuth && !isLoggedIn()) {
536
+ uni.showToast({ title: '请先登录', icon: 'none' })
537
+ return 'login'
538
+ }
539
+ })
540
+
541
+ // 商品详情页数据预加载
542
+ router.register('productDetail', async (payload) => {
543
+ const { query } = payload
544
+ const product = await fetchProduct(query.id)
545
+ return product
546
+ })
547
+
548
+ // 订单列表页权限检查
549
+ router.register('orderList', async () => {
550
+ const user = await getCurrentUser()
551
+ if (!user.hasOrders) {
552
+ throw new Error('暂无订单权限')
553
+ }
554
+ })
555
+
556
+ export const { useRouter, useRoute } = router
557
+ ```
558
+
559
+ ### 示例 2:商品详情页
560
+
561
+ ```vue
562
+ <script setup lang="ts">
563
+ import { useRoute } from '@/router'
564
+
565
+ // 获取路由信息
566
+ const route = useRoute()
567
+
568
+ // 从 handler 获取预加载的数据
569
+ const product = computed(() => route.handlerResult as Product)
570
+
571
+ onLoad(() => {
572
+ console.log('页面参数:', route.query)
573
+ console.log('预加载数据:', product.value)
574
+ })
575
+ </script>
576
+
577
+ <template>
578
+ <view class="product-detail">
579
+ <image :src="product?.image" />
580
+ <text>{{ product?.name }}</text>
581
+ <text>¥{{ product?.price }}</text>
582
+ </view>
583
+ </template>
584
+ ```
585
+
586
+ ### 示例 3:商品列表页
587
+
588
+ ```vue
589
+ <script setup lang="ts">
590
+ import { useRouter } from '@/router'
591
+
592
+ const { push } = useRouter()
593
+
594
+ const products = ref([])
595
+
596
+ const goToDetail = (productId: string) => {
597
+ push({
598
+ path: 'productDetail',
599
+ query: { id: productId },
600
+ success: () => {
601
+ console.log('跳转成功')
602
+ },
603
+ fail: (error) => {
604
+ uni.showToast({ title: error.message, icon: 'none' })
605
+ }
606
+ })
607
+ }
608
+ </script>
609
+
610
+ <template>
611
+ <view>
612
+ <view
613
+ v-for="item in products"
614
+ :key="item.id"
615
+ @click="goToDetail(item.id)"
616
+ >
617
+ {{ item.name }}
618
+ </view>
619
+ </view>
620
+ </template>
621
+ ```
622
+
623
+ ## 🔍 TypeScript 支持
624
+
625
+ 本库完全使用 TypeScript 编写,提供完整的类型定义。
626
+
627
+ ### 自动类型推断
628
+
629
+ ```typescript
630
+ // ✅ 类型安全 - 路由名称会被自动检查
631
+ push('home')
632
+
633
+ // ❌ 类型错误 - 不存在的路由
634
+ push('nonexistent')
635
+
636
+ // ✅ 查询参数类型安全
637
+ push({
638
+ path: 'detail',
639
+ query: {
640
+ id: '123',
641
+ tab: 'info'
642
+ }
643
+ })
644
+ ```
645
+
646
+ ### 自定义类型
647
+
648
+ ```typescript
649
+ // 定义查询参数类型
650
+ interface ProductDetailQuery {
651
+ id: string
652
+ from?: 'list' | 'search'
653
+ }
654
+
655
+ // 在 handler 中使用
656
+ router.register('productDetail', async (payload) => {
657
+ const query = payload.query as ProductDetailQuery
658
+ console.log(query.id, query.from)
659
+ })
660
+ ```
661
+
662
+ ## ⚠️ 注意事项
663
+
664
+ 1. **TabBar 页面限制**
665
+ - TabBar 页面只能使用 `uni.switchTab` 跳转
666
+ - 跳转到 TabBar 页面时,query 参数会被忽略
667
+
668
+ 2. **路由名称提取规则**
669
+ - 路由名称从页面路径的第二段提取
670
+ - 例如:`pages/home/index` → 路由名称为 `home`
671
+ - 分包路径:`subpackage/detail/index` → 路由名称为 `detail`
672
+
673
+ 3. **Handler 执行时机**
674
+ - Handler 在导航守卫之后、页面跳转之前执行
675
+ - Handler 抛出错误会阻止页面跳转
676
+ - Handler 返回 `false` 会取消导航
677
+
678
+ 4. **缓存清理**
679
+ - 页面缓存在跳转失败时会自动清理
680
+ - 建议在页面 `onLoad` 后及时获取缓存数据
681
+
682
+ ## 🤝 贡献
683
+
684
+ 欢迎提交 Issue 和 Pull Request!
685
+
686
+ ## 📄 License
687
+
688
+ MIT License
689
+
690
+ ---
691
+
692
+ **Made with ❤️ for uni-app developers**