vitarx-router 4.0.7 → 4.0.8
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/dist/core/common/utils.d.ts +7 -1
- package/dist/core/common/utils.js +42 -1
- package/dist/core/router/memory.d.ts +2 -1
- package/dist/core/router/memory.js +8 -3
- package/dist/core/router/router.js +17 -20
- package/dist/core/router/web.js +3 -36
- package/dist/file-router/config/validate.js +31 -19
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type AnyCallback } from 'vitarx';
|
|
2
|
-
import type { GuardResult, NavTarget, RouteIndex, RouteLocation, RoutePath, URLHash, URLQuery } from '../types/index.js';
|
|
2
|
+
import type { GuardResult, NavTarget, RouteIndex, RouteLocation, RoutePath, ScrollTarget, URLHash, URLQuery } from '../types/index.js';
|
|
3
3
|
/**
|
|
4
4
|
* 判断是否为外部链接
|
|
5
5
|
*
|
|
@@ -85,3 +85,9 @@ export declare function resolveNavTarget(index: NavTarget | RouteIndex | RouteLo
|
|
|
85
85
|
export declare function registerHookTool<T extends {
|
|
86
86
|
[key: string]: any;
|
|
87
87
|
}>(hooks: T, type: keyof T, hook: AnyCallback): void;
|
|
88
|
+
/**
|
|
89
|
+
* web 滚动到指定位置
|
|
90
|
+
*
|
|
91
|
+
* @param target - 滚动目标
|
|
92
|
+
*/
|
|
93
|
+
export declare function webScrollTo(target: ScrollTarget): void;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isDeepEqual, isPlainObject, isString } from 'vitarx';
|
|
1
|
+
import { isDeepEqual, isPlainObject, isString, logger } from 'vitarx';
|
|
2
2
|
import { normalizePath, parseQuery } from '../shared/utils.js';
|
|
3
3
|
/**
|
|
4
4
|
* 判断是否为外部链接
|
|
@@ -149,3 +149,44 @@ export function registerHookTool(hooks, type, hook) {
|
|
|
149
149
|
set.add(hook);
|
|
150
150
|
}
|
|
151
151
|
}
|
|
152
|
+
/**
|
|
153
|
+
* web 滚动到指定位置
|
|
154
|
+
*
|
|
155
|
+
* @param target - 滚动目标
|
|
156
|
+
*/
|
|
157
|
+
export function webScrollTo(target) {
|
|
158
|
+
try {
|
|
159
|
+
if ('el' in target && target.el) {
|
|
160
|
+
const { el, ...rest } = target;
|
|
161
|
+
if (isString(el)) {
|
|
162
|
+
let element = null;
|
|
163
|
+
try {
|
|
164
|
+
// 如果选择器以 # 开头,则进行解码
|
|
165
|
+
const selector = el.startsWith('#') ? decodeURIComponent(el) : el;
|
|
166
|
+
element = document.querySelector(selector);
|
|
167
|
+
}
|
|
168
|
+
catch (e) {
|
|
169
|
+
logger.warn(`[Router] Invalid selector "${el}", skipping scroll to element`, e);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
if (element) {
|
|
173
|
+
if (element.scrollIntoView) {
|
|
174
|
+
element.scrollIntoView(rest);
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
window.scrollTo({
|
|
178
|
+
top: element.getBoundingClientRect().top + window.scrollY,
|
|
179
|
+
left: element.getBoundingClientRect().left + window.scrollX
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
window.scrollTo(target);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
catch (e) {
|
|
190
|
+
logger.warn(`[Router] Failed to scroll to specified position, please check scroll target parameters: ${JSON.stringify(target)}`, e);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { RouteLocation } from '../types/index.js';
|
|
1
|
+
import type { RouteLocation, RouterOptions } from '../types/index.js';
|
|
2
2
|
import { Router } from './router.js';
|
|
3
3
|
/**
|
|
4
4
|
* 基于内存实现的路由器
|
|
@@ -11,6 +11,7 @@ export declare class MemoryRouter extends Router {
|
|
|
11
11
|
protected _history: RouteLocation[];
|
|
12
12
|
protected _pendingGo: number | null;
|
|
13
13
|
private _currentIndex;
|
|
14
|
+
constructor(options: RouterOptions);
|
|
14
15
|
/**
|
|
15
16
|
* 当前历史路由索引
|
|
16
17
|
*
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { webScrollTo } from '../common/utils.js';
|
|
1
2
|
import { Router } from './router.js';
|
|
2
3
|
/**
|
|
3
4
|
* 基于内存实现的路由器
|
|
@@ -7,8 +8,8 @@ import { Router } from './router.js';
|
|
|
7
8
|
* > 注意:不要在浏览器端使用,因为浏览器端有原生的history对象,使用内存模式会和浏览器端的历史记录冲突,导致路由异常。
|
|
8
9
|
*/
|
|
9
10
|
export class MemoryRouter extends Router {
|
|
10
|
-
constructor() {
|
|
11
|
-
super(
|
|
11
|
+
constructor(options) {
|
|
12
|
+
super(options);
|
|
12
13
|
// 路由历史记录数组
|
|
13
14
|
Object.defineProperty(this, "_history", {
|
|
14
15
|
enumerable: true,
|
|
@@ -23,13 +24,17 @@ export class MemoryRouter extends Router {
|
|
|
23
24
|
writable: true,
|
|
24
25
|
value: null
|
|
25
26
|
});
|
|
26
|
-
|
|
27
|
+
// 当前历史路由索引
|
|
27
28
|
Object.defineProperty(this, "_currentIndex", {
|
|
28
29
|
enumerable: true,
|
|
29
30
|
configurable: true,
|
|
30
31
|
writable: true,
|
|
31
32
|
value: -1
|
|
32
33
|
});
|
|
34
|
+
// 兼容在electron 中使用内存路由器时享有滚动行为
|
|
35
|
+
if (typeof window !== 'undefined' && typeof window.scrollTo === 'function') {
|
|
36
|
+
this.scrollTo = webScrollTo;
|
|
37
|
+
}
|
|
33
38
|
}
|
|
34
39
|
/**
|
|
35
40
|
* 当前历史路由索引
|
|
@@ -789,31 +789,28 @@ export class Router {
|
|
|
789
789
|
if (__VITARX_SSR__)
|
|
790
790
|
return;
|
|
791
791
|
const defaultTarget = to.hash ? { el: to.hash } : { top: 0, left: 0 };
|
|
792
|
-
// 初始化目标滚动位置为保存的位置
|
|
793
|
-
let target = savedPosition || defaultTarget;
|
|
794
|
-
// 检查是否配置了自定义滚动行为函数
|
|
795
|
-
if (isFunction(this.config.scrollBehavior)) {
|
|
796
|
-
try {
|
|
797
|
-
// 调用自定义滚动行为函数
|
|
798
|
-
const result = this.config.scrollBehavior(to, from, savedPosition);
|
|
799
|
-
if (result === false)
|
|
800
|
-
return; // 如果返回 false,则不进行滚动
|
|
801
|
-
if (isPlainObject(result))
|
|
802
|
-
target = result; // 如果有返回值,则更新目标位置
|
|
803
|
-
}
|
|
804
|
-
catch (e) {
|
|
805
|
-
// 捕获并记录自定义滚动行为函数中的错误
|
|
806
|
-
logger.error('[Router] Error in scrollBehavior callback:', e);
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
|
-
// 没有滚动行为函数,则直接返回
|
|
810
|
-
if (!this.scrollTo)
|
|
811
|
-
return;
|
|
812
792
|
const id = ++this._scrollTaskID;
|
|
813
793
|
// 等待路由组件解析完成后滚动
|
|
814
794
|
this.waitViewRender().then(async () => {
|
|
815
795
|
// 确保滚动目标未改变
|
|
816
796
|
if (id === this._scrollTaskID) {
|
|
797
|
+
// 初始化目标滚动位置为保存的位置
|
|
798
|
+
let target = savedPosition || defaultTarget;
|
|
799
|
+
// 检查是否配置了自定义滚动行为函数
|
|
800
|
+
if (isFunction(this.config.scrollBehavior)) {
|
|
801
|
+
try {
|
|
802
|
+
// 调用自定义滚动行为函数
|
|
803
|
+
const result = this.config.scrollBehavior(to, from, savedPosition);
|
|
804
|
+
if (result === false)
|
|
805
|
+
return; // 如果返回 false,则不进行滚动
|
|
806
|
+
if (isPlainObject(result))
|
|
807
|
+
target = result; // 如果有返回值,则更新目标位置
|
|
808
|
+
}
|
|
809
|
+
catch (e) {
|
|
810
|
+
// 捕获并记录自定义滚动行为函数中的错误
|
|
811
|
+
logger.error('[Router] Error in scrollBehavior callback:', e);
|
|
812
|
+
}
|
|
813
|
+
}
|
|
817
814
|
// 执行滚动到目标位置
|
|
818
815
|
this.scrollTo?.(target);
|
|
819
816
|
}
|
package/dist/core/router/web.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { logger } from 'vitarx';
|
|
2
2
|
import { NavState } from '../common/constant.js';
|
|
3
|
-
import { parseHashContent } from '../common/utils.js';
|
|
3
|
+
import { parseHashContent, webScrollTo } from '../common/utils.js';
|
|
4
4
|
import { normalizePath, parseQuery } from '../shared/utils.js';
|
|
5
5
|
import { Router } from './router.js';
|
|
6
6
|
export class WebRouter extends Router {
|
|
@@ -136,40 +136,7 @@ export class WebRouter extends Router {
|
|
|
136
136
|
* @inheritDoc
|
|
137
137
|
*/
|
|
138
138
|
scrollTo(target) {
|
|
139
|
-
|
|
140
|
-
if ('el' in target && target.el) {
|
|
141
|
-
const { el, ...rest } = target;
|
|
142
|
-
if (isString(el)) {
|
|
143
|
-
let element = null;
|
|
144
|
-
try {
|
|
145
|
-
// 如果选择器以 # 开头,则进行解码
|
|
146
|
-
const selector = el.startsWith('#') ? decodeURIComponent(el) : el;
|
|
147
|
-
element = document.querySelector(selector);
|
|
148
|
-
}
|
|
149
|
-
catch (e) {
|
|
150
|
-
logger.warn(`[Router] Invalid selector "${el}", skipping scroll to element`, e);
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
if (element) {
|
|
154
|
-
if (element.scrollIntoView) {
|
|
155
|
-
element.scrollIntoView(rest);
|
|
156
|
-
}
|
|
157
|
-
else {
|
|
158
|
-
window.scrollTo({
|
|
159
|
-
top: element.getBoundingClientRect().top + window.scrollY,
|
|
160
|
-
left: element.getBoundingClientRect().left + window.scrollX
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
else {
|
|
167
|
-
window.scrollTo(target);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
catch (e) {
|
|
171
|
-
logger.warn(`[Router] Failed to scroll to specified position, please check scroll target parameters: ${JSON.stringify(target)}`, e);
|
|
172
|
-
}
|
|
139
|
+
webScrollTo(target);
|
|
173
140
|
}
|
|
174
141
|
/**
|
|
175
142
|
* 将当前 URL 转换为 NavigateTarget 对象
|
|
@@ -7,64 +7,76 @@
|
|
|
7
7
|
/**
|
|
8
8
|
* 验证 pages 配置
|
|
9
9
|
*
|
|
10
|
+
* pages 支持 string(单目录路径)、PageDirOptions(单目录配置对象)以及
|
|
11
|
+
* 它们的数组形式,此处对应每种形态分别校验,与 FileRouterOptions.pages 类型保持一致。
|
|
12
|
+
*
|
|
10
13
|
* @param opts - 配置选项
|
|
11
14
|
* @throws {Error} 当配置无效时抛出错误
|
|
12
15
|
*/
|
|
13
16
|
function validatePagesDir(opts) {
|
|
14
17
|
if (opts.pages === undefined)
|
|
15
18
|
return;
|
|
19
|
+
// 字符串形式:单个页面目录路径
|
|
16
20
|
if (typeof opts.pages === 'string') {
|
|
17
|
-
|
|
18
|
-
throw new Error('options.pages 不能为空字符串');
|
|
19
|
-
}
|
|
21
|
+
validatePageSource(opts.pages, 'options.pages');
|
|
20
22
|
return;
|
|
21
23
|
}
|
|
24
|
+
// 数组形式:多个页面来源
|
|
22
25
|
if (Array.isArray(opts.pages)) {
|
|
23
26
|
if (opts.pages.length === 0) {
|
|
24
27
|
throw new Error('options.pages 数组不能为空');
|
|
25
28
|
}
|
|
26
29
|
opts.pages.forEach((item, i) => {
|
|
27
|
-
|
|
30
|
+
validatePageSource(item, `options.pages[${i}]`);
|
|
28
31
|
});
|
|
29
32
|
return;
|
|
30
33
|
}
|
|
31
|
-
|
|
34
|
+
// 对象形式:单个页面目录配置(PageDirOptions)
|
|
35
|
+
if (typeof opts.pages === 'object' && opts.pages !== null) {
|
|
36
|
+
validatePageSource(opts.pages, 'options.pages');
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
throw new Error('options.pages 必须是字符串、对象或字符串/对象数组');
|
|
32
40
|
}
|
|
33
41
|
/**
|
|
34
|
-
*
|
|
42
|
+
* 验证单个页面来源配置
|
|
43
|
+
*
|
|
44
|
+
* 同时用于单对象配置(fieldPrefix 为 'options.pages')与数组项
|
|
45
|
+
* (fieldPrefix 为 'options.pages[i]')的校验,通过 fieldPrefix 拼接出
|
|
46
|
+
* 准确的错误定位信息,避免单对象与数组两套校验逻辑的重复实现。
|
|
35
47
|
*
|
|
36
|
-
* @param
|
|
37
|
-
* @param
|
|
48
|
+
* @param source - 页面来源,可为字符串或 PageDirOptions 对象
|
|
49
|
+
* @param fieldPrefix - 错误信息字段前缀
|
|
38
50
|
* @throws {Error} 当配置无效时抛出错误
|
|
39
51
|
*/
|
|
40
|
-
function
|
|
41
|
-
if (typeof
|
|
42
|
-
if (
|
|
43
|
-
throw new Error(
|
|
52
|
+
function validatePageSource(source, fieldPrefix) {
|
|
53
|
+
if (typeof source === 'string') {
|
|
54
|
+
if (source.trim() === '') {
|
|
55
|
+
throw new Error(`${fieldPrefix} 不能为空字符串`);
|
|
44
56
|
}
|
|
45
57
|
return;
|
|
46
58
|
}
|
|
47
|
-
if (typeof
|
|
48
|
-
const config =
|
|
59
|
+
if (typeof source === 'object' && source !== null) {
|
|
60
|
+
const config = source;
|
|
49
61
|
if (!config.dir || typeof config.dir !== 'string' || config.dir.trim() === '') {
|
|
50
|
-
throw new Error(
|
|
62
|
+
throw new Error(`${fieldPrefix}.dir 必须是非空字符串`);
|
|
51
63
|
}
|
|
52
64
|
if (config.include !== undefined && !Array.isArray(config.include)) {
|
|
53
|
-
throw new Error(
|
|
65
|
+
throw new Error(`${fieldPrefix}.include 必须是数组`);
|
|
54
66
|
}
|
|
55
67
|
if (config.exclude !== undefined && !Array.isArray(config.exclude)) {
|
|
56
|
-
throw new Error(
|
|
68
|
+
throw new Error(`${fieldPrefix}.exclude 必须是数组`);
|
|
57
69
|
}
|
|
58
70
|
if (config.group === true &&
|
|
59
71
|
config.prefix !== undefined &&
|
|
60
72
|
typeof config.prefix === 'string' &&
|
|
61
73
|
config.prefix !== '/' &&
|
|
62
74
|
config.prefix.endsWith('/')) {
|
|
63
|
-
throw new Error(
|
|
75
|
+
throw new Error(`${fieldPrefix}.prefix 当 group 为 true 时不能以 '/' 结尾,请使用 '${config.prefix.slice(0, -1)}'`);
|
|
64
76
|
}
|
|
65
77
|
return;
|
|
66
78
|
}
|
|
67
|
-
throw new Error(
|
|
79
|
+
throw new Error(`${fieldPrefix} 必须是字符串或对象`);
|
|
68
80
|
}
|
|
69
81
|
/**
|
|
70
82
|
* 验证 root 配置
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vitarx-router",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.8",
|
|
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",
|