v0.0.1-final
tangjie 2026-07-07 11:18:00 +08:00
parent 2d812ec4fd
commit d343838f4d
16 changed files with 47 additions and 2752 deletions

1
.gitignore vendored
View File

@ -7,6 +7,7 @@
/prototype
/dist
/demo
/docs
/.trae/
npm-debug.log*
yarn-debug.log*

View File

@ -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");

View File

@ -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
```

View File

@ -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-runtimeGBS 底座 DVA 运行时)
- rspack 构建 / pnpm 包管理
- qiankun 微前端子应用
## 重要提示
- 本项目为 GBS 底座微前端子应用,独立启动时有内置模拟布局
- 本地独立运行时 API 调用会返回 401缺少底座注入的认证 Token
- 仅 Test 页面可在本地无底座环境下正常访问
- 重命名项目目录后需 `pnpm install` 重建 node_modules 符号链接
- 完整开发指南请参考 [dev-guide.md](./dev-guide.md)

View File

@ -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~08UI-STAFF-03 |
| 2 | 新增/编辑文字遮挡 | 弹窗改 `layout="vertical"``labelCol/wrapperCol=24` | UI-PAGE-*-03手工打开各模块弹窗 |
| 3 | 离职缺审核状态/用户名 | CO 增 `account`enricher 组装;列表 Tag 展示 | EI-API-RESIGN-04~05UI-RESIGN-01~02 |
| 4 | 页面卡顿/报错 | 去掉失败的上传/删文件调用;修复 SELECT 空选项 | 控制台无红错;列表加载 <3s |
| 5 | 变更次数配色 | `CHANGE_COUNT_STYLE` 蓝色高亮/灰色零值 | UI-CHANGE-01人员变更列表目视 |
| 6 | 新增人员部门/岗位不可选 | 人员表单改用 antd `Select``formSelectField` 适配 Search | UI-STAFF-01~02TC-STAFF-03 |
| 7 | 上传默认通过 | `src/utils/mockUpload.js` 固定 URL | UI-COMMON-03TC-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~08deptName / postName 正常
- EI-API-RESIGN-04~05auditStatusCode / 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` |

View File

@ -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 '未找到')"

View File

@ -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);
});

View File

@ -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);
});

View File

@ -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);

View File

@ -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();

View File

@ -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();

View File

@ -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;

View File

@ -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);
});

View File

@ -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] ?? "-"}

View File

@ -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) => {
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;
}

View File

@ -11,8 +11,10 @@ function QualApplication(props) {
useEffect(() => {
if (!orgInfoId) {
props.orgInfoGet().then((res) => {
if (res?.data?.id) {
setOrgInfoId(res?.data?.id);
sessionStorage.setItem("orgInfoId", res?.data?.id);
}
});
}
}, []);