diff --git a/frontend/frontend-guide.md b/frontend/frontend-guide.md new file mode 100644 index 00000000..60f753c8 --- /dev/null +++ b/frontend/frontend-guide.md @@ -0,0 +1,362 @@ +# 工作流平台前端开发指南 + +## 一、项目概述 + +### 1.1 技术栈 +- 框架:Ant Design Pro +- 语言:TypeScript +- HTTP请求:Umi Request +- 流程设计器:LogicFlow +- 表单设计器:FormRender +- 图表库:ECharts + +### 1.2 开发环境 +- Node.js >= 16 +- yarn >= 1.22 +- TypeScript >= 4.x + +## 二、接口定义 + +### 2.1 工作流定义管理 +```typescript +// 工作流定义相关接口 +interface WorkflowDefinitionAPI { + // 基础CRUD接口 + list: '/api/v1/workflow-definitions' // GET 查询列表 + create: '/api/v1/workflow-definitions' // POST 创建 + update: '/api/v1/workflow-definitions/{id}' // PUT 更新 + delete: '/api/v1/workflow-definitions/{id}' // DELETE 删除 + get: '/api/v1/workflow-definitions/{id}' // GET 获取详情 + + // 特殊操作接口 + publish: '/api/v1/workflow-definitions/{id}/publish' // POST 发布 + disable: '/api/v1/workflow-definitions/{id}/disable' // POST 禁用 + enable: '/api/v1/workflow-definitions/{id}/enable' // POST 启用 +} + +// 工作流定义数据结构 +interface WorkflowDefinitionDTO { + id: number + code: string // 工作流编码 + name: string // 工作流名称 + description: string // 描述 + status: 'DRAFT' | 'PUBLISHED' | 'DISABLED' // 状态 + version: number // 版本号 + nodeConfig: string // 节点配置(JSON) + transitionConfig: string // 流转配置(JSON) + formDefinition: string // 表单定义 + graphDefinition: string // 图形信息 + enabled: boolean // 是否启用 + remark: string // 备注 + nodes: NodeDefinitionDTO[] // 节点定义列表 +} + +// 节点定义数据结构 +interface NodeDefinitionDTO { + id: number + nodeId: string // 节点ID + name: string // 节点名称 + type: 'START' | 'END' | 'TASK' | 'GATEWAY' | 'SUB_PROCESS' | 'SHELL' // 节点类型 + config: string // 节点配置(JSON) + description: string // 节点描述 + workflowDefinitionId: number // 工作流定义ID + orderNum: number // 排序号 +} +``` + +### 2.2 工作流实例管理 +```typescript +// 工作流实例相关接口 +interface WorkflowInstanceAPI { + // 基础CRUD接口 + list: '/api/v1/workflow-instance' // GET 查询列表 + get: '/api/v1/workflow-instance/{id}' // GET 获取详情 + + // 实例操作接口 + create: '/api/v1/workflow-instance' // POST 创建实例 + start: '/api/v1/workflow-instance/{id}/start' // POST 启动 + cancel: '/api/v1/workflow-instance/{id}/cancel' // POST 取消 + pause: '/api/v1/workflow-instance/{id}/pause' // POST 暂停 + resume: '/api/v1/workflow-instance/{id}/resume' // POST 恢复 +} + +// 工作流实例数据结构 +interface WorkflowInstanceDTO { + id: number + definitionId: number // 工作流定义ID + businessKey: string // 业务标识 + status: 'RUNNING' | 'COMPLETED' | 'FAILED' | 'CANCELLED' | 'PAUSED' // 状态 + startTime: string // 开始时间 + endTime: string // 结束时间 + variables: string // 工作流变量(JSON) + error: string // 错误信息 +} +``` + +### 2.3 节点实例管理 +```typescript +// 节点实例相关接口 +interface NodeInstanceAPI { + // 查询接口 + list: '/api/v1/node-instance' // GET 查询列表 + get: '/api/v1/node-instance/{id}' // GET 获取详情 + findByWorkflow: '/api/v1/node-instance/workflow/{workflowInstanceId}' // GET 查询工作流下的节点 + findByStatus: '/api/v1/node-instance/workflow/{workflowInstanceId}/status/{status}' // GET 查询指定状态的节点 + + // 节点操作 + updateStatus: '/api/v1/node-instance/{id}/status' // PUT 更新状态 +} + +// 节点实例数据结构 +interface NodeInstanceDTO { + id: number + workflowInstanceId: number // 工作流实例ID + nodeId: string // 节点ID + nodeType: 'START' | 'END' | 'TASK' | 'GATEWAY' | 'SUB_PROCESS' | 'SHELL' // 节点类型 + name: string // 节点名称 + status: 'PENDING' | 'RUNNING' | 'COMPLETED' | 'FAILED' | 'CANCELLED' | 'PAUSED' | 'SKIPPED' // 状态 + startTime: string // 开始时间 + endTime: string // 结束时间 + config: string // 节点配置(JSON) + input: string // 输入参数(JSON) + output: string // 输出结果(JSON) + error: string // 错误信息 + preNodeId: string // 前置节点ID +} +``` + +### 2.4 工作流日志管理 +```typescript +// 日志相关接口 +interface WorkflowLogAPI { + // 基础CRUD接口 + list: '/api/v1/workflow-logs' // GET 查询列表 + create: '/api/v1/workflow-logs' // POST 创建 + + // 特殊查询接口 + getWorkflowLogs: '/api/v1/workflow-logs/workflow/{workflowInstanceId}' // GET 查询工作流日志 + getNodeLogs: '/api/v1/workflow-logs/node/{workflowInstanceId}/{nodeId}' // GET 查询节点日志 + record: '/api/v1/workflow-logs/record' // POST 记录日志 +} + +// 日志数据结构 +interface WorkflowLogDTO { + id: number + workflowInstanceId: number // 工作流实例ID + nodeId: string // 节点ID + message: string // 日志内容 + level: 'INFO' | 'WARN' | 'ERROR' // 日志级别 + detail: string // 详细信息 + createTime: string // 创建时间 +} +``` + +## 三、页面功能 + +### 3.1 工作流定义管理(/workflow/definition) + +#### 3.1.1 列表页(/workflow/definition/list) +- 功能点: + - 工作流定义列表展示 + - 支持按名称、编码、状态搜索 + - 支持创建、编辑、删除操作 + - 支持发布、禁用操作 + - 支持查看历史版本 +- 列表字段: + - 工作流编码 + - 工作流名称 + - 版本号 + - 状态 + - 创建时间 + - 更新时间 + - 操作按钮 + +#### 3.1.2 编辑页(/workflow/definition/edit) +- 功能点: + 1. 基本信息编辑 + - 工作流编码 + - 工作流名称 + - 描述 + - 备注 + 2. 流程设计器 + - 节点拖拽 + - 连线绘制 + - 节点配置 + - 流程校验 + 3. 表单设计器 + - 表单字段配置 + - 字段验证规则 + - 表单预览 + 4. 节点配置面板 + - 节点基本信息 + - 节点类型配置 + - 执行条件配置 + - 表单关联配置 + +### 3.2 工作流实例管理(/workflow/instance) + +#### 3.2.1 列表页(/workflow/instance/list) +- 功能点: + - 工作流实例列表展示 + - 支持按工作流名称、状态、时间搜索 + - 支持启动、暂停、恢复、取消操作 +- 列表字段: + - 实例ID + - 工作流名称 + - 业务标识 + - 状态 + - 开始时间 + - 结束时间 + - 操作按钮 + +#### 3.2.2 详情页(/workflow/instance/detail) +- 功能点: + 1. 基本信息展示 + - 实例ID + - 工作流名称 + - 状态 + - 时间信息 + 2. 流程图展示 + - 显示当前节点 + - 节点状态标识 + - 流程进度展示 + 3. 变量信息 + - 变量列表 + - 变量历史值 + 4. 日志信息 + - 操作日志 + - 执行日志 + - 错误日志 + +### 3.3 监控大盘(/workflow/monitor) +- 功能点: + 1. 统计信息 + - 总工作流数 + - 运行中实例数 + - 完成实例数 + - 失败实例数 + 2. 状态分布 + - 饼图展示各状态占比 + - 支持时间范围筛选 + 3. 执行时长统计 + - 平均执行时长 + - 最长执行时长 + - 执行时长分布 + 4. 异常情况统计 + - 异常类型分布 + - 异常趋势图 + - 异常节点TOP5 + +## 四、开发规范 + +### 4.1 项目结构 +``` +src/ + ├── components/ # 公共组件 + │ ├── FlowDesigner/ # 流程设计器组件 + │ ├── FormDesigner/ # 表单设计器组件 + │ └── NodeConfig/ # 节点配置组件 + ├── pages/ # 页面组件 + │ └── workflow/ # 工作流相关页面 + ├── services/ # API服务 + │ └── workflow/ # 工作流相关API + ├── models/ # 数据模型 + ├── utils/ # 工具函数 + ├── constants/ # 常量定义 + ├── types/ # TypeScript类型 + └── locales/ # 国际化资源 +``` + +### 4.2 命名规范 +- 文件命名:使用 PascalCase + - 组件文件:`FlowDesigner.tsx` + - 类型文件:`WorkflowTypes.ts` + - 工具文件:`FlowUtils.ts` +- 变量命名:使用 camelCase + - 普通变量:`flowInstance` + - 布尔值:`isRunning`、`hasError` +- 常量命名:使用 UPPER_CASE + - `MAX_NODE_COUNT` + - `DEFAULT_CONFIG` + +### 4.3 组件开发规范 +1. 使用函数组件和 Hooks +2. 使用 TypeScript 类型声明 +3. 添加必要的注释 +4. 实现错误处理 +5. 添加加载状态 +6. 做好性能优化 + +### 4.4 代码提交规范 +- feat: 新功能 +- fix: 修复bug +- docs: 文档更新 +- style: 代码格式 +- refactor: 重构 +- test: 测试 +- chore: 构建过程或辅助工具的变动 + +## 五、开发流程 + +### 5.1 环境搭建 +1. 创建项目 +```bash +yarn create umi +``` + +2. 安装依赖 +```bash +yarn add @ant-design/pro-components @logicflow/core @logicflow/extension form-render echarts +``` + +### 5.2 开发步骤 +1. 搭建基础框架和路由(2天) +2. 实现工作流定义CRUD(3天) +3. 开发流程设计器(5天) +4. 开发表单设计器(3天) +5. 实现工作流实例管理(3天) +6. 实现节点实例管理(2天) +7. 实现日志管理(2天) +8. 开发监控大盘(3天) +9. 测试和优化(2天) + +### 5.3 测试要求 +1. 单元测试覆盖率 > 80% +2. 必须包含组件测试 +3. 必须包含集成测试 +4. 必须进行性能测试 + +### 5.4 部署要求 +1. 使用 Docker 部署 +2. 配置 Nginx 代理 +3. 启用 GZIP 压缩 +4. 配置缓存策略 + +## 六、注意事项 + +### 6.1 性能优化 +1. 使用路由懒加载 +2. 组件按需加载 +3. 大数据列表虚拟化 +4. 合理使用缓存 +5. 避免不必要的渲染 + +### 6.2 安全性 +1. 添加权限控制 +2. 防止XSS攻击 +3. 添加数据验证 +4. 敏感信息加密 + +### 6.3 用户体验 +1. 添加适当的加载状态 +2. 提供操作反馈 +3. 添加错误处理 +4. 支持快捷键操作 +5. 添加操作确认 +6. 支持数据导出 + +### 6.4 兼容性 +1. 支持主流浏览器最新版本 +2. 支持响应式布局 +3. 支持暗黑模式 +4. 支持国际化 \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index dc7f0469..efe5d79b 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -10,9 +10,12 @@ "dependencies": { "@ant-design/icons": "^5.2.6", "@antv/x6": "^2.18.1", + "@logicflow/core": "^2.0.9", + "@logicflow/extension": "^2.0.13", "@reduxjs/toolkit": "^2.0.1", "antd": "^5.22.2", "axios": "^1.6.2", + "form-render": "^2.5.1", "react": "^18.2.0", "react-dom": "^18.2.0", "react-redux": "^9.0.4", @@ -135,6 +138,11 @@ "react": ">=16.9.0" } }, + "node_modules/@antv/hierarchy": { + "version": "0.6.14", + "resolved": "https://registry.npmmirror.com/@antv/hierarchy/-/hierarchy-0.6.14.tgz", + "integrity": "sha512-V3uknf7bhynOqQDw2sg+9r9DwZ9pc6k/EcqyTFdfXB1+ydr7urisP0MipIuimucvQKN+Qkd+d6w601r1UIroqQ==" + }, "node_modules/@antv/x6": { "version": "2.18.1", "resolved": "https://registry.npmmirror.com/@antv/x6/-/x6-2.18.1.tgz", @@ -1032,6 +1040,40 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@logicflow/core": { + "version": "2.0.9", + "resolved": "https://registry.npmmirror.com/@logicflow/core/-/core-2.0.9.tgz", + "integrity": "sha512-HRc3W+XbcXbq9E3wElFNkaxNBja7Ga+FK6LYbsoOGWDWzZ7gSBCaYTpKPUlQsfUK0EvztqzCuer2DRJQQ77Ylg==", + "dependencies": { + "classnames": "^2.3.2", + "lodash-es": "^4.17.21", + "mobx": "^5.15.7", + "mobx-preact": "^3.0.0", + "mobx-utils": "^5.6.1", + "mousetrap": "^1.6.5", + "preact": "^10.17.1", + "uuid": "^9.0.0" + } + }, + "node_modules/@logicflow/extension": { + "version": "2.0.13", + "resolved": "https://registry.npmmirror.com/@logicflow/extension/-/extension-2.0.13.tgz", + "integrity": "sha512-1csZP2RYyGItvOMxVSpzrP7MguPrMRQYV+PUbm+8jZLrFbt3LcOQCNdHsEFU0cfuaiBF6Nx4qBdBeo0kog0eCw==", + "dependencies": { + "@antv/hierarchy": "^0.6.11", + "@logicflow/core": "2.0.9", + "classnames": "^2.3.2", + "lodash-es": "^4.17.21", + "medium-editor": "^5.23.3", + "mobx": "^5.15.7", + "preact": "^10.17.1", + "rangy": "^1.3.1", + "vanilla-picker": "^2.12.3" + }, + "peerDependencies": { + "@logicflow/core": "2.0.9" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1472,6 +1514,11 @@ "win32" ] }, + "node_modules/@sphinxxxx/color-conversion": { + "version": "2.2.2", + "resolved": "https://registry.npmmirror.com/@sphinxxxx/color-conversion/-/color-conversion-2.2.2.tgz", + "integrity": "sha512-XExJS3cLqgrmNBIP3bBw6+1oQ1ksGjFh0+oClDKFYpCCqx/hlqwWO5KO/S63fzUo67SxI9dMrF0y5T/Ey7h8Zw==" + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmmirror.com/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -1806,6 +1853,36 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/add-dom-event-listener": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/add-dom-event-listener/-/add-dom-event-listener-1.1.0.tgz", + "integrity": "sha512-WCxx1ixHT0GQU9hb0KI/mhgRQhnU+U3GvwY6ZvVjYq8rsihIGoaIOUbY0yMPBxLH5MDtr0kz3fisWGNcbWW7Jw==", + "dependencies": { + "object-assign": "4.x" + } + }, + "node_modules/ahooks": { + "version": "3.8.1", + "resolved": "https://registry.npmmirror.com/ahooks/-/ahooks-3.8.1.tgz", + "integrity": "sha512-JoP9+/RWO7MnI/uSKdvQ8WB10Y3oo1PjLv+4Sv4Vpm19Z86VUMdXh+RhWvMGxZZs06sq2p0xVtFk8Oh5ZObsoA==", + "dependencies": { + "@babel/runtime": "^7.21.0", + "dayjs": "^1.9.1", + "intersection-observer": "^0.12.0", + "js-cookie": "^3.0.5", + "lodash": "^4.17.21", + "react-fast-compare": "^3.2.2", + "resize-observer-polyfill": "^1.5.1", + "screenfull": "^5.0.0", + "tslib": "^2.4.1" + }, + "engines": { + "node": ">=8.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmmirror.com/ajv/-/ajv-6.12.6.tgz", @@ -1925,6 +2002,11 @@ "node": ">=8" } }, + "node_modules/async-validator": { + "version": "3.5.2", + "resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-3.5.2.tgz", + "integrity": "sha512-8eLCg00W9pIRZSB781UUX/H6Oskmm8xloZfr09lz5bikRpBVDlJ3hRVuxxP1SxcwsEYfJ4IU8Q19Y8/893r3rQ==" + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", @@ -1940,6 +2022,20 @@ "proxy-from-env": "^1.1.0" } }, + "node_modules/babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmmirror.com/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==", + "dependencies": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "node_modules/babel-runtime/node_modules/regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", @@ -2049,6 +2145,15 @@ "resolved": "https://registry.npmmirror.com/classnames/-/classnames-2.5.1.tgz", "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" }, + "node_modules/color": { + "version": "3.2.1", + "resolved": "https://registry.npmmirror.com/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "dependencies": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", @@ -2064,8 +2169,29 @@ "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmmirror.com/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/color/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" }, "node_modules/combined-stream": { "version": "1.0.8", @@ -2078,6 +2204,19 @@ "node": ">= 0.8" } }, + "node_modules/component-classes": { + "version": "1.2.6", + "resolved": "https://registry.npmmirror.com/component-classes/-/component-classes-1.2.6.tgz", + "integrity": "sha512-hPFGULxdwugu1QWW3SvVOCUHLzO34+a2J6Wqy0c5ASQkfi9/8nZcBB0ZohaEbXOQlCflMAEMmEWk7u7BVs4koA==", + "dependencies": { + "component-indexof": "0.0.3" + } + }, + "node_modules/component-indexof": { + "version": "0.0.3", + "resolved": "https://registry.npmmirror.com/component-indexof/-/component-indexof-0.0.3.tgz", + "integrity": "sha512-puDQKvx/64HZXb4hBwIcvQLaLgux8o1CbWl39s41hrIIZDl1lJiD5jc22gj3RBeGK0ovxALDYpIbyjqDUUl0rw==" + }, "node_modules/compute-scroll-into-view": { "version": "3.1.0", "resolved": "https://registry.npmmirror.com/compute-scroll-into-view/-/compute-scroll-into-view-3.1.0.tgz", @@ -2103,6 +2242,22 @@ "toggle-selection": "^1.0.6" } }, + "node_modules/core-js": { + "version": "2.6.12", + "resolved": "https://registry.npmmirror.com/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", + "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", + "hasInstallScript": true + }, + "node_modules/create-react-class": { + "version": "15.7.0", + "resolved": "https://registry.npmmirror.com/create-react-class/-/create-react-class-15.7.0.tgz", + "integrity": "sha512-QZv4sFWG9S5RUvkTYWbflxeZX+JG7Cz0Tn33rQBJ+WFQTqTfUTjMjiv9tnfXazjsO5r0KhPs+AqCjyrQX6h2ng==", + "dependencies": { + "loose-envify": "^1.3.1", + "object-assign": "^4.1.1" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -2117,6 +2272,15 @@ "node": ">= 8" } }, + "node_modules/css-animation": { + "version": "1.6.1", + "resolved": "https://registry.npmmirror.com/css-animation/-/css-animation-1.6.1.tgz", + "integrity": "sha512-/48+/BaEaHRY6kNQ2OIPzKf9A6g8WjZYjhiNDNuIVbsm5tXCGIAsHDjB4Xu1C4vXJtUWZo26O68OQkDpNBaPog==", + "dependencies": { + "babel-runtime": "6.x", + "component-classes": "^1.2.5" + } + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz", @@ -2182,6 +2346,11 @@ "node": ">=6.0.0" } }, + "node_modules/dom-align": { + "version": "1.12.4", + "resolved": "https://registry.npmmirror.com/dom-align/-/dom-align-1.12.4.tgz", + "integrity": "sha512-R8LUSEay/68zE5c8/3BDxiTEvgb4xZTF0RKmAHfiEVN3klfIpXfi2/QCoiWPccVQ0J/ZGdz9OjzL4uJEP/MRAw==" + }, "node_modules/electron-to-chromium": { "version": "1.5.64", "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.64.tgz", @@ -2595,6 +2764,89 @@ "node": ">= 6" } }, + "node_modules/form-render": { + "version": "2.5.1", + "resolved": "https://registry.npmmirror.com/form-render/-/form-render-2.5.1.tgz", + "integrity": "sha512-oNbJ+McqB5h1yuyxYAT3ixJF8itmHlnKvqDgQhJT9Tw1c3yGwfRnVXboRxBV+Myz0dkf47zL6lyY1l74yQsWsg==", + "dependencies": { + "@ant-design/icons": "^4.0.2", + "ahooks": "^3.7.5", + "async-validator": "^3.5.1", + "classnames": "^2.3.1", + "color": "^3.1.2", + "dayjs": "^1.11.7", + "lodash-es": "^4.17.21", + "rc-color-picker": "^1.2.6", + "virtualizedtableforantd4": "^1.1.2", + "zustand": "^4.1.5" + }, + "peerDependencies": { + "antd": "4.x || 5.x", + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/form-render/node_modules/@ant-design/colors": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/@ant-design/colors/-/colors-6.0.0.tgz", + "integrity": "sha512-qAZRvPzfdWHtfameEGP2Qvuf838NhergR35o+EuVyB5XvSA98xod5r4utvi4TJ3ywmevm290g9nsCG5MryrdWQ==", + "dependencies": { + "@ctrl/tinycolor": "^3.4.0" + } + }, + "node_modules/form-render/node_modules/@ant-design/icons": { + "version": "4.8.3", + "resolved": "https://registry.npmmirror.com/@ant-design/icons/-/icons-4.8.3.tgz", + "integrity": "sha512-HGlIQZzrEbAhpJR6+IGdzfbPym94Owr6JZkJ2QCCnOkPVIWMO2xgIVcOKnl8YcpijIo39V7l2qQL5fmtw56cMw==", + "dependencies": { + "@ant-design/colors": "^6.0.0", + "@ant-design/icons-svg": "^4.3.0", + "@babel/runtime": "^7.11.2", + "classnames": "^2.2.6", + "lodash": "^4.17.15", + "rc-util": "^5.9.4" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/form-render/node_modules/rc-color-picker": { + "version": "1.2.6", + "resolved": "https://registry.npmmirror.com/rc-color-picker/-/rc-color-picker-1.2.6.tgz", + "integrity": "sha512-AaC9Pg7qCHSy5M4eVbqDIaNb2FC4SEw82GOHB2C4R/+vF2FVa/r5XA+Igg5+zLPmAvBLhz9tL4MAfkRA8yWNJw==", + "dependencies": { + "classnames": "^2.2.5", + "prop-types": "^15.5.8", + "rc-trigger": "1.x", + "rc-util": "^4.0.2", + "tinycolor2": "^1.4.1" + }, + "peerDependencies": { + "react": "16.x", + "react-dom": "16.x" + } + }, + "node_modules/form-render/node_modules/rc-color-picker/node_modules/rc-util": { + "version": "4.21.1", + "resolved": "https://registry.npmmirror.com/rc-util/-/rc-util-4.21.1.tgz", + "integrity": "sha512-Z+vlkSQVc1l8O2UjR3WQ+XdWlhj5q9BMQNLk2iOBch75CqPfrJyGtcWMcnhRlNuDu0Ndtt4kLVO8JI8BrABobg==", + "dependencies": { + "add-dom-event-listener": "^1.1.0", + "prop-types": "^15.5.10", + "react-is": "^16.12.0", + "react-lifecycles-compat": "^3.0.4", + "shallowequal": "^1.1.0" + } + }, + "node_modules/form-render/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmmirror.com/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -2723,6 +2975,11 @@ "node": ">=8" } }, + "node_modules/hoist-non-react-statics": { + "version": "2.5.5", + "resolved": "https://registry.npmmirror.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz", + "integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==" + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmmirror.com/ignore/-/ignore-5.3.2.tgz", @@ -2783,6 +3040,16 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, + "node_modules/intersection-observer": { + "version": "0.12.2", + "resolved": "https://registry.npmmirror.com/intersection-observer/-/intersection-observer-0.12.2.tgz", + "integrity": "sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg==" + }, + "node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmmirror.com/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz", @@ -2828,6 +3095,14 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmmirror.com/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "engines": { + "node": ">=14" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz", @@ -2932,6 +3207,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, "node_modules/lodash-es": { "version": "4.17.21", "resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.21.tgz", @@ -2964,6 +3244,11 @@ "yallist": "^3.0.2" } }, + "node_modules/medium-editor": { + "version": "5.23.3", + "resolved": "https://registry.npmmirror.com/medium-editor/-/medium-editor-5.23.3.tgz", + "integrity": "sha512-he9/TdjX8f8MGdXGfCs8AllrYnqXJJvjNkDKmPg3aPW/uoIrlRqtkFthrwvmd+u4QyzEiadhCCM0EwTiRdUCJw==" + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz", @@ -3020,6 +3305,40 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/mobx": { + "version": "5.15.7", + "resolved": "https://registry.npmmirror.com/mobx/-/mobx-5.15.7.tgz", + "integrity": "sha512-wyM3FghTkhmC+hQjyPGGFdpehrcX1KOXsDuERhfK2YbJemkUhEB+6wzEN639T21onxlfYBmriA1PFnvxTUhcKw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mobx" + } + }, + "node_modules/mobx-preact": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/mobx-preact/-/mobx-preact-3.0.0.tgz", + "integrity": "sha512-ijan/cBs3WmRye87E5+3JmoFBB00KDAwNA3pm7bMwYLPHBAXlN86aC3gdrXw8aKzM5RI8V3a993PphzPv6P4FA==", + "dependencies": { + "hoist-non-react-statics": "^2.3.1" + }, + "peerDependencies": { + "mobx": "5.x", + "preact": ">=8" + } + }, + "node_modules/mobx-utils": { + "version": "5.6.2", + "resolved": "https://registry.npmmirror.com/mobx-utils/-/mobx-utils-5.6.2.tgz", + "integrity": "sha512-a/WlXyGkp6F12b01sTarENpxbmlRgPHFyR1Xv2bsSjQBm5dcOtd16ONb40/vOqck8L99NHpI+C9MXQ+SZ8f+yw==", + "peerDependencies": { + "mobx": "^4.13.1 || ^5.13.1" + } + }, + "node_modules/mousetrap": { + "version": "1.6.5", + "resolved": "https://registry.npmmirror.com/mousetrap/-/mousetrap-1.6.5.tgz", + "integrity": "sha512-QNo4kEepaIBwiT8CDhP98umTetp+JNfQYBWvC1pc6/OAibuXtRcxZ58Qz8skvEHYvURne/7R8T5VoOI7rDsEUA==" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", @@ -3056,6 +3375,14 @@ "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", "dev": true }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz", @@ -3160,6 +3487,11 @@ "node": ">=8" } }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", @@ -3206,6 +3538,15 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/preact": { + "version": "10.25.1", + "resolved": "https://registry.npmmirror.com/preact/-/preact-10.25.1.tgz", + "integrity": "sha512-frxeZV2vhQSohQwJ7FvlqC40ze89+8friponWUFeVEkaCfhC6Eu4V0iND5C9CXz8JLndV07QRDeXzH1+Anz5Og==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -3215,6 +3556,21 @@ "node": ">= 0.8.0" } }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmmirror.com/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmmirror.com/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -3249,6 +3605,78 @@ } ] }, + "node_modules/raf": { + "version": "3.4.1", + "resolved": "https://registry.npmmirror.com/raf/-/raf-3.4.1.tgz", + "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", + "dependencies": { + "performance-now": "^2.1.0" + } + }, + "node_modules/rangy": { + "version": "1.3.2", + "resolved": "https://registry.npmmirror.com/rangy/-/rangy-1.3.2.tgz", + "integrity": "sha512-fS1C4MOyk8T+ZJZdLcgrukPWxkyDXa+Hd2Kj+Zg4wIK71yrWgmjzHubzPMY1G+WD9EgGxMp3fIL0zQ1ickmSWA==" + }, + "node_modules/rc-align": { + "version": "2.4.5", + "resolved": "https://registry.npmmirror.com/rc-align/-/rc-align-2.4.5.tgz", + "integrity": "sha512-nv9wYUYdfyfK+qskThf4BQUSIadeI/dCsfaMZfNEoxm9HwOIioQ+LyqmMK6jWHAZQgOzMLaqawhuBXlF63vgjw==", + "dependencies": { + "babel-runtime": "^6.26.0", + "dom-align": "^1.7.0", + "prop-types": "^15.5.8", + "rc-util": "^4.0.4" + } + }, + "node_modules/rc-align/node_modules/rc-util": { + "version": "4.21.1", + "resolved": "https://registry.npmmirror.com/rc-util/-/rc-util-4.21.1.tgz", + "integrity": "sha512-Z+vlkSQVc1l8O2UjR3WQ+XdWlhj5q9BMQNLk2iOBch75CqPfrJyGtcWMcnhRlNuDu0Ndtt4kLVO8JI8BrABobg==", + "dependencies": { + "add-dom-event-listener": "^1.1.0", + "prop-types": "^15.5.10", + "react-is": "^16.12.0", + "react-lifecycles-compat": "^3.0.4", + "shallowequal": "^1.1.0" + } + }, + "node_modules/rc-align/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmmirror.com/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/rc-animate": { + "version": "2.11.1", + "resolved": "https://registry.npmmirror.com/rc-animate/-/rc-animate-2.11.1.tgz", + "integrity": "sha512-1NyuCGFJG/0Y+9RKh5y/i/AalUCA51opyyS/jO2seELpgymZm2u9QV3xwODwEuzkmeQ1BDPxMLmYLcTJedPlkQ==", + "dependencies": { + "babel-runtime": "6.x", + "classnames": "^2.2.6", + "css-animation": "^1.3.2", + "prop-types": "15.x", + "raf": "^3.4.0", + "rc-util": "^4.15.3", + "react-lifecycles-compat": "^3.0.4" + } + }, + "node_modules/rc-animate/node_modules/rc-util": { + "version": "4.21.1", + "resolved": "https://registry.npmmirror.com/rc-util/-/rc-util-4.21.1.tgz", + "integrity": "sha512-Z+vlkSQVc1l8O2UjR3WQ+XdWlhj5q9BMQNLk2iOBch75CqPfrJyGtcWMcnhRlNuDu0Ndtt4kLVO8JI8BrABobg==", + "dependencies": { + "add-dom-event-listener": "^1.1.0", + "prop-types": "^15.5.10", + "react-is": "^16.12.0", + "react-lifecycles-compat": "^3.0.4", + "shallowequal": "^1.1.0" + } + }, + "node_modules/rc-animate/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmmirror.com/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/rc-cascader": { "version": "3.30.0", "resolved": "https://registry.npmmirror.com/rc-cascader/-/rc-cascader-3.30.0.tgz", @@ -3775,6 +4203,36 @@ "react-dom": "*" } }, + "node_modules/rc-trigger": { + "version": "1.11.5", + "resolved": "https://registry.npmmirror.com/rc-trigger/-/rc-trigger-1.11.5.tgz", + "integrity": "sha512-MBuUPw1nFzA4K7jQOwb7uvFaZFjXGd00EofUYiZ+l/fgKVq8wnLC0lkv36kwqM7vfKyftRo2sh7cWVpdPuNnnw==", + "dependencies": { + "babel-runtime": "6.x", + "create-react-class": "15.x", + "prop-types": "15.x", + "rc-align": "2.x", + "rc-animate": "2.x", + "rc-util": "4.x" + } + }, + "node_modules/rc-trigger/node_modules/rc-util": { + "version": "4.21.1", + "resolved": "https://registry.npmmirror.com/rc-util/-/rc-util-4.21.1.tgz", + "integrity": "sha512-Z+vlkSQVc1l8O2UjR3WQ+XdWlhj5q9BMQNLk2iOBch75CqPfrJyGtcWMcnhRlNuDu0Ndtt4kLVO8JI8BrABobg==", + "dependencies": { + "add-dom-event-listener": "^1.1.0", + "prop-types": "^15.5.10", + "react-is": "^16.12.0", + "react-lifecycles-compat": "^3.0.4", + "shallowequal": "^1.1.0" + } + }, + "node_modules/rc-trigger/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmmirror.com/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/rc-upload": { "version": "4.8.1", "resolved": "https://registry.npmmirror.com/rc-upload/-/rc-upload-4.8.1.tgz", @@ -3843,11 +4301,21 @@ "react": "^18.3.1" } }, + "node_modules/react-fast-compare": { + "version": "3.2.2", + "resolved": "https://registry.npmmirror.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz", + "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==" + }, "node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmmirror.com/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" }, + "node_modules/react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + }, "node_modules/react-redux": { "version": "9.1.2", "resolved": "https://registry.npmmirror.com/react-redux/-/react-redux-9.1.2.tgz", @@ -4040,6 +4508,17 @@ "loose-envify": "^1.1.0" } }, + "node_modules/screenfull": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/screenfull/-/screenfull-5.2.0.tgz", + "integrity": "sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA==", + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/scroll-into-view-if-needed": { "version": "3.1.0", "resolved": "https://registry.npmmirror.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz", @@ -4060,6 +4539,11 @@ "node": ">=10" } }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz", @@ -4081,6 +4565,14 @@ "node": ">=8" } }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmmirror.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmmirror.com/slash/-/slash-3.0.0.tgz", @@ -4159,6 +4651,11 @@ "node": ">=12.22" } }, + "node_modules/tinycolor2": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/tinycolor2/-/tinycolor2-1.6.0.tgz", + "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -4188,6 +4685,11 @@ "typescript": ">=4.2.0" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmmirror.com/type-check/-/type-check-0.4.0.tgz", @@ -4287,6 +4789,36 @@ "node": ">= 4" } }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmmirror.com/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/vanilla-picker": { + "version": "2.12.3", + "resolved": "https://registry.npmmirror.com/vanilla-picker/-/vanilla-picker-2.12.3.tgz", + "integrity": "sha512-qVkT1E7yMbUsB2mmJNFmaXMWE2hF8ffqzMMwe9zdAikd8u2VfnsVY2HQcOUi2F38bgbxzlJBEdS1UUhOXdF9GQ==", + "dependencies": { + "@sphinxxxx/color-conversion": "^2.2.2" + } + }, + "node_modules/virtualizedtableforantd4": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/virtualizedtableforantd4/-/virtualizedtableforantd4-1.3.1.tgz", + "integrity": "sha512-rW8KoToI2nt1jNtweXIUIiygi74XMzKLzUrrtZbGsQc7m3v68AaedPuf4CZcte+nosgYuPEWnAgjuI/KR8BVbg==", + "peerDependencies": { + "antd": "^4.0.0 || ^5.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/vite": { "version": "5.4.11", "resolved": "https://registry.npmmirror.com/vite/-/vite-5.4.11.tgz", @@ -4393,6 +4925,33 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zustand": { + "version": "4.5.5", + "resolved": "https://registry.npmmirror.com/zustand/-/zustand-4.5.5.tgz", + "integrity": "sha512-+0PALYNJNgK6hldkgDq2vLrw5f6g/jCInz52n9RTpropGgeAf/ioFUCdtsjCqu4gNhW9D01rUQBROoRjdzyn2Q==", + "dependencies": { + "use-sync-external-store": "1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } } } } diff --git a/frontend/package.json b/frontend/package.json index 6ae3b8f1..171a15d1 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -12,9 +12,12 @@ "dependencies": { "@ant-design/icons": "^5.2.6", "@antv/x6": "^2.18.1", + "@logicflow/core": "^2.0.9", + "@logicflow/extension": "^2.0.13", "@reduxjs/toolkit": "^2.0.1", "antd": "^5.22.2", "axios": "^1.6.2", + "form-render": "^2.5.1", "react": "^18.2.0", "react-dom": "^18.2.0", "react-redux": "^9.0.4", diff --git a/frontend/src/components/LoadingComponent/index.tsx b/frontend/src/components/LoadingComponent/index.tsx new file mode 100644 index 00000000..d4c32c45 --- /dev/null +++ b/frontend/src/components/LoadingComponent/index.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import { Spin } from 'antd'; + +const LoadingComponent: React.FC = () => { + return ( +
+ +
+ ); +}; + +export default LoadingComponent; \ No newline at end of file diff --git a/frontend/src/pages/System/Menu/service.ts b/frontend/src/pages/System/Menu/service.ts index 7cec6030..d0a566df 100644 --- a/frontend/src/pages/System/Menu/service.ts +++ b/frontend/src/pages/System/Menu/service.ts @@ -1,6 +1,7 @@ import request from '@/utils/request'; import type { Page } from '@/types/base/page'; import type { MenuResponse, MenuRequest } from './types'; +import { MenuTypeEnum } from './types'; const BASE_URL = '/api/v1/menu'; @@ -15,6 +16,8 @@ export const getMenuTree = () => // 获取当前用户菜单 export const getCurrentUserMenus = async () => { const menus = await request.get(`${BASE_URL}/current`); + console.log('Backend menus:', menus); + // 添加首页路由 const dashboard: MenuResponse = { id: 0, @@ -25,7 +28,7 @@ export const getCurrentUserMenus = async () => { path: "/dashboard", component: "/Dashboard/index", icon: "dashboard", - type: 2, + type: MenuTypeEnum.MENU, parentId: 0, sort: 0, hidden: false, @@ -34,6 +37,77 @@ export const getCurrentUserMenus = async () => { updateBy: "system" }; + // 添加工作流菜单 + const workflow: MenuResponse = { + id: -2, + createTime: new Date().toISOString(), + updateTime: new Date().toISOString(), + version: 0, + name: "工作流管理", + path: "/workflow", + icon: "apartment", + type: MenuTypeEnum.DIRECTORY, + parentId: 0, + sort: 2, + hidden: false, + enabled: true, + createBy: "system", + updateBy: "system", + children: [ + { + id: -21, + createTime: new Date().toISOString(), + updateTime: new Date().toISOString(), + version: 0, + name: "流程定义", + path: "/workflow/definition", + component: "/pages/Workflow/Definition/index", + icon: "apartment", + type: MenuTypeEnum.MENU, + parentId: -2, + sort: 0, + hidden: false, + enabled: true, + createBy: "system", + updateBy: "system" + }, + { + id: -22, + createTime: new Date().toISOString(), + updateTime: new Date().toISOString(), + version: 0, + name: "流程实例", + path: "/workflow/instance", + component: "/pages/Workflow/Instance/index", + icon: "apartment", + type: MenuTypeEnum.MENU, + parentId: -2, + sort: 1, + hidden: false, + enabled: true, + createBy: "system", + updateBy: "system" + }, + { + id: -23, + createTime: new Date().toISOString(), + updateTime: new Date().toISOString(), + version: 0, + name: "流程监控", + path: "/workflow/monitor", + component: "/pages/Workflow/Monitor/index", + icon: "dashboard", + type: MenuTypeEnum.MENU, + parentId: -2, + sort: 2, + hidden: false, + enabled: true, + createBy: "system", + updateBy: "system" + } + ] + }; + // 添加X6测试菜单 const x6Test: MenuResponse = { id: -1, @@ -44,7 +118,7 @@ export const getCurrentUserMenus = async () => { path: "/x6-test", component: "/X6Test/index", icon: "experiment", - type: 2, + type: MenuTypeEnum.MENU, parentId: 0, sort: 1, hidden: false, @@ -71,7 +145,9 @@ export const getCurrentUserMenus = async () => { }; const processedMenus = menus.map(processMenu); - return [dashboard, x6Test, ...processedMenus]; + const allMenus = [dashboard, x6Test, workflow, ...processedMenus]; + console.log('All menus:', allMenus); + return allMenus; }; // 创建菜单 diff --git a/frontend/src/pages/Workflow/Definition/Edit/components/FlowDesigner/index.module.css b/frontend/src/pages/Workflow/Definition/Edit/components/FlowDesigner/index.module.css new file mode 100644 index 00000000..7af1a028 --- /dev/null +++ b/frontend/src/pages/Workflow/Definition/Edit/components/FlowDesigner/index.module.css @@ -0,0 +1,27 @@ +.container { + width: 100%; + height: 600px; + position: relative; + background: #fff; + border: 1px solid #e8e8e8; + border-radius: 2px; + display: flex; +} + +.canvas { + flex: 1; + height: 100%; +} + +.configPanel { + width: 320px; + height: 100%; + border-left: 1px solid #e8e8e8; + overflow-y: auto; +} + +:global(.lf-control) { + position: absolute; + right: 330px; + top: 10px; +} \ No newline at end of file diff --git a/frontend/src/pages/Workflow/Definition/Edit/components/FlowDesigner/index.tsx b/frontend/src/pages/Workflow/Definition/Edit/components/FlowDesigner/index.tsx new file mode 100644 index 00000000..126c4773 --- /dev/null +++ b/frontend/src/pages/Workflow/Definition/Edit/components/FlowDesigner/index.tsx @@ -0,0 +1,91 @@ +import React, { useEffect, useRef, useState } from 'react'; +import { Card, Space } from 'antd'; +import LogicFlow from '@logicflow/core'; +import { DndPanel, SelectionSelect, Control } from '@logicflow/extension'; +import '@logicflow/core/es/index.css'; +import '@logicflow/extension/es/style/index.css'; +import styles from './index.module.css'; +import { registerNodes } from './nodes'; +import NodeConfig from '../NodeConfig'; + +interface FlowDesignerProps { + value?: string; + onChange?: (value: string) => void; +} + +const FlowDesigner: React.FC = ({ + value, + onChange +}) => { + const containerRef = useRef(null); + const [lf, setLf] = useState(); + const [selectedNode, setSelectedNode] = useState(null); + + useEffect(() => { + if (containerRef.current) { + // 初始化 LogicFlow + const logicflow = new LogicFlow({ + container: containerRef.current, + grid: true, + plugins: [DndPanel, SelectionSelect, Control] + }); + + // 注册自定义节点 + registerNodes(logicflow); + + // 注册事件 + logicflow.on('node:click', ({ data }) => { + setSelectedNode(data); + }); + + logicflow.on('blank:click', () => { + setSelectedNode(null); + }); + + // 如果有初始值,渲染流程图 + if (value) { + try { + const graphData = JSON.parse(value); + logicflow.render(graphData); + } catch (error) { + console.error('Failed to parse graph data:', error); + } + } + + setLf(logicflow); + + return () => { + logicflow.destroy(); + }; + } + }, []); + + // 处理节点配置更新 + const handleNodeConfigSave = (config: any) => { + if (lf && selectedNode) { + lf.setProperties(selectedNode.id, config); + setSelectedNode(null); + + // 触发 onChange + const graphData = lf.getGraphData(); + onChange?.(JSON.stringify(graphData)); + } + }; + + return ( +
+
+ {selectedNode && ( +
+ setSelectedNode(null)} + /> +
+ )} +
+ ); +}; + +export default FlowDesigner; \ No newline at end of file diff --git a/frontend/src/pages/Workflow/Definition/Edit/components/FlowDesigner/nodes.ts b/frontend/src/pages/Workflow/Definition/Edit/components/FlowDesigner/nodes.ts new file mode 100644 index 00000000..7f8133ee --- /dev/null +++ b/frontend/src/pages/Workflow/Definition/Edit/components/FlowDesigner/nodes.ts @@ -0,0 +1,81 @@ +import LogicFlow from '@logicflow/core'; +import { CircleNode, RectNode, DiamondNode } from '@logicflow/core'; + +export function registerNodes(lf: LogicFlow) { + // 开始节点 + class StartNode extends CircleNode { + static extendKey = 'StartNode'; + getNodeStyle() { + const style = super.getNodeStyle(); + return { + ...style, + fill: '#C6E5FF', + stroke: '#1890FF' + }; + } + } + + // 任务节点 + class TaskNode extends RectNode { + static extendKey = 'TaskNode'; + getNodeStyle() { + const style = super.getNodeStyle(); + return { + ...style, + fill: '#FFF', + stroke: '#1890FF' + }; + } + } + + // 网关节点 + class GatewayNode extends DiamondNode { + static extendKey = 'GatewayNode'; + getNodeStyle() { + const style = super.getNodeStyle(); + return { + ...style, + fill: '#FFF', + stroke: '#1890FF' + }; + } + } + + // 结束节点 + class EndNode extends CircleNode { + static extendKey = 'EndNode'; + getNodeStyle() { + const style = super.getNodeStyle(); + return { + ...style, + fill: '#FFE7E7', + stroke: '#FF4D4F' + }; + } + } + + // 注册节点 + lf.register({ + type: 'start', + view: StartNode, + model: StartNode + }); + + lf.register({ + type: 'task', + view: TaskNode, + model: TaskNode + }); + + lf.register({ + type: 'gateway', + view: GatewayNode, + model: GatewayNode + }); + + lf.register({ + type: 'end', + view: EndNode, + model: EndNode + }); +} \ No newline at end of file diff --git a/frontend/src/pages/Workflow/Definition/Edit/components/FormDesigner/index.tsx b/frontend/src/pages/Workflow/Definition/Edit/components/FormDesigner/index.tsx new file mode 100644 index 00000000..fe798f4b --- /dev/null +++ b/frontend/src/pages/Workflow/Definition/Edit/components/FormDesigner/index.tsx @@ -0,0 +1,57 @@ +import React, { useState } from 'react'; +import { Card, Tabs } from 'antd'; +import type { TabsProps } from 'antd'; +import FormRender from 'form-render'; + +interface FormDesignerProps { + value?: string; + onChange?: (value: string) => void; + readOnly?: boolean; +} + +const defaultSchema = { + type: 'object', + properties: { + input1: { + title: '输入框', + type: 'string' + } + } +}; + +const FormDesigner: React.FC = ({ + value, + onChange, + readOnly = false +}) => { + const [schema, setSchema] = useState(() => { + try { + return value ? JSON.parse(value) : defaultSchema; + } catch (error) { + return defaultSchema; + } + }); + + const handleSubmit = (formData: any) => { + console.log('Form data:', formData); + }; + + const handleValidate = (errors: any) => { + console.log('Form errors:', errors); + }; + + return ( +
+ + + +
+ ); +}; + +export default FormDesigner; \ No newline at end of file diff --git a/frontend/src/pages/Workflow/Definition/Edit/components/NodeConfig/index.tsx b/frontend/src/pages/Workflow/Definition/Edit/components/NodeConfig/index.tsx new file mode 100644 index 00000000..6f1fd779 --- /dev/null +++ b/frontend/src/pages/Workflow/Definition/Edit/components/NodeConfig/index.tsx @@ -0,0 +1,90 @@ +import React from 'react'; +import { Form, Input, Button, Space } from 'antd'; +import type { BaseNodeModel } from '@logicflow/core'; + +type NodeType = 'start' | 'end' | 'task' | 'gateway'; + +interface NodeConfigProps { + node: BaseNodeModel & { type: NodeType }; + onSave: (config: any) => void; + onCancel: () => void; +} + +const NodeConfig: React.FC = ({ + node, + onSave, + onCancel +}) => { + const [form] = Form.useForm(); + + const handleFinish = (values: any) => { + onSave({ + ...values, + text: { value: values.text, x: node.text?.x, y: node.text?.y } + }); + }; + + return ( +
+
+ + + + + {node.type === 'task' && ( + <> + + + + + + + + )} + + {node.type === 'gateway' && ( + + + + )} + + + + + + + +
+
+ ); +}; + +export default NodeConfig; \ No newline at end of file diff --git a/frontend/src/pages/Workflow/Definition/Edit/index.tsx b/frontend/src/pages/Workflow/Definition/Edit/index.tsx new file mode 100644 index 00000000..898549f2 --- /dev/null +++ b/frontend/src/pages/Workflow/Definition/Edit/index.tsx @@ -0,0 +1,131 @@ +import React, { useEffect, useState } from 'react'; +import { Card, Tabs, Button, Form, Input, message } from 'antd'; +import type { TabsProps } from 'antd'; +import { useNavigate, useParams } from 'react-router-dom'; +import FlowDesigner from './components/FlowDesigner'; +import FormDesigner from './components/FormDesigner'; +import { getDefinition, createDefinition, updateDefinition } from '../../service'; +import type { WorkflowDefinitionRequest } from '../../types'; + +const Edit: React.FC = () => { + const navigate = useNavigate(); + const { id } = useParams<{ id: string }>(); + const [form] = Form.useForm(); + const [loading, setLoading] = useState(false); + const [graphData, setGraphData] = useState(''); + const [formData, setFormData] = useState(''); + + useEffect(() => { + if (id) { + setLoading(true); + getDefinition(parseInt(id)) + .then(data => { + form.setFieldsValue({ + name: data.name, + code: data.code, + description: data.description + }); + setGraphData(data.graphDefinition || ''); + setFormData(data.formDefinition || ''); + }) + .finally(() => { + setLoading(false); + }); + } + }, [id, form]); + + const handleSave = async () => { + try { + const values = await form.validateFields(); + const data: WorkflowDefinitionRequest = { + ...values, + graphDefinition: graphData, + formDefinition: formData, + nodeConfig: '{}', + transitionConfig: '{}', + enabled: true + }; + + if (id) { + await updateDefinition(parseInt(id), data); + } else { + await createDefinition(data); + } + + message.success('保存成功'); + navigate('/workflow/definition'); + } catch (error) { + console.error('Save failed:', error); + } + }; + + const items: TabsProps['items'] = [ + { + key: 'basic', + label: '基本信息', + children: ( +
+ + + + + + + + + +
+ ) + }, + { + key: 'flow', + label: '流程设计', + children: ( + + ) + }, + { + key: 'form', + label: '表单设计', + children: ( + + ) + } + ]; + + return ( + + 保存 + + } + > + + + ); +}; + +export default Edit; \ No newline at end of file diff --git a/frontend/src/pages/Workflow/Definition/index.tsx b/frontend/src/pages/Workflow/Definition/index.tsx new file mode 100644 index 00000000..e2544a68 --- /dev/null +++ b/frontend/src/pages/Workflow/Definition/index.tsx @@ -0,0 +1,165 @@ +import React from 'react'; +import { Card, Table, Button, Space, Tag, Popconfirm, message } from 'antd'; +import { useNavigate } from 'react-router-dom'; +import type { ColumnsType } from 'antd/es/table'; +import { useTableData } from '@/hooks/useTableData'; +import { getDefinitions, deleteDefinition, publishDefinition, disableDefinition, enableDefinition } from '../service'; +import { WorkflowDefinitionResponse, WorkflowStatus } from '../types'; + +const Definition: React.FC = () => { + const navigate = useNavigate(); + const { list, loading, pagination, handleTableChange, refresh } = useTableData({ + service: { + list: getDefinitions + } + }); + + const handlePublish = async (id: number) => { + try { + await publishDefinition(id); + message.success('发布成功'); + refresh(); + } catch (error) { + message.error('发布失败'); + } + }; + + const handleDisable = async (id: number) => { + try { + await disableDefinition(id); + message.success('禁用成功'); + refresh(); + } catch (error) { + message.error('禁用失败'); + } + }; + + const handleEnable = async (id: number) => { + try { + await enableDefinition(id); + message.success('启用成功'); + refresh(); + } catch (error) { + message.error('启用失败'); + } + }; + + const handleDelete = async (id: number) => { + try { + await deleteDefinition(id); + message.success('删除成功'); + refresh(); + } catch (error) { + message.error('删除失败'); + } + }; + + const columns: ColumnsType = [ + { + title: '流程名称', + dataIndex: 'name', + width: 200, + }, + { + title: '流程编码', + dataIndex: 'code', + width: 200, + }, + { + title: '状态', + dataIndex: 'status', + width: 120, + render: (status: WorkflowStatus) => { + const statusMap = { + 'DRAFT': { text: '草稿', color: 'default' }, + 'PUBLISHED': { text: '已发布', color: 'success' }, + 'DISABLED': { text: '已禁用', color: 'error' }, + }; + const { text, color } = statusMap[status]; + return {text}; + }, + }, + { + title: '描述', + dataIndex: 'description', + ellipsis: true, + }, + { + title: '创建时间', + dataIndex: 'createTime', + width: 180, + }, + { + title: '操作', + width: 280, + render: (_, record) => ( + + + {record.status === 'DRAFT' && ( + + )} + {record.status === 'PUBLISHED' && ( + + )} + {record.status === 'DISABLED' && ( + + )} + handleDelete(record.id)} + > + + + + ), + }, + ]; + + return ( + navigate('/workflow/definition/edit')} + > + 新建流程 + + } + > + + + ); +}; + +export default Definition; \ No newline at end of file diff --git a/frontend/src/pages/Workflow/Instance/index.tsx b/frontend/src/pages/Workflow/Instance/index.tsx new file mode 100644 index 00000000..a68ab2f5 --- /dev/null +++ b/frontend/src/pages/Workflow/Instance/index.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import { Card } from 'antd'; + +const WorkflowInstance: React.FC = () => { + return ( + +
工作流实例管理(开发中)
+
+ ); +}; + +export default WorkflowInstance; \ No newline at end of file diff --git a/frontend/src/pages/Workflow/Monitor/index.tsx b/frontend/src/pages/Workflow/Monitor/index.tsx new file mode 100644 index 00000000..3f301aaf --- /dev/null +++ b/frontend/src/pages/Workflow/Monitor/index.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import { Card } from 'antd'; + +const WorkflowMonitor: React.FC = () => { + return ( + +
工作流监控大盘(开发中)
+
+ ); +}; + +export default WorkflowMonitor; \ No newline at end of file diff --git a/frontend/src/pages/Workflow/service.ts b/frontend/src/pages/Workflow/service.ts new file mode 100644 index 00000000..dbc567ab --- /dev/null +++ b/frontend/src/pages/Workflow/service.ts @@ -0,0 +1,97 @@ +import request from '@/utils/request'; +import type { Page } from '@/types/base/page'; +import type { BaseResponse } from '@/types/base/response'; +import type { + WorkflowDefinitionResponse, + WorkflowDefinitionRequest, + WorkflowDefinitionQuery, + WorkflowInstanceResponse, + WorkflowInstanceQuery, + NodeInstanceResponse, + WorkflowLogResponse, + WorkflowDefinition +} from './types'; + +const DEFINITION_URL = '/api/v1/workflow-definitions'; +const INSTANCE_URL = '/api/v1/workflow-instance'; +const NODE_URL = '/api/v1/node-instance'; +const LOG_URL = '/api/v1/workflow-logs'; + +// 工作流定义相关接口 +export const getDefinitions = (params?: WorkflowDefinitionQuery) => + request.get>(`${DEFINITION_URL}`, { params }); + +export const getDefinition = (id: number) => + request.get(`${DEFINITION_URL}/${id}`); + +export const createDefinition = (data: WorkflowDefinitionRequest) => + request.post(DEFINITION_URL, data); + +export const updateDefinition = (id: number, data: WorkflowDefinitionRequest) => + request.put(`${DEFINITION_URL}/${id}`, data); + +export const deleteDefinition = (id: number) => + request.delete(`${DEFINITION_URL}/${id}`); + +export const publishDefinition = (id: number) => + request.post(`${DEFINITION_URL}/${id}/publish`); + +export const disableDefinition = (id: number) => + request.post(`${DEFINITION_URL}/${id}/disable`); + +export const enableDefinition = (id: number) => + request.post(`${DEFINITION_URL}/${id}/enable`); + +// 工作流实例相关接口 +export const getInstances = (params?: WorkflowInstanceQuery) => + request.get>(`${INSTANCE_URL}`, { params }); + +export const getInstance = (id: number) => + request.get(`${INSTANCE_URL}/${id}`); + +export const createInstance = (definitionId: number, businessKey: string, variables?: Record) => + request.post(INSTANCE_URL, { definitionId, businessKey, variables }); + +export const startInstance = (id: number) => + request.post(`${INSTANCE_URL}/${id}/start`); + +export const cancelInstance = (id: number) => + request.post(`${INSTANCE_URL}/${id}/cancel`); + +export const pauseInstance = (id: number) => + request.post(`${INSTANCE_URL}/${id}/pause`); + +export const resumeInstance = (id: number) => + request.post(`${INSTANCE_URL}/${id}/resume`); + +// 节点实例相关接口 +export const getNodeInstances = (workflowInstanceId: number) => + request.get(`${NODE_URL}/workflow/${workflowInstanceId}`); + +export const getNodeInstancesByStatus = (workflowInstanceId: number, status: string) => + request.get(`${NODE_URL}/workflow/${workflowInstanceId}/status/${status}`); + +export const updateNodeStatus = (id: number, status: string) => + request.put(`${NODE_URL}/${id}/status`, { status }); + +// 日志相关接口 +export const getWorkflowLogs = (workflowInstanceId: number) => + request.get(`${LOG_URL}/workflow/${workflowInstanceId}`); + +export const getNodeLogs = (workflowInstanceId: number, nodeId: string) => + request.get(`${LOG_URL}/node/${workflowInstanceId}/${nodeId}`); + +export const recordLog = (data: Omit) => + request.post(`${LOG_URL}/record`, data); + +// 获取工作流定义列表 +export const getDefinitionList = (params: WorkflowDefinitionQuery) => + request.get('/api/v1/workflow/definition', { params }); + +// 保存工作流定义 +export const saveDefinition = (data: WorkflowDefinition) => { + if (data.id) { + return request.put(`/api/v1/workflow/definition/${data.id}`, data); + } + return request.post('/api/v1/workflow/definition', data); +}; \ No newline at end of file diff --git a/frontend/src/pages/Workflow/types.ts b/frontend/src/pages/Workflow/types.ts new file mode 100644 index 00000000..68c889f9 --- /dev/null +++ b/frontend/src/pages/Workflow/types.ts @@ -0,0 +1,146 @@ +import { BaseResponse } from '@/types/base/response'; +import { BaseQuery } from '@/types/base/query'; + +// 工作流状态枚举 +export enum WorkflowStatus { + DRAFT = 'DRAFT', + PUBLISHED = 'PUBLISHED', + DISABLED = 'DISABLED' +} + +// 节点类型枚举 +export enum NodeType { + START = 'START', + END = 'END', + TASK = 'TASK', + GATEWAY = 'GATEWAY', + SUB_PROCESS = 'SUB_PROCESS', + SHELL = 'SHELL' +} + +// 节点状态枚举 +export enum NodeStatus { + PENDING = 'PENDING', + RUNNING = 'RUNNING', + COMPLETED = 'COMPLETED', + FAILED = 'FAILED', + CANCELLED = 'CANCELLED', + PAUSED = 'PAUSED', + SKIPPED = 'SKIPPED' +} + +// 工作流实例状态枚举 +export enum InstanceStatus { + RUNNING = 'RUNNING', + COMPLETED = 'COMPLETED', + FAILED = 'FAILED', + CANCELLED = 'CANCELLED', + PAUSED = 'PAUSED' +} + +// 日志级别枚举 +export enum LogLevel { + INFO = 'INFO', + WARN = 'WARN', + ERROR = 'ERROR' +} + +// 工作流定义基础接口 +export interface WorkflowDefinition { + id?: number; + name: string; + code: string; + description?: string; + nodeConfig: string; + transitionConfig: string; + formDefinition: string; + graphDefinition: string; + enabled: boolean; + remark?: string; + status?: WorkflowStatus; + createTime?: string; + updateTime?: string; +} + +// 工作流定义响应数据 +export interface WorkflowDefinitionResponse extends BaseResponse, Omit { + id: number; + status: WorkflowStatus; + createTime: string; + updateTime: string; +} + +// 工作流定义查询参数 +export interface WorkflowDefinitionQuery extends BaseQuery { + name?: string; + code?: string; + status?: WorkflowStatus; +} + +// 工作流定义请求数据 +export interface WorkflowDefinitionRequest { + code: string; + name: string; + description?: string; + nodeConfig: string; + transitionConfig: string; + formDefinition: string; + graphDefinition: string; + enabled: boolean; + remark?: string; +} + +// 节点定义响应数据 +export interface NodeDefinitionResponse extends BaseResponse { + nodeId: string; + name: string; + type: NodeType; + config: string; + description?: string; + workflowDefinitionId: number; + orderNum: number; +} + +// 工作流实例查询参数 +export interface WorkflowInstanceQuery extends BaseQuery { + definitionId?: number; + status?: InstanceStatus; + startTime?: string; + endTime?: string; +} + +// 工作流实例响应数据 +export interface WorkflowInstanceResponse extends BaseResponse { + definitionId: number; + businessKey: string; + status: InstanceStatus; + startTime: string; + endTime?: string; + variables: string; + error?: string; +} + +// 节点实例响应数据 +export interface NodeInstanceResponse extends BaseResponse { + workflowInstanceId: number; + nodeId: string; + nodeType: NodeType; + name: string; + status: NodeStatus; + startTime: string; + endTime?: string; + config: string; + input: string; + output: string; + error?: string; + preNodeId: string; +} + +// 工作流日志响应数据 +export interface WorkflowLogResponse extends BaseResponse { + workflowInstanceId: number; + nodeId: string; + message: string; + level: LogLevel; + detail?: string; +} \ No newline at end of file diff --git a/frontend/src/router/index.tsx b/frontend/src/router/index.tsx index 313c86c1..7a03726a 100644 --- a/frontend/src/router/index.tsx +++ b/frontend/src/router/index.tsx @@ -6,7 +6,7 @@ import BasicLayout from '../layouts/BasicLayout'; import {useSelector} from 'react-redux'; import {RootState} from '../store'; -// 加载中组件 +// 加中组件 const LoadingComponent = () => (
@@ -32,9 +32,17 @@ const Menu = lazy(() => import('../pages/System/Menu')); const Department = lazy(() => import('../pages/System/Department')); const External = lazy(() => import('../pages/System/External')); const X6Test = lazy(() => import('../pages/X6Test')); +const WorkflowDefinition = lazy(() => import('../pages/Workflow/Definition')); +const WorkflowDefinitionEdit = lazy(() => import('../pages/Workflow/Definition/Edit')); +const WorkflowInstance = lazy(() => import('../pages/Workflow/Instance')); +const WorkflowMonitor = lazy(() => import('../pages/Workflow/Monitor')); // 创建路由 const router = createBrowserRouter([ + { + path: '/login', + element: + }, { path: '/', element: ( @@ -108,15 +116,53 @@ const router = createBrowserRouter([ ) }, + { + path: 'workflow', + children: [ + { + path: 'definition', + children: [ + { + path: '', + element: ( + }> + + + ) + }, + { + path: 'edit/:id?', + element: ( + }> + + + ) + } + ] + }, + { + path: 'instance', + element: ( + }> + + + ) + }, + { + path: 'monitor', + element: ( + }> + + + ) + } + ] + }, { path: '*', element: } ] - }, - { - path: '/login', - element: } ]);