17 KiB
证照管理子应用 — 本地开发指南
目录
- 1. 项目概述
- 2. 环境准备与启动
- 3. 路由系统
- 4. 菜单配置
- 5. 页面开发流程
- 6. API 接口声明
- 7. 状态管理(DVA)
- 8. 共享组件
- 9. 本地模拟布局
- 10. 已知问题与注意事项
- 11. 目录结构参考
1. 项目概述
本项目是基于 qiankun 微前端 框架的子应用,运行在 GBS 底座平台 内。使用以下核心技术栈:
| 技术 | 说明 |
|---|---|
| React 18 | UI 组件框架 |
| Ant Design 5.x | UI 组件库 |
| @cqsjjb/jjb-dva-runtime | 底座 DVA 状态管理运行时 |
| @cqsjjb/jjb-common-decorator | 装饰器工具(权限、模块加载等) |
| rspack | 构建打包工具 |
| pnpm | 包管理器 |
项目通过 jjb.config.js 中配置的 appIdentifier: "certificate" 注册到底座,所有页面路由前缀为 /certificate/container/。
2. 环境准备与启动
2.1 安装依赖
pnpm install
2.2 启动命令
| 命令 | 说明 |
|---|---|
pnpm run serve:development |
开发环境启动(推荐) |
pnpm run serve:production |
生产环境配置启动 |
pnpm run build:development |
开发环境构建 |
pnpm run build:production |
生产环境构建 |
2.3 访问地址
启动后开发服务器默认监听 http://127.0.0.1:8081,访问:
http://localhost:8081/certificate/container/
http://localhost:8081/certificate/container/Test
注意:本地独立启动时,应用处于 qiankun 独立运行模式(
window.__POWERED_BY_QIANKUN__为 false), 会使用内置的SimulatedLayout模拟侧边栏和标签页布局。
2.4 配置说明
jjb.config.js 中的关键配置项:
module.exports = {
appIdentifier: "certificate", // 应用唯一标识,对应路由前缀
server: {
port: "8081", // 开发服务器端口
host: "127.0.0.1", // 服务地址
},
environment: {
development: {
API_HOST: "https://gbs-gateway.qhdsafety.com", // 开发环境接口地址
},
},
};
3. 路由系统
3.1 约定式路由
本项目使用 文件系统约定式路由,由 @cqsjjb/jjb-dva-runtime 框架自动注册。页面对应的路径由目录结构决定:
src/pages/Container/<目录层级>/index.js → /certificate/container/<目录层级>
示例:
| 文件路径 | 对应路由 |
|---|---|
src/pages/Container/Test/index.js |
/certificate/container/Test |
src/pages/Container/Supervision/index.js |
/certificate/container/Supervision |
src/pages/Container/Supervision/PersonnelLicense/PersonInCharge/List/index.js |
/certificate/container/Supervision/PersonnelLicense/PersonInCharge/List |
3.2 页面组件类型
项目中有两类页面组件:
-
容器组件(中间层级):作为
{props.children}的容器,负责嵌套子页面function ContainerPage(props) { return <div>{props.children}</div>; } export default ContainerPage; -
叶子页面(终端页面):实际的业务功能页面,通过
Connect连接 DVA 数据import { Connect } from "@cqsjjb/jjb-dva-runtime"; import { NS_USER_CERTIFICATE } from "~/enumerate/namespace"; function List(props) { return <div>业务内容</div>; } export default Connect([NS_USER_CERTIFICATE], true)(List);
3.3 路由匹配规则
- 框架自动递归扫描
src/pages/目录,每个index.js对应一个路由 - 路由路径是将目录路径转换为小写并用
/连接 - 带参数的路由(如
/View/:id)需在目录中创建对应层级
4. 菜单配置
侧边栏菜单定义在 src/pages/Container/Layout/menuConfig.js 中。
4.1 菜单数据结构
{
key: "/certificate/container/<实际路由>", // 必须与文件目录对应的路由完全一致
label: "菜单显示名称",
icon: <SomeIcon />, // Ant Design 图标组件
children: [...] // 可选,子菜单项
}
4.2 当前菜单结构
📁 监管端 (Supervision)
├── 📄 企业证照 (EnterpriseLicense)
│ ├── 企业证照管理
│ ├── 分公司统计
│ └── 干系人统计
└── 📄 人员证照 (PersonnelLicense)
├── 负责人
├── 安全管理员
├── 特种设备
├── 特种作业人员
├── 分公司人员统计
└── 干系人人员统计
📁 分公司端 (BranchCompany)
└── 📄 企业证照 (EnterpriseLicense)
├── 企业证照管理
└── 📄 人员证照 (PersonnelLicense)
├── 负责人
├── 安全管理员
├── 特种设备
└── 特种作业人员
📁 干系人端 (Stakeholder)
└── 📄 企业证照 (EnterpriseLicense)
├── 企业证照管理
└── 📄 人员证照 (PersonnelLicense)
├── 负责人
├── 安全管理员
├── 特种设备
└── 特种作业人员
📁 测试页面 (Test)
4.3 菜单配置注意事项
关键规则:菜单的
key必须与文件系统的实际目录层级完全一致,否则会出现"页面不存在"。
以"干系人端 → 负责人"为例:
- 文件位置:
Stakeholder/EnterpriseLicense/PersonnelLicense/PersonInCharge/List/index.js - 正确路径:
/certificate/container/Stakeholder/EnterpriseLicense/PersonnelLicense/PersonInCharge/List - 错误路径(缺少
EnterpriseLicense层级):/certificate/container/Stakeholder/PersonnelLicense/PersonInCharge/List
4.4 工具函数
menuConfig.js 同时导出以下辅助函数:
| 函数 | 参数 | 返回值 | 说明 |
|---|---|---|---|
flattenMenu(items) |
菜单树 | 扁平数组 | 将树形菜单拍平 |
findMenuPath(path) |
当前路由 | 菜单项数组 | 从根到叶子节点的面包屑路径 |
getPageLabel(path) |
当前路由 | 字符串 | 获取页面标签名 |
getOpenKeys(path) |
当前路由 | 路径数组 | 默认展开的菜单项 |
getSelectedKeys(path) |
当前路由 | 路径数组 | 默认选中的菜单项 |
5. 页面开发流程
5.1 新增业务页面
以新增"监管端 → 人员证照 → 新增功能"为例:
步骤 1:创建目录结构
src/pages/Container/Supervision/PersonnelLicense/NewFeature/
├── index.js # 容器组件
├── List/
│ └── index.js # 列表页
└── View/
└── index.js # 详情页
步骤 2:编写容器组件
src/pages/Container/Supervision/PersonnelLicense/NewFeature/index.js:
function NewFeature(props) {
return <div>{props.children}</div>;
}
export default NewFeature;
步骤 3:编写列表页面
src/pages/Container/Supervision/PersonnelLicense/NewFeature/List/index.js:
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { NS_USER_CERTIFICATE } from "~/enumerate/namespace";
function List(props) {
return (
<div>
<h1>新功能列表</h1>
</div>
);
}
export default Connect([NS_USER_CERTIFICATE], true)(List);
步骤 4:添加菜单配置
在 src/pages/Container/Layout/menuConfig.js 中添加:
{
key: "/certificate/container/Supervision/PersonnelLicense",
label: "人员证照",
icon: <TeamOutlined />,
children: [
// ... 已有菜单项
{
key: "/certificate/container/Supervision/PersonnelLicense/NewFeature/List",
label: "新功能",
icon: <SomeIcon />,
},
],
}
5.2 新增测试页面(独立于业务)
在 src/pages/Container/ 下创建目录,并在菜单中添加即可:
src/pages/Container/MyDebug/
└── index.js
菜单:
{
key: "/certificate/container/MyDebug",
label: "调试页面",
icon: <ExperimentOutlined />,
}
5.3 验证路径是否正确
启动项目后,通过以下方式验证:
- 在浏览器直接访问完整 URL
- 从侧边栏菜单点击导航
- 检查开发者工具 Network 面板,404 表示路径不匹配
6. API 接口声明
6.1 接口定义位置
API 接口声明在 src/api/ 目录下,按模块划分:
src/api/
├── userCertificate/index.js # 人员证书接口
├── corpCertificate/index.js # 企业证书接口
├── courseware/index.js # 课件接口
└── global/index.js # 全局接口
6.2 declareRequest 语法
import { declareRequest } from "@cqsjjb/jjb-dva-runtime";
// 格式:declareRequest(loadingKey, "HttpMethod > 接口路径")
export const listApi = declareRequest(
"namespaceLoading", // loading 状态 key
"Post > @/certificate/userCertificate/list", // POST + 相对路径(带 @)
);
export const getApi = declareRequest(
"namespaceLoading",
"Get > /certificate/userCertificate/getInfoById/{id}", // GET + 绝对路径
);
语法规则:
| 符号 | 说明 | 示例 |
|---|---|---|
@/ |
相对路径,使用 jjb.config.js 中配置的 API_HOST |
Post > @/certificate/xxx/list |
/ |
绝对路径 | Get > /config/dict-trees/list |
{param} |
路径参数 | /certificate/xxx/delete/{id} |
Post > |
POST 请求 | — |
Get > |
GET 请求 | — |
Put > |
PUT 请求 | — |
Delete > |
DELETE 请求 | — |
6.3 在组件中使用 API
API 通过 DVA 的 dispatch 和 Connect 来调用。declareRequest 会自动生成对应的 dva effect:
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { NS_USER_CERTIFICATE } from "~/enumerate/namespace";
function List({ dispatch, userCertificate }) {
const fetchData = () => {
dispatch({
type: `${NS_USER_CERTIFICATE}/userCertificateList`, // namespace/接口名
payload: { page: 1, size: 10 },
});
};
return <div>{/* ... */}</div>;
}
export default Connect([NS_USER_CERTIFICATE], true)(List);
7. 状态管理(DVA)
7.1 Namespace 定义
src/enumerate/namespace/index.js 中定义了所有 DVA namespace:
export const NS_GLOBAL = defineNamespace("global");
export const NS_PERSNONEL_CERTFICATE = defineNamespace("personnelCertificate");
export const NS_CORP_CERTIFICATE = defineNamespace("corpCertificate");
export const NS_USER_CERTIFICATE = defineNamespace("userCertificate");
export const NS_COURSEWARE = defineNamespace("courseware");
7.2 Connect 高阶组件
Connect([NS_USER_CERTIFICATE], true)(MyComponent)
- 第一个参数:需要连接的 namespace 数组
- 第二个参数:是否在组件挂载时自动加载数据(
true表示自动触发loadeffect)
7.3 数据流向
dispatch(action) → DVA Effect → declareRequest → HTTP API → reducer → state 更新 → 组件重渲染
7.4 本地开发时的限制
本地独立运行时(非底座环境),DVA 状态管理正常可用,但 declareRequest 发起的 HTTP 请求会因缺少认证 Token 而返回 401。详见 §10 已知问题。
8. 共享组件
业务组件位于 src/components/ 目录下,目前包括:
src/components/
├── PersonInChargeInfo/ # 负责人详情组件
├── PersonInChargeList/ # 负责人列表组件(含搜索、分页、CRUD)
├── SpecialCertificateInfo/ # 特种证书详情组件
└── SpecialCertificateList/ # 特种证书列表组件
组件通过 @/components/xxx 或别名 ~/components/xxx 引入:
import PersonInChargeList from "~/components/PersonInChargeList";
8.1 组件参数传递
列表组件通过 props 接收业务参数:
<PersonInChargeList
props={props} // dva props 透传
certificatePhotoType={161} // 证照照片类型
personnelType="zyfzr" // 人员类型标识
permissionAdd="gfd-xxx-add" // 新增权限码
permissionEdit="gfd-xxx-edit" // 编辑权限码
permissionView="gfd-xxx-info" // 查看权限码
permissionDel="gfd-xxx-del" // 删除权限码
dictionaryType="zyfzrgwmc0000" // 字典类型
/>
9. 本地模拟布局
9.1 工作原理
src/pages/Container/index.js 中的 AppMiddle 组件检测运行环境:
function AppMiddle(props) {
const content = process.env.NODE_ENV === "development" && !window.__POWERED_BY_QIANKUN__
? <SimulatedLayout>{props.children}</SimulatedLayout>
: props.children;
// ...
}
- 本地开发(
development+ 非 qiankun):使用SimulatedLayout包裹内容,提供模拟的侧边栏、面包屑和标签页 - 底座环境:由底座平台提供真实的布局
9.2 SimulatedLayout 功能
src/pages/Container/Layout/index.jsx 提供:
| 功能 | 说明 |
|---|---|
| 侧边栏 | 使用 menuConfig.js 的菜单数据渲染,支持折叠 |
| 面包屑 | 根据当前路径自动计算导航路径 |
| 多标签页 | 支持打开多个页面标签,右键关闭操作 |
| 标签持久化 | 使用 sessionStorage 保存标签状态 |
| 主题适配 | 通过 window.base?.themeConfig 支持底座主题切换 |
10. 已知问题与注意事项
10.1 API 401 错误
现象:本地启动后,点击侧边栏菜单(除 Test 页面外),控制台报 401 错误。
原因:本项目是 qiankun 微前端子应用,API 请求需要的认证 Token 由 GBS 底座注入。本地独立运行时没有底座,也就没有 Token,所有 API 调用被拒绝。
处理方式:
- 纯前端调试:使用 Test 页面(
/certificate/container/Test),该页面不发起任何 API 请求 - 完整功能调试:在 GBS 底座环境中打开应用
10.2 路由路径与菜单 key 必须匹配
菜单的 key 值必须和文件目录层级完全一致。错误示例见 §4.3 菜单配置注意事项。
10.3 重命名项目目录后需重新安装依赖
由于 pnpm 使用基于绝对路径的符号链接管理 node_modules,重命名项目根目录后需要:
Remove-Item -Recurse -Force node_modules
pnpm install
10.4 权限控制
页面使用 @cqsjjb/jjb-common-decorator/permission 的 Permission 装饰器控制访问:
import { Permission } from "@cqsjjb/jjb-common-decorator/permission";
export default Connect([NS_USER_CERTIFICATE], true)(Permission(List));
权限码由底座平台管理,本地开发时权限校验可能不生效。
10.5 pnpm overrides
package.json 中通过 pnpm.overrides 强制统一 React 等依赖版本,避免多副本冲突:
{
"pnpm": {
"overrides": {
"react": "$react",
"react-dom": "$react-dom"
}
}
}
11. 目录结构参考
safety-eval-service-frontend/
├── public/
│ └── index.html # HTML 模板
├── src/
│ ├── main.js # 应用入口(setup + 生命周期导出)
│ ├── api/ # API 接口声明
│ │ ├── global/
│ │ ├── userCertificate/
│ │ ├── corpCertificate/
│ │ └── courseware/
│ ├── components/ # 共享业务组件
│ │ ├── PersonInChargeList/
│ │ ├── PersonInChargeInfo/
│ │ ├── SpecialCertificateList/
│ │ └── SpecialCertificateInfo/
│ ├── enumerate/ # 枚举/常量定义
│ │ ├── namespace/ # DVA namespace
│ │ ├── context/ # React Context
│ │ └── constant/ # 常量
│ ├── pages/
│ │ └── Container/
│ │ ├── index.js # 容器入口(Antd 主题 + 布局切换)
│ │ ├── Entry/ # 底座入口页
│ │ ├── Layout/ # 本地模拟布局
│ │ │ ├── index.jsx # SimulatedLayout 组件
│ │ │ └── menuConfig.js # ★ 菜单配置
│ │ ├── Test/ # 测试页面(无 API 依赖)
│ │ ├── Supervision/ # 监管端
│ │ │ ├── EnterpriseLicense/
│ │ │ └── PersonnelLicense/
│ │ ├── BranchCompany/ # 分公司端
│ │ │ └── EnterpriseLicense/
│ │ └── Stakeholder/ # 干系人端
│ │ └── EnterpriseLicense/
│ ├── assets/ # 静态资源
│ └── utils/ # 工具函数
├── jjb.config.js # 项目配置(端口、API_HOST、主题等)
├── package.json
├── pnpm-lock.yaml
└── pnpm-workspace.yaml