Compare commits

...

No commits in common. "main" and "master" have entirely different histories.
main ... master

121 changed files with 18199 additions and 19 deletions

13
.editorconfig Normal file
View File

@ -0,0 +1,13 @@
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
insert_final_newline = false
trim_trailing_whitespace = false

34
.gitignore vendored
View File

@ -1,20 +1,20 @@
# ---> Actionscript # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# Build and Release Folders
bin-debug/
bin-release/
[Oo]bj/
[Bb]in/
# Other files and folders # dependencies
.settings/ /node_modules
# Executables # production
*.swf /dist
*.air /demo
*.ipa
*.apk
# Project files, i.e. `.project`, `.actionScriptProperties` and `.flexProperties`
# should NOT be excluded as they contain compiler settings and other important
# information for Eclipse / Flash Builder.
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.idea
yarn.lock
/openspec/
/.codebuddy/
/.idea/
/.playwright-cli/
/.vscode/
/.workbuddy/

1
.npmrc Normal file
View File

@ -0,0 +1 @@
shamefully-hoist=true

View File

@ -1,3 +1,75 @@
# safety-eval-service-frontend # 微应用模板说明文档
重庆安全评价前端项目 ## 在线文档
https://www.yuque.com/buhangjiecheshen-ymbtb/qc0093/gxdun1dphetcurko
## 安装依赖
项目依赖可通过 **yarn****npm** 进行安装:
```bash
# 使用 yarn
yarn
# 或使用 npm
npm i
```
## 开发服务&打包应用
```bash
# 启动开发服务
yarn serve:<env>
# 或
npm run serve:<env>
# 开发环境打包
yarn build:<env>
# 或
npm run build:<env>
```
## 路由配置&路由访问&自动化路由
所有页面必须放在`src/pages/container`目录下,启动访问页面请在浏览器地址栏输入`/<appIdentifier>/container/<你的路由页面文件名称>`
解释:
1. 所有页面组件命名为`index.js`或`index.jsx`,必须放在一个首字母大写的文件中。
2. `container`为固定路径访问格式
3. `<appIdentifier>`为应用的唯一标识符,也是应用路由的`basename`,在底座中用于区分其他应用。可在根目录 `jjb.config.js` 文件的 `appIdentifier` 节点中进行修改。
4. 自动化路由将根据`pages/container`中的路由页面文件自动生成路由树。
5. `id`匹配路由,文件夹命名`_id`
## 应用接口环境配置
应用接口环境相关配置在根目录 `jjb.config.js` 文件的 `environment` 节点中进行定义。
## 应用开发服务配置
应用开发服务相关配置在根目录 `jjb.config.js` 文件的 `server` 节点中进行定义。
## Babel 配置
应用的 `Babel` 配置在根目录 `jjb.babel.js` 文件中进行管理。
## 目录说明
1. `src/api/` 配置各个 store 模块的接口数据。
2. `src/components/` 全局公共组件。
3. `src/enumerate/` 全局各种枚举配置。
4. `src/pages/` 页面文件目录。
5. `src/main.js` 应用的入口文件。
## 核心依赖
1. `@cqsjjb/jjb-common-decorator`
1. 公共装饰器库,内部包含:
1. 按钮权限处理
2. antd/Table 控制
3. 文本重命名处理
4. 具体使用方式可参考各个模块的 `d.ts`
2. `@cqsjjb/jjb-common-lib`
1. 公共工具库,具体 API 使用请查看 `d.ts`
3. `@cqsjjb/jjb-dva-runtime`
1. 核心运行时,基于 `dvajs` 实现。
1. 应用核心依赖模块
2. 应用的自动化路由
3. `store` 模块接口数据处理
4. 均基于此依赖实现,具体使用方式请查看 `d.ts`
4. `@cqsjjb/jjb-react-admin-component`
1. 公共组件库,具体组件使用方式请查看 `d.ts`

66
blessed_by_buddha.js Normal file
View File

@ -0,0 +1,66 @@
/*
_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");

572
docs/dev-guide.md Normal file
View File

@ -0,0 +1,572 @@
# 证照管理子应用 — 本地开发指南
## 目录
- [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. 目录结构参考
```
safety-eval-service-frontend/
├── public/
│ └── index.html # HTML 模板
├── src/
│ ├── main.js # 应用入口setup + 生命周期导出)
│ ├── api/ # API 接口声明
│ │ ├── global/
│ │ ├── userCertificate/
│ │ ├── corpCertificate/
│ │ └── courseware/
│ ├── components/ # 共享业务组件
│ │ ├── PersonInChargeList/
│ │ ├── PersonInChargeInfo/
│ │ ├── SpecialCertificateList/
│ │ └── SpecialCertificateInfo/
│ ├── enumerate/ # 枚举/常量定义
│ │ ├── namespace/ # DVA namespace
│ │ ├── context/ # React Context
│ │ └── constant/ # 常量
│ ├── pages/
│ │ └── Container/
│ │ ├── index.js # 容器入口Antd 主题 + 布局切换)
│ │ ├── Entry/ # 底座入口页
│ │ ├── Layout/ # 本地模拟布局
│ │ │ ├── index.jsx # SimulatedLayout 组件
│ │ │ └── menuConfig.js # ★ 菜单配置
│ │ ├── Test/ # 测试页面(无 API 依赖)
│ │ ├── Supervision/ # 监管端
│ │ │ ├── EnterpriseLicense/
│ │ │ └── PersonnelLicense/
│ │ ├── BranchCompany/ # 分公司端
│ │ │ └── EnterpriseLicense/
│ │ └── Stakeholder/ # 干系人端
│ │ └── EnterpriseLicense/
│ ├── assets/ # 静态资源
│ └── utils/ # 工具函数
├── jjb.config.js # 项目配置端口、API_HOST、主题等
├── package.json
├── pnpm-lock.yaml
└── pnpm-workspace.yaml
```

47
docs/startup.md Normal file
View File

@ -0,0 +1,47 @@
# 证照管理子应用 — 快速启动
## 环境要求
- 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)

48
eslint.config.js Normal file
View File

@ -0,0 +1,48 @@
import antfu from "@antfu/eslint-config";
export default antfu({
formatters: {
html: false,
css: true,
},
test: false,
typescript: true,
react: true,
vue: false,
markdown: false,
stylistic: {
semi: true,
quotes: "double",
},
overrides: {
react: {
"react/no-comment-textnodes": "off",
"react-hooks-extra/no-unnecessary-use-prefix": "off",
"react-hooks-extra/prefer-use-state-lazy-initialization": "off",
"react-hooks/exhaustive-deps": "off",
},
javascript: {
"no-console": process.env.NODE_ENV === "production" ? "error" : "warn",
"no-debugger": process.env.NODE_ENV === "production" ? "error" : "warn",
"no-alert": process.env.NODE_ENV === "production" ? "error" : "warn",
"no-restricted-syntax": [
"error",
{
selector: "VariableDeclarator[id.name='pd']",
message: "不允许使用 pd请改用有语义化的变量名",
},
{
selector: "ObjectExpression > Property[key.name='pd']",
message: "不允许使用 pd请改用有语义化的变量名",
},
],
"no-unused-vars": ["error", { varsIgnorePattern: "^React$" }],
},
},
rules: {
"antfu/top-level-function": "off",
"node/prefer-global/process": "off",
"dot-notation": "off",
"linebreak-style": ["off", "windows"],
},
});

23
jjb.babel.js Normal file
View File

@ -0,0 +1,23 @@
module.exports = {
compact: false,
// 插件
plugins: [
[
"@babel/plugin-proposal-decorators",
{
legacy: true,
},
],
],
// 预设
presets: [
["@babel/preset-env", {
targets: {
browsers: ["ie >= 10"],
},
}],
["@babel/preset-react", {
runtime: "automatic",
}],
],
};

78
jjb.config.js Normal file
View File

@ -0,0 +1,78 @@
module.exports = {
// 应用后端git地址部署上线需要
javaGit: "<git-url>",
// 应用后端仓库名称,部署上线需要
javaGitName: "<git-name>",
// 环境配置
environment: {
development: {
// 应用后端分支名称,部署上线需要
javaGitBranch: "<branch-name>",
// 接口服务地址
// API_HOST: "http://192.168.20.100:30140",
API_HOST: "https://gbs-gateway.qhdsafety.com",
},
production: {
// 应用后端分支名称,部署上线需要
javaGitBranch: "<branch-name>",
// 接口服务地址
API_HOST: "",
},
},
// 应用唯一标识符
appIdentifier: "certificate",
// 应用上下文注入全局变量
contextInject: {
// 应用Key
appKey: "",
// fileUrl: "http://192.168.20.240:9787/mnt",
// fileUrl: "https://jpfz.qhdsafety.com/gbsFileTest/",
fileUrl: "https://skqhdg.porthebei.com:9004/file/uploadFiles2/",
},
// public/index.html注入全局变量
windowInject: {
// 应用标题
title: "微应用模板",
// 注入css链接集合
links: [],
element: {
root: {
// 挂载DOM元素ID
id: "root",
},
},
// 注入js链接集合
scripts: [],
},
// 开发服务
server: {
// 监听端口号
port: "8081",
// 服务地址
host: "127.0.0.1",
// 是否自动打开浏览器
open: true,
},
// 框架
framework: {
// ant-design
antd: {
// 全局antd-class-name前缀
"ant-prefix": "micro-temp",
// 全局字体
"fontFamily": "PingFangSC-Regular",
// 全局主题色
"colorPrimary": "#1677ff",
// 全局圆角
"borderRadius": 2,
},
},
// webpack
webpackConfig: {
// 单页面插件
htmlWebpackPluginOption: {
// 自动注入编译后的文件到public/index.html中
inject: true,
},
},
};

9
jsconfig.json Normal file
View File

@ -0,0 +1,9 @@
{
"compilerOptions": {
"baseUrl": "src",
"paths": {
"~/*": ["*"]
}
},
"include": ["src"]
}

63
package.json Normal file
View File

@ -0,0 +1,63 @@
{
"name": "micro-app",
"version": "2.0.0",
"description": "建教帮微应用模板",
"author": "JJB",
"license": "MIT",
"main": "index.js",
"scripts": {
"serve": "node node_modules/@cqsjjb/scripts/rspack.dev.server.js",
"build": "node node_modules/@cqsjjb/scripts/rspack.build.js",
"push": "jjb-cmd push java production",
"clean-cache": "rimraf node_modules/.cache/webpack",
"serve:development": "cross-env NODE_ENV=development npm run serve",
"serve:production": "cross-env NODE_ENV=production npm run serve",
"build:development": "cross-env NODE_ENV=development npm run build",
"build:production": "cross-env NODE_ENV=production npm run build",
"code-optimization": "node node_modules/@cqsjjb/scripts/code-optimization.js",
"lint": "eslint --ext .js,.jsx,.tsx --fix src"
},
"dependencies": {
"@ant-design/icons": "^5.0.0",
"@ant-design/pro-components": "^2.8.10",
"@cqsjjb/jjb-common-decorator": "latest",
"@cqsjjb/jjb-common-lib": "latest",
"@cqsjjb/jjb-dva-runtime": "latest",
"@cqsjjb/jjb-react-admin-component": "latest",
"@rc-component/motion": "^1.0.0",
"@rc-component/util": "^1.11.1",
"ahooks": "^3.9.5",
"antd": "^5.11.2",
"dayjs": "^1.11.7",
"lodash-es": "^4.17.21",
"qrcode.react": "^4.2.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"zy-react-library": "^1.2.3"
},
"devDependencies": {
"@antfu/eslint-config": "^5.4.1",
"@babel/plugin-proposal-decorators": "^7.19.3",
"@cqsjjb/scripts": "latest",
"@eslint-react/eslint-plugin": "^2.2.2",
"babel-loader": "^9.1.3",
"cross-env": "^7.0.3",
"css-loader": "^6.8.1",
"eslint": "^9.37.0",
"eslint-plugin-format": "^1.0.2",
"eslint-plugin-react-hooks": "^7.0.0",
"eslint-plugin-react-refresh": "^0.4.23",
"file-loader": "^6.2.0",
"less-loader": "^11.1.3",
"style-loader": "^3.3.3",
"typescript": "^5.9.3"
},
"pnpm": {
"overrides": {
"history": "^4.10.1",
"path-to-regexp": "^1.9.0",
"react": "$react",
"react-dom": "$react-dom"
}
}
}

11822
pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load Diff

3
pnpm-workspace.yaml Normal file
View File

@ -0,0 +1,3 @@
allowBuilds:
es5-ext: false
zy-react-library: false

54
public/index.html Normal file
View File

@ -0,0 +1,54 @@
<!--BEGIN-->
<!--<% var { env: $env, process: $process, mode: $mode, builtInfo: $builtInfo, links: $links, redirectLogin: $redirectLogin, framework: $framework, scripts: $scripts, element: $element } = htmlWebpackPlugin.options %>-->
<!--<% var { appKey: $appKey, antd: $antd, basename: $basename, API_HOST: $API_HOST } = $process %>-->
<!--<% var { ['ant-prefix']: $antPrefix, fontFamily: $fontFamily, colorPrimary: $colorPrimary, borderRadius: $borderRadius } = $antd %>-->
<!--NED-->
<!DOCTYPE html>
<html lang="zh">
<head data-built-info="<%= $builtInfo %>">
<meta charset="UTF-8"/>
<meta name="renderer" content="webkit"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge,Chrome=1"/>
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no,viewport-fit=cover">
<% for (const item of $links) { %>
<link type="text/css" rel="stylesheet" href="<%= item %>"></link>
<% } %>
<title>--</title>
<script>
(function () {
const APP_ENV = {
antd: {
'ant-prefix': '<%= $antPrefix %>',
fontFamily: '<%= $fontFamily %>',
colorPrimary: '<%= $colorPrimary %>',
borderRadius: parseInt('<%= $borderRadius %>')
},
appKey: '<%= $appKey %>',
basename: '<%= $basename %>',
API_HOST: '<%= $API_HOST %>'
};
APP_ENV.API_HOST = sessionStorage.API_HOST || APP_ENV.API_HOST || window.location.origin;
window.process = {
env: { app: APP_ENV },
NODE_ENV: '<%= $mode %>'
};
window.__JJB_ENVIRONMENT__ = {
API_HOST: APP_ENV.API_HOST,
redirect: '<%= $redirectLogin %>',
FRAMEWORK: APP_ENV.antd
};
})();
</script>
<!-- SCRIPTS -->
<% for (const item of $scripts) { %>
<script src="<%= item %>" type="text/javascript"></script>
<% } %>
</head>
<body >
<!-- NOSCRIPT -->
<noscript>此网页需要开启JavaScript功能。</noscript>
<!-- MAIN -->
<% const { root } = $element; %>
<div id="<%= root.id %>" style="width: 100%; height: 100%; position: relative;overflow-y: auto"></div>
</body>
</html>

View File

@ -0,0 +1,41 @@
import { declareRequest } from "@cqsjjb/jjb-dva-runtime";
export const corpCertificateList = declareRequest(
"corpCertificateLoading",
"Post > @/certificate/corpCertificate/list",
);
export const corpCertificateInfo = declareRequest(
"corpCertificateLoading",
"Get > /certificate/corpCertificate/getInfoById?id={id}",
);
export const corpCertificateAdd = declareRequest(
"corpCertificateLoading",
"Post > @/certificate/corpCertificate/save",
);
export const corpCertificateEdit = declareRequest(
"corpCertificateLoading",
"Put > @/certificate/corpCertificate/edit",
);
// 股份端查看分公司-相关方证照统计信息分页
export const corpCertificateStatPage = declareRequest(
"corpCertificateLoading",
"Post > @/certificate/corpCertificate/statPage",
);
export const corpCertificateRemove = declareRequest(
"corpCertificateLoading",
"Put > @/certificate/corpCertificate/remove?id={id}",
);
// 获取字典
export const dictValuesData = declareRequest(
"corpCertificateLoading",
"Get > /config/dict-trees/list/by/dictValues",
);
export const corpCertificateIsExistCertNo = declareRequest(
"corpCertificateLoading",
"Get > /certificate/corpCertificate/isExistCertNo",
);

View File

@ -0,0 +1,6 @@
import { declareRequest } from "@cqsjjb/jjb-dva-runtime";
export const identifyPartList = declareRequest(
"coursewareLoading",
"Post > @/risk/busIdentifyPart/list",
);

11
src/api/global/index.js Normal file
View File

@ -0,0 +1,11 @@
export {};
// export const riskList = declareRequest(
// "loading",
// "Post > @/xxx",
// "dataSource: [] | res.data || [] & total: 0 | res.totalCount || 0 & pageIndex: 1 | res.pageIndex || 1 & pageSize: 10 | res.pageSize || 10",
// );
// export const riskDelete = declareRequest(
// "loading",
// "Delete > @/xxx/{id}",
// );

View File

@ -0,0 +1,48 @@
import { declareRequest } from "@cqsjjb/jjb-dva-runtime";
export const userCertificateList = declareRequest(
"userCertificateLoading",
"Post > @/certificate/userCertificate/list",
);
export const userCertificateInfo = declareRequest(
"userCertificateLoading",
"Get > /certificate/userCertificate/getInfoById/{id}",
);
export const userCertificateAdd = declareRequest(
"userCertificateLoading",
"Post > @/certificate/userCertificate/save",
);
export const userCertificateEdit = declareRequest(
"userCertificateLoading",
"Put > @/certificate/userCertificate/edit",
);
// 股份端查看分公司-相关方证照统计信息分页
export const userCertificateStatPage = declareRequest(
"userCertificateLoading",
"Post > @/certificate/userCertificate/corpCertificateStatPage",
);
export const userCertificateRemove = declareRequest(
"userCertificateLoading",
"Delete > @/certificate/userCertificate/delete/{id}",
);
// 获取字典
export const dictValuesData = declareRequest(
"userCertificateLoading",
"Get > /config/dict-trees/list/by/dictValues",
);
export const userCertificateIsExistCertNo = declareRequest(
"userCertificateLoading",
"Get > /certificate/userCertificate/isExistCertNo",
);
export const projectHasUser = declareRequest(
"userCertificateLoading",
`Get > /xgfManager/project/projectHasUser`,
);
// 获取人员 (带权限)
export const getUserlistAll = declareRequest(
"corpCertificateLoading",
"Post > @/certificate/user/listAll",
);

62
src/assets/css/common.css Normal file
View File

@ -0,0 +1,62 @@
/* 打印时:隐藏所有内容 */
@media print {
body * {
visibility: hidden;
}
/* 只显示要打印的 div 及其子元素 */
#print-invoice,
#print-invoice * {
visibility: visible;
}
#print-invoice {
position: absolute;
left: 0;
top: 0;
width: 100%;
padding: 1cm;
box-sizing: border-box;
font-size: 12pt;
font-family: 'SimSun', '宋体', serif;
}
/* 隐藏弹窗中的操作按钮(双重保险) */
.no-print {
display: none !important;
}
}
/* 屏幕上正常显示 */
@media screen {
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.modal-content {
background: white;
padding: 20px;
border-radius: 8px;
width: 80%;
max-width: 600px;
}
.modal-actions {
margin-top: 20px;
text-align: right;
}
.modal-actions button {
margin-left: 10px;
padding: 8px 16px;
}
}

View File

@ -0,0 +1,142 @@
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { Descriptions, Divider } from "antd";
import { useEffect, useState } from "react";
import Page from "zy-react-library/components/Page";
import PreviewImg from "zy-react-library/components/PreviewImg";
import { UPLOAD_FILE_TYPE_ENUM } from "zy-react-library/enum/uploadFile/gwj";
import useGetFile from "zy-react-library/hooks/useGetFile";
import useGetUrlQuery from "zy-react-library/hooks/useGetUrlQuery";
import { NS_USER_CERTIFICATE } from "~/enumerate/namespace";
const JOB_STATUS = {
1: "在职",
0: "离职",
2: "信息变更中",
3: "未入职",
4: "实习生",
5: "实习结束",
6: "退休",
7: "劳务派遣",
8: "劳务派遣结束",
11: "入职待审核",
10: "离职待审核",
};
function PersonInChargeInfo({ props, certificatePhotoType }) {
const [info, setInfo] = useState({});
const queryParams = useGetUrlQuery();
const { loading: getFileLoading, getFile } = useGetFile();
useEffect(() => {
console.log(queryParams);
const fetchData = async () => {
const res = await props.userCertificateInfo({
id: queryParams["id"],
});
const licenseFile = await getFile({
eqType: UPLOAD_FILE_TYPE_ENUM[certificatePhotoType],
eqForeignKey: res.data.userCertificateId,
});
res.data.licenseFile = licenseFile;
setInfo(res.data || {});
};
fetchData();
}, []);
return (
<div>
<Page headerTitle="证书信息查看">
<div>
<Divider orientation="left">人员信息</Divider>
<Descriptions
bordered
loding={getFileLoading}
items={[
{
label: "姓名",
children: info.name,
},
{
label: "企业名称",
children: info.corpinfoName,
},
{
label: "部门名称",
children: info.departmentName,
},
{
label: "岗位名称",
children: info.userPostName,
},
{
label: "就职状态",
children: <div>{JOB_STATUS[info.employmentStatus]}</div>,
},
]}
column={2}
labelStyle={{
width: 200,
}}
contentStyle={{
width: 500,
}}
/>
<Divider orientation="left">证书信息</Divider>
<Descriptions
bordered
loding={getFileLoading}
items={[
{
label: "证书名称",
children: info.certificateName,
},
{
label: "证书编号",
children: info.certificateCode,
},
{
label: "岗位名称",
children: info.postName,
},
{
label: "发证机构",
children: info.issuingAuthority,
},
{
label: "发证日期",
children: info.dateIssue,
},
{
label: "有效期至",
children: <div>{`${info.certificateDateStart ?? ""} - ${info.certificateDateEnd ?? ""}`}</div>,
},
{
label: "证照照片",
children: <PreviewImg files={info.licenseFile} />,
},
]}
column={2}
labelStyle={{
width: 200,
}}
contentStyle={{
width: 500,
}}
/>
</div>
</Page>
</div>
);
}
export default Connect([NS_USER_CERTIFICATE], true)(PersonInChargeInfo);

View File

@ -0,0 +1,665 @@
import { Permission } from "@cqsjjb/jjb-common-decorator/permission";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { Button, Form, message, Modal, Space } from "antd";
import { useEffect, useRef, useState } from "react";
import FormBuilder from "zy-react-library/components/FormBuilder";
import AddIcon from "zy-react-library/components/Icon/AddIcon";
import Page from "zy-react-library/components/Page";
import Search from "zy-react-library/components/Search";
import DictionarySelect from "zy-react-library/components/Select/Dictionary";
import PersonnelSelect from "zy-react-library/components/Select/Personnel/Gwj";
import Table from "zy-react-library/components/Table";
import TooltipPreviewImg from "zy-react-library/components/TooltipPreviewImg";
import Upload from "zy-react-library/components/Upload";
import { FORM_ITEM_RENDER_ENUM } from "zy-react-library/enum/formItemRender";
import { UPLOAD_FILE_TYPE_ENUM } from "zy-react-library/enum/uploadFile/gwj";
import useDeleteFile from "zy-react-library/hooks/useDeleteFile";
import useGetFile from "zy-react-library/hooks/useGetFile";
import useGetUrlQuery from "zy-react-library/hooks/useGetUrlQuery";
import useGetUserInfo from "zy-react-library/hooks/useGetUserInfo";
import useTable from "zy-react-library/hooks/useTable";
import useUploadFile from "zy-react-library/hooks/useUploadFile";
import { NS_USER_CERTIFICATE } from "~/enumerate/namespace";
import { useDebounce } from "~/utils";
import { useLocation } from 'react-router-dom';
const JOB_STATUS = {
1: "在职",
0: "离职",
2: "信息变更中",
3: "未入职",
4: "实习生",
5: "实习结束",
6: "退休",
7: "劳务派遣",
8: "劳务派遣结束",
11: "入职待审核",
10: "离职待审核",
};
function List({ props, certificatePhotoType, displayType, personnelType, permissionAdd, permissionEdit, permissionView, permissionDel, dictionaryType }) {
const [addModalOpen, setAddModalOpen] = useState(false);
const [currentId, setCurrentId] = useState("");
const queryParams = useGetUrlQuery();
const { loading: getFileLoading, getFile } = useGetFile();
const [form] = Form.useForm();
const { tableProps, getData } = useTable(props["userCertificateList"], {
form,
transform: (formData) => {
return {
...formData,
eqCorpinfoId: queryParams["corpinfoId"],
eqType: personnelType,
};
},
});
const onDelete = async (row) => {
props["projectHasUser"]({ eqUserId: row.userId }).then((res) => {
console.log(res);
if (res.data && res.data.length > 0) {
const arr = [];
const userArr = [];
res.data.forEach((item) => {
arr.push(item.projectName);
userArr.push(item.userName);
});
Modal.confirm({
title: "提示",
content: (
<div>
<div>
{[...new Set(userArr)].join(",")}
有正在进行的项目包含
{
[...new Set(arr)].join(",")
}
确定删除吗
</div>
<div style={{ fontSize: 14, color: "red" }}>删除可能会对正在进行的项目造成异常状态请谨慎操作</div>
</div>
),
onOk: () => {
props["userCertificateRemove"]({
id: row.id,
}).then((res) => {
if (res.success) {
message.success("删除成功");
getData();
}
});
},
});
}
else {
Modal.confirm({
title: "提示",
content: "确定删除吗?",
onOk: () => {
props["userCertificateRemove"]({
id: row.id,
}).then((res) => {
if (res.success) {
message.success("删除成功");
getData();
}
});
},
});
}
});
};
const [fileCache, setFileCache] = useState({});
const [loadingKeys, setLoadingKeys] = useState(new Set());
const pendingLoadIdsRef = useRef(new Set());
const requestQueueRef = useRef([]);
const activeRequestsRef = useRef(0);
const MAX_CONCURRENT_REQUESTS = 3; // 最大并发请求数
const processQueue = () => {
while (
requestQueueRef.current.length > 0
&& activeRequestsRef.current < MAX_CONCURRENT_REQUESTS
) {
const { id, resolve, reject } = requestQueueRef.current.shift();
activeRequestsRef.current++;
getFile({
eqType: UPLOAD_FILE_TYPE_ENUM[certificatePhotoType],
eqForeignKey: id,
})
.then((res) => {
setFileCache(prev => ({
...prev,
[id]: res || [],
}));
resolve(res);
})
.catch((err) => {
console.error("图片加载失败:", err);
reject(err);
})
.finally(() => {
activeRequestsRef.current--;
setLoadingKeys((prev) => {
const newSet = new Set(prev);
newSet.delete(id);
return newSet;
});
processQueue();
});
}
};
const loadFileForRecord = (userQualificationinfoId) => {
if (!userQualificationinfoId)
return Promise.resolve();
if (fileCache[userQualificationinfoId])
return Promise.resolve();
if (loadingKeys.has(userQualificationinfoId))
return Promise.resolve();
if (pendingLoadIdsRef.current.has(userQualificationinfoId))
return Promise.resolve();
pendingLoadIdsRef.current.add(userQualificationinfoId);
setLoadingKeys(prev => new Set([...prev, userQualificationinfoId]));
return new Promise((resolve, reject) => {
requestQueueRef.current.push({ id: userQualificationinfoId, resolve, reject });
processQueue();
}).finally(() => {
pendingLoadIdsRef.current.delete(userQualificationinfoId);
});
};
// 清除伪类
useEffect(() => {
if (displayType === "View") {
const style = document.createElement("style");
style.innerHTML = `
.search-layout::after {
content: none !important;
display: none !important;
}
`;
document.head.appendChild(style);
// 清理函数,在组件卸载时移除样式
return () => {
document.head.removeChild(style);
};
}
}, []);
// 缓存数据变化时清空图片缓存
useEffect(() => {
if (tableProps.dataSource) {
const currentIds = new Set(tableProps.dataSource.map(item => item.userCertificateId).filter(Boolean));
setFileCache((prev) => {
const newCache = {};
Object.keys(prev).forEach((id) => {
if (currentIds.has(id)) {
newCache[id] = prev[id];
}
});
return newCache;
});
}
}, [tableProps.dataSource]);
// 记录已经触发过加载的 ID避免 render 重复触发
const triggeredLoadIdsRef = useRef(new Set());
useEffect(() => {
if (tableProps.dataSource) {
tableProps.dataSource.forEach((record) => {
const id = record.userCertificateId;
if (id && !triggeredLoadIdsRef.current.has(id)) {
triggeredLoadIdsRef.current.add(id);
loadFileForRecord(id);
}
});
}
// 组件卸载或数据源变化时重置
return () => {
triggeredLoadIdsRef.current.clear();
};
}, [tableProps.dataSource]);
return (
<Page isShowAllAction={false}>
<Search
form={form}
options={[
{
name: "likeUserName",
label: "姓名",
},
{
name: "likePostName",
label: "岗位名称",
},
]}
onFinish={getData}
/>
<Table
loding={getFileLoading}
options={displayType !== "View"}
toolBarRender={() => (
<>
{
(displayType !== "View" && props.permission(permissionAdd))
&& (
<Button
type="primary"
icon={<AddIcon />}
onClick={() => {
setAddModalOpen(true);
}}
>
新增
</Button>
)
}
</>
)}
columns={[
{
title: "姓名",
dataIndex: "name",
},
{
title: "证书名称",
dataIndex: "certificateName",
},
{
title: "证书编号",
dataIndex: "certificateCode",
},
{
title: "岗位名称",
dataIndex: "postName",
},
{
title: "有效期至",
dataIndex: "certificateNo",
width: 280,
render: (_, record) =>
<div>{`${record.certificateDateStart ?? ""} - ${record.certificateDateEnd ?? ""}`}</div>,
},
{
title: "就职状态",
dataIndex: "certificateNo",
render: (_, record) => (
<div>{JOB_STATUS[record.employmentStatus] || "-"}</div>
),
},
{
title: "图片",
dataIndex: "name",
render: (_, record) => {
const id = record.userCertificateId;
const files = fileCache[id] || [];
if (!files.length)
return <span></span>;
return <TooltipPreviewImg files={files} />;
},
},
{
title: "操作",
width: 200,
render: (_, record) => (
<Space>
{
props.permission(permissionView)
&& (
<Button
type="link"
onClick={() => props.history.push(`./View?id=${record.id}`)}
>
查看
</Button>
)
}
{
(displayType !== "View" && props.permission(permissionEdit))
&& (
<Button
type="link"
onClick={() => {
setAddModalOpen(true);
setCurrentId(record.id);
}}
>
编辑
</Button>
)
}
{
(displayType !== "View" && props.permission(permissionDel))
&& (
<Button
danger
type="link"
onClick={() => onDelete(record)}
>
删除
</Button>
)
}
</Space>
),
},
]}
{...tableProps}
/>
{addModalOpen && (
<AddModal
open={addModalOpen}
loding={props.userCertificate.userCertificateLoading}
getData={getData}
currentId={currentId}
requestAdd={props["userCertificateAdd"]}
requestEdit={props["userCertificateEdit"]}
requestDetails={props["userCertificateInfo"]}
userCertificateIsExistCertNo={props["userCertificateIsExistCertNo"]}
getUserlistAll={props["getUserlistAll"]}
certificatePhotoType={certificatePhotoType}
personnelType={personnelType}
dictionaryType={dictionaryType}
onCancel={() => {
setAddModalOpen(false);
setCurrentId("");
}}
onSuccess={(userQualificationinfoId) => {
// 清除该记录的图片缓存,强制下次 render 时重新加载
setFileCache((prev) => {
const newCache = { ...prev };
delete newCache[userQualificationinfoId];
return newCache;
});
}}
/>
)}
</Page>
);
}
function AddModalComponent(props) {
const [form] = Form.useForm();
const [userQualificationinfoId, setUserQualificationinfoId] = useState("");
const [userObj, setUserObj] = useState({});
const { loading: deleteFileLoading, deleteFile } = useDeleteFile();
const { loading: uploadFileLoading, uploadFile } = useUploadFile();
const { loading: getFileLoading, getFile } = useGetFile();
const { getUserInfo } = useGetUserInfo();
const [deleteCardImageFiles, setDeleteCardImageFiles] = useState([]);
const [CertificateCodeValue, setPertificateCodeValue] = useState(null);
const debouncedCertificateCodeValue = useDebounce(CertificateCodeValue, 100);
const [personnelData, setPersonnelData] = useState([]);
const [isSubmit, setIsSubmit] = useState(true);
const location = useLocation();
const getUserInfoFun = async () => {
const userInfo = await getUserInfo();
setUserObj(userInfo);
};
const getUserlistFun = async () => {
props.getUserlistAll({menuPath:"/certificate"+location.pathname}).then(res => {
if(res.data) {
res.data.forEach(item => {
item.bianma=item.id;
})
setPersonnelData(res.data)
}
})
};
useEffect(() => {
if (props.currentId) {
const fetchData = async () => {
const { data } = await props.requestDetails({
id: props.currentId,
});
const certificateImgs = await getFile({
eqType: UPLOAD_FILE_TYPE_ENUM[props.certificatePhotoType],
eqForeignKey: data.userCertificateId,
});
data.certificateDate = [data.certificateDateStart, data.certificateDateEnd];
data.certificateImgs = certificateImgs;
form.setFieldsValue(data);
setUserQualificationinfoId(data.userCertificateId);
};
fetchData();
}
getUserInfoFun();
getUserlistFun()
}, [props.currentId]);
const onCancel = () => {
form.resetFields();
props.onCancel();
};
const onSubmit = async (values) => {
if (!isSubmit) {
form.setFields([
{
name: "certificateCode",
errors: ["证书编号重复"],
},
]);
return;
}
if (values.certificateImgs.length !== 2) {
message.error("证照照片必须上传正反面两张");
return;
}
const maxSizeInBytes = 10 * 1024 * 1024;
const isOverSize = values.certificateImgs.some(file => file.size > maxSizeInBytes);
if (isOverSize) {
message.error("选择的图片不能大于10MB");
return;
}
await deleteFile({
single: false,
files: deleteCardImageFiles,
});
const { id } = await uploadFile({
single: false,
files: values.certificateImgs,
params: {
type: UPLOAD_FILE_TYPE_ENUM[props.certificatePhotoType],
foreignKey: userQualificationinfoId,
},
});
values.certificateDateStart = values.certificateDate[0];
values.certificateDateEnd = values.certificateDate[1];
values.type = props.personnelType;
const personnelTypeMap = {
tezhongzuoye: "特种作业人员",
tzsbczry: "特种设备操作人员",
zyfzr: "主要负责人",
aqscglry: "安全生产管理人员",
};
values.typeName = personnelTypeMap[props.personnelType];
if (props.currentId) {
values.id = props.currentId;
values.userCertificateId = userQualificationinfoId;
await props.requestEdit(values).then((res) => {
if (res.success) {
onCancel();
props.getData();
props.onSuccess(userQualificationinfoId);
message.success("编辑成功");
}
});
}
else {
values.userCertificateId = id;
await props.requestAdd(values).then((res) => {
if (res.success) {
onCancel();
props.getData();
props.onSuccess(userQualificationinfoId);
message.success("新增成功");
}
});
}
};
// 校验重复
useEffect(() => {
if (!debouncedCertificateCodeValue) {
form.setFields([
{
name: "certificateCode",
errors: [],
},
]);
return;
}
props["userCertificateIsExistCertNo"]({
certNo: debouncedCertificateCodeValue,
id: props.currentId ?? "",
}).then((res) => {
if (res.data) {
form.setFields([
{
name: "certificateCode",
errors: ["证书编号重复"],
},
]);
setIsSubmit(false);
}
else {
setIsSubmit(true);
}
});
}, [debouncedCertificateCodeValue]);
const onValuesChange = (changedValues) => {
if ("certificateCode" in changedValues) {
setPertificateCodeValue(changedValues.certificateCode ?? "");
}
};
return (
<Modal
open={props.open}
maskClosable={false}
title={props.currentId ? "编辑" : "新增"}
width={800}
confirmLoading={
deleteFileLoading || uploadFileLoading || getFileLoading || props.loding
}
onOk={form.submit}
onCancel={onCancel}
>
<FormBuilder
form={form}
onValuesChange={onValuesChange}
span={24}
values={{
securityFlag: 0,
}}
options={[
{
name: "userId",
label: "选择人员",
render: FORM_ITEM_RENDER_ENUM.SELECT,
items:personnelData
},
{ name: "userName", label: "人员名称", onlyForLabel: true },
{
name: "certificateName",
label: "证书名称",
},
{
name: "certificateCode",
label: "证书编号",
},
{
name: "postId",
label: "岗位名称",
render: (
<DictionarySelect
dictValue={props.dictionaryType}
onGetLabel={label => form.setFieldValue("postName", label)}
/>
),
},
{ name: "postName", label: "岗位名称", onlyForLabel: true },
{
name: "issuingAuthority",
label: "发证机构",
},
{
name: "dateIssue",
label: "发证日期",
render: FORM_ITEM_RENDER_ENUM.DATE,
},
{
name: "certificateDate",
label: "有效期",
render: FORM_ITEM_RENDER_ENUM.DATE_RANGE,
rules: [
{
validator: (_, value) => {
const allEmptyStrings = Array.isArray(value) && value.every(item => item === "");
if (allEmptyStrings) {
return Promise.reject(new Error("请选择有效期"));
}
return Promise.resolve();
},
},
],
},
{
name: "certificateImgs",
label: "证照照片",
render: (
<Upload
maxCount={2}
onGetRemoveFile={(file) => {
setDeleteCardImageFiles([...deleteCardImageFiles, file]);
}}
tipContent={(
<div
style={{
lineHeight: 1.6,
color: "red",
fontSize: 12,
}}
>
<div>
温馨提示用户要上传证照照片正反面证照照片数量是2张单张大小不超过10MB支持jpgjpegpng格式
</div>
</div>
)}
/>
),
},
]}
labelCol={{
span: 10,
}}
showActionButtons={false}
onFinish={onSubmit}
/>
</Modal>
);
}
const AddModal = AddModalComponent;
export default Connect([NS_USER_CERTIFICATE], true)(Permission(List));

View File

@ -0,0 +1,151 @@
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { Descriptions, Divider } from "antd";
import { useEffect, useState } from "react";
import Page from "zy-react-library/components/Page";
import PreviewImg from "zy-react-library/components/PreviewImg";
import { UPLOAD_FILE_TYPE_ENUM } from "zy-react-library/enum/uploadFile/gwj";
import useGetFile from "zy-react-library/hooks/useGetFile";
import useGetUrlQuery from "zy-react-library/hooks/useGetUrlQuery";
import { NS_USER_CERTIFICATE } from "~/enumerate/namespace";
const JOB_STATUS = {
1: "在职",
0: "离职",
2: "信息变更中",
3: "未入职",
4: "实习生",
5: "实习结束",
6: "退休",
7: "劳务派遣",
8: "劳务派遣结束",
11: "入职待审核",
10: "离职待审核",
};
function SpecialCertificateInfo({ props, certificatePhotoType }) {
const [info, setInfo] = useState({});
const queryParams = useGetUrlQuery();
const { loading: getFileLoading, getFile } = useGetFile();
useEffect(() => {
console.log(queryParams["personnelType"]);
const fetchData = async () => {
const res = await props.userCertificateInfo({
id: queryParams["id"],
});
const licenseFile = await getFile({
eqType: UPLOAD_FILE_TYPE_ENUM[certificatePhotoType],
eqForeignKey: res.data.userCertificateId,
});
res.data.licenseFile = licenseFile;
setInfo(res.data || {});
};
fetchData();
}, []);
return (
<div>
<Page headerTitle="证书信息查看">
<div>
<Divider orientation="left">人员信息</Divider>
<Descriptions
bordered
loding={getFileLoading}
items={[
{
label: "姓名",
children: info.name,
},
{
label: "企业名称",
children: info.corpinfoName,
},
{
label: "部门名称",
children: info.departmentName,
},
{
label: "岗位名称",
children: info.userPostName,
},
{
label: "就职状态",
children: <div>{JOB_STATUS[info.employmentStatus]}</div>,
},
]}
column={2}
labelStyle={{
width: 200,
}}
contentStyle={{
width: 500,
}}
/>
<Divider orientation="left">证书信息</Divider>
<Descriptions
bordered
loding={getFileLoading}
items={[
{
label: "证书名称",
children: info.certificateName,
},
{
label: "证书编号",
children: info.certificateCode,
},
{
label: "发证机构",
children: info.issuingAuthority,
},
{
label: queryParams["personnelType"] === "tzsbczry" ? "操作项目" : "行业类别",
children: queryParams["personnelType"] === "tzsbczry" ? info.assignmentOperatingItemsName : info.industryCategoryName,
},
{
label: queryParams["personnelType"] === "tzsbczry" ? "作业类别" : "操作项目",
children: queryParams["personnelType"] === "tzsbczry" ? info.assignmentCategoryName : info.industryOperatingItemsName,
},
{
label: "发证日期",
children: info.dateIssue,
},
{
label: "复审日期",
children: info.reviewDate,
},
{
label: "有效期至",
children: <div>{`${info.certificateDateStart ?? ""} - ${info.certificateDateEnd ?? ""}`}</div>,
},
{
label: "证照照片",
children: <PreviewImg files={info.licenseFile} />,
},
]}
column={2}
labelStyle={{
width: 200,
}}
contentStyle={{
width: 500,
}}
/>
</div>
</Page>
</div>
);
}
export default Connect([NS_USER_CERTIFICATE], true)(SpecialCertificateInfo);

View File

@ -0,0 +1,796 @@
import { Permission } from "@cqsjjb/jjb-common-decorator/permission";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { Button, Form, message, Modal, Space } from "antd";
import { useEffect, useRef, useState } from "react";
import { useLocation } from "react-router-dom";
import FormBuilder from "zy-react-library/components/FormBuilder";
import AddIcon from "zy-react-library/components/Icon/AddIcon";
import Page from "zy-react-library/components/Page";
import Search from "zy-react-library/components/Search";
import DictionarySelect from "zy-react-library/components/Select/Dictionary";
import Table from "zy-react-library/components/Table";
import TooltipPreviewImg from "zy-react-library/components/TooltipPreviewImg";
import Upload from "zy-react-library/components/Upload";
import { FORM_ITEM_RENDER_ENUM } from "zy-react-library/enum/formItemRender";
import { UPLOAD_FILE_TYPE_ENUM } from "zy-react-library/enum/uploadFile/gwj";
import useDeleteFile from "zy-react-library/hooks/useDeleteFile";
import useGetFile from "zy-react-library/hooks/useGetFile";
import useGetUrlQuery from "zy-react-library/hooks/useGetUrlQuery";
import useGetUserInfo from "zy-react-library/hooks/useGetUserInfo";
import useTable from "zy-react-library/hooks/useTable";
import useUploadFile from "zy-react-library/hooks/useUploadFile";
import { NS_USER_CERTIFICATE } from "~/enumerate/namespace";
import { useDebounce } from "~/utils";
const JOB_STATUS = {
1: "在职",
0: "离职",
2: "信息变更中",
3: "未入职",
4: "实习生",
5: "实习结束",
6: "退休",
7: "劳务派遣",
8: "劳务派遣结束",
11: "入职待审核",
10: "离职待审核",
};
function SpecialCertificateList({ props, certificatePhotoType, displayType, personnelType, permissionAdd, permissionEdit, permissionView, permissionDel, dictionaryType }) {
const [addModalOpen, setAddModalOpen] = useState(false);
const [currentId, setCurrentId] = useState("");
const [operatingProjectType, setOperatingProjectType] = useState("");
const queryParams = useGetUrlQuery();
const { loading: getFileLoading, getFile } = useGetFile();
const [form] = Form.useForm();
const { tableProps, getData } = useTable(props["userCertificateList"], {
form,
transform: (formData) => {
if (formData.eqIndustryCategoryCode) {
setOperatingProjectType(formData.eqIndustryCategoryCode);
}
if (formData.eqAssignmentOperatingItemsCode) {
setOperatingProjectType(formData.eqAssignmentOperatingItemsCode);
}
return {
...formData,
eqCorpinfoId: queryParams["corpinfoId"],
eqType: personnelType,
};
},
});
// 清除伪类
useEffect(() => {
if (displayType === "View") {
const style = document.createElement("style");
style.innerHTML = `
.search-layout::after {
content: none !important;
display: none !important;
}
`;
document.head.appendChild(style);
// 清理函数,在组件卸载时移除样式
return () => {
document.head.removeChild(style);
};
}
}, []);
const onDelete = async (row) => {
props["projectHasUser"]({ eqUserId: row.userId }).then((res) => {
if (res.data && res.data.length > 0) {
const arr = [];
const userArr = [];
res.data.forEach((item) => {
arr.push(item.projectName);
userArr.push(item.userName);
});
Modal.confirm({
title: "提示",
content: (
<div>
<div>
{[...new Set(userArr)].join(",")}
有正在进行的项目包含
{
[...new Set(arr)].join(",")
}
确定删除吗
</div>
<div style={{ fontSize: 14, color: "red" }}>删除可能会对正在进行的项目造成异常状态请谨慎操作</div>
</div>
),
onOk: () => {
props["userCertificateRemove"]({
id: row.id,
}).then((res) => {
if (res.success) {
message.success("删除成功");
getData();
}
});
},
});
}
else {
Modal.confirm({
title: "提示",
content: "确定删除吗?",
onOk: () => {
props["userCertificateRemove"]({
id: row.id,
}).then((res) => {
if (res.success) {
message.success("删除成功");
getData();
}
});
},
});
}
});
};
const [fileCache, setFileCache] = useState({});
const [loadingKeys, setLoadingKeys] = useState(new Set());
const pendingLoadIdsRef = useRef(new Set());
const requestQueueRef = useRef([]);
const activeRequestsRef = useRef(0);
const MAX_CONCURRENT_REQUESTS = 3; // 最大并发请求数
const processQueue = () => {
while (
requestQueueRef.current.length > 0
&& activeRequestsRef.current < MAX_CONCURRENT_REQUESTS
) {
const { id, resolve, reject } = requestQueueRef.current.shift();
activeRequestsRef.current++;
getFile({
eqType: UPLOAD_FILE_TYPE_ENUM[certificatePhotoType],
eqForeignKey: id,
})
.then((res) => {
setFileCache(prev => ({
...prev,
[id]: res || [],
}));
resolve(res);
})
.catch((err) => {
console.error("图片加载失败:", err);
reject(err);
})
.finally(() => {
activeRequestsRef.current--;
setLoadingKeys((prev) => {
const newSet = new Set(prev);
newSet.delete(id);
return newSet;
});
processQueue();
});
}
};
const loadFileForRecord = (userQualificationinfoId) => {
if (!userQualificationinfoId)
return Promise.resolve();
if (fileCache[userQualificationinfoId])
return Promise.resolve();
if (loadingKeys.has(userQualificationinfoId))
return Promise.resolve();
if (pendingLoadIdsRef.current.has(userQualificationinfoId))
return Promise.resolve();
pendingLoadIdsRef.current.add(userQualificationinfoId);
setLoadingKeys(prev => new Set([...prev, userQualificationinfoId]));
return new Promise((resolve, reject) => {
requestQueueRef.current.push({ id: userQualificationinfoId, resolve, reject });
processQueue();
}).finally(() => {
pendingLoadIdsRef.current.delete(userQualificationinfoId);
});
};
// 缓存数据变化时清空图片缓存
useEffect(() => {
if (tableProps.dataSource) {
const currentIds = new Set(tableProps.dataSource.map(item => item.userCertificateId).filter(Boolean));
setFileCache((prev) => {
const newCache = {};
Object.keys(prev).forEach((id) => {
if (currentIds.has(id)) {
newCache[id] = prev[id];
}
});
return newCache;
});
}
}, [tableProps.dataSource]);
// 记录已经触发过加载的 ID避免 render 重复触发
const triggeredLoadIdsRef = useRef(new Set());
useEffect(() => {
if (tableProps.dataSource) {
tableProps.dataSource.forEach((record) => {
const id = record.userCertificateId;
if (id && !triggeredLoadIdsRef.current.has(id)) {
triggeredLoadIdsRef.current.add(id);
loadFileForRecord(id);
}
});
}
// 组件卸载或数据源变化时重置
return () => {
triggeredLoadIdsRef.current.clear();
};
}, [tableProps.dataSource]);
const onValuesChange = (changedValues) => {
if ("eqIndustryCategoryCode" in changedValues) {
const newIdCard = changedValues.eqIndustryCategoryCode;
if (newIdCard) {
setOperatingProjectType(newIdCard);
form.setFieldsValue({
eqIndustryOperatingItemsCode: undefined,
});
}
}
if ("eqAssignmentOperatingItemsCode" in changedValues) {
const newIdCard = changedValues.eqAssignmentOperatingItemsCode;
if (newIdCard) {
setOperatingProjectType(newIdCard);
form.setFieldsValue({
eqAssignmentCategoryCode: undefined,
});
}
}
};
return (
<Page isShowAllAction={false}>
<Search
onValuesChange={onValuesChange}
form={form}
options={[
{
name: "likeUserName",
label: "姓名",
},
{
name: personnelType === "tzsbczry" ? "eqAssignmentOperatingItemsCode" : "eqIndustryCategoryCode",
label: personnelType === "tzsbczry" ? "操作项目" : "行业类别",
render: (
<DictionarySelect
dictValue={dictionaryType}
/>
),
},
{
name: personnelType === "tzsbczry" ? "eqAssignmentCategoryCode" : "eqIndustryOperatingItemsCode",
label: personnelType === "tzsbczry" ? "作业类别" : "操作项目",
render: (
<DictionarySelect
dictValue={operatingProjectType}
/>
),
},
]}
onFinish={getData}
/>
<Table
loding={getFileLoading}
options={displayType !== "View"}
toolBarRender={() => (
<>
{
(displayType !== "View" && props.permission(permissionAdd))
&& (
<Button
type="primary"
icon={<AddIcon />}
onClick={() => {
setAddModalOpen(true);
}}
>
新增
</Button>
)
}
</>
)}
columns={[
{
title: "姓名",
dataIndex: "name",
},
{
title: "证书名称",
dataIndex: "certificateName",
},
{
title: "证书编号",
dataIndex: "certificateCode",
},
{
title: personnelType === "tzsbczry" ? "操作项目" : "行业类别",
dataIndex: personnelType === "tzsbczry" ? "assignmentOperatingItemsName" : "industryCategoryName",
},
{
title: personnelType === "tzsbczry" ? "作业类别" : "操作项目",
dataIndex: personnelType === "tzsbczry" ? "assignmentCategoryName" : "industryOperatingItemsName",
},
{
title: "有效期至",
dataIndex: "certificateDate",
width: 280,
render: (_, record) =>
<div>{`${record.certificateDateStart ?? ""} - ${record.certificateDateEnd ?? ""}`}</div>,
},
{
title: "就职状态",
dataIndex: "employmentStatus",
render: (_, record) => (
<div>{JOB_STATUS[record.employmentStatus] || "-"}</div>
),
},
{
title: "图片",
dataIndex: "name",
render: (_, record) => {
const id = record.userCertificateId;
const files = fileCache[id] || [];
if (!files.length)
return <span></span>;
return <TooltipPreviewImg files={files} />;
},
},
{
title: "操作",
width: 200,
render: (_, record) => (
<Space>
{
props.permission(permissionView)
&& (
<Button
type="link"
onClick={() => props.history.push(`./View?id=${record.id}&personnelType=${personnelType}`)}
>
查看
</Button>
)
}
{
(displayType !== "View" && props.permission(permissionEdit))
&& (
<Button
type="link"
onClick={() => {
setAddModalOpen(true);
setCurrentId(record.id);
}}
>
编辑
</Button>
)
}
{
displayType !== "View" && props.permission(permissionDel)
&& (
<Button
danger
type="link"
onClick={() => onDelete(record)}
>
删除
</Button>
)
}
</Space>
),
},
]}
{...tableProps}
/>
{addModalOpen && (
<AddModal
open={addModalOpen}
loding={props.userCertificate.userCertificateLoading}
getData={getData}
currentId={currentId}
requestAdd={props["userCertificateAdd"]}
requestEdit={props["userCertificateEdit"]}
requestDetails={props["userCertificateInfo"]}
userCertificateIsExistCertNo={props["userCertificateIsExistCertNo"]}
certificatePhotoType={certificatePhotoType}
getUserlistAll={props["getUserlistAll"]}
personnelType={personnelType}
dictionaryType={dictionaryType}
onCancel={() => {
setAddModalOpen(false);
setCurrentId("");
}}
onSuccess={(userQualificationinfoId) => {
// 清除该记录的图片缓存,强制下次 render 时重新加载
setFileCache((prev) => {
const newCache = { ...prev };
delete newCache[userQualificationinfoId];
return newCache;
});
}}
/>
)}
</Page>
);
}
function AddModalComponent(props) {
const [form] = Form.useForm();
const [userQualificationinfoId, setUserQualificationinfoId] = useState("");
const { loading: deleteFileLoading, deleteFile } = useDeleteFile();
const { loading: uploadFileLoading, uploadFile } = useUploadFile();
const { loading: getFileLoading, getFile } = useGetFile();
const [operatingProjectType, setOperatingProjectType] = useState("");
const [deleteCardImageFiles, setDeleteCardImageFiles] = useState([]);
const [CertificateCodeValue, setPertificateCodeValue] = useState(null);
const [userObj, setUserObj] = useState({});
const debouncedCertificateCodeValue = useDebounce(CertificateCodeValue, 100);
const [isSubmit, setIsSubmit] = useState(true);
const [personnelData, setPersonnelData] = useState([]);
const { getUserInfo } = useGetUserInfo();
const location = useLocation();
const getUserInfoFun = async () => {
const userInfo = await getUserInfo();
setUserObj(userInfo);
};
// 校验重复
useEffect(() => {
if (!debouncedCertificateCodeValue) {
form.setFields([
{
name: "certificateCode",
errors: [],
},
]);
return;
}
props["userCertificateIsExistCertNo"]({
certNo: debouncedCertificateCodeValue,
id: props.currentId ?? "",
}).then((res) => {
if (res.data) {
form.setFields([
{
name: "certificateCode",
errors: ["证书编号重复"],
},
]);
setIsSubmit(false);
}
else {
setIsSubmit(true);
}
});
}, [debouncedCertificateCodeValue]);
const getUserlistFun = async () => {
props.getUserlistAll({ menuPath: `/certificate${location.pathname}` }).then((res) => {
if (res.data) {
console.log(res.data);
res.data.forEach((item) => {
item.bianma = item.id;
});
setPersonnelData(res.data);
}
});
};
useEffect(() => {
if (props.currentId) {
const fetchData = async () => {
const { data } = await props.requestDetails({
id: props.currentId,
});
setOperatingProjectType(data.industryCategoryCode ? data.industryCategoryCode : data.assignmentOperatingItemsCode);
data.certificateDate = [data.certificateDateStart, data.certificateDateEnd];
const certificateImgs = await getFile({
eqType: UPLOAD_FILE_TYPE_ENUM[props.certificatePhotoType],
eqForeignKey: data.userCertificateId,
});
data.certificateImgs = certificateImgs;
form.setFieldsValue(data);
setUserQualificationinfoId(data.userCertificateId);
};
fetchData();
}
getUserInfoFun();
getUserlistFun();
}, [props.currentId]);
const onCancel = () => {
form.resetFields();
props.onCancel();
};
const onSubmit = async (values) => {
if (!isSubmit) {
form.setFields([
{
name: "certificateCode",
errors: ["证书编号重复"],
},
]);
return;
}
if (values.certificateImgs.length !== 2) {
message.error("证照照片必须上传正反面两张");
return;
}
const maxSizeInBytes = 10 * 1024 * 1024;
const isOverSize = values.certificateImgs.some(file => file.size > maxSizeInBytes);
if (isOverSize) {
message.error("选择的图片不能大于10MB");
return;
}
await deleteFile({
single: false,
files: deleteCardImageFiles,
});
const { id } = await uploadFile({
single: false,
files: values.certificateImgs,
params: {
type: UPLOAD_FILE_TYPE_ENUM[props.certificatePhotoType],
foreignKey: userQualificationinfoId,
},
});
values.type = props.personnelType;
const personnelTypeMap = {
tezhongzuoye: "特种作业人员",
tzsbczry: "特种设备操作人员",
zyfzr: "主要负责人",
aqscglry: "安全生产管理人员",
};
values.typeName = personnelTypeMap[props.personnelType];
values.certificateDateStart = values.certificateDate[0];
values.certificateDateEnd = values.certificateDate[1];
if (props.currentId) {
values.id = props.currentId;
values.userCertificateId = userQualificationinfoId;
await props.requestEdit(values).then((res) => {
if (res.success) {
onCancel();
props.getData();
props.onSuccess(userQualificationinfoId);
message.success("编辑成功");
}
});
}
else {
values.userCertificateId = id;
await props.requestAdd(values).then((res) => {
if (res.success) {
onCancel();
props.getData();
props.onSuccess(userQualificationinfoId);
message.success("新增成功");
}
});
}
};
const onValuesChange = (changedValues) => {
if ("industryCategoryCode" in changedValues) {
const industryCategoryCode = changedValues.industryCategoryCode;
if (industryCategoryCode) {
setOperatingProjectType(industryCategoryCode);
form.setFieldsValue({
industryOperatingItemsCode: undefined,
});
}
}
if ("assignmentOperatingItemsCode" in changedValues) {
const assignmentOperatingItemsCode = changedValues.assignmentOperatingItemsCode;
if (assignmentOperatingItemsCode) {
setOperatingProjectType(assignmentOperatingItemsCode);
form.setFieldsValue({
assignmentCategoryCode: undefined,
});
}
}
if ("certificateCode" in changedValues) {
setPertificateCodeValue(changedValues.certificateCode ?? "");
}
};
return (
<Modal
open={props.open}
maskClosable={false}
title={props.currentId ? "编辑" : "新增"}
width={800}
confirmLoading={
deleteFileLoading || uploadFileLoading || getFileLoading || props.loding
}
onOk={form.submit}
onCancel={onCancel}
>
<FormBuilder
form={form}
onValuesChange={onValuesChange}
span={24}
values={{
securityFlag: 0,
}}
options={[
{
name: "userId",
label: "选择人员",
render: FORM_ITEM_RENDER_ENUM.SELECT,
items: personnelData,
},
{
name: "certificateName",
label: "证书名称",
},
{
name: "certificateCode",
label: "证书编号",
},
{
name: "issuingAuthority",
label: "发证机构",
},
{
name: "industryCategoryCode",
label: "行业类别",
hidden: !(props.personnelType === "tezhongzuoye"),
render: (
<DictionarySelect
dictValue={props.dictionaryType}
onGetLabel={label => form.setFieldValue("industryCategoryName", label)}
/>
),
},
{ name: "industryCategoryName", label: "行业类别名称", onlyForLabel: true },
{
name: "industryOperatingItemsCode",
label: "操作项目",
hidden: !(props.personnelType === "tezhongzuoye"),
render: (
<DictionarySelect
dictValue={operatingProjectType}
onGetLabel={label => form.setFieldValue("industryOperatingItemsName", label)}
/>
),
},
{ name: "industryOperatingItemsName", label: "操作项目名称", onlyForLabel: true },
{
name: "assignmentOperatingItemsCode",
label: "操作项目",
hidden: !(props.personnelType === "tzsbczry"),
render: (
<DictionarySelect
dictValue={props.dictionaryType}
onGetLabel={label => form.setFieldValue("assignmentOperatingItemsName", label)}
/>
),
},
{ name: "assignmentOperatingItemsName", label: "操作项目名称", onlyForLabel: true },
{
name: "assignmentCategoryCode",
label: "作业类别",
hidden: !(props.personnelType === "tzsbczry"),
render: (
<DictionarySelect
dictValue={operatingProjectType}
onGetLabel={label => form.setFieldValue("assignmentCategoryName", label)}
/>
),
},
{ name: "assignmentCategoryName", label: "作业类别名称", onlyForLabel: true },
{
name: "dateIssue",
label: "发证日期",
render: FORM_ITEM_RENDER_ENUM.DATE,
},
{
name: "certificateDate",
label: "有效期",
render: FORM_ITEM_RENDER_ENUM.DATE_RANGE,
rules: [
{
validator: (_, value) => {
const allEmptyStrings = Array.isArray(value) && value.every(item => item === "");
if (allEmptyStrings) {
return Promise.reject(new Error("请选择有效期"));
}
return Promise.resolve();
},
},
],
},
{
name: "reviewDate",
label: "复审日期",
render: FORM_ITEM_RENDER_ENUM.DATE,
},
{
name: "certificateImgs",
label: "证照照片",
render: (
<Upload
maxCount={2}
onGetRemoveFile={(file) => {
setDeleteCardImageFiles([...deleteCardImageFiles, file]);
}}
tipContent={(
<div
style={{
lineHeight: 1.6,
color: "red",
fontSize: 12,
}}
>
<div>
温馨提示用户要上传证照照片正反面证照照片数量是2张单张大小不超过10MB支持jpgjpegpng格式
</div>
</div>
)}
/>
),
},
]}
labelCol={{
span: 10,
}}
showActionButtons={false}
onFinish={onSubmit}
/>
</Modal>
);
}
const AddModal = AddModalComponent;
export default Connect([NS_USER_CERTIFICATE], true)(Permission(SpecialCertificateList));

1
src/components/index.js Normal file
View File

@ -0,0 +1 @@
export {};

View File

@ -0,0 +1,5 @@
/**
* 全局常量定义
*/
export {};

View File

@ -0,0 +1,8 @@
/**
* 全局上下文定义
*/
import React from "react";
// 获取antd全局静态方法
export const InjectContext = React.createContext({});

View File

@ -0,0 +1,10 @@
/**
* 全局数据状态管理模块定义
*/
import { defineNamespace } from "@cqsjjb/jjb-dva-runtime";
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");

49
src/main.js Normal file
View File

@ -0,0 +1,49 @@
import { setJJBCommonAntdMessage } from "@cqsjjb/jjb-common-lib";
import { setup } from "@cqsjjb/jjb-dva-runtime";
import { message } from "antd";
import dayjs from "dayjs";
import { getFileUrlFromServer } from "zy-react-library/utils";
import "dayjs/locale/zh-cn";
import "../blessed_by_buddha";
require("antd/dist/reset.css");
require("zy-react-library/css/common.less");
dayjs.locale("zh-cn");
setJJBCommonAntdMessage(message);
const app = setup();
getFileUrlFromServer();
// 非底座环境运行
if (!window.__POWERED_BY_QIANKUN__) {
// 云组件默认依赖
window.__coreLib = {};
window.__coreLib.React = require("react");
window.__coreLib.ReactDOM = require("react-dom");
window.__coreLib.jjbCommonLib = require("@cqsjjb/jjb-common-lib");
}
/**
* @description 挂载
* @param props {{ setGlobalState: ({ rendered: boolean }) => void }}
* @returns {Promise<*>} ''
*/
export const mount = async (props) => {
// 云组件默认依赖
window.__coreLib.React = require("react");
window.__coreLib.ReactDOM = require("react-dom");
window.__coreLib.jjbCommonLib = require("@cqsjjb/jjb-common-lib");
app.mount(props);
};
/**
* @description 卸载
* @param props {object}
* @returns {Promise<*>} ''
*/
export const unmount = async props => app.unmount(props);
/**
* @description 启动
* @param props
*/
export const bootstrap = async props => app.bootstrap(props);

View File

@ -0,0 +1,17 @@
import EnterpriseLicenseList from "~/pages/Container/Supervision/EnterpriseLicense/EnterpriseLicense";
function EnterpriseLicense(props) {
return (
<div>
<EnterpriseLicenseList
props={props}
permissionAdd="qyd-qyzzgl-add"
permissionEdit="qyd-qyzzgl-edit"
permissionView="qyd-qyzzgl-info"
permissionDel="qyd-qyzzgl-del"
/>
</div>
);
}
export default EnterpriseLicense;

View File

@ -0,0 +1,23 @@
import { Permission } from "@cqsjjb/jjb-common-decorator/permission";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import PersonInChargeList from "~/components/PersonInChargeList";
import { NS_USER_CERTIFICATE } from "~/enumerate/namespace";
// 企业主要负责人管理
function List(props) {
return (
<div>
<PersonInChargeList
props={props}
certificatePhotoType={161}
personnelType="zyfzr"
permissionAdd="qyd-zyfzrgl-add"
permissionEdit="qyd-zyfzrgl-edit"
permissionView="qyd-zyfzrgl-info"
permissionDel="qyd-zyfzrgl-del"
dictionaryType="zyfzrgwmc0000"
/>
</div>
);
}
export default Connect([NS_USER_CERTIFICATE], true)(Permission(List));

View File

@ -0,0 +1,16 @@
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import PersonInChargeInfo from "~/components/PersonInChargeInfo";
import { NS_USER_CERTIFICATE } from "~/enumerate/namespace";
function View(props) {
return (
<div>
<PersonInChargeInfo
props={props}
certificatePhotoType={161}
/>
</div>
);
}
export default Connect([NS_USER_CERTIFICATE], true)(View);

View File

@ -0,0 +1,9 @@
function PersonInCharge(props) {
return (
<div>
{props.children}
</div>
);
}
export default PersonInCharge;

View File

@ -0,0 +1,23 @@
import { Permission } from "@cqsjjb/jjb-common-decorator/permission";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import PersonInChargeList from "~/components/PersonInChargeList";
import { NS_USER_CERTIFICATE } from "~/enumerate/namespace";
// 企业安全生产管理人员管理
function List(props) {
return (
<div>
<PersonInChargeList
props={props}
certificatePhotoType={162}
personnelType="aqscglry"
permissionAdd="qyd-aqscglrygl-add"
permissionEdit="qyd-aqscglrygl-edit"
permissionView="qyd-aqscglrygl-info"
permissionDel="qyd-aqscglrygl-del"
dictionaryType="aqscglrygwmc0000"
/>
</div>
);
}
export default Connect([NS_USER_CERTIFICATE], true)(Permission(List));

View File

@ -0,0 +1,16 @@
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import PersonInChargeInfo from "~/components/PersonInChargeInfo";
import { NS_USER_CERTIFICATE } from "~/enumerate/namespace";
function View(props) {
return (
<div>
<PersonInChargeInfo
props={props}
certificatePhotoType={162}
/>
</div>
);
}
export default Connect([NS_USER_CERTIFICATE], true)(View);

View File

@ -0,0 +1,9 @@
function SecurityAdmini(props) {
return (
<div>
{props.children}
</div>
);
}
export default SecurityAdmini;

View File

@ -0,0 +1,23 @@
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import SpecialCertificateList from "~/components/SpecialCertificateList";
import { NS_USER_CERTIFICATE } from "~/enumerate/namespace";
import {Permission} from "@cqsjjb/jjb-common-decorator/permission";
// 企业特种设备操作人员管理
function List(props) {
return (
<div>
<SpecialCertificateList
props={props}
certificatePhotoType={160}
personnelType="tzsbczry"
permissionAdd="qyd-tzsbczrygl-add"
permissionEdit="qyd-tzsbczrygl-edit"
permissionView="qyd-tzsbczrygl-info"
permissionDel="qyd-tzsbczrygl-del"
dictionaryType="tzsbczryczxmzylb0000"
/>
</div>
);
}
export default Connect([NS_USER_CERTIFICATE], true)(Permission(List));

View File

@ -0,0 +1,16 @@
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import SpecialCertificateInfo from "~/components/SpecialCertificateInfo";
import { NS_USER_CERTIFICATE } from "~/enumerate/namespace";
function View(props) {
return (
<div>
<SpecialCertificateInfo
props={props}
certificatePhotoType={160}
/>
</div>
);
}
export default Connect([NS_USER_CERTIFICATE], true)(View);

View File

@ -0,0 +1,9 @@
function SpecialEquipment(props) {
return (
<div>
{props.children}
</div>
);
}
export default SpecialEquipment;

View File

@ -0,0 +1,23 @@
import { Permission } from "@cqsjjb/jjb-common-decorator/permission";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import SpecialCertificateList from "~/components/SpecialCertificateList";
import { NS_USER_CERTIFICATE } from "~/enumerate/namespace";
// 企业特种作业人员管理
function List(props) {
return (
<div>
<SpecialCertificateList
props={props}
certificatePhotoType={159}
personnelType="tezhongzuoye"
permissionAdd="qyd-tzzyrugl-add"
permissionEdit="qyd-tzzyrugl-edit"
permissionView="qyd-tzzyrugl-info"
permissionDel="qyd-tzzyrugl-del"
dictionaryType="tzzyryhylb0000"
/>
</div>
);
}
export default Connect([NS_USER_CERTIFICATE], true)(Permission(List));

View File

@ -0,0 +1,16 @@
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import SpecialCertificateInfo from "~/components/SpecialCertificateInfo";
import { NS_USER_CERTIFICATE } from "~/enumerate/namespace";
function View(props) {
return (
<div>
<SpecialCertificateInfo
props={props}
certificatePhotoType={159}
/>
</div>
);
}
export default Connect([NS_USER_CERTIFICATE], true)(View);

View File

@ -0,0 +1,9 @@
function SpecialPersonnel(props) {
return (
<div>
{props.children}
</div>
);
}
export default SpecialPersonnel;

View File

@ -0,0 +1,9 @@
function PersonnelLicense(props) {
return (
<div>
{props.children}
</div>
);
}
export default PersonnelLicense;

View File

@ -0,0 +1,9 @@
function EnterpriseLicenseManage(props) {
return (
<div>
{props.children}
</div>
);
}
export default EnterpriseLicenseManage;

View File

@ -0,0 +1,9 @@
function BranchCompany(props) {
return (
<div>
{props.children}
</div>
);
}
export default BranchCompany;

View File

@ -0,0 +1,31 @@
import { ImportCore } from "@cqsjjb/jjb-common-decorator/module";
import React from "react";
export default class Entry extends React.Component {
state = {
Component: undefined,
};
componentDidMount() {
if (process.env.app.appKey) {
ImportCore({
name: "$",
from: "https://cdn.cqjjb.cn/jcloud/use/plugin/b31c9840a57f11ef91cf7f3cabbb7484/latest",
}).then((res) => {
if (res.status) {
this.setState({ Component: res.module?.default });
}
});
}
}
render() {
const { Component } = this.state;
return (Component && process.env.app.appKey) && (
<Component
detail={{ componentKey: process.env.app.appKey }}
appKey={process.env.app.appKey}
/>
);
}
}

View File

@ -0,0 +1,434 @@
import React, {
useState,
useCallback,
useMemo,
useEffect,
} from "react";
import { Layout, Menu, Breadcrumb, Tabs, Button, Dropdown, Space, Typography } from "antd";
import {
ReloadOutlined,
CloseOutlined,
CloseCircleOutlined,
DownOutlined,
MenuFoldOutlined,
MenuUnfoldOutlined,
} from "@ant-design/icons";
import menuItems, {
findMenuPath,
getPageLabel,
getOpenKeys,
getSelectedKeys,
} from "./menuConfig";
const { Header, Sider, Content } = Layout;
const { Text } = Typography;
const TABS_KEY = "sim_layout_tabs";
const ACTIVE_KEY = "sim_layout_active";
/* ---- sessionStorage 持久化 ---- */
function loadState() {
try {
const tabs = JSON.parse(sessionStorage.getItem(TABS_KEY) || "[]");
const activeKey = sessionStorage.getItem(ACTIVE_KEY) || "";
return { tabs, activeKey };
} catch {
return { tabs: [], activeKey: "" };
}
}
function saveState(tabs, activeKey) {
sessionStorage.setItem(TABS_KEY, JSON.stringify(tabs));
sessionStorage.setItem(ACTIVE_KEY, activeKey || "");
}
export default function SimulatedLayout({ children }) {
const [collapsed, setCollapsed] = useState(false);
const [state, setState] = useState(loadState);
const currentPath = window.location.pathname;
//
useEffect(() => {
if (currentPath && !currentPath.endsWith("/container/")) {
addTab(currentPath);
}
}, []); //
/* ---- 标签操作 ---- */
const addTab = useCallback((path) => {
setState((prev) => {
if (prev.tabs.find((t) => t.key === path)) {
// activeKey
if (prev.activeKey !== path) {
saveState(prev.tabs, path);
return { ...prev, activeKey: path };
}
return prev;
}
const newTabs = [...prev.tabs, { key: path, label: getPageLabel(path) }];
saveState(newTabs, path);
return { tabs: newTabs, activeKey: path };
});
}, []);
const navigateTo = useCallback((path) => {
if (path !== currentPath) {
window.location.href = path;
}
}, [currentPath]);
const handleCloseTab = useCallback((targetKey) => {
setState((prev) => {
const idx = prev.tabs.findIndex((t) => t.key === targetKey);
const newTabs = prev.tabs.filter((t) => t.key !== targetKey);
if (newTabs.length === 0) {
saveState([], "");
return { tabs: [], activeKey: "" };
}
let newActive = prev.activeKey;
if (targetKey === prev.activeKey) {
newActive = newTabs[Math.max(0, idx - 1)]?.key || newTabs[0]?.key;
navigateTo(newActive);
}
saveState(newTabs, newActive);
return { tabs: newTabs, activeKey: newActive };
});
}, [navigateTo]);
const handleCloseOthers = useCallback((targetKey) => {
setState((prev) => {
const target = prev.tabs.find((t) => t.key === targetKey);
const newTabs = target ? [target] : [];
saveState(newTabs, targetKey);
if (targetKey !== currentPath) navigateTo(targetKey);
return { tabs: newTabs, activeKey: targetKey };
});
}, [currentPath, navigateTo]);
const handleCloseRight = useCallback((targetKey) => {
setState((prev) => {
const idx = prev.tabs.findIndex((t) => t.key === targetKey);
if (idx === -1) return prev;
const newTabs = prev.tabs.slice(0, idx + 1);
saveState(newTabs, targetKey);
return { tabs: newTabs, activeKey: targetKey };
});
}, []);
const handleCloseAll = useCallback(() => {
saveState([], "");
setState({ tabs: [], activeKey: "" });
}, []);
const handleRefresh = useCallback(() => {
window.location.reload();
}, []);
/* ---- 菜单事件 ---- */
const handleMenuClick = useCallback(
({ key }) => {
navigateTo(key);
},
[navigateTo],
);
const handleTabChange = useCallback(
(key) => {
navigateTo(key);
},
[navigateTo],
);
const handleTabEdit = useCallback(
(targetKey, action) => {
if (action === "remove") handleCloseTab(targetKey);
},
[handleCloseTab],
);
/* ---- 面包屑 ---- */
const breadcrumbItems = useMemo(() => {
const path = findMenuPath(currentPath);
return path.map((item, idx) => ({
title:
idx === path.length - 1 ? (
item.label
) : (
<a onClick={() => handleMenuClick({ key: item.key })}>{item.label}</a>
),
key: item.key,
}));
}, [currentPath, handleMenuClick]);
/* ---- 菜单选中/展开 ---- */
const selectedKeys = useMemo(() => getSelectedKeys(currentPath), [currentPath]);
const defaultOpenKeys = useMemo(() => getOpenKeys(currentPath), [currentPath]);
/* ---- 标签页右键菜单 ---- */
const buildContextMenu = useCallback(
(tabKey) => {
const { tabs } = state;
const isOnlyOne = tabs.length <= 1;
const idx = tabs.findIndex((t) => t.key === tabKey);
const hasRight = idx >= 0 && idx < tabs.length - 1;
return {
items: [
{
key: "refresh",
icon: <ReloadOutlined />,
label: "刷新当前标签",
},
{
key: "close",
icon: <CloseOutlined />,
label: "关闭当前标签",
disabled: isOnlyOne,
},
{
key: "close-others",
icon: <CloseCircleOutlined />,
label: "关闭其他标签",
disabled: isOnlyOne,
},
{
key: "close-right",
icon: <CloseOutlined />,
label: "关闭右侧标签",
disabled: !hasRight,
},
{ type: "divider" },
{
key: "close-all",
icon: <CloseCircleOutlined />,
label: "关闭所有标签",
disabled: tabs.length === 0,
},
],
onClick: ({ key }) => {
switch (key) {
case "refresh":
handleRefresh();
break;
case "close":
handleCloseTab(tabKey);
break;
case "close-others":
handleCloseOthers(tabKey);
break;
case "close-right":
handleCloseRight(tabKey);
break;
case "close-all":
handleCloseAll();
break;
}
},
};
},
[state, handleRefresh, handleCloseTab, handleCloseOthers, handleCloseRight, handleCloseAll],
);
/* ---- 标签 label 渲染(带右键菜单) ---- */
const renderTabLabel = useCallback(
(tab) => (
<Dropdown menu={buildContextMenu(tab.key)} trigger={["contextMenu"]}>
<span style={{ display: "inline-block", padding: "0 8px", userSelect: "none" }}>
{tab.label}
</span>
</Dropdown>
),
[buildContextMenu],
);
const { tabs, activeKey } = state;
const siderWidth = collapsed ? 64 : 220;
return (
<Layout style={{ minHeight: "100vh" }}>
{/* ---- 侧边栏 ---- */}
<Sider
collapsible
collapsed={collapsed}
onCollapse={setCollapsed}
theme="dark"
width={220}
style={{
overflow: "auto",
height: "100vh",
position: "fixed",
left: 0,
top: 0,
bottom: 0,
zIndex: 10,
}}
>
<div
style={{
height: 48,
display: "flex",
alignItems: "center",
justifyContent: "center",
color: "#fff",
fontWeight: 700,
fontSize: collapsed ? 15 : 17,
borderBottom: "1px solid rgba(255,255,255,0.1)",
letterSpacing: 1,
flexShrink: 0,
}}
>
{collapsed ? "证照" : "证照管理系统"}
</div>
<Menu
theme="dark"
mode="inline"
selectedKeys={selectedKeys}
defaultOpenKeys={defaultOpenKeys}
onClick={handleMenuClick}
items={menuItems}
style={{ borderRight: 0 }}
/>
</Sider>
{/* ---- 右侧主体 ---- */}
<Layout
style={{
marginLeft: siderWidth,
transition: "margin-left 0.2s",
}}
>
{/* ---- Header + 面包屑 + 操作按钮 ---- */}
<Header
style={{
background: "#fff",
padding: "0 16px",
display: "flex",
alignItems: "center",
justifyContent: "space-between",
borderBottom: "1px solid #f0f0f0",
height: 48,
}}
>
<Space>
<Button
type="text"
icon={collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
onClick={() => setCollapsed(!collapsed)}
/>
<Breadcrumb items={breadcrumbItems} />
</Space>
<Space>
<Button
type="text"
icon={<ReloadOutlined />}
onClick={handleRefresh}
>
刷新
</Button>
<Button
type="text"
icon={<CloseCircleOutlined />}
onClick={handleCloseAll}
disabled={tabs.length === 0}
>
关闭所有
</Button>
<Text type="secondary" style={{ fontSize: 12 }}>
模拟布局·Dev
</Text>
</Space>
</Header>
{/* ---- 标签栏 ---- */}
<div
style={{
background: "#fff",
borderBottom: "1px solid #f0f0f0",
padding: "4px 8px 0",
}}
>
{tabs.length > 0 ? (
<Tabs
type="editable-card"
hideAdd
activeKey={activeKey}
onChange={handleTabChange}
onEdit={handleTabEdit}
size="small"
style={{ marginBottom: 0 }}
items={tabs.map((tab) => ({
key: tab.key,
label: renderTabLabel(tab),
closable: true,
}))}
tabBarExtraContent={
<Dropdown
menu={{
items: [
{
key: "refresh",
icon: <ReloadOutlined />,
label: "刷新当前标签",
},
{
key: "close-others",
icon: <CloseOutlined />,
label: "关闭其他标签",
disabled: tabs.length <= 1,
},
{
key: "close-all",
icon: <CloseCircleOutlined />,
label: "关闭所有标签",
disabled: tabs.length === 0,
},
],
onClick: ({ key }) => {
switch (key) {
case "refresh":
handleRefresh();
break;
case "close-others":
handleCloseOthers(activeKey);
break;
case "close-all":
handleCloseAll();
break;
}
},
}}
placement="bottomRight"
>
<Button type="text" size="small" icon={<DownOutlined />} />
</Dropdown>
}
/>
) : (
<div
style={{
height: 36,
display: "flex",
alignItems: "center",
paddingLeft: 8,
color: "#bbb",
fontSize: 12,
}}
>
暂无打开的标签页请从左侧菜单选择页面
</div>
)}
</div>
{/* ---- 内容区 ---- */}
<Content
style={{
margin: 0,
minHeight: 280,
position: "relative",
}}
>
{children}
</Content>
</Layout>
</Layout>
);
}

View File

@ -0,0 +1,239 @@
import React from "react";
import {
DashboardOutlined,
FileProtectOutlined,
TeamOutlined,
BankOutlined,
UserSwitchOutlined,
IdcardOutlined,
SafetyCertificateOutlined,
ToolOutlined,
UserOutlined,
BarChartOutlined,
PieChartOutlined,
ExperimentOutlined,
} from "@ant-design/icons";
const menuItems = [
{
key: "/certificate/container/Supervision",
label: "监管端",
icon: <DashboardOutlined />,
children: [
{
key: "/certificate/container/Supervision/test2",
label: "Test2 测试",
icon: <ExperimentOutlined />,
},
{
key: "/certificate/container/Supervision/EnterpriseLicense",
label: "企业证照",
icon: <FileProtectOutlined />,
children: [
{
key: "/certificate/container/Supervision/EnterpriseLicense/EnterpriseLicense",
label: "企业证照管理",
icon: <IdcardOutlined />,
},
{
key: "/certificate/container/Supervision/EnterpriseLicense/BranchStatistics/List",
label: "分公司统计",
icon: <BarChartOutlined />,
},
{
key: "/certificate/container/Supervision/EnterpriseLicense/StakeholderStatistics/List",
label: "干系人统计",
icon: <PieChartOutlined />,
},
],
},
{
key: "/certificate/container/Supervision/PersonnelLicense",
label: "人员证照",
icon: <TeamOutlined />,
children: [
{
key: "/certificate/container/Supervision/PersonnelLicense/PersonInCharge/List",
label: "负责人",
icon: <UserOutlined />,
},
{
key: "/certificate/container/Supervision/PersonnelLicense/SecurityAdmini/List",
label: "安全管理员",
icon: <SafetyCertificateOutlined />,
},
{
key: "/certificate/container/Supervision/PersonnelLicense/SpecialDevice/List",
label: "特种设备",
icon: <ToolOutlined />,
},
{
key: "/certificate/container/Supervision/PersonnelLicense/SpecialPersonnel/List",
label: "特种作业人员",
icon: <IdcardOutlined />,
},
{
key: "/certificate/container/Supervision/PersonnelLicense/BranchCompanyStat/List",
label: "分公司人员统计",
icon: <BarChartOutlined />,
},
{
key: "/certificate/container/Supervision/PersonnelLicense/StakeholderStat/List",
label: "干系人人员统计",
icon: <PieChartOutlined />,
},
],
},
],
},
{
key: "/certificate/container/BranchCompany",
label: "分公司端",
icon: <BankOutlined />,
children: [
{
key: "/certificate/container/BranchCompany/EnterpriseLicense/EnterpriseLicense",
label: "企业证照管理",
icon: <IdcardOutlined />,
},
{
key: "/certificate/container/BranchCompany/EnterpriseLicense/PersonnelLicense",
label: "人员证照",
icon: <TeamOutlined />,
children: [
{
key: "/certificate/container/BranchCompany/EnterpriseLicense/PersonnelLicense/PersonInCharge/List",
label: "负责人",
icon: <UserOutlined />,
},
{
key: "/certificate/container/BranchCompany/EnterpriseLicense/PersonnelLicense/SecurityAdmini/List",
label: "安全管理员",
icon: <SafetyCertificateOutlined />,
},
{
key: "/certificate/container/BranchCompany/EnterpriseLicense/PersonnelLicense/SpecialDevice/List",
label: "特种设备",
icon: <ToolOutlined />,
},
{
key: "/certificate/container/BranchCompany/EnterpriseLicense/PersonnelLicense/SpecialPersonnel/List",
label: "特种作业人员",
icon: <IdcardOutlined />,
},
],
},
],
},
{
key: "/certificate/container/Stakeholder",
label: "干系人端",
icon: <UserSwitchOutlined />,
children: [
{
key: "/certificate/container/Stakeholder/EnterpriseLicense/EnterpriseLicense",
label: "企业证照管理",
icon: <IdcardOutlined />,
},
{
key: "/certificate/container/Stakeholder/EnterpriseLicense/PersonnelLicense",
label: "人员证照",
icon: <TeamOutlined />,
children: [
{
key: "/certificate/container/Stakeholder/EnterpriseLicense/PersonnelLicense/PersonInCharge/List",
label: "负责人",
icon: <UserOutlined />,
},
{
key: "/certificate/container/Stakeholder/EnterpriseLicense/PersonnelLicense/SecurityAdmini/List",
label: "安全管理员",
icon: <SafetyCertificateOutlined />,
},
{
key: "/certificate/container/Stakeholder/EnterpriseLicense/PersonnelLicense/SpecialDevice/List",
label: "特种设备",
icon: <ToolOutlined />,
},
{
key: "/certificate/container/Stakeholder/EnterpriseLicense/PersonnelLicense/SpecialPersonnel/List",
label: "特种作业人员",
icon: <IdcardOutlined />,
},
],
},
],
},
{
key: "/certificate/container/Test",
label: "测试页面",
icon: <ExperimentOutlined />,
},
];
export default menuItems;
/** 扁平化菜单 */
export function flattenMenu(items) {
const result = [];
function walk(list) {
for (const item of list) {
result.push(item);
if (item.children) walk(item.children);
}
}
walk(items);
return result;
}
/**
* 根据路径查找面包屑路径
* 返回从根到叶子节点的菜单项数组
*/
export function findMenuPath(path) {
function search(items, ancestors) {
for (const item of items) {
const current = [...ancestors, item];
if (item.key === path) {
return current;
}
if (item.children) {
const found = search(item.children, current);
if (found) return found;
}
}
return null;
}
return search(menuItems, []) || [];
}
/**
* 根据路径获取页面标签名
*/
export function getPageLabel(path) {
const flat = flattenMenu(menuItems);
const item = flat.find((m) => m.key === path);
return item?.label || path.split("/").filter(Boolean).pop() || "未命名页面";
}
/**
* 根据路径获取默认展开的菜单项
*/
export function getOpenKeys(path) {
const breadcrumb = findMenuPath(path);
return breadcrumb.slice(0, -1).map((i) => i.key);
}
/**
* 根据路径获取选中的菜单项
*/
export function getSelectedKeys(path) {
const allPaths = flattenMenu(menuItems).map((m) => m.key);
let match = "";
for (const p of allPaths) {
if (path.startsWith(p) && p.length > match.length) {
match = p;
}
}
return match ? [match] : [];
}

View File

@ -0,0 +1,17 @@
import EnterpriseLicenseList from "~/pages/Container/Supervision/EnterpriseLicense/EnterpriseLicense";
function EnterpriseLicense(props) {
return (
<div>
<EnterpriseLicenseList
props={props}
permissionAdd="xgfd-qyzzgl-add"
permissionEdit="xgfd-qyzzgl-edit"
permissionView="xgfd-qyzzgl-info"
permissionDel="xgfd-qyzzgl-del"
/>
</div>
);
}
export default EnterpriseLicense;

View File

@ -0,0 +1,23 @@
import { Permission } from "@cqsjjb/jjb-common-decorator/permission";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import PersonInChargeList from "~/components/PersonInChargeList";
import { NS_USER_CERTIFICATE } from "~/enumerate/namespace";
// 企业主要负责人管理
function List(props) {
return (
<div>
<PersonInChargeList
props={props}
certificatePhotoType={161}
personnelType="zyfzr"
permissionAdd="xgfd-zyfzrgl-add"
permissionEdit="xgfd-zyfzrgl-edit"
permissionView="xgfd-zyfzrgl-info"
permissionDel="xgfd-zyfzrgl-del"
dictionaryType="zyfzrgwmc0000"
/>
</div>
);
}
export default Connect([NS_USER_CERTIFICATE], true)(Permission(List));

View File

@ -0,0 +1,16 @@
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import PersonInChargeInfo from "~/components/PersonInChargeInfo";
import { NS_USER_CERTIFICATE } from "~/enumerate/namespace";
function View(props) {
return (
<div>
<PersonInChargeInfo
props={props}
certificatePhotoType={161}
/>
</div>
);
}
export default Connect([NS_USER_CERTIFICATE], true)(View);

View File

@ -0,0 +1,9 @@
function PersonInCharge(props) {
return (
<div>
{props.children}
</div>
);
}
export default PersonInCharge;

View File

@ -0,0 +1,23 @@
import { Permission } from "@cqsjjb/jjb-common-decorator/permission";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import PersonInChargeList from "~/components/PersonInChargeList";
import { NS_USER_CERTIFICATE } from "~/enumerate/namespace";
// 企业安全生产管理人员管理
function List(props) {
return (
<div>
<PersonInChargeList
props={props}
certificatePhotoType={162}
personnelType="aqscglry"
permissionAdd="xgfd-aqscglrygl-add"
permissionEdit="xgfd-aqscglrygl-edit"
permissionView="xgfd-aqscglrygl-info"
permissionDel="xgfd-aqscglrygl-del"
dictionaryType="aqscglrygwmc0000"
/>
</div>
);
}
export default Connect([NS_USER_CERTIFICATE], true)(Permission(List));

View File

@ -0,0 +1,16 @@
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import PersonInChargeInfo from "~/components/PersonInChargeInfo";
import { NS_USER_CERTIFICATE } from "~/enumerate/namespace";
function View(props) {
return (
<div>
<PersonInChargeInfo
props={props}
certificatePhotoType={162}
/>
</div>
);
}
export default Connect([NS_USER_CERTIFICATE], true)(View);

View File

@ -0,0 +1,9 @@
function SecurityAdmini(props) {
return (
<div>
{props.children}
</div>
);
}
export default SecurityAdmini;

View File

@ -0,0 +1,23 @@
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import SpecialCertificateList from "~/components/SpecialCertificateList";
import { NS_USER_CERTIFICATE } from "~/enumerate/namespace";
import {Permission} from "@cqsjjb/jjb-common-decorator/permission";
// 企业特种设备操作人员管理
function List(props) {
return (
<div>
<SpecialCertificateList
props={props}
certificatePhotoType={160}
personnelType="tzsbczry"
permissionAdd="xgfd-tzzzsbczrygl-add"
permissionEdit="xgfd-tzzzsbczrygl-edit"
permissionView="xgfd-tzzzsbczrygl-info"
permissionDel="xgfd-tzzzsbczrygl-del"
dictionaryType="tzsbczryczxmzylb0000"
/>
</div>
);
}
export default Connect([NS_USER_CERTIFICATE], true)(Permission(List));

View File

@ -0,0 +1,16 @@
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import SpecialCertificateInfo from "~/components/SpecialCertificateInfo";
import { NS_USER_CERTIFICATE } from "~/enumerate/namespace";
function View(props) {
return (
<div>
<SpecialCertificateInfo
props={props}
certificatePhotoType={160}
/>
</div>
);
}
export default Connect([NS_USER_CERTIFICATE], true)(View);

View File

@ -0,0 +1,9 @@
function SpecialEquipment(props) {
return (
<div>
{props.children}
</div>
);
}
export default SpecialEquipment;

View File

@ -0,0 +1,23 @@
import { Permission } from "@cqsjjb/jjb-common-decorator/permission";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import SpecialCertificateList from "~/components/SpecialCertificateList";
import { NS_USER_CERTIFICATE } from "~/enumerate/namespace";
// 企业特种作业人员管理
function List(props) {
return (
<div>
<SpecialCertificateList
props={props}
certificatePhotoType={159}
personnelType="tezhongzuoye"
permissionAdd="xgfd-tzzyrugl-add"
permissionEdit="xgfd-tzzyrugl-edit"
permissionView="xgfd-tzzyrugl-info"
permissionDel="xgfd-tzzyrugl-del"
dictionaryType="tzzyryhylb0000"
/>
</div>
);
}
export default Connect([NS_USER_CERTIFICATE], true)(Permission(List));

View File

@ -0,0 +1,16 @@
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import SpecialCertificateInfo from "~/components/SpecialCertificateInfo";
import { NS_USER_CERTIFICATE } from "~/enumerate/namespace";
function View(props) {
return (
<div>
<SpecialCertificateInfo
props={props}
certificatePhotoType={159}
/>
</div>
);
}
export default Connect([NS_USER_CERTIFICATE], true)(View);

View File

@ -0,0 +1,9 @@
function SpecialPersonnel(props) {
return (
<div>
{props.children}
</div>
);
}
export default SpecialPersonnel;

View File

@ -0,0 +1,9 @@
function PersonnelLicense(props) {
return (
<div>
{props.children}
</div>
);
}
export default PersonnelLicense;

View File

@ -0,0 +1,9 @@
function EnterpriseLicenseManage(props) {
return (
<div>
{props.children}
</div>
);
}
export default EnterpriseLicenseManage;

View File

@ -0,0 +1,9 @@
function Stakeholder(props) {
return (
<div>
{props.children}
</div>
);
}
export default Stakeholder;

View File

@ -0,0 +1,70 @@
import { Permission } from "@cqsjjb/jjb-common-decorator/permission";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { Button, Form, Space } from "antd";
import Page from "zy-react-library/components/Page";
import Search from "zy-react-library/components/Search";
import Table from "zy-react-library/components/Table";
import useTable from "zy-react-library/hooks/useTable";
import { NS_CORP_CERTIFICATE } from "~/enumerate/namespace";
function List(props) {
const [form] = Form.useForm();
const { tableProps, getData } = useTable(props["corpCertificateStatPage"], {
form,
transform: (formData) => {
return {
...formData,
corpType: 0,
};
},
});
return (
<Page isShowAllAction={false}>
<Search
form={form}
options={[
{
name: "corpName",
label: "公司名称",
},
]}
onFinish={getData}
/>
<Table
columns={[
{
title: "公司名称",
dataIndex: "corpName",
},
{
title: "证书数量",
dataIndex: "certCount",
},
{
title: "操作",
width: 200,
hidden: !props.permission("gfd-zgsztj-info"),
render: (_, record) => (
<Space>
<Button
type="link"
onClick={() => props.history.push(`./View?corpinfoId=${record.corpId}`)}
>
查看
</Button>
</Space>
),
},
]}
{...tableProps}
/>
</Page>
);
}
export default Connect([NS_CORP_CERTIFICATE], true)(Permission(List));

View File

@ -0,0 +1,15 @@
import EnterpriseLicense from "../../EnterpriseLicense";
function View(props) {
return (
<div>
<EnterpriseLicense
props={props}
type="View"
permissionView="gfd-zgsztj-insideInfo"
/>
</div>
);
}
export default View;

View File

@ -0,0 +1,9 @@
function BranchStatistics(props) {
return (
<div>
{props.children}
</div>
);
}
export default BranchStatistics;

View File

@ -0,0 +1,600 @@
import { Permission } from "@cqsjjb/jjb-common-decorator/permission";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { Button, Descriptions, Form, message, Modal, Space } from "antd";
import { useEffect, useRef, useState } from "react";
import FormBuilder from "zy-react-library/components/FormBuilder";
import AddIcon from "zy-react-library/components/Icon/AddIcon";
import Page from "zy-react-library/components/Page";
import PreviewImg from "zy-react-library/components/PreviewImg";
import Search from "zy-react-library/components/Search";
import Table from "zy-react-library/components/Table";
import TooltipPreviewImg from "zy-react-library/components/TooltipPreviewImg";
import Upload from "zy-react-library/components/Upload";
import { FORM_ITEM_RENDER_ENUM } from "zy-react-library/enum/formItemRender";
import { UPLOAD_FILE_TYPE_ENUM } from "zy-react-library/enum/uploadFile/gwj";
import useDeleteFile from "zy-react-library/hooks/useDeleteFile";
import useGetFile from "zy-react-library/hooks/useGetFile";
import useGetUrlQuery from "zy-react-library/hooks/useGetUrlQuery";
import useTable from "zy-react-library/hooks/useTable";
import useUploadFile from "zy-react-library/hooks/useUploadFile";
import { NS_CORP_CERTIFICATE } from "~/enumerate/namespace";
import { useDebounce } from "~/utils";
function EnterpriseLicense(props) {
const [addModalOpen, setAddModalOpen] = useState(false);
const [viewModalOpen, setViewModalOpen] = useState(false);
const [currentId, setCurrentId] = useState("");
const queryParams = useGetUrlQuery();
const permissionAdd = props.permissionAdd ? props.permissionAdd : "gfd-qyzzgl-add";
const permissionEdit = props.permissionEdit ? props.permissionEdit : "gfd-qyzzgl-edit";
const permissionView = props.permissionView ? props.permissionView : "gfd-qyzzgl-info";
const permissionDel = props.permissionDel ? props.permissionDel : "gfd-qyzzgl-del";
const { loading: getFileLoading, getFile } = useGetFile();
const [form] = Form.useForm();
const { tableProps, getData } = useTable(props["corpCertificateList"], {
form,
transform: (formData) => {
return {
...formData,
geCertificateDateStart: formData.certificateDate?.[0],
leCertificateDateEnd: formData.certificateDate?.[1],
eqCorpinfoId: queryParams["corpinfoId"],
};
},
});
const onDelete = (id) => {
Modal.confirm({
title: "提示",
content: "确定删除吗?",
onOk: () => {
props["corpCertificateRemove"]({
id,
}).then((res) => {
if (res.success) {
message.success("删除成功");
getData();
}
});
getData();
},
});
};
const [fileCache, setFileCache] = useState({});
const [loadingKeys, setLoadingKeys] = useState(new Set());
const pendingLoadIdsRef = useRef(new Set());
const requestQueueRef = useRef([]);
const activeRequestsRef = useRef(0);
const MAX_CONCURRENT_REQUESTS = 3; // 最大并发请求数
const processQueue = () => {
while (
requestQueueRef.current.length > 0
&& activeRequestsRef.current < MAX_CONCURRENT_REQUESTS
) {
const { id, resolve, reject } = requestQueueRef.current.shift();
activeRequestsRef.current++;
getFile({
eqType: UPLOAD_FILE_TYPE_ENUM["6"],
eqForeignKey: id,
})
.then((res) => {
setFileCache(prev => ({
...prev,
[id]: res || [],
}));
resolve(res);
})
.catch((err) => {
reject(err);
})
.finally(() => {
activeRequestsRef.current--;
setLoadingKeys((prev) => {
const newSet = new Set(prev);
newSet.delete(id);
return newSet;
});
processQueue();
});
}
};
const loadFileForRecord = (userQualificationinfoId) => {
if (!userQualificationinfoId)
return Promise.resolve();
if (fileCache[userQualificationinfoId])
return Promise.resolve();
if (loadingKeys.has(userQualificationinfoId))
return Promise.resolve();
if (pendingLoadIdsRef.current.has(userQualificationinfoId))
return Promise.resolve();
pendingLoadIdsRef.current.add(userQualificationinfoId);
setLoadingKeys(prev => new Set([...prev, userQualificationinfoId]));
return new Promise((resolve, reject) => {
requestQueueRef.current.push({ id: userQualificationinfoId, resolve, reject });
processQueue();
}).finally(() => {
pendingLoadIdsRef.current.delete(userQualificationinfoId);
});
};
// 缓存数据变化时清空图片缓存
useEffect(() => {
if (tableProps.dataSource) {
const currentIds = new Set(tableProps.dataSource.map(item => item.corpCertificateId).filter(Boolean));
setFileCache((prev) => {
const newCache = {};
Object.keys(prev).forEach((id) => {
if (currentIds.has(id)) {
newCache[id] = prev[id];
}
});
return newCache;
});
}
}, [tableProps.dataSource]);
// 记录已经触发过加载的 ID避免 render 重复触发
const triggeredLoadIdsRef = useRef(new Set());
useEffect(() => {
if (tableProps.dataSource) {
tableProps.dataSource.forEach((record) => {
const id = record.corpCertificateId;
if (id && !triggeredLoadIdsRef.current.has(id)) {
triggeredLoadIdsRef.current.add(id);
loadFileForRecord(id);
}
});
}
// 组件卸载或数据源变化时重置
return () => {
triggeredLoadIdsRef.current.clear();
};
}, [tableProps.dataSource]);
return (
<Page isShowAllAction={false}>
<Search
form={form}
options={[
{
name: "likeCertificateName",
label: "证书名称",
},
{
name: "certificateDate",
label: "证书有效期",
render: FORM_ITEM_RENDER_ENUM.DATE_RANGE,
},
]}
onFinish={getData}
/>
<Table
loding={getFileLoading}
toolBarRender={() => (
<>
{
(!props.type && props.permission(permissionAdd))
&& (
<Button
type="primary"
icon={<AddIcon />}
onClick={() => {
setAddModalOpen(true);
}}
>
新增
</Button>
)
}
</>
)}
columns={[
{
title: "证书名称",
dataIndex: "certificateName",
},
{
title: "证书编号",
dataIndex: "certificateCode",
},
{
title: "证书有效期",
dataIndex: "certificateNo",
width: 370,
render: (_, record) =>
<div>{`${record.certificateDateStart ?? ""} - ${record.certificateDateEnd ?? ""}`}</div>,
},
{
title: "图片",
render: (_, record) => {
const id = record.corpCertificateId;
const files = fileCache[id] || [];
if (!files.length)
return <span></span>;
return <TooltipPreviewImg files={files} />;
},
},
{
title: "操作",
width: 200,
render: (_, record) => (
<Space>
{
props.permission(permissionView)
&& (
<Button
type="link"
onClick={() => {
setViewModalOpen(true);
setCurrentId(record.id);
}}
>
查看
</Button>
)
}
{
(!props.type && props.permission(permissionEdit))
&& (
<Button
type="link"
onClick={() => {
setAddModalOpen(true);
setCurrentId(record.id);
}}
>
编辑
</Button>
)
}
{
(!props.type && props.permission(permissionDel))
&& (
<Button
danger
type="link"
onClick={() => onDelete(record.id)}
>
删除
</Button>
)
}
</Space>
),
},
]}
{...tableProps}
/>
{addModalOpen && (
<AddModal
open={addModalOpen}
loding={props.corpCertificate.corpCertificateLoading}
getData={getData}
currentId={currentId}
requestAdd={props["corpCertificateAdd"]}
requestEdit={props["corpCertificateEdit"]}
requestDetails={props["corpCertificateInfo"]}
corpCertificateIsExistCertNo={props["corpCertificateIsExistCertNo"]}
onCancel={() => {
setAddModalOpen(false);
setCurrentId("");
}}
onSuccess={(userQualificationinfoId) => {
// 清除该记录的图片缓存,强制下次 render 时重新加载
setFileCache((prev) => {
const newCache = { ...prev };
delete newCache[userQualificationinfoId];
return newCache;
});
}}
/>
)}
{viewModalOpen && (
<ViewModal
open={viewModalOpen}
loding={props.corpCertificate.corpCertificateLoading}
getData={getData}
currentId={currentId}
requestDetails={props["corpCertificateInfo"]}
onCancel={() => {
setViewModalOpen(false);
setCurrentId("");
}}
/>
)}
</Page>
);
}
function AddModalComponent(props) {
const [form] = Form.useForm();
const [userQualificationinfoId, setUserQualificationinfoId] = useState("");
const { loading: deleteFileLoading, deleteFile } = useDeleteFile();
const { loading: uploadFileLoading, uploadFile } = useUploadFile();
const { loading: getFileLoading, getFile } = useGetFile();
const [deleteCardImageFiles, setDeleteCardImageFiles] = useState([]);
const [CertificateCodeValue, setPertificateCodeValue] = useState(null);
const [isSubmit, setIsSubmit] = useState(true);
const debouncedCertificateCodeValue = useDebounce(CertificateCodeValue, 100);
useEffect(() => {
if (props.currentId) {
const fetchData = async () => {
const { data } = await props.requestDetails({
id: props.currentId,
});
data.certificateDate = [data.certificateDateStart, data.certificateDateEnd];
const certificateImgs = await getFile({
eqType: UPLOAD_FILE_TYPE_ENUM["6"],
eqForeignKey: data.corpCertificateId,
});
data.certificateImgs = certificateImgs;
form.setFieldsValue(data);
setUserQualificationinfoId(data.corpCertificateId);
};
fetchData();
}
}, [props.currentId]);
const onCancel = () => {
form.resetFields();
props.onCancel();
};
const onSubmit = async (values) => {
await deleteFile({
single: false,
files: deleteCardImageFiles,
});
values.certificateDateStart = values.certificateDate[0];
values.certificateDateEnd = values.certificateDate[1];
if (!isSubmit) {
form.setFields([
{
name: "certificateCode",
errors: ["证书编号重复"],
},
]);
return;
}
const { id } = await uploadFile({
single: false,
files: values.certificateImgs,
params: {
type: UPLOAD_FILE_TYPE_ENUM["6"],
foreignKey: userQualificationinfoId,
},
});
if (props.currentId) {
values.id = props.currentId;
values.corpCertificateId = userQualificationinfoId;
await props.requestEdit(values).then((res) => {
if (res.success) {
onCancel();
props.getData();
message.success("编辑成功");
}
});
}
else {
values.corpCertificateId = id;
await props.requestAdd(values).then((res) => {
if (res.success) {
onCancel();
props.getData();
message.success("新增成功");
}
});
}
};
// 校验重复
useEffect(() => {
if (!debouncedCertificateCodeValue) {
form.setFields([
{
name: "certificateCode",
errors: [],
},
]);
return;
}
props["corpCertificateIsExistCertNo"]({
certNo: debouncedCertificateCodeValue,
id: props.currentId ?? "",
}).then((res) => {
if (res.data) {
form.setFields([
{
name: "certificateCode",
errors: ["证书编号重复"],
},
]);
setIsSubmit(false);
}
else {
setIsSubmit(true);
}
});
}, [debouncedCertificateCodeValue]);
const onValuesChange = (changedValues) => {
if ("certificateCode" in changedValues) {
setPertificateCodeValue(changedValues.certificateCode ?? "");
}
};
return (
<Modal
open={props.open}
maskClosable={false}
title={props.currentId ? "编辑" : "新增"}
width={800}
confirmLoading={
deleteFileLoading || uploadFileLoading || getFileLoading || props.loding
}
onOk={form.submit}
onCancel={onCancel}
>
<FormBuilder
form={form}
span={24}
onValuesChange={onValuesChange}
values={{
securityFlag: 0,
}}
options={[
{
name: "certificateName",
label: "证书名称",
},
{
name: "certificateDate",
label: "证书有效期",
render: FORM_ITEM_RENDER_ENUM.DATE_RANGE,
rules: [
{
validator: (_, value) => {
const allEmptyStrings = Array.isArray(value) && value.every(item => item === "");
if (allEmptyStrings) {
return Promise.reject(new Error("请选择有效期"));
}
return Promise.resolve();
},
},
],
},
{
name: "certificateCode",
label: "证书编号",
},
{
name: "remark",
label: "备注",
required: false,
render: FORM_ITEM_RENDER_ENUM.TEXTAREA,
},
{
name: "certificateImgs",
label: "证书图片",
render: (
<Upload
maxCount={3}
onGetRemoveFile={(file) => {
setDeleteCardImageFiles([...deleteCardImageFiles, file]);
}}
/>
),
},
]}
labelCol={{
span: 10,
}}
showActionButtons={false}
onFinish={onSubmit}
/>
</Modal>
);
}
const AddModal = AddModalComponent;
function ViewModalComponent(props) {
const [form] = Form.useForm();
const [info, setInfo] = useState({});
const { loading: getFileLoading, getFile } = useGetFile();
useEffect(() => {
if (props.currentId) {
const fetchData = async () => {
const { data } = await props.requestDetails({
id: props.currentId,
});
const certificateImgs = await getFile({
eqType: UPLOAD_FILE_TYPE_ENUM["6"],
eqForeignKey: data.corpCertificateId,
});
data.certificateImgs = certificateImgs;
setInfo(data);
};
fetchData();
}
}, [props.currentId]);
const onCancel = () => {
form.resetFields();
props.onCancel();
};
return (
<Modal
open={props.open}
maskClosable={false}
title="查看"
width={800}
cancelText="关闭"
okButtonProps={{
style: {
display: "none",
},
}}
confirmLoading={
getFileLoading || props.loding
}
onCancel={onCancel}
>
<Descriptions
size="middle"
bordered
labelStyle={{ width: 200 }}
items={[
{
label: "证书名称",
children: info.certificateName,
span: 4,
},
{
label: "证书有效期",
children: <div>{`${info.certificateDateStart ?? ""} - ${info.certificateDateEnd ?? ""}`}</div>,
span: 4,
},
{
label: "证书编号",
children: info.certificateCode,
span: 4,
},
{
label: "备注",
children: info.remark,
span: 4,
},
{
label: "证书图片",
children: <PreviewImg files={info.certificateImgs} />,
span: 4,
},
]}
/>
</Modal>
);
}
const ViewModal = ViewModalComponent;
export default Connect([NS_CORP_CERTIFICATE], true)(Permission(EnterpriseLicense));

View File

@ -0,0 +1,72 @@
import { Permission } from "@cqsjjb/jjb-common-decorator/permission";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { Button, Form, Space } from "antd";
import Page from "zy-react-library/components/Page";
import Search from "zy-react-library/components/Search";
import Table from "zy-react-library/components/Table";
import useTable from "zy-react-library/hooks/useTable";
import { NS_CORP_CERTIFICATE } from "~/enumerate/namespace";
function List(props) {
const [form] = Form.useForm();
const { tableProps, getData } = useTable(props["corpCertificateStatPage"], {
form,
transform: (formData) => {
return {
...formData,
corpType: 1,
};
},
});
return (
<Page isShowAllAction={false}>
<Search
form={form}
options={[
{
name: "corpName",
label: "公司名称",
},
]}
onFinish={getData}
/>
<Table
columns={[
{
title: "公司名称",
dataIndex: "corpName",
},
{
title: "证书数量",
dataIndex: "certCount",
},
{
title: "操作",
width: 200,
hidden: !props.permission("gfd-xgfztj-info"),
render: (_, record) => (
<Space>
<Button
type="link"
onClick={() => props.history.push(`./View?corpinfoId=${record.corpId}`)}
>
查看
</Button>
</Space>
),
},
]}
{...tableProps}
/>
</Page>
);
}
export default Connect([NS_CORP_CERTIFICATE], true)(Permission(List));

View File

@ -0,0 +1,15 @@
import EnterpriseLicense from "../../EnterpriseLicense";
function View(props) {
return (
<div>
<EnterpriseLicense
props={props}
type="View"
permissionView="gfd-xgfztj-insideInfo"
/>
</div>
);
}
export default View;

View File

@ -0,0 +1,9 @@
function StakeholderStatistics(props) {
return (
<div>
{props.children}
</div>
);
}
export default StakeholderStatistics;

View File

@ -0,0 +1,9 @@
function EnterpriseLicenseManage(props) {
return (
<div>
{props.children}
</div>
);
}
export default EnterpriseLicenseManage;

View File

@ -0,0 +1,117 @@
import { Permission } from "@cqsjjb/jjb-common-decorator/permission";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { Button, Form, Space } from "antd";
import Page from "zy-react-library/components/Page";
import Search from "zy-react-library/components/Search";
import Table from "zy-react-library/components/Table";
import useTable from "zy-react-library/hooks/useTable";
import { NS_USER_CERTIFICATE } from "~/enumerate/namespace";
function List(props) {
const [form] = Form.useForm();
const { tableProps, getData } = useTable(props["userCertificateStatPage"], {
form,
transform: (formData) => {
return {
...formData,
corpType: 0,
};
},
});
return (
<Page isShowAllAction={false}>
<Search
form={form}
options={[
{
name: "corpName",
label: "公司名称",
},
]}
onFinish={getData}
/>
<Table
columns={[
{
title: "公司名称",
dataIndex: "corpName",
},
{
title: "特种作业人员证书数",
dataIndex: "specialWorkCertCount",
render: (_, record) => (
<Space>
<Button
type="link"
onClick={() => props.history.push(`./SpecialPersonnel/List?corpinfoId=${record.corpinfoId}`)}
disabled={!props.permission("gfd-fzgsryzztj-tzzyInfo")}
>
{record.specialWorkCertCount}
</Button>
</Space>
),
},
{
title: "特种设备操作人员证书数",
dataIndex: "specialEquipmentCertCount",
render: (_, record) => (
<Space>
<Button
type="link"
onClick={() => props.history.push(`./SpecialDevice/List?corpinfoId=${record.corpinfoId}`)}
disabled={!props.permission("gfd-fzgsryzztj-tzsbInfo")}
>
{record.specialEquipmentCertCount}
</Button>
</Space>
),
},
{
title: "主要负责人证书数",
dataIndex: "principalCertCount",
render: (_, record) => (
<Space>
<Button
type="link"
onClick={() => props.history.push(`./PersonInCharge/List?corpinfoId=${record.corpinfoId}`)}
disabled={!props.permission("gfd-fzgsryzztj-zyfzrInfo")}
>
{record.principalCertCount}
</Button>
</Space>
),
},
{
title: "安全生产管理人员证书数",
dataIndex: "safetyManagerCertCount",
render: (_, record) => (
<Space>
<Button
type="link"
onClick={() => props.history.push(`./SecurityAdmini/List?corpinfoId=${record.corpinfoId}`)}
disabled={!props.permission("gfd-fzgsryzztj-aqgly-info")}
>
{record.safetyManagerCertCount}
</Button>
</Space>
),
},
]}
{...tableProps}
/>
</Page>
);
}
export default Connect([NS_USER_CERTIFICATE], true)(Permission(List));

View File

@ -0,0 +1,24 @@
import { Permission } from "@cqsjjb/jjb-common-decorator/permission";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import Page from "zy-react-library/components/Page";
import PersonInChargeList from "~/components/PersonInChargeList";
import { NS_USER_CERTIFICATE } from "~/enumerate/namespace";
// 企业主要负责人管理
function List(props) {
return (
<Page headerTitle="主要负责人证书数" isShowFooter={false}>
<div>
<PersonInChargeList
props={props}
certificatePhotoType={161}
displayType="View"
personnelType="zyfzr"
permissionView="gfd-fzgsryzztj-zyfzrInfoNb"
dictionaryType="zyfzrgwmc0000"
/>
</div>
</Page>
);
}
export default Connect([NS_USER_CERTIFICATE], true)(Permission(List));

View File

@ -0,0 +1,16 @@
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import PersonInChargeInfo from "~/components/PersonInChargeInfo";
import { NS_USER_CERTIFICATE } from "~/enumerate/namespace";
function View(props) {
return (
<div>
<PersonInChargeInfo
props={props}
certificatePhotoType={161}
/>
</div>
);
}
export default Connect([NS_USER_CERTIFICATE], true)(View);

View File

@ -0,0 +1,9 @@
function PersonInCharge(props) {
return (
<div>
{props.children}
</div>
);
}
export default PersonInCharge;

View File

@ -0,0 +1,22 @@
import { Permission } from "@cqsjjb/jjb-common-decorator/permission";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import Page from "zy-react-library/components/Page";
import PersonInChargeList from "~/components/PersonInChargeList";
import { NS_USER_CERTIFICATE } from "~/enumerate/namespace";
// 企业安全生产管理人员管理
function List(props) {
return (
<Page headerTitle="安全生产管理人员证书数" isShowFooter={false}>
<PersonInChargeList
props={props}
certificatePhotoType={162}
displayType="View"
personnelType="aqscglry"
permissionView="gfd-fzgsryzztj-aqgly-infoNb"
dictionaryType="aqscglrygwmc0000"
/>
</Page>
);
}
export default Connect([NS_USER_CERTIFICATE], true)(Permission(List));

View File

@ -0,0 +1,16 @@
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import PersonInChargeInfo from "~/components/PersonInChargeInfo";
import { NS_USER_CERTIFICATE } from "~/enumerate/namespace";
function View(props) {
return (
<div>
<PersonInChargeInfo
props={props}
certificatePhotoType={162}
/>
</div>
);
}
export default Connect([NS_USER_CERTIFICATE], true)(View);

View File

@ -0,0 +1,9 @@
function SecurityAdmini(props) {
return (
<div>
{props.children}
</div>
);
}
export default SecurityAdmini;

View File

@ -0,0 +1,22 @@
import { Permission } from "@cqsjjb/jjb-common-decorator/permission";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import Page from "zy-react-library/components/Page";
import SpecialCertificateList from "~/components/SpecialCertificateList";
import { NS_USER_CERTIFICATE } from "~/enumerate/namespace";
// 企业特种设备操作人员管理
function List(props) {
return (
<Page headerTitle="特种设备操作人员证书数" isShowFooter={false}>
<SpecialCertificateList
props={props}
certificatePhotoType={160}
displayType="View"
personnelType="tzsbczry"
permissionView="gfd-fzgsryzztj-tzsbInfoNb"
dictionaryType="tzsbczryczxmzylb0000"
/>
</Page>
);
}
export default Connect([NS_USER_CERTIFICATE], true)(Permission(List));

View File

@ -0,0 +1,16 @@
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import SpecialCertificateInfo from "~/components/SpecialCertificateInfo";
import { NS_USER_CERTIFICATE } from "~/enumerate/namespace";
function View(props) {
return (
<div>
<SpecialCertificateInfo
props={props}
certificatePhotoType={160}
/>
</div>
);
}
export default Connect([NS_USER_CERTIFICATE], true)(View);

View File

@ -0,0 +1,9 @@
function SpecialEquipment(props) {
return (
<div>
{props.children}
</div>
);
}
export default SpecialEquipment;

View File

@ -0,0 +1,22 @@
import { Permission } from "@cqsjjb/jjb-common-decorator/permission";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import Page from "zy-react-library/components/Page";
import SpecialCertificateList from "~/components/SpecialCertificateList";
import { NS_USER_CERTIFICATE } from "~/enumerate/namespace";
// 企业特种作业人员管理
function List(props) {
return (
<Page headerTitle="特种作业人员证书数" isShowFooter={false}>
<SpecialCertificateList
props={props}
certificatePhotoType={159}
displayType="View"
personnelType="tezhongzuoye"
permissionView="gfd-fzgsryzztj-tzzyInfoNb"
dictionaryType="tzzyryhylb0000"
/>
</Page>
);
}
export default Connect([NS_USER_CERTIFICATE], true)(Permission(List));

View File

@ -0,0 +1,16 @@
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import SpecialCertificateInfo from "~/components/SpecialCertificateInfo";
import { NS_USER_CERTIFICATE } from "~/enumerate/namespace";
function View(props) {
return (
<div>
<SpecialCertificateInfo
props={props}
certificatePhotoType={159}
/>
</div>
);
}
export default Connect([NS_USER_CERTIFICATE], true)(View);

View File

@ -0,0 +1,9 @@
function SpecialPersonnel(props) {
return (
<div>
{props.children}
</div>
);
}
export default SpecialPersonnel;

View File

@ -0,0 +1,9 @@
function BranchCompanyStat(props) {
return (
<div>
{props.children}
</div>
);
}
export default BranchCompanyStat;

View File

@ -0,0 +1,23 @@
import { Permission } from "@cqsjjb/jjb-common-decorator/permission";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import PersonInChargeList from "~/components/PersonInChargeList";
import { NS_USER_CERTIFICATE } from "~/enumerate/namespace";
// 企业主要负责人管理
function List(props) {
return (
<div>
<PersonInChargeList
props={props}
certificatePhotoType={161}
personnelType="zyfzr"
permissionAdd="gfd-qyzyfzrgl-add"
permissionEdit="gfd-qyzyfzrgl-edit"
permissionView="gfd-qyzyfzrgl-info"
permissionDel="gfd-qyzyfzrgl-del"
dictionaryType="zyfzrgwmc0000"
/>
</div>
);
}
export default Connect([NS_USER_CERTIFICATE], true)(Permission(List));

View File

@ -0,0 +1,16 @@
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import PersonInChargeInfo from "~/components/PersonInChargeInfo";
import { NS_USER_CERTIFICATE } from "~/enumerate/namespace";
function View(props) {
return (
<div>
<PersonInChargeInfo
props={props}
certificatePhotoType={161}
/>
</div>
);
}
export default Connect([NS_USER_CERTIFICATE], true)(View);

View File

@ -0,0 +1,9 @@
function PersonInCharge(props) {
return (
<div>
{props.children}
</div>
);
}
export default PersonInCharge;

View File

@ -0,0 +1,23 @@
import { Permission } from "@cqsjjb/jjb-common-decorator/permission";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import PersonInChargeList from "~/components/PersonInChargeList";
import { NS_USER_CERTIFICATE } from "~/enumerate/namespace";
// 企业安全生产管理人员管理
function List(props) {
return (
<div>
<PersonInChargeList
props={props}
certificatePhotoType={162}
personnelType="aqscglry"
permissionAdd="gfd-qyaqscglrygl-add"
permissionEdit="gfd-qyaqscglrygl-edit"
permissionView="gfd-qyaqscglrygl-info"
permissionDel="gfd-qyaqscglrygl-del"
dictionaryType="aqscglrygwmc0000"
/>
</div>
);
}
export default Connect([NS_USER_CERTIFICATE], true)(Permission(List));

View File

@ -0,0 +1,16 @@
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import PersonInChargeInfo from "~/components/PersonInChargeInfo";
import { NS_USER_CERTIFICATE } from "~/enumerate/namespace";
function View(props) {
return (
<div>
<PersonInChargeInfo
props={props}
certificatePhotoType={162}
/>
</div>
);
}
export default Connect([NS_USER_CERTIFICATE], true)(View);

View File

@ -0,0 +1,9 @@
function SecurityAdmini(props) {
return (
<div>
{props.children}
</div>
);
}
export default SecurityAdmini;

View File

@ -0,0 +1,23 @@
import { Permission } from "@cqsjjb/jjb-common-decorator/permission";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import SpecialCertificateList from "~/components/SpecialCertificateList";
import { NS_USER_CERTIFICATE } from "~/enumerate/namespace";
// 企业特种设备操作人员管理
function List(props) {
return (
<div>
<SpecialCertificateList
props={props}
certificatePhotoType={160}
personnelType="tzsbczry"
permissionAdd="gfd-tzsbczrygl-add"
permissionEdit="gfd-tzsbczrygl-edit"
permissionView="gfd-tzsbczrygl-info"
permissionDel="gfd-tzsbczrygl-del"
dictionaryType="tzsbczryczxmzylb0000"
/>
</div>
);
}
export default Connect([NS_USER_CERTIFICATE], true)(Permission(List));

View File

@ -0,0 +1,16 @@
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import SpecialCertificateInfo from "~/components/SpecialCertificateInfo";
import { NS_USER_CERTIFICATE } from "~/enumerate/namespace";
function View(props) {
return (
<div>
<SpecialCertificateInfo
props={props}
certificatePhotoType={160}
/>
</div>
);
}
export default Connect([NS_USER_CERTIFICATE], true)(View);

View File

@ -0,0 +1,9 @@
function SpecialEquipment(props) {
return (
<div>
{props.children}
</div>
);
}
export default SpecialEquipment;

View File

@ -0,0 +1,22 @@
import { Permission } from "@cqsjjb/jjb-common-decorator/permission";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import SpecialCertificateList from "~/components/SpecialCertificateList";
import { NS_USER_CERTIFICATE } from "~/enumerate/namespace";
// 企业特种作业人员管理
function List(props) {
return (
<div>
<SpecialCertificateList
props={props}
certificatePhotoType={159}
personnelType="tezhongzuoye"
permissionAdd="gfd-tzzyrugl-add"
permissionEdit="gfd-tzzyrugl-edit"
permissionView="gfd-tzzyrugl-info"
permissionDel="gfd-tzzyrugl-del"
dictionaryType="tzzyryhylb0000"
/>
</div>
);
}
export default Connect([NS_USER_CERTIFICATE], true)(Permission(List));

View File

@ -0,0 +1,16 @@
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import SpecialCertificateInfo from "~/components/SpecialCertificateInfo";
import { NS_USER_CERTIFICATE } from "~/enumerate/namespace";
function View(props) {
return (
<div>
<SpecialCertificateInfo
props={props}
certificatePhotoType={159}
/>
</div>
);
}
export default Connect([NS_USER_CERTIFICATE], true)(View);

View File

@ -0,0 +1,9 @@
function SpecialPersonnel(props) {
return (
<div>
{props.children}
</div>
);
}
export default SpecialPersonnel;

View File

@ -0,0 +1,119 @@
import { Permission } from "@cqsjjb/jjb-common-decorator/permission";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { Button, Form, Space } from "antd";
import Page from "zy-react-library/components/Page";
import Search from "zy-react-library/components/Search";
import Table from "zy-react-library/components/Table";
import useTable from "zy-react-library/hooks/useTable";
import { NS_USER_CERTIFICATE } from "~/enumerate/namespace";
function List(props) {
const [form] = Form.useForm();
const { tableProps, getData } = useTable(props["userCertificateStatPage"], {
form,
transform: (formData) => {
return {
...formData,
corpType: 1,
};
},
});
return (
<Page isShowAllAction={false}>
<Search
form={form}
options={[
{
name: "corpName",
label: "公司名称",
},
]}
onFinish={getData}
/>
<Table
columns={[
{
title: "公司名称",
dataIndex: "corpName",
},
{
title: "特种作业人员证书数",
dataIndex: "specialWorkCertCount",
render: (_, record) => (
<Space>
<Button
type="link"
disabled={!props.permission("gfd-xgfryzztj-tzzyInfo")}
onClick={() => props.history.push(`./SpecialPersonnel/List?corpinfoId=${record.corpinfoId}`)}
>
{record.specialWorkCertCount}
</Button>
</Space>
),
},
{
title: "特种设备操作人员证书数",
dataIndex: "specialEquipmentCertCount",
render: (_, record) => (
<Space>
<Button
type="link"
onClick={() => props.history.push(`./SpecialDevice/List?corpinfoId=${record.corpinfoId}`)}
disabled={!props.permission("gfd-xgfryzztj-tzsbInfo")}
>
{record.specialEquipmentCertCount}
</Button>
</Space>
),
},
{
title: "主要负责人证书数",
dataIndex: "principalCertCount",
render: (_, record) => (
<Space>
<Button
type="link"
onClick={() => props.history.push(`./PersonInCharge/List?corpinfoId=${record.corpinfoId}`)}
disabled={!props.permission("gfd-xgfryzztj-zyfzrInfo")}
>
{record.principalCertCount}
</Button>
</Space>
),
},
{
title: "安全生产管理人员证书数",
dataIndex: "safetyManagerCertCount",
render: (_, record) => (
<Space>
<Button
type="link"
onClick={() => props.history.push(`./SecurityAdmini/List?corpinfoId=${record.corpinfoId}`)}
disabled={!props.permission("gfd-xgfryzztj-aqscInfo")}
>
{record.safetyManagerCertCount}
</Button>
</Space>
),
},
]}
{...tableProps}
/>
</Page>
);
}
export default Connect([NS_USER_CERTIFICATE], true)(Permission(List));

View File

@ -0,0 +1,22 @@
import { Permission } from "@cqsjjb/jjb-common-decorator/permission";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import Page from "zy-react-library/components/Page";
import PersonInChargeList from "~/components/PersonInChargeList";
import { NS_USER_CERTIFICATE } from "~/enumerate/namespace";
// 企业主要负责人管理
function List(props) {
return (
<Page headerTitle="主要负责人证书数" isShowFooter={false}>
<PersonInChargeList
props={props}
certificatePhotoType={161}
displayType="View"
personnelType="zyfzr"
permissionView="gfd-xgfryzztj-zyfzrInfoNb"
dictionaryType="zyfzrgwmc0000"
/>
</Page>
);
}
export default Connect([NS_USER_CERTIFICATE], true)(Permission(List));

Some files were not shown because too many files have changed in this diff Show More