vitarx-router 4.0.0-beta.24 → 4.0.0-beta.26
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 +26 -14
- package/dist/components/RouterLink.d.ts +10 -5
- package/dist/components/RouterLink.js +37 -17
- package/dist/components/RouterView.d.ts +4 -3
- package/dist/components/RouterView.js +1 -0
- package/dist/core/common/constant.d.ts +7 -20
- package/dist/core/common/constant.js +3 -16
- package/dist/core/common/utils.d.ts +10 -3
- package/dist/core/common/utils.js +16 -7
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.js +1 -0
- package/dist/core/router/manager.js +3 -5
- package/dist/core/router/router.d.ts +3 -2
- package/dist/core/router/router.js +20 -9
- package/dist/core/router/web.js +8 -3
- package/dist/core/shared/link.d.ts +45 -8
- package/dist/core/shared/link.js +86 -44
- package/dist/core/shared/utils.d.ts +1 -1
- package/dist/core/shared/utils.js +2 -2
- package/dist/core/types/navigation.d.ts +1 -1
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -291,6 +291,7 @@ if (hasSuccess(result)) {
|
|
|
291
291
|
| `cancelled` | 4 | 导航被新导航取消 |
|
|
292
292
|
| `duplicated` | 8 | 重复导航 |
|
|
293
293
|
| `notfound` | 16 | 路由未匹配 |
|
|
294
|
+
| `external` | 32 | 外部跳转 |
|
|
294
295
|
|
|
295
296
|
## 路由未匹配处理
|
|
296
297
|
|
|
@@ -536,20 +537,31 @@ declare module 'vitarx-router' {
|
|
|
536
537
|
|
|
537
538
|
### 助手函数
|
|
538
539
|
|
|
539
|
-
| 函数 | 说明
|
|
540
|
-
|
|
541
|
-
| `createRouter(options)` | 创建路由器实例
|
|
542
|
-
| `createWebRouter(options)` | 创建 Web 模式路由器
|
|
543
|
-
| `createMemoryRouter(options)` | 创建 Memory 模式路由器
|
|
544
|
-
| `createRouteManager(routes, options?)` | 创建路由管理器
|
|
545
|
-
| `defineRoutes(...routes)` | 定义路由表
|
|
546
|
-
| `createMissingRoute(component, target, meta?)` | 创建未匹配路由的 RouteLocation
|
|
547
|
-
| `
|
|
548
|
-
| `
|
|
549
|
-
| `
|
|
550
|
-
| `
|
|
551
|
-
| `
|
|
552
|
-
| `
|
|
540
|
+
| 函数 | 说明 |
|
|
541
|
+
|------------------------------------------------|---------------------------|
|
|
542
|
+
| `createRouter(options)` | 创建路由器实例 |
|
|
543
|
+
| `createWebRouter(options)` | 创建 Web 模式路由器 |
|
|
544
|
+
| `createMemoryRouter(options)` | 创建 Memory 模式路由器 |
|
|
545
|
+
| `createRouteManager(routes, options?)` | 创建路由管理器 |
|
|
546
|
+
| `defineRoutes(...routes)` | 定义路由表 |
|
|
547
|
+
| `createMissingRoute(component, target, meta?)` | 创建未匹配路由的 RouteLocation |
|
|
548
|
+
| `cloneRouteLocation(location)` | 克隆 RouteLocation |
|
|
549
|
+
| `useRouter()` | 获取路由器实例 |
|
|
550
|
+
| `useRoute(global?)` | 获取当前路由信息 |
|
|
551
|
+
| `useLink(options)` | 创建链接助手 |
|
|
552
|
+
| `onBeforeRouteLeave(guard)` | 注册离开守卫 |
|
|
553
|
+
| `onBeforeRouteUpdate(callback)` | 注册更新钩子 |
|
|
554
|
+
| `removeTrailingSlash(path)` | 删除路径末尾的斜杠 |
|
|
555
|
+
| `normalizePath(path, removeTrailingSlash?)` | 规范化路径 |
|
|
556
|
+
| `parseQuery(queryString)` | 解析查询字符串 |
|
|
557
|
+
| `stringifyQuery(query)` | 序列化查询对象 |
|
|
558
|
+
| `isNavTarget(value)` | 检查一个值是否为导航目标对象 |
|
|
559
|
+
| `isNavIndex(value)` | 检查一个值是否为有效的导航索引 |
|
|
560
|
+
| `isRouteLocation(value)` | 检查一个值是否为 RouteLocation 对象 |
|
|
561
|
+
| `isRoutePath(index)` | 检查一个值是否为有效的路由路径 |
|
|
562
|
+
| `isExternalLink(href)` | 检查一个值是否为外部链接 |
|
|
563
|
+
| `isPathExactMatch(currentPath, targetPath)` | 判断路径是否完全匹配 |
|
|
564
|
+
| `isPathPrefixMatch(currentPath, targetPath)` | 判断路径是否前缀匹配 |
|
|
553
565
|
|
|
554
566
|
### Router 实例方法
|
|
555
567
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { ElementView, type
|
|
1
|
+
import { type CodeLocation, ElementView, type RenderChildren, type WithProps } from 'vitarx';
|
|
2
2
|
import { type NavigateResult } from '../core/index.js';
|
|
3
3
|
import { type UseLinkOptions } from '../core/shared/index.js';
|
|
4
4
|
export interface RouterLinkProps extends UseLinkOptions, WithProps<'a'> {
|
|
5
5
|
/**
|
|
6
6
|
* 子节点插槽
|
|
7
7
|
*/
|
|
8
|
-
children?:
|
|
8
|
+
children?: RenderChildren;
|
|
9
9
|
/**
|
|
10
10
|
* 是否禁用
|
|
11
11
|
*
|
|
@@ -26,10 +26,12 @@ export interface RouterLinkProps extends UseLinkOptions, WithProps<'a'> {
|
|
|
26
26
|
* 当链接完全匹配时应用的类名
|
|
27
27
|
*/
|
|
28
28
|
exactActiveClass?: string;
|
|
29
|
+
/**
|
|
30
|
+
* 当链接禁用时应用的类名
|
|
31
|
+
*/
|
|
32
|
+
disabledClass?: string;
|
|
29
33
|
/**
|
|
30
34
|
* Value passed to the attribute `aria-current` when the link is exact active.
|
|
31
|
-
*
|
|
32
|
-
* @defaultValue `'page'`
|
|
33
35
|
*/
|
|
34
36
|
ariaCurrentValue?: 'page' | 'step' | 'location' | 'date' | 'time' | 'true' | 'false';
|
|
35
37
|
}
|
|
@@ -40,6 +42,7 @@ export interface RouterLinkProps extends UseLinkOptions, WithProps<'a'> {
|
|
|
40
42
|
* 动态设置激活状态类名、`href` 属性以及 `aria-current` 无障碍属性。
|
|
41
43
|
*
|
|
42
44
|
* @param {RouterLinkProps} props - 组件属性
|
|
45
|
+
* @param {CodeLocation} [location] - 代码位置信息
|
|
43
46
|
* @returns {ElementView<'a'>} 返回一个锚点元素视图
|
|
44
47
|
*
|
|
45
48
|
* @example
|
|
@@ -54,6 +57,8 @@ export interface RouterLinkProps extends UseLinkOptions, WithProps<'a'> {
|
|
|
54
57
|
* <RouterLink to="/about" disabled>关于我们</RouterLink>
|
|
55
58
|
* // 透传属性
|
|
56
59
|
* <RouterLink to="/about" class="nav-link">关于我们</RouterLink>
|
|
60
|
+
* // 带参数的导航
|
|
61
|
+
* <RouterLink to={{ index: '/user', query: { id: 123 } }}>用户信息</RouterLink>
|
|
57
62
|
* ```
|
|
58
63
|
*/
|
|
59
|
-
export declare function RouterLink(props: RouterLinkProps): ElementView<'a'>;
|
|
64
|
+
export declare function RouterLink(props: RouterLinkProps, location?: CodeLocation): ElementView<'a'>;
|
|
@@ -1,5 +1,19 @@
|
|
|
1
|
-
import { createView, isFunction } from 'vitarx';
|
|
1
|
+
import { createView, isFunction, isPlainObject, logger } from 'vitarx';
|
|
2
|
+
import { isExternalLink } from '../core/index.js';
|
|
2
3
|
import { useLink } from '../core/shared/index.js';
|
|
4
|
+
const EXTRA_PROPS = [
|
|
5
|
+
'to',
|
|
6
|
+
'replace',
|
|
7
|
+
'viewTransition',
|
|
8
|
+
'exactMatchMode',
|
|
9
|
+
'disabled',
|
|
10
|
+
'callback',
|
|
11
|
+
'onclick',
|
|
12
|
+
'activeClass',
|
|
13
|
+
'exactActiveClass',
|
|
14
|
+
'disabledClass',
|
|
15
|
+
'ariaCurrentValue'
|
|
16
|
+
];
|
|
3
17
|
/**
|
|
4
18
|
* 路由链接组件,渲染为一个 `<a>` 标签,用于在应用内进行声明式导航。
|
|
5
19
|
*
|
|
@@ -7,6 +21,7 @@ import { useLink } from '../core/shared/index.js';
|
|
|
7
21
|
* 动态设置激活状态类名、`href` 属性以及 `aria-current` 无障碍属性。
|
|
8
22
|
*
|
|
9
23
|
* @param {RouterLinkProps} props - 组件属性
|
|
24
|
+
* @param {CodeLocation} [location] - 代码位置信息
|
|
10
25
|
* @returns {ElementView<'a'>} 返回一个锚点元素视图
|
|
11
26
|
*
|
|
12
27
|
* @example
|
|
@@ -21,10 +36,17 @@ import { useLink } from '../core/shared/index.js';
|
|
|
21
36
|
* <RouterLink to="/about" disabled>关于我们</RouterLink>
|
|
22
37
|
* // 透传属性
|
|
23
38
|
* <RouterLink to="/about" class="nav-link">关于我们</RouterLink>
|
|
39
|
+
* // 带参数的导航
|
|
40
|
+
* <RouterLink to={{ index: '/user', query: { id: 123 } }}>用户信息</RouterLink>
|
|
24
41
|
* ```
|
|
25
42
|
*/
|
|
26
|
-
export function RouterLink(props) {
|
|
43
|
+
export function RouterLink(props, location) {
|
|
27
44
|
const link = useLink(props);
|
|
45
|
+
if (__VITARX_DEV__) {
|
|
46
|
+
if (!link.route.value && !isExternalLink(link.href.value)) {
|
|
47
|
+
logger.warn(`[RouterLink] No match found for to: ${isPlainObject(props.to) ? JSON.stringify(props.to) : String(props.to)}`, location);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
28
50
|
const isDisabled = () => props.disabled ?? false;
|
|
29
51
|
const navigate = async (e) => {
|
|
30
52
|
if (isDisabled())
|
|
@@ -36,28 +58,26 @@ export function RouterLink(props) {
|
|
|
36
58
|
const aProps = {
|
|
37
59
|
onClick: navigate,
|
|
38
60
|
children: props.children,
|
|
39
|
-
'v-bind': [
|
|
40
|
-
props,
|
|
41
|
-
['to', 'children', 'href', 'disabled', 'callback', 'onClick', 'onclick', 'aria-current']
|
|
42
|
-
],
|
|
61
|
+
'v-bind': [props, EXTRA_PROPS],
|
|
43
62
|
get class() {
|
|
44
63
|
return [
|
|
45
|
-
|
|
46
|
-
|
|
64
|
+
props.activeClass && link.isActive.value ? props.activeClass : undefined,
|
|
65
|
+
props.exactActiveClass && link.isExactActive.value ? props.exactActiveClass : undefined,
|
|
66
|
+
props.disabledClass && isDisabled() ? props.disabledClass : undefined
|
|
47
67
|
].filter(Boolean);
|
|
48
68
|
},
|
|
49
69
|
get href() {
|
|
50
70
|
return link.href.value;
|
|
51
|
-
},
|
|
52
|
-
get draggable() {
|
|
53
|
-
return props.draggable ?? false;
|
|
54
|
-
},
|
|
55
|
-
get 'aria-current'() {
|
|
56
|
-
return link.isActive.value && !isDisabled() ? props.ariaCurrentValue || 'page' : undefined;
|
|
57
|
-
},
|
|
58
|
-
get disabled() {
|
|
59
|
-
return isDisabled() ? '' : undefined;
|
|
60
71
|
}
|
|
61
72
|
};
|
|
73
|
+
if ('ariaCurrentValue' in props) {
|
|
74
|
+
Object.defineProperty(aProps, 'aria-current', {
|
|
75
|
+
enumerable: true,
|
|
76
|
+
configurable: true,
|
|
77
|
+
get() {
|
|
78
|
+
return link.isExactActive.value ? props.ariaCurrentValue : undefined;
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}
|
|
62
82
|
return createView('a', aProps);
|
|
63
83
|
}
|
|
@@ -8,17 +8,18 @@ export interface RouterViewOptions {
|
|
|
8
8
|
*/
|
|
9
9
|
name?: string;
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
11
|
+
* 自定义渲染当前路由匹配到的组件
|
|
12
12
|
*
|
|
13
|
-
*
|
|
13
|
+
* 接收三个参数:
|
|
14
14
|
* - `component: Computed<Component | null>`:当前要渲染的组件,
|
|
15
15
|
* - `props: Computed<AnyProps | null>`:要注入给组件的属性对象
|
|
16
|
+
* - `route: Computed<RouteRecord | null>`:当前匹配的路由记录
|
|
16
17
|
*
|
|
17
18
|
* @example
|
|
18
19
|
* ```jsx
|
|
19
20
|
* // 搭配 Freeze 使用
|
|
20
21
|
* <RouterView>
|
|
21
|
-
* {(component, props
|
|
22
|
+
* {(component, props) => <Freeze is={component} props={props}/>}
|
|
22
23
|
* </RouterView>
|
|
23
24
|
* ```
|
|
24
25
|
*
|
|
@@ -21,6 +21,7 @@ export function RouterView(props) {
|
|
|
21
21
|
provide(__ROUTER_VIEW_DEPTH_KEY__, index); // 向子组件提供当前索引
|
|
22
22
|
// 匹配的路由线路
|
|
23
23
|
const viewName = computed(() => props.name || 'default');
|
|
24
|
+
// 匹配的路由记录
|
|
24
25
|
const matchedRoute = computed(() => {
|
|
25
26
|
return router.route.matched[index] ?? null;
|
|
26
27
|
});
|
|
@@ -7,28 +7,15 @@
|
|
|
7
7
|
* 4. cancelled: 导航被取消
|
|
8
8
|
* 8. duplicated: 重复导航
|
|
9
9
|
* 16. notfound: 路由未匹配
|
|
10
|
+
* 32. external: 外部链接(不由路由器处理,useLink navigate 内使用)
|
|
10
11
|
*/
|
|
11
12
|
export declare enum NavState {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
*/
|
|
19
|
-
aborted = 2,// 2
|
|
20
|
-
/**
|
|
21
|
-
* 导航被取消 (二进制: 0100)
|
|
22
|
-
*/
|
|
23
|
-
cancelled = 4,// 4
|
|
24
|
-
/**
|
|
25
|
-
* 重复导航 (二进制: 1000)
|
|
26
|
-
*/
|
|
27
|
-
duplicated = 8,// 8
|
|
28
|
-
/**
|
|
29
|
-
* 路由未匹配 (二进制: 10000)
|
|
30
|
-
*/
|
|
31
|
-
notfound = 16
|
|
13
|
+
success = 1,// 1 - 导航成功
|
|
14
|
+
aborted = 2,// 2 - 导航被阻止
|
|
15
|
+
cancelled = 4,// 4 - 导航被取消
|
|
16
|
+
duplicated = 8,// 8 - 重复导航
|
|
17
|
+
notfound = 16,// 16 - 路由未匹配
|
|
18
|
+
external = 32
|
|
32
19
|
}
|
|
33
20
|
/**
|
|
34
21
|
* 路由器注入键
|
|
@@ -7,29 +7,16 @@
|
|
|
7
7
|
* 4. cancelled: 导航被取消
|
|
8
8
|
* 8. duplicated: 重复导航
|
|
9
9
|
* 16. notfound: 路由未匹配
|
|
10
|
+
* 32. external: 外部链接(不由路由器处理,useLink navigate 内使用)
|
|
10
11
|
*/
|
|
11
12
|
export var NavState;
|
|
12
13
|
(function (NavState) {
|
|
13
|
-
/**
|
|
14
|
-
* 导航成功 (二进制: 0001)
|
|
15
|
-
*/
|
|
16
14
|
NavState[NavState["success"] = 1] = "success";
|
|
17
|
-
/**
|
|
18
|
-
* 导航被阻止 (二进制: 0010)
|
|
19
|
-
*/
|
|
20
15
|
NavState[NavState["aborted"] = 2] = "aborted";
|
|
21
|
-
/**
|
|
22
|
-
* 导航被取消 (二进制: 0100)
|
|
23
|
-
*/
|
|
24
16
|
NavState[NavState["cancelled"] = 4] = "cancelled";
|
|
25
|
-
/**
|
|
26
|
-
* 重复导航 (二进制: 1000)
|
|
27
|
-
*/
|
|
28
17
|
NavState[NavState["duplicated"] = 8] = "duplicated";
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
*/
|
|
32
|
-
NavState[NavState["notfound"] = 16] = "notfound"; // 16
|
|
18
|
+
NavState[NavState["notfound"] = 16] = "notfound";
|
|
19
|
+
NavState[NavState["external"] = 32] = "external"; // 32 - 外部链接(不由路由器处理)
|
|
33
20
|
})(NavState || (NavState = {}));
|
|
34
21
|
/**
|
|
35
22
|
* 路由器注入键
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import { type AnyCallback } from 'vitarx';
|
|
2
2
|
import type { GuardResult, NavTarget, RouteIndex, RouteLocation, RoutePath, URLHash, URLQuery } from '../types/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* 判断是否为外部链接
|
|
5
|
+
*
|
|
6
|
+
* @param href - 链接地址
|
|
7
|
+
* @returns 是否为外部链接
|
|
8
|
+
*/
|
|
9
|
+
export declare function isExternalLink(href: string): boolean;
|
|
3
10
|
/**
|
|
4
11
|
* 检查给定的值是否为一个合法的导航配置对象
|
|
5
12
|
*
|
|
6
13
|
* @param val 要检查的未知类型值
|
|
7
14
|
* @returns {boolean} 如果值是一个导航目标对象则返回true,否则返回false
|
|
8
15
|
*/
|
|
9
|
-
export declare function
|
|
16
|
+
export declare function isNavTarget(val: unknown): val is NavTarget;
|
|
10
17
|
/**
|
|
11
18
|
* 检查给定的值是否为 RouteLocation 对象
|
|
12
19
|
*
|
|
@@ -24,7 +31,7 @@ export declare function isRouteLocation(val: unknown): val is RouteLocation;
|
|
|
24
31
|
* @returns {boolean} 返回一个布尔值,表示值是否为有效的路由索引
|
|
25
32
|
* 同时使用类型谓词(val is RouteIndex)来缩小类型范围
|
|
26
33
|
*/
|
|
27
|
-
export declare function
|
|
34
|
+
export declare function isNavIndex(val: unknown): val is RouteIndex;
|
|
28
35
|
/**
|
|
29
36
|
* 判断两个路由位置对象是否只有 hash 不同
|
|
30
37
|
*
|
|
@@ -38,7 +45,7 @@ export declare function hasOnlyChangeHash(route1: RouteLocation, route2: RouteLo
|
|
|
38
45
|
* @param index - 要判断的索引
|
|
39
46
|
* @returns {boolean} - 如果索引为路径索引则返回true,否则返回false
|
|
40
47
|
*/
|
|
41
|
-
export declare function
|
|
48
|
+
export declare function isRoutePath(index: unknown): index is RoutePath;
|
|
42
49
|
/**
|
|
43
50
|
* 移除路径字符串中的指定后缀
|
|
44
51
|
* @param path - 原始路径字符串
|
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
import { isDeepEqual, isPlainObject, isString } from 'vitarx';
|
|
2
2
|
import { normalizePath, parseQuery } from '../shared/utils.js';
|
|
3
|
+
/**
|
|
4
|
+
* 判断是否为外部链接
|
|
5
|
+
*
|
|
6
|
+
* @param href - 链接地址
|
|
7
|
+
* @returns 是否为外部链接
|
|
8
|
+
*/
|
|
9
|
+
export function isExternalLink(href) {
|
|
10
|
+
return href.startsWith('http://') || href.startsWith('https://') || href.startsWith('//');
|
|
11
|
+
}
|
|
3
12
|
/**
|
|
4
13
|
* 检查给定的值是否为一个合法的导航配置对象
|
|
5
14
|
*
|
|
6
15
|
* @param val 要检查的未知类型值
|
|
7
16
|
* @returns {boolean} 如果值是一个导航目标对象则返回true,否则返回false
|
|
8
17
|
*/
|
|
9
|
-
export function
|
|
10
|
-
return isPlainObject(val) && 'index' in val &&
|
|
18
|
+
export function isNavTarget(val) {
|
|
19
|
+
return isPlainObject(val) && 'index' in val && isNavIndex(val.index);
|
|
11
20
|
}
|
|
12
21
|
/**
|
|
13
22
|
* 检查给定的值是否为 RouteLocation 对象
|
|
@@ -28,7 +37,7 @@ export function isRouteLocation(val) {
|
|
|
28
37
|
* @returns {boolean} 返回一个布尔值,表示值是否为有效的路由索引
|
|
29
38
|
* 同时使用类型谓词(val is RouteIndex)来缩小类型范围
|
|
30
39
|
*/
|
|
31
|
-
export function
|
|
40
|
+
export function isNavIndex(val) {
|
|
32
41
|
// 检查值是否为字符串类型或者是否为symbol类型
|
|
33
42
|
return isString(val) || typeof val === 'symbol';
|
|
34
43
|
}
|
|
@@ -40,7 +49,7 @@ export function hasValidRouteIndex(val) {
|
|
|
40
49
|
*/
|
|
41
50
|
export function hasOnlyChangeHash(route1, route2) {
|
|
42
51
|
return (route1.hash !== route2.hash &&
|
|
43
|
-
route1.
|
|
52
|
+
route1.matched.at(-1) === route2.matched.at(-1) &&
|
|
44
53
|
isDeepEqual(route1.query, route2.query));
|
|
45
54
|
}
|
|
46
55
|
/**
|
|
@@ -49,7 +58,7 @@ export function hasOnlyChangeHash(route1, route2) {
|
|
|
49
58
|
* @param index - 要判断的索引
|
|
50
59
|
* @returns {boolean} - 如果索引为路径索引则返回true,否则返回false
|
|
51
60
|
*/
|
|
52
|
-
export function
|
|
61
|
+
export function isRoutePath(index) {
|
|
53
62
|
return isString(index) && index.startsWith('/');
|
|
54
63
|
}
|
|
55
64
|
/**
|
|
@@ -98,7 +107,7 @@ export function processGuardResult(res) {
|
|
|
98
107
|
if ((res && isString(res)) || typeof res === 'symbol') {
|
|
99
108
|
return { index: res };
|
|
100
109
|
}
|
|
101
|
-
if (
|
|
110
|
+
if (isNavTarget(res))
|
|
102
111
|
return res;
|
|
103
112
|
return true;
|
|
104
113
|
}
|
|
@@ -111,7 +120,7 @@ export function resolveNavTarget(index) {
|
|
|
111
120
|
if (isString(index) || typeof index === 'symbol') {
|
|
112
121
|
return { index };
|
|
113
122
|
}
|
|
114
|
-
if (
|
|
123
|
+
if (isNavTarget(index)) {
|
|
115
124
|
return index;
|
|
116
125
|
}
|
|
117
126
|
if (isPlainObject(index) && index.path && index.matched) {
|
package/dist/core/index.d.ts
CHANGED
package/dist/core/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isArray, isFunction, isString, logger, markRaw } from 'vitarx';
|
|
2
2
|
import { resolveComponent, resolvePattern, resolveProps } from '../common/resolve.js';
|
|
3
|
-
import {
|
|
3
|
+
import { isNavIndex, isNavTarget, isRoutePath } from '../common/utils.js';
|
|
4
4
|
import { isVariablePath, mergePathVariable, mergePattern, optionalVariableCount, validateAliasVariables } from '../common/variable.js';
|
|
5
5
|
import { normalizePath } from '../shared/utils.js';
|
|
6
6
|
/**
|
|
@@ -288,7 +288,7 @@ export class RouteManager {
|
|
|
288
288
|
* @returns 匹配结果对象,包含路由记录和解析后的参数;未匹配返回 null
|
|
289
289
|
*/
|
|
290
290
|
match(index, params) {
|
|
291
|
-
if (
|
|
291
|
+
if (isRoutePath(index)) {
|
|
292
292
|
return this.matchByPath(index);
|
|
293
293
|
}
|
|
294
294
|
return this.matchByName(index, params);
|
|
@@ -474,9 +474,7 @@ export class RouteManager {
|
|
|
474
474
|
record.props = props;
|
|
475
475
|
}
|
|
476
476
|
if (route.redirect &&
|
|
477
|
-
(isFunction(route.redirect) ||
|
|
478
|
-
hasValidRouteIndex(route.redirect) ||
|
|
479
|
-
hasValidNavTarget(route.redirect))) {
|
|
477
|
+
(isFunction(route.redirect) || isNavIndex(route.redirect) || isNavTarget(route.redirect))) {
|
|
480
478
|
record.redirect = route.redirect;
|
|
481
479
|
}
|
|
482
480
|
if (!component && !route.redirect && !isGroup) {
|
|
@@ -132,8 +132,8 @@ export declare abstract class Router {
|
|
|
132
132
|
/**
|
|
133
133
|
* 判断路由器是否已准备就绪
|
|
134
134
|
*
|
|
135
|
-
* 返回一个 Promise
|
|
136
|
-
*
|
|
135
|
+
* 返回一个 Promise,它会在路由器完成首次导航之后被解析,
|
|
136
|
+
* 如果首次导航已经完成,则该 Promise 会被立刻解析。
|
|
137
137
|
*
|
|
138
138
|
* @returns {Promise<void>} - 导航结果
|
|
139
139
|
*/
|
|
@@ -425,6 +425,7 @@ export declare abstract class Router {
|
|
|
425
425
|
* 导航到指定位置
|
|
426
426
|
*
|
|
427
427
|
* 作为导航流程的编排器,按顺序协调各场景处理方法的执行:
|
|
428
|
+
* 0. 处理外部链接
|
|
428
429
|
* 1. 创建导航上下文(路由匹配、并发控制初始化)
|
|
429
430
|
* 2. 处理 404 场景(路由未匹配)
|
|
430
431
|
* 3. 处理重复路由场景
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { getLazyLoader, isArray, isFunction, isPlainObject, isPromise, isString, logger, nextTick, preloadComponent, readonly, shallowReactive } from 'vitarx';
|
|
2
2
|
import { __ROUTER_KEY__, NavState } from '../common/constant.js';
|
|
3
3
|
import { updateRouteLocation } from '../common/update.js';
|
|
4
|
-
import { hasOnlyChangeHash,
|
|
4
|
+
import { hasOnlyChangeHash, isExternalLink, isNavIndex, isNavTarget, isRouteLocation, isRoutePath, processGuardResult, registerHookTool, removePathSuffix, resolveNavTarget } from '../common/utils.js';
|
|
5
5
|
import { cloneRouteLocation, normalizePath, stringifyQuery } from '../shared/utils.js';
|
|
6
6
|
import { checkRouterOptions } from './checkOptions.js';
|
|
7
7
|
import { RouteManager } from './manager.js';
|
|
@@ -186,8 +186,8 @@ export class Router {
|
|
|
186
186
|
/**
|
|
187
187
|
* 判断路由器是否已准备就绪
|
|
188
188
|
*
|
|
189
|
-
* 返回一个 Promise
|
|
190
|
-
*
|
|
189
|
+
* 返回一个 Promise,它会在路由器完成首次导航之后被解析,
|
|
190
|
+
* 如果首次导航已经完成,则该 Promise 会被立刻解析。
|
|
191
191
|
*
|
|
192
192
|
* @returns {Promise<void>} - 导航结果
|
|
193
193
|
*/
|
|
@@ -446,7 +446,7 @@ export class Router {
|
|
|
446
446
|
const to = this.matchRoute(target, redirectFrom);
|
|
447
447
|
// name-based 导航匹配失败视为编程错误,直接抛出异常
|
|
448
448
|
// 因为名称导航是编程式调用,name 不存在或参数校验失败属于代码 bug
|
|
449
|
-
if (!to && !
|
|
449
|
+
if (!to && !isRoutePath(target.index)) {
|
|
450
450
|
const name = target.index;
|
|
451
451
|
const route = this.manager.findByName(name);
|
|
452
452
|
if (route) {
|
|
@@ -590,6 +590,8 @@ export class Router {
|
|
|
590
590
|
if (!context.to)
|
|
591
591
|
return null;
|
|
592
592
|
if (hasOnlyChangeHash(context.to, context.from)) {
|
|
593
|
+
// 更新路由位置的 hash 值并触发 hashUpdate 回调
|
|
594
|
+
this._routeLocation.hash = context.to.hash;
|
|
593
595
|
this._routeLocation.href = context.to.href;
|
|
594
596
|
this.hashUpdate?.(context.to);
|
|
595
597
|
context.result.state = NavState.success;
|
|
@@ -621,12 +623,12 @@ export class Router {
|
|
|
621
623
|
if (!redirect)
|
|
622
624
|
return null;
|
|
623
625
|
// 重定向目标为路由索引(路径或名称)
|
|
624
|
-
if (
|
|
626
|
+
if (isNavIndex(redirect)) {
|
|
625
627
|
context.checkRedirectLoop(String(redirect));
|
|
626
628
|
return this.navigate({ index: redirect }, context.from, context.redirectFrom ?? to);
|
|
627
629
|
}
|
|
628
630
|
// 重定向目标为完整的导航目标对象
|
|
629
|
-
if (
|
|
631
|
+
if (isNavTarget(redirect)) {
|
|
630
632
|
context.checkRedirectLoop(String(redirect.index));
|
|
631
633
|
return this.navigate(redirect, context.from, context.redirectFrom ?? to);
|
|
632
634
|
}
|
|
@@ -696,7 +698,7 @@ export class Router {
|
|
|
696
698
|
return context.result;
|
|
697
699
|
}
|
|
698
700
|
// 守卫重定向
|
|
699
|
-
if (
|
|
701
|
+
if (isNavTarget(guardResult)) {
|
|
700
702
|
context.checkRedirectLoop(String(guardResult.index));
|
|
701
703
|
return this.navigate(guardResult, context.from, context.redirectFrom ?? context.to ?? undefined);
|
|
702
704
|
}
|
|
@@ -724,6 +726,7 @@ export class Router {
|
|
|
724
726
|
* 导航到指定位置
|
|
725
727
|
*
|
|
726
728
|
* 作为导航流程的编排器,按顺序协调各场景处理方法的执行:
|
|
729
|
+
* 0. 处理外部链接
|
|
727
730
|
* 1. 创建导航上下文(路由匹配、并发控制初始化)
|
|
728
731
|
* 2. 处理 404 场景(路由未匹配)
|
|
729
732
|
* 3. 处理重复路由场景
|
|
@@ -738,6 +741,14 @@ export class Router {
|
|
|
738
741
|
* @returns - 返回导航结果
|
|
739
742
|
*/
|
|
740
743
|
async navigate(target, fromRoute, redirectFrom) {
|
|
744
|
+
if (isString(target.index) && isExternalLink(target.index)) {
|
|
745
|
+
return {
|
|
746
|
+
state: NavState.external,
|
|
747
|
+
message: `Navigate to the external link ${target.index}`,
|
|
748
|
+
to: null,
|
|
749
|
+
from: fromRoute ?? cloneRouteLocation(this._routeLocation)
|
|
750
|
+
};
|
|
751
|
+
}
|
|
741
752
|
// 创建导航上下文
|
|
742
753
|
const context = this.createNavigationContext(target, fromRoute, redirectFrom);
|
|
743
754
|
// 路由未匹配 (404)
|
|
@@ -836,7 +847,7 @@ export class Router {
|
|
|
836
847
|
if (isRouteLocation(result))
|
|
837
848
|
return result;
|
|
838
849
|
// 判断 NavTarget(有 index 属性)
|
|
839
|
-
if (
|
|
850
|
+
if (isNavTarget(result))
|
|
840
851
|
return result;
|
|
841
852
|
// 字符串或 symbol 包装为 NavTarget
|
|
842
853
|
if (isString(result) || typeof result === 'symbol') {
|
|
@@ -1017,7 +1028,7 @@ export class Router {
|
|
|
1017
1028
|
*/
|
|
1018
1029
|
matchRoute(target, redirectFrom) {
|
|
1019
1030
|
let matchTarget = target.index;
|
|
1020
|
-
const isPath =
|
|
1031
|
+
const isPath = isRoutePath(matchTarget);
|
|
1021
1032
|
// 如果配置了后缀且目标是路径,则去除后缀
|
|
1022
1033
|
if (this.config.suffix && isPath) {
|
|
1023
1034
|
// 去除路径后缀
|
package/dist/core/router/web.js
CHANGED
|
@@ -140,9 +140,14 @@ export class WebRouter extends Router {
|
|
|
140
140
|
if ('el' in target && target.el) {
|
|
141
141
|
const { el, ...rest } = target;
|
|
142
142
|
if (isString(el)) {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
143
|
+
let element = null;
|
|
144
|
+
try {
|
|
145
|
+
element = document.querySelector(el);
|
|
146
|
+
}
|
|
147
|
+
catch (e) {
|
|
148
|
+
logger.warn(`[Router] Invalid selector "${el}", skipping scroll to element`, e);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
146
151
|
if (element) {
|
|
147
152
|
if (element.scrollIntoView) {
|
|
148
153
|
element.scrollIntoView(rest);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type Computed } from 'vitarx';
|
|
2
|
-
import type { NavigateResult, NavTarget, RouteIndex, RouteLocation
|
|
3
|
-
|
|
4
|
-
type
|
|
2
|
+
import type { NavigateResult, NavTarget, RouteIndex, RouteLocation } from '../types/index.js';
|
|
3
|
+
type LinkToTarget<T extends RouteIndex = RouteIndex> = NavTarget<T> | T | string;
|
|
4
|
+
export type LinkExactMatchMode = 'path' | 'href' | 'hash' | 'query';
|
|
5
5
|
export interface UseLinkOptions<T extends RouteIndex = RouteIndex> {
|
|
6
6
|
/**
|
|
7
7
|
* 要跳转的目标
|
|
@@ -23,6 +23,17 @@ export interface UseLinkOptions<T extends RouteIndex = RouteIndex> {
|
|
|
23
23
|
* @default false
|
|
24
24
|
*/
|
|
25
25
|
viewTransition?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* 精确匹配模式
|
|
28
|
+
*
|
|
29
|
+
* - 'path':精确匹配路径
|
|
30
|
+
* - 'href':精确匹配完整链接
|
|
31
|
+
* - 'hash':精确匹配路径和锚点
|
|
32
|
+
* - 'query':精确匹配路径和查询参数
|
|
33
|
+
*
|
|
34
|
+
* @default 'path'
|
|
35
|
+
*/
|
|
36
|
+
exactMatchMode?: LinkExactMatchMode;
|
|
26
37
|
}
|
|
27
38
|
export interface UseLinkReturn {
|
|
28
39
|
/**
|
|
@@ -46,15 +57,40 @@ export interface UseLinkReturn {
|
|
|
46
57
|
*
|
|
47
58
|
* @param [e] 点击事件
|
|
48
59
|
*/
|
|
49
|
-
navigate: (e?: MouseEvent) => Promise<NavigateResult
|
|
60
|
+
navigate: (e?: MouseEvent) => Promise<NavigateResult>;
|
|
50
61
|
}
|
|
51
62
|
/**
|
|
52
|
-
*
|
|
63
|
+
* 判断当前路径是否以目标路径为前缀(路径段级别匹配)
|
|
64
|
+
*
|
|
65
|
+
* 去除尾部斜杠后,判断 currentPath 是否等于 targetPath 或以 targetPath/ 开头,
|
|
66
|
+
* 避免如 `/users-admin` 错误匹配 `/users` 的问题。
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* isPathPrefixMatch('/users/123', '/users') // true
|
|
70
|
+
* isPathPrefixMatch('/users', '/users') // true
|
|
71
|
+
* isPathPrefixMatch('/users-admin', '/users') // false
|
|
72
|
+
* isPathPrefixMatch('/', '/') // true
|
|
73
|
+
*
|
|
74
|
+
* @param currentPath - 当前路由路径
|
|
75
|
+
* @param targetPath - 目标路由路径
|
|
76
|
+
* @returns 是否为前缀匹配
|
|
77
|
+
*/
|
|
78
|
+
export declare function isPathPrefixMatch(currentPath: string, targetPath: string): boolean;
|
|
79
|
+
/**
|
|
80
|
+
* 判断当前路径是否与目标路径完全匹配
|
|
81
|
+
*
|
|
82
|
+
* 去除尾部斜杠后进行严格相等比较。
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* isPathExactMatch('/users', '/users') // true
|
|
86
|
+
* isPathExactMatch('/users/', '/users') // true
|
|
87
|
+
* isPathExactMatch('/users/123', '/users') // false
|
|
53
88
|
*
|
|
54
|
-
* @param
|
|
55
|
-
* @
|
|
89
|
+
* @param currentPath - 当前路由路径
|
|
90
|
+
* @param targetPath - 目标路由路径
|
|
91
|
+
* @returns 是否为精确匹配
|
|
56
92
|
*/
|
|
57
|
-
export declare function
|
|
93
|
+
export declare function isPathExactMatch(currentPath: string, targetPath: string): boolean;
|
|
58
94
|
/**
|
|
59
95
|
* 创建一个链接助手,用于处理路由导航、生成链接属性及判断激活状态。
|
|
60
96
|
*
|
|
@@ -63,6 +99,7 @@ export declare function isExternalLink(href: string): boolean;
|
|
|
63
99
|
* @param props.to - 要跳转的目标,可以是路由目标对象、路由索引、带查询参数的路径字符串、哈希值或 HTTP/HTTPS 链接。
|
|
64
100
|
* @param [props.replace] - 是否使用 `router.replace()` 而不是 `router.push()`。优先级低于 `to.replace`。默认为 `false`。
|
|
65
101
|
* @param [props.viewTransition] - 如果支持则使用 `document.startViewTransition()` 进行视图过渡。默认为 `false`。
|
|
102
|
+
* @param [props.exactMatchMode] - 精确匹配模式。可选值有:'path'、'href'、'hash'、'query'。默认为 'path'。
|
|
66
103
|
* @returns 返回一个包含链接属性和导航方法的对象。
|
|
67
104
|
* @returns {Computed<string>} returns.href - 链接的 `href` 属性值。
|
|
68
105
|
* @returns {Computed<RouteLocation | null>} returns.route - 匹配的路由信息,如果未匹配则返回 `null`。
|
package/dist/core/shared/link.js
CHANGED
|
@@ -1,27 +1,59 @@
|
|
|
1
|
-
import { computed, isPlainObject, isString
|
|
2
|
-
import {
|
|
1
|
+
import { computed, isPlainObject, isString } from 'vitarx';
|
|
2
|
+
import { NavState } from '../common/constant.js';
|
|
3
|
+
import { isExternalLink, isNavTarget, isRoutePath } from '../common/utils.js';
|
|
3
4
|
import { useRouter } from './inject.js';
|
|
4
|
-
import { cloneRouteLocation, parseQuery } from './utils.js';
|
|
5
|
+
import { cloneRouteLocation, parseQuery, removeTrailingSlash, stringifyQuery } from './utils.js';
|
|
5
6
|
/**
|
|
6
7
|
* 处理视图转换
|
|
7
8
|
* @param callback
|
|
8
9
|
*/
|
|
9
|
-
|
|
10
|
+
async function handleTransition(callback) {
|
|
10
11
|
if (typeof document === 'undefined' || typeof document.startViewTransition !== 'function') {
|
|
11
12
|
await callback();
|
|
12
13
|
return;
|
|
13
14
|
}
|
|
14
15
|
const transition = document.startViewTransition(callback);
|
|
15
16
|
await transition.finished;
|
|
16
|
-
}
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* 判断当前路径是否以目标路径为前缀(路径段级别匹配)
|
|
20
|
+
*
|
|
21
|
+
* 去除尾部斜杠后,判断 currentPath 是否等于 targetPath 或以 targetPath/ 开头,
|
|
22
|
+
* 避免如 `/users-admin` 错误匹配 `/users` 的问题。
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* isPathPrefixMatch('/users/123', '/users') // true
|
|
26
|
+
* isPathPrefixMatch('/users', '/users') // true
|
|
27
|
+
* isPathPrefixMatch('/users-admin', '/users') // false
|
|
28
|
+
* isPathPrefixMatch('/', '/') // true
|
|
29
|
+
*
|
|
30
|
+
* @param currentPath - 当前路由路径
|
|
31
|
+
* @param targetPath - 目标路由路径
|
|
32
|
+
* @returns 是否为前缀匹配
|
|
33
|
+
*/
|
|
34
|
+
export function isPathPrefixMatch(currentPath, targetPath) {
|
|
35
|
+
const current = removeTrailingSlash(currentPath);
|
|
36
|
+
const target = removeTrailingSlash(targetPath);
|
|
37
|
+
if (target === '/')
|
|
38
|
+
return current.startsWith('/');
|
|
39
|
+
return current === target || current.startsWith(target + '/');
|
|
40
|
+
}
|
|
17
41
|
/**
|
|
18
|
-
*
|
|
42
|
+
* 判断当前路径是否与目标路径完全匹配
|
|
43
|
+
*
|
|
44
|
+
* 去除尾部斜杠后进行严格相等比较。
|
|
19
45
|
*
|
|
20
|
-
* @
|
|
21
|
-
*
|
|
46
|
+
* @example
|
|
47
|
+
* isPathExactMatch('/users', '/users') // true
|
|
48
|
+
* isPathExactMatch('/users/', '/users') // true
|
|
49
|
+
* isPathExactMatch('/users/123', '/users') // false
|
|
50
|
+
*
|
|
51
|
+
* @param currentPath - 当前路由路径
|
|
52
|
+
* @param targetPath - 目标路由路径
|
|
53
|
+
* @returns 是否为精确匹配
|
|
22
54
|
*/
|
|
23
|
-
export function
|
|
24
|
-
return
|
|
55
|
+
export function isPathExactMatch(currentPath, targetPath) {
|
|
56
|
+
return removeTrailingSlash(currentPath) === removeTrailingSlash(targetPath);
|
|
25
57
|
}
|
|
26
58
|
/**
|
|
27
59
|
* 创建一个链接助手,用于处理路由导航、生成链接属性及判断激活状态。
|
|
@@ -31,6 +63,7 @@ export function isExternalLink(href) {
|
|
|
31
63
|
* @param props.to - 要跳转的目标,可以是路由目标对象、路由索引、带查询参数的路径字符串、哈希值或 HTTP/HTTPS 链接。
|
|
32
64
|
* @param [props.replace] - 是否使用 `router.replace()` 而不是 `router.push()`。优先级低于 `to.replace`。默认为 `false`。
|
|
33
65
|
* @param [props.viewTransition] - 如果支持则使用 `document.startViewTransition()` 进行视图过渡。默认为 `false`。
|
|
66
|
+
* @param [props.exactMatchMode] - 精确匹配模式。可选值有:'path'、'href'、'hash'、'query'。默认为 'path'。
|
|
34
67
|
* @returns 返回一个包含链接属性和导航方法的对象。
|
|
35
68
|
* @returns {Computed<string>} returns.href - 链接的 `href` 属性值。
|
|
36
69
|
* @returns {Computed<RouteLocation | null>} returns.route - 匹配的路由信息,如果未匹配则返回 `null`。
|
|
@@ -48,7 +81,7 @@ export function useLink(props) {
|
|
|
48
81
|
const to = props.to;
|
|
49
82
|
let target = null;
|
|
50
83
|
// 验证目标类型
|
|
51
|
-
if (
|
|
84
|
+
if (isNavTarget(to)) {
|
|
52
85
|
target = to;
|
|
53
86
|
}
|
|
54
87
|
else if (isString(to)) {
|
|
@@ -61,12 +94,9 @@ export function useLink(props) {
|
|
|
61
94
|
target = { index: to };
|
|
62
95
|
}
|
|
63
96
|
else {
|
|
64
|
-
if (__VITARX_DEV__) {
|
|
65
|
-
logger.warn(`[RouterLink] Invalid "to" prop: ${isPlainObject(to) ? JSON.stringify(to) : String(to)}`);
|
|
66
|
-
}
|
|
67
97
|
return null;
|
|
68
98
|
}
|
|
69
|
-
//
|
|
99
|
+
// 处理路由路径
|
|
70
100
|
if (isString(target.index)) {
|
|
71
101
|
// 兼容纯锚点连接跳转
|
|
72
102
|
if (target.index.startsWith('#')) {
|
|
@@ -88,28 +118,19 @@ export function useLink(props) {
|
|
|
88
118
|
target.query = parseQuery(queryString);
|
|
89
119
|
}
|
|
90
120
|
}
|
|
91
|
-
|
|
92
|
-
if (route)
|
|
93
|
-
return route;
|
|
94
|
-
if (__VITARX_DEV__) {
|
|
95
|
-
logger.warn(`[RouterLink] No match found for to ${isPlainObject(to) ? JSON.stringify(to) : String(to)}`);
|
|
96
|
-
}
|
|
97
|
-
return null;
|
|
121
|
+
return router.matchRoute(target);
|
|
98
122
|
});
|
|
99
123
|
/**
|
|
100
124
|
* 计算属性:生成链接的 href 属性
|
|
101
125
|
* @returns 返回路由的 href 或原始字符串,默认返回 'javascript:void(0)'
|
|
102
126
|
*/
|
|
103
127
|
const href = computed(() => {
|
|
104
|
-
if (route.value?.href)
|
|
128
|
+
if (route.value?.href)
|
|
105
129
|
return route.value.href;
|
|
106
|
-
|
|
107
|
-
if (isPlainObject(props.to) && isValidPath(props.to.index)) {
|
|
130
|
+
if (isPlainObject(props.to) && isRoutePath(props.to.index))
|
|
108
131
|
return props.to.index;
|
|
109
|
-
|
|
110
|
-
if (isString(props.to)) {
|
|
132
|
+
if (isString(props.to))
|
|
111
133
|
return props.to;
|
|
112
|
-
}
|
|
113
134
|
return 'javascript:void(0)';
|
|
114
135
|
});
|
|
115
136
|
/**
|
|
@@ -120,7 +141,7 @@ export function useLink(props) {
|
|
|
120
141
|
const matchedRoute = route.value;
|
|
121
142
|
if (!matchedRoute)
|
|
122
143
|
return false;
|
|
123
|
-
return router.route.
|
|
144
|
+
return isPathPrefixMatch(router.route.path, matchedRoute.path);
|
|
124
145
|
});
|
|
125
146
|
/**
|
|
126
147
|
* 计算属性:判断当前路由是否是精确激活状态
|
|
@@ -130,39 +151,60 @@ export function useLink(props) {
|
|
|
130
151
|
const matchedRoute = route.value;
|
|
131
152
|
if (!matchedRoute)
|
|
132
153
|
return false;
|
|
133
|
-
|
|
154
|
+
const isMatched = isPathExactMatch(router.route.path, matchedRoute.path);
|
|
155
|
+
if (!isMatched)
|
|
156
|
+
return false;
|
|
157
|
+
const mode = props.exactMatchMode || 'path';
|
|
158
|
+
switch (mode) {
|
|
159
|
+
case 'href':
|
|
160
|
+
return router.route.href === matchedRoute.href;
|
|
161
|
+
case 'hash':
|
|
162
|
+
return router.route.hash === matchedRoute.hash;
|
|
163
|
+
case 'query':
|
|
164
|
+
return stringifyQuery(router.route.query) === stringifyQuery(matchedRoute.query);
|
|
165
|
+
default:
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
134
168
|
});
|
|
135
169
|
/**
|
|
136
170
|
* 导航处理函数
|
|
137
171
|
* @param e - 可选的鼠标事件对象
|
|
138
|
-
* @returns
|
|
172
|
+
* @returns {Promise<NavigateResult | void>} 如果
|
|
139
173
|
*/
|
|
140
174
|
const navigate = async (e) => {
|
|
141
|
-
const
|
|
142
|
-
//
|
|
143
|
-
if (
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
175
|
+
const routeHref = href.value;
|
|
176
|
+
// 如果是无效的链接或外部链接都返回模拟的未匹配路由结果
|
|
177
|
+
if (routeHref === 'javascript:void(0)') {
|
|
178
|
+
return {
|
|
179
|
+
state: NavState.notfound,
|
|
180
|
+
message: `No match found for target: ${isPlainObject(props.to) ? JSON.stringify(props.to) : String(props.to)}`,
|
|
181
|
+
to: null,
|
|
182
|
+
from: cloneRouteLocation(router.route)
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
if (isExternalLink(routeHref)) {
|
|
186
|
+
return {
|
|
187
|
+
state: NavState.external,
|
|
188
|
+
message: `Open External link ${routeHref}`,
|
|
189
|
+
to: null,
|
|
190
|
+
from: cloneRouteLocation(router.route)
|
|
191
|
+
};
|
|
149
192
|
}
|
|
150
193
|
// 阻止默认行为
|
|
151
194
|
e?.preventDefault();
|
|
152
195
|
const defaultReplace = props.replace ?? false;
|
|
153
|
-
const isReplace =
|
|
154
|
-
? (props.to.replace ?? defaultReplace)
|
|
155
|
-
: defaultReplace;
|
|
196
|
+
const isReplace = isNavTarget(props.to) ? (props.to.replace ?? defaultReplace) : defaultReplace;
|
|
156
197
|
let result;
|
|
198
|
+
const routeTarget = route.value || routeHref;
|
|
157
199
|
// 处理视图过渡
|
|
158
200
|
if (!__VITARX_SSR__ && props.viewTransition) {
|
|
159
201
|
await handleTransition(async () => {
|
|
160
|
-
result = isReplace ? await router.replace(
|
|
202
|
+
result = isReplace ? await router.replace(routeTarget) : await router.push(routeTarget);
|
|
161
203
|
await router.waitViewRender();
|
|
162
204
|
});
|
|
163
205
|
}
|
|
164
206
|
else {
|
|
165
|
-
result = isReplace ? await router.replace(
|
|
207
|
+
result = isReplace ? await router.replace(routeTarget) : await router.push(routeTarget);
|
|
166
208
|
}
|
|
167
209
|
return result;
|
|
168
210
|
};
|
|
@@ -24,7 +24,7 @@ export declare function stringifyQuery(obj: Record<string, string>): `?${string}
|
|
|
24
24
|
* @param {string} str - 路径字符串
|
|
25
25
|
* @return {string} - 去除末尾斜杠后的路径字符串
|
|
26
26
|
*/
|
|
27
|
-
export declare function
|
|
27
|
+
export declare function removeTrailingSlash<T extends string>(str: T): T;
|
|
28
28
|
/**
|
|
29
29
|
* 归一化path
|
|
30
30
|
*
|
|
@@ -35,7 +35,7 @@ export function stringifyQuery(obj) {
|
|
|
35
35
|
* @param {string} str - 路径字符串
|
|
36
36
|
* @return {string} - 去除末尾斜杠后的路径字符串
|
|
37
37
|
*/
|
|
38
|
-
export function
|
|
38
|
+
export function removeTrailingSlash(str) {
|
|
39
39
|
if (str === '/')
|
|
40
40
|
return str;
|
|
41
41
|
return str.endsWith('/') ? str.slice(0, -1) : str;
|
|
@@ -60,7 +60,7 @@ export function normalizePath(path, removeEndSlash = false) {
|
|
|
60
60
|
// 去除所有空格 处理重复//
|
|
61
61
|
let normalizedPath = `/${path.trim()}`.replace(/\s+/g, '').replace(/\/+/g, '/');
|
|
62
62
|
if (removeEndSlash) {
|
|
63
|
-
normalizedPath =
|
|
63
|
+
normalizedPath = removeTrailingSlash(normalizedPath);
|
|
64
64
|
}
|
|
65
65
|
// 去除尾随斜杠
|
|
66
66
|
return normalizedPath;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vitarx-router",
|
|
3
|
-
"version": "4.0.0-beta.
|
|
3
|
+
"version": "4.0.0-beta.26",
|
|
4
4
|
"description": "Official routing solution for Vitarx framework with declarative routing, navigation guards, dynamic routes, file-based routing with HMR, and full TypeScript support.",
|
|
5
5
|
"author": "ZhuChonglin <8210856@qq.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -55,6 +55,9 @@
|
|
|
55
55
|
"peerDependenciesMeta": {
|
|
56
56
|
"vite": {
|
|
57
57
|
"optional": true
|
|
58
|
+
},
|
|
59
|
+
"vitarx": {
|
|
60
|
+
"optional": true
|
|
58
61
|
}
|
|
59
62
|
},
|
|
60
63
|
"dependencies": {
|