概述
Shell 拥有报表区。插件在 register() 中注册一个 page(通常是带嵌套 Routes 的 hub)。重聚合放在后端。
- AppRegistry.report.add({ pluginName, page })
- page 一般为 hub + 嵌套 Routes
- 子路径必须是相对路径(无前导 /)
- 用与报表查询相同的 API 做权限控制
1. 用 AppRegistry.report.add 注册
在 register() 中传入 { pluginName, page }。
- pluginName = metadata.name
- page 必须是 JSX
TSXfrontend/src/index.tsx
1import type { AppRegistryState, PluginModule } from "@quan-erp/shared-types";
2import metadata from "../module.metadata.json" with { type: "json" };
3import { MyModuleReports } from "./page/report";
4
5const Plugin: PluginModule = {
6 register(AppRegistry: AppRegistryState) {
7 AppRegistry.report.add({
8 pluginName: metadata.name,
9 page: <MyModuleReports />,
10 });
11 },
12};
13
14export default Plugin;2. 报表 hub + 嵌套路由
多个报表时做落地 hub,并用嵌套 Routes。子路径不要以 / 开头。
- 使用 react-router-dom 的 Routes / Route
- index = hub;子路由 = 各报表
- 文案必须本地化
TSXfrontend/src/page/report/index.tsx
1import { Route, Routes, useNavigate } from "react-router-dom";
2import { Page, PageContent, PageTitle } from "@quan-erp/shared-ui";
3import metadata from "../../../module.metadata.json" with { type: "json" };
4import { EmployeesReport } from "./employees-report";
5
6const reportList = [
7 {
8 id: "employees",
9 name: "Employees",
10 description: "Headcount and status breakdown",
11 path: "employees",
12 element: <EmployeesReport />,
13 },
14];
15
16export function MyModuleReports() {
17 const navigate = useNavigate();
18
19 return (
20 <Routes>
21 <Route
22 index
23 element={
24 <Page pluginName={metadata.name}>
25 <PageTitle>
26 <span>Module reports</span>
27 </PageTitle>
28 <PageContent>
29 <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mt-4">
30 {reportList.map((item) => (
31 <button
32 key={item.id}
33 type="button"
34 className="text-left border rounded-xl p-4 hover:shadow-md transition"
35 onClick={() => navigate(item.path)}
36 >
37 <div className="font-medium">{item.name}</div>
38 <div className="text-sm text-muted-foreground">
39 {item.description}
40 </div>
41 </button>
42 ))}
43 </div>
44 </PageContent>
45 </Page>
46 }
47 />
48 {reportList.map((item) => (
49 <Route key={item.path} path={item.path} element={item.element} />
50 ))}
51 </Routes>
52 );
53}3. 数据层
通过 withApiMetadataFetchFn + React Query 加载。重聚合放在后端。
- Declare report endpoints in .api.ts; consume via .queries.ts
- Pass requiredApis / Protected using the same API objects
- Prefer server-side aggregates over shipping huge raw tables to the browser
4. 表格
DataTable 不来自 shared-ui — 使用插件内本地 DataTable。
TSXlocal DataTable
import { DataTable } from "../../components/DataTable";
// columns + rows from your report query hook
<DataTable columns={columns} data={rows} />关键规则
- 只在 register() 中 report.add
- pluginName 必须等于 metadata.name
- 嵌套路径必须是相对路径
- 本地化 UI 文案
检查清单
- Create page/report hub with nested Routes
- AppRegistry.report.add({ pluginName, page }) in register()
- Wire report APIs with withApiMetadataFetchFn + React Query
- Relative sub-paths; localize labels
- Verify under Reports after quan-erp watch and install
常见失败
- 报表不出现 — 未注册 / pluginName 错误
- 嵌套页空白 — 路径带了前导 /
- 深链 404 — 缺少嵌套 Routes
- 权限错误 — requiredApis 不一致