目标
交付打开时不带 ERP 侧栏 / 管理外壳的页面——厨房看板、二维码菜单、客户门户等非标准管理页。
- 通过 AppRegistry.rootRoute.add() 使用空白布局
- 路径始终以 /${metadata.name} 为前缀
- 可选多页门户:一条 /* shell + 嵌套 Routes + React.lazy
- 公开客户 ≠ Quan ERP 管理员——鉴权分离
1. 前置条件
需要已有可运行的 frontend register(),以及能打开公开 URL 的 base 栈。
- plugins/<name>/ 下有 frontend/src/index.tsx
- metadata.name 与目录名一致
- 插件已构建/watch 并安装,register() 会执行
- 明确是单页还是多页门户
2. 注册单个公开页
一次性屏幕在 register() 中调用 rootRoute.add(),传入命名空间 path 与 element。空白布局不要用 AppRegistry.route.add()。
- path — `/${metadata.name}/…` 或 `/${metadata.name}/:id`
- element — 页面组件(可用 @quan-erp/shared-ui)
- 公开页不要 menu.add()——没有 ERP 菜单外壳
TSXfrontend/src/index.tsx
1import type { AppRegistryState, PluginModule } from "@quan-erp/shared-types";
2import metadata from "../module.metadata.json" with { type: "json" };
3import { FoodKitchenBoard } from "./page/kitchen/kitchen.page";
4import { FoodPublicMenu } from "./page/public-menu/public-menu.page";
5
6const Plugin: PluginModule = {
7 register(AppRegistry: AppRegistryState) {
8 AppRegistry.rootRoute.add({
9 path: `/${metadata.name}/kitchen`,
10 element: <FoodKitchenBoard />,
11 });
12
13 // Dynamic segment
14 AppRegistry.rootRoute.add({
15 path: `/${metadata.name}/:id`,
16 element: <FoodPublicMenu />,
17 });
18 },
19};
20
21export default Plugin;3. 嵌套公开门户(多页)
登录 / 首页 / 注册等流程:注册一条 `/${metadata.name}/*` shell,并在内部嵌套 react-router-dom Routes。每个子页用 React.lazy 加载。
- 整个门户只需一条 rootRoute
- React.lazy + Suspense + LoadingState
- 子路径相对 splat(login → /${metadata.name}/login)
TSfrontend/src/index.tsx — shell
AppRegistry.rootRoute.add({
path: `/${metadata.name}/*`,
element: <PublicShellPage />,
});TSXfrontend/src/page/public/public-shell.page.tsx
1import { LoadingState } from "@quan-erp/shared-ui";
2import { lazy, Suspense } from "react";
3import { Navigate, Route, Routes } from "react-router-dom";
4
5const LoginPage = lazy(() =>
6 import("./login/login.page").then((m) => ({ default: m.LoginPage })),
7);
8const SignupPage = lazy(() =>
9 import("./signup/signup.page").then((m) => ({ default: m.SignupPage })),
10);
11const HomePage = lazy(() =>
12 import("./home/home.page").then((m) => ({ default: m.HomePage })),
13);
14
15const HOME_PATH = "home";
16
17export function PublicShellPage() {
18 return (
19 <Suspense fallback={<LoadingState />}>
20 <Routes>
21 <Route index element={<Navigate replace to={HOME_PATH} />} />
22 <Route path="login" element={<LoginPage />} />
23 <Route path="register" element={<SignupPage />} />
24 <Route path="home" element={<HomePage />} />
25 <Route path="*" element={<Navigate replace to={HOME_PATH} />} />
26 </Routes>
27 </Suspense>
28 );
29}4. 公开客户 ≠ 管理员鉴权
外部客户不是 Quan ERP 用户。不要复用核心管理员登录、base axios 或 localStorage token。
- 独立 public axios,withCredentials: true
- 后端以 ${metadata.name} 前缀的 httpOnly cookie 管理会话
- 以 cookie 认证的 API 成功与否做门禁(401 → 登录)
- 可选内存 Zustand 资料 store——不要持久化 token
5. 验证
watch + 安装(或热更新)后,在浏览器打开公开 URL。
- 单页 — 打开 /${metadata.name}/kitchen;无 ERP 侧栏
- 门户 — 打开 /${metadata.name}/login 并导航到 home;嵌套路由正常
- 刚改 register() 时强制刷新
- Network:公开 API 使用插件 cookie,而非管理员会话
常见错误
- 用了 AppRegistry.route.add() — 仍显示 ERP 外壳
- 路径缺少 /${metadata.name} — 与其他插件冲突
- 多页门户 shell 缺少 /* — 嵌套路由 404
- 门户页全部 eager import — 登录包过大
- 在公开客户页假设管理员已登录