Compare commits
No commits in common. "main" and "master" have entirely different histories.
|
|
@ -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
|
||||
|
|
@ -1,20 +1,20 @@
|
|||
# ---> Actionscript
|
||||
# Build and Release Folders
|
||||
bin-debug/
|
||||
bin-release/
|
||||
[Oo]bj/
|
||||
[Bb]in/
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# Other files and folders
|
||||
.settings/
|
||||
# dependencies
|
||||
/node_modules
|
||||
|
||||
# Executables
|
||||
*.swf
|
||||
*.air
|
||||
*.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.
|
||||
# production
|
||||
/dist
|
||||
/demo
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.idea
|
||||
yarn.lock
|
||||
/openspec/
|
||||
/.codebuddy/
|
||||
/.idea/
|
||||
/.playwright-cli/
|
||||
/.vscode/
|
||||
/.workbuddy/
|
||||
|
|
|
|||
76
README.md
76
README.md
|
|
@ -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`。
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
@ -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
|
||||
```
|
||||
|
|
@ -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-runtime(GBS 底座 DVA 运行时)
|
||||
- rspack 构建 / pnpm 包管理
|
||||
- qiankun 微前端子应用
|
||||
|
||||
## 重要提示
|
||||
|
||||
- 本项目为 GBS 底座微前端子应用,独立启动时有内置模拟布局
|
||||
- 本地独立运行时 API 调用会返回 401(缺少底座注入的认证 Token)
|
||||
- 仅 Test 页面可在本地无底座环境下正常访问
|
||||
- 重命名项目目录后需 `pnpm install` 重建 node_modules 符号链接
|
||||
- 完整开发指南请参考 [dev-guide.md](./dev-guide.md)
|
||||
|
|
@ -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"],
|
||||
},
|
||||
});
|
||||
|
|
@ -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",
|
||||
}],
|
||||
],
|
||||
};
|
||||
|
|
@ -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,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": "src",
|
||||
"paths": {
|
||||
"~/*": ["*"]
|
||||
}
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,3 @@
|
|||
allowBuilds:
|
||||
es5-ext: false
|
||||
zy-react-library: false
|
||||
|
|
@ -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>
|
||||
|
|
@ -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",
|
||||
);
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
import { declareRequest } from "@cqsjjb/jjb-dva-runtime";
|
||||
|
||||
export const identifyPartList = declareRequest(
|
||||
"coursewareLoading",
|
||||
"Post > @/risk/busIdentifyPart/list",
|
||||
);
|
||||
|
|
@ -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}",
|
||||
// );
|
||||
|
|
@ -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",
|
||||
);
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
@ -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,支持jpg、jpeg、png格式。
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
),
|
||||
},
|
||||
]}
|
||||
labelCol={{
|
||||
span: 10,
|
||||
}}
|
||||
showActionButtons={false}
|
||||
onFinish={onSubmit}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
const AddModal = AddModalComponent;
|
||||
|
||||
export default Connect([NS_USER_CERTIFICATE], true)(Permission(List));
|
||||
|
|
@ -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);
|
||||
|
|
@ -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,支持jpg、jpeg、png格式。
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
),
|
||||
},
|
||||
]}
|
||||
labelCol={{
|
||||
span: 10,
|
||||
}}
|
||||
showActionButtons={false}
|
||||
onFinish={onSubmit}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
const AddModal = AddModalComponent;
|
||||
|
||||
export default Connect([NS_USER_CERTIFICATE], true)(Permission(SpecialCertificateList));
|
||||
|
|
@ -0,0 +1 @@
|
|||
export {};
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
/**
|
||||
* 全局常量定义
|
||||
*/
|
||||
|
||||
export {};
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
/**
|
||||
* 全局上下文定义
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
|
||||
// 获取antd全局静态方法
|
||||
export const InjectContext = React.createContext({});
|
||||
|
|
@ -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");
|
||||
|
|
@ -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);
|
||||
|
|
@ -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;
|
||||
|
|
@ -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));
|
||||
|
|
@ -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);
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
function PersonInCharge(props) {
|
||||
return (
|
||||
<div>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default PersonInCharge;
|
||||
|
|
@ -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));
|
||||
|
|
@ -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);
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
function SecurityAdmini(props) {
|
||||
return (
|
||||
<div>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default SecurityAdmini;
|
||||
|
|
@ -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));
|
||||
|
|
@ -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);
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
function SpecialEquipment(props) {
|
||||
return (
|
||||
<div>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default SpecialEquipment;
|
||||
|
|
@ -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));
|
||||
|
|
@ -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);
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
function SpecialPersonnel(props) {
|
||||
return (
|
||||
<div>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default SpecialPersonnel;
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
function PersonnelLicense(props) {
|
||||
return (
|
||||
<div>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default PersonnelLicense;
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
function EnterpriseLicenseManage(props) {
|
||||
return (
|
||||
<div>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default EnterpriseLicenseManage;
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
function BranchCompany(props) {
|
||||
return (
|
||||
<div>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default BranchCompany;
|
||||
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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] : [];
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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));
|
||||
|
|
@ -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);
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
function PersonInCharge(props) {
|
||||
return (
|
||||
<div>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default PersonInCharge;
|
||||
|
|
@ -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));
|
||||
|
|
@ -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);
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
function SecurityAdmini(props) {
|
||||
return (
|
||||
<div>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default SecurityAdmini;
|
||||
|
|
@ -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));
|
||||
|
|
@ -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);
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
function SpecialEquipment(props) {
|
||||
return (
|
||||
<div>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default SpecialEquipment;
|
||||
|
|
@ -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));
|
||||
|
|
@ -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);
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
function SpecialPersonnel(props) {
|
||||
return (
|
||||
<div>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default SpecialPersonnel;
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
function PersonnelLicense(props) {
|
||||
return (
|
||||
<div>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default PersonnelLicense;
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
function EnterpriseLicenseManage(props) {
|
||||
return (
|
||||
<div>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default EnterpriseLicenseManage;
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
function Stakeholder(props) {
|
||||
return (
|
||||
<div>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Stakeholder;
|
||||
|
|
@ -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));
|
||||
|
|
@ -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;
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
function BranchStatistics(props) {
|
||||
return (
|
||||
<div>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default BranchStatistics;
|
||||
|
|
@ -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));
|
||||
|
|
@ -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));
|
||||
|
|
@ -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;
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
function StakeholderStatistics(props) {
|
||||
return (
|
||||
<div>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default StakeholderStatistics;
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
function EnterpriseLicenseManage(props) {
|
||||
return (
|
||||
<div>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default EnterpriseLicenseManage;
|
||||
|
|
@ -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));
|
||||
|
|
@ -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));
|
||||
|
|
@ -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);
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
function PersonInCharge(props) {
|
||||
return (
|
||||
<div>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default PersonInCharge;
|
||||
|
|
@ -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));
|
||||
|
|
@ -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);
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
function SecurityAdmini(props) {
|
||||
return (
|
||||
<div>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default SecurityAdmini;
|
||||
|
|
@ -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));
|
||||
|
|
@ -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);
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
function SpecialEquipment(props) {
|
||||
return (
|
||||
<div>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default SpecialEquipment;
|
||||
|
|
@ -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));
|
||||
|
|
@ -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);
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
function SpecialPersonnel(props) {
|
||||
return (
|
||||
<div>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default SpecialPersonnel;
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
function BranchCompanyStat(props) {
|
||||
return (
|
||||
<div>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default BranchCompanyStat;
|
||||
|
|
@ -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));
|
||||
|
|
@ -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);
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
function PersonInCharge(props) {
|
||||
return (
|
||||
<div>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default PersonInCharge;
|
||||
|
|
@ -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));
|
||||
|
|
@ -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);
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
function SecurityAdmini(props) {
|
||||
return (
|
||||
<div>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default SecurityAdmini;
|
||||
|
|
@ -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));
|
||||
|
|
@ -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);
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
function SpecialEquipment(props) {
|
||||
return (
|
||||
<div>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default SpecialEquipment;
|
||||
|
|
@ -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));
|
||||
|
|
@ -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);
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
function SpecialPersonnel(props) {
|
||||
return (
|
||||
<div>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default SpecialPersonnel;
|
||||
|
|
@ -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));
|
||||
|
|
@ -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
Loading…
Reference in New Issue