@e7w/easy-routes 0.1.2 → 0.1.3
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 +3 -1
- package/dist/index.d.mts +26 -0
- package/dist/index.mjs +224 -0
- package/dist/style.css +838 -0
- package/package.json +28 -10
- package/tsconfig.json +2 -1
- package/dist/assets/index.css +0 -109
- package/dist/components/DefaultOutlet.d.ts +0 -2
- package/dist/components/DefaultOutlet.js +0 -3
- package/dist/components/ErrorBoundary.d.ts +0 -2
- package/dist/components/ErrorBoundary.js +0 -6
- package/dist/components/Loading.d.ts +0 -3
- package/dist/components/Loading.js +0 -12
- package/dist/components/ProgressLayout.d.ts +0 -3
- package/dist/components/ProgressLayout.js +0 -25
- package/dist/index.d.ts +0 -2
- package/dist/index.js +0 -2
- package/dist/models/system.d.ts +0 -7
- package/dist/models/system.js +0 -145
- package/dist/router.d.ts +0 -3
- package/dist/router.js +0 -153
- package/dist/types/index.d.ts +0 -23
- package/dist/types/index.js +0 -20
- package/dist/utils/hooks.d.ts +0 -1
- package/dist/utils/hooks.js +0 -15
package/README.md
CHANGED
|
@@ -21,6 +21,8 @@ export function Layout() {
|
|
|
21
21
|
src/\*\*/pages/\*\*/meta.ts配置如下
|
|
22
22
|
|
|
23
23
|
```ts
|
|
24
|
-
export const permission: number = 0b0; // 默认是0b0
|
|
24
|
+
export const permission: number = 0b0; // 默认是0b0代表不需要登录,只有最右边的一位代表登录,其他位可以自己定enum代表权限
|
|
25
25
|
export const loader: Loader = async (ctx, meta) => {}; // 可以动态修改ctx,ctx.data是返回给页面组件的值,ctx.title是页面的title,signed代表是否登录成功,noPermission代表是否没有权限
|
|
26
|
+
export const displayInMenu: string = "首页"; // 空字符串或者没有代表不展示在menu里
|
|
27
|
+
export const menuIcon: string = "xxx.svg"; // menu的icon
|
|
26
28
|
```
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import z from "zod";
|
|
2
|
+
import { Params, RouteObject } from "react-router";
|
|
3
|
+
|
|
4
|
+
//#region src/types/index.d.ts
|
|
5
|
+
interface Ctx {
|
|
6
|
+
title: string;
|
|
7
|
+
signed: boolean;
|
|
8
|
+
noPermission: boolean;
|
|
9
|
+
req: {
|
|
10
|
+
params: Params;
|
|
11
|
+
query: Params;
|
|
12
|
+
pathname: string;
|
|
13
|
+
};
|
|
14
|
+
data?: any;
|
|
15
|
+
}
|
|
16
|
+
declare abstract class Permission {
|
|
17
|
+
permission: number;
|
|
18
|
+
abstract defaultTitle: string;
|
|
19
|
+
abstract init(req: Ctx["req"]): Promise<void> | void;
|
|
20
|
+
}
|
|
21
|
+
declare const PermissionSchema: z.ZodType<Permission, Permission>;
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region src/router.d.ts
|
|
24
|
+
declare const routes: RouteObject[];
|
|
25
|
+
//#endregion
|
|
26
|
+
export { type Ctx, Permission, PermissionSchema, routes };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import z from "zod";
|
|
2
|
+
import { Navigate, Outlet, isRouteErrorResponse, redirect, useNavigation, useRouteError } from "react-router";
|
|
3
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
import { useEffect, useRef, useState } from "react";
|
|
5
|
+
import nProgress from "nprogress";
|
|
6
|
+
import "nprogress/nprogress.css";
|
|
7
|
+
import { inject, loader, provide, useLoader } from "@e7w/easy-model";
|
|
8
|
+
import './style.css';
|
|
9
|
+
//#region src/types/index.ts
|
|
10
|
+
var Permission = class {
|
|
11
|
+
permission = 0;
|
|
12
|
+
};
|
|
13
|
+
const PermissionSchema = z.object({
|
|
14
|
+
init: z.function({ output: z.union([z.promise(z.void()), z.void()]) }),
|
|
15
|
+
permission: z.number(),
|
|
16
|
+
defaultTitle: z.string()
|
|
17
|
+
});
|
|
18
|
+
//#endregion
|
|
19
|
+
//#region src/components/DefaultOutlet.tsx
|
|
20
|
+
const DefaultOutlet = () => /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(Outlet, {}) });
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region src/components/ErrorBoundary.tsx
|
|
23
|
+
const ErrorBoundary = () => {
|
|
24
|
+
const error = useRouteError();
|
|
25
|
+
return /* @__PURE__ */ jsx("div", {
|
|
26
|
+
className: "flex h-screen w-full items-center justify-center",
|
|
27
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
28
|
+
className: "flex flex-col items-center justify-center",
|
|
29
|
+
children: [
|
|
30
|
+
/* @__PURE__ */ jsx("h1", {
|
|
31
|
+
className: "text-2xl font-bold mb-4",
|
|
32
|
+
children: "Error"
|
|
33
|
+
}),
|
|
34
|
+
/* @__PURE__ */ jsx("p", { children: "Something went wrong." }),
|
|
35
|
+
isRouteErrorResponse(error) && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("p", {
|
|
36
|
+
className: "text-red-300",
|
|
37
|
+
children: [
|
|
38
|
+
error.status,
|
|
39
|
+
" ",
|
|
40
|
+
error.statusText
|
|
41
|
+
]
|
|
42
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
43
|
+
className: "text-red-300",
|
|
44
|
+
children: JSON.stringify(error.data)
|
|
45
|
+
})] }),
|
|
46
|
+
error instanceof Error && /* @__PURE__ */ jsx("p", {
|
|
47
|
+
className: "text-red-300",
|
|
48
|
+
children: error.message
|
|
49
|
+
})
|
|
50
|
+
]
|
|
51
|
+
})
|
|
52
|
+
});
|
|
53
|
+
};
|
|
54
|
+
//#endregion
|
|
55
|
+
//#region src/utils/hooks.ts
|
|
56
|
+
function useDebouncedValue(value) {
|
|
57
|
+
const [result, setResult] = useState(value);
|
|
58
|
+
const timer = useRef(void 0);
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
clearTimeout(timer.current);
|
|
61
|
+
timer.current = window.setTimeout(() => {
|
|
62
|
+
setResult(value);
|
|
63
|
+
}, 700);
|
|
64
|
+
return () => {
|
|
65
|
+
window.clearTimeout(timer.current);
|
|
66
|
+
};
|
|
67
|
+
}, [result, value]);
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
//#endregion
|
|
71
|
+
//#region src/components/Loading.tsx
|
|
72
|
+
const Loading = () => {
|
|
73
|
+
const { isGlobalLoading } = useLoader();
|
|
74
|
+
if (!useDebouncedValue(isGlobalLoading)) return;
|
|
75
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
76
|
+
className: "spinner-box",
|
|
77
|
+
children: [
|
|
78
|
+
/* @__PURE__ */ jsx("div", { className: "blue-orbit leo" }),
|
|
79
|
+
/* @__PURE__ */ jsx("div", { className: "green-orbit leo" }),
|
|
80
|
+
/* @__PURE__ */ jsx("div", { className: "red-orbit leo" }),
|
|
81
|
+
/* @__PURE__ */ jsx("div", { className: "white-orbit w1 leo" }),
|
|
82
|
+
/* @__PURE__ */ jsx("div", { className: "white-orbit w2 leo" }),
|
|
83
|
+
/* @__PURE__ */ jsx("div", { className: "white-orbit w3 leo" })
|
|
84
|
+
]
|
|
85
|
+
});
|
|
86
|
+
};
|
|
87
|
+
//#endregion
|
|
88
|
+
//#region src/components/ProgressLayout.tsx
|
|
89
|
+
nProgress.configure({
|
|
90
|
+
showSpinner: false,
|
|
91
|
+
speed: 500,
|
|
92
|
+
minimum: .1,
|
|
93
|
+
easing: "ease",
|
|
94
|
+
trickleSpeed: 200
|
|
95
|
+
});
|
|
96
|
+
const ProgressLayout = () => {
|
|
97
|
+
const { state } = useNavigation();
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
if (state === "loading") nProgress.start();
|
|
100
|
+
else nProgress.done();
|
|
101
|
+
}, [state]);
|
|
102
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Outlet, {}), /* @__PURE__ */ jsx(Loading, {})] });
|
|
103
|
+
};
|
|
104
|
+
//#endregion
|
|
105
|
+
//#region src/models/system.ts
|
|
106
|
+
const system = provide(class System {
|
|
107
|
+
@inject(PermissionSchema) router;
|
|
108
|
+
@loader.once @loader.load(true) async init(req) {
|
|
109
|
+
await this.router?.init(req);
|
|
110
|
+
}
|
|
111
|
+
hasPermission(permission = 0) {
|
|
112
|
+
return ((this.router?.permission || 0) & permission) === permission;
|
|
113
|
+
}
|
|
114
|
+
getCtx(req, permission = 1) {
|
|
115
|
+
return {
|
|
116
|
+
title: this.router?.defaultTitle ?? document.title,
|
|
117
|
+
signed: (permission & 1) === 0 || this.hasPermission(1),
|
|
118
|
+
noPermission: !this.hasPermission(permission),
|
|
119
|
+
req
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
})();
|
|
123
|
+
//#endregion
|
|
124
|
+
//#region src/router.tsx
|
|
125
|
+
const pages = import.meta.glob("/src/**/pages/**/index.tsx", {
|
|
126
|
+
eager: true,
|
|
127
|
+
import: "Page"
|
|
128
|
+
});
|
|
129
|
+
const layouts = import.meta.glob("/src/**/pages/**/layout.tsx", {
|
|
130
|
+
eager: true,
|
|
131
|
+
import: "Layout"
|
|
132
|
+
});
|
|
133
|
+
const metas = import.meta.glob("/src/**/pages/**/meta.ts", { eager: true });
|
|
134
|
+
const menuObj = { "/": {
|
|
135
|
+
name: "首页",
|
|
136
|
+
path: "/"
|
|
137
|
+
} };
|
|
138
|
+
const routes = [];
|
|
139
|
+
const obj = {};
|
|
140
|
+
const filePaths = new Set(Object.keys({
|
|
141
|
+
...pages,
|
|
142
|
+
...layouts
|
|
143
|
+
}).map((p) => p.replace(/\/(index|layout)\.tsx$/, "")));
|
|
144
|
+
filePaths.forEach((filePath) => {
|
|
145
|
+
const Page = pages[filePath + "/index.tsx"];
|
|
146
|
+
const Layout = layouts[filePath + "/layout.tsx"] || DefaultOutlet;
|
|
147
|
+
const meta = metas[filePath + "/meta.ts"] || {};
|
|
148
|
+
const path = filePath.replace(/.*\/pages/, "").replace(/\[([^\]]+)\]/, ":$1") || "/";
|
|
149
|
+
const hasPage = Boolean(Page);
|
|
150
|
+
obj[path] = {
|
|
151
|
+
path,
|
|
152
|
+
element: /* @__PURE__ */ jsx(Layout, {}),
|
|
153
|
+
children: [{
|
|
154
|
+
element: hasPage ? /* @__PURE__ */ jsx(Page, {}) : /* @__PURE__ */ jsx(Navigate, {
|
|
155
|
+
to: "/",
|
|
156
|
+
replace: true
|
|
157
|
+
}),
|
|
158
|
+
index: true,
|
|
159
|
+
loader: hasPage ? async ({ params, request }) => {
|
|
160
|
+
const url = new URL(request.url);
|
|
161
|
+
const req = {
|
|
162
|
+
params,
|
|
163
|
+
query: Object.fromEntries(url.searchParams),
|
|
164
|
+
pathname: url.pathname.replace(/\/?$/, "/")
|
|
165
|
+
};
|
|
166
|
+
await system.init(req)?.catch?.(() => {});
|
|
167
|
+
const ctx = system.getCtx(req, meta.permission);
|
|
168
|
+
await meta.loader?.(ctx)?.catch?.(() => {});
|
|
169
|
+
if (!ctx.signed) return redirect(`/login?from=${encodeURIComponent(request.url)}`);
|
|
170
|
+
if (ctx.noPermission) return redirect("/403");
|
|
171
|
+
document.title = ctx.title;
|
|
172
|
+
return ctx.data;
|
|
173
|
+
} : void 0
|
|
174
|
+
}]
|
|
175
|
+
};
|
|
176
|
+
if (meta.displayInMenu) menuObj[path] = {
|
|
177
|
+
name: meta.displayInMenu,
|
|
178
|
+
path,
|
|
179
|
+
icon: meta.menuIcon
|
|
180
|
+
};
|
|
181
|
+
});
|
|
182
|
+
filePaths.forEach((filePath) => {
|
|
183
|
+
const path = filePath.replace(/.*\/pages/, "").replace(/\[([^\]]+)\]/, ":$1") || "/";
|
|
184
|
+
if (path === "/") return;
|
|
185
|
+
const currentObj = obj[path];
|
|
186
|
+
const currentMenu = menuObj[path];
|
|
187
|
+
const segments = path.split("/").filter(Boolean);
|
|
188
|
+
const paths = ["/"];
|
|
189
|
+
let finished1 = false;
|
|
190
|
+
let finished2 = !currentMenu;
|
|
191
|
+
segments.reduce((acc, curr) => {
|
|
192
|
+
const newPath = acc + "/" + curr;
|
|
193
|
+
if (newPath !== path) paths.unshift(newPath);
|
|
194
|
+
return newPath;
|
|
195
|
+
}, "");
|
|
196
|
+
for (const p of paths) {
|
|
197
|
+
if (!finished1 && obj[p]) {
|
|
198
|
+
obj[p].children.push(currentObj);
|
|
199
|
+
finished1 = true;
|
|
200
|
+
}
|
|
201
|
+
if (!finished2 && menuObj[p]) {
|
|
202
|
+
menuObj[p].children ??= [];
|
|
203
|
+
menuObj[p].children.push(currentMenu);
|
|
204
|
+
finished2 = true;
|
|
205
|
+
}
|
|
206
|
+
if (finished1 && finished2) break;
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
obj["/"].errorElement = /* @__PURE__ */ jsx(ErrorBoundary, {});
|
|
210
|
+
routes.push({
|
|
211
|
+
path: "/",
|
|
212
|
+
element: /* @__PURE__ */ jsx(ProgressLayout, {}),
|
|
213
|
+
children: [obj["/"]]
|
|
214
|
+
});
|
|
215
|
+
routes.push({
|
|
216
|
+
path: "*",
|
|
217
|
+
element: /* @__PURE__ */ jsx(Navigate, {
|
|
218
|
+
to: obj["/404"] ? "/404" : "/",
|
|
219
|
+
replace: true
|
|
220
|
+
})
|
|
221
|
+
});
|
|
222
|
+
menuObj["/"];
|
|
223
|
+
//#endregion
|
|
224
|
+
export { Permission, PermissionSchema, routes };
|