@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 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代表需要登录,只有最右边的一位代表登录,其他位可以自己定enum代表权限
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
  ```
@@ -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 };