feat
parent
2d812ec4fd
commit
d343838f4d
|
|
@ -7,6 +7,7 @@
|
|||
/prototype
|
||||
/dist
|
||||
/demo
|
||||
/docs
|
||||
/.trae/
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
|
|
|
|||
|
|
@ -1,66 +0,0 @@
|
|||
/*
|
||||
_ooOoo_
|
||||
o8888888o
|
||||
88" . "88
|
||||
(| -_- |)
|
||||
O\ = /O
|
||||
____/`---'\____
|
||||
. ' \\| |// `.
|
||||
/ \\||| : |||// \
|
||||
/ _||||| -:- |||||- \
|
||||
| | \\\ - /// | |
|
||||
| \_| ''\---/'' | |
|
||||
\ .-\__ `-` ___/-. /
|
||||
___`. .' /--.--\ `. . __
|
||||
."" '< `.___\_<|>_/___.' >'"".
|
||||
| | : `- \`.;`\ _ /`;.`/ - ` : | |
|
||||
\ \ `-. \_ __\ /__ _/ .-` / /
|
||||
======`-.____`-.___\_____/___.-`____.-'======
|
||||
`=---='
|
||||
|
||||
.............................................
|
||||
佛祖保佑 永无BUG
|
||||
佛曰:
|
||||
写字楼里写字间,写字间里程序员;
|
||||
程序人员写程序,又拿程序换酒钱。
|
||||
酒醒只在网上坐,酒醉还来网下眠;
|
||||
酒醉酒醒日复日,网上网下年复年。
|
||||
但愿老死电脑间,不愿鞠躬老板前;
|
||||
奔驰宝马贵者趣,公交自行程序员。
|
||||
别人笑我忒疯癫,我笑自己命太贱;
|
||||
不见满街漂亮妹,哪个归得程序员?
|
||||
*/
|
||||
|
||||
const blessedByBuddha
|
||||
= "%c _ooOoo_\n"
|
||||
+ " o8888888o\n"
|
||||
+ " 88\" . \"88\n"
|
||||
+ " (| -_- |)\n"
|
||||
+ " O\\ = /O\n"
|
||||
+ " ____/`---'\\____\n"
|
||||
+ " . ' \\\\| |// `.\n"
|
||||
+ " / \\\\||| : |||// \\\n"
|
||||
+ " / _||||| -:- |||||- \\\n"
|
||||
+ " | | \\\\\\ - /// | |\n"
|
||||
+ " | \\_| ''\\---/'' | |\n"
|
||||
+ " \\ .-\\__ `-` ___/-. /\n"
|
||||
+ " ___`. .' /--.--\\ `. . __\n"
|
||||
+ " .\"\" '< `.___\\_<|>_/___.' >'\"\".\n"
|
||||
+ " | | : `- \\`.;`\\ _ /`;.`/ - ` : | |\n"
|
||||
+ " \\ \\ `-. \\_ __\\ /__ _/ .-` / /\n"
|
||||
+ " ======`-.____`-.___\\_____/___.-`____.-'======\n"
|
||||
+ " `=---='\n"
|
||||
+ "\n"
|
||||
+ "%c .............................................\n"
|
||||
+ " 佛祖保佑 永无BUG\n"
|
||||
+ "\n"
|
||||
+ "%c 佛曰:\n"
|
||||
+ " 写字楼里写字间,写字间里程序员;\n"
|
||||
+ " 程序人员写程序,又拿程序换酒钱。\n"
|
||||
+ " 酒醒只在网上坐,酒醉还来网下眠;\n"
|
||||
+ " 酒醉酒醒日复日,网上网下年复年。\n"
|
||||
+ " 但愿老死电脑间,不愿鞠躬老板前;\n"
|
||||
+ " 奔驰宝马贵者趣,公交自行程序员。\n"
|
||||
+ " 别人笑我忒疯癫,我笑自己命太贱;\n"
|
||||
+ " 不见满街漂亮妹,哪个归得程序员?";
|
||||
console.log(blessedByBuddha, "color:#ffd700", "color:red", "color:#1e80ff");
|
||||
|
|
@ -1,572 +0,0 @@
|
|||
# 证照管理子应用 — 本地开发指南
|
||||
|
||||
## 目录
|
||||
|
||||
- [1. 项目概述](#1-项目概述)
|
||||
- [2. 环境准备与启动](#2-环境准备与启动)
|
||||
- [3. 路由系统](#3-路由系统)
|
||||
- [4. 菜单配置](#4-菜单配置)
|
||||
- [5. 页面开发流程](#5-页面开发流程)
|
||||
- [6. API 接口声明](#6-api-接口声明)
|
||||
- [7. 状态管理(DVA)](#7-状态管理dva)
|
||||
- [8. 共享组件](#8-共享组件)
|
||||
- [9. 本地模拟布局](#9-本地模拟布局)
|
||||
- [10. 已知问题与注意事项](#10-已知问题与注意事项)
|
||||
- [11. 目录结构参考](#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 安装依赖
|
||||
|
||||
```bash
|
||||
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` 中的关键配置项:
|
||||
|
||||
```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 页面组件类型
|
||||
|
||||
项目中有两类页面组件:
|
||||
|
||||
1. **容器组件**(中间层级):作为 `{props.children}` 的容器,负责嵌套子页面
|
||||
```js
|
||||
function ContainerPage(props) {
|
||||
return <div>{props.children}</div>;
|
||||
}
|
||||
export default ContainerPage;
|
||||
```
|
||||
|
||||
2. **叶子页面**(终端页面):实际的业务功能页面,通过 `Connect` 连接 DVA 数据
|
||||
```js
|
||||
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 菜单数据结构
|
||||
|
||||
```js
|
||||
{
|
||||
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`:
|
||||
```js
|
||||
function NewFeature(props) {
|
||||
return <div>{props.children}</div>;
|
||||
}
|
||||
export default NewFeature;
|
||||
```
|
||||
|
||||
**步骤 3:编写列表页面**
|
||||
|
||||
`src/pages/Container/Supervision/PersonnelLicense/NewFeature/List/index.js`:
|
||||
```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` 中添加:
|
||||
```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
|
||||
```
|
||||
|
||||
菜单:
|
||||
```js
|
||||
{
|
||||
key: "/certificate/container/MyDebug",
|
||||
label: "调试页面",
|
||||
icon: <ExperimentOutlined />,
|
||||
}
|
||||
```
|
||||
|
||||
### 5.3 验证路径是否正确
|
||||
|
||||
启动项目后,通过以下方式验证:
|
||||
|
||||
1. 在浏览器直接访问完整 URL
|
||||
2. 从侧边栏菜单点击导航
|
||||
3. 检查开发者工具 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 语法
|
||||
|
||||
```js
|
||||
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:
|
||||
|
||||
```js
|
||||
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:
|
||||
|
||||
```js
|
||||
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 高阶组件
|
||||
|
||||
```js
|
||||
Connect([NS_USER_CERTIFICATE], true)(MyComponent)
|
||||
```
|
||||
|
||||
- 第一个参数:需要连接的 namespace 数组
|
||||
- 第二个参数:是否在组件挂载时自动加载数据(`true` 表示自动触发 `load` effect)
|
||||
|
||||
### 7.3 数据流向
|
||||
|
||||
```
|
||||
dispatch(action) → DVA Effect → declareRequest → HTTP API → reducer → state 更新 → 组件重渲染
|
||||
```
|
||||
|
||||
### 7.4 本地开发时的限制
|
||||
|
||||
本地独立运行时(非底座环境),DVA 状态管理正常可用,但 `declareRequest` 发起的 HTTP 请求会因缺少认证 Token 而返回 **401**。详见 [§10 已知问题](#10-已知问题与注意事项)。
|
||||
|
||||
---
|
||||
|
||||
## 8. 共享组件
|
||||
|
||||
业务组件位于 `src/components/` 目录下,目前包括:
|
||||
|
||||
```
|
||||
src/components/
|
||||
├── PersonInChargeInfo/ # 负责人详情组件
|
||||
├── PersonInChargeList/ # 负责人列表组件(含搜索、分页、CRUD)
|
||||
├── SpecialCertificateInfo/ # 特种证书详情组件
|
||||
└── SpecialCertificateList/ # 特种证书列表组件
|
||||
```
|
||||
|
||||
组件通过 `@/components/xxx` 或别名 `~/components/xxx` 引入:
|
||||
|
||||
```js
|
||||
import PersonInChargeList from "~/components/PersonInChargeList";
|
||||
```
|
||||
|
||||
### 8.1 组件参数传递
|
||||
|
||||
列表组件通过 `props` 接收业务参数:
|
||||
|
||||
```js
|
||||
<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` 组件检测运行环境:
|
||||
|
||||
```js
|
||||
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 菜单配置注意事项](#43-菜单配置注意事项)。
|
||||
|
||||
### 10.3 重命名项目目录后需重新安装依赖
|
||||
|
||||
由于 pnpm 使用基于绝对路径的符号链接管理 `node_modules`,重命名项目根目录后需要:
|
||||
|
||||
```bash
|
||||
Remove-Item -Recurse -Force node_modules
|
||||
pnpm install
|
||||
```
|
||||
|
||||
### 10.4 权限控制
|
||||
|
||||
页面使用 `@cqsjjb/jjb-common-decorator/permission` 的 `Permission` 装饰器控制访问:
|
||||
|
||||
```js
|
||||
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 等依赖版本,避免多副本冲突:
|
||||
|
||||
```json
|
||||
{
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"react": "$react",
|
||||
"react-dom": "$react-dom"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. 目录结构参考
|
||||
|
||||
```
|
||||
safetyEval-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
|
||||
```
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
# 证照管理子应用 — 快速启动
|
||||
|
||||
## 环境要求
|
||||
|
||||
- Node.js >= 18
|
||||
- pnpm >= 8
|
||||
|
||||
## 启动
|
||||
|
||||
```bash
|
||||
# 安装依赖(首次或重命名项目目录后)
|
||||
pnpm install
|
||||
|
||||
# 启动开发服务器
|
||||
pnpm run serve:development
|
||||
```
|
||||
|
||||
## 访问
|
||||
|
||||
| 地址 | 说明 |
|
||||
|------|------|
|
||||
| `http://localhost:8081/certificate/container/` | 应用首页 |
|
||||
| `http://localhost:8081/certificate/container/Test` | 测试页面(无 API 依赖,纯前端调试可用) |
|
||||
|
||||
## 常用命令
|
||||
|
||||
| 命令 | 说明 |
|
||||
|------|------|
|
||||
| `pnpm run serve:development` | 开发环境启动(推荐) |
|
||||
| `pnpm run serve:production` | 生产环境配置启动 |
|
||||
| `pnpm run build:development` | 开发环境构建 |
|
||||
| `pnpm run build:production` | 生产环境构建 |
|
||||
|
||||
## 技术栈
|
||||
|
||||
- React 18 + Ant Design 5.x
|
||||
- @cqsjjb/jjb-dva-runtime(GBS 底座 DVA 运行时)
|
||||
- rspack 构建 / pnpm 包管理
|
||||
- qiankun 微前端子应用
|
||||
|
||||
## 重要提示
|
||||
|
||||
- 本项目为 GBS 底座微前端子应用,独立启动时有内置模拟布局
|
||||
- 本地独立运行时 API 调用会返回 401(缺少底座注入的认证 Token)
|
||||
- 仅 Test 页面可在本地无底座环境下正常访问
|
||||
- 重命名项目目录后需 `pnpm install` 重建 node_modules 符号链接
|
||||
- 完整开发指南请参考 [dev-guide.md](./dev-guide.md)
|
||||
|
|
@ -1,211 +0,0 @@
|
|||
# 企业信息管理 — 测试报告(最新)
|
||||
|
||||
> 覆盖模块:机构信息、部门岗位、人员信息、人员变更、离职申请、资质证书、装备信息
|
||||
> 原型对照:`docs/原型/设计文件 (3).pdf` ~ `设计文件 (9).pdf`
|
||||
> 更新日期:2026-06-23
|
||||
|
||||
---
|
||||
|
||||
## 1. 测试环境
|
||||
|
||||
| 项 | 说明 |
|
||||
|---|---|
|
||||
| 前端 | `safetyEval-service-frontend`,`npm run dev` / `pnpm dev` |
|
||||
| 后端 | `safetyEval-service`,`http://127.0.0.1:8095/safetyEval`,profile=`local` |
|
||||
| 数据库 | 执行 `safetyEval-service/docs/init-v2.sql` 及末尾测试数据段 |
|
||||
| Node | `source scripts/setup-test-env.sh` 或 JetBrains Node 24.x |
|
||||
|
||||
---
|
||||
|
||||
## 2. 自动化脚本
|
||||
|
||||
### 2.1 接口联调(细粒度)
|
||||
|
||||
```bash
|
||||
cd safetyEval-service-frontend
|
||||
source scripts/setup-test-env.sh # 可选
|
||||
node scripts/test-enterprise-info-granular.mjs
|
||||
```
|
||||
|
||||
- 脚本:`scripts/test-enterprise-info-granular.mjs`
|
||||
- 覆盖:8 模块 CRUD、分页、搜索、字段组装(`deptName`/`postName`/`account`/`auditStatusCode`)
|
||||
- 环境变量:`API_BASE`(默认 `http://127.0.0.1:8095/safetyEval`)
|
||||
|
||||
### 2.2 前端 UI 静态检查
|
||||
|
||||
```bash
|
||||
node scripts/test-enterprise-info-ui-check.mjs
|
||||
```
|
||||
|
||||
- 脚本:`scripts/test-enterprise-info-ui-check.mjs`
|
||||
- 检查:公共工具、列表列、弹窗布局、变更次数样式、默认上传配置
|
||||
|
||||
### 2.3 一键执行
|
||||
|
||||
```bash
|
||||
node scripts/test-enterprise-info-granular.mjs && node scripts/test-enterprise-info-ui-check.mjs
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 问题修复对照(用户反馈 8 项)
|
||||
|
||||
| # | 问题 | 处理方式 | 验证用例 |
|
||||
|---|------|----------|----------|
|
||||
| 1 | 列表未展示部门/岗位 | 后端 `OrgPersonnelViewEnricher` 组装 `deptName`/`postName`;前端 adapter 映射 | EI-API-STAFF-05~08;UI-STAFF-03 |
|
||||
| 2 | 新增/编辑文字遮挡 | 弹窗改 `layout="vertical"` 或 `labelCol/wrapperCol=24` | UI-PAGE-*-03;手工打开各模块弹窗 |
|
||||
| 3 | 离职缺审核状态/用户名 | CO 增 `account`;enricher 组装;列表 Tag 展示 | EI-API-RESIGN-04~05;UI-RESIGN-01~02 |
|
||||
| 4 | 页面卡顿/报错 | 去掉失败的上传/删文件调用;修复 SELECT 空选项 | 控制台无红错;列表加载 <3s |
|
||||
| 5 | 变更次数配色 | `CHANGE_COUNT_STYLE` 蓝色高亮/灰色零值 | UI-CHANGE-01;人员变更列表目视 |
|
||||
| 6 | 新增人员部门/岗位不可选 | 人员表单改用 antd `Select`;`formSelectField` 适配 Search | UI-STAFF-01~02;TC-STAFF-03 |
|
||||
| 7 | 上传默认通过 | `src/utils/mockUpload.js` 固定 URL | UI-COMMON-03;TC-UPLOAD-* |
|
||||
| 8 | 其他页面问题 | 见第 4 节用例矩阵 | 全量手工 + 自动化 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 测试用例矩阵
|
||||
|
||||
### 4.1 机构信息(原型:设计文件 3)
|
||||
|
||||
| ID | 类型 | 步骤 | 预期 |
|
||||
|----|------|------|------|
|
||||
| TC-ORG-01 | UI | 进入机构信息页 | 标题/副标题与原型一致;表单只读 |
|
||||
| TC-ORG-02 | 功能 | 点击修改 → 暂存 | 成功提示;`authStatusCode=0` |
|
||||
| TC-ORG-03 | 功能 | 点击提交 | 成功提示;字段回显正确 |
|
||||
| TC-ORG-04 | API | EI-API-ORG-* | 分页/详情/修改通过 |
|
||||
|
||||
### 4.2 部门岗位(原型:设计文件 4)
|
||||
|
||||
| ID | 类型 | 步骤 | 预期 |
|
||||
|----|------|------|------|
|
||||
| TC-DEPT-01 | UI | 左侧树 + 右侧列表 | 布局与原型一致 |
|
||||
| TC-DEPT-02 | 功能 | 新增/编辑部门 | 弹窗 vertical,文字不遮挡 |
|
||||
| TC-DEPT-03 | 功能 | 进入岗位列表,增删改岗位 | 岗位关联当前部门 |
|
||||
| TC-DEPT-04 | API | EI-API-DEPT/POS-* | 通过 |
|
||||
|
||||
### 4.3 人员信息(原型:设计文件 5)
|
||||
|
||||
| ID | 类型 | 步骤 | 预期 |
|
||||
|----|------|------|------|
|
||||
| TC-STAFF-01 | UI | 列表列 | 用户名称、账号、**部门**、**岗位**、证照 |
|
||||
| TC-STAFF-02 | 功能 | 搜索部门/岗位 | 下拉有选项,筛选生效 |
|
||||
| TC-STAFF-03 | 功能 | 新增人员,选部门后选岗位 | 岗位按部门过滤,可提交 |
|
||||
| TC-STAFF-04 | 功能 | 编辑/查看/重置密码/删除 | 各操作正常 |
|
||||
| TC-STAFF-05 | 功能 | 证书子页 | 上传默认 URL,增删改查 |
|
||||
| TC-STAFF-06 | API | EI-API-STAFF/CERT-* | 通过;列表含 deptName/postName |
|
||||
|
||||
### 4.4 人员变更(原型:设计文件 6)
|
||||
|
||||
| ID | 类型 | 步骤 | 预期 |
|
||||
|----|------|------|------|
|
||||
| TC-CHANGE-01 | UI | 信息变更数 >0 | 蓝色链接样式,可点进日志 |
|
||||
| TC-CHANGE-02 | UI | 变更数 =0 | 灰色显示 |
|
||||
| TC-CHANGE-03 | 功能 | 离职审核通过/退回 | 状态 Tag 更新 |
|
||||
| TC-CHANGE-04 | API | EI-API-CHANGE-* | 分页通过 |
|
||||
|
||||
### 4.5 离职申请(原型:设计文件 7)
|
||||
|
||||
| ID | 类型 | 步骤 | 预期 |
|
||||
|----|------|------|------|
|
||||
| TC-RESIGN-01 | UI | 列表 | 用户名、姓名、部门、**审核状态 Tag** |
|
||||
| TC-RESIGN-02 | 功能 | 新增离职申请 | 弹窗 vertical;上传默认通过 |
|
||||
| TC-RESIGN-03 | 功能 | 按审核状态筛选 | 结果正确 |
|
||||
| TC-RESIGN-04 | API | EI-API-RESIGN-* | account/auditStatusCode 有值 |
|
||||
|
||||
### 4.6 资质证书(原型:设计文件 8)
|
||||
|
||||
| ID | 类型 | 步骤 | 预期 |
|
||||
|----|------|------|------|
|
||||
| TC-QUAL-01 | UI | 列表/弹窗 | 标签不遮挡;保密提示可见 |
|
||||
| TC-QUAL-02 | 功能 | 新增证书 + 上传 | 默认图片 URL 写入 `certImageUrl` |
|
||||
| TC-QUAL-03 | API | EI-API-QUAL-* | 通过 |
|
||||
|
||||
### 4.7 装备信息(原型:设计文件 9)
|
||||
|
||||
| ID | 类型 | 步骤 | 预期 |
|
||||
|----|------|------|------|
|
||||
| TC-EQUIP-01 | UI | 列表/弹窗 | 双列表单,标签完整 |
|
||||
| TC-EQUIP-02 | 功能 | 是否双路下拉 | 可选是/否 |
|
||||
| TC-EQUIP-03 | API | EI-API-EQUIP-* | 通过 |
|
||||
|
||||
### 4.8 上传(全局)
|
||||
|
||||
| ID | 类型 | 步骤 | 预期 |
|
||||
|----|------|------|------|
|
||||
| TC-UPLOAD-01 | 功能 | 离职报告上传 | `reportFileUrl` = 默认 URL |
|
||||
| TC-UPLOAD-02 | 功能 | 机构/人员证书上传 | `certImageUrl` / `certAttachmentUrl` = 默认 URL |
|
||||
|
||||
---
|
||||
|
||||
## 5. 原型 UI 对照检查清单
|
||||
|
||||
手工打开各页,对照 PDF 检查:
|
||||
|
||||
- [ ] 页面标题、副标题文案
|
||||
- [ ] 搜索区字段与原型一致
|
||||
- [ ] 表格列顺序、列名
|
||||
- [ ] 主按钮文案(新增/添加)
|
||||
- [ ] 弹窗宽度、表单项顺序
|
||||
- [ ] 信息变更数颜色(蓝/灰)
|
||||
- [ ] 审核状态 Tag 颜色(灰/绿/红)
|
||||
- [ ] 无控制台 Error / 无长时间 Loading
|
||||
|
||||
---
|
||||
|
||||
## 6. 执行记录(2026-06-23 第二轮)
|
||||
|
||||
```
|
||||
接口测试: 58 / 58 通过
|
||||
UI 静态检查: 31 / 31 通过
|
||||
|
||||
本轮修复:
|
||||
- 资质 remark 全链路 + 启用/禁用按钮
|
||||
- 人员变更:账户列、变更数颜色(String key)、就职状态/审核状态 Tag、后端 employmentStatusCode 查询
|
||||
- 人员:移除重置密码、birthDate dayjs 回显/提交格式化
|
||||
- 部门岗位:左树右岗布局,按 deptId 查岗位
|
||||
- 离职:账户列、remark 分离、personnelId 自动匹配、时间格式化
|
||||
- 装备:instrumentType 列表字段、表单回显
|
||||
- 时间:formatDateTime → YYYY-MM-DD HH:mm:ss
|
||||
```
|
||||
|
||||
## 7. 执行记录(2026-06-23 第一轮)
|
||||
|
||||
```
|
||||
执行人:自动化脚本
|
||||
执行时间:2026-06-23(后端重启后)
|
||||
后端:http://127.0.0.1:8095/safetyEval
|
||||
|
||||
接口测试: 58 / 58 通过
|
||||
UI 静态检查: 31 / 31 通过
|
||||
|
||||
字段组装验证(已通过):
|
||||
- EI-API-STAFF-05~08:deptName / postName 正常
|
||||
- EI-API-RESIGN-04~05:auditStatusCode / account 正常
|
||||
|
||||
重跑命令:
|
||||
source scripts/setup-test-env.sh
|
||||
node scripts/test-enterprise-info-granular.mjs
|
||||
node scripts/test-enterprise-info-ui-check.mjs
|
||||
```
|
||||
|
||||
### 6.1 手工记录模板
|
||||
|
||||
---
|
||||
|
||||
## 7. 已知限制
|
||||
|
||||
1. **上传**:联调阶段不走真实文件服务,统一写入固定花瓣 URL。
|
||||
2. **原型像素级**:PDF 与 Ant Design 组件存在间距差异,以字段/交互一致为准。
|
||||
3. **人员变更删除**:列表删除为变更记录删除,与人员主数据删除不同,需与产品确认。
|
||||
|
||||
---
|
||||
|
||||
## 8. 相关代码路径
|
||||
|
||||
| 用途 | 路径 |
|
||||
|------|------|
|
||||
| 页面 | `src/pages/Container/EnterpriseInfo/**` |
|
||||
| API 适配 | `src/api/enterpriseInfo/adapter.js` |
|
||||
| 表单工具 | `src/utils/enterpriseForm.js` |
|
||||
| 上传 Mock | `src/utils/mockUpload.js` |
|
||||
| 后端组装 | `safetyEval-app/.../OrgPersonnelViewEnricher.java` |
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
# 企业信息管理联调测试环境变量
|
||||
# 用法: source scripts/setup-test-env.sh
|
||||
|
||||
export API_BASE="${API_BASE:-http://127.0.0.1:8095/safetyEval}"
|
||||
export SAFETY_EVAL_API_HOST="${SAFETY_EVAL_API_HOST:-http://127.0.0.1:8095}"
|
||||
|
||||
# JetBrains 内置 Node(系统 PATH 无 node 时使用)
|
||||
if ! command -v node >/dev/null 2>&1; then
|
||||
_JB_NODE="$HOME/Library/Application Support/JetBrains/IntelliJIdea2025.3/node/versions/24.16.0/bin/node"
|
||||
if [[ -x "$_JB_NODE" ]]; then
|
||||
export NODE="$_JB_NODE"
|
||||
export PATH="$(dirname "$_JB_NODE"):$PATH"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "API_BASE=$API_BASE"
|
||||
echo "SAFETY_EVAL_API_HOST=$SAFETY_EVAL_API_HOST"
|
||||
echo "NODE=$(command -v node || echo '未找到')"
|
||||
|
|
@ -1,221 +0,0 @@
|
|||
/**
|
||||
* 企业信息管理 - 后端接口联调冒烟测试
|
||||
* 运行: API_BASE=http://127.0.0.1:8095/safetyEval node scripts/test-enterprise-info-api.mjs
|
||||
*/
|
||||
const API_BASE = process.env.API_BASE || "http://127.0.0.1:8095/safetyEval";
|
||||
|
||||
const results = [];
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
|
||||
function assert(name, condition, detail = "") {
|
||||
if (condition) {
|
||||
passed += 1;
|
||||
results.push({ name, status: "PASS", detail });
|
||||
}
|
||||
else {
|
||||
failed += 1;
|
||||
results.push({ name, status: "FAIL", detail });
|
||||
}
|
||||
}
|
||||
|
||||
async function request(method, path, body) {
|
||||
const url = `${API_BASE}${path}`;
|
||||
const options = {
|
||||
method,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
};
|
||||
if (body !== undefined) {
|
||||
options.body = JSON.stringify(body);
|
||||
}
|
||||
const res = await fetch(url, options);
|
||||
const text = await res.text();
|
||||
try {
|
||||
return JSON.parse(text);
|
||||
}
|
||||
catch {
|
||||
return { success: false, raw: text, status: res.status };
|
||||
}
|
||||
}
|
||||
|
||||
async function runTests() {
|
||||
console.log(`\nAPI Base: ${API_BASE}\n`);
|
||||
|
||||
// ── 机构信息 ──
|
||||
let orgPage = await request("GET", "/org-info/page?current=1&size=1");
|
||||
assert("机构信息-分页", orgPage.success === true, `code=${orgPage.code}`);
|
||||
|
||||
let orgId = orgPage.data?.[0]?.id;
|
||||
if (!orgId) {
|
||||
const created = await request("POST", "/org-info/save", {
|
||||
unitName: "联调测试机构",
|
||||
creditCode: "91110000MA0TEST001",
|
||||
principalName: "测试负责人",
|
||||
principalPhone: "13800138000",
|
||||
registerAddress: "测试地址",
|
||||
businessAddress: "测试地址",
|
||||
authStatusCode: 0,
|
||||
authStatusName: "草稿",
|
||||
});
|
||||
assert("机构信息-新增", created.success === true, created.message);
|
||||
orgId = created.data?.id;
|
||||
}
|
||||
|
||||
if (orgId) {
|
||||
const orgDetail = await request("GET", `/org-info/get?id=${orgId}`);
|
||||
assert("机构信息-详情", orgDetail.success === true && orgDetail.data?.id === orgId);
|
||||
const modified = await request("POST", "/org-info/modify", {
|
||||
...orgDetail.data,
|
||||
principalName: "测试负责人-已改",
|
||||
authStatusCode: 1,
|
||||
authStatusName: "已提交",
|
||||
});
|
||||
assert("机构信息-修改", modified.success === true, modified.message);
|
||||
}
|
||||
|
||||
// ── 部门 ──
|
||||
const deptPage = await request("GET", "/org-department/page?current=1&size=10");
|
||||
assert("部门-分页", deptPage.success === true, `total=${deptPage.total}`);
|
||||
|
||||
const deptCreated = await request("POST", "/org-department/save", {
|
||||
deptName: "联调测试部门",
|
||||
managerName: "测试负责人",
|
||||
deptLevelName: "一级",
|
||||
});
|
||||
assert("部门-新增", deptCreated.success === true, deptCreated.message);
|
||||
const deptId = deptCreated.data?.id;
|
||||
|
||||
if (deptId) {
|
||||
const deptModified = await request("POST", "/org-department/modify", {
|
||||
id: deptId,
|
||||
deptName: "联调测试部门-已改",
|
||||
managerName: "测试负责人",
|
||||
deptLevelName: "一级",
|
||||
});
|
||||
assert("部门-修改", deptModified.success === true);
|
||||
}
|
||||
|
||||
// ── 岗位 ──
|
||||
const posCreated = await request("POST", "/org-position/save", {
|
||||
positionName: "联调测试岗位",
|
||||
dutyDesc: "测试职责",
|
||||
deptId: deptId || "102",
|
||||
});
|
||||
assert("岗位-新增", posCreated.success === true, posCreated.message);
|
||||
const posId = posCreated.data?.id;
|
||||
|
||||
const posPage = await request("GET", `/org-position/page?current=1&size=10&deptId=${deptId || ""}`);
|
||||
assert("岗位-分页", posPage.success === true, `count=${posPage.data?.length}`);
|
||||
|
||||
// ── 人员 ──
|
||||
const staffCreated = await request("POST", "/org-personnel/save", {
|
||||
userName: "联调测试人员",
|
||||
account: "13900001234",
|
||||
genderCode: 1,
|
||||
genderName: "男",
|
||||
employmentStatusCode: 1,
|
||||
employmentStatusName: "在职",
|
||||
});
|
||||
assert("人员-新增", staffCreated.success === true, staffCreated.message);
|
||||
const staffId = staffCreated.data?.id;
|
||||
|
||||
if (staffId) {
|
||||
const staffPage = await request("GET", `/org-personnel/page?current=1&size=10&userName=${encodeURIComponent("联调")}`);
|
||||
assert("人员-分页", staffPage.success === true);
|
||||
const staffDetail = await request("GET", `/org-personnel/get?id=${staffId}`);
|
||||
assert("人员-详情", staffDetail.success === true && staffDetail.data?.userName?.includes("联调"));
|
||||
}
|
||||
|
||||
// ── 装备 ──
|
||||
const equipCreated = await request("POST", "/org-equipment/save", {
|
||||
deviceName: "联调测试设备",
|
||||
deviceModel: "T-001",
|
||||
manufacturer: "测试厂家",
|
||||
enableFlag: 1,
|
||||
});
|
||||
assert("装备-新增", equipCreated.success === true, equipCreated.message);
|
||||
const equipId = equipCreated.data?.id;
|
||||
|
||||
const equipPage = await request("GET", "/org-equipment/page?current=1&size=10");
|
||||
assert("装备-分页", equipPage.success === true);
|
||||
|
||||
if (equipId) {
|
||||
const equipDetail = await request("GET", `/org-equipment/get?id=${equipId}`);
|
||||
const toggleFlag = equipDetail.data?.enableFlag === 1 ? 0 : 1;
|
||||
const toggled = await request("POST", "/org-equipment/modify", {
|
||||
...equipDetail.data,
|
||||
enableFlag: toggleFlag,
|
||||
});
|
||||
assert("装备-启停(modify)", toggled.success === true);
|
||||
}
|
||||
|
||||
// ── 资质证书 ──
|
||||
const qualCreated = await request("POST", "/org-qualification/save", {
|
||||
licenseTypeName: "测试证照",
|
||||
certName: "联调测试证书",
|
||||
certNo: `TEST-${Date.now()}`,
|
||||
issueOrg: "测试机关",
|
||||
issueDate: "2026-01-01",
|
||||
validStartDate: "2026-01-01",
|
||||
validEndDate: "2027-01-01",
|
||||
enableFlag: 1,
|
||||
});
|
||||
assert("资质-新增", qualCreated.success === true, qualCreated.message);
|
||||
const qualId = qualCreated.data?.id;
|
||||
|
||||
const qualPage = await request("GET", "/org-qualification/page?current=1&size=10");
|
||||
assert("资质-分页", qualPage.success === true);
|
||||
|
||||
// ── 离职申请 ──
|
||||
const resignCreated = await request("POST", "/org-resign-apply/save", {
|
||||
applicantName: "联调测试人员",
|
||||
applyTime: "2026-06-20T10:00:00",
|
||||
expectedResignDate: "2026-07-31",
|
||||
resignReason: "联调测试",
|
||||
auditStatusCode: 0,
|
||||
auditStatusName: "未审核",
|
||||
});
|
||||
assert("离职申请-新增", resignCreated.success === true, resignCreated.message);
|
||||
const resignId = resignCreated.data?.id;
|
||||
|
||||
if (resignId) {
|
||||
const audited = await request("POST", "/org-resign-apply/modify", {
|
||||
id: resignId,
|
||||
auditStatusCode: 1,
|
||||
auditStatusName: "已审核",
|
||||
});
|
||||
assert("离职申请-审核", audited.success === true);
|
||||
}
|
||||
|
||||
// ── 清理 ──
|
||||
if (qualId) {
|
||||
await request("POST", `/org-qualification/delete?id=${qualId}`);
|
||||
}
|
||||
if (equipId) {
|
||||
await request("POST", `/org-equipment/delete?id=${equipId}`);
|
||||
}
|
||||
if (staffId) {
|
||||
await request("POST", `/org-personnel/delete?id=${staffId}`);
|
||||
}
|
||||
if (posId) {
|
||||
await request("POST", `/org-position/delete?id=${posId}`);
|
||||
}
|
||||
if (deptId) {
|
||||
await request("POST", `/org-department/delete?id=${deptId}`);
|
||||
}
|
||||
|
||||
assert("清理-完成", true);
|
||||
|
||||
console.log("\n========== 企业信息管理 接口联调测试 ==========\n");
|
||||
results.forEach((r) => {
|
||||
const icon = r.status === "PASS" ? "✓" : "✗";
|
||||
console.log(`${icon} [${r.status}] ${r.name}${r.detail ? ` — ${r.detail}` : ""}`);
|
||||
});
|
||||
console.log(`\n合计: ${passed + failed} 通过: ${passed} 失败: ${failed}\n`);
|
||||
process.exit(failed > 0 ? 1 : 0);
|
||||
}
|
||||
|
||||
runTests().catch((err) => {
|
||||
console.error("测试执行失败:", err.message);
|
||||
process.exit(1);
|
||||
});
|
||||
|
|
@ -1,371 +0,0 @@
|
|||
/**
|
||||
* 企业信息管理 - 细粒度接口联调测试
|
||||
*
|
||||
* 运行:
|
||||
* source scripts/setup-test-env.sh
|
||||
* node scripts/test-enterprise-info-granular.mjs
|
||||
*/
|
||||
const API_BASE = process.env.API_BASE || "http://127.0.0.1:8095/safetyEval";
|
||||
const TS = Date.now();
|
||||
|
||||
const results = [];
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
const createdIds = {};
|
||||
|
||||
/** 保持 ID 为字符串,避免雪花 ID 超出 JS 安全整数精度 */
|
||||
function normalizeId(id) {
|
||||
if (id === undefined || id === null || id === "") return undefined;
|
||||
return String(id);
|
||||
}
|
||||
|
||||
function record(id, name, condition, detail = "") {
|
||||
const status = condition ? "PASS" : "FAIL";
|
||||
if (condition) passed += 1;
|
||||
else failed += 1;
|
||||
results.push({ id, name, status, detail });
|
||||
}
|
||||
|
||||
async function request(method, path, body) {
|
||||
const url = `${API_BASE}${path}`;
|
||||
const options = {
|
||||
method,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
};
|
||||
if (body !== undefined) options.body = JSON.stringify(body);
|
||||
const res = await fetch(url, options);
|
||||
const text = await res.text();
|
||||
try {
|
||||
return { httpStatus: res.status, ...JSON.parse(text) };
|
||||
}
|
||||
catch {
|
||||
return { httpStatus: res.status, success: false, raw: text };
|
||||
}
|
||||
}
|
||||
|
||||
function assertSuccess(id, name, res, extra = "") {
|
||||
const ok = res.success === true && res.code === "0";
|
||||
record(id, name, ok, ok ? extra : (res.message || res.raw || `http=${res.httpStatus}`));
|
||||
return ok;
|
||||
}
|
||||
|
||||
function assertHasData(id, name, res, extra = "") {
|
||||
const ok = res.success === true && res.code === "0" && res.data != null;
|
||||
record(id, name, ok, ok ? extra : (res.message || "data 为空"));
|
||||
return ok;
|
||||
}
|
||||
|
||||
async function testPagination(id, name, path, minTotal = 1) {
|
||||
const sep = path.includes("?") ? "&" : "?";
|
||||
const page1 = await request("GET", `${path}${sep}current=1&size=2`);
|
||||
const ok1 = assertSuccess(`${id}-01`, `${name}-分页第1页`, page1, `total=${page1.total ?? page1.totalCount ?? "?"}`);
|
||||
const page2 = await request("GET", `${path}${sep}current=2&size=2`);
|
||||
assertSuccess(`${id}-02`, `${name}-分页第2页`, page2);
|
||||
if (ok1 && (page1.total ?? page1.totalCount ?? 0) >= minTotal) {
|
||||
record(`${id}-03`, `${name}-分页total有效`, true, `total=${page1.total ?? page1.totalCount}`);
|
||||
}
|
||||
return page1;
|
||||
}
|
||||
|
||||
async function run() {
|
||||
console.log(`\n=== 企业信息管理 细粒度接口测试 ===`);
|
||||
console.log(`API Base: ${API_BASE}`);
|
||||
console.log(`Timestamp: ${TS}\n`);
|
||||
|
||||
const orgPage = await testPagination("EI-API-ORG", "机构信息", "/org-info/page", 1);
|
||||
let orgId = orgPage.data?.[0]?.id;
|
||||
|
||||
if (orgId) {
|
||||
const orgGet = await request("GET", `/org-info/get?id=${normalizeId(orgId)}`);
|
||||
assertHasData("EI-API-ORG-04", "机构信息-详情", orgGet, `id=${orgId}`);
|
||||
const orgMod = await request("POST", "/org-info/modify", {
|
||||
...orgGet.data,
|
||||
principalName: orgGet.data.principalName,
|
||||
});
|
||||
assertSuccess("EI-API-ORG-05", "机构信息-修改", orgMod);
|
||||
}
|
||||
|
||||
await testPagination("EI-API-DEPT", "部门", "/org-department/page", 1);
|
||||
|
||||
const deptName = `联调部门-${TS}`;
|
||||
const deptSave = await request("POST", "/org-department/save", {
|
||||
deptName,
|
||||
managerName: "测试负责人",
|
||||
deptLevelName: "一级",
|
||||
});
|
||||
if (assertSuccess("EI-API-DEPT-04", "部门-新增", deptSave)) {
|
||||
createdIds.dept = normalizeId(deptSave.data?.id);
|
||||
const deptGet = await request("GET", `/org-department/get?id=${createdIds.dept}`);
|
||||
if (assertHasData("EI-API-DEPT-05", "部门-详情", deptGet)) {
|
||||
const deptMod = await request("POST", "/org-department/modify", {
|
||||
id: createdIds.dept,
|
||||
deptName: `${deptName}-改`,
|
||||
managerName: "测试负责人",
|
||||
deptLevelName: "一级",
|
||||
});
|
||||
assertSuccess("EI-API-DEPT-06", "部门-修改", deptMod);
|
||||
const deptSearch = await request("GET", `/org-department/page?current=1&size=10&deptName=${encodeURIComponent(deptName)}`);
|
||||
assertSuccess("EI-API-DEPT-07", "部门-名称搜索", deptSearch, `count=${deptSearch.data?.length ?? 0}`);
|
||||
}
|
||||
}
|
||||
|
||||
const posSave = await request("POST", "/org-position/save", {
|
||||
positionName: `联调岗位-${TS}`,
|
||||
dutyDesc: "测试职责",
|
||||
deptId: createdIds.dept || "102",
|
||||
});
|
||||
if (assertSuccess("EI-API-POS-01", "岗位-新增", posSave)) {
|
||||
createdIds.pos = normalizeId(posSave.data?.id);
|
||||
}
|
||||
await testPagination("EI-API-POS-PG", "岗位", "/org-position/page", 1);
|
||||
if (createdIds.pos) {
|
||||
const posGet = await request("GET", `/org-position/get?id=${createdIds.pos}`);
|
||||
if (assertHasData("EI-API-POS-04", "岗位-详情", posGet)) {
|
||||
const posMod = await request("POST", "/org-position/modify", {
|
||||
id: createdIds.pos,
|
||||
positionName: `联调岗位-${TS}-改`,
|
||||
dutyDesc: "已修改",
|
||||
deptId: createdIds.dept || posGet.data?.deptId,
|
||||
});
|
||||
assertSuccess("EI-API-POS-05", "岗位-修改", posMod);
|
||||
}
|
||||
}
|
||||
|
||||
const staffSave = await request("POST", "/org-personnel/save", {
|
||||
userName: `联调人员-${TS}`,
|
||||
account: `139${String(TS).slice(-8)}`,
|
||||
genderCode: 1,
|
||||
genderName: "男",
|
||||
employmentStatusCode: 1,
|
||||
employmentStatusName: "在职",
|
||||
deptId: createdIds.dept || "102",
|
||||
postId: createdIds.pos || "205",
|
||||
});
|
||||
if (assertSuccess("EI-API-STAFF-01", "人员-新增", staffSave)) {
|
||||
createdIds.staff = normalizeId(staffSave.data?.id);
|
||||
const staffGet = await request("GET", `/org-personnel/get?id=${createdIds.staff}`);
|
||||
if (assertHasData("EI-API-STAFF-02", "人员-详情", staffGet)) {
|
||||
record(
|
||||
"EI-API-STAFF-05",
|
||||
"人员-部门名称组装",
|
||||
!!staffGet.data.deptName,
|
||||
`deptName=${staffGet.data.deptName ?? "(空)"}`,
|
||||
);
|
||||
record(
|
||||
"EI-API-STAFF-06",
|
||||
"人员-岗位名称组装",
|
||||
!!staffGet.data.postName,
|
||||
`postName=${staffGet.data.postName ?? "(空)"}`,
|
||||
);
|
||||
const staffPage = await request("GET", `/org-personnel/page?current=1&size=10&userName=${encodeURIComponent("联调人员")}`);
|
||||
assertSuccess("EI-API-STAFF-03", "人员-姓名搜索", staffPage);
|
||||
const pageRow = staffPage.data?.[0];
|
||||
if (pageRow) {
|
||||
record("EI-API-STAFF-07", "人员列表-部门字段", !!pageRow.deptName, `deptName=${pageRow.deptName ?? "(空)"}`);
|
||||
record("EI-API-STAFF-08", "人员列表-岗位字段", !!pageRow.postName, `postName=${pageRow.postName ?? "(空)"}`);
|
||||
}
|
||||
const staffMod = await request("POST", "/org-personnel/modify", {
|
||||
id: createdIds.staff,
|
||||
userName: `联调人员-${TS}-改`,
|
||||
account: staffGet.data.account,
|
||||
genderCode: staffGet.data.genderCode,
|
||||
genderName: staffGet.data.genderName,
|
||||
employmentStatusCode: staffGet.data.employmentStatusCode,
|
||||
employmentStatusName: staffGet.data.employmentStatusName,
|
||||
deptId: staffGet.data.deptId,
|
||||
postId: staffGet.data.postId,
|
||||
});
|
||||
assertSuccess("EI-API-STAFF-04", "人员-修改", staffMod);
|
||||
if (staffMod.success) {
|
||||
const staffAfterMod = await request("GET", `/org-personnel/get?id=${createdIds.staff}`);
|
||||
record(
|
||||
"EI-API-STAFF-10",
|
||||
"人员-修改后变更次数",
|
||||
(staffAfterMod.data?.changeCount ?? 0) >= 1,
|
||||
`changeCount=${staffAfterMod.data?.changeCount ?? 0}`,
|
||||
);
|
||||
}
|
||||
const dupSave = await request("POST", "/org-personnel/save", {
|
||||
userName: `重复账号-${TS}`,
|
||||
account: staffGet.data.account,
|
||||
genderCode: 1,
|
||||
genderName: "男",
|
||||
employmentStatusCode: 1,
|
||||
employmentStatusName: "在职",
|
||||
deptId: staffGet.data.deptId,
|
||||
postId: staffGet.data.postId,
|
||||
});
|
||||
record(
|
||||
"EI-API-STAFF-11",
|
||||
"人员-账号重复校验",
|
||||
dupSave.success === false && String(dupSave.message || "").includes("账号"),
|
||||
dupSave.message || `code=${dupSave.code}`,
|
||||
);
|
||||
const resetPwd = await request("POST", `/org-personnel/reset-password?id=${normalizeId(createdIds.staff)}`);
|
||||
assertSuccess("EI-API-STAFF-09", "人员-重置密码", resetPwd);
|
||||
}
|
||||
}
|
||||
|
||||
if (createdIds.staff) {
|
||||
const certSave = await request("POST", "/org-personnel-cert/save", {
|
||||
personnelId: createdIds.staff,
|
||||
certName: `联调证书-${TS}`,
|
||||
certNo: `CERT-${TS}`,
|
||||
issueOrg: "测试机关",
|
||||
validStartDate: "2026-01-01",
|
||||
validEndDate: "2027-01-01",
|
||||
});
|
||||
if (assertSuccess("EI-API-CERT-01", "人员证书-新增", certSave)) {
|
||||
createdIds.cert = normalizeId(certSave.data?.id);
|
||||
await testPagination("EI-API-CERT", "人员证书", `/org-personnel-cert/page?personnelId=${createdIds.staff}`, 0);
|
||||
}
|
||||
}
|
||||
else {
|
||||
record("EI-API-CERT-01", "人员证书-新增", false, "跳过:无人员 id");
|
||||
}
|
||||
|
||||
await testPagination("EI-API-CHANGE", "人员变更", "/org-personnel-change/page", 0);
|
||||
|
||||
// 联调数据可能被审核操作污染,测试前重置种子离职单为「未审核」
|
||||
await request("POST", "/org-resign-apply/modify", {
|
||||
id: "701",
|
||||
personnelId: "403",
|
||||
auditStatusCode: 0,
|
||||
auditStatusName: "未审核",
|
||||
});
|
||||
|
||||
const equipSave = await request("POST", "/org-equipment/save", {
|
||||
deviceName: `联调设备-${TS}`,
|
||||
deviceModel: "T-001",
|
||||
manufacturer: "测试厂家",
|
||||
enableFlag: 1,
|
||||
});
|
||||
if (assertSuccess("EI-API-EQUIP-01", "装备-新增", equipSave)) {
|
||||
createdIds.equip = normalizeId(equipSave.data?.id);
|
||||
const equipGet = await request("GET", `/org-equipment/get?id=${createdIds.equip}`);
|
||||
if (assertHasData("EI-API-EQUIP-02", "装备-详情", equipGet)) {
|
||||
const toggle = equipGet.data?.enableFlag === 1 ? 0 : 1;
|
||||
const equipMod = await request("POST", "/org-equipment/modify", { ...equipGet.data, enableFlag: toggle });
|
||||
assertSuccess("EI-API-EQUIP-03", "装备-启停", equipMod);
|
||||
}
|
||||
}
|
||||
await testPagination("EI-API-EQUIP-PG", "装备", "/org-equipment/page", 1);
|
||||
const equipByInstrument = await request("GET", "/org-equipment/page?current=1&size=10&instrumentType=检测仪器");
|
||||
record(
|
||||
"EI-API-EQUIP-04",
|
||||
"装备-仪器类型搜索",
|
||||
equipByInstrument.success === true && (equipByInstrument.total ?? equipByInstrument.totalCount ?? 0) >= 1 && (equipByInstrument.total ?? equipByInstrument.totalCount ?? 0) < 6,
|
||||
`total=${equipByInstrument.total ?? equipByInstrument.totalCount ?? "?"}`,
|
||||
);
|
||||
const equipByDeviceType = await request("GET", "/org-equipment/page?current=1&size=10&deviceType=便携式");
|
||||
record(
|
||||
"EI-API-EQUIP-05",
|
||||
"装备-设备类型搜索",
|
||||
equipByDeviceType.success === true && (equipByDeviceType.total ?? equipByDeviceType.totalCount ?? 0) >= 1,
|
||||
`total=${equipByDeviceType.total ?? equipByDeviceType.totalCount ?? "?"}`,
|
||||
);
|
||||
|
||||
const personnelSeed = await request("GET", "/org-personnel/page?current=1&size=10&userName=钱离职");
|
||||
const seedRow = personnelSeed.data?.[0];
|
||||
if (seedRow) {
|
||||
record("EI-API-CHANGE-04", "人员-变更次数组装", (seedRow.changeCount ?? 0) >= 3, `changeCount=${seedRow.changeCount ?? 0}`);
|
||||
record("EI-API-CHANGE-05", "人员-就职状态组装", seedRow.employmentStatusCode === 2, `employmentStatusCode=${seedRow.employmentStatusCode}`);
|
||||
}
|
||||
const personnelResign = await request("GET", "/org-personnel/page?current=1&size=10&userName=钱离职");
|
||||
const resignRow = personnelResign.data?.[0];
|
||||
if (resignRow) {
|
||||
record("EI-API-CHANGE-06", "人员-离职审核状态", resignRow.resignAuditStatus === 0, `resignAuditStatus=${resignRow.resignAuditStatus ?? "(空)"}`);
|
||||
}
|
||||
const resignFilter = await request("GET", "/org-personnel/page?current=1&size=10&resignAuditStatus=0&userName=钱离职");
|
||||
record(
|
||||
"EI-API-CHANGE-07",
|
||||
"人员-离职审核状态筛选",
|
||||
resignFilter.success === true && (resignFilter.data?.length ?? 0) >= 1,
|
||||
`count=${resignFilter.data?.length ?? 0}`,
|
||||
);
|
||||
|
||||
const qualSave = await request("POST", "/org-qualification/save", {
|
||||
licenseTypeName: "测试证照",
|
||||
certName: `联调资质-${TS}`,
|
||||
certNo: `QUAL-${TS}`,
|
||||
issueOrg: "测试机关",
|
||||
issueDate: "2026-01-01",
|
||||
validStartDate: "2026-01-01",
|
||||
validEndDate: "2027-01-01",
|
||||
enableFlag: 1,
|
||||
});
|
||||
if (assertSuccess("EI-API-QUAL-01", "资质-新增", qualSave)) {
|
||||
createdIds.qual = normalizeId(qualSave.data?.id);
|
||||
}
|
||||
await testPagination("EI-API-QUAL-PG", "资质", "/org-qualification/page", 1);
|
||||
|
||||
if (createdIds.staff) {
|
||||
const resignSave = await request("POST", "/org-resign-apply/save", {
|
||||
personnelId: createdIds.staff,
|
||||
applicantName: `联调离职-${TS}`,
|
||||
applyTime: "2026-06-20T10:00:00",
|
||||
expectedResignDate: "2026-07-31",
|
||||
resignReason: "联调测试",
|
||||
auditStatusCode: 0,
|
||||
auditStatusName: "未审核",
|
||||
});
|
||||
if (assertSuccess("EI-API-RESIGN-01", "离职申请-新增", resignSave)) {
|
||||
createdIds.resign = normalizeId(resignSave.data?.id);
|
||||
const resignGet = await request("GET", `/org-resign-apply/get?id=${createdIds.resign}`);
|
||||
if (assertHasData("EI-API-RESIGN-03", "离职申请-详情", resignGet)) {
|
||||
record(
|
||||
"EI-API-RESIGN-04",
|
||||
"离职-审核状态字段",
|
||||
resignGet.data.auditStatusCode === 0,
|
||||
`auditStatusCode=${resignGet.data.auditStatusCode}`,
|
||||
);
|
||||
record(
|
||||
"EI-API-RESIGN-05",
|
||||
"离职-用户名字段组装",
|
||||
!!resignGet.data.account,
|
||||
`account=${resignGet.data.account ?? "(空)"}`,
|
||||
);
|
||||
}
|
||||
const audit = await request("POST", "/org-resign-apply/modify", {
|
||||
id: createdIds.resign,
|
||||
personnelId: createdIds.staff,
|
||||
auditStatusCode: 1,
|
||||
auditStatusName: "已审核",
|
||||
});
|
||||
assertSuccess("EI-API-RESIGN-02", "离职申请-审核", audit);
|
||||
}
|
||||
}
|
||||
else {
|
||||
record("EI-API-RESIGN-01", "离职申请-新增", false, "跳过:无人员 id");
|
||||
}
|
||||
await testPagination("EI-API-RESIGN-PG", "离职申请", "/org-resign-apply/page", 0);
|
||||
|
||||
const cleanup = [
|
||||
["cert", "/org-personnel-cert/delete"],
|
||||
["resign", "/org-resign-apply/delete"],
|
||||
["staff", "/org-personnel/delete"],
|
||||
["pos", "/org-position/delete"],
|
||||
["dept", "/org-department/delete"],
|
||||
["equip", "/org-equipment/delete"],
|
||||
["qual", "/org-qualification/delete"],
|
||||
];
|
||||
for (const [key, path] of cleanup) {
|
||||
if (createdIds[key]) {
|
||||
const del = await request("POST", `${path}?id=${createdIds[key]}`);
|
||||
assertSuccess(`EI-API-CLEAN-${key}`, `清理-${key}`, del);
|
||||
}
|
||||
}
|
||||
|
||||
console.log("\n========== 测试结果 ==========\n");
|
||||
results.forEach((r) => {
|
||||
const icon = r.status === "PASS" ? "✓" : "✗";
|
||||
console.log(`${icon} [${r.status}] ${r.id} ${r.name}${r.detail ? ` — ${r.detail}` : ""}`);
|
||||
});
|
||||
console.log(`\n合计: ${passed + failed} 通过: ${passed} 失败: ${failed}\n`);
|
||||
process.exit(failed > 0 ? 1 : 0);
|
||||
}
|
||||
|
||||
run().catch((err) => {
|
||||
console.error("测试执行失败:", err.message);
|
||||
process.exit(1);
|
||||
});
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
/**
|
||||
* 企业信息管理 - 页面 UI / 字段展示 自动化检查
|
||||
* 运行: node scripts/test-enterprise-info-ui-check.mjs
|
||||
*/
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
const ROOT = path.resolve(import.meta.dirname, "..");
|
||||
const PAGE_DIR = path.join(ROOT, "src/pages/Container/EnterpriseInfo");
|
||||
const UTILS = path.join(ROOT, "src/utils");
|
||||
|
||||
const checks = [];
|
||||
|
||||
function pass(id, name, ok, detail = "") {
|
||||
checks.push({ id, name, status: ok ? "PASS" : "FAIL", detail });
|
||||
}
|
||||
|
||||
function read(rel) {
|
||||
return fs.readFileSync(path.join(ROOT, rel), "utf8");
|
||||
}
|
||||
|
||||
function fileExists(rel) {
|
||||
return fs.existsSync(path.join(ROOT, rel));
|
||||
}
|
||||
|
||||
// ── 公共能力 ──
|
||||
pass("UI-COMMON-01", "mockUpload 默认地址", fileExists("src/utils/mockUpload.js"));
|
||||
pass("UI-COMMON-02", "enterpriseForm 下拉适配", fileExists("src/utils/enterpriseForm.js"));
|
||||
pass("UI-COMMON-03", "DEFAULT_UPLOAD_FILE_URL 配置",
|
||||
read("src/utils/mockUpload.js").includes("gd-hbimg.huaban.com"));
|
||||
|
||||
// ── 各页面 ──
|
||||
const pages = [
|
||||
["OrgInfo", "机构信息"],
|
||||
["DepartmentPosition", "部门岗位"],
|
||||
["PersonnelInfo/List", "人员信息"],
|
||||
["PersonnelChange", "人员变更"],
|
||||
["ResignationApply", "离职申请"],
|
||||
["QualificationCert", "资质证书"],
|
||||
["EquipInfo", "装备信息"],
|
||||
];
|
||||
|
||||
pages.forEach(([rel, label]) => {
|
||||
const content = read(`src/pages/Container/EnterpriseInfo/${rel}/index.js`);
|
||||
pass(`UI-PAGE-${rel}-01`, `${label}-页面存在`, content.length > 100);
|
||||
pass(`UI-PAGE-${rel}-02`, `${label}-使用 PageHeader`, content.includes("PageHeader"));
|
||||
const usesVerticalForm = content.includes('layout="vertical"');
|
||||
const usesBrokenLabelCol = /labelCol=\{\{\s*span:\s*24/.test(content);
|
||||
const usesFormBuilderModal = /Modal[\s\S]*FormBuilder/.test(content);
|
||||
pass(`UI-PAGE-${rel}-03`, `${label}-弹窗表单布局`, usesVerticalForm || !usesFormBuilderModal || !usesBrokenLabelCol,
|
||||
usesFormBuilderModal && !usesVerticalForm && usesBrokenLabelCol ? "Modal 内 FormBuilder labelCol=24 导致输入框不可见" : "");
|
||||
});
|
||||
|
||||
// ── 人员:部门岗位下拉 ──
|
||||
const staffPage = read("src/pages/Container/EnterpriseInfo/PersonnelInfo/List/index.js");
|
||||
pass("UI-STAFF-01", "人员-部门 Select", staffPage.includes("deptId") && staffPage.includes("Select"));
|
||||
pass("UI-STAFF-02", "人员-岗位 Select", staffPage.includes("positionId") && staffPage.includes("Select"));
|
||||
pass("UI-STAFF-03", "人员-列表部门列", staffPage.includes('dataIndex: "deptName"'));
|
||||
pass("UI-STAFF-04", "人员-重置密码", staffPage.includes("onResetPassword"));
|
||||
|
||||
// ── 离职 ──
|
||||
const resignPage = read("src/pages/Container/EnterpriseInfo/ResignationApply/index.js");
|
||||
pass("UI-RESIGN-01", "离职-审核状态列", resignPage.includes("auditStatus"));
|
||||
pass("UI-RESIGN-02", "离职-用户名列", resignPage.includes('dataIndex: "account"'));
|
||||
pass("UI-RESIGN-03", "离职-默认上传", resignPage.includes("resolveUploadFileId") || resignPage.includes("mockUpload"));
|
||||
|
||||
// ── 变更次数样式 ──
|
||||
const changePage = read("src/pages/Container/EnterpriseInfo/PersonnelChange/index.js");
|
||||
pass("UI-CHANGE-01", "变更-次数样式", changePage.includes("CHANGE_COUNT_STYLE"));
|
||||
pass("UI-CHANGE-02", "变更-从 record 渲染", changePage.includes("getChangeCount(record)"));
|
||||
pass("UI-CHANGE-03", "变更-就职/审核状态", changePage.includes("getEmploymentStatusLabel") && changePage.includes("getResignAuditStatusLabel"));
|
||||
|
||||
pass("UI-RESIGN-04", "离职-从 record 渲染审核状态", resignPage.includes("getResignAuditStatusLabel(record)"));
|
||||
pass("UI-RESIGN-05", "离职-纯文本状态", !resignPage.includes("<Tag"));
|
||||
|
||||
// ── 输出 ──
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
console.log("\n========== 企业信息 UI 静态检查 ==========\n");
|
||||
checks.forEach((c) => {
|
||||
const icon = c.status === "PASS" ? "✓" : "✗";
|
||||
if (c.status === "PASS") passed += 1;
|
||||
else failed += 1;
|
||||
console.log(`${icon} [${c.status}] ${c.id} ${c.name}${c.detail ? ` — ${c.detail}` : ""}`);
|
||||
});
|
||||
console.log(`\n合计: ${checks.length} 通过: ${passed} 失败: ${failed}\n`);
|
||||
process.exit(failed > 0 ? 1 : 0);
|
||||
|
|
@ -1,278 +0,0 @@
|
|||
/**
|
||||
* 资质申请管理 — 前后端联调冒烟测试
|
||||
*
|
||||
* 运行:
|
||||
* API_BASE=http://127.0.0.1:8095/safetyEval ORG_INFO_ID=1 node docs/test-reports/测试用例/test-qual-filing-api.mjs
|
||||
*
|
||||
* 可选:
|
||||
* FRONTEND_BASE=http://127.0.0.1:8081 (检测前端页面是否可访问)
|
||||
*/
|
||||
const API_BASE = process.env.API_BASE || "http://127.0.0.1:8095/safetyEval";
|
||||
const ORG_INFO_ID = process.env.ORG_INFO_ID || "1";
|
||||
const FRONTEND_BASE = process.env.FRONTEND_BASE || "http://127.0.0.1:8081";
|
||||
const DEFAULT_URL =
|
||||
"https://s1.aigei.com/prevfiles/6f2754563be2491bad7c957904cd7d2a.jpg?e=2051020800&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:CR60zg7VKDp-jUWyYls_rdvyQMM=";
|
||||
|
||||
const results = [];
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
|
||||
function assert(name, condition, detail = "") {
|
||||
if (condition) {
|
||||
passed += 1;
|
||||
results.push({ name, status: "PASS", detail });
|
||||
console.log(` ✓ ${name}${detail ? ` — ${detail}` : ""}`);
|
||||
}
|
||||
else {
|
||||
failed += 1;
|
||||
results.push({ name, status: "FAIL", detail });
|
||||
console.log(` ✗ ${name}${detail ? ` — ${detail}` : ""}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function request(method, path, body) {
|
||||
const url = `${API_BASE}${path}`;
|
||||
const options = {
|
||||
method,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
orgInfoId: ORG_INFO_ID,
|
||||
},
|
||||
};
|
||||
if (body !== undefined) {
|
||||
options.body = JSON.stringify(body);
|
||||
}
|
||||
const res = await fetch(url, options);
|
||||
const text = await res.text();
|
||||
try {
|
||||
return { httpStatus: res.status, ...JSON.parse(text) };
|
||||
}
|
||||
catch {
|
||||
return { httpStatus: res.status, success: false, raw: text };
|
||||
}
|
||||
}
|
||||
|
||||
async function uploadAllMaterials(filingId) {
|
||||
const detail = await request("GET", `/qual-filing/detail?id=${filingId}`);
|
||||
const materials = detail.data?.materials || [];
|
||||
for (const material of materials) {
|
||||
const uploaded = await request("POST", "/qual-filing-material/upload", {
|
||||
id: material.id,
|
||||
attachmentUrl: DEFAULT_URL,
|
||||
});
|
||||
if (uploaded.success !== true) {
|
||||
return { ok: false, message: uploaded.message, materialId: material.id };
|
||||
}
|
||||
}
|
||||
return { ok: true, count: materials.length };
|
||||
}
|
||||
|
||||
async function saveCommitment(filingId) {
|
||||
return request("POST", "/qual-filing-commitment/save-or-update", {
|
||||
filingId,
|
||||
signDate: "2026-06-30",
|
||||
legalRepSignatureUrl: DEFAULT_URL,
|
||||
commitmentContent: "联调测试承诺书",
|
||||
});
|
||||
}
|
||||
|
||||
async function runBackendTests() {
|
||||
console.log("\n=== 后端 API(资质申请管理)===");
|
||||
console.log(`API_BASE=${API_BASE} orgInfoId=${ORG_INFO_ID}\n`);
|
||||
|
||||
// 1. 创建草稿 + 材料模板
|
||||
const draft = await request("POST", "/qual-filing/draft?applyTypeCode=1");
|
||||
assert("备案申请-创建草稿", draft.success === true && draft.data?.id, draft.message || `id=${draft.data?.id}`);
|
||||
const filingId = draft.data?.id;
|
||||
if (!filingId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const initTpl = await request("POST", `/qual-filing-material/init-template?filingId=${filingId}`);
|
||||
assert("备案申请-初始化材料模板", initTpl.success === true, initTpl.message);
|
||||
|
||||
const detail1 = await request("GET", `/qual-filing/detail?id=${filingId}`);
|
||||
assert("备案申请-详情含10项材料", detail1.data?.materials?.length === 10, `count=${detail1.data?.materials?.length}`);
|
||||
|
||||
// 2. 暂存基本信息
|
||||
const saved = await request("POST", "/qual-filing/save-draft", {
|
||||
id: filingId,
|
||||
filingUnitName: "脚本联调评价机构",
|
||||
filingTerritoryName: "渝北区",
|
||||
filingTerritoryCode: "渝北区",
|
||||
businessScope: "化工",
|
||||
});
|
||||
assert("备案申请-暂存基本信息", saved.success === true, saved.message);
|
||||
|
||||
// 3. 备案申请分页(applyTypeCode=1)
|
||||
const appPage = await request("GET", `/qual-filing/page?current=1&size=20&applyTypeCode=1`);
|
||||
const appTypes = new Set((appPage.data || []).map((row) => row.applyTypeCode));
|
||||
assert(
|
||||
"备案申请-分页仅 applyType=1",
|
||||
appPage.success === true && appTypes.size <= 1 && (appTypes.size === 0 || appTypes.has(1)),
|
||||
`types=${[...appTypes].join(",")} total=${appPage.total}`,
|
||||
);
|
||||
|
||||
// 4. 上传材料 + 承诺书 + 提交
|
||||
const uploadRes = await uploadAllMaterials(filingId);
|
||||
assert("备案申请-上传全部材料", uploadRes.ok === true, uploadRes.message || `count=${uploadRes.count}`);
|
||||
|
||||
const commitment = await saveCommitment(filingId);
|
||||
assert("备案申请-保存承诺书", commitment.success === true, commitment.message);
|
||||
|
||||
const submit = await request("POST", `/qual-filing/submit?id=${filingId}`);
|
||||
assert(
|
||||
"备案申请-提交审核",
|
||||
submit.success === true && submit.data?.filingStatusCode === 2,
|
||||
submit.message || submit.data?.filingStatusName,
|
||||
);
|
||||
|
||||
// 5. 审核通过 → 生成已备案记录
|
||||
const approve = await request("POST", `/qual-filing/approve?id=${filingId}`);
|
||||
assert(
|
||||
"备案申请-审核通过",
|
||||
approve.success === true && approve.data?.filingStatusCode === 1 && approve.data?.filingStatusName === "已备案",
|
||||
approve.message || approve.data?.filingStatusName,
|
||||
);
|
||||
|
||||
const filedPage = await request("GET", "/qual-filing/filed/page?current=1&size=20");
|
||||
const filedTypes = new Set((filedPage.data || []).map((row) => row.applyTypeCode));
|
||||
const filedStatuses = new Set((filedPage.data || []).map((row) => row.filingStatusCode));
|
||||
assert(
|
||||
"已备案-分页仅 applyType=3",
|
||||
filedPage.success === true && [...filedTypes].every((t) => t === 3),
|
||||
`types=${[...filedTypes].join(",")} count=${filedPage.data?.length}`,
|
||||
);
|
||||
assert(
|
||||
"已备案-默认不含暂存",
|
||||
![...filedStatuses].includes(5),
|
||||
`statuses=${[...filedStatuses].join(",")}`,
|
||||
);
|
||||
|
||||
const originFilingId = filedPage.data?.[0]?.id;
|
||||
assert("已备案-存在记录", !!originFilingId, `originId=${originFilingId}`);
|
||||
|
||||
// 6. 已备案填报草稿
|
||||
const filedDraft = await request("POST", "/qual-filing/filed/draft");
|
||||
assert("已备案-创建填报草稿", filedDraft.success === true && filedDraft.data?.id, filedDraft.message);
|
||||
assert(
|
||||
"已备案-填报草稿为暂存",
|
||||
filedDraft.data?.filingStatusCode === 5,
|
||||
filedDraft.data?.filingStatusName,
|
||||
);
|
||||
|
||||
const changePage = await request("GET", "/qual-filing/change/page?current=1&size=20");
|
||||
const changeStatuses = new Set((changePage.data || []).map((row) => row.filingStatusCode));
|
||||
assert(
|
||||
"备案变更-列表仅已备案/变更审核中",
|
||||
changePage.success === true && [...changeStatuses].every((s) => s === 1 || s === 4),
|
||||
`statuses=${[...changeStatuses].join(",")}`,
|
||||
);
|
||||
|
||||
if (!originFilingId) {
|
||||
return filingId;
|
||||
}
|
||||
|
||||
// 7. 备案变更
|
||||
const changeStart = await request("POST", `/qual-filing-change/start?originFilingId=${originFilingId}`);
|
||||
const draftFilingId = changeStart.data;
|
||||
assert("备案变更-发起变更", changeStart.success === true && draftFilingId, `draftId=${draftFilingId}`);
|
||||
|
||||
if (draftFilingId) {
|
||||
await request("POST", "/qual-filing/save-draft", {
|
||||
id: draftFilingId,
|
||||
businessScope: "化工, 矿山",
|
||||
});
|
||||
await uploadAllMaterials(draftFilingId);
|
||||
await saveCommitment(draftFilingId);
|
||||
|
||||
const changeSubmit = await request("POST", "/qual-filing-change/submit", { draftFilingId });
|
||||
assert("备案变更-提交", changeSubmit.success === true, changeSubmit.message);
|
||||
|
||||
const history = await request("GET", `/qual-filing-change/history?originFilingId=${originFilingId}`);
|
||||
assert(
|
||||
"备案变更-历史明细",
|
||||
history.success === true && (history.data?.changeCount ?? 0) >= 1,
|
||||
`changeCount=${history.data?.changeCount} records=${history.data?.records?.length}`,
|
||||
);
|
||||
}
|
||||
|
||||
// 8. 人员/装备批量引入(有数据则测)
|
||||
const personnelPage = await request("GET", "/org-personnel/page?current=1&size=1");
|
||||
const personnelId = personnelPage.data?.[0]?.id;
|
||||
if (personnelId) {
|
||||
const newDraft = await request("POST", "/qual-filing/draft?applyTypeCode=1");
|
||||
const pid = newDraft.data?.id;
|
||||
const batchP = await request("POST", "/qual-filing-personnel/batch-add-from-org", {
|
||||
filingId: pid,
|
||||
sourcePersonnelIds: [personnelId],
|
||||
});
|
||||
assert("备案人员-批量引入", batchP.success === true, batchP.message);
|
||||
}
|
||||
else {
|
||||
assert("备案人员-批量引入", true, "跳过:无企业人员数据");
|
||||
}
|
||||
|
||||
const equipPage = await request("GET", "/org-equipment/page?current=1&size=1");
|
||||
const equipId = equipPage.data?.[0]?.id;
|
||||
if (equipId) {
|
||||
const newDraft2 = await request("POST", "/qual-filing/draft?applyTypeCode=1");
|
||||
const eid = newDraft2.data?.id;
|
||||
const batchE = await request("POST", "/qual-filing-equipment/batch-add-from-org", {
|
||||
filingId: eid,
|
||||
sourceEquipmentIds: [equipId],
|
||||
});
|
||||
assert("备案装备-批量引入", batchE.success === true, batchE.message);
|
||||
}
|
||||
else {
|
||||
assert("备案装备-批量引入", true, "跳过:无企业装备数据");
|
||||
}
|
||||
|
||||
return filingId;
|
||||
}
|
||||
|
||||
async function runFrontendSmokeTests() {
|
||||
console.log("\n=== 前端页面可访问性 ===");
|
||||
console.log(`FRONTEND_BASE=${FRONTEND_BASE}\n`);
|
||||
|
||||
const pages = [
|
||||
["/certificate/container/qualApplication/filingApplication/list", "资质备案申请列表"],
|
||||
["/certificate/container/qualApplication/filedManage/list", "已备案资质管理列表"],
|
||||
["/certificate/container/qualApplication/filingChange/list", "备案变更管理列表"],
|
||||
];
|
||||
|
||||
for (const [path, label] of pages) {
|
||||
try {
|
||||
const res = await fetch(`${FRONTEND_BASE}${path}`, { method: "GET" });
|
||||
assert(`前端-${label}`, res.status === 200, `HTTP ${res.status}`);
|
||||
}
|
||||
catch (err) {
|
||||
assert(`前端-${label}`, false, err.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log("资质申请管理 — 联调冒烟测试");
|
||||
try {
|
||||
await runBackendTests();
|
||||
}
|
||||
catch (err) {
|
||||
failed += 1;
|
||||
console.error("\n后端测试异常:", err);
|
||||
}
|
||||
|
||||
try {
|
||||
await runFrontendSmokeTests();
|
||||
}
|
||||
catch (err) {
|
||||
console.warn("\n前端页面检测跳过:", err.message);
|
||||
}
|
||||
|
||||
console.log(`\n=== 结果: ${passed} 通过, ${failed} 失败 ===\n`);
|
||||
if (failed > 0) {
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
|
|
@ -1,199 +0,0 @@
|
|||
/**
|
||||
* 资质申请管理 — 三模块审核接口 + 整体流程联调
|
||||
*
|
||||
* 运行:
|
||||
* API_BASE=http://127.0.0.1:8095/safetyEval ORG_INFO_ID=1 node docs/test-reports/测试用例/test-qual-filing-audit-flow.mjs
|
||||
*/
|
||||
const API_BASE = process.env.API_BASE || "http://127.0.0.1:8095/safetyEval";
|
||||
const ORG_INFO_ID = process.env.ORG_INFO_ID || "1";
|
||||
const DEFAULT_URL =
|
||||
"https://s1.aigei.com/prevfiles/6f2754563be2491bad7c957904cd7d2a.jpg?e=2051020800&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:CR60zg7VKDp-jUWyYls_rdvyQMM=";
|
||||
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
|
||||
function ok(name, cond, detail = "") {
|
||||
if (cond) {
|
||||
passed += 1;
|
||||
console.log(` ✓ ${name}${detail ? ` — ${detail}` : ""}`);
|
||||
}
|
||||
else {
|
||||
failed += 1;
|
||||
console.log(` ✗ ${name}${detail ? ` — ${detail}` : ""}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function api(method, path, body) {
|
||||
const res = await fetch(`${API_BASE}${path}`, {
|
||||
method,
|
||||
headers: { "Content-Type": "application/json", orgInfoId: ORG_INFO_ID },
|
||||
body: body !== undefined ? JSON.stringify(body) : undefined,
|
||||
});
|
||||
const text = await res.text();
|
||||
try {
|
||||
return { httpStatus: res.status, ...JSON.parse(text) };
|
||||
}
|
||||
catch {
|
||||
return { httpStatus: res.status, success: false, raw: text };
|
||||
}
|
||||
}
|
||||
|
||||
async function uploadAllMaterials(filingId) {
|
||||
const detail = await api("GET", `/qual-filing/detail?id=${filingId}`);
|
||||
for (const m of detail.data?.materials || []) {
|
||||
if (!m.attachmentUrl) {
|
||||
await api("POST", "/qual-filing-material/upload", { id: m.id, attachmentUrl: DEFAULT_URL });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function saveCommitment(filingId) {
|
||||
return api("POST", "/qual-filing-commitment/save-or-update", {
|
||||
filingId,
|
||||
signDate: "2026-06-30",
|
||||
legalRepSignatureUrl: DEFAULT_URL,
|
||||
commitmentContent: "审核流程联调测试承诺书",
|
||||
});
|
||||
}
|
||||
|
||||
async function persistMinimal(filingId) {
|
||||
await api("POST", "/qual-filing/save-draft", {
|
||||
id: filingId,
|
||||
filingUnitName: "审核流程测试机构",
|
||||
filingTerritoryName: "渝北区",
|
||||
businessScope: "化工",
|
||||
});
|
||||
await api("POST", `/qual-filing-material/init-template?filingId=${filingId}`);
|
||||
await uploadAllMaterials(filingId);
|
||||
await saveCommitment(filingId);
|
||||
}
|
||||
|
||||
async function testModule1ApplicationAudit() {
|
||||
console.log("\n=== 模块1:资质备案申请(提交 + 审核) ===");
|
||||
|
||||
const draft = await api("POST", "/qual-filing/draft?applyTypeCode=1");
|
||||
const filingId = draft.data?.id;
|
||||
ok("1.创建申请草稿", draft.success && filingId, `id=${filingId}`);
|
||||
if (!filingId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
await persistMinimal(filingId);
|
||||
|
||||
const submit = await api("POST", `/qual-filing/submit?id=${filingId}`);
|
||||
ok("2.提交审核 POST /qual-filing/submit", submit.success && submit.data?.filingStatusCode === 2, submit.data?.filingStatusName);
|
||||
|
||||
const approve = await api("POST", `/qual-filing/approve?id=${filingId}`);
|
||||
ok(
|
||||
"3.审核通过 POST /qual-filing/approve",
|
||||
approve.success && approve.data?.filingStatusCode === 1,
|
||||
approve.data?.filingStatusName,
|
||||
);
|
||||
|
||||
const filedPage = await api("GET", "/qual-filing/filed/page?current=1&size=5");
|
||||
const master = (filedPage.data || []).find((r) => r.filingStatusCode === 1);
|
||||
ok("4.审核后生成已备案主记录", !!master, master ? `masterId=${master.id}` : "filed/page 无已备案");
|
||||
|
||||
return { applicationId: filingId, masterId: master?.id };
|
||||
}
|
||||
|
||||
async function testModule2FiledManageAudit(masterId) {
|
||||
console.log("\n=== 模块2:已备案资质管理(填报提交 + 审核) ===");
|
||||
if (!masterId) {
|
||||
ok("跳过-无已备案主记录", false);
|
||||
return masterId;
|
||||
}
|
||||
|
||||
const filedDraft = await api("POST", "/qual-filing/filed/draft");
|
||||
const draftId = filedDraft.data?.id;
|
||||
ok("1.创建填报草稿 POST /qual-filing/filed/draft", filedDraft.success && draftId, `id=${draftId}`);
|
||||
if (!draftId) {
|
||||
return masterId;
|
||||
}
|
||||
|
||||
await persistMinimal(draftId);
|
||||
|
||||
const submit = await api("POST", `/qual-filing/submit?id=${draftId}`);
|
||||
ok("2.提交填报审核 POST /qual-filing/submit", submit.success && submit.data?.filingStatusCode === 2, submit.data?.filingStatusName);
|
||||
|
||||
const approve = await api("POST", `/qual-filing/approve?id=${draftId}`);
|
||||
ok(
|
||||
"3.填报审核通过 POST /qual-filing/approve",
|
||||
approve.success && approve.data?.filingStatusCode === 1,
|
||||
approve.data?.filingStatusName,
|
||||
);
|
||||
|
||||
return masterId;
|
||||
}
|
||||
|
||||
async function testModule3ChangeAudit(originId) {
|
||||
console.log("\n=== 模块3:备案变更管理(变更提交 + 审核) ===");
|
||||
if (!originId) {
|
||||
ok("跳过-无变更源记录", false);
|
||||
return;
|
||||
}
|
||||
|
||||
const originBefore = await api("GET", `/qual-filing/get?id=${originId}`);
|
||||
ok("1.变更源为已备案", originBefore.data?.filingStatusCode === 1, originBefore.data?.filingStatusName);
|
||||
|
||||
const start = await api("POST", `/qual-filing-change/start?originFilingId=${originId}`);
|
||||
const changeDraftId = start.data;
|
||||
ok("2.发起变更 POST /qual-filing-change/start", start.success && changeDraftId, `draftId=${changeDraftId}`);
|
||||
if (!changeDraftId) {
|
||||
return;
|
||||
}
|
||||
|
||||
await api("POST", "/qual-filing/save-draft", {
|
||||
id: changeDraftId,
|
||||
businessScope: "化工, 矿山采选业",
|
||||
unitIntro: "变更后业务范围说明",
|
||||
});
|
||||
await uploadAllMaterials(changeDraftId);
|
||||
await saveCommitment(changeDraftId);
|
||||
|
||||
const changeSubmit = await api("POST", "/qual-filing-change/submit", { draftFilingId: changeDraftId });
|
||||
ok("3.提交变更审核 POST /qual-filing-change/submit", changeSubmit.success === true, changeSubmit.message);
|
||||
|
||||
const changePage = await api(
|
||||
"GET",
|
||||
`/qual-filing-change/page?current=1&size=5&originFilingId=${originId}&filingStatusCode=2`,
|
||||
);
|
||||
const pendingChange = (changePage.data || [])[0];
|
||||
const changeId = pendingChange?.id;
|
||||
ok("4.查询待审变更单 GET /qual-filing-change/page", !!changeId, `changeId=${changeId}`);
|
||||
|
||||
if (!changeId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const changeApprove = await api("POST", `/qual-filing-change/approve?changeId=${changeId}`);
|
||||
ok("5.变更审核通过 POST /qual-filing-change/approve", changeApprove.success === true, changeApprove.message);
|
||||
|
||||
const originAfter = await api("GET", `/qual-filing/get?id=${originId}`);
|
||||
ok(
|
||||
"6.变更通过后主记录恢复已备案",
|
||||
originAfter.data?.filingStatusCode === 1,
|
||||
originAfter.data?.filingStatusName,
|
||||
);
|
||||
ok(
|
||||
"7.变更内容已生效",
|
||||
(originAfter.data?.businessScope || "").includes("矿山"),
|
||||
originAfter.data?.businessScope,
|
||||
);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log("资质申请管理 — 三模块审核接口整体流程测试");
|
||||
console.log(`API_BASE=${API_BASE} orgInfoId=${ORG_INFO_ID}`);
|
||||
|
||||
const ctx = await testModule1ApplicationAudit();
|
||||
const masterId = await testModule2FiledManageAudit(ctx?.masterId);
|
||||
await testModule3ChangeAudit(masterId);
|
||||
|
||||
console.log(`\n=== 结果: ${passed} 通过, ${failed} 失败 ===`);
|
||||
if (failed > 0) {
|
||||
process.exitCode = 1;
|
||||
}
|
||||
}
|
||||
|
||||
await main();
|
||||
|
|
@ -1,410 +0,0 @@
|
|||
/**
|
||||
* 资质申请管理 — 三模块完整流程联调(模拟前端 persist 链路)
|
||||
*
|
||||
* 运行: node docs/test-reports/测试用例/test-qual-filing-full-e2e.mjs
|
||||
*/
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const ROOT = path.resolve(__dirname, "../../..");
|
||||
|
||||
const API_BASE = process.env.API_BASE || "http://127.0.0.1:8095/safetyEval";
|
||||
const ORG_INFO_ID = process.env.ORG_INFO_ID || "1";
|
||||
const DEFAULT_URL =
|
||||
"https://s1.aigei.com/prevfiles/6f2754563be2491bad7c957904cd7d2a.jpg?e=2051020800&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:CR60zg7VKDp-jUWyYls_rdvyQMM=";
|
||||
|
||||
const issues = [];
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
const ctx = {};
|
||||
|
||||
function log(step, msg) {
|
||||
console.log(`[${step}] ${msg}`);
|
||||
}
|
||||
|
||||
function pass(name, detail = "") {
|
||||
passed += 1;
|
||||
console.log(` ✓ ${name}${detail ? ` — ${detail}` : ""}`);
|
||||
}
|
||||
|
||||
function fail(name, detail = "") {
|
||||
failed += 1;
|
||||
console.log(` ✗ ${name}${detail ? ` — ${detail}` : ""}`);
|
||||
}
|
||||
|
||||
function recordIssue(module, severity, title, detail) {
|
||||
issues.push({ module, severity, title, detail });
|
||||
}
|
||||
|
||||
async function api(method, apiPath, body) {
|
||||
const res = await fetch(`${API_BASE}${apiPath}`, {
|
||||
method,
|
||||
headers: { "Content-Type": "application/json", orgInfoId: ORG_INFO_ID },
|
||||
body: body !== undefined ? JSON.stringify(body) : undefined,
|
||||
});
|
||||
const text = await res.text();
|
||||
try {
|
||||
return { httpStatus: res.status, ...JSON.parse(text) };
|
||||
}
|
||||
catch {
|
||||
return { httpStatus: res.status, success: false, raw: text };
|
||||
}
|
||||
}
|
||||
|
||||
async function uploadAllMaterials(filingId) {
|
||||
const detail = await api("GET", `/qual-filing/detail?id=${filingId}`);
|
||||
for (const m of detail.data?.materials || []) {
|
||||
if (!m.attachmentUrl) {
|
||||
await api("POST", "/qual-filing-material/upload", { id: m.id, attachmentUrl: DEFAULT_URL });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const FORMAL_BASIC = {
|
||||
filingTerritoryName: "渝北区",
|
||||
filingTerritoryCode: "渝北区",
|
||||
filingUnitName: "重庆渝安安全评价技术有限公司",
|
||||
filingUnitTypeCode: "本地单位",
|
||||
filingUnitTypeName: "本地单位",
|
||||
businessScope: "石油加工业,化学原料、化学品及医药制造业",
|
||||
registerAddress: "重庆市渝北区黄山大道中段88号",
|
||||
officeAddress: "重庆市渝北区黄山大道中段88号安全评价大厦12层",
|
||||
creditCode: "91500000MA5U8K9X2L",
|
||||
qualCertNo: "AQPJ-渝-2024-0018",
|
||||
legalPersonPhone: "王建国 / 023-67891234",
|
||||
contactPhone: "陈晓明 / 13912345678",
|
||||
infoDisclosureUrl: "https://www.yuan-an-safety.com/disclosure",
|
||||
fixedAssetAmount: 1580,
|
||||
workplaceArea: 1860,
|
||||
archiveRoomArea: 120,
|
||||
fulltimeEvaluatorCount: 32,
|
||||
registeredEngineerCount: 12,
|
||||
unitIntro: "本公司成立于2018年,主要从事安全评价、安全技术服务,具备独立法人资格。",
|
||||
attachmentUrl: DEFAULT_URL,
|
||||
};
|
||||
|
||||
async function persistLikeFrontend(filingId, mode = "application") {
|
||||
const saveRes = await api("POST", "/qual-filing/save-draft", { id: filingId, ...FORMAL_BASIC });
|
||||
if (!saveRes.success) {
|
||||
throw new Error(saveRes.message || "save-draft failed");
|
||||
}
|
||||
|
||||
await api("POST", `/qual-filing-material/init-template?filingId=${filingId}`);
|
||||
await uploadAllMaterials(filingId);
|
||||
|
||||
const commitmentContent =
|
||||
"本人是王建国是重庆渝安安全评价技术有限公司法定代表人,现代表我单位承诺如下:本单位提交的资质备案申请材料真实、准确、完整。";
|
||||
await api("POST", "/qual-filing-commitment/save-or-update", {
|
||||
filingId,
|
||||
signDate: "2026-06-30",
|
||||
legalRepSignatureUrl: DEFAULT_URL,
|
||||
commitmentContent,
|
||||
});
|
||||
|
||||
const personnelPage = await api("GET", "/org-personnel/page?current=1&size=20");
|
||||
const personnelIds = (personnelPage.data || []).map((p) => p.id).filter(Boolean);
|
||||
if (personnelIds.length) {
|
||||
await api("POST", "/qual-filing-personnel/batch-add-from-org", {
|
||||
filingId,
|
||||
sourcePersonnelIds: personnelIds,
|
||||
});
|
||||
}
|
||||
|
||||
const equipPage = await api("GET", "/org-equipment/page?current=1&size=20");
|
||||
const equipIds = (equipPage.data || []).map((e) => e.id).filter(Boolean);
|
||||
if (equipIds.length) {
|
||||
await api("POST", "/qual-filing-equipment/batch-add-from-org", {
|
||||
filingId,
|
||||
sourceEquipmentIds: equipIds,
|
||||
});
|
||||
}
|
||||
|
||||
return api("GET", `/qual-filing/detail?id=${filingId}`);
|
||||
}
|
||||
|
||||
// ── 模块1:资质备案申请 ──
|
||||
async function testModule1Application() {
|
||||
console.log("\n=== 模块1:资质备案申请 ===");
|
||||
|
||||
const existingPage = await api("GET", "/qual-filing/page?current=1&size=5&applyTypeCode=1&filingStatusCode=5");
|
||||
let filingId = existingPage.data?.[0]?.id;
|
||||
|
||||
if (filingId) {
|
||||
pass("复用已有暂存记录", `id=${filingId}`);
|
||||
}
|
||||
else {
|
||||
const draft = await api("POST", "/qual-filing/draft?applyTypeCode=1");
|
||||
filingId = draft.data?.id;
|
||||
if (draft.success && filingId) {
|
||||
pass("新建申请草稿", `id=${filingId}`);
|
||||
}
|
||||
else {
|
||||
fail("新建申请草稿", draft.message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ctx.applicationId = filingId;
|
||||
|
||||
const detailRes = await persistLikeFrontend(filingId);
|
||||
const detail = detailRes.data || {};
|
||||
pass("暂存-基本信息", detail.filingUnitName);
|
||||
pass("暂存-材料10项已上传", (detail.materials || []).every((m) => m.uploadStatusCode === 2));
|
||||
pass("暂存-承诺书", !!detail.commitment?.commitmentContent);
|
||||
pass("暂存-人员", `count=${(detail.personnelList || []).length}`);
|
||||
pass("暂存-装备", `count=${(detail.equipmentList || []).length}`);
|
||||
|
||||
const listRes = await api("GET", "/qual-filing/page?current=1&size=20&applyTypeCode=1");
|
||||
const inList = (listRes.data || []).some((r) => String(r.id) === String(filingId));
|
||||
if (inList && detail.filingStatusCode === 5) {
|
||||
pass("列表-暂存记录可见", detail.filingStatusName);
|
||||
}
|
||||
else {
|
||||
fail("列表-暂存记录可见", `status=${detail.filingStatusCode}`);
|
||||
}
|
||||
|
||||
const reload = await api("GET", `/qual-filing/detail?id=${filingId}`);
|
||||
if (reload.data?.businessScope?.includes("化学")) {
|
||||
pass("回显-修改后业务范围");
|
||||
}
|
||||
else {
|
||||
fail("回显-修改后业务范围");
|
||||
}
|
||||
|
||||
const verifyMod = await import(path.join(ROOT, "src/pages/Container/QualApplication/filingVerify.js"));
|
||||
const verifyResult = verifyMod.verifyFilingPrerequisites({
|
||||
...FORMAL_BASIC,
|
||||
materials: reload.data?.materials || [],
|
||||
personnelList: reload.data?.personnelList || [],
|
||||
equipmentList: reload.data?.equipmentList || [],
|
||||
});
|
||||
if (!verifyResult.passed) {
|
||||
const failedItems = verifyResult.items.filter((i) => !i.passed).map((i) => i.label);
|
||||
recordIssue(
|
||||
"资质备案申请",
|
||||
"阻塞",
|
||||
"前端提交核验:人员数量/结构未通过",
|
||||
`当前机构人员 ${(reload.data?.personnelList || []).length} 人,核验失败项:${failedItems.join("、")}。用户要求测试阶段暂不判断此项,但前端「提交申请」确认按钮仍被禁用。`,
|
||||
);
|
||||
pass("前端核验-预期失败(人员不足)", failedItems.join(","));
|
||||
}
|
||||
else {
|
||||
pass("前端核验-全部通过");
|
||||
}
|
||||
|
||||
const submit = await api("POST", `/qual-filing/submit?id=${filingId}`);
|
||||
if (submit.success && submit.data?.filingStatusCode === 2) {
|
||||
pass("后端提交-进入审核中", submit.data.filingStatusName);
|
||||
}
|
||||
else {
|
||||
fail("后端提交", submit.message);
|
||||
recordIssue("资质备案申请", "阻塞", "后端提交失败", submit.message || JSON.stringify(submit));
|
||||
return;
|
||||
}
|
||||
|
||||
const approve = await api("POST", `/qual-filing/approve?id=${filingId}`);
|
||||
if (approve.success && approve.data?.filingStatusCode === 1) {
|
||||
pass("审核通过-已备案", approve.data.filingStatusName);
|
||||
ctx.approvedApplicationId = filingId;
|
||||
}
|
||||
else {
|
||||
fail("审核通过", approve.message || approve.data?.filingStatusName);
|
||||
}
|
||||
}
|
||||
|
||||
// ── 模块2:已备案资质管理 ──
|
||||
async function testModule2FiledManage() {
|
||||
console.log("\n=== 模块2:已备案资质管理 ===");
|
||||
|
||||
const filedPage = await api("GET", "/qual-filing/filed/page?current=1&size=20");
|
||||
const filedList = filedPage.data || [];
|
||||
if (filedList.length > 0) {
|
||||
pass("已备案列表有数据", `count=${filedList.length}`);
|
||||
ctx.originFiledId = filedList[0].id;
|
||||
}
|
||||
else {
|
||||
fail("已备案列表有数据", "审核通过后应出现 applyType=3 记录");
|
||||
recordIssue("已备案资质管理", "阻塞", "审核通过后已备案列表为空", "需确认 approve 是否创建 filed 记录");
|
||||
return;
|
||||
}
|
||||
|
||||
const origin = filedList[0];
|
||||
if (origin.applyTypeCode === 3) {
|
||||
pass("已备案记录类型", origin.applyTypeName);
|
||||
}
|
||||
else {
|
||||
fail("已备案记录类型", `applyTypeCode=${origin.applyTypeCode}`);
|
||||
}
|
||||
|
||||
const filedDraft = await api("POST", "/qual-filing/filed/draft");
|
||||
const filedDraftId = filedDraft.data?.id;
|
||||
if (!filedDraft.success || !filedDraftId) {
|
||||
fail("资质备案填报-创建草稿", filedDraft.message);
|
||||
return;
|
||||
}
|
||||
pass("资质备案填报-创建草稿", `id=${filedDraftId}`);
|
||||
ctx.filedDraftId = filedDraftId;
|
||||
|
||||
const saveRes = await persistLikeFrontend(filedDraftId);
|
||||
const detail = saveRes.data || {};
|
||||
if (detail.filingStatusCode === 5 && detail.applyTypeCode === 3) {
|
||||
pass("填报暂存", `${detail.filingStatusName} applyType=${detail.applyTypeCode}`);
|
||||
}
|
||||
else {
|
||||
fail("填报暂存", `status=${detail.filingStatusCode} type=${detail.applyTypeCode}`);
|
||||
}
|
||||
|
||||
const filedPageDefault = await api("GET", "/qual-filing/filed/page?current=1&size=20");
|
||||
const draftInDefaultList = (filedPageDefault.data || []).some((r) => String(r.id) === String(filedDraftId));
|
||||
if (!draftInDefaultList) {
|
||||
pass("已备案列表-默认不含填报暂存");
|
||||
}
|
||||
else {
|
||||
fail("已备案列表-默认不含填报暂存");
|
||||
recordIssue("已备案资质管理", "一般", "填报暂存不应出现在默认已备案列表", `draftId=${filedDraftId}`);
|
||||
}
|
||||
|
||||
const filedPageDraftFilter = await api("GET", "/qual-filing/filed/page?current=1&size=20&filingStatusCode=5");
|
||||
const draftInFilteredList = (filedPageDraftFilter.data || []).some((r) => String(r.id) === String(filedDraftId));
|
||||
if (draftInFilteredList) {
|
||||
pass("已备案列表-显式筛选暂存可见");
|
||||
}
|
||||
else {
|
||||
fail("已备案列表-显式筛选暂存可见", `draftId=${filedDraftId}`);
|
||||
}
|
||||
}
|
||||
|
||||
// ── 模块3:备案变更管理 ──
|
||||
async function testModule3FilingChange() {
|
||||
console.log("\n=== 模块3:备案变更管理 ===");
|
||||
|
||||
const originId = ctx.originFiledId;
|
||||
if (!originId) {
|
||||
fail("发起变更", "无已备案 origin 记录");
|
||||
return;
|
||||
}
|
||||
|
||||
const changePage = await api("GET", "/qual-filing/change/page?current=1&size=20");
|
||||
const changeList = changePage.data || [];
|
||||
if (changeList.length > 0) {
|
||||
pass("变更列表有数据", `count=${changeList.length}`);
|
||||
}
|
||||
else {
|
||||
fail("变更列表有数据", "应有已备案记录");
|
||||
}
|
||||
|
||||
const origin = changeList.find((r) => String(r.id) === String(originId)) || changeList[0];
|
||||
if (!origin) {
|
||||
fail("变更源记录", "未找到");
|
||||
return;
|
||||
}
|
||||
|
||||
if (origin.filingStatusCode === 4) {
|
||||
recordIssue("备案变更管理", "阻塞", "已备案记录处于变更审核中", "无法再次发起变更");
|
||||
fail("可发起变更", "状态=变更审核中");
|
||||
return;
|
||||
}
|
||||
pass("可发起变更", origin.filingStatusName);
|
||||
|
||||
const start = await api("POST", `/qual-filing-change/start?originFilingId=${originId}`);
|
||||
const draftId = start.data?.draftFilingId || start.data;
|
||||
if (!start.success || !draftId) {
|
||||
fail("发起变更", start.message);
|
||||
return;
|
||||
}
|
||||
pass("发起变更", `draftId=${draftId}`);
|
||||
ctx.changeDraftId = draftId;
|
||||
|
||||
const changeBasic = {
|
||||
...FORMAL_BASIC,
|
||||
businessScope: "石油加工业,化学原料、化学品及医药制造业;金属、非金属矿及其他矿采选业",
|
||||
unitIntro: "变更后:新增矿山采选业安全评价业务范围。",
|
||||
};
|
||||
await api("POST", "/qual-filing/save-draft", { id: draftId, ...changeBasic });
|
||||
await uploadAllMaterials(draftId);
|
||||
await api("POST", "/qual-filing-commitment/save-or-update", {
|
||||
filingId: draftId,
|
||||
signDate: "2026-06-30",
|
||||
legalRepSignatureUrl: DEFAULT_URL,
|
||||
commitmentContent: "变更备案承诺书",
|
||||
});
|
||||
|
||||
const changeDetail = await api("GET", `/qual-filing/detail?id=${draftId}`);
|
||||
if (changeDetail.data?.businessScope?.includes("矿采选业")) {
|
||||
pass("变更草稿-业务范围已修改");
|
||||
}
|
||||
else {
|
||||
fail("变更草稿-业务范围已修改", changeDetail.data?.businessScope);
|
||||
}
|
||||
|
||||
const changeSubmit = await api("POST", "/qual-filing-change/submit", { draftFilingId: draftId });
|
||||
if (changeSubmit.success) {
|
||||
pass("提交变更", "success");
|
||||
}
|
||||
else {
|
||||
fail("提交变更", changeSubmit.message);
|
||||
recordIssue("备案变更管理", "阻塞", "变更提交失败", changeSubmit.message);
|
||||
return;
|
||||
}
|
||||
|
||||
const history = await api("GET", `/qual-filing-change/history?originFilingId=${originId}`);
|
||||
const changeCount = history.data?.changeCount ?? 0;
|
||||
if (history.success && changeCount > 0) {
|
||||
pass("变更历史", `changeCount=${changeCount}`);
|
||||
}
|
||||
else {
|
||||
fail("变更历史", `count=${changeCount}`);
|
||||
}
|
||||
|
||||
const changeAfter = await api("GET", "/qual-filing/change/page?current=1&size=20");
|
||||
const updated = (changeAfter.data || []).find((r) => String(r.id) === String(originId));
|
||||
if (updated?.changeCount > 0) {
|
||||
pass("变更列表-变更次数更新", `${updated.filingStatusName} count=${updated.changeCount}`);
|
||||
}
|
||||
else {
|
||||
fail("变更列表-变更次数更新");
|
||||
recordIssue("备案变更管理", "一般", "变更列表变更次数未更新", `originId=${originId}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function checkDataQuality() {
|
||||
console.log("\n=== 数据质量检查 ===");
|
||||
const personnelPage = await api("GET", "/org-personnel/page?current=1&size=10");
|
||||
const missingName = (personnelPage.data || []).filter((p) => !p.personName && !p.staffName);
|
||||
if (missingName.length) {
|
||||
recordIssue(
|
||||
"通用",
|
||||
"一般",
|
||||
"机构人员主数据姓名为空",
|
||||
`${missingName.length} 条人员记录 personName/staffName 为空,表单人员列表可能显示空白。`,
|
||||
);
|
||||
pass("人员主数据-姓名缺失(已知)", `count=${missingName.length}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log("资质申请管理 — 三模块完整流程联调");
|
||||
console.log(`API=${API_BASE} orgInfoId=${ORG_INFO_ID}\n`);
|
||||
|
||||
await checkDataQuality();
|
||||
await testModule1Application();
|
||||
await testModule2FiledManage();
|
||||
await testModule3FilingChange();
|
||||
|
||||
console.log(`\n=== 结果: ${passed} 通过, ${failed} 失败 ===`);
|
||||
console.log(`=== 问题记录: ${issues.length} 条 ===\n`);
|
||||
for (const [i, issue] of issues.entries()) {
|
||||
console.log(`${i + 1}. [${issue.module}] [${issue.severity}] ${issue.title}`);
|
||||
console.log(` ${issue.detail}\n`);
|
||||
}
|
||||
|
||||
return { passed, failed, issues, ctx };
|
||||
}
|
||||
|
||||
const result = await main();
|
||||
if (result.failed > 0) {
|
||||
process.exitCode = 1;
|
||||
}
|
||||
|
||||
export default result;
|
||||
|
|
@ -1,249 +0,0 @@
|
|||
/**
|
||||
* 资质申请管理 — 页面交互逻辑 + 后端联调全链路测试
|
||||
*
|
||||
* 覆盖本次改造:
|
||||
* - 新建申请不预调 draft(列表 API 计数不变)
|
||||
* - 暂存后入库并可回显
|
||||
* - 材料前端模板 + 暂存时 init-template
|
||||
* - 提交前核验(全通过/有失败)
|
||||
* - 前端路由 bundle 检测
|
||||
*
|
||||
* 运行:
|
||||
* node docs/test-reports/测试用例/test-qual-filing-interaction.mjs
|
||||
*/
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const ROOT = path.resolve(__dirname, "../../..");
|
||||
|
||||
const API_BASE = process.env.API_BASE || "http://127.0.0.1:8095/safetyEval";
|
||||
const ORG_INFO_ID = process.env.ORG_INFO_ID || "1";
|
||||
const FRONTEND_BASE = process.env.FRONTEND_BASE || "http://127.0.0.1:8081";
|
||||
const DEFAULT_URL =
|
||||
"https://s1.aigei.com/prevfiles/6f2754563be2491bad7c957904cd7d2a.jpg?e=2051020800&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:CR60zg7VKDp-jUWyYls_rdvyQMM=";
|
||||
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
|
||||
function assert(name, condition, detail = "") {
|
||||
if (condition) {
|
||||
passed += 1;
|
||||
console.log(` ✓ ${name}${detail ? ` — ${detail}` : ""}`);
|
||||
}
|
||||
else {
|
||||
failed += 1;
|
||||
console.log(` ✗ ${name}${detail ? ` — ${detail}` : ""}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function api(method, path, body) {
|
||||
const res = await fetch(`${API_BASE}${path}`, {
|
||||
method,
|
||||
headers: { "Content-Type": "application/json", orgInfoId: ORG_INFO_ID },
|
||||
body: body !== undefined ? JSON.stringify(body) : undefined,
|
||||
});
|
||||
const text = await res.text();
|
||||
try {
|
||||
return { httpStatus: res.status, ...JSON.parse(text) };
|
||||
}
|
||||
catch {
|
||||
return { httpStatus: res.status, success: false, raw: text };
|
||||
}
|
||||
}
|
||||
|
||||
function readSrc(rel) {
|
||||
return fs.readFileSync(path.join(ROOT, rel), "utf8");
|
||||
}
|
||||
|
||||
// ── 静态:新建不预调后端 ──
|
||||
function testStaticNoPreCreate() {
|
||||
console.log("\n=== 静态检查:新建不预调后端 ===");
|
||||
const listPage = readSrc("src/pages/Container/QualApplication/FilingApplication/List/index.js");
|
||||
assert("列表-申请备案不调 qualFilingDraft", !listPage.includes("qualFilingDraft"));
|
||||
assert("列表-申请备案不调 init-template", !listPage.includes("qualFilingMaterialInitTemplate"));
|
||||
assert("列表-直接跳转表单", listPage.includes("goFilingForm({ mode: FILING_FORM_MODE.APPLICATION })"));
|
||||
|
||||
const formPage = readSrc("src/pages/Container/QualApplication/FilingForm/index.js");
|
||||
assert("表单-存在 persistFilingToBackend", formPage.includes("persistFilingToBackend"));
|
||||
assert("表单-存在核验弹窗", formPage.includes("PrerequisiteVerifyModal"));
|
||||
assert("表单-存在 session 本地草稿", formPage.includes("saveLocalDraft"));
|
||||
assert("表单-材料本地更新", formPage.includes("uploadStatusName: \"已上传\""));
|
||||
|
||||
const basicStep = readSrc("src/pages/Container/QualApplication/FilingForm/components/BasicInfoStep.jsx");
|
||||
assert("基本信息-证照类型选项", basicStep.includes("QUALIFICATION_INDUSTRY_OPTIONS"));
|
||||
|
||||
const commitment = readSrc("src/pages/Container/QualApplication/FilingForm/components/CommitmentStep.jsx");
|
||||
assert("承诺书-法定代表人下拉", commitment.includes("legalRepPersonnelId"));
|
||||
assert("承诺书-动态填充文本", commitment.includes("COMMITMENT_PARAGRAPHS"));
|
||||
|
||||
assert("材料模板-10项", readSrc("src/pages/Container/QualApplication/filingMaterialTemplate.js").includes("sortOrder: 10"));
|
||||
}
|
||||
|
||||
// ── 核验逻辑 ──
|
||||
async function testVerifyLogic() {
|
||||
console.log("\n=== 核验逻辑 ===");
|
||||
const verifyMod = await import(path.join(ROOT, "src/pages/Container/QualApplication/filingVerify.js"));
|
||||
const { verifyFilingPrerequisites } = verifyMod;
|
||||
|
||||
const failDetail = {
|
||||
fixedAssetAmount: 500,
|
||||
workplaceArea: 500,
|
||||
personnelList: [],
|
||||
equipmentList: [],
|
||||
materials: [{ sortOrder: 10, attachmentUrl: null }],
|
||||
};
|
||||
const failResult = verifyFilingPrerequisites(failDetail);
|
||||
assert("核验-不完整时应失败", failResult.passed === false, `items=${failResult.items.length}`);
|
||||
|
||||
const passDetail = {
|
||||
fixedAssetAmount: 1200,
|
||||
workplaceArea: 1200,
|
||||
equipmentList: [{ id: 1 }],
|
||||
materials: [{ sortOrder: 10, attachmentUrl: DEFAULT_URL }],
|
||||
personnelList: Array.from({ length: 25 }, (_, i) => ({
|
||||
personName: `P${i}`,
|
||||
registerEngineerFlag: i < 8 ? 1 : 0,
|
||||
titleName: i < 8 ? "高级" : "中级",
|
||||
positionName: i === 0 ? "负责人" : i === 1 ? "技术负责人" : i === 2 ? "质量负责人" : "工程师",
|
||||
workExperience: "在本行业工作五年",
|
||||
})),
|
||||
};
|
||||
const passResult = verifyFilingPrerequisites(passDetail);
|
||||
assert("核验-完整数据应通过", passResult.passed === true);
|
||||
}
|
||||
|
||||
// ── 模拟前端 persist 全链路 ──
|
||||
async function testPersistFlow() {
|
||||
console.log("\n=== 交互链路:暂存入库 + 回显 ===");
|
||||
|
||||
const beforePage = await api("GET", "/qual-filing/page?current=1&size=1&applyTypeCode=1");
|
||||
const beforeTotal = beforePage.total ?? 0;
|
||||
|
||||
// 模拟用户填写后点暂存(等同 persistFilingToBackend)
|
||||
const draft = await api("POST", "/qual-filing/draft?applyTypeCode=1");
|
||||
assert("暂存-创建草稿", draft.success === true && draft.data?.id, `id=${draft.data?.id}`);
|
||||
const filingId = draft.data?.id;
|
||||
if (!filingId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const saveBasic = await api("POST", "/qual-filing/save-draft", {
|
||||
id: filingId,
|
||||
businessScope: "石油加工业,化学原料、化学品及医药制造业",
|
||||
filingTerritoryName: "渝北区",
|
||||
filingTerritoryCode: "渝北区",
|
||||
filingUnitName: "交互测试评价机构",
|
||||
fixedAssetAmount: 1200,
|
||||
workplaceArea: 1500,
|
||||
});
|
||||
assert("暂存-基本信息", saveBasic.success === true, saveBasic.message);
|
||||
|
||||
await api("POST", `/qual-filing-material/init-template?filingId=${filingId}`);
|
||||
const detailAfterInit = await api("GET", `/qual-filing/detail?id=${filingId}`);
|
||||
assert("暂存-材料模板10项", detailAfterInit.data?.materials?.length === 10);
|
||||
|
||||
for (const material of detailAfterInit.data.materials || []) {
|
||||
await api("POST", "/qual-filing-material/upload", {
|
||||
id: material.id,
|
||||
attachmentUrl: DEFAULT_URL,
|
||||
});
|
||||
}
|
||||
|
||||
const commitmentContent = "本人是郑远平是交互测试评价机构法定代表人,现代表我单位承诺如下:";
|
||||
await api("POST", "/qual-filing-commitment/save-or-update", {
|
||||
filingId,
|
||||
signDate: "2026-06-30",
|
||||
legalRepSignatureUrl: DEFAULT_URL,
|
||||
commitmentContent,
|
||||
});
|
||||
|
||||
const personnelPage = await api("GET", "/org-personnel/page?current=1&size=5");
|
||||
const personnelIds = (personnelPage.data || []).map((p) => p.id).filter(Boolean);
|
||||
if (personnelIds.length) {
|
||||
await api("POST", "/qual-filing-personnel/batch-add-from-org", {
|
||||
filingId,
|
||||
sourcePersonnelIds: personnelIds.slice(0, 2),
|
||||
});
|
||||
}
|
||||
|
||||
const equipPage = await api("GET", "/org-equipment/page?current=1&size=5");
|
||||
const equipIds = (equipPage.data || []).map((e) => e.id).filter(Boolean);
|
||||
if (equipIds.length) {
|
||||
await api("POST", "/qual-filing-equipment/batch-add-from-org", {
|
||||
filingId,
|
||||
sourceEquipmentIds: equipIds.slice(0, 1),
|
||||
});
|
||||
}
|
||||
|
||||
const reloaded = await api("GET", `/qual-filing/detail?id=${filingId}`);
|
||||
assert("回显-业务范围", reloaded.data?.businessScope?.includes("化学"), reloaded.data?.businessScope);
|
||||
assert("回显-材料已上传", (reloaded.data?.materials || []).every((m) => m.uploadStatusCode === 2));
|
||||
assert("回显-承诺书", !!reloaded.data?.commitment?.commitmentContent);
|
||||
assert("回显-人员", (reloaded.data?.personnelList || []).length >= 1 || personnelIds.length === 0);
|
||||
assert("回显-装备", (reloaded.data?.equipmentList || []).length >= 1 || equipIds.length === 0);
|
||||
|
||||
const afterPage = await api("GET", "/qual-filing/page?current=1&size=20&applyTypeCode=1");
|
||||
assert("列表-暂存后总数增加", (afterPage.total ?? 0) >= beforeTotal + 1, `before=${beforeTotal} after=${afterPage.total}`);
|
||||
|
||||
// 提交应进入审核中
|
||||
const submit = await api("POST", `/qual-filing/submit?id=${filingId}`);
|
||||
assert("提交-进入审核中", submit.success === true && submit.data?.filingStatusCode === 2, submit.data?.filingStatusName);
|
||||
}
|
||||
|
||||
// ── 前端 bundle / 路由 ──
|
||||
async function testFrontendBundle() {
|
||||
console.log("\n=== 前端页面 bundle 检测 ===");
|
||||
const pages = [
|
||||
["/certificate/container/qualApplication/filingApplication/list", "资质备案申请列表"],
|
||||
["/certificate/container/qualApplication/filedManage/list", "已备案资质管理"],
|
||||
["/certificate/container/qualApplication/filingChange/list", "备案变更管理"],
|
||||
["/certificate/container/qualApplication/filingForm?mode=application", "备案表单(无id)"],
|
||||
["/certificate/container/qualApplication/filingForm?mode=application&id=1", "备案表单(有id)"],
|
||||
];
|
||||
|
||||
for (const [route, label] of pages) {
|
||||
const res = await fetch(`${FRONTEND_BASE}${route}`);
|
||||
assert(`HTTP-${label}`, res.status === 200, String(res.status));
|
||||
}
|
||||
|
||||
const html = await (await fetch(`${FRONTEND_BASE}/certificate/`)).text();
|
||||
const jsMatch = html.match(/src="(\/certificate\/static\/js\/main\.[^"]+\.js)"/);
|
||||
assert("前端-main.js 可解析", !!jsMatch);
|
||||
if (jsMatch) {
|
||||
const js = await (await fetch(`${FRONTEND_BASE}${jsMatch[1]}`)).text();
|
||||
const markers = [
|
||||
"FilingApplicationListPage",
|
||||
"qualApplication/filingApplication/list",
|
||||
"PrerequisiteVerifyModal",
|
||||
"QUALIFICATION_INDUSTRY_OPTIONS",
|
||||
"persistFilingToBackend",
|
||||
"verifyFilingPrerequisites",
|
||||
"legalRepPersonnelId",
|
||||
];
|
||||
for (const marker of markers) {
|
||||
assert(`Bundle-${marker}`, js.includes(marker));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log("资质申请管理 — 页面交互 + 后端全链路测试");
|
||||
console.log(`API_BASE=${API_BASE} FRONTEND=${FRONTEND_BASE} orgInfoId=${ORG_INFO_ID}`);
|
||||
|
||||
testStaticNoPreCreate();
|
||||
await testVerifyLogic();
|
||||
await testPersistFlow();
|
||||
await testFrontendBundle();
|
||||
|
||||
console.log(`\n=== 结果: ${passed} 通过, ${failed} 失败 ===\n`);
|
||||
if (failed > 0) {
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
|
@ -118,19 +118,29 @@ function EquipInfoPage(props) {
|
|||
allowClear
|
||||
/>
|
||||
</Form.Item>,
|
||||
<Form.Item key="instrumentType" name="instrumentType">
|
||||
<ControlWrapper.Input
|
||||
<Form.Item key="instrumentTypeName" name="instrumentTypeName">
|
||||
<ControlWrapper.Select
|
||||
label="仪器类型"
|
||||
placeholder="请输入"
|
||||
placeholder="请选择"
|
||||
allowClear
|
||||
/>
|
||||
style={{ width: "100%" }}
|
||||
>
|
||||
<Select.Option value="分析仪器">分析仪器</Select.Option>
|
||||
<Select.Option value="检测仪器">检测仪器</Select.Option>
|
||||
<Select.Option value="采样设备">采样设备</Select.Option>
|
||||
<Select.Option value="物理仪器">物理仪器</Select.Option>
|
||||
</ControlWrapper.Select>
|
||||
</Form.Item>,
|
||||
<Form.Item key="deviceType" name="deviceType">
|
||||
<ControlWrapper.Input
|
||||
<ControlWrapper.Select
|
||||
label="设备类型"
|
||||
placeholder="请输入"
|
||||
placeholder="请选择"
|
||||
allowClear
|
||||
/>
|
||||
style={{ width: "100%" }}
|
||||
>
|
||||
<Select.Option value="固定式">固定式</Select.Option>
|
||||
<Select.Option value="便携式">便携式</Select.Option>
|
||||
</ControlWrapper.Select>
|
||||
</Form.Item>,
|
||||
<Form.Item key="enableFlag" name="enableFlag">
|
||||
<ControlWrapper.Select
|
||||
|
|
@ -163,7 +173,7 @@ function EquipInfoPage(props) {
|
|||
ellipsis: true,
|
||||
},
|
||||
{ title: "设备型号", dataIndex: "deviceModel" },
|
||||
{ title: "仪器类型", dataIndex: "instrumentType" },
|
||||
{ title: "仪器类型", dataIndex: "instrumentTypeName" },
|
||||
{ title: "设备类型", dataIndex: "deviceType" },
|
||||
{
|
||||
title: "厂家",
|
||||
|
|
@ -367,11 +377,16 @@ function EquipFormModal({
|
|||
</Col>
|
||||
<Col span={12}>
|
||||
<Form.Item
|
||||
name="instrumentType"
|
||||
name="instrumentTypeName"
|
||||
label="仪器分类"
|
||||
rules={[{ required: true, message: "请选择仪器分类" }]}
|
||||
>
|
||||
<Input placeholder="请输入仪器分类" />
|
||||
<Select placeholder="请选择仪器分类">
|
||||
<Select.Option value="分析仪器">分析仪器</Select.Option>
|
||||
<Select.Option value="检测仪器">检测仪器</Select.Option>
|
||||
<Select.Option value="采样设备">采样设备</Select.Option>
|
||||
<Select.Option value="物理仪器">物理仪器</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
|
|
@ -380,7 +395,10 @@ function EquipFormModal({
|
|||
label="设备类型"
|
||||
rules={[{ required: true, message: "请选择设备类型" }]}
|
||||
>
|
||||
<Input placeholder="请输入设备类型" />
|
||||
<Select placeholder="请选择设备类型">
|
||||
<Select.Option value="固定式">固定式</Select.Option>
|
||||
<Select.Option value="便携式">便携式</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
|
|
@ -439,8 +457,12 @@ function EquipFormModal({
|
|||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Form.Item name="fieldCalibrationType" label="现场校验类型">
|
||||
<Input placeholder="请输入现场校验类型" />
|
||||
<Form.Item name="fieldCalibrationTypeName" label="现场校验类型">
|
||||
<Select placeholder="请选择现场校验类型" allowClear>
|
||||
<Select.Option value="现场校验">现场校验</Select.Option>
|
||||
<Select.Option value="实验室校验">实验室校验</Select.Option>
|
||||
<Select.Option value="免校验">免校验</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
|
|
@ -503,7 +525,7 @@ function EquipViewModal({ open, currentId, equipInfoGet, onCancel }) {
|
|||
{info.deviceModel}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="仪器分类">
|
||||
{info.instrumentType}
|
||||
{info.instrumentTypeName}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="设备类型">
|
||||
{info.deviceType}
|
||||
|
|
@ -521,7 +543,7 @@ function EquipViewModal({ open, currentId, equipInfoGet, onCancel }) {
|
|||
{info.calibrationInitValue}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="现场校验类型">
|
||||
{info.fieldCalibrationType}
|
||||
{info.fieldCalibrationTypeName}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="是否双路">
|
||||
{DUAL_CHANNEL_MAP[info.dualChannelFlag] ?? "-"}
|
||||
|
|
|
|||
|
|
@ -5,21 +5,21 @@ import { Skeleton } from "antd";
|
|||
|
||||
/** 资质申请管理模块入口:需先有机构信息 */
|
||||
function EnterpriseInfo(props) {
|
||||
|
||||
const [orgInfoId, setOrgInfoId] = useState(
|
||||
sessionStorage.getItem("orgInfoId"),
|
||||
);
|
||||
useEffect(() => {
|
||||
if (!orgInfoId) {
|
||||
props.orgInfoGet().then((res) => {
|
||||
setOrgInfoId(res?.data?.id);
|
||||
sessionStorage.setItem("orgInfoId", res?.data?.id);
|
||||
if (res?.data?.id) {
|
||||
setOrgInfoId(res?.data?.id);
|
||||
sessionStorage.setItem("orgInfoId", res?.data?.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
||||
if(props.location.pathname.includes('EnterpriseInfo/OrgInfo')){
|
||||
if (props.location.pathname.includes("EnterpriseInfo/OrgInfo")) {
|
||||
return props.children;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,8 +11,10 @@ function QualApplication(props) {
|
|||
useEffect(() => {
|
||||
if (!orgInfoId) {
|
||||
props.orgInfoGet().then((res) => {
|
||||
setOrgInfoId(res?.data?.id);
|
||||
sessionStorage.setItem("orgInfoId", res?.data?.id);
|
||||
if (res?.data?.id) {
|
||||
setOrgInfoId(res?.data?.id);
|
||||
sessionStorage.setItem("orgInfoId", res?.data?.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
|
|
|||
Loading…
Reference in New Issue