提交 1aa4010f authored 作者: 王鹏飞's avatar 王鹏飞

refactor(admin): add domain route boundaries

上级 a44bdccb
......@@ -15,9 +15,14 @@ function ErrorFallback({
title="页面加载失败"
subTitle={error?.message || "未知错误"}
extra={
<>
<Button type="primary" onClick={resetErrorBoundary}>
重试
</Button>
<Button onClick={() => window.location.assign(import.meta.env.BASE_URL)}>
返回入口
</Button>
</>
}
/>
);
......@@ -25,7 +30,10 @@ function ErrorFallback({
export default function RouteErrorBoundary({ children }: { children: ReactNode }) {
return (
<ErrorBoundary FallbackComponent={ErrorFallback} onReset={() => {}}>
<ErrorBoundary
FallbackComponent={ErrorFallback}
onReset={() => window.location.reload()}
>
{children}
</ErrorBoundary>
);
......
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import test from "node:test";
const appRoutesPath = new URL("./routes.tsx", import.meta.url);
const domains = [
"books",
"members",
"advertisements",
"settings",
"access",
"teachers",
"auth",
"account",
"editor",
"student-book",
];
test("root routes import domain route modules without importing page views", async () => {
const source = await readFile(appRoutesPath, "utf8");
for (const domain of domains) {
assert.match(source, new RegExp(`import\\(\"@/modules/${domain}/routes\"\\)`));
}
assert.doesNotMatch(source, /@\/pages\//);
assert.doesNotMatch(source, /modules\/books\/management\/views/);
});
test("every top-level domain owns a routes.tsx file", async () => {
await Promise.all(
domains.map(async (domain) => {
const routeFile = new URL(`../modules/${domain}/routes.tsx`, import.meta.url);
const source = await readFile(routeFile, "utf8");
assert.match(source, /export default function \w+Routes/);
}),
);
});
test("root route declarations contain only top-level entry paths", async () => {
const source = await readFile(appRoutesPath, "utf8");
const declaredPaths = [...source.matchAll(/\bpath:\s*\"([^\"]+)\"/g)].map(
([, path]) => path,
);
const expectedPaths = [
"/",
"books/*",
"member/*",
"advertisement/*",
"setting/*",
"jurisdiction/*",
"teacher/*",
"login/*",
"userinfo/*",
"editor/*",
"student/book/*",
"*",
];
assert.deepEqual(declaredPaths.toSorted(), expectedPaths.toSorted());
});
test("route boundaries provide suspense, reload retry, and an app entry action", async () => {
const [moduleBoundary, errorBoundary] = await Promise.all([
readFile(new URL("./ModuleBoundary.tsx", import.meta.url), "utf8"),
readFile(new URL("./RouteErrorBoundary.tsx", import.meta.url), "utf8"),
]);
assert.match(moduleBoundary, /<Suspense/);
assert.match(moduleBoundary, /<RouteErrorBoundary>/);
assert.match(errorBoundary, /window\.location\.reload\(\)/);
assert.match(errorBoundary, /window\.location\.assign\(import\.meta\.env\.BASE_URL\)/);
});
import { lazy } from "react";
import { Navigate } from "react-router";
import ModuleBoundary from "./ModuleBoundary";
import Layout from "@/layout/index";
import ModuleBoundary from "./ModuleBoundary";
const BooksRoutes = lazy(() => import("@/modules/books/routes"));
const MembersRoutes = lazy(() => import("@/modules/members/routes"));
const AdvertisementsRoutes = lazy(() => import("@/modules/advertisements/routes"));
const SettingsRoutes = lazy(() => import("@/modules/settings/routes"));
const AccessRoutes = lazy(() => import("@/modules/access/routes"));
const TeachersRoutes = lazy(() => import("@/modules/teachers/routes"));
const AuthRoutes = lazy(() => import("@/modules/auth/routes"));
const AccountRoutes = lazy(() => import("@/modules/account/routes"));
const EditorRoutes = lazy(() => import("@/modules/editor/routes"));
const StudentBookRoutes = lazy(() => import("@/modules/student-book/routes"));
const baseRouter = [
{
......@@ -13,169 +24,18 @@ const baseRouter = [
),
children: [
{ index: true, element: <Navigate to="/books/management" /> },
{
path: "books",
Component: lazy(() => import("@/pages/books/index")),
children: [
{ index: true, element: <Navigate to="management" /> },
{
path: "management",
Component: lazy(() => import("@/pages/books/management/index")),
children: [
{ index: true, Component: lazy(() => import("@/pages/books/management/list/index")) },
{
path: "list",
Component: lazy(() => import("@/pages/books/management/list/index")),
},
{
path: "add-edit",
Component: lazy(() => import("@/pages/books/management/addedit/index")),
},
{ path: "chapter", Component: lazy(() => import("@/pages/books/section/index")) },
],
},
{
path: "audit",
Component: lazy(() => import("@/pages/books/audit/index")),
children: [
{ index: true, Component: lazy(() => import("@/pages/books/audit/table")) },
{ path: "dataset", Component: lazy(() => import("@/pages/books/audit/table")) },
{ path: "detail", Component: lazy(() => import("@/pages/books/audit/detail")) },
],
},
{ path: "classify", Component: lazy(() => import("@/pages/books/classify/index")) },
{ path: "label", Component: lazy(() => import("@/pages/books/label/index")) },
{
path: "sale",
Component: lazy(() => import("@/pages/books/sale/index")),
children: [
{ index: true, Component: lazy(() => import("@/pages/books/sale/table")) },
{ path: "dataset", Component: lazy(() => import("@/pages/books/sale/table")) },
{ path: "detail", Component: lazy(() => import("@/pages/books/sale/edit")) },
{
path: "discussP",
Component: lazy(() => import("@/pages/books/sale/discussList/index")),
},
{
path: "discuss-detail",
Component: lazy(() => import("@/pages/books/sale/discussDetail/index")),
},
],
},
{
path: "question-bank",
Component: lazy(() => import("@/pages/books/question-bank/index")),
},
{ path: "discussion", Component: lazy(() => import("@/pages/books/discussion/index")) },
{
path: "order-management",
Component: lazy(() => import("@/pages/books/order-management/index")),
children: [
{
index: true,
Component: lazy(() => import("@/pages/books/order-management/tab/table")),
},
{
path: "dataset",
Component: lazy(() => import("@/pages/books/order-management/tab/table")),
},
{
path: "sale-statistics",
Component: lazy(() => import("@/pages/books/order-management/tab/salesStatistics")),
},
],
},
{ path: "design", Component: lazy(() => import("@/pages/books/design/index")) },
],
},
{
path: "member",
Component: lazy(() => import("@/pages/member/index")),
children: [
{ index: true, element: <Navigate to="/member/list" /> },
{
path: "list",
Component: lazy(() => import("@/pages/member/list/index")),
children: [
{ index: true, Component: lazy(() => import("@/pages/member/list/list")) },
{ path: "query", Component: lazy(() => import("@/pages/member/list/list")) },
{ path: "detail", Component: lazy(() => import("@/pages/member/detail/index")) },
],
},
{ path: "userlevel", Component: lazy(() => import("@/pages/member/userlevel/index")) },
],
},
{
path: "advertisement",
Component: lazy(() => import("@/pages/advertisement/index")),
children: [
{ index: true, element: <Navigate to="/advertisement/advert" /> },
{ path: "advert", Component: lazy(() => import("@/pages/advertisement/advert/index")) },
{ path: "adsense", Component: lazy(() => import("@/pages/advertisement/adsense/index")) },
],
},
{
path: "setting",
Component: lazy(() => import("@/pages/setting/index")),
children: [
{ index: true, element: <Navigate to="/setting/applied" /> },
{
path: "help",
Component: lazy(() => import("@/pages/setting/help/index")),
children: [
{ index: true, element: <Navigate to="/setting/help/list" /> },
{ path: "list", Component: lazy(() => import("@/pages/setting/help/table")) },
{
path: "addedit",
Component: lazy(() => import("@/pages/setting/help/addedit/index")),
},
],
},
{ path: "applied", Component: lazy(() => import("@/pages/setting/applied/index")) },
{
path: "personalized-setting",
Component: lazy(() => import("@/pages/setting/personalized-setting/index")),
},
{
path: "point-management",
Component: lazy(() => import("@/pages/setting/point-management/index")),
},
{
path: "app-version",
Component: lazy(() => import("@/pages/setting/app-version/index")),
},
{ path: "coupon", Component: lazy(() => import("@/pages/setting/coupon/index")) },
{ path: "sensitive", Component: lazy(() => import("@/pages/setting/sensitive/index")) },
{ path: "feedback", Component: lazy(() => import("@/pages/setting/feedback/index")) },
],
},
{
path: "jurisdiction",
Component: lazy(() => import("@/pages/jurisdiction/index")),
children: [
{ index: true, element: <Navigate to="/jurisdiction/role" /> },
{ path: "admin", Component: lazy(() => import("@/pages/jurisdiction/admin/index")) },
{ path: "role", Component: lazy(() => import("@/pages/jurisdiction/role/index")) },
],
},
{
path: "teacher",
Component: lazy(() => import("@/pages/teacher/index")),
children: [
{ index: true, element: <Navigate to="/teacher/list" /> },
{ path: "list", Component: lazy(() => import("@/pages/teacher/query/index")) },
],
},
{ path: "userinfo", Component: lazy(() => import("@/pages/user-module/userInfo")) },
{ path: "editor", Component: lazy(() => import("@/pages/editor/index")) },
],
},
{
path: "login",
Component: lazy(() => import("@/pages/user-module")),
children: [{ index: true, Component: lazy(() => import("@/pages/user-module/login")) }],
},
{ path: "student/book", Component: lazy(() => import("@/pages/student/book/index")) },
{ path: "books/*", Component: BooksRoutes },
{ path: "member/*", Component: MembersRoutes },
{ path: "advertisement/*", Component: AdvertisementsRoutes },
{ path: "setting/*", Component: SettingsRoutes },
{ path: "jurisdiction/*", Component: AccessRoutes },
{ path: "teacher/*", Component: TeachersRoutes },
{ path: "userinfo/*", Component: AccountRoutes },
{ path: "editor/*", Component: EditorRoutes },
],
},
{ path: "login/*", Component: AuthRoutes },
{ path: "student/book/*", Component: StudentBookRoutes },
{ path: "*", element: <Navigate to="/" /> },
];
......
import { lazy } from "react";
import { Navigate, useRoutes } from "react-router";
import ModuleBoundary from "@/app/ModuleBoundary";
const AccessLayout = lazy(() => import("@/pages/jurisdiction/index"));
const Admin = lazy(() => import("@/pages/jurisdiction/admin/index"));
const Role = lazy(() => import("@/pages/jurisdiction/role/index"));
export default function AccessRoutes() {
const routes = useRoutes([
{
Component: AccessLayout,
children: [
{ index: true, element: <Navigate to="/jurisdiction/role" /> },
{ path: "admin", Component: Admin },
{ path: "role", Component: Role },
],
},
{ path: "*", element: <Navigate to="/" /> },
]);
return <ModuleBoundary>{routes}</ModuleBoundary>;
}
import { lazy } from "react";
import { Navigate, useRoutes } from "react-router";
import ModuleBoundary from "@/app/ModuleBoundary";
const UserInfo = lazy(() => import("@/pages/user-module/userInfo"));
export default function AccountRoutes() {
const routes = useRoutes([
{ index: true, Component: UserInfo },
{ path: "*", element: <Navigate to="/" /> },
]);
return <ModuleBoundary>{routes}</ModuleBoundary>;
}
import { lazy } from "react";
import { Navigate, useRoutes } from "react-router";
import ModuleBoundary from "@/app/ModuleBoundary";
const AdvertisementsLayout = lazy(() => import("@/pages/advertisement/index"));
const Advert = lazy(() => import("@/pages/advertisement/advert/index"));
const Adsense = lazy(() => import("@/pages/advertisement/adsense/index"));
export default function AdvertisementsRoutes() {
const routes = useRoutes([
{
Component: AdvertisementsLayout,
children: [
{ index: true, element: <Navigate to="/advertisement/advert" /> },
{ path: "advert", Component: Advert },
{ path: "adsense", Component: Adsense },
],
},
{ path: "*", element: <Navigate to="/" /> },
]);
return <ModuleBoundary>{routes}</ModuleBoundary>;
}
import { lazy } from "react";
import { Navigate, useRoutes } from "react-router";
import ModuleBoundary from "@/app/ModuleBoundary";
const AuthLayout = lazy(() => import("@/pages/user-module"));
const Login = lazy(() => import("@/pages/user-module/login"));
export default function AuthRoutes() {
const routes = useRoutes([
{
Component: AuthLayout,
children: [{ index: true, Component: Login }],
},
{ path: "*", element: <Navigate to="/" /> },
]);
return <ModuleBoundary>{routes}</ModuleBoundary>;
}
import { lazy } from "react";
import { Navigate, useRoutes } from "react-router";
import ModuleBoundary from "@/app/ModuleBoundary";
const BooksLayout = lazy(() => import("@/pages/books/index"));
const ManagementLayout = lazy(() => import("@/pages/books/management/index"));
const ManagementList = lazy(() => import("@/pages/books/management/list/index"));
const ManagementAddEdit = lazy(() => import("@/pages/books/management/addedit/index"));
const ChapterManagement = lazy(() => import("@/pages/books/section/index"));
const AuditLayout = lazy(() => import("@/pages/books/audit/index"));
const AuditTable = lazy(() => import("@/pages/books/audit/table"));
const AuditDetail = lazy(() => import("@/pages/books/audit/detail"));
const Classify = lazy(() => import("@/pages/books/classify/index"));
const Label = lazy(() => import("@/pages/books/label/index"));
const SaleLayout = lazy(() => import("@/pages/books/sale/index"));
const SaleTable = lazy(() => import("@/pages/books/sale/table"));
const SaleEdit = lazy(() => import("@/pages/books/sale/edit"));
const DiscussList = lazy(() => import("@/pages/books/sale/discussList/index"));
const DiscussDetail = lazy(() => import("@/pages/books/sale/discussDetail/index"));
const QuestionBank = lazy(() => import("@/pages/books/question-bank/index"));
const Discussion = lazy(() => import("@/pages/books/discussion/index"));
const OrderManagementLayout = lazy(() => import("@/pages/books/order-management/index"));
const OrderTable = lazy(() => import("@/pages/books/order-management/tab/table"));
const SaleStatistics = lazy(
() => import("@/pages/books/order-management/tab/salesStatistics"),
);
const Design = lazy(() => import("@/pages/books/design/index"));
export default function BooksRoutes() {
const routes = useRoutes([
{
Component: BooksLayout,
children: [
{ index: true, element: <Navigate to="management" /> },
{
path: "management",
Component: ManagementLayout,
children: [
{ index: true, Component: ManagementList },
{ path: "list", Component: ManagementList },
{ path: "add-edit", Component: ManagementAddEdit },
{ path: "chapter", Component: ChapterManagement },
],
},
{
path: "audit",
Component: AuditLayout,
children: [
{ index: true, Component: AuditTable },
{ path: "dataset", Component: AuditTable },
{ path: "detail", Component: AuditDetail },
],
},
{ path: "classify", Component: Classify },
{ path: "label", Component: Label },
{
path: "sale",
Component: SaleLayout,
children: [
{ index: true, Component: SaleTable },
{ path: "dataset", Component: SaleTable },
{ path: "detail", Component: SaleEdit },
{ path: "discussP", Component: DiscussList },
{ path: "discuss-detail", Component: DiscussDetail },
],
},
{ path: "question-bank", Component: QuestionBank },
{ path: "discussion", Component: Discussion },
{
path: "order-management",
Component: OrderManagementLayout,
children: [
{ index: true, Component: OrderTable },
{ path: "dataset", Component: OrderTable },
{ path: "sale-statistics", Component: SaleStatistics },
],
},
{ path: "design", Component: Design },
],
},
{ path: "*", element: <Navigate to="/" /> },
]);
return <ModuleBoundary>{routes}</ModuleBoundary>;
}
import { lazy, useState } from "react";
import { Navigate, useRoutes } from "react-router";
import ModuleBoundary from "@/app/ModuleBoundary";
const EditorPage = lazy(() => import("@/pages/editor/index"));
function EditorRouteView() {
const [value, setValue] = useState("");
return <EditorPage value={value} onChange={setValue} />;
}
export default function EditorRoutes() {
const routes = useRoutes([
{ index: true, Component: EditorRouteView },
{ path: "*", element: <Navigate to="/" /> },
]);
return <ModuleBoundary>{routes}</ModuleBoundary>;
}
import { lazy } from "react";
import { Navigate, useRoutes } from "react-router";
import ModuleBoundary from "@/app/ModuleBoundary";
const MembersLayout = lazy(() => import("@/pages/member/index"));
const MemberListLayout = lazy(() => import("@/pages/member/list/index"));
const MemberList = lazy(() => import("@/pages/member/list/list"));
const MemberDetail = lazy(() => import("@/pages/member/detail/index"));
const UserLevel = lazy(() => import("@/pages/member/userlevel/index"));
export default function MembersRoutes() {
const routes = useRoutes([
{
Component: MembersLayout,
children: [
{ index: true, element: <Navigate to="/member/list" /> },
{
path: "list",
Component: MemberListLayout,
children: [
{ index: true, Component: MemberList },
{ path: "query", Component: MemberList },
{ path: "detail", Component: MemberDetail },
],
},
{ path: "userlevel", Component: UserLevel },
],
},
{ path: "*", element: <Navigate to="/" /> },
]);
return <ModuleBoundary>{routes}</ModuleBoundary>;
}
import { lazy } from "react";
import { Navigate, useRoutes } from "react-router";
import ModuleBoundary from "@/app/ModuleBoundary";
const SettingsLayout = lazy(() => import("@/pages/setting/index"));
const HelpLayout = lazy(() => import("@/pages/setting/help/index"));
const HelpList = lazy(() => import("@/pages/setting/help/table"));
const HelpAddEdit = lazy(() => import("@/pages/setting/help/addedit/index"));
const Applied = lazy(() => import("@/pages/setting/applied/index"));
const PersonalizedSetting = lazy(
() => import("@/pages/setting/personalized-setting/index"),
);
const PointManagement = lazy(() => import("@/pages/setting/point-management/index"));
const AppVersion = lazy(() => import("@/pages/setting/app-version/index"));
const Coupon = lazy(() => import("@/pages/setting/coupon/index"));
const Sensitive = lazy(() => import("@/pages/setting/sensitive/index"));
const Feedback = lazy(() => import("@/pages/setting/feedback/index"));
export default function SettingsRoutes() {
const routes = useRoutes([
{
Component: SettingsLayout,
children: [
{ index: true, element: <Navigate to="/setting/applied" /> },
{
path: "help",
Component: HelpLayout,
children: [
{ index: true, element: <Navigate to="/setting/help/list" /> },
{ path: "list", Component: HelpList },
{ path: "addedit", Component: HelpAddEdit },
],
},
{ path: "applied", Component: Applied },
{ path: "personalized-setting", Component: PersonalizedSetting },
{ path: "point-management", Component: PointManagement },
{ path: "app-version", Component: AppVersion },
{ path: "coupon", Component: Coupon },
{ path: "sensitive", Component: Sensitive },
{ path: "feedback", Component: Feedback },
],
},
{ path: "*", element: <Navigate to="/" /> },
]);
return <ModuleBoundary>{routes}</ModuleBoundary>;
}
import { lazy } from "react";
import { Navigate, useRoutes } from "react-router";
import ModuleBoundary from "@/app/ModuleBoundary";
const StudentBook = lazy(() => import("@/pages/student/book/index"));
export default function StudentBookRoutes() {
const routes = useRoutes([
{ index: true, Component: StudentBook },
{ path: "*", element: <Navigate to="/" /> },
]);
return <ModuleBoundary>{routes}</ModuleBoundary>;
}
import { lazy } from "react";
import { Navigate, useRoutes } from "react-router";
import ModuleBoundary from "@/app/ModuleBoundary";
const TeachersLayout = lazy(() => import("@/pages/teacher/index"));
const TeacherList = lazy(() => import("@/pages/teacher/query/index"));
export default function TeachersRoutes() {
const routes = useRoutes([
{
Component: TeachersLayout,
children: [
{ index: true, element: <Navigate to="/teacher/list" /> },
{ path: "list", Component: TeacherList },
],
},
{ path: "*", element: <Navigate to="/" /> },
]);
return <ModuleBoundary>{routes}</ModuleBoundary>;
}
Markdown 格式
0% 或
您添加了 0 人 到此讨论。请谨慎行事。
请先完成此评论的编辑!
请 注册 或者 后发表评论