@esmx/router-vue 3.0.0-rc.12
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/index.d.ts +4 -0
- package/dist/index.mjs +4 -0
- package/dist/link.d.ts +101 -0
- package/dist/link.mjs +103 -0
- package/dist/plugin.d.ts +11 -0
- package/dist/plugin.mjs +16 -0
- package/dist/symbols.d.ts +3 -0
- package/dist/symbols.mjs +3 -0
- package/dist/use.d.ts +4 -0
- package/dist/use.mjs +15 -0
- package/dist/view.d.ts +21 -0
- package/dist/view.mjs +75 -0
- package/package.json +70 -0
- package/src/index.ts +4 -0
- package/src/link.ts +177 -0
- package/src/plugin.ts +36 -0
- package/src/symbols.ts +8 -0
- package/src/use.ts +20 -0
- package/src/view.ts +95 -0
package/dist/index.d.ts
ADDED
package/dist/index.mjs
ADDED
package/dist/link.d.ts
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { type RouterRawLocation } from '@esmx/router';
|
|
2
|
+
import { type PropType } from 'vue';
|
|
3
|
+
export interface RouterLinkProps {
|
|
4
|
+
/**
|
|
5
|
+
* 前往的路由路径
|
|
6
|
+
*/
|
|
7
|
+
to: RouterRawLocation;
|
|
8
|
+
/**
|
|
9
|
+
* 节点使用的标签名
|
|
10
|
+
* @default 'a'
|
|
11
|
+
*/
|
|
12
|
+
tag: string;
|
|
13
|
+
/**
|
|
14
|
+
* 调用 router.replace 以替换 router.push。
|
|
15
|
+
* @default false
|
|
16
|
+
*/
|
|
17
|
+
replace: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* 路径激活匹配规则
|
|
20
|
+
* @example include => 路径包含即激活.
|
|
21
|
+
* 如: 当前路由为/en/news/list 此时router-link 的路径为 /en/news 也会激活
|
|
22
|
+
* @example route => 路由匹配才会激活,需要匹配的路由树一致.
|
|
23
|
+
* 如: 当前路由为/en/news/list/123 此时router-link 的路径为 /en/news/list 也会激活
|
|
24
|
+
* @example exact => 路径全匹配才会激活,不仅需要匹配路由树一致,还需要参数匹配才会激活.
|
|
25
|
+
* 如: 当前路由为/en/news/list/123 此时router-link 的路径为 /en/news/list/123 才会激活,如果配置的路径为/en/news/list/123456 也不会激活
|
|
26
|
+
* @default 'include'
|
|
27
|
+
*/
|
|
28
|
+
exact: 'include' | 'route' | 'exact';
|
|
29
|
+
/**
|
|
30
|
+
* 是否为相对路径
|
|
31
|
+
* 按照 Hanson 要求目前都是绝对路径,因此废弃此属性
|
|
32
|
+
* @default false
|
|
33
|
+
*/
|
|
34
|
+
/**
|
|
35
|
+
* 路由激活时的class
|
|
36
|
+
* @default 'router-link-active'
|
|
37
|
+
*/
|
|
38
|
+
activeClass: string;
|
|
39
|
+
/**
|
|
40
|
+
* 哪些事件触发路由跳转
|
|
41
|
+
* @default 'click'
|
|
42
|
+
*/
|
|
43
|
+
event: string | string[];
|
|
44
|
+
}
|
|
45
|
+
export declare const RouterLink: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
|
46
|
+
to: {
|
|
47
|
+
type: PropType<RouterLinkProps["to"]>;
|
|
48
|
+
required: true;
|
|
49
|
+
};
|
|
50
|
+
tag: {
|
|
51
|
+
type: PropType<RouterLinkProps["tag"]>;
|
|
52
|
+
default: string;
|
|
53
|
+
};
|
|
54
|
+
replace: {
|
|
55
|
+
type: PropType<RouterLinkProps["replace"]>;
|
|
56
|
+
default: boolean;
|
|
57
|
+
};
|
|
58
|
+
exact: {
|
|
59
|
+
type: PropType<RouterLinkProps["exact"]>;
|
|
60
|
+
default: string;
|
|
61
|
+
};
|
|
62
|
+
activeClass: {
|
|
63
|
+
type: PropType<RouterLinkProps["activeClass"]>;
|
|
64
|
+
default: string;
|
|
65
|
+
};
|
|
66
|
+
event: {
|
|
67
|
+
type: PropType<RouterLinkProps["event"]>;
|
|
68
|
+
default: string;
|
|
69
|
+
};
|
|
70
|
+
}>, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
|
71
|
+
to: {
|
|
72
|
+
type: PropType<RouterLinkProps["to"]>;
|
|
73
|
+
required: true;
|
|
74
|
+
};
|
|
75
|
+
tag: {
|
|
76
|
+
type: PropType<RouterLinkProps["tag"]>;
|
|
77
|
+
default: string;
|
|
78
|
+
};
|
|
79
|
+
replace: {
|
|
80
|
+
type: PropType<RouterLinkProps["replace"]>;
|
|
81
|
+
default: boolean;
|
|
82
|
+
};
|
|
83
|
+
exact: {
|
|
84
|
+
type: PropType<RouterLinkProps["exact"]>;
|
|
85
|
+
default: string;
|
|
86
|
+
};
|
|
87
|
+
activeClass: {
|
|
88
|
+
type: PropType<RouterLinkProps["activeClass"]>;
|
|
89
|
+
default: string;
|
|
90
|
+
};
|
|
91
|
+
event: {
|
|
92
|
+
type: PropType<RouterLinkProps["event"]>;
|
|
93
|
+
default: string;
|
|
94
|
+
};
|
|
95
|
+
}>> & Readonly<{}>, {
|
|
96
|
+
exact: "include" | "route" | "exact";
|
|
97
|
+
tag: string;
|
|
98
|
+
replace: boolean;
|
|
99
|
+
activeClass: string;
|
|
100
|
+
event: string | string[];
|
|
101
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
package/dist/link.mjs
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isEqualRoute,
|
|
3
|
+
isSameRoute
|
|
4
|
+
} from "@esmx/router";
|
|
5
|
+
import { defineComponent, h } from "vue";
|
|
6
|
+
import { useRoute, useRouter } from "./use.mjs";
|
|
7
|
+
export const RouterLink = defineComponent({
|
|
8
|
+
props: {
|
|
9
|
+
to: {
|
|
10
|
+
type: [String, Object],
|
|
11
|
+
required: true
|
|
12
|
+
},
|
|
13
|
+
tag: {
|
|
14
|
+
type: String,
|
|
15
|
+
default: "a"
|
|
16
|
+
},
|
|
17
|
+
replace: {
|
|
18
|
+
type: Boolean,
|
|
19
|
+
default: false
|
|
20
|
+
},
|
|
21
|
+
exact: {
|
|
22
|
+
type: String,
|
|
23
|
+
default: "include"
|
|
24
|
+
},
|
|
25
|
+
// append: {
|
|
26
|
+
// type: Boolean as PropType<boolean>,
|
|
27
|
+
// default: false
|
|
28
|
+
// },
|
|
29
|
+
activeClass: {
|
|
30
|
+
type: String,
|
|
31
|
+
default: "router-link-active"
|
|
32
|
+
},
|
|
33
|
+
event: {
|
|
34
|
+
type: String,
|
|
35
|
+
default: "click"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
render(props) {
|
|
39
|
+
const { to, tag, replace, exact, activeClass, event } = props;
|
|
40
|
+
const router = useRouter();
|
|
41
|
+
const current = useRoute();
|
|
42
|
+
const resolveRoute = router.resolve(to);
|
|
43
|
+
let compare;
|
|
44
|
+
switch (exact) {
|
|
45
|
+
/* 路由级匹配 */
|
|
46
|
+
case "route":
|
|
47
|
+
compare = isSameRoute;
|
|
48
|
+
break;
|
|
49
|
+
/* 全匹配 */
|
|
50
|
+
case "exact":
|
|
51
|
+
compare = isEqualRoute;
|
|
52
|
+
break;
|
|
53
|
+
/* 是否包含 */
|
|
54
|
+
case "include":
|
|
55
|
+
default:
|
|
56
|
+
compare = (current2, route) => {
|
|
57
|
+
return current2.fullPath.startsWith(route.fullPath);
|
|
58
|
+
};
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
const active = compare(current, resolveRoute);
|
|
62
|
+
const handler = (e) => {
|
|
63
|
+
if (guardEvent(e)) {
|
|
64
|
+
router[replace ? "replace" : "push"](to);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
const on = {};
|
|
68
|
+
const eventTypeList = getEventTypeList(event);
|
|
69
|
+
eventTypeList.forEach((eventName) => {
|
|
70
|
+
on[`on${eventName.toLocaleLowerCase()}`] = handler;
|
|
71
|
+
});
|
|
72
|
+
return h(
|
|
73
|
+
tag,
|
|
74
|
+
{
|
|
75
|
+
class: ["router-link", active ? [activeClass] : ""],
|
|
76
|
+
href: resolveRoute.fullPath,
|
|
77
|
+
...on
|
|
78
|
+
},
|
|
79
|
+
this.$slots
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
function getEventTypeList(eventType) {
|
|
84
|
+
if (eventType instanceof Array) {
|
|
85
|
+
if (eventType.length > 0) {
|
|
86
|
+
return eventType;
|
|
87
|
+
}
|
|
88
|
+
return ["click"];
|
|
89
|
+
}
|
|
90
|
+
return [eventType || "click"];
|
|
91
|
+
}
|
|
92
|
+
function guardEvent(e) {
|
|
93
|
+
var _a;
|
|
94
|
+
if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) return;
|
|
95
|
+
if (e.defaultPrevented) return;
|
|
96
|
+
if (e.button !== void 0 && e.button !== 0) return;
|
|
97
|
+
if ((_a = e.currentTarget) == null ? void 0 : _a.getAttribute) {
|
|
98
|
+
const target = e.currentTarget.getAttribute("target");
|
|
99
|
+
if (/\b_blank\b/i.test(target)) return;
|
|
100
|
+
}
|
|
101
|
+
if (e.preventDefault) e.preventDefault();
|
|
102
|
+
return true;
|
|
103
|
+
}
|
package/dist/plugin.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Route, RouterInstance } from '@esmx/router';
|
|
2
|
+
import { type App, type ShallowReactive } from 'vue';
|
|
3
|
+
declare module '@vue/runtime-core' {
|
|
4
|
+
interface ComponentCustomProperties {
|
|
5
|
+
$route: ShallowReactive<Route>;
|
|
6
|
+
$router: RouterInstance;
|
|
7
|
+
}
|
|
8
|
+
interface GlobalComponents {
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export declare function RouterVuePlugin(router: RouterInstance): (app: App) => void;
|
package/dist/plugin.mjs
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { shallowReactive, unref } from "vue";
|
|
2
|
+
import { RouterLink } from "./link.mjs";
|
|
3
|
+
import { routerKey, routerViewLocationKey } from "./symbols.mjs";
|
|
4
|
+
import { RouterView } from "./view.mjs";
|
|
5
|
+
export function RouterVuePlugin(router) {
|
|
6
|
+
return function install(app) {
|
|
7
|
+
const route = shallowReactive(router.route);
|
|
8
|
+
router.route = route;
|
|
9
|
+
app.config.globalProperties.$router = router;
|
|
10
|
+
app.config.globalProperties.$route = router.route;
|
|
11
|
+
app.provide(routerKey, unref(router));
|
|
12
|
+
app.provide(routerViewLocationKey, unref(router.route));
|
|
13
|
+
app.component("router-view", RouterView);
|
|
14
|
+
app.component("router-link", RouterLink);
|
|
15
|
+
};
|
|
16
|
+
}
|
package/dist/symbols.mjs
ADDED
package/dist/use.d.ts
ADDED
package/dist/use.mjs
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { getCurrentInstance, inject } from "vue";
|
|
2
|
+
import { routerKey, routerViewLocationKey } from "./symbols.mjs";
|
|
3
|
+
export function throwNoCurrentInstance(method) {
|
|
4
|
+
if (!getCurrentInstance()) {
|
|
5
|
+
throw new Error(
|
|
6
|
+
`[router-vue]: Missing current instance. ${method}() must be called inside <script setup> or setup().`
|
|
7
|
+
);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export function useRouter() {
|
|
11
|
+
return inject(routerKey);
|
|
12
|
+
}
|
|
13
|
+
export function useRoute() {
|
|
14
|
+
return inject(routerViewLocationKey);
|
|
15
|
+
}
|
package/dist/view.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Route } from '@esmx/router';
|
|
2
|
+
import { type PropType } from 'vue';
|
|
3
|
+
export declare const RouterView: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
|
4
|
+
name: {
|
|
5
|
+
type: PropType<string>;
|
|
6
|
+
default: string;
|
|
7
|
+
};
|
|
8
|
+
route: PropType<Route>;
|
|
9
|
+
}>, (() => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
|
|
10
|
+
[key: string]: any;
|
|
11
|
+
}> | import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
|
|
12
|
+
[key: string]: any;
|
|
13
|
+
}>[] | null) | undefined, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
|
14
|
+
name: {
|
|
15
|
+
type: PropType<string>;
|
|
16
|
+
default: string;
|
|
17
|
+
};
|
|
18
|
+
route: PropType<Route>;
|
|
19
|
+
}>> & Readonly<{}>, {
|
|
20
|
+
name: string;
|
|
21
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
package/dist/view.mjs
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import {
|
|
2
|
+
computed,
|
|
3
|
+
defineComponent,
|
|
4
|
+
getCurrentInstance,
|
|
5
|
+
h,
|
|
6
|
+
inject,
|
|
7
|
+
provide,
|
|
8
|
+
shallowReactive
|
|
9
|
+
} from "vue";
|
|
10
|
+
import { routerViewDepthKey, routerViewLocationKey } from "./symbols.mjs";
|
|
11
|
+
export const RouterView = defineComponent({
|
|
12
|
+
name: "RouterView",
|
|
13
|
+
inheritAttrs: true,
|
|
14
|
+
props: {
|
|
15
|
+
name: {
|
|
16
|
+
type: String,
|
|
17
|
+
default: "default"
|
|
18
|
+
},
|
|
19
|
+
route: Object
|
|
20
|
+
},
|
|
21
|
+
// Better compat for @vue/compat users
|
|
22
|
+
// https://github.com/vuejs/router/issues/1315
|
|
23
|
+
compatConfig: { MODE: 3 },
|
|
24
|
+
setup: (props, { attrs, slots }) => {
|
|
25
|
+
const instance = getCurrentInstance();
|
|
26
|
+
if (!instance) {
|
|
27
|
+
console.error("no current instance");
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const injectedRoute = inject(
|
|
31
|
+
routerViewLocationKey
|
|
32
|
+
);
|
|
33
|
+
const routeToDisplay = computed(
|
|
34
|
+
() => props.route || injectedRoute
|
|
35
|
+
);
|
|
36
|
+
const injectedDepth = inject(
|
|
37
|
+
routerViewDepthKey,
|
|
38
|
+
computed(() => 0)
|
|
39
|
+
);
|
|
40
|
+
const depth = computed(() => {
|
|
41
|
+
let initialDepth = injectedDepth.value;
|
|
42
|
+
const { matched } = routeToDisplay.value;
|
|
43
|
+
let matchedRoute;
|
|
44
|
+
while ((matchedRoute = matched[initialDepth]) && !(matchedRoute == null ? void 0 : matchedRoute.component)) {
|
|
45
|
+
initialDepth++;
|
|
46
|
+
}
|
|
47
|
+
return initialDepth;
|
|
48
|
+
});
|
|
49
|
+
provide(routerViewLocationKey, shallowReactive(routeToDisplay.value));
|
|
50
|
+
provide(
|
|
51
|
+
routerViewDepthKey,
|
|
52
|
+
computed(() => depth.value + 1)
|
|
53
|
+
);
|
|
54
|
+
return () => {
|
|
55
|
+
const matchRoute = routeToDisplay.value.matched[depth.value];
|
|
56
|
+
if (!matchRoute) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
const component = h(
|
|
60
|
+
matchRoute.component,
|
|
61
|
+
Object.assign({}, props, attrs)
|
|
62
|
+
);
|
|
63
|
+
return (
|
|
64
|
+
// pass the vnode to the slot as a prop.
|
|
65
|
+
// h and <component :is="..."> both accept vnodes
|
|
66
|
+
normalizeSlot(slots.default, { Component: component }) || component
|
|
67
|
+
);
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
function normalizeSlot(slot, data) {
|
|
72
|
+
if (!slot) return null;
|
|
73
|
+
const slotContent = slot(data);
|
|
74
|
+
return slotContent.length === 1 ? slotContent[0] : slotContent;
|
|
75
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@esmx/router-vue",
|
|
3
|
+
"template": "library",
|
|
4
|
+
"scripts": {
|
|
5
|
+
"lint:js": "biome check --write --no-errors-on-unmatched",
|
|
6
|
+
"lint:css": "stylelint '**/*.{css,vue}' --fix --aei",
|
|
7
|
+
"lint:type": "tsc --noEmit",
|
|
8
|
+
"test": "vitest run --pass-with-no-tests",
|
|
9
|
+
"coverage": "vitest run --coverage --pass-with-no-tests",
|
|
10
|
+
"build": "unbuild"
|
|
11
|
+
},
|
|
12
|
+
"contributors": [
|
|
13
|
+
{
|
|
14
|
+
"name": "lzxb",
|
|
15
|
+
"url": "https://github.com/lzxb"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"name": "RockShi1994",
|
|
19
|
+
"url": "https://github.com/RockShi1994"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"name": "jerrychan7",
|
|
23
|
+
"url": "https://github.com/jerrychan7"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"name": "wesloong",
|
|
27
|
+
"url": "https://github.com/wesloong"
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
"peerDependencies": {
|
|
31
|
+
"vue": "^3.0.0"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@esmx/router": "3.0.0-rc.12"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@biomejs/biome": "1.9.4",
|
|
38
|
+
"@esmx/lint": "3.0.0-rc.12",
|
|
39
|
+
"@gez/lint": "3.0.0-rc.9",
|
|
40
|
+
"@types/node": "22.13.10",
|
|
41
|
+
"@vitest/coverage-v8": "3.0.8",
|
|
42
|
+
"@vue/runtime-core": "^3.4.27",
|
|
43
|
+
"@vue/runtime-dom": "^3.4.27",
|
|
44
|
+
"stylelint": "16.15.0",
|
|
45
|
+
"typescript": "5.8.2",
|
|
46
|
+
"unbuild": "2.0.0",
|
|
47
|
+
"vitest": "3.0.8",
|
|
48
|
+
"vue": "^3.4.27"
|
|
49
|
+
},
|
|
50
|
+
"version": "3.0.0-rc.12",
|
|
51
|
+
"type": "module",
|
|
52
|
+
"private": false,
|
|
53
|
+
"exports": {
|
|
54
|
+
".": {
|
|
55
|
+
"import": "./dist/index.mjs",
|
|
56
|
+
"types": "./dist/index.d.ts"
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"module": "dist/index.mjs",
|
|
60
|
+
"types": "./dist/index.d.ts",
|
|
61
|
+
"files": [
|
|
62
|
+
"lib",
|
|
63
|
+
"src",
|
|
64
|
+
"dist",
|
|
65
|
+
"*.mjs",
|
|
66
|
+
"template",
|
|
67
|
+
"public"
|
|
68
|
+
],
|
|
69
|
+
"gitHead": "7d2c2fc4fe2cc98ebdbc12560f19637ca04398e5"
|
|
70
|
+
}
|
package/src/index.ts
ADDED
package/src/link.ts
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type RouteRecord,
|
|
3
|
+
type RouterRawLocation,
|
|
4
|
+
isEqualRoute,
|
|
5
|
+
isSameRoute
|
|
6
|
+
} from '@esmx/router';
|
|
7
|
+
import { type PropType, defineComponent, h } from 'vue';
|
|
8
|
+
|
|
9
|
+
import { useRoute, useRouter } from './use';
|
|
10
|
+
|
|
11
|
+
export interface RouterLinkProps {
|
|
12
|
+
/**
|
|
13
|
+
* 前往的路由路径
|
|
14
|
+
*/
|
|
15
|
+
to: RouterRawLocation;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 节点使用的标签名
|
|
19
|
+
* @default 'a'
|
|
20
|
+
*/
|
|
21
|
+
tag: string;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 调用 router.replace 以替换 router.push。
|
|
25
|
+
* @default false
|
|
26
|
+
*/
|
|
27
|
+
replace: boolean;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 路径激活匹配规则
|
|
31
|
+
* @example include => 路径包含即激活.
|
|
32
|
+
* 如: 当前路由为/en/news/list 此时router-link 的路径为 /en/news 也会激活
|
|
33
|
+
* @example route => 路由匹配才会激活,需要匹配的路由树一致.
|
|
34
|
+
* 如: 当前路由为/en/news/list/123 此时router-link 的路径为 /en/news/list 也会激活
|
|
35
|
+
* @example exact => 路径全匹配才会激活,不仅需要匹配路由树一致,还需要参数匹配才会激活.
|
|
36
|
+
* 如: 当前路由为/en/news/list/123 此时router-link 的路径为 /en/news/list/123 才会激活,如果配置的路径为/en/news/list/123456 也不会激活
|
|
37
|
+
* @default 'include'
|
|
38
|
+
*/
|
|
39
|
+
exact: 'include' | 'route' | 'exact';
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 是否为相对路径
|
|
43
|
+
* 按照 Hanson 要求目前都是绝对路径,因此废弃此属性
|
|
44
|
+
* @default false
|
|
45
|
+
*/
|
|
46
|
+
// append?: boolean;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 路由激活时的class
|
|
50
|
+
* @default 'router-link-active'
|
|
51
|
+
*/
|
|
52
|
+
activeClass: string;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 哪些事件触发路由跳转
|
|
56
|
+
* @default 'click'
|
|
57
|
+
*/
|
|
58
|
+
event: string | string[];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const RouterLink = defineComponent({
|
|
62
|
+
props: {
|
|
63
|
+
to: {
|
|
64
|
+
type: [String, Object] as PropType<RouterLinkProps['to']>,
|
|
65
|
+
required: true
|
|
66
|
+
},
|
|
67
|
+
tag: {
|
|
68
|
+
type: String as PropType<RouterLinkProps['tag']>,
|
|
69
|
+
default: 'a'
|
|
70
|
+
},
|
|
71
|
+
replace: {
|
|
72
|
+
type: Boolean as PropType<RouterLinkProps['replace']>,
|
|
73
|
+
default: false
|
|
74
|
+
},
|
|
75
|
+
exact: {
|
|
76
|
+
type: String as PropType<RouterLinkProps['exact']>,
|
|
77
|
+
default: 'include'
|
|
78
|
+
},
|
|
79
|
+
// append: {
|
|
80
|
+
// type: Boolean as PropType<boolean>,
|
|
81
|
+
// default: false
|
|
82
|
+
// },
|
|
83
|
+
activeClass: {
|
|
84
|
+
type: String as PropType<RouterLinkProps['activeClass']>,
|
|
85
|
+
default: 'router-link-active'
|
|
86
|
+
},
|
|
87
|
+
event: {
|
|
88
|
+
type: String as PropType<RouterLinkProps['event']>,
|
|
89
|
+
default: 'click'
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
render(props: RouterLinkProps) {
|
|
93
|
+
const { to, tag, replace, exact, activeClass, event } = props;
|
|
94
|
+
const router = useRouter();
|
|
95
|
+
const current = useRoute();
|
|
96
|
+
const resolveRoute = router.resolve(to);
|
|
97
|
+
|
|
98
|
+
/* 匹配函数 */
|
|
99
|
+
let compare: (current: RouteRecord, route: RouteRecord) => Boolean;
|
|
100
|
+
switch (exact) {
|
|
101
|
+
/* 路由级匹配 */
|
|
102
|
+
case 'route':
|
|
103
|
+
compare = isSameRoute;
|
|
104
|
+
break;
|
|
105
|
+
|
|
106
|
+
/* 全匹配 */
|
|
107
|
+
case 'exact':
|
|
108
|
+
compare = isEqualRoute;
|
|
109
|
+
break;
|
|
110
|
+
|
|
111
|
+
/* 是否包含 */
|
|
112
|
+
case 'include':
|
|
113
|
+
default:
|
|
114
|
+
compare = (current: RouteRecord, route: RouteRecord) => {
|
|
115
|
+
return current.fullPath.startsWith(route.fullPath);
|
|
116
|
+
};
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/* 根据路由是否匹配获取高亮 */
|
|
121
|
+
const active = compare(current, resolveRoute);
|
|
122
|
+
|
|
123
|
+
/* 事件处理函数 */
|
|
124
|
+
const handler = (e: MouseEvent) => {
|
|
125
|
+
if (guardEvent(e)) {
|
|
126
|
+
router[replace ? 'replace' : 'push'](to);
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
/* 可触发事件 map */
|
|
131
|
+
const on: Record<string, Function | Function[]> = {};
|
|
132
|
+
const eventTypeList = getEventTypeList(event);
|
|
133
|
+
eventTypeList.forEach((eventName) => {
|
|
134
|
+
on[`on${eventName.toLocaleLowerCase()}`] = handler;
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
return h(
|
|
138
|
+
tag,
|
|
139
|
+
{
|
|
140
|
+
class: ['router-link', active ? [activeClass] : ''],
|
|
141
|
+
href: resolveRoute.fullPath,
|
|
142
|
+
...on
|
|
143
|
+
},
|
|
144
|
+
this.$slots
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
function getEventTypeList(eventType: string | string[]): string[] {
|
|
150
|
+
if (eventType instanceof Array) {
|
|
151
|
+
if (eventType.length > 0) {
|
|
152
|
+
return eventType;
|
|
153
|
+
}
|
|
154
|
+
return ['click'];
|
|
155
|
+
}
|
|
156
|
+
return [eventType || 'click'];
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function guardEvent(e: MouseEvent) {
|
|
160
|
+
// don't redirect with control keys
|
|
161
|
+
if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) return;
|
|
162
|
+
// don't redirect when preventDefault called
|
|
163
|
+
if (e.defaultPrevented) return;
|
|
164
|
+
// don't redirect on right click
|
|
165
|
+
if (e.button !== undefined && e.button !== 0) return;
|
|
166
|
+
// don't redirect if `target="_blank"`
|
|
167
|
+
// @ts-expect-error getAttribute does exist
|
|
168
|
+
if (e.currentTarget?.getAttribute) {
|
|
169
|
+
// @ts-expect-error getAttribute exists
|
|
170
|
+
const target = e.currentTarget.getAttribute('target');
|
|
171
|
+
if (/\b_blank\b/i.test(target)) return;
|
|
172
|
+
}
|
|
173
|
+
// this may be a Weex event which doesn't have this method
|
|
174
|
+
if (e.preventDefault) e.preventDefault();
|
|
175
|
+
|
|
176
|
+
return true;
|
|
177
|
+
}
|
package/src/plugin.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Route, RouterInstance } from '@esmx/router';
|
|
2
|
+
import { type App, type ShallowReactive, shallowReactive, unref } from 'vue';
|
|
3
|
+
|
|
4
|
+
import { RouterLink } from './link';
|
|
5
|
+
import { routerKey, routerViewLocationKey } from './symbols';
|
|
6
|
+
import { RouterView } from './view';
|
|
7
|
+
|
|
8
|
+
declare module '@vue/runtime-core' {
|
|
9
|
+
interface ComponentCustomProperties {
|
|
10
|
+
// $route: Route;
|
|
11
|
+
$route: ShallowReactive<Route>;
|
|
12
|
+
$router: RouterInstance;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface GlobalComponents {
|
|
16
|
+
// RouterView:
|
|
17
|
+
// RouterLink:
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function RouterVuePlugin(router: RouterInstance) {
|
|
22
|
+
return function install(app: App) {
|
|
23
|
+
const route = shallowReactive(router.route);
|
|
24
|
+
router.route = route;
|
|
25
|
+
|
|
26
|
+
app.config.globalProperties.$router = router;
|
|
27
|
+
app.config.globalProperties.$route = router.route;
|
|
28
|
+
|
|
29
|
+
app.provide(routerKey, unref(router));
|
|
30
|
+
app.provide(routerViewLocationKey, unref(router.route));
|
|
31
|
+
|
|
32
|
+
// 注册组件
|
|
33
|
+
app.component('router-view', RouterView);
|
|
34
|
+
app.component('router-link', RouterLink);
|
|
35
|
+
};
|
|
36
|
+
}
|
package/src/symbols.ts
ADDED
package/src/use.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Route, RouterInstance } from '@esmx/router';
|
|
2
|
+
import { getCurrentInstance, inject } from 'vue';
|
|
3
|
+
|
|
4
|
+
import { routerKey, routerViewLocationKey } from './symbols';
|
|
5
|
+
|
|
6
|
+
export function throwNoCurrentInstance(method: string) {
|
|
7
|
+
if (!getCurrentInstance()) {
|
|
8
|
+
throw new Error(
|
|
9
|
+
`[router-vue]: Missing current instance. ${method}() must be called inside <script setup> or setup().`
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function useRouter(): RouterInstance {
|
|
15
|
+
return inject(routerKey)!;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function useRoute(): Route {
|
|
19
|
+
return inject(routerViewLocationKey)!;
|
|
20
|
+
}
|
package/src/view.ts
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { Route, RouteConfig } from '@esmx/router';
|
|
2
|
+
import {
|
|
3
|
+
type ComputedRef,
|
|
4
|
+
type PropType,
|
|
5
|
+
type ShallowReactive,
|
|
6
|
+
type Slot,
|
|
7
|
+
computed,
|
|
8
|
+
defineComponent,
|
|
9
|
+
getCurrentInstance,
|
|
10
|
+
h,
|
|
11
|
+
inject,
|
|
12
|
+
provide,
|
|
13
|
+
shallowReactive
|
|
14
|
+
} from 'vue';
|
|
15
|
+
|
|
16
|
+
import { routerViewDepthKey, routerViewLocationKey } from './symbols';
|
|
17
|
+
|
|
18
|
+
export const RouterView = defineComponent({
|
|
19
|
+
name: 'RouterView',
|
|
20
|
+
inheritAttrs: true,
|
|
21
|
+
props: {
|
|
22
|
+
name: {
|
|
23
|
+
type: String as PropType<string>,
|
|
24
|
+
default: 'default'
|
|
25
|
+
},
|
|
26
|
+
route: Object as PropType<Route>
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
// Better compat for @vue/compat users
|
|
30
|
+
// https://github.com/vuejs/router/issues/1315
|
|
31
|
+
compatConfig: { MODE: 3 },
|
|
32
|
+
|
|
33
|
+
setup: (props, { attrs, slots }) => {
|
|
34
|
+
const instance = getCurrentInstance();
|
|
35
|
+
if (!instance) {
|
|
36
|
+
console.error('no current instance');
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const injectedRoute = inject<ShallowReactive<Route>>(
|
|
41
|
+
routerViewLocationKey
|
|
42
|
+
)!;
|
|
43
|
+
|
|
44
|
+
const routeToDisplay = computed<Route>(
|
|
45
|
+
() => props.route || injectedRoute
|
|
46
|
+
);
|
|
47
|
+
const injectedDepth = inject<ComputedRef<number>>(
|
|
48
|
+
routerViewDepthKey,
|
|
49
|
+
computed(() => 0)
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
const depth = computed<number>(() => {
|
|
53
|
+
let initialDepth = injectedDepth.value;
|
|
54
|
+
const { matched } = routeToDisplay.value;
|
|
55
|
+
let matchedRoute: RouteConfig | undefined;
|
|
56
|
+
while (
|
|
57
|
+
(matchedRoute = matched[initialDepth]) &&
|
|
58
|
+
!matchedRoute?.component
|
|
59
|
+
) {
|
|
60
|
+
initialDepth++;
|
|
61
|
+
}
|
|
62
|
+
return initialDepth;
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
provide(routerViewLocationKey, shallowReactive(routeToDisplay.value));
|
|
66
|
+
provide(
|
|
67
|
+
routerViewDepthKey,
|
|
68
|
+
computed(() => depth.value + 1)
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
return () => {
|
|
72
|
+
const matchRoute = routeToDisplay.value.matched[depth.value];
|
|
73
|
+
if (!matchRoute) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const component = h(
|
|
78
|
+
matchRoute.component,
|
|
79
|
+
Object.assign({}, props, attrs)
|
|
80
|
+
);
|
|
81
|
+
return (
|
|
82
|
+
// pass the vnode to the slot as a prop.
|
|
83
|
+
// h and <component :is="..."> both accept vnodes
|
|
84
|
+
normalizeSlot(slots.default, { Component: component }) ||
|
|
85
|
+
component
|
|
86
|
+
);
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
function normalizeSlot(slot: Slot | undefined, data: any) {
|
|
92
|
+
if (!slot) return null;
|
|
93
|
+
const slotContent = slot(data);
|
|
94
|
+
return slotContent.length === 1 ? slotContent[0] : slotContent;
|
|
95
|
+
}
|