├── doc ├── images │ ├── Lombok.png │ ├── flow.png │ ├── leave_flow.jpg │ ├── task_list.jpg │ ├── task_approve.jpg │ ├── flow_definition.jpg │ └── flow_definition_v2.jpg ├── deploy │ └── deploy_windows.md ├── sql │ └── data.sql └── wiki │ └── swagger2.md ├── easyflow-html ├── src │ ├── pages │ │ ├── flow │ │ │ ├── components │ │ │ │ ├── QueryFilter │ │ │ │ │ ├── index.less │ │ │ │ │ └── index.jsx │ │ │ │ ├── OpinionForm │ │ │ │ │ └── index.jsx │ │ │ │ └── ChangeNodeForm │ │ │ │ │ └── index.jsx │ │ │ ├── ApplyForm │ │ │ │ ├── index.jsx │ │ │ │ └── Leave.jsx │ │ │ └── MyTask.jsx │ │ ├── leave │ │ │ ├── components │ │ │ │ ├── QueryFilter │ │ │ │ │ ├── index.less │ │ │ │ │ └── index.jsx │ │ │ │ └── ApplyForm │ │ │ │ │ └── index.jsx │ │ │ └── ApplyList.jsx │ │ ├── Welcome.less │ │ ├── admin │ │ │ ├── xflow │ │ │ │ ├── react-node │ │ │ │ │ ├── node1.jsx │ │ │ │ │ ├── node1.less │ │ │ │ │ ├── node2.less │ │ │ │ │ └── node2.jsx │ │ │ │ ├── react-edge │ │ │ │ │ ├── edge1.jsx │ │ │ │ │ └── edge1.less │ │ │ │ ├── config-graph.jsx │ │ │ │ └── config-menu.js │ │ │ ├── components │ │ │ │ ├── DefinitionForm │ │ │ │ │ └── index.jsx │ │ │ │ └── DefinitionNodeForm │ │ │ │ │ └── index.jsx │ │ │ ├── index.less │ │ │ └── DefinitionList.jsx │ │ ├── 404.jsx │ │ ├── Welcome.jsx │ │ └── user │ │ │ └── Login │ │ │ └── index.less │ ├── locales │ │ ├── zh-CN │ │ │ ├── component.js │ │ │ ├── pwa.js │ │ │ ├── menu.js │ │ │ ├── globalHeader.js │ │ │ ├── settingDrawer.js │ │ │ └── settings.js │ │ └── zh-CN.js │ ├── access.js │ ├── components │ │ ├── HeaderDropdown │ │ │ ├── index.jsx │ │ │ └── index.less │ │ ├── InstanceStatus │ │ │ └── index.jsx │ │ ├── Footer │ │ │ └── index.jsx │ │ ├── HeaderSearch │ │ │ ├── index.less │ │ │ └── index.jsx │ │ ├── NodeStatus │ │ │ └── index.jsx │ │ ├── NoticeIcon │ │ │ ├── index.less │ │ │ ├── NoticeList.less │ │ │ └── NoticeList.jsx │ │ └── RightContent │ │ │ ├── index.jsx │ │ │ ├── index.less │ │ │ └── AvatarDropdown.jsx │ ├── utils │ │ ├── request.js │ │ ├── consts.js │ │ ├── errorHandler.js │ │ └── basic.js │ ├── services │ │ ├── leave.js │ │ ├── flow.js │ │ ├── api.js │ │ └── admin.js │ ├── manifest.json │ ├── models │ │ ├── admin.js │ │ ├── leave.js │ │ └── flow.js │ ├── global.less │ ├── e2e │ │ └── baseLayout.e2e.js │ ├── service-worker.js │ └── app.jsx ├── .eslintignore ├── .prettierrc.js ├── .stylelintrc.js ├── .vscode │ ├── settings.json │ └── extensions.json ├── mock │ ├── route.js │ └── user.js ├── .eslintrc.js ├── tests │ ├── setupTests.js │ ├── PuppeteerEnvironment.js │ ├── getBrowser.js │ ├── beforeTest.js │ └── run-tests.js ├── jsconfig.json ├── .editorconfig ├── jest.config.js ├── .prettierignore ├── config │ ├── defaultSettings.js │ ├── config.dev.js │ ├── proxy.js │ ├── routes.js │ └── config.js ├── README.md ├── .gitignore └── LICENSE ├── src ├── main │ ├── resources │ │ ├── mapper │ │ │ ├── sys │ │ │ │ └── SysUserMapper.xml │ │ │ ├── core │ │ │ │ ├── FlowVariableMapper.xml │ │ │ │ ├── FlowDefinitionMapper.xml │ │ │ │ ├── FlowDefinitonNodeMapper.xml │ │ │ │ ├── FlowInstanceNodeMapper.xml │ │ │ │ ├── FlowDefinitionNodeMapper.xml │ │ │ │ └── FlowInstanceMapper.xml │ │ │ └── leave │ │ │ │ ├── LeaveEmployeeMapper.xml │ │ │ │ └── LeaveApplyMapper.xml │ │ ├── banner.txt │ │ ├── application.yml │ │ ├── application-prod.yml │ │ ├── ehcache.xml │ │ ├── application-test.yml │ │ └── logback-spring.xml │ └── java │ │ └── org │ │ └── lecoder │ │ └── easyflow │ │ ├── modules │ │ ├── core │ │ │ ├── toolkit │ │ │ │ ├── FlowActionHelper.java │ │ │ │ └── VariableTypeHelper.java │ │ │ ├── dto │ │ │ │ ├── ApproveFormDTO.java │ │ │ │ ├── NodeUserDTO.java │ │ │ │ ├── TaskFilterDTO.java │ │ │ │ ├── DefinitionFormDTO.java │ │ │ │ ├── NodeTaskDTO.java │ │ │ │ ├── DefinitionNodeFormDTO.java │ │ │ │ └── TaskInstanceDTO.java │ │ │ ├── mapper │ │ │ │ ├── FlowVariableMapper.java │ │ │ │ ├── FlowDefinitionMapper.java │ │ │ │ ├── FlowInstanceNodeMapper.java │ │ │ │ ├── FlowDefinitionNodeMapper.java │ │ │ │ └── FlowInstanceMapper.java │ │ │ ├── service │ │ │ │ ├── IFlowVariableService.java │ │ │ │ ├── impl │ │ │ │ │ ├── FlowVariableServiceImpl.java │ │ │ │ │ ├── FlowInstanceServiceImpl.java │ │ │ │ │ └── FlowDefinitionServiceImpl.java │ │ │ │ ├── IFlowInstanceService.java │ │ │ │ ├── IFlowBaseService.java │ │ │ │ ├── IFlowInstanceNodeService.java │ │ │ │ ├── IFlowDefinitionService.java │ │ │ │ ├── IFlowApiService.java │ │ │ │ ├── IFlowDefinitionNodeService.java │ │ │ │ └── IFlowService.java │ │ │ ├── enums │ │ │ │ ├── FlowActionEnum.java │ │ │ │ ├── NodeStatusEnum.java │ │ │ │ ├── InstanceStatusEnum.java │ │ │ │ └── FlowModuleEnum.java │ │ │ ├── vo │ │ │ │ ├── NodeRelClassVO.java │ │ │ │ ├── DefinitionDetailVO.java │ │ │ │ └── FlowDetailVO.java │ │ │ ├── annotation │ │ │ │ └── Node.java │ │ │ ├── node │ │ │ │ ├── BaseNode.java │ │ │ │ ├── HrManager.java │ │ │ │ ├── DeptManager.java │ │ │ │ └── GeneralManager.java │ │ │ └── entity │ │ │ │ ├── FlowDefinition.java │ │ │ │ ├── FlowVariable.java │ │ │ │ ├── FlowDefinitionNode.java │ │ │ │ ├── FlowInstance.java │ │ │ │ └── FlowInstanceNode.java │ │ ├── sys │ │ │ ├── constant │ │ │ │ └── CacheConsts.java │ │ │ ├── vo │ │ │ │ └── SearchUserVO.java │ │ │ ├── mapper │ │ │ │ └── SysUserMapper.java │ │ │ ├── dto │ │ │ │ ├── UserDTO.java │ │ │ │ ├── LoginUserDTO.java │ │ │ │ └── LoginFormDTO.java │ │ │ ├── entity │ │ │ │ └── SysUser.java │ │ │ ├── service │ │ │ │ ├── ISysUserService.java │ │ │ │ └── impl │ │ │ │ │ └── SysUserServiceImpl.java │ │ │ ├── config │ │ │ │ └── InterceptorConfig.java │ │ │ └── interceptor │ │ │ │ └── LoginInterceptor.java │ │ └── leave │ │ │ ├── service │ │ │ ├── ILeaveApplyService.java │ │ │ ├── ILeaveEmployeeService.java │ │ │ ├── impl │ │ │ │ ├── LeaveEmployeeServiceImpl.java │ │ │ │ └── LeaveApplyServiceImpl.java │ │ │ └── ILeaveService.java │ │ │ ├── mapper │ │ │ ├── LeaveEmployeeMapper.java │ │ │ └── LeaveApplyMapper.java │ │ │ ├── dto │ │ │ ├── QueryFilterDTO.java │ │ │ ├── LeaveFormDTO.java │ │ │ └── ApplyListDTO.java │ │ │ ├── entity │ │ │ ├── LeaveEmployee.java │ │ │ └── LeaveApply.java │ │ │ ├── handler │ │ │ ├── ILeaveTypeHandler.java │ │ │ ├── MaternityLeaveHandler.java │ │ │ └── AnnualLeaveHandler.java │ │ │ ├── enums │ │ │ └── LeaveTypeEnum.java │ │ │ └── controller │ │ │ └── LeaveController.java │ │ ├── common │ │ ├── exception │ │ │ ├── FlowException.java │ │ │ └── MyExceptionHandler.java │ │ ├── toolkit │ │ │ ├── Constants.java │ │ │ ├── NetworkUtils.java │ │ │ ├── RequestHolder.java │ │ │ └── SpringContextHolder.java │ │ ├── config │ │ │ ├── CacheConfig.java │ │ │ ├── ScriptEngineConfig.java │ │ │ ├── MybatisPlusConfig.java │ │ │ ├── WebConfig.java │ │ │ └── SwaggerConfig.java │ │ ├── entity │ │ │ └── BaseEntity.java │ │ ├── constraint │ │ │ ├── Contains.java │ │ │ └── ContainsValidator.java │ │ └── web │ │ │ └── CommonResult.java │ │ └── EasyflowApplication.java └── test │ └── java │ └── org │ └── lecoder │ └── easyflow │ └── core │ ├── FlowDefinitionMapperTest.java │ └── FlowApiServiceTest.java ├── LICENSE └── README.md /doc/images/Lombok.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lijile/easyflow/HEAD/doc/images/Lombok.png -------------------------------------------------------------------------------- /doc/images/flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lijile/easyflow/HEAD/doc/images/flow.png -------------------------------------------------------------------------------- /doc/images/leave_flow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lijile/easyflow/HEAD/doc/images/leave_flow.jpg -------------------------------------------------------------------------------- /doc/images/task_list.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lijile/easyflow/HEAD/doc/images/task_list.jpg -------------------------------------------------------------------------------- /doc/images/task_approve.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lijile/easyflow/HEAD/doc/images/task_approve.jpg -------------------------------------------------------------------------------- /easyflow-html/src/pages/flow/components/QueryFilter/index.less: -------------------------------------------------------------------------------- 1 | .formitem { 2 | min-width: 200px; 3 | } 4 | -------------------------------------------------------------------------------- /doc/images/flow_definition.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lijile/easyflow/HEAD/doc/images/flow_definition.jpg -------------------------------------------------------------------------------- /easyflow-html/.eslintignore: -------------------------------------------------------------------------------- 1 | /lambda/ 2 | /scripts 3 | /config 4 | .history 5 | public 6 | dist 7 | .umi 8 | mock -------------------------------------------------------------------------------- /easyflow-html/src/pages/leave/components/QueryFilter/index.less: -------------------------------------------------------------------------------- 1 | .formitem { 2 | min-width: 200px; 3 | } 4 | -------------------------------------------------------------------------------- /doc/images/flow_definition_v2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lijile/easyflow/HEAD/doc/images/flow_definition_v2.jpg -------------------------------------------------------------------------------- /easyflow-html/.prettierrc.js: -------------------------------------------------------------------------------- 1 | const fabric = require('@umijs/fabric'); 2 | 3 | module.exports = { 4 | ...fabric.prettier, 5 | }; 6 | -------------------------------------------------------------------------------- /easyflow-html/.stylelintrc.js: -------------------------------------------------------------------------------- 1 | const fabric = require('@umijs/fabric'); 2 | 3 | module.exports = { 4 | ...fabric.stylelint, 5 | }; 6 | -------------------------------------------------------------------------------- /easyflow-html/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "prettier.requireConfig": true, 4 | "editor.defaultFormatter": "esbenp.prettier-vscode" 5 | } 6 | -------------------------------------------------------------------------------- /easyflow-html/mock/route.js: -------------------------------------------------------------------------------- 1 | export default { 2 | '/api/auth_routes': { 3 | '/form/advanced-form': { 4 | authority: ['admin', 'user'], 5 | }, 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /easyflow-html/src/locales/zh-CN/component.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'component.tagSelect.expand': '展开', 3 | 'component.tagSelect.collapse': '收起', 4 | 'component.tagSelect.all': '全部', 5 | }; 6 | -------------------------------------------------------------------------------- /easyflow-html/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "esbenp.prettier-vscode", 4 | "dbaeumer.vscode-eslint", 5 | "stylelint.vscode-stylelint", 6 | "wangzy.sneak-mark" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /easyflow-html/src/pages/Welcome.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .pre { 4 | margin: 12px 0; 5 | padding: 12px 20px; 6 | background: @input-bg; 7 | box-shadow: @card-shadow; 8 | } 9 | -------------------------------------------------------------------------------- /easyflow-html/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [require.resolve('@umijs/fabric/dist/eslint')], 3 | globals: { 4 | ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION: true, 5 | page: true, 6 | REACT_APP_ENV: true, 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /easyflow-html/src/locales/zh-CN/pwa.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'app.pwa.offline': '当前处于离线状态', 3 | 'app.pwa.serviceworker.updated': '有新内容', 4 | 'app.pwa.serviceworker.updated.hint': '请点击“刷新”按钮或者手动刷新页面', 5 | 'app.pwa.serviceworker.updated.ok': '刷新', 6 | }; 7 | -------------------------------------------------------------------------------- /easyflow-html/tests/setupTests.js: -------------------------------------------------------------------------------- 1 | // do some test init 2 | 3 | const localStorageMock = { 4 | getItem: jest.fn(), 5 | setItem: jest.fn(), 6 | removeItem: jest.fn(), 7 | clear: jest.fn(), 8 | }; 9 | 10 | global.localStorage = localStorageMock; 11 | -------------------------------------------------------------------------------- /easyflow-html/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "jsx": "react-jsx", 4 | "emitDecoratorMetadata": true, 5 | "experimentalDecorators": true, 6 | "baseUrl": ".", 7 | "paths": { 8 | "@/*": ["./src/*"] 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/resources/mapper/sys/SysUserMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /easyflow-html/src/access.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @see https://umijs.org/zh-CN/plugins/plugin-access 3 | * */ 4 | export default function access(initialState) { 5 | const { currentUser } = initialState || {}; 6 | return { 7 | canAdmin: currentUser && currentUser.access === 'admin', 8 | }; 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/core/toolkit/FlowActionHelper.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.core.toolkit; 2 | 3 | /** 4 | * 流程操作权限工具类 5 | * 6 | * @author: lijile 7 | * @date: 2021/11/2 14:16 8 | * @version: 1.0 9 | */ 10 | public class FlowActionHelper { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/resources/mapper/core/FlowVariableMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /easyflow-html/src/pages/admin/xflow/react-node/node1.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './node1.less'; 3 | 4 | const Node1 = ({ data }) => { 5 | return ( 6 |
7 |
{data.info.text}
8 |
9 | ); 10 | }; 11 | export default Node1; 12 | -------------------------------------------------------------------------------- /src/main/resources/mapper/core/FlowDefinitionMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/main/resources/mapper/leave/LeaveEmployeeMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/main/resources/mapper/core/FlowDefinitonNodeMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/main/resources/mapper/core/FlowInstanceNodeMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/main/resources/mapper/core/FlowDefinitionNodeMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /easyflow-html/src/pages/admin/xflow/react-node/node1.less: -------------------------------------------------------------------------------- 1 | .node1-container { 2 | display: flex; 3 | align-items: center; 4 | justify-content: center; 5 | width: 100%; 6 | height: 100%; 7 | font-weight: 600; 8 | background-color: #fff; 9 | border: 1px solid #873bf4; 10 | border-radius: 4px; 11 | } 12 | -------------------------------------------------------------------------------- /easyflow-html/src/pages/admin/xflow/react-node/node2.less: -------------------------------------------------------------------------------- 1 | .node2-container { 2 | display: flex; 3 | align-items: center; 4 | justify-content: center; 5 | width: 100%; 6 | height: 100%; 7 | font-weight: 600; 8 | background-color: #fff; 9 | border: 1px solid #dd4a68; 10 | border-radius: 4px; 11 | } 12 | -------------------------------------------------------------------------------- /easyflow-html/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | 15 | [Makefile] 16 | indent_style = tab 17 | -------------------------------------------------------------------------------- /easyflow-html/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testURL: 'http://localhost:8000', 3 | testEnvironment: './tests/PuppeteerEnvironment', 4 | verbose: false, 5 | extraSetupFiles: ['./tests/setupTests.js'], 6 | globals: { 7 | ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION: false, 8 | localStorage: null, 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /easyflow-html/.prettierignore: -------------------------------------------------------------------------------- 1 | **/*.svg 2 | package.json 3 | .umi 4 | .umi-production 5 | /dist 6 | .dockerignore 7 | .DS_Store 8 | .eslintignore 9 | *.png 10 | *.toml 11 | docker 12 | .editorconfig 13 | Dockerfile* 14 | .gitignore 15 | .prettierignore 16 | LICENSE 17 | .eslintcache 18 | *.lock 19 | yarn-error.log 20 | .history 21 | CNAME 22 | /build 23 | /public -------------------------------------------------------------------------------- /easyflow-html/config/defaultSettings.js: -------------------------------------------------------------------------------- 1 | const Settings = { 2 | navTheme: 'light', 3 | // 拂晓蓝 4 | primaryColor: '#1890ff', 5 | layout: 'mix', 6 | contentWidth: 'Fluid', 7 | fixedHeader: false, 8 | fixSiderbar: true, 9 | colorWeak: false, 10 | title: 'easyflow', 11 | pwa: false, 12 | iconfontUrl: '', 13 | }; 14 | export default Settings; 15 | -------------------------------------------------------------------------------- /easyflow-html/src/pages/admin/xflow/react-node/node2.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useAppContext } from '@antv/xflow'; 3 | import './node2.less'; 4 | 5 | const Node2 = ({ data }) => { 6 | return ( 7 |
8 |
{data.info.nodeName}
9 |
10 | ); 11 | }; 12 | export default Node2; 13 | -------------------------------------------------------------------------------- /easyflow-html/src/locales/zh-CN/menu.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'menu.welcome': '欢迎', 3 | 'menu.login': '登录', 4 | 'menu.leave.list': '休假模块', 5 | 'menu.flow': '流程', 6 | 'menu.flow.myTask': '我的任务', 7 | 'menu.flow.detail': '详情', 8 | 'menu.admin': '管理控制台', 9 | 'menu.admin.definition.list': '流程定义', 10 | 'menu.admin.definition.detail': '流程定义详情', 11 | }; 12 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/sys/constant/CacheConsts.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.sys.constant; 2 | 3 | /** 4 | * 关于缓存的常量 5 | * 6 | * @author: lijile 7 | * @date: 2021/10/27 10:52 8 | * @version: 1.0 9 | */ 10 | public interface CacheConsts { 11 | /** 12 | * 保存用户登录新的缓存 13 | */ 14 | String USER_TOKEN = "UserToken"; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/common/exception/FlowException.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.common.exception; 2 | 3 | /** 4 | * 常规流程异常 5 | * 6 | * @author: lijile 7 | * @date: 2021/10/25 13:44 8 | * @version: 1.0 9 | */ 10 | public class FlowException extends RuntimeException { 11 | public FlowException(String errMsg) { 12 | super(errMsg); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/core/dto/ApproveFormDTO.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.core.dto; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * 批阅表单 7 | * 8 | * @author: lijile 9 | * @date: 2021/11/2 16:12 10 | * @version: 1.0 11 | */ 12 | @Data 13 | public class ApproveFormDTO { 14 | 15 | private Integer opinion; 16 | 17 | private String note; 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/core/dto/NodeUserDTO.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.core.dto; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * 节点用户 7 | * 8 | * @author: lijile 9 | * @date: 2021/10/25 15:49 10 | * @version: 1.0 11 | */ 12 | @Data 13 | public class NodeUserDTO { 14 | 15 | private String username; 16 | 17 | private String fullname; 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/sys/vo/SearchUserVO.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.sys.vo; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * 用户搜索返回对象 7 | * 8 | * @author: lijile 9 | * @date: 2021/11/3 16:03 10 | * @version: 1.0 11 | */ 12 | @Data 13 | public class SearchUserVO { 14 | 15 | private String username; 16 | 17 | private String fullname; 18 | 19 | } 20 | -------------------------------------------------------------------------------- /easyflow-html/src/components/HeaderDropdown/index.jsx: -------------------------------------------------------------------------------- 1 | import { Dropdown } from 'antd'; 2 | import React from 'react'; 3 | import classNames from 'classnames'; 4 | import styles from './index.less'; 5 | 6 | const HeaderDropdown = ({ overlayClassName: cls, ...restProps }) => ( 7 | 8 | ); 9 | 10 | export default HeaderDropdown; 11 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/common/toolkit/Constants.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.common.toolkit; 2 | 3 | /** 4 | * 常量 5 | * 6 | * @author: lijile 7 | * @date: 2021/11/2 11:07 8 | * @version: 1.0 9 | */ 10 | public interface Constants { 11 | String INSTANCE_CODE = "instance_code"; 12 | 13 | String TASK_CODE = "task_code"; 14 | 15 | String DEFINITION_CODE = "definition_code"; 16 | } 17 | -------------------------------------------------------------------------------- /easyflow-html/src/components/HeaderDropdown/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | 3 | .container > * { 4 | background-color: @popover-bg; 5 | border-radius: 4px; 6 | box-shadow: @shadow-1-down; 7 | } 8 | 9 | @media screen and (max-width: @screen-xs) { 10 | .container { 11 | width: 100% !important; 12 | } 13 | .container > * { 14 | border-radius: 0 !important; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/common/config/CacheConfig.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.common.config; 2 | 3 | import org.springframework.cache.annotation.EnableCaching; 4 | import org.springframework.context.annotation.Configuration; 5 | 6 | /** 7 | * 缓存配置 8 | * 9 | * @author: lijile 10 | * @date: 2021/10/27 10:21 11 | * @version: 1.0 12 | */ 13 | @Configuration 14 | @EnableCaching 15 | public class CacheConfig { 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/sys/mapper/SysUserMapper.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.sys.mapper; 2 | 3 | import org.lecoder.easyflow.modules.sys.entity.SysUser; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | 6 | /** 7 | *

8 | * 用户表 Mapper 接口 9 | *

10 | * 11 | * @author lijile 12 | * @since 2021-10-26 13 | */ 14 | public interface SysUserMapper extends BaseMapper { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/core/mapper/FlowVariableMapper.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.core.mapper; 2 | 3 | import org.lecoder.easyflow.modules.core.entity.FlowVariable; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | 6 | /** 7 | *

8 | * 流程实例变量 Mapper 接口 9 | *

10 | * 11 | * @author lijile 12 | * @since 2021-10-25 13 | */ 14 | public interface FlowVariableMapper extends BaseMapper { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/leave/service/ILeaveApplyService.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.leave.service; 2 | 3 | import com.baomidou.mybatisplus.extension.service.IService; 4 | import org.lecoder.easyflow.modules.leave.entity.LeaveApply; 5 | 6 | /** 7 | *

8 | * 请假申请表 服务类 9 | *

10 | * 11 | * @author lijile 12 | * @since 2021-10-27 13 | */ 14 | public interface ILeaveApplyService extends IService { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /easyflow-html/config/config.dev.js: -------------------------------------------------------------------------------- 1 | // https://umijs.org/config/ 2 | import { defineConfig } from 'umi'; 3 | export default defineConfig({ 4 | plugins: [ 5 | // https://github.com/zthxxx/react-dev-inspector 6 | 'react-dev-inspector/plugins/umi/react-inspector', 7 | ], 8 | // https://github.com/zthxxx/react-dev-inspector#inspector-loader-props 9 | inspectorConfig: { 10 | exclude: [], 11 | babelPlugins: [], 12 | babelOptions: {}, 13 | }, 14 | }); 15 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/core/service/IFlowVariableService.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.core.service; 2 | 3 | import com.baomidou.mybatisplus.extension.service.IService; 4 | import org.lecoder.easyflow.modules.core.entity.FlowVariable; 5 | 6 | /** 7 | *

8 | * 流程实例变量 服务类 9 | *

10 | * 11 | * @author lijile 12 | * @since 2021-10-25 13 | */ 14 | public interface IFlowVariableService extends IService { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /easyflow-html/src/pages/admin/xflow/react-edge/edge1.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Tooltip } from 'antd'; 3 | import './edge1.less'; 4 | 5 | const Edge1 = ({ data }) => { 6 | const { conditionScript } = data.info; 7 | return ( 8 |
9 | 10 |
{conditionScript}
11 |
12 |
13 | ); 14 | }; 15 | export default Edge1; 16 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/core/mapper/FlowDefinitionMapper.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.core.mapper; 2 | 3 | import org.lecoder.easyflow.modules.core.entity.FlowDefinition; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | 6 | /** 7 | *

8 | * 流程定义表 Mapper 接口 9 | *

10 | * 11 | * @author lijile 12 | * @since 2021-10-25 13 | */ 14 | public interface FlowDefinitionMapper extends BaseMapper { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/leave/mapper/LeaveEmployeeMapper.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.leave.mapper; 2 | 3 | import org.lecoder.easyflow.modules.leave.entity.LeaveEmployee; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | 6 | /** 7 | *

8 | * 请假员工信息 Mapper 接口 9 | *

10 | * 11 | * @author lijile 12 | * @since 2021-11-15 13 | */ 14 | public interface LeaveEmployeeMapper extends BaseMapper { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/leave/service/ILeaveEmployeeService.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.leave.service; 2 | 3 | import org.lecoder.easyflow.modules.leave.entity.LeaveEmployee; 4 | import com.baomidou.mybatisplus.extension.service.IService; 5 | 6 | /** 7 | *

8 | * 请假员工信息 服务类 9 | *

10 | * 11 | * @author lijile 12 | * @since 2021-11-15 13 | */ 14 | public interface ILeaveEmployeeService extends IService { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /easyflow-html/src/pages/404.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Button, Result } from 'antd'; 3 | import { history } from 'umi'; 4 | 5 | const NoFoundPage = () => ( 6 | history.push('/')}> 12 | Back Home 13 | 14 | } 15 | /> 16 | ); 17 | 18 | export default NoFoundPage; 19 | -------------------------------------------------------------------------------- /easyflow-html/src/pages/admin/xflow/react-edge/edge1.less: -------------------------------------------------------------------------------- 1 | .edge1-container { 2 | display: flex; 3 | align-items: center; 4 | justify-content: center; 5 | width: 100%; 6 | height: 100%; 7 | font-size: 14px; 8 | background-color: #fff; 9 | border: 1px solid #690; 10 | border-radius: 4px; 11 | cursor: pointer; 12 | } 13 | 14 | .edge1-content { 15 | overflow: hidden; 16 | white-space: nowrap; 17 | text-overflow: ellipsis; 18 | word-break: break-all; 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/core/mapper/FlowInstanceNodeMapper.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.core.mapper; 2 | 3 | import org.lecoder.easyflow.modules.core.entity.FlowInstanceNode; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | 6 | /** 7 | *

8 | * 流程节点表 Mapper 接口 9 | *

10 | * 11 | * @author lijile 12 | * @since 2021-10-25 13 | */ 14 | public interface FlowInstanceNodeMapper extends BaseMapper { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/core/mapper/FlowDefinitionNodeMapper.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.core.mapper; 2 | 3 | import org.lecoder.easyflow.modules.core.entity.FlowDefinitionNode; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | 6 | /** 7 | *

8 | * 流程定义节点表 Mapper 接口 9 | *

10 | * 11 | * @author lijile 12 | * @since 2021-10-25 13 | */ 14 | public interface FlowDefinitionNodeMapper extends BaseMapper { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /easyflow-html/src/components/InstanceStatus/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Badge } from 'antd'; 3 | import { INSTANCE_STATUS } from '@/utils/consts'; 4 | 5 | const statusMap = { 6 | [INSTANCE_STATUS.IN_PROGRESS]: , 7 | [INSTANCE_STATUS.FINISHED]: , 8 | [INSTANCE_STATUS.TERMINATED]: , 9 | } 10 | export default ({ status }) => { 11 | return statusMap[status] || ''; 12 | } -------------------------------------------------------------------------------- /easyflow-html/src/utils/request.js: -------------------------------------------------------------------------------- 1 | import { request as umiRequest } from 'umi'; 2 | import { message } from 'antd'; 3 | import { SERVER_PREFIX } from '@/utils/consts'; 4 | 5 | export async function request(url, options = {}) { 6 | const hide = options.showLoading && message.loading('loading...', 0); 7 | const res = await umiRequest(url, { 8 | prefix: SERVER_PREFIX, 9 | credentials: 'include', 10 | ...options, 11 | }); 12 | if (hide) { 13 | hide(); 14 | } 15 | return res; 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/core/enums/FlowActionEnum.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.core.enums; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | 6 | /** 7 | * 流程操作 8 | * 9 | * @author: lijile 10 | * @date: 2021/11/2 11:39 11 | * @version: 1.0 12 | */ 13 | @Getter 14 | @AllArgsConstructor 15 | public enum FlowActionEnum { 16 | 17 | AGREE("同意"), 18 | DISAGREE("不同意"), 19 | CHANGE_NODE("改签"), 20 | ; 21 | 22 | private String note; 23 | } 24 | -------------------------------------------------------------------------------- /easyflow-html/src/components/Footer/index.jsx: -------------------------------------------------------------------------------- 1 | import { useIntl } from 'umi'; 2 | import { DefaultFooter } from '@ant-design/pro-layout'; 3 | export default () => { 4 | const intl = useIntl(); 5 | const defaultMessage = intl.formatMessage({ 6 | id: 'app.copyright.produced', 7 | defaultMessage: '哞哞集团体验技术部出品', 8 | }); 9 | const currentYear = new Date().getFullYear(); 10 | return ( 11 | 15 | ); 16 | }; 17 | -------------------------------------------------------------------------------- /easyflow-html/src/services/leave.js: -------------------------------------------------------------------------------- 1 | import { request } from '@/utils/request'; 2 | 3 | export async function queryApplyList(params, options) { 4 | return request('/leave/apply-list', { 5 | method: 'GET', 6 | showLoading: true, 7 | params: { ...params }, 8 | ...(options || {}), 9 | }); 10 | } 11 | 12 | export async function submit(form, options) { 13 | return request('/leave/submit', { 14 | method: 'POST', 15 | showLoading: true, 16 | data: form, 17 | ...(options || {}), 18 | }); 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/EasyflowApplication.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | /** 7 | * 项目主入口 8 | * 9 | * @author: lijile 10 | * @date: 2021/10/25 10:46 11 | * @version: 1.0 12 | */ 13 | @SpringBootApplication 14 | public class EasyflowApplication { 15 | public static void main(String[] args) { 16 | SpringApplication.run(EasyflowApplication.class); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | ${AnsiColor.BRIGHT_BLUE} 2 | __ _ 3 | / _| | 4 | ___ __ _ ___ _ _| |_| | _____ __ 5 | / _ \/ _` / __| | | | _| |/ _ \ \ /\ / / 6 | | __/ (_| \__ \ |_| | | | | (_) \ V V / 7 | \___|\__,_|___/\__, |_| |_|\___/ \_/\_/ 8 | __/ | 9 | |___/ 10 | 11 | 12 | ${AnsiColor.BRIGHT_GREEN} 13 | Easyflow Version: 1.0.0 14 | Spring Boot Version: ${spring-boot.version}${spring-boot.formatted-version} 15 | ${AnsiColor.BLACK} 16 | -------------------------------------------------------------------------------- /easyflow-html/config/proxy.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 在生产环境 代理是无法生效的,所以这里没有生产环境的配置 3 | * ------------------------------- 4 | * The agent cannot take effect in the production environment 5 | * so there is no configuration of the production environment 6 | * For details, please see 7 | * https://pro.ant.design/docs/deploy 8 | */ 9 | export default { 10 | dev: { 11 | '/easyflow/api/': { 12 | target: 'http://localhost:8080', 13 | changeOrigin: true, 14 | pathRewrite: { 15 | '^': '', 16 | }, 17 | }, 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/leave/dto/QueryFilterDTO.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.leave.dto; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * 查询过滤器 7 | * 8 | * @author: lijile 9 | * @date: 2021/10/29 14:37 10 | * @version: 1.0 11 | */ 12 | @Data 13 | public class QueryFilterDTO { 14 | /** 15 | * 假期类型 16 | */ 17 | private String type; 18 | /** 19 | * 开始日期 20 | */ 21 | private String startDate; 22 | 23 | /** 24 | * 结束日期 25 | */ 26 | private String endDate; 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/core/dto/TaskFilterDTO.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.core.dto; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * 任务过滤 7 | * 8 | * @author: lijile 9 | * @date: 2021/11/4 11:25 10 | * @version: 1.0 11 | */ 12 | @Data 13 | public class TaskFilterDTO { 14 | 15 | /** 16 | * 模块id 17 | */ 18 | private String moduleId; 19 | /** 20 | * 开始日期 21 | */ 22 | private String startDate; 23 | 24 | /** 25 | * 结束日期 26 | */ 27 | private String endDate; 28 | 29 | } 30 | -------------------------------------------------------------------------------- /easyflow-html/src/pages/flow/ApplyForm/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Card, Empty } from 'antd'; 3 | import Leave from './Leave'; 4 | 5 | const EmptyCard = () => ( 6 | 7 | 8 | 9 | ) 10 | 11 | const ApplyForm = ({ instance, applyForm }) => { 12 | if (!applyForm) { 13 | return ; 14 | } 15 | switch (instance?.moduleId) { 16 | case 'leave': 17 | return 18 | } 19 | return ; 20 | } 21 | export default ApplyForm; -------------------------------------------------------------------------------- /easyflow-html/src/utils/consts.js: -------------------------------------------------------------------------------- 1 | // 服务器地址前缀 2 | const isDev = process.env.NODE_ENV === 'development'; 3 | export const SERVER_PREFIX = isDev ? 'http://localhost:8080' : ''; 4 | 5 | export const LEAVE_TYPES = ['年假', '事假', '产假', '病假']; 6 | export const MODULE_LIST = [ 7 | { 8 | moduleId: 'leave', 9 | moduleName: '请假', 10 | }, 11 | ]; 12 | 13 | export const ACTION_STATUS = { 14 | WAIT: 0, 15 | AGREE: 1, 16 | DISAGREE: 2, 17 | }; 18 | 19 | export const INSTANCE_STATUS = { 20 | IN_PROGRESS: 0, 21 | FINISHED: 1, 22 | TERMINATED: 2, 23 | }; 24 | -------------------------------------------------------------------------------- /easyflow-html/src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Ant Design Pro", 3 | "short_name": "Ant Design Pro", 4 | "display": "standalone", 5 | "start_url": "./?utm_source=homescreen", 6 | "theme_color": "#002140", 7 | "background_color": "#001529", 8 | "icons": [ 9 | { 10 | "src": "icons/icon-192x192.png", 11 | "sizes": "192x192" 12 | }, 13 | { 14 | "src": "icons/icon-128x128.png", 15 | "sizes": "128x128" 16 | }, 17 | { 18 | "src": "icons/icon-512x512.png", 19 | "sizes": "512x512" 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /easyflow-html/README.md: -------------------------------------------------------------------------------- 1 | # easyflow-html 2 | 3 | easyflow 是一个简单、易用、高效的流程审批项目. 4 | 5 | ## 环境准备 6 | 7 | 测试时,由于前端和后端地址不一致,为了解决跨域问题,需要修改 config/proxy.js 的代理地址; 8 | 9 | 此外,需要根据需要修改 src/utils/consts.js 的服务器地址前缀。 10 | 11 | ```bash 12 | npm install 13 | ``` 14 | 15 | ### 项目启动 16 | 17 | ```bash 18 | npm start 19 | ``` 20 | 21 | ### 项目构建 22 | 23 | ```bash 24 | npm run build 25 | ``` 26 | 27 | ### Nginx 配置文件参考 28 | 29 | ``` 30 | location ^~ /easyflow/html { 31 | root D:/software/nginx/data/html; 32 | try_files $uri $uri/ /easyflow/html/index.html; 33 | } 34 | ``` 35 | -------------------------------------------------------------------------------- /easyflow-html/src/pages/Welcome.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { PageContainer } from '@ant-design/pro-layout'; 3 | import { Card } from 'antd'; 4 | import { useIntl } from 'umi'; 5 | 6 | export default () => { 7 | const intl = useIntl(); 8 | return ( 9 | 10 | 11 |

12 | {intl.formatMessage({ 13 | id: 'pages.welcome.alertMessage', 14 | defaultMessage: '欢迎光临!!!', 15 | }) 16 | } 17 |

18 |
19 |
20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/core/vo/NodeRelClassVO.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.core.vo; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * 节点关联类(位于org.lecoder.easyflow.modules.core.node) 7 | * 8 | * @author lijile 9 | * @date 2022/1/19 15:05 10 | */ 11 | @Data 12 | public class NodeRelClassVO { 13 | 14 | /** 15 | * 类名 16 | */ 17 | private String simpleName; 18 | 19 | /** 20 | * 类全路径 21 | */ 22 | private String name; 23 | 24 | /** 25 | * 备注说明 26 | */ 27 | private String note; 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/core/dto/DefinitionFormDTO.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.core.dto; 2 | 3 | import lombok.Data; 4 | 5 | import javax.validation.constraints.NotBlank; 6 | 7 | /** 8 | * 流程定义表单 9 | * 10 | * @author lijile 11 | * @date 2022/1/17 14:16 12 | */ 13 | @Data 14 | public class DefinitionFormDTO { 15 | 16 | /** 17 | * 定义代码 18 | */ 19 | private String definitionCode; 20 | 21 | /** 22 | * 流程定义名称 23 | */ 24 | @NotBlank(message = "流程定义名称不为空") 25 | private String definitionName; 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/core/vo/DefinitionDetailVO.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.core.vo; 2 | 3 | import lombok.Data; 4 | import org.lecoder.easyflow.modules.core.entity.FlowDefinition; 5 | import org.lecoder.easyflow.modules.core.entity.FlowDefinitionNode; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * 流程定义详情 11 | * 12 | * @author lijile 13 | * @date 2021/12/16 17:32 14 | */ 15 | @Data 16 | public class DefinitionDetailVO { 17 | 18 | private FlowDefinition definition; 19 | 20 | private List nodeList; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | profiles: 3 | active: test 4 | application: 5 | name: easyflow 6 | servlet: 7 | multipart: 8 | # 上传最大文件大小 9 | max-file-size: 50MB 10 | mvc: 11 | static-path-pattern: public/** 12 | mybatis-plus: 13 | configuration: 14 | map-underscore-to-camel-case: true 15 | auto-mapping-behavior: full 16 | mapper-locations: classpath*:mapper/**/*Mapper.xml 17 | org: 18 | lecoder: 19 | easyflow: 20 | interceptor: 21 | excluded-urls: # 免登陆地址 22 | - /sys/user/login 23 | - /sys/user/logout -------------------------------------------------------------------------------- /easyflow-html/src/pages/flow/ApplyForm/Leave.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Card, Descriptions } from 'antd'; 3 | 4 | export default ({ applyForm }) => { 5 | return ( 6 | 7 | 8 | {applyForm.type} 9 | {applyForm.startDate} ~ {applyForm.endDate} 10 | {applyForm.leaveDay} 天 11 | {applyForm.reason} 12 | 13 | 14 | ) 15 | } -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/core/annotation/Node.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.core.annotation; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.Target; 6 | 7 | import static java.lang.annotation.ElementType.*; 8 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 9 | 10 | /** 11 | * 节点类标识 12 | * 13 | * @author lijile 14 | * @date 2022/1/19 15:10 15 | */ 16 | @Target({ TYPE }) 17 | @Retention(RUNTIME) 18 | @Documented 19 | public @interface Node { 20 | String value() default ""; 21 | } 22 | -------------------------------------------------------------------------------- /src/main/resources/mapper/core/FlowInstanceMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | -------------------------------------------------------------------------------- /easyflow-html/src/components/HeaderSearch/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | 3 | .headerSearch { 4 | display: inline-flex; 5 | align-items: center; 6 | .input { 7 | width: 0; 8 | min-width: 0; 9 | overflow: hidden; 10 | background: transparent; 11 | border-radius: 0; 12 | transition: width 0.3s, margin-left 0.3s; 13 | :global(.ant-select-selection) { 14 | background: transparent; 15 | } 16 | input { 17 | box-shadow: none !important; 18 | } 19 | 20 | &.show { 21 | width: 210px; 22 | margin-left: 8px; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /easyflow-html/src/components/NodeStatus/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Tag } from 'antd'; 3 | import { SyncOutlined, CheckCircleOutlined, CloseCircleOutlined } from '@ant-design/icons'; 4 | import { ACTION_STATUS } from '@/utils/consts'; 5 | 6 | const statusMap = { 7 | [ACTION_STATUS.WAIT]: } color="processing">待处理, 8 | [ACTION_STATUS.AGREE]: } color="success">已同意, 9 | [ACTION_STATUS.DISAGREE]: } color="error">不同意, 10 | } 11 | export default ({ status }) => { 12 | return statusMap[status] || ''; 13 | } -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/sys/dto/UserDTO.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.sys.dto; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * 用户传输对象 7 | * 8 | * @author: lijile 9 | * @date: 2021/10/27 10:43 10 | * @version: 1.0 11 | */ 12 | @Data 13 | public class UserDTO { 14 | private Integer id; 15 | /** 16 | * 用户名 17 | */ 18 | private String username; 19 | 20 | /** 21 | * 姓名 22 | */ 23 | private String fullname; 24 | 25 | /** 26 | * 手机号 27 | */ 28 | private String phone; 29 | 30 | /** 31 | * 邮箱 32 | */ 33 | private String email; 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/core/dto/NodeTaskDTO.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.core.dto; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * 节点任务 7 | * 8 | * @author: lijile 9 | * @date: 2021/10/25 15:37 10 | * @version: 1.0 11 | */ 12 | @Data 13 | public class NodeTaskDTO { 14 | /** 15 | * 实例代码 16 | */ 17 | private String instanceCode; 18 | 19 | /** 20 | * 定义代码 21 | */ 22 | private String definitionCode; 23 | 24 | /** 25 | * 节点代码 26 | */ 27 | private String nodeCode; 28 | 29 | /** 30 | * 业务代码,一般指申请人用户名 31 | */ 32 | private String businessCode; 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/core/enums/NodeStatusEnum.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.core.enums; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | 6 | /** 7 | * 流程节点状态 8 | * 9 | * @author: lijile 10 | * @date: 2021/10/25 17:11 11 | * @version: 1.0 12 | */ 13 | @Getter 14 | @AllArgsConstructor 15 | public enum NodeStatusEnum { 16 | 17 | WAIT(0, "待处理"), 18 | AGREE(1, "同意"), 19 | DISAGREE(2, "不同意"), 20 | ; 21 | 22 | int status; 23 | 24 | String name; 25 | 26 | public static boolean canAgree(int status) { 27 | return status == WAIT.status; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/core/node/BaseNode.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.core.node; 2 | 3 | import org.lecoder.easyflow.modules.core.dto.NodeTaskDTO; 4 | import org.lecoder.easyflow.modules.core.dto.NodeUserDTO; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * 基础节点,所有节点定义类必须继承 10 | * 11 | * @author: lijile 12 | * @date: 2021/10/25 15:21 13 | * @version: 1.0 14 | */ 15 | public interface BaseNode { 16 | /** 17 | * 分配节点的处理人 18 | * @author lijile 19 | * @date 2022/1/19 14:53 20 | * @param nodeTask 21 | * @return 22 | */ 23 | List assign(NodeTaskDTO nodeTask); 24 | } 25 | -------------------------------------------------------------------------------- /easyflow-html/src/components/NoticeIcon/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | 3 | .popover { 4 | position: relative; 5 | width: 336px; 6 | } 7 | 8 | .noticeButton { 9 | display: inline-block; 10 | cursor: pointer; 11 | transition: all 0.3s; 12 | } 13 | .icon { 14 | padding: 4px; 15 | vertical-align: middle; 16 | } 17 | 18 | .badge { 19 | font-size: 16px; 20 | } 21 | 22 | .tabs { 23 | :global { 24 | .ant-tabs-nav-list { 25 | margin: auto; 26 | } 27 | 28 | .ant-tabs-nav-scroll { 29 | text-align: center; 30 | } 31 | .ant-tabs-bar { 32 | margin-bottom: 0; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /easyflow-html/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | **/node_modules 5 | # roadhog-api-doc ignore 6 | /src/utils/request-temp.js 7 | _roadhog-api-doc 8 | 9 | # production 10 | /dist 11 | 12 | # misc 13 | .DS_Store 14 | npm-debug.log* 15 | yarn-error.log 16 | 17 | /coverage 18 | .idea 19 | yarn.lock 20 | package-lock.json 21 | pnpm-lock.yaml 22 | *bak 23 | 24 | 25 | # visual studio code 26 | .history 27 | *.log 28 | functions/* 29 | .temp/** 30 | 31 | # umi 32 | .umi 33 | .umi-production 34 | 35 | # screenshot 36 | screenshot 37 | .firebase 38 | .eslintcache 39 | 40 | build 41 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/core/enums/InstanceStatusEnum.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.core.enums; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | 6 | /** 7 | * 流程实例状态 8 | * 9 | * @author: lijile 10 | * @date: 2021/10/25 17:11 11 | * @version: 1.0 12 | */ 13 | @Getter 14 | @AllArgsConstructor 15 | public enum InstanceStatusEnum { 16 | 17 | IN_PROGRESS(0, "进行中"), 18 | FINISHED(1, "已完成"), 19 | TERMINATED(2, "已终止"), 20 | ; 21 | 22 | int status; 23 | 24 | String name; 25 | 26 | public static boolean inProgress(int status) { 27 | return status == IN_PROGRESS.status; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/sys/dto/LoginUserDTO.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.sys.dto; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * 登录后返回用户实体 7 | * 8 | * @author: lijile 9 | * @date: 2021/10/27 10:24 10 | * @version: 1.0 11 | */ 12 | @Data 13 | public class LoginUserDTO { 14 | private Integer id; 15 | /** 16 | * 用户名 17 | */ 18 | private String username; 19 | 20 | /** 21 | * 姓名 22 | */ 23 | private String fullname; 24 | 25 | /** 26 | * 手机号 27 | */ 28 | private String phone; 29 | 30 | /** 31 | * 邮箱 32 | */ 33 | private String email; 34 | 35 | private String token; 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/common/entity/BaseEntity.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.common.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableId; 5 | import lombok.Data; 6 | import lombok.EqualsAndHashCode; 7 | import lombok.experimental.Accessors; 8 | 9 | import java.io.Serializable; 10 | 11 | /** 12 | * 基础实体类 13 | * 14 | * @author: lijile 15 | * @date: 2021/10/22 19:39 16 | * @version: 1.0 17 | */ 18 | @Data 19 | @EqualsAndHashCode(callSuper = false) 20 | @Accessors(chain = true) 21 | public class BaseEntity implements Serializable { 22 | 23 | @TableId(type = IdType.AUTO) 24 | private Integer id; 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/mapper/leave/LeaveApplyMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/core/entity/FlowDefinition.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.core.entity; 2 | 3 | import org.lecoder.easyflow.common.entity.BaseEntity; 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | 7 | /** 8 | *

9 | * 流程定义表 10 | *

11 | * 12 | * @author lijile 13 | * @since 2021-10-25 14 | */ 15 | @Data 16 | @EqualsAndHashCode(callSuper = true) 17 | public class FlowDefinition extends BaseEntity { 18 | 19 | private static final long serialVersionUID = 1L; 20 | 21 | /** 22 | * 定义代码 23 | */ 24 | private String definitionCode; 25 | 26 | /** 27 | * 流程定义名称 28 | */ 29 | private String definitionName; 30 | 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/common/config/ScriptEngineConfig.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.common.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | 6 | import javax.script.ScriptEngine; 7 | import javax.script.ScriptEngineManager; 8 | 9 | /** 10 | * js脚本引擎配置 11 | * @author: lijile 12 | * @date: 2021/10/25 19:30 13 | * @version: 1.0 14 | */ 15 | @Configuration 16 | public class ScriptEngineConfig { 17 | 18 | @Bean 19 | public ScriptEngine scriptEngine() { 20 | ScriptEngineManager manager = new ScriptEngineManager(); 21 | ScriptEngine engine = manager.getEngineByName("js"); 22 | return engine; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/core/service/impl/FlowVariableServiceImpl.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.core.service.impl; 2 | 3 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 4 | import org.lecoder.easyflow.modules.core.entity.FlowVariable; 5 | import org.lecoder.easyflow.modules.core.mapper.FlowVariableMapper; 6 | import org.lecoder.easyflow.modules.core.service.IFlowVariableService; 7 | import org.springframework.stereotype.Service; 8 | 9 | /** 10 | *

11 | * 流程实例变量 服务实现类 12 | *

13 | * 14 | * @author lijile 15 | * @since 2021-10-25 16 | */ 17 | @Service 18 | public class FlowVariableServiceImpl extends ServiceImpl implements IFlowVariableService { 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/sys/dto/LoginFormDTO.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.sys.dto; 2 | 3 | import io.swagger.annotations.ApiModel; 4 | import io.swagger.annotations.ApiModelProperty; 5 | import lombok.Data; 6 | 7 | import javax.validation.constraints.NotEmpty; 8 | 9 | /** 10 | * 登录表单 11 | * 12 | * @author: lijile 13 | * @date: 2021/10/27 9:36 14 | * @version: 1.0 15 | */ 16 | @Data 17 | @ApiModel("登录参数") 18 | public class LoginFormDTO { 19 | 20 | @ApiModelProperty(value = "用户名", required = true) 21 | @NotEmpty(message = "用户名不为空") 22 | private String username; 23 | 24 | @ApiModelProperty(value = "密码", required = true) 25 | @NotEmpty(message = "密码不为空") 26 | private String password; 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/leave/service/impl/LeaveEmployeeServiceImpl.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.leave.service.impl; 2 | 3 | import org.lecoder.easyflow.modules.leave.entity.LeaveEmployee; 4 | import org.lecoder.easyflow.modules.leave.mapper.LeaveEmployeeMapper; 5 | import org.lecoder.easyflow.modules.leave.service.ILeaveEmployeeService; 6 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 7 | import org.springframework.stereotype.Service; 8 | 9 | /** 10 | *

11 | * 请假员工信息 服务实现类 12 | *

13 | * 14 | * @author lijile 15 | * @since 2021-11-15 16 | */ 17 | @Service 18 | public class LeaveEmployeeServiceImpl extends ServiceImpl implements ILeaveEmployeeService { 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/common/constraint/Contains.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.common.constraint; 2 | 3 | import javax.validation.Constraint; 4 | import javax.validation.Payload; 5 | import java.lang.annotation.*; 6 | 7 | /** 8 | * 检验值是否存在数组中 9 | * @author: lijile 10 | * @date: 2021/10/27 14:24 11 | * @version: 1.0 12 | */ 13 | @Documented 14 | @Constraint(validatedBy = ContainsValidator.class) 15 | @Target({ElementType.FIELD}) 16 | @Retention(RetentionPolicy.RUNTIME) 17 | public @interface Contains { 18 | 19 | boolean required() default true; 20 | 21 | String[] list() default {}; 22 | 23 | String message() default "无效值"; 24 | 25 | Class[] groups() default {}; 26 | 27 | Class[] payload() default {}; 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/leave/entity/LeaveEmployee.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.leave.entity; 2 | 3 | import org.lecoder.easyflow.common.entity.BaseEntity; 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | 7 | /** 8 | *

9 | * 请假员工信息 10 | *

11 | * 12 | * @author lijile 13 | * @since 2021-11-15 14 | */ 15 | @Data 16 | @EqualsAndHashCode(callSuper = true) 17 | public class LeaveEmployee extends BaseEntity { 18 | 19 | private static final long serialVersionUID = 1L; 20 | 21 | /** 22 | * 用户名 23 | */ 24 | private String username; 25 | 26 | /** 27 | * 性别 28 | */ 29 | private Integer gender; 30 | 31 | /** 32 | * 剩余年假天数 33 | */ 34 | private Integer annualDays; 35 | 36 | 37 | } 38 | -------------------------------------------------------------------------------- /easyflow-html/src/pages/admin/components/DefinitionForm/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Modal, Input, Form, } from 'antd'; 3 | 4 | export default ({ definition, onCancel, onOk }) => { 5 | const [form] = Form.useForm(); 6 | return ( 7 | 13 |
{ 19 | onOk(values); 20 | }} 21 | labelCol={{ span: 4 }} 22 | wrapperCol={{ span: 20 }} 23 | > 24 | 25 | 26 | 27 |
28 |
29 | ) 30 | } -------------------------------------------------------------------------------- /easyflow-html/src/locales/zh-CN/globalHeader.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'component.globalHeader.search': '站内搜索', 3 | 'component.globalHeader.search.example1': '搜索提示一', 4 | 'component.globalHeader.search.example2': '搜索提示二', 5 | 'component.globalHeader.search.example3': '搜索提示三', 6 | 'component.globalHeader.help': '使用文档', 7 | 'component.globalHeader.notification': '通知', 8 | 'component.globalHeader.notification.empty': '你已查看所有通知', 9 | 'component.globalHeader.message': '消息', 10 | 'component.globalHeader.message.empty': '您已读完所有消息', 11 | 'component.globalHeader.event': '待办', 12 | 'component.globalHeader.event.empty': '你已完成所有待办', 13 | 'component.noticeIcon.clear': '清空', 14 | 'component.noticeIcon.cleared': '清空了', 15 | 'component.noticeIcon.empty': '暂无数据', 16 | 'component.noticeIcon.view-more': '查看更多', 17 | }; 18 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/leave/service/impl/LeaveApplyServiceImpl.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.leave.service.impl; 2 | 3 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 4 | import org.lecoder.easyflow.modules.core.service.IFlowBaseService; 5 | import org.lecoder.easyflow.modules.leave.entity.LeaveApply; 6 | import org.lecoder.easyflow.modules.leave.mapper.LeaveApplyMapper; 7 | import org.lecoder.easyflow.modules.leave.service.ILeaveApplyService; 8 | import org.springframework.stereotype.Service; 9 | 10 | /** 11 | *

12 | * 请假申请表 服务实现类 13 | *

14 | * 15 | * @author lijile 16 | * @since 2021-10-27 17 | */ 18 | @Service 19 | public class LeaveApplyServiceImpl extends ServiceImpl implements ILeaveApplyService, IFlowBaseService { 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/core/node/HrManager.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.core.node; 2 | 3 | import org.lecoder.easyflow.modules.core.annotation.Node; 4 | import org.lecoder.easyflow.modules.core.dto.NodeTaskDTO; 5 | import org.lecoder.easyflow.modules.core.dto.NodeUserDTO; 6 | 7 | import java.util.Arrays; 8 | import java.util.List; 9 | 10 | /** 11 | * 人力经理节点 12 | * 13 | * @author: lijile 14 | * @date: 2021/10/25 16:27 15 | * @version: 1.0 16 | */ 17 | @Node("人力经理") 18 | public class HrManager implements BaseNode { 19 | @Override 20 | public List assign(NodeTaskDTO nodeTask) { 21 | NodeUserDTO hrManager = new NodeUserDTO(); 22 | hrManager.setUsername("lisi"); 23 | hrManager.setFullname("李四"); 24 | return Arrays.asList(hrManager); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/core/vo/FlowDetailVO.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.core.vo; 2 | 3 | import io.swagger.annotations.ApiModel; 4 | import lombok.Data; 5 | import org.lecoder.easyflow.modules.core.entity.FlowInstance; 6 | import org.lecoder.easyflow.modules.core.entity.FlowInstanceNode; 7 | import org.lecoder.easyflow.modules.core.enums.FlowActionEnum; 8 | 9 | import java.util.EnumSet; 10 | import java.util.List; 11 | 12 | /** 13 | * 流程详情 14 | * 15 | * @author: lijile 16 | * @date: 2021/10/27 15:20 17 | * @version: 1.0 18 | */ 19 | @ApiModel("流程详情") 20 | @Data 21 | public class FlowDetailVO { 22 | 23 | private FlowInstance instance; 24 | 25 | private List nodeList; 26 | 27 | private EnumSet actions; 28 | 29 | private Object applyForm; 30 | 31 | } 32 | -------------------------------------------------------------------------------- /easyflow-html/src/locales/zh-CN.js: -------------------------------------------------------------------------------- 1 | import component from './zh-CN/component'; 2 | import globalHeader from './zh-CN/globalHeader'; 3 | import menu from './zh-CN/menu'; 4 | import pwa from './zh-CN/pwa'; 5 | import settingDrawer from './zh-CN/settingDrawer'; 6 | import settings from './zh-CN/settings'; 7 | import pages from './zh-CN/pages'; 8 | export default { 9 | 'navBar.lang': '语言', 10 | 'layout.user.link.help': '帮助', 11 | 'layout.user.link.privacy': '隐私', 12 | 'layout.user.link.terms': '条款', 13 | 'app.copyright.produced': '哞哞集团体验技术部出品', 14 | 'app.preview.down.block': '下载此页面到本地项目', 15 | 'app.welcome.link.fetch-blocks': '获取全部区块', 16 | 'app.welcome.link.block-list': '基于 block 开发,快速构建标准页面', 17 | ...pages, 18 | ...globalHeader, 19 | ...menu, 20 | ...settingDrawer, 21 | ...settings, 22 | ...pwa, 23 | ...component, 24 | }; 25 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/core/node/DeptManager.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.core.node; 2 | 3 | import org.lecoder.easyflow.modules.core.annotation.Node; 4 | import org.lecoder.easyflow.modules.core.dto.NodeTaskDTO; 5 | import org.lecoder.easyflow.modules.core.dto.NodeUserDTO; 6 | 7 | import java.util.Arrays; 8 | import java.util.List; 9 | 10 | /** 11 | * 部门经理节点 12 | * 13 | * @author: lijile 14 | * @date: 2021/10/25 16:27 15 | * @version: 1.0 16 | */ 17 | @Node("部门经理") 18 | public class DeptManager implements BaseNode { 19 | @Override 20 | public List assign(NodeTaskDTO nodeTask) { 21 | NodeUserDTO deptManager = new NodeUserDTO(); 22 | deptManager.setUsername("zhangsan"); 23 | deptManager.setFullname("张三"); 24 | return Arrays.asList(deptManager); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/leave/mapper/LeaveApplyMapper.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.leave.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.conditions.Wrapper; 4 | import com.baomidou.mybatisplus.core.metadata.IPage; 5 | import com.baomidou.mybatisplus.core.toolkit.Constants; 6 | import org.apache.ibatis.annotations.Param; 7 | import org.lecoder.easyflow.modules.leave.dto.ApplyListDTO; 8 | import org.lecoder.easyflow.modules.leave.entity.LeaveApply; 9 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 10 | 11 | /** 12 | *

13 | * 请假申请表 Mapper 接口 14 | *

15 | * 16 | * @author lijile 17 | * @since 2021-10-27 18 | */ 19 | public interface LeaveApplyMapper extends BaseMapper { 20 | IPage myPage(IPage page, @Param(Constants.WRAPPER) Wrapper queryWrapper); 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/core/entity/FlowVariable.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.core.entity; 2 | 3 | import org.lecoder.easyflow.common.entity.BaseEntity; 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | 7 | /** 8 | *

9 | * 流程实例变量 10 | *

11 | * 12 | * @author lijile 13 | * @since 2021-10-25 14 | */ 15 | @Data 16 | @EqualsAndHashCode(callSuper = true) 17 | public class FlowVariable extends BaseEntity { 18 | 19 | private static final long serialVersionUID = 1L; 20 | 21 | /** 22 | * 实例代码 23 | */ 24 | private String instanceCode; 25 | 26 | /** 27 | * 变量键 28 | */ 29 | private String keyy; 30 | 31 | /** 32 | * 变量值 33 | */ 34 | private String valuee; 35 | 36 | /** 37 | * 变量类型 38 | */ 39 | private String type; 40 | 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/core/mapper/FlowInstanceMapper.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.core.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.conditions.Wrapper; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | import com.baomidou.mybatisplus.core.metadata.IPage; 6 | import com.baomidou.mybatisplus.core.toolkit.Constants; 7 | import org.apache.ibatis.annotations.Param; 8 | import org.lecoder.easyflow.modules.core.dto.TaskInstanceDTO; 9 | import org.lecoder.easyflow.modules.core.entity.FlowInstance; 10 | 11 | /** 12 | *

13 | * 流程实例表 Mapper 接口 14 | *

15 | * 16 | * @author lijile 17 | * @since 2021-10-25 18 | */ 19 | public interface FlowInstanceMapper extends BaseMapper { 20 | IPage taskPage(IPage page, @Param(Constants.WRAPPER) Wrapper queryWrapper); 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/core/node/GeneralManager.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.core.node; 2 | 3 | import org.lecoder.easyflow.modules.core.annotation.Node; 4 | import org.lecoder.easyflow.modules.core.dto.NodeTaskDTO; 5 | import org.lecoder.easyflow.modules.core.dto.NodeUserDTO; 6 | 7 | import java.util.Arrays; 8 | import java.util.List; 9 | 10 | /** 11 | * 总经理节点 12 | * 13 | * @author: lijile 14 | * @date: 2021/10/25 16:27 15 | * @version: 1.0 16 | */ 17 | @Node("总经理") 18 | public class GeneralManager implements BaseNode { 19 | @Override 20 | public List assign(NodeTaskDTO nodeTask) { 21 | NodeUserDTO generalManager = new NodeUserDTO(); 22 | generalManager.setUsername("wangwu"); 23 | generalManager.setFullname("王五"); 24 | return Arrays.asList(generalManager); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/leave/handler/ILeaveTypeHandler.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.leave.handler; 2 | 3 | import org.lecoder.easyflow.modules.leave.dto.LeaveFormDTO; 4 | import org.lecoder.easyflow.modules.leave.entity.LeaveApply; 5 | 6 | /** 7 | * 请假类型处理器 8 | * 9 | * @author: lijile 10 | * @date: 2021/11/15 15:50 11 | * @version: 1.0 12 | */ 13 | public interface ILeaveTypeHandler { 14 | /** 15 | * 提交前做的准备,如,检查请假天数是否合理 16 | * @author: lijile 17 | * @date: 2021/11/15 15:56 18 | * @return 19 | */ 20 | default void beforeSubmit(String username, LeaveFormDTO leaveForm) {} 21 | 22 | /** 23 | * 退回以后执行的操作,如恢复剩余年假天数 24 | * @author: lijile 25 | * @date: 2021/11/15 15:55 26 | * @return 27 | */ 28 | default void afterRollback(String username, LeaveApply leaveApply) {} 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/common/toolkit/NetworkUtils.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.common.toolkit; 2 | 3 | import javax.servlet.http.Cookie; 4 | import javax.servlet.http.HttpServletRequest; 5 | 6 | /** 7 | * 网络工具 8 | * 9 | * @author: lijile 10 | * @date: 2021/11/4 16:01 11 | * @version: 1.0 12 | */ 13 | public class NetworkUtils { 14 | 15 | public static String getSessionToken(HttpServletRequest request) { 16 | // 1.从cookies获取 17 | Cookie[] cookies = request.getCookies(); 18 | if (cookies != null) { 19 | for (Cookie cookie : cookies) { 20 | if ("session_token".equals(cookie.getName())) { 21 | return cookie.getValue(); 22 | } 23 | } 24 | } 25 | // 2.从header获取 26 | return request.getHeader("Authorization"); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /easyflow-html/src/components/RightContent/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Space } from 'antd'; 3 | import { useModel, SelectLang } from 'umi'; 4 | import Avatar from './AvatarDropdown'; 5 | import styles from './index.less'; 6 | 7 | const GlobalHeaderRight = () => { 8 | const { initialState } = useModel('@@initialState'); 9 | 10 | if (!initialState || !initialState.settings) { 11 | return null; 12 | } 13 | 14 | const { navTheme, layout } = initialState.settings; 15 | let className = styles.right; 16 | 17 | if ((navTheme === 'dark' && layout === 'top') || layout === 'mix') { 18 | className = `${styles.right} ${styles.dark}`; 19 | } 20 | 21 | return ( 22 | 23 | 24 | 25 | 26 | ); 27 | }; 28 | 29 | export default GlobalHeaderRight; 30 | -------------------------------------------------------------------------------- /src/main/resources/application-prod.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | datasource: 3 | driver-class-name: com.mysql.cj.jdbc.Driver 4 | url: jdbc:mysql://127.0.0.1:3306/easyflow?useSSL=false&useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=UTC 5 | username: easyflow_user 6 | password: easyflow@2021 7 | # springboot2.0以后默认连接池Hikari,不需要引入依赖,号称“史上最快连接池” 8 | hikari: 9 | # 最小空闲连接数量,默认值:1 10 | minimum-idle: 5 11 | # 自动提交,默认值:true 12 | auto-commit: true 13 | # 连接后测试语句 14 | connection-test-query: SELECT 1 15 | log: 16 | # 日志基础目录 17 | basedir: /app/logs 18 | springfox: 19 | documentation: 20 | # 删除环境禁止swagger 21 | enabled: false 22 | org: 23 | lecoder: 24 | easyflow: 25 | interceptor: 26 | excluded-urls: 27 | - /sys/user/login 28 | - /sys/user/logout 29 | - /public/** -------------------------------------------------------------------------------- /easyflow-html/src/pages/admin/index.less: -------------------------------------------------------------------------------- 1 | .quick-start { 2 | .__dumi-default-previewer-demo { 3 | padding: 0 0; 4 | } 5 | 6 | @light-border: ~'1px solid #d9d9d9'; 7 | 8 | .xflow-user-container { 9 | position: relative; 10 | width: 100%; 11 | height: 320px; 12 | overflow: scroll; 13 | 14 | /** 覆盖节点默认选中色 */ 15 | .x6-node-selected rect { 16 | stroke: #dd4a68; 17 | stroke-width: 3px; 18 | } 19 | 20 | /** 覆盖连线默认选中色 */ 21 | .x6-edge-selected path { 22 | stroke: #873bf4; 23 | stroke-width: 2px; 24 | } 25 | 26 | .xflow-workspace-toolbar-top { 27 | border-bottom: @light-border; 28 | } 29 | 30 | .xflow-custom-minimap { 31 | border: 1px solid rgba(0, 0, 0, 0.1); 32 | 33 | .x6-widget-minimap-viewport { 34 | border: 1px solid rgba(0, 0, 0, 0.3); 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/sys/entity/SysUser.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.sys.entity; 2 | 3 | import org.lecoder.easyflow.common.entity.BaseEntity; 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | 7 | /** 8 | *

9 | * 用户表 10 | *

11 | * 12 | * @author lijile 13 | * @since 2021-10-26 14 | */ 15 | @Data 16 | @EqualsAndHashCode(callSuper = true) 17 | public class SysUser extends BaseEntity { 18 | 19 | private static final long serialVersionUID = 1L; 20 | 21 | /** 22 | * 用户名 23 | */ 24 | private String username; 25 | 26 | /** 27 | * 密码 28 | */ 29 | private String password; 30 | 31 | /** 32 | * 姓名 33 | */ 34 | private String fullname; 35 | 36 | /** 37 | * 手机号 38 | */ 39 | private String phone; 40 | 41 | /** 42 | * 邮箱 43 | */ 44 | private String email; 45 | 46 | 47 | } 48 | -------------------------------------------------------------------------------- /easyflow-html/src/pages/admin/xflow/config-graph.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createGraphConfig } from '@antv/xflow'; 3 | import Node1 from './react-node/node1'; 4 | import Node2 from './react-node/node2'; 5 | import Edge1 from './react-edge/edge1'; 6 | 7 | export const useGraphConfig = createGraphConfig((config) => { 8 | /** 设置XFlow画布配置项 */ 9 | config.setX6Config({ 10 | /** 画布网格 */ 11 | grid: true, 12 | /** 画布缩放等级 */ 13 | scaling: { 14 | min: 0.2, 15 | max: 3, 16 | }, 17 | /** 画布滚轮缩放 */ 18 | mousewheel: { 19 | enabled: true, 20 | /** 将鼠标位置作为中心缩放 */ 21 | zoomAtMousePosition: true, 22 | }, 23 | }); 24 | 25 | /** 设置XFlow画布需要渲染的React节点/边 */ 26 | config.setNodeRender('NODE1', (props) => ); 27 | config.setNodeRender('NODE2', (props) => ); 28 | config.setEdgeRender('EDGE1', (props) => ); 29 | }); 30 | -------------------------------------------------------------------------------- /src/main/resources/ehcache.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | 13 | 2000 14 | 100 15 | 16 | 17 | 18 | 19 | 20 | 600 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /easyflow-html/src/models/admin.js: -------------------------------------------------------------------------------- 1 | import * as api from '@/services/admin'; 2 | import { history } from 'umi'; 3 | 4 | export default { 5 | namespace: 'admin', 6 | state: { 7 | definitionList: [], 8 | }, 9 | effects: { 10 | *listDefinition({ payload }, { call, put }) { 11 | const res = yield call(api.listDefinition); 12 | const { data } = res; 13 | yield put({ 14 | type: 'updateState', 15 | payload: { 16 | definitionList: data, 17 | }, 18 | }); 19 | }, 20 | *saveDefinition({ payload }, { call, put }) { 21 | const res = yield call(api.saveDefinition, payload); 22 | const { data } = res; 23 | history.push(`/admin/definition/detail?definition_code=${data.definition.definitionCode}`); 24 | }, 25 | }, 26 | reducers: { 27 | updateState(state, { payload }) { 28 | return { 29 | ...state, 30 | ...payload, 31 | }; 32 | }, 33 | }, 34 | }; 35 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/sys/service/ISysUserService.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.sys.service; 2 | 3 | import com.baomidou.mybatisplus.extension.service.IService; 4 | import org.lecoder.easyflow.modules.sys.dto.LoginFormDTO; 5 | import org.lecoder.easyflow.modules.sys.dto.UserDTO; 6 | import org.lecoder.easyflow.modules.sys.entity.SysUser; 7 | 8 | /** 9 | *

10 | * 用户表 服务类 11 | *

12 | * 13 | * @author lijile 14 | * @since 2021-10-26 15 | */ 16 | public interface ISysUserService extends IService { 17 | /** 18 | * 登录 19 | * @author: lijile 20 | * @date: 2021/10/27 9:55 21 | * @param loginFormDTO 22 | * @return 23 | */ 24 | UserDTO login(LoginFormDTO loginFormDTO); 25 | 26 | /** 27 | * 根据token获取用户信息 28 | * @author: lijile 29 | * @date: 2021/10/27 10:29 30 | * @param token 31 | * @return 32 | */ 33 | UserDTO getUserByToken(String token); 34 | } 35 | -------------------------------------------------------------------------------- /doc/deploy/deploy_windows.md: -------------------------------------------------------------------------------- 1 | ## IDEA 2 | 3 | - 关于IDEA的安装与使用请参考:https://github.com/judasn/IntelliJ-IDEA-Tutorial 4 | - 搜索插件仓库,安装插件Lombok 5 | 6 | ![](https://raw.githubusercontent.com/lijile/easyflow/master/doc/images/Lombok.png) 7 | 8 | 9 | 10 | ## Mysql 11 | 12 | - 下载并安装mysql **8.0**,下载地址:https://dev.mysql.com/downloads/installer/ 13 | 14 | - 设置数据库用户名和密码:easyflow_user/easyflow@2021,或在application-*.yml文件中修改对应信息。 15 | 16 | - 分别导入doc/sql下的schema.sql和data.sql文件 17 | 18 | 19 | 20 | ## 启动后端项目 21 | 22 | - 直接运行 org.lecoder.easyflow.EasyflowApplication 的main方法即可; 23 | - 接口文档地址:http://localhost:8080/swagger-ui/ 24 | 25 | 26 | ## 构建项目 27 | - 构建前端项目 28 | ``` 29 | # 进入根目录下的easyflow-html 30 | cd easyflow-html 31 | # 安装依赖 32 | npm install 33 | # 执行构建 34 | npm run build 35 | ``` 36 | - 打包jar 37 | ``` 38 | # 进入根目录 39 | cd easyflow 40 | # 执行构建 41 | mvn clean package -Dmaven.test.skip=true 42 | # 运行jar包 43 | java -jar target/easyflow-1.0.0.jar 44 | ``` 45 | - 访问地址:http://localhost:8080/public/index.html -------------------------------------------------------------------------------- /easyflow-html/src/pages/flow/components/OpinionForm/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Modal, Form, Input, Select } from 'antd'; 3 | import { ACTION_STATUS } from '@/utils/consts'; 4 | 5 | export default ({ opinion, onCancel, onOk }) => { 6 | const [form] = Form.useForm(); 7 | return ( 8 | 14 |
{ 20 | onOk(values); 21 | }}> 22 | 23 | 27 | 28 | 29 | 30 | 31 |
32 |
33 | ) 34 | } -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/leave/dto/LeaveFormDTO.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.leave.dto; 2 | 3 | import io.swagger.annotations.ApiModel; 4 | import lombok.Data; 5 | import org.lecoder.easyflow.common.constraint.Contains; 6 | 7 | import javax.validation.constraints.NotNull; 8 | import java.time.LocalDate; 9 | 10 | /** 11 | * 请假表单 12 | * 13 | * @author: lijile 14 | * @date: 2021/10/27 14:19 15 | * @version: 1.0 16 | */ 17 | @Data 18 | @ApiModel("请假参数") 19 | public class LeaveFormDTO { 20 | 21 | /** 22 | * 假期类型 23 | */ 24 | @Contains(list = {"年假", "事假", "产假", "病假"}, message = "假期类型无效") 25 | private String type; 26 | 27 | /** 28 | * 开始日期 29 | */ 30 | @NotNull(message = "开始日期不为空") 31 | private LocalDate startDate; 32 | 33 | /** 34 | * 结束日期 35 | */ 36 | @NotNull(message = "结束日期不为空") 37 | private LocalDate endDate; 38 | 39 | /** 40 | * 请假理由 41 | */ 42 | private String reason; 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/org/lecoder/easyflow/core/FlowDefinitionMapperTest.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.core; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.lecoder.easyflow.modules.core.entity.FlowDefinition; 7 | import org.lecoder.easyflow.modules.core.mapper.FlowDefinitionMapper; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.boot.test.context.SpringBootTest; 10 | import org.springframework.test.context.junit4.SpringRunner; 11 | 12 | 13 | /** 14 | * 流程定义服务测试 15 | * 16 | * @author: lijile 17 | * @date: 2021/10/25 10:27 18 | * @version: 1.0 19 | */ 20 | @SpringBootTest 21 | @RunWith(SpringRunner.class) 22 | public class FlowDefinitionMapperTest { 23 | 24 | @Test 25 | public void test() { 26 | FlowDefinition flowDefinition = flowDefinitionMapper.selectById(1); 27 | Assert.assertNotNull(flowDefinition); 28 | } 29 | 30 | @Autowired private FlowDefinitionMapper flowDefinitionMapper; 31 | 32 | } 33 | -------------------------------------------------------------------------------- /easyflow-html/src/services/flow.js: -------------------------------------------------------------------------------- 1 | import { request } from '@/utils/request'; 2 | 3 | export async function queryList(params, options) { 4 | return request('/core/flow/my-task', { 5 | method: 'GET', 6 | showLoading: true, 7 | params: { ...params }, 8 | ...(options || {}), 9 | }); 10 | } 11 | 12 | export async function queryFlowDetail(params, options) { 13 | return request('/core/flow/detail', { 14 | method: 'GET', 15 | showLoading: true, 16 | params: { ...params }, 17 | ...(options || {}), 18 | }); 19 | } 20 | 21 | export async function approve(taskCode, data, options) { 22 | return request('/core/flow/approve', { 23 | method: 'POST', 24 | showLoading: true, 25 | params: { taskCode }, 26 | data: { ...data }, 27 | ...(options || {}), 28 | }); 29 | } 30 | 31 | export async function changeNode(params, options) { 32 | return request('/core/flow/changeNode', { 33 | method: 'POST', 34 | showLoading: true, 35 | params: { ...params }, 36 | ...(options || {}), 37 | }); 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/core/service/IFlowInstanceService.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.core.service; 2 | 3 | import com.baomidou.mybatisplus.extension.service.IService; 4 | import org.lecoder.easyflow.modules.core.entity.FlowInstance; 5 | import org.lecoder.easyflow.modules.core.enums.FlowModuleEnum; 6 | 7 | /** 8 | *

9 | * 流程实例表 服务类 10 | *

11 | * 12 | * @author lijile 13 | * @since 2021-10-25 14 | */ 15 | public interface IFlowInstanceService extends IService { 16 | 17 | /** 18 | * 根据流程定义编码生成流程实例 19 | * @author: lijile 20 | * @date: 2021/10/25 16:09 21 | * @param definitionCode 22 | * @return 23 | */ 24 | FlowInstance saveInstance(String definitionCode); 25 | 26 | /** 27 | * 根据流程定义编码生成流程实例 28 | * @author: lijile 29 | * @date: 2021/10/25 16:09 30 | * @param flowModuleEnum 31 | * @param definitionCode 32 | * @return 33 | */ 34 | FlowInstance saveInstance(FlowModuleEnum flowModuleEnum, String definitionCode); 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/leave/entity/LeaveApply.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.leave.entity; 2 | 3 | import java.time.LocalDate; 4 | import org.lecoder.easyflow.common.entity.BaseEntity; 5 | import lombok.Data; 6 | import lombok.EqualsAndHashCode; 7 | 8 | /** 9 | *

10 | * 请假申请表 11 | *

12 | * 13 | * @author lijile 14 | * @since 2021-10-27 15 | */ 16 | @Data 17 | @EqualsAndHashCode(callSuper = true) 18 | public class LeaveApply extends BaseEntity { 19 | 20 | private static final long serialVersionUID = 1L; 21 | 22 | /** 23 | * 实例代码 24 | */ 25 | private String instanceCode; 26 | 27 | /** 28 | * 假期类型 29 | */ 30 | private String type; 31 | 32 | /** 33 | * 请假天数 34 | */ 35 | private Integer leaveDay; 36 | 37 | /** 38 | * 开始日期 39 | */ 40 | private LocalDate startDate; 41 | 42 | /** 43 | * 结束日期 44 | */ 45 | private LocalDate endDate; 46 | 47 | /** 48 | * 请假理由 49 | */ 50 | private String reason; 51 | 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/core/service/IFlowBaseService.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.core.service; 2 | 3 | /** 4 | * 流程基本服务 5 | * 6 | * @author: lijile 7 | * @date: 2021/10/27 12:34 8 | * @version: 1.0 9 | */ 10 | public interface IFlowBaseService { 11 | /** 12 | * 流程结束后调用 13 | * @author: lijile 14 | * @date: 2021/10/27 12:41 15 | * @param instanceCode 16 | * @return 17 | */ 18 | default void flowFinished(String instanceCode) { 19 | 20 | } 21 | 22 | /** 23 | * 流程终止后调用,回退某些业务操作,如剩余假期还原 24 | * @author: lijile 25 | * @date: 2021/11/2 17:07 26 | * @param instanceCode 27 | * @return 28 | */ 29 | default void flowTerminated(String instanceCode) { 30 | 31 | } 32 | 33 | /** 34 | * 查询表单详细时调用 35 | * 需要返回展示给审批人审阅的表单信息 36 | * @author: lijile 37 | * @date: 2021/10/27 15:32 38 | * @param instanceCode 39 | * @return 40 | */ 41 | default Object getApplyForm(String instanceCode) { 42 | return null; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/core/dto/DefinitionNodeFormDTO.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.core.dto; 2 | 3 | import lombok.Data; 4 | 5 | import javax.validation.constraints.NotEmpty; 6 | 7 | /** 8 | * 流程节点表单 9 | * 10 | * @author lijile 11 | * @date 2022/1/17 14:27 12 | */ 13 | @Data 14 | public class DefinitionNodeFormDTO { 15 | 16 | /** 17 | * 定义代码 18 | */ 19 | @NotEmpty(message = "流程定义代码不为空") 20 | private String definitionCode; 21 | 22 | /** 23 | * 父节点代码 24 | */ 25 | private String parentCode; 26 | 27 | /** 28 | * 节点代码 29 | */ 30 | private String nodeCode; 31 | 32 | /** 33 | * 节点名称 34 | */ 35 | @NotEmpty(message = "节点名称不为空") 36 | private String nodeName; 37 | 38 | /** 39 | * 条件脚本 40 | */ 41 | private String conditionScript; 42 | 43 | /** 44 | * 关联类的全路径 45 | */ 46 | @NotEmpty(message = "关联类路径不为空") 47 | private String relClass; 48 | 49 | /** 50 | * 优先级 51 | */ 52 | private Integer priority; 53 | 54 | } 55 | -------------------------------------------------------------------------------- /easyflow-html/src/global.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | 3 | html, 4 | body, 5 | #root { 6 | height: 100%; 7 | } 8 | 9 | .colorWeak { 10 | filter: invert(80%); 11 | } 12 | 13 | .ant-layout { 14 | min-height: 100vh; 15 | } 16 | .ant-pro-sider.ant-layout-sider.ant-pro-sider-fixed { 17 | left: unset; 18 | } 19 | 20 | canvas { 21 | display: block; 22 | } 23 | 24 | body { 25 | text-rendering: optimizeLegibility; 26 | -webkit-font-smoothing: antialiased; 27 | -moz-osx-font-smoothing: grayscale; 28 | } 29 | 30 | ul, 31 | ol { 32 | list-style: none; 33 | } 34 | 35 | @media (max-width: @screen-xs) { 36 | .ant-table { 37 | width: 100%; 38 | overflow-x: auto; 39 | &-thead > tr, 40 | &-tbody > tr { 41 | > th, 42 | > td { 43 | white-space: pre; 44 | > span { 45 | display: block; 46 | } 47 | } 48 | } 49 | } 50 | } 51 | 52 | // Compatible with IE11 53 | @media screen and(-ms-high-contrast: active), (-ms-high-contrast: none) { 54 | body .ant-design-pro > .ant-layout { 55 | min-height: 100vh; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/common/toolkit/RequestHolder.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.common.toolkit; 2 | 3 | import org.lecoder.easyflow.modules.sys.dto.UserDTO; 4 | 5 | /** 6 | * 前线程请求 7 | * @author: lijile 8 | * @date: 2021/10/27 10:44 9 | * @version: 1.0 10 | */ 11 | public class RequestHolder { 12 | /** 13 | * 获取当前用户 14 | * @author: lijile 15 | * @date: 2021/10/27 10:45 16 | * @return 17 | */ 18 | public static UserDTO getCurrentUser() { 19 | return userThreadLocal.get(); 20 | } 21 | 22 | /** 23 | * 设置当前用户 24 | * @author: lijile 25 | * @date: 2021/10/27 10:45 26 | * @return 27 | */ 28 | public static void setCurrentUser(UserDTO user) { 29 | userThreadLocal.set(user); 30 | } 31 | 32 | /** 33 | * 移除当前线程的用户信息 34 | * @author: lijile 35 | * @date: 2021/10/27 10:45 36 | * @return 37 | */ 38 | public static void removeCurrentUser() { 39 | userThreadLocal.remove(); 40 | } 41 | 42 | private static final ThreadLocal userThreadLocal = new ThreadLocal<>(); 43 | } 44 | -------------------------------------------------------------------------------- /easyflow-html/src/pages/user/Login/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | 3 | .container { 4 | display: flex; 5 | flex-direction: column; 6 | height: 100vh; 7 | overflow: auto; 8 | background: @layout-body-background; 9 | } 10 | 11 | .lang { 12 | width: 100%; 13 | height: 40px; 14 | line-height: 44px; 15 | text-align: right; 16 | :global(.ant-dropdown-trigger) { 17 | margin-right: 24px; 18 | } 19 | } 20 | 21 | .content { 22 | flex: 1; 23 | padding: 32px 0; 24 | } 25 | 26 | @media (min-width: @screen-md-min) { 27 | .container { 28 | background-image: url('https://gw.alipayobjects.com/zos/rmsportal/TVYTbAXWheQpRcWDaDMu.svg'); 29 | background-repeat: no-repeat; 30 | background-position: center 110px; 31 | background-size: 100%; 32 | } 33 | 34 | .content { 35 | padding: 32px 0 24px; 36 | } 37 | } 38 | 39 | .icon { 40 | margin-left: 8px; 41 | color: rgba(0, 0, 0, 0.2); 42 | font-size: 24px; 43 | vertical-align: middle; 44 | cursor: pointer; 45 | transition: color 0.3s; 46 | 47 | &:hover { 48 | color: @primary-color; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 lecoder 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/core/entity/FlowDefinitionNode.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.core.entity; 2 | 3 | import org.lecoder.easyflow.common.entity.BaseEntity; 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | 7 | /** 8 | *

9 | * 流程定义节点表 10 | *

11 | * 12 | * @author lijile 13 | * @since 2021-10-26 14 | */ 15 | @Data 16 | @EqualsAndHashCode(callSuper = true) 17 | public class FlowDefinitionNode extends BaseEntity { 18 | 19 | private static final long serialVersionUID = 1L; 20 | 21 | /** 22 | * 定义代码 23 | */ 24 | private String definitionCode; 25 | 26 | /** 27 | * 父节点代码 28 | */ 29 | private String parentCode; 30 | 31 | /** 32 | * 节点代码 33 | */ 34 | private String nodeCode; 35 | 36 | /** 37 | * 节点名称 38 | */ 39 | private String nodeName; 40 | 41 | /** 42 | * 条件脚本 43 | */ 44 | private String conditionScript; 45 | 46 | /** 47 | * 关联类的全路径 48 | */ 49 | private String relClass; 50 | 51 | /** 52 | * 优先级 53 | */ 54 | private Integer priority; 55 | 56 | 57 | } 58 | -------------------------------------------------------------------------------- /easyflow-html/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 lecoder 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /easyflow-html/src/utils/errorHandler.js: -------------------------------------------------------------------------------- 1 | import { notification } from 'antd'; 2 | 3 | const codeMessage = { 4 | 200: '服务器成功返回请求的数据。', 5 | 201: '新建或修改数据成功。', 6 | 202: '一个请求已经进入后台排队(异步任务)。', 7 | 204: '删除数据成功。', 8 | 400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。', 9 | 401: '用户没有权限(令牌、用户名、密码错误)。', 10 | 403: '用户得到授权,但是访问是被禁止的。', 11 | 404: '发出的请求针对的是不存在的记录,服务器没有进行操作。', 12 | 406: '请求的格式不可得。', 13 | 410: '请求的资源被永久删除,且不会再得到的。', 14 | 422: '当创建一个对象时,发生一个验证错误。', 15 | 500: '服务器发生错误,请检查服务器。', 16 | 502: '网关错误。', 17 | 503: '服务不可用,服务器暂时过载或维护。', 18 | 504: '网关超时。', 19 | }; 20 | 21 | /** 22 | * 异常处理程序 23 | */ 24 | const errorHandler = (error) => { 25 | if (error.name === 'BizError') { 26 | notification.error({ 27 | message: '请求错误', 28 | description: error.data.msg, 29 | }); 30 | return error.data; 31 | } else { 32 | const { response } = error; 33 | const errortext = codeMessage[response.status] || response.statusText; 34 | const { status } = response; 35 | notification.error({ 36 | message: `请求错误 ${status}`, 37 | description: errortext, 38 | }); 39 | } 40 | }; 41 | 42 | export default errorHandler; 43 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/common/constraint/ContainsValidator.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.common.constraint; 2 | 3 | import cn.hutool.core.util.StrUtil; 4 | 5 | import javax.validation.ConstraintValidator; 6 | import javax.validation.ConstraintValidatorContext; 7 | import java.util.Arrays; 8 | 9 | /** 10 | * 检验器 11 | * @author: lijile 12 | * @date: 2021/10/27 14:33 13 | * @version: 1.0 14 | */ 15 | public class ContainsValidator implements ConstraintValidator { 16 | 17 | boolean required; 18 | 19 | private String[] list; 20 | 21 | @Override 22 | public void initialize(Contains constraintAnnotation) { 23 | required = constraintAnnotation.required(); 24 | list = constraintAnnotation.list(); 25 | } 26 | 27 | @Override 28 | public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) { 29 | if (required) { 30 | return value != null && Arrays.stream(list).anyMatch(item -> item.equals(value)); 31 | } 32 | return StrUtil.isEmpty(value) || Arrays.stream(list).anyMatch(item -> item.equals(value)); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/leave/enums/LeaveTypeEnum.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.leave.enums; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import org.lecoder.easyflow.modules.leave.handler.AnnualLeaveHandler; 6 | import org.lecoder.easyflow.modules.leave.handler.ILeaveTypeHandler; 7 | import org.lecoder.easyflow.modules.leave.handler.MaternityLeaveHandler; 8 | 9 | /** 10 | * 请假类型 11 | * 12 | * @author: lijile 13 | * @date: 2021/11/15 15:59 14 | * @version: 1.0 15 | */ 16 | @Getter 17 | @AllArgsConstructor 18 | public enum LeaveTypeEnum { 19 | 20 | ANNUAL("年假", AnnualLeaveHandler.class), 21 | MATERINITY("产假",MaternityLeaveHandler.class), 22 | ; 23 | 24 | public static LeaveTypeEnum get(String name) { 25 | for (LeaveTypeEnum leaveTypeEnum : values()) { 26 | if (leaveTypeEnum.name.equals(name)) { 27 | return leaveTypeEnum; 28 | } 29 | } 30 | return null; 31 | } 32 | 33 | private String name; 34 | /** 35 | * 处理工具类 36 | */ 37 | private Class handlerClass; 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/core/service/IFlowInstanceNodeService.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.core.service; 2 | 3 | import org.lecoder.easyflow.modules.core.entity.FlowDefinitionNode; 4 | import org.lecoder.easyflow.modules.core.entity.FlowInstance; 5 | import org.lecoder.easyflow.modules.core.entity.FlowInstanceNode; 6 | import com.baomidou.mybatisplus.extension.service.IService; 7 | 8 | /** 9 | *

10 | * 流程节点表 服务类 11 | *

12 | * 13 | * @author lijile 14 | * @since 2021-10-25 15 | */ 16 | public interface IFlowInstanceNodeService extends IService { 17 | 18 | /** 19 | * 保存实例节点 20 | * @author: lijile 21 | * @date: 2021/10/25 16:10 22 | * @param flowInstance 23 | * @param parentNodeId 24 | * @param definitionNode 25 | * @return 26 | */ 27 | int saveInstanceNode(FlowInstance flowInstance, int parentNodeId, FlowDefinitionNode definitionNode); 28 | 29 | /** 30 | * 根据任务编码查询节点 31 | * @author: lijile 32 | * @date: 2021/11/3 14:37 33 | * @param taskCode 34 | * @return 35 | */ 36 | FlowInstanceNode getByTaskCode(String taskCode); 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/sys/config/InterceptorConfig.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.sys.config; 2 | 3 | import lombok.Setter; 4 | import org.lecoder.easyflow.modules.sys.interceptor.LoginInterceptor; 5 | import org.springframework.boot.context.properties.ConfigurationProperties; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 8 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 9 | 10 | import java.util.List; 11 | 12 | /** 13 | * 拦截器配置 14 | * @author: lijile 15 | * @date: 2021/10/27 10:58 16 | * @version: 1.0 17 | */ 18 | @Setter 19 | @Configuration 20 | @ConfigurationProperties(prefix = "org.lecoder.easyflow.interceptor") 21 | public class InterceptorConfig implements WebMvcConfigurer { 22 | 23 | @Override 24 | public void addInterceptors(InterceptorRegistry registry) { 25 | LoginInterceptor loginInterceptor = new LoginInterceptor(); 26 | registry.addInterceptor(loginInterceptor).excludePathPatterns(excludedUrls); 27 | } 28 | 29 | private List excludedUrls; 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/resources/application-test.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | datasource: 3 | driver-class-name: com.mysql.cj.jdbc.Driver 4 | url: jdbc:mysql://127.0.0.1:3306/easyflow?useSSL=false&useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=UTC 5 | username: easyflow_user 6 | password: easyflow@2021 7 | # springboot2.0以后默认连接池Hikari,不需要引入依赖,号称“史上最快连接池” 8 | hikari: 9 | # 最小空闲连接数量,默认值:1 10 | minimum-idle: 5 11 | # 自动提交,默认值:true 12 | auto-commit: true 13 | # 连接后测试语句 14 | connection-test-query: SELECT 1 15 | log: 16 | # 日志基础目录 17 | basedir: D:/logs/ 18 | logging: 19 | level: 20 | # 打印sql日志 21 | org.lecoder.easyflow.modules.core.mapper: debug 22 | org.lecoder.easyflow.modules.sys.mapper: debug 23 | org.lecoder.easyflow.modules.leave.mapper: debug 24 | org: 25 | lecoder: 26 | easyflow: 27 | interceptor: 28 | excluded-urls: 29 | - /sys/user/login 30 | - /sys/user/logout 31 | - /public/** 32 | - /swagger-ui/** # swagger免登陆地址 33 | - /v2/** 34 | - /v3/** 35 | - /swagger-resources/** 36 | - /webjars/** -------------------------------------------------------------------------------- /easyflow-html/src/utils/basic.js: -------------------------------------------------------------------------------- 1 | import moment from 'moment'; 2 | /** 3 | * 设置cookie 4 | * @param name 5 | * @param value 6 | * @param expires 7 | * @param path 8 | */ 9 | export function setCookie(name, value, expires = 60 * 60 * 1000, path = '/') { 10 | const expiresDate = new Date(); 11 | expiresDate.setTime(expiresDate.getTime() + expires); 12 | document.cookie = `${name}=${value};expires=${expiresDate};path=${path}`; 13 | } 14 | 15 | /** 16 | * 获取cookie值 17 | * @param name 18 | * @return {string|null} 19 | */ 20 | export function getCookie(name) { 21 | const reg = new RegExp(`(^| )${name}=([^;]*)(;|$)`); 22 | const arr = document.cookie.match(reg); 23 | if (arr) return unescape(arr[2]); 24 | return null; 25 | } 26 | 27 | export function formatDate(date, format = 'YYYY-MM-DD') { 28 | if (date) { 29 | return moment(date).format(format); 30 | } 31 | return date; 32 | } 33 | 34 | export function toMoment(date) { 35 | if (date) { 36 | return moment(date); 37 | } 38 | return date; 39 | } 40 | 41 | export function toDate(m, format = 'YYYY-MM-DD') { 42 | if (m) { 43 | return new Date(m.format(format)); 44 | } 45 | return m; 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/core/enums/FlowModuleEnum.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.core.enums; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import org.lecoder.easyflow.modules.core.service.IFlowBaseService; 6 | import org.lecoder.easyflow.modules.leave.service.ILeaveService; 7 | 8 | /** 9 | * 流程模块 10 | * 11 | * @author: lijile 12 | * @date: 2021/10/27 11:57 13 | * @version: 1.0 14 | */ 15 | @Getter 16 | @AllArgsConstructor 17 | public enum FlowModuleEnum { 18 | 19 | LEAVE("leave", "请假", ILeaveService.class), 20 | ; 21 | 22 | public static FlowModuleEnum getById(String moduleId) { 23 | for (FlowModuleEnum moduleEnum : values()) { 24 | if (moduleEnum.getModuleId().equals(moduleId)) { 25 | return moduleEnum; 26 | } 27 | } 28 | return null; 29 | } 30 | 31 | /** 32 | * 模块id,唯一 33 | */ 34 | private String moduleId; 35 | 36 | /** 37 | * 模块名称 38 | */ 39 | private String moduleName; 40 | 41 | /** 42 | * 模块对应的核心服务接口,用于回调 43 | */ 44 | private Class serviceClass; 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/common/config/MybatisPlusConfig.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.common.config; 2 | 3 | import com.baomidou.mybatisplus.annotation.DbType; 4 | import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; 5 | import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; 6 | import org.mybatis.spring.annotation.MapperScan; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.transaction.annotation.EnableTransactionManagement; 10 | 11 | /** 12 | * mybatis plus 13 | * @author: lijile 14 | * @date: 2021/10/25 10:50 15 | * @version: 1.0 16 | */ 17 | @Configuration 18 | @EnableTransactionManagement 19 | @MapperScan("org.lecoder.easyflow.modules.*.mapper") 20 | public class MybatisPlusConfig { 21 | 22 | @Bean 23 | public MybatisPlusInterceptor mybatisPlusInterceptor() { 24 | MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); 25 | // 解决分页失败问题(total和pages为0) 26 | interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); 27 | return interceptor; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/core/service/IFlowDefinitionService.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.core.service; 2 | 3 | import org.lecoder.easyflow.modules.core.dto.DefinitionFormDTO; 4 | import org.lecoder.easyflow.modules.core.entity.FlowDefinition; 5 | import com.baomidou.mybatisplus.extension.service.IService; 6 | 7 | /** 8 | *

9 | * 流程定义表 服务类 10 | *

11 | * 12 | * @author lijile 13 | * @since 2021-10-25 14 | */ 15 | public interface IFlowDefinitionService extends IService { 16 | 17 | /** 18 | * 根据流程代码返回实例 19 | * @author lijile 20 | * @date 2022/1/17 14:54 21 | * @param definitionCode 22 | * @return 23 | */ 24 | FlowDefinition getByCode(String definitionCode); 25 | 26 | /** 27 | * 保存流程定义 28 | * @author lijile 29 | * @date 2022/1/17 14:52 30 | * @param definitionForm 31 | * @return 32 | */ 33 | boolean saveDefinition(DefinitionFormDTO definitionForm); 34 | 35 | /** 36 | * 删除流程定义 37 | * @author lijile 38 | * @date 2022/1/18 14:35 39 | * @param definitionCode 40 | * @return 41 | */ 42 | boolean deleteDefinition(String definitionCode); 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/leave/service/ILeaveService.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.leave.service; 2 | 3 | import com.baomidou.mybatisplus.core.metadata.IPage; 4 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 5 | import org.lecoder.easyflow.modules.core.service.IFlowBaseService; 6 | import org.lecoder.easyflow.modules.leave.dto.ApplyListDTO; 7 | import org.lecoder.easyflow.modules.leave.dto.LeaveFormDTO; 8 | import org.lecoder.easyflow.modules.leave.dto.QueryFilterDTO; 9 | import org.lecoder.easyflow.modules.leave.entity.LeaveApply; 10 | 11 | /** 12 | * 请假接口 13 | * 14 | * @author: lijile 15 | * @date: 2021/11/1 13:58 16 | * @version: 1.0 17 | */ 18 | public interface ILeaveService extends IFlowBaseService { 19 | /** 20 | * 提交表单 21 | * @author: lijile 22 | * @date: 2021/10/27 14:41 23 | * @param leaveForm 24 | * @return 25 | */ 26 | LeaveApply submit(LeaveFormDTO leaveForm); 27 | 28 | /** 29 | * 申请列表 30 | * @author: lijile 31 | * @date: 2021/10/29 15:02 32 | * @param page 33 | * @param queryFilter 34 | * @return 35 | */ 36 | IPage applyList(Page page, QueryFilterDTO queryFilter); 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/leave/handler/MaternityLeaveHandler.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.leave.handler; 2 | 3 | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 4 | import org.lecoder.easyflow.common.exception.FlowException; 5 | import org.lecoder.easyflow.modules.leave.dto.LeaveFormDTO; 6 | import org.lecoder.easyflow.modules.leave.entity.LeaveEmployee; 7 | import org.lecoder.easyflow.modules.leave.service.ILeaveEmployeeService; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.stereotype.Component; 10 | 11 | /** 12 | * 产假处理器 13 | * 14 | * @author: lijile 15 | * @date: 2021/11/15 16:38 16 | * @version: 1.0 17 | */ 18 | @Component 19 | public class MaternityLeaveHandler implements ILeaveTypeHandler { 20 | @Override 21 | public void beforeSubmit(String username, LeaveFormDTO leaveForm) { 22 | LeaveEmployee leaveEmployee = leaveEmployeeService.getOne(new QueryWrapper().eq("username", username)); 23 | if (leaveEmployee == null || leaveEmployee.getGender() != 1) { 24 | throw new FlowException("目前只支持女同志申请产假"); 25 | } 26 | } 27 | @Autowired private ILeaveEmployeeService leaveEmployeeService; 28 | } 29 | -------------------------------------------------------------------------------- /easyflow-html/tests/PuppeteerEnvironment.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line 2 | const NodeEnvironment = require('jest-environment-node'); 3 | const getBrowser = require('./getBrowser'); 4 | 5 | class PuppeteerEnvironment extends NodeEnvironment { 6 | // Jest is not available here, so we have to reverse engineer 7 | // the setTimeout function, see https://github.com/facebook/jest/blob/v23.1.0/packages/jest-runtime/src/index.js#L823 8 | setTimeout(timeout) { 9 | if (this.global.jasmine) { 10 | // eslint-disable-next-line no-underscore-dangle 11 | this.global.jasmine.DEFAULT_TIMEOUT_INTERVAL = timeout; 12 | } else { 13 | this.global[Symbol.for('TEST_TIMEOUT_SYMBOL')] = timeout; 14 | } 15 | } 16 | 17 | async setup() { 18 | const browser = await getBrowser(); 19 | const page = await browser.newPage(); 20 | this.global.browser = browser; 21 | this.global.page = page; 22 | } 23 | 24 | async teardown() { 25 | const { page, browser } = this.global; 26 | 27 | if (page) { 28 | await page.close(); 29 | } 30 | 31 | if (browser) { 32 | await browser.disconnect(); 33 | } 34 | 35 | if (browser) { 36 | await browser.close(); 37 | } 38 | } 39 | } 40 | 41 | module.exports = PuppeteerEnvironment; 42 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/common/config/WebConfig.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.common.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.context.annotation.Profile; 6 | import org.springframework.web.cors.CorsConfiguration; 7 | import org.springframework.web.cors.UrlBasedCorsConfigurationSource; 8 | import org.springframework.web.filter.CorsFilter; 9 | 10 | /** 11 | * 网络相关配置 12 | * @author: lijile 13 | * @date: 2021/10/28 11:24 14 | * @version: 1.0 15 | */ 16 | @Configuration 17 | public class WebConfig { 18 | 19 | /** 20 | * 跨域问题解决方案 21 | * @return 22 | */ 23 | @Bean 24 | @Profile({"test"}) 25 | public CorsFilter corsFilter() { 26 | CorsConfiguration corsConfiguration = new CorsConfiguration(); 27 | corsConfiguration.addAllowedOrigin("*"); 28 | corsConfiguration.addAllowedHeader("*"); 29 | corsConfiguration.addAllowedMethod("*"); 30 | corsConfiguration.setAllowCredentials(true); 31 | 32 | UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); 33 | source.registerCorsConfiguration("/**", corsConfiguration); 34 | return new CorsFilter(source); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/core/dto/TaskInstanceDTO.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.core.dto; 2 | 3 | import lombok.Data; 4 | 5 | import java.time.LocalDateTime; 6 | 7 | /** 8 | * 任务实例 9 | * 10 | * @author: lijile 11 | * @date: 2021/11/4 16:35 12 | * @version: 1.0 13 | */ 14 | @Data 15 | public class TaskInstanceDTO { 16 | 17 | /** 18 | * 实例代码 19 | */ 20 | private String instanceCode; 21 | 22 | /** 23 | * 定义代码 24 | */ 25 | private String definitionCode; 26 | 27 | /** 28 | * 申请人用户名 29 | */ 30 | private String username; 31 | 32 | /** 33 | * 申请人姓名 34 | */ 35 | private String fullname; 36 | 37 | /** 38 | * 模块id 39 | */ 40 | private String moduleId; 41 | 42 | /** 43 | * 模块名称 44 | */ 45 | private String moduleName; 46 | 47 | /** 48 | * 申请标题 49 | */ 50 | private String title; 51 | 52 | /** 53 | * 状态:0进行中,1已完成,2已终止等 54 | */ 55 | private Integer status; 56 | 57 | /** 58 | * 申请时间 59 | */ 60 | private LocalDateTime applyTime; 61 | 62 | 63 | 64 | /** 65 | * 任务编号 66 | */ 67 | private String taskCode; 68 | 69 | /** 70 | * 状态:0待处理,1同意,2不同意等 71 | */ 72 | private Integer actionStatus; 73 | 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/leave/dto/ApplyListDTO.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.leave.dto; 2 | 3 | import lombok.Data; 4 | 5 | import java.time.LocalDate; 6 | 7 | /** 8 | * 申请列表 9 | * 10 | * @author: lijile 11 | * @date: 2021/10/29 14:42 12 | * @version: 1.0 13 | */ 14 | @Data 15 | public class ApplyListDTO { 16 | 17 | /** 18 | * 实例代码 19 | */ 20 | private String instanceCode; 21 | 22 | /** 23 | * 申请人用户名 24 | */ 25 | private String username; 26 | 27 | /** 28 | * 申请人姓名 29 | */ 30 | private String fullname; 31 | 32 | /** 33 | * 模块名称 34 | */ 35 | private String moduleName; 36 | 37 | /** 38 | * 申请标题 39 | */ 40 | private String title; 41 | 42 | /** 43 | * 状态:0进行中,1已完成,2已终止等 44 | */ 45 | private Integer status; 46 | 47 | // ============== 申请单明细 ============= 48 | 49 | /** 50 | * 假期类型 51 | */ 52 | private String type; 53 | 54 | /** 55 | * 请假天数 56 | */ 57 | private Integer leaveDay; 58 | 59 | /** 60 | * 开始日期 61 | */ 62 | private LocalDate startDate; 63 | 64 | /** 65 | * 结束日期 66 | */ 67 | private LocalDate endDate; 68 | 69 | /** 70 | * 请假理由 71 | */ 72 | private String reason; 73 | 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/core/entity/FlowInstance.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.core.entity; 2 | 3 | import java.time.LocalDateTime; 4 | import org.lecoder.easyflow.common.entity.BaseEntity; 5 | import lombok.Data; 6 | import lombok.EqualsAndHashCode; 7 | 8 | /** 9 | *

10 | * 流程实例表 11 | *

12 | * 13 | * @author lijile 14 | * @since 2021-11-04 15 | */ 16 | @Data 17 | @EqualsAndHashCode(callSuper = true) 18 | public class FlowInstance extends BaseEntity { 19 | 20 | private static final long serialVersionUID = 1L; 21 | 22 | /** 23 | * 实例代码 24 | */ 25 | private String instanceCode; 26 | 27 | /** 28 | * 定义代码 29 | */ 30 | private String definitionCode; 31 | 32 | /** 33 | * 申请人用户名 34 | */ 35 | private String username; 36 | 37 | /** 38 | * 申请人姓名 39 | */ 40 | private String fullname; 41 | 42 | /** 43 | * 模块id 44 | */ 45 | private String moduleId; 46 | 47 | /** 48 | * 模块名称 49 | */ 50 | private String moduleName; 51 | 52 | /** 53 | * 申请标题 54 | */ 55 | private String title; 56 | 57 | /** 58 | * 状态:0进行中,1已完成,2已终止等 59 | */ 60 | private Integer status; 61 | 62 | /** 63 | * 申请时间 64 | */ 65 | private LocalDateTime applyTime; 66 | 67 | 68 | } 69 | -------------------------------------------------------------------------------- /easyflow-html/tests/getBrowser.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable global-require */ 2 | /* eslint-disable import/no-extraneous-dependencies */ 3 | const findChrome = require('carlo/lib/find_chrome'); 4 | 5 | const getBrowser = async () => { 6 | try { 7 | // eslint-disable-next-line import/no-unresolved 8 | const puppeteer = require('puppeteer'); 9 | const browser = await puppeteer.launch({ 10 | args: [ 11 | '--disable-gpu', 12 | '--disable-dev-shm-usage', 13 | '--no-first-run', 14 | '--no-zygote', 15 | '--no-sandbox', 16 | ], 17 | }); 18 | return browser; 19 | } catch (error) { 20 | // console.log(error) 21 | } 22 | 23 | try { 24 | // eslint-disable-next-line import/no-unresolved 25 | const puppeteer = require('puppeteer-core'); 26 | const findChromePath = await findChrome({}); 27 | const { executablePath } = findChromePath; 28 | const browser = await puppeteer.launch({ 29 | executablePath, 30 | args: [ 31 | '--disable-gpu', 32 | '--disable-dev-shm-usage', 33 | '--no-first-run', 34 | '--no-zygote', 35 | '--no-sandbox', 36 | ], 37 | }); 38 | return browser; 39 | } catch (error) { 40 | console.log('🧲 no find chrome'); 41 | } 42 | throw new Error('no find puppeteer'); 43 | }; 44 | 45 | module.exports = getBrowser; 46 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/common/toolkit/SpringContextHolder.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.common.toolkit; 2 | 3 | import org.springframework.beans.BeansException; 4 | import org.springframework.context.ApplicationContext; 5 | import org.springframework.context.ApplicationContextAware; 6 | import org.springframework.stereotype.Component; 7 | 8 | /** 9 | * 以静态变量保存Spring ApplicationContext 10 | * @author: lijile 11 | * @date: 2021/10/25 14:14 12 | * @version: 1.0 13 | */ 14 | @Component 15 | public class SpringContextHolder implements ApplicationContextAware { 16 | @Override 17 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 18 | SpringContextHolder.applicationContext = applicationContext; 19 | } 20 | 21 | public static ApplicationContext getApplicationContext() { 22 | return applicationContext; 23 | } 24 | 25 | public static T getBean(String beanName) { 26 | return (T) applicationContext.getBean(beanName); 27 | } 28 | 29 | public static T getBean(Class clazz) { 30 | return applicationContext.getBean(clazz); 31 | } 32 | 33 | public static String getActiveProfile() { 34 | return applicationContext.getEnvironment().getActiveProfiles()[0]; 35 | } 36 | 37 | private static ApplicationContext applicationContext; 38 | } 39 | -------------------------------------------------------------------------------- /easyflow-html/src/locales/zh-CN/settingDrawer.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'app.setting.pagestyle': '整体风格设置', 3 | 'app.setting.pagestyle.dark': '暗色菜单风格', 4 | 'app.setting.pagestyle.light': '亮色菜单风格', 5 | 'app.setting.content-width': '内容区域宽度', 6 | 'app.setting.content-width.fixed': '定宽', 7 | 'app.setting.content-width.fluid': '流式', 8 | 'app.setting.themecolor': '主题色', 9 | 'app.setting.themecolor.dust': '薄暮', 10 | 'app.setting.themecolor.volcano': '火山', 11 | 'app.setting.themecolor.sunset': '日暮', 12 | 'app.setting.themecolor.cyan': '明青', 13 | 'app.setting.themecolor.green': '极光绿', 14 | 'app.setting.themecolor.daybreak': '拂晓蓝(默认)', 15 | 'app.setting.themecolor.geekblue': '极客蓝', 16 | 'app.setting.themecolor.purple': '酱紫', 17 | 'app.setting.navigationmode': '导航模式', 18 | 'app.setting.sidemenu': '侧边菜单布局', 19 | 'app.setting.topmenu': '顶部菜单布局', 20 | 'app.setting.fixedheader': '固定 Header', 21 | 'app.setting.fixedsidebar': '固定侧边菜单', 22 | 'app.setting.fixedsidebar.hint': '侧边菜单布局时可配置', 23 | 'app.setting.hideheader': '下滑时隐藏 Header', 24 | 'app.setting.hideheader.hint': '固定 Header 时可配置', 25 | 'app.setting.othersettings': '其他设置', 26 | 'app.setting.weakmode': '色弱模式', 27 | 'app.setting.copy': '拷贝设置', 28 | 'app.setting.copyinfo': '拷贝成功,请到 config/defaultSettings.js 中替换默认配置', 29 | 'app.setting.production.hint': 30 | '配置栏只在开发环境用于预览,生产环境不会展现,请拷贝后手动修改配置文件', 31 | }; 32 | -------------------------------------------------------------------------------- /easyflow-html/tests/beforeTest.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable global-require */ 2 | /* eslint-disable import/no-extraneous-dependencies */ 3 | const { execSync } = require('child_process'); 4 | const { join } = require('path'); 5 | const findChrome = require('carlo/lib/find_chrome'); 6 | const detectInstaller = require('detect-installer'); 7 | 8 | const installPuppeteer = () => { 9 | // find can use package manager 10 | const packages = detectInstaller(join(__dirname, '../')); 11 | // get installed package manager 12 | const packageName = packages.find(detectInstaller.hasPackageCommand) || 'npm'; 13 | console.log(`🤖 will use ${packageName} install puppeteer`); 14 | const command = `${packageName} ${packageName.includes('yarn') ? 'add' : 'i'} puppeteer`; 15 | execSync(command, { 16 | stdio: 'inherit', 17 | }); 18 | }; 19 | 20 | const initPuppeteer = async () => { 21 | try { 22 | // eslint-disable-next-line import/no-unresolved 23 | const findChromePath = await findChrome({}); 24 | const { executablePath } = findChromePath; 25 | console.log(`🧲 find you browser in ${executablePath}`); 26 | return; 27 | } catch (error) { 28 | console.log('🧲 no find chrome'); 29 | } 30 | 31 | try { 32 | require.resolve('puppeteer'); 33 | } catch (error) { 34 | // need install puppeteer 35 | await installPuppeteer(); 36 | } 37 | }; 38 | 39 | initPuppeteer(); 40 | -------------------------------------------------------------------------------- /easyflow-html/src/pages/leave/components/ApplyForm/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Modal, Form, Input, DatePicker, Select } from 'antd'; 3 | import { toDate } from '@/utils/basic'; 4 | import { LEAVE_TYPES } from '@/utils/consts'; 5 | 6 | const ApplyForm = ({ onOk, onCancel }) => { 7 | const [form] = Form.useForm(); 8 | return ( 9 | 15 |
{ 18 | const values = { 19 | ...fieldsValue, 20 | startDate: toDate(fieldsValue.dateRange[0]), 21 | endDate: toDate(fieldsValue.dateRange[1]), 22 | }; 23 | delete values.dateRange; 24 | onOk(values); 25 | }}> 26 | 27 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
40 |
41 | ) 42 | } 43 | export default ApplyForm; -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/core/entity/FlowInstanceNode.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.core.entity; 2 | 3 | import java.time.LocalDateTime; 4 | import org.lecoder.easyflow.common.entity.BaseEntity; 5 | import lombok.Data; 6 | import lombok.EqualsAndHashCode; 7 | 8 | /** 9 | *

10 | * 流程实例节点表 11 | *

12 | * 13 | * @author lijile 14 | * @since 2021-11-04 15 | */ 16 | @Data 17 | @EqualsAndHashCode(callSuper = true) 18 | public class FlowInstanceNode extends BaseEntity { 19 | 20 | private static final long serialVersionUID = 1L; 21 | 22 | /** 23 | * 节点id 24 | */ 25 | private Integer nodeId; 26 | 27 | /** 28 | * 父节点id 29 | */ 30 | private Integer parentId; 31 | 32 | /** 33 | * 实例代码 34 | */ 35 | private String instanceCode; 36 | 37 | /** 38 | * 任务编号 39 | */ 40 | private String taskCode; 41 | 42 | /** 43 | * 审批人用户名 44 | */ 45 | private String username; 46 | 47 | /** 48 | * 审批人姓名 49 | */ 50 | private String fullname; 51 | 52 | /** 53 | * 节点代码 54 | */ 55 | private String nodeCode; 56 | 57 | /** 58 | * 节点名称 59 | */ 60 | private String nodeName; 61 | 62 | /** 63 | * 状态:0待处理,1同意,2不同意等 64 | */ 65 | private Integer actionStatus; 66 | 67 | /** 68 | * 批阅时间 69 | */ 70 | private LocalDateTime actionTime; 71 | 72 | /** 73 | * 批注 74 | */ 75 | private String note; 76 | 77 | 78 | } 79 | -------------------------------------------------------------------------------- /easyflow-html/src/models/leave.js: -------------------------------------------------------------------------------- 1 | import * as api from '@/services/leave'; 2 | 3 | export default { 4 | namespace: 'leave', 5 | state: { 6 | condition: {}, 7 | pagination: { 8 | current: 1, 9 | pageSize: 10, 10 | total: 0, 11 | showSizeChanger: true, 12 | }, 13 | records: [], 14 | }, 15 | effects: { 16 | *list({ payload }, { call, put, select }) { 17 | const res = yield call(api.queryApplyList, payload); 18 | const { pagination } = yield select((state) => state.leave); 19 | const { data } = res; 20 | yield put({ 21 | type: 'updateState', 22 | payload: { 23 | pagination: { 24 | ...pagination, 25 | current: data.current, 26 | total: data.total, 27 | pageSize: data.size, 28 | showTotal: (total) => `总共 ${total} 条`, 29 | }, 30 | records: data.records, 31 | }, 32 | }); 33 | }, 34 | *submit({ payload }, { call, put, select }) { 35 | yield call(api.submit, payload); 36 | const { condition, pagination } = yield select((state) => state.leave); 37 | yield put({ 38 | type: 'list', 39 | payload: { 40 | ...condition, 41 | pageNo: pagination.current, 42 | pageSize: pagination.pageSize, 43 | }, 44 | }); 45 | }, 46 | }, 47 | reducers: { 48 | updateState(state, { payload }) { 49 | return { 50 | ...state, 51 | ...payload, 52 | }; 53 | }, 54 | }, 55 | }; 56 | -------------------------------------------------------------------------------- /easyflow-html/src/pages/flow/components/QueryFilter/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Form, Select, DatePicker, Button } from 'antd'; 3 | import { SearchOutlined, PlusOutlined } from '@ant-design/icons'; 4 | import { formatDate } from '@/utils/basic'; 5 | import { MODULE_LIST } from '@/utils/consts'; 6 | import styles from './index.less'; 7 | 8 | const QueryFilter = ({ initialValues, onSearch }) => { 9 | const [form] = Form.useForm(); 10 | return ( 11 |
{ 16 | const values = form.getFieldsValue(); 17 | const { dateRange } = values; 18 | if (dateRange) { 19 | values['startDate'] = formatDate(dateRange[0]); 20 | values['endDate'] = formatDate(dateRange[1]); 21 | delete values.dateRange; 22 | } 23 | onSearch(values); 24 | }} 25 | > 26 | 27 | 32 | 33 | 34 | 35 | 36 | 37 | 45 | 46 |
47 | ); 48 | }; 49 | export default QueryFilter; 50 | -------------------------------------------------------------------------------- /easyflow-html/tests/run-tests.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable eslint-comments/disable-enable-pair */ 2 | /* eslint-disable @typescript-eslint/no-var-requires */ 3 | /* eslint-disable eslint-comments/no-unlimited-disable */ 4 | const { spawn } = require('child_process'); 5 | // eslint-disable-next-line import/no-extraneous-dependencies 6 | const { kill } = require('cross-port-killer'); 7 | 8 | const env = Object.create(process.env); 9 | env.BROWSER = 'none'; 10 | env.TEST = true; 11 | env.UMI_UI = 'none'; 12 | env.PROGRESS = 'none'; 13 | // flag to prevent multiple test 14 | let once = false; 15 | 16 | const startServer = spawn(/^win/.test(process.platform) ? 'npm.cmd' : 'npm', ['run', 'serve'], { 17 | env, 18 | }); 19 | 20 | startServer.stderr.on('data', (data) => { 21 | // eslint-disable-next-line 22 | console.log(data.toString()); 23 | }); 24 | 25 | startServer.on('exit', () => { 26 | kill(process.env.PORT || 8000); 27 | }); 28 | 29 | console.log('Starting development server for e2e tests...'); 30 | startServer.stdout.on('data', (data) => { 31 | console.log(data.toString()); 32 | // hack code , wait umi 33 | if (!once && data.toString().indexOf('Serving your umi project!') >= 0) { 34 | // eslint-disable-next-line 35 | once = true; 36 | console.log('Development server is started, ready to run tests.'); 37 | const testCmd = spawn( 38 | /^win/.test(process.platform) ? 'npm.cmd' : 'npm', 39 | ['test', '--', '--maxWorkers=1', '--runInBand'], 40 | { 41 | stdio: 'inherit', 42 | }, 43 | ); 44 | testCmd.on('exit', (code) => { 45 | console.log(code); 46 | startServer.kill(); 47 | process.exit(code); 48 | }); 49 | } 50 | }); 51 | -------------------------------------------------------------------------------- /easyflow-html/config/routes.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | path: '/user', 4 | layout: false, 5 | routes: [ 6 | { 7 | path: '/user', 8 | routes: [ 9 | { 10 | name: 'login', 11 | path: '/user/login', 12 | component: './user/Login', 13 | }, 14 | ], 15 | }, 16 | { 17 | component: './404', 18 | }, 19 | ], 20 | }, 21 | { 22 | path: '/welcome', 23 | name: 'welcome', 24 | icon: 'smile', 25 | component: './Welcome', 26 | }, 27 | { 28 | path: '/flow', 29 | name: 'flow', 30 | icon: 'bulb', 31 | routes: [ 32 | { 33 | path: '/flow/my-task', 34 | name: 'myTask', 35 | component: './flow/MyTask', 36 | }, 37 | { 38 | path: '/flow/detail', 39 | name: 'detail', 40 | hideInMenu: true, 41 | component: './flow/Detail', 42 | }, 43 | ], 44 | }, 45 | { 46 | path: '/leave/list', 47 | name: 'leave.list', 48 | icon: 'table', 49 | component: './leave/ApplyList', 50 | }, 51 | { 52 | path: '/admin', 53 | name: 'admin', 54 | icon: 'dashboard', 55 | routes: [ 56 | { 57 | path: '/admin/definition/list', 58 | name: 'definition.list', 59 | component: './admin/DefinitionList', 60 | }, 61 | { 62 | path: '/admin/definition/detail', 63 | name: 'definition.detail', 64 | component: './admin/DefinitionDetail', 65 | hideInMenu: true, 66 | }, 67 | ], 68 | }, 69 | { 70 | path: '/', 71 | redirect: '/welcome', 72 | }, 73 | { 74 | component: './404', 75 | }, 76 | ]; 77 | -------------------------------------------------------------------------------- /easyflow-html/src/components/RightContent/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | 3 | @pro-header-hover-bg: rgba(0, 0, 0, 0.025); 4 | 5 | .menu { 6 | :global(.anticon) { 7 | margin-right: 8px; 8 | } 9 | :global(.ant-dropdown-menu-item) { 10 | min-width: 160px; 11 | } 12 | } 13 | 14 | .right { 15 | display: flex; 16 | float: right; 17 | height: 48px; 18 | margin-left: auto; 19 | overflow: hidden; 20 | .action { 21 | display: flex; 22 | align-items: center; 23 | height: 48px; 24 | padding: 0 12px; 25 | cursor: pointer; 26 | transition: all 0.3s; 27 | > span { 28 | vertical-align: middle; 29 | } 30 | &:hover { 31 | background: @pro-header-hover-bg; 32 | } 33 | &:global(.opened) { 34 | background: @pro-header-hover-bg; 35 | } 36 | } 37 | .search { 38 | padding: 0 12px; 39 | &:hover { 40 | background: transparent; 41 | } 42 | } 43 | .account { 44 | .avatar { 45 | margin-right: 8px; 46 | color: @primary-color; 47 | vertical-align: top; 48 | background: @primary-color; 49 | } 50 | } 51 | } 52 | 53 | .dark { 54 | .action { 55 | &:hover { 56 | background: #252a3d; 57 | } 58 | &:global(.opened) { 59 | background: #252a3d; 60 | } 61 | } 62 | } 63 | 64 | @media only screen and (max-width: @screen-md) { 65 | :global(.ant-divider-vertical) { 66 | vertical-align: unset; 67 | } 68 | .name { 69 | display: none; 70 | } 71 | .right { 72 | position: absolute; 73 | top: 0; 74 | right: 12px; 75 | .account { 76 | .avatar { 77 | margin-right: 0; 78 | } 79 | } 80 | .search { 81 | display: none; 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /easyflow-html/src/pages/leave/components/QueryFilter/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Form, Select, DatePicker, Button } from 'antd'; 3 | import { SearchOutlined, PlusOutlined } from '@ant-design/icons'; 4 | import { formatDate } from '@/utils/basic'; 5 | import { LEAVE_TYPES } from '@/utils/consts'; 6 | import styles from './index.less'; 7 | 8 | const QueryFilter = ({ initialValues, onSearch, onCreate }) => { 9 | const [form] = Form.useForm(); 10 | return ( 11 |
{ 16 | const values = form.getFieldsValue(); 17 | const { dateRange } = values; 18 | if (dateRange) { 19 | values['startDate'] = formatDate(dateRange[0]); 20 | values['endDate'] = formatDate(dateRange[1]); 21 | delete values.dateRange; 22 | } 23 | onSearch(values); 24 | }} 25 | > 26 | 27 | 32 | 33 | 34 | 35 | 36 | 37 | 45 | 48 | 49 |
50 | ); 51 | }; 52 | export default QueryFilter; 53 | -------------------------------------------------------------------------------- /easyflow-html/src/services/api.js: -------------------------------------------------------------------------------- 1 | import { request } from '@/utils/request'; 2 | /** 获取当前的用户 GET /api/currentUser */ 3 | 4 | export async function currentUser(options) { 5 | return request('/sys/user/currentUser', { 6 | method: 'GET', 7 | ...(options || {}), 8 | }); 9 | } 10 | /** 退出登录接口 POST /api/login/outLogin */ 11 | 12 | export async function logout(options) { 13 | return request('/sys/user/logout', { 14 | method: 'GET', 15 | ...(options || {}), 16 | }); 17 | } 18 | /** 登录接口 POST /api/login/account */ 19 | 20 | export async function login(body, options) { 21 | return request('/sys/user/login', { 22 | method: 'POST', 23 | headers: { 24 | 'Content-Type': 'application/json', 25 | }, 26 | data: body, 27 | ...(options || {}), 28 | }); 29 | } 30 | /** 此处后端没有提供注释 GET /api/notices */ 31 | 32 | export async function getNotices(options) { 33 | return request('/api/notices', { 34 | method: 'GET', 35 | ...(options || {}), 36 | }); 37 | } 38 | /** 获取规则列表 GET /api/rule */ 39 | 40 | export async function rule(params, options) { 41 | return request('/api/rule', { 42 | method: 'GET', 43 | params: { ...params }, 44 | ...(options || {}), 45 | }); 46 | } 47 | /** 新建规则 PUT /api/rule */ 48 | 49 | export async function updateRule(options) { 50 | return request('/api/rule', { 51 | method: 'PUT', 52 | ...(options || {}), 53 | }); 54 | } 55 | /** 新建规则 POST /api/rule */ 56 | 57 | export async function addRule(options) { 58 | return request('/api/rule', { 59 | method: 'POST', 60 | ...(options || {}), 61 | }); 62 | } 63 | /** 删除规则 DELETE /api/rule */ 64 | 65 | export async function removeRule(options) { 66 | return request('/api/rule', { 67 | method: 'DELETE', 68 | ...(options || {}), 69 | }); 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/common/exception/MyExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.common.exception; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.lecoder.easyflow.common.web.CommonResult; 5 | import org.springframework.web.bind.MethodArgumentNotValidException; 6 | import org.springframework.web.bind.annotation.ControllerAdvice; 7 | import org.springframework.web.bind.annotation.ExceptionHandler; 8 | import org.springframework.web.bind.annotation.ResponseBody; 9 | 10 | /** 11 | * 异常统一处理 12 | * 13 | * @author: lijile 14 | * @date: 2021/10/25 13:46 15 | * @version: 1.0 16 | */ 17 | @Slf4j 18 | @ControllerAdvice 19 | public class MyExceptionHandler { 20 | 21 | /** 22 | * 参数验证失败异常处理 23 | * 优势:无需在每个controller的方法处添加 BindingResult 24 | * @author: lijile 25 | * @date: 2021/10/27 9:40 26 | * @param e 27 | * @return 28 | */ 29 | @ResponseBody 30 | @ExceptionHandler(MethodArgumentNotValidException.class) 31 | public CommonResult methodArgumentNotValidException(MethodArgumentNotValidException e) { 32 | return CommonResult.fail(e.getBindingResult().getFieldError().getDefaultMessage()); 33 | } 34 | 35 | /** 36 | * 流程异常处理 37 | * @author: lijile 38 | * @date: 2021/10/25 14:07 39 | * @return 40 | */ 41 | @ResponseBody 42 | @ExceptionHandler(FlowException.class) 43 | public CommonResult flowException(FlowException e) { 44 | return CommonResult.fail(e.getMessage()); 45 | } 46 | 47 | /** 48 | * 未设置异常处理,统一执行返回系统异常错误 49 | * @author: lijile 50 | * @date: 2021/10/25 14:06 51 | * @return 52 | */ 53 | @ResponseBody 54 | @ExceptionHandler(Exception.class) 55 | public CommonResult exception(Exception e) { 56 | log.error("【未知错误】", e); 57 | return CommonResult.fail("系统异常,请稍后重试!"); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/core/toolkit/VariableTypeHelper.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.core.toolkit; 2 | 3 | import cn.hutool.core.util.ObjectUtil; 4 | import org.lecoder.easyflow.modules.core.entity.FlowVariable; 5 | 6 | import java.math.BigDecimal; 7 | import java.util.*; 8 | import java.util.stream.Collectors; 9 | 10 | /** 11 | * 变量类型工具类 12 | * 13 | * @author: lijile 14 | * @date: 2021/10/26 9:34 15 | * @version: 1.0 16 | */ 17 | public class VariableTypeHelper { 18 | 19 | public static List getVariables(Map variables) { 20 | if (variables == null) { 21 | return Collections.EMPTY_LIST; 22 | } 23 | List variableList = new ArrayList<>(); 24 | for (Map.Entry entry : variables.entrySet()) { 25 | String key = entry.getKey(); 26 | Object value = entry.getValue(); 27 | FlowVariable variable = new FlowVariable(); 28 | if (ObjectUtil.isBasicType(value) || value instanceof BigDecimal) { 29 | variable.setType(value.getClass().getName()); 30 | } else if(value instanceof Collection) { 31 | variable.setType(Collection.class.getName()); 32 | variable.setValuee(join((Collection) value)); 33 | } else { 34 | variable.setType(Object.class.getName()); 35 | } 36 | variable.setKeyy(key); 37 | if (variable.getValuee() == null) { 38 | variable.setValuee(value.toString()); 39 | } 40 | variableList.add(variable); 41 | } 42 | return variableList; 43 | } 44 | 45 | private static String join(Collection value) { 46 | Set set = new HashSet(value); 47 | return set.stream().map(Object::toString).collect(Collectors.joining(",")); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/resources/logback-spring.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n 11 | 12 | 13 | 14 | 15 | 16 | 17 | ${basedir}/${applicationName}-%d{yyyy-MM-dd}.%i.log 18 | 19 | 30 20 | 10MB 21 | 22 | 23 | 24 | %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /easyflow-html/config/config.js: -------------------------------------------------------------------------------- 1 | // https://umijs.org/config/ 2 | import { defineConfig } from 'umi'; 3 | import defaultSettings from './defaultSettings'; 4 | import proxy from './proxy'; 5 | import routes from './routes'; 6 | const { REACT_APP_ENV } = process.env; 7 | 8 | const isDev = process.env.NODE_ENV === 'development'; 9 | const basePath = isDev ? '/' : '/public/'; 10 | export default defineConfig({ 11 | base: basePath, 12 | publicPath: basePath, 13 | // window.publicPath 全局变量输出 14 | runtimePublicPath: true, 15 | history: { 16 | type: "hash" 17 | }, 18 | hash: false, 19 | antd: {}, 20 | outputPath: '../src/main/resources/public', 21 | dva: { 22 | hmr: true, 23 | }, 24 | layout: { 25 | // https://umijs.org/zh-CN/plugins/plugin-layout 26 | locale: true, 27 | siderWidth: 208, 28 | ...defaultSettings, 29 | }, 30 | // https://umijs.org/zh-CN/plugins/plugin-locale 31 | locale: { 32 | // default zh-CN 33 | default: 'zh-CN', 34 | antd: true, 35 | // default true, when it is true, will use `navigator.language` overwrite default 36 | baseNavigator: true, 37 | }, 38 | targets: { 39 | ie: 11, 40 | }, 41 | // umi routes: https://umijs.org/docs/routing 42 | routes, 43 | // Theme for antd: https://ant.design/docs/react/customize-theme-cn 44 | theme: { 45 | 'primary-color': defaultSettings.primaryColor, 46 | }, 47 | // esbuild is father build tools 48 | // https://umijs.org/plugins/plugin-esbuild 49 | // 导致ie11无效字符 50 | esbuild: isDev && {}, 51 | title: false, 52 | ignoreMomentLocale: true, 53 | proxy: proxy[REACT_APP_ENV || 'dev'], 54 | manifest: { 55 | basePath: '/', 56 | }, 57 | // Fast Refresh 热更新 58 | fastRefresh: {}, 59 | nodeModulesTransform: { 60 | // ie11 语法错误 61 | // all和none,前者速度较慢,但可规避常见的兼容性等问题,后者反之。 62 | type: isDev ? 'none' : 'all', 63 | }, 64 | // ie11时需要注释掉,否则报错缺少 ')' 65 | mfsu: {}, 66 | webpack5: {}, 67 | }); 68 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/core/service/IFlowApiService.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.core.service; 2 | 3 | import org.lecoder.easyflow.modules.core.dto.NodeUserDTO; 4 | import org.lecoder.easyflow.modules.core.entity.FlowInstance; 5 | import org.lecoder.easyflow.modules.core.entity.FlowInstanceNode; 6 | import org.lecoder.easyflow.modules.core.enums.FlowModuleEnum; 7 | 8 | import java.util.List; 9 | import java.util.Map; 10 | 11 | /** 12 | * 流程接口 13 | * 14 | * @author: lijile 15 | * @date: 2021/10/25 14:21 16 | * @version: 1.0 17 | */ 18 | public interface IFlowApiService { 19 | /** 20 | * 开启流程 21 | * @author: lijile 22 | * @date: 2021/10/25 14:31 23 | * @param flowModuleEnum 模块 24 | * @param definitionCode 流程定义编号 25 | * @param variables 流程实例变量 26 | * @return 流程实例编号 27 | */ 28 | String start(FlowModuleEnum flowModuleEnum, String definitionCode, Map variables); 29 | 30 | /** 31 | * 同意通过 32 | * @author: lijile 33 | * @date: 2021/10/25 17:03 34 | * @param taskCode 35 | * @param note 36 | * @return 37 | */ 38 | FlowInstance agree(String taskCode, String note); 39 | 40 | /** 41 | * 不同意终止 42 | * @author: lijile 43 | * @date: 2021/11/2 16:53 44 | * @param taskCode 45 | * @return 46 | */ 47 | FlowInstance disagree(String taskCode, String note); 48 | 49 | /** 50 | * 改签/改派 51 | * @author: lijile 52 | * @date: 2021/11/3 14:13 53 | * @param taskCode 54 | * @param nodeUser 55 | * @return 56 | */ 57 | void changeNode(String taskCode, NodeUserDTO nodeUser); 58 | 59 | /** 60 | * 预览流程 61 | * @author: lijile 62 | * @date: 2021/10/26 11:24 63 | * @param definitionCode 64 | * @param variables 65 | * @return 66 | */ 67 | List preview(String definitionCode, Map variables); 68 | } 69 | -------------------------------------------------------------------------------- /easyflow-html/src/services/admin.js: -------------------------------------------------------------------------------- 1 | import { request } from '@/utils/request'; 2 | 3 | export async function listDefinition(options) { 4 | return request('/core/admin/list-definition', { 5 | method: 'GET', 6 | showLoading: true, 7 | ...(options || {}), 8 | }); 9 | } 10 | 11 | export async function getDefinitionDetail(params, options) { 12 | return request('/core/admin/get-definition', { 13 | method: 'GET', 14 | showLoading: true, 15 | params: { ...params }, 16 | ...(options || {}), 17 | }); 18 | } 19 | 20 | export async function saveDefinition(data, options) { 21 | return request('/core/admin/save-definition', { 22 | data, 23 | method: 'POST', 24 | showLoading: true, 25 | ...(options || {}), 26 | }); 27 | } 28 | 29 | export async function updateDefinition(data, options) { 30 | return request('/core/admin/update-definition', { 31 | data, 32 | method: 'PUT', 33 | showLoading: true, 34 | ...(options || {}), 35 | }); 36 | } 37 | 38 | export async function deleteDefinition(params, options) { 39 | return request('/core/admin/delete-definition', { 40 | method: 'DELETE', 41 | showLoading: true, 42 | params: { ...params }, 43 | ...(options || {}), 44 | }); 45 | } 46 | 47 | export async function saveDefinitionNode(data, options) { 48 | return request('/core/admin/save-definition-node', { 49 | data, 50 | method: 'POST', 51 | showLoading: true, 52 | ...(options || {}), 53 | }); 54 | } 55 | 56 | export async function updateDefinitionNode(data, options) { 57 | return request('/core/admin/update-definition-node', { 58 | data, 59 | method: 'PUT', 60 | showLoading: true, 61 | ...(options || {}), 62 | }); 63 | } 64 | 65 | export async function deleteDefinitionNode(params, options) { 66 | return request('/core/admin/delete-definition-node', { 67 | method: 'DELETE', 68 | showLoading: true, 69 | params: { ...params }, 70 | ...(options || {}), 71 | }); 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/core/service/IFlowDefinitionNodeService.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.core.service; 2 | 3 | import org.lecoder.easyflow.modules.core.dto.DefinitionNodeFormDTO; 4 | import org.lecoder.easyflow.modules.core.entity.FlowDefinitionNode; 5 | import com.baomidou.mybatisplus.extension.service.IService; 6 | import org.lecoder.easyflow.modules.core.entity.FlowVariable; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | *

12 | * 流程定义节点表 服务类 13 | *

14 | * 15 | * @author lijile 16 | * @since 2021-10-26 17 | */ 18 | public interface IFlowDefinitionNodeService extends IService { 19 | /** 20 | * 获取下一个流程定义节点 21 | * @author: lijile 22 | * @date: 2021/10/25 17:40 23 | * @param definitionCode 流程定义编号 24 | * @param parentCode 父节点 25 | * @param variables 26 | * @return 27 | */ 28 | FlowDefinitionNode getNextDefinitionCode(String definitionCode, String parentCode, List variables); 29 | 30 | /** 31 | * 创建流程节点 32 | * @author lijile 33 | * @date 2022/1/17 14:34 34 | * @param definitionNodeForm 35 | * @return 36 | */ 37 | boolean saveDefinitionNode(DefinitionNodeFormDTO definitionNodeForm); 38 | 39 | /** 40 | * 更新流程节点 41 | * @author lijile 42 | * @date 2022/1/18 10:38 43 | * @param definitionNodeForm 44 | * @return 45 | */ 46 | boolean updateDefinitionNode(DefinitionNodeFormDTO definitionNodeForm); 47 | 48 | /** 49 | * 删除节点 50 | * @author lijile 51 | * @date 2022/1/18 14:36 52 | * @param definitionCode 53 | * @param nodeCode 54 | * @return 55 | */ 56 | boolean deleteDefinitionNode(String definitionCode, String nodeCode); 57 | 58 | /** 59 | * 获取流程节点 60 | * @author lijile 61 | * @date 2022/1/18 18:40 62 | * @param definitionCode 63 | * @param nodeCode 64 | * @return 65 | */ 66 | FlowDefinitionNode getByCode(String definitionCode, String nodeCode); 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/core/service/IFlowService.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.core.service; 2 | 3 | import com.baomidou.mybatisplus.core.metadata.IPage; 4 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 5 | import org.lecoder.easyflow.modules.core.dto.TaskFilterDTO; 6 | import org.lecoder.easyflow.modules.core.dto.TaskInstanceDTO; 7 | import org.lecoder.easyflow.modules.core.entity.FlowInstance; 8 | import org.lecoder.easyflow.modules.core.entity.FlowInstanceNode; 9 | import org.lecoder.easyflow.modules.core.enums.FlowActionEnum; 10 | 11 | import java.util.EnumSet; 12 | import java.util.List; 13 | 14 | /** 15 | * 流程接口 16 | * 17 | * @author: lijile 18 | * @date: 2021/11/2 11:43 19 | * @version: 1.0 20 | */ 21 | public interface IFlowService { 22 | /** 23 | * 计算流程操作权限 24 | * @author: lijile 25 | * @date: 2021/11/2 11:55 26 | * @param instance 27 | * @param nodeList 28 | * @return 29 | */ 30 | EnumSet countActions(FlowInstance instance, List nodeList); 31 | 32 | /** 33 | * 同意 34 | * @author: lijile 35 | * @date: 2021/11/2 16:34 36 | * @param taskCode 37 | * @param note 38 | * @return 39 | */ 40 | void agree(String taskCode, String note); 41 | 42 | /** 43 | * 不同意 44 | * @author: lijile 45 | * @date: 2021/11/2 16:34 46 | * @param taskCode 47 | * @param note 48 | * @return 49 | */ 50 | void disagree(String taskCode, String note); 51 | 52 | /** 53 | * 改派 54 | * @author: lijile 55 | * @date: 2021/11/3 15:16 56 | * @param taskCode 57 | * @param username 58 | * @return 59 | */ 60 | void changeNode(String taskCode, String username); 61 | 62 | /** 63 | * 任务列表 64 | * @author: lijile 65 | * @date: 2021/11/4 16:36 66 | * @param page 67 | * @param queryFilter 68 | * @return 69 | */ 70 | IPage listMyTask(Page page, TaskFilterDTO queryFilter); 71 | } 72 | -------------------------------------------------------------------------------- /easyflow-html/src/e2e/baseLayout.e2e.js: -------------------------------------------------------------------------------- 1 | const { uniq } = require('lodash'); 2 | const RouterConfig = require('../../config/config').default.routes; 3 | 4 | const BASE_URL = `http://localhost:${process.env.PORT || 8001}`; 5 | 6 | function formatter(routes, parentPath = '') { 7 | const fixedParentPath = parentPath.replace(/\/{1,}/g, '/'); 8 | let result = []; 9 | routes.forEach((item) => { 10 | if (item.path && !item.path.startsWith('/')) { 11 | result.push(`${fixedParentPath}/${item.path}`.replace(/\/{1,}/g, '/')); 12 | } 13 | if (item.path && item.path.startsWith('/')) { 14 | result.push(`${item.path}`.replace(/\/{1,}/g, '/')); 15 | } 16 | if (item.routes) { 17 | result = result.concat( 18 | formatter(item.routes, item.path ? `${fixedParentPath}/${item.path}` : parentPath), 19 | ); 20 | } 21 | }); 22 | return uniq(result.filter((item) => !!item)); 23 | } 24 | 25 | beforeEach(async () => { 26 | await page.goto(`${BASE_URL}`); 27 | await page.evaluate(() => { 28 | localStorage.setItem('antd-pro-authority', '["admin"]'); 29 | }); 30 | }); 31 | 32 | describe('Ant Design Pro E2E test', () => { 33 | const testPage = (path) => async () => { 34 | await page.goto(`${BASE_URL}${path}`); 35 | await page.waitForSelector('footer', { 36 | timeout: 2000, 37 | }); 38 | const haveFooter = await page.evaluate( 39 | () => document.getElementsByTagName('footer').length > 0, 40 | ); 41 | expect(haveFooter).toBeTruthy(); 42 | }; 43 | 44 | const routers = formatter(RouterConfig); 45 | routers.forEach((route) => { 46 | it(`test pages ${route}`, testPage(route)); 47 | }); 48 | 49 | it('topmenu should have footer', async () => { 50 | const params = '?navTheme=light&layout=topmenu'; 51 | await page.goto(`${BASE_URL}${params}`); 52 | await page.waitForSelector('footer', { 53 | timeout: 2000, 54 | }); 55 | 56 | const haveFooter = await page.evaluate( 57 | () => document.getElementsByTagName('footer').length > 0, 58 | ); 59 | expect(haveFooter).toBeTruthy(); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/leave/handler/AnnualLeaveHandler.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.leave.handler; 2 | 3 | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 4 | import org.lecoder.easyflow.common.exception.FlowException; 5 | import org.lecoder.easyflow.modules.leave.dto.LeaveFormDTO; 6 | import org.lecoder.easyflow.modules.leave.entity.LeaveApply; 7 | import org.lecoder.easyflow.modules.leave.entity.LeaveEmployee; 8 | import org.lecoder.easyflow.modules.leave.service.ILeaveEmployeeService; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.stereotype.Component; 11 | 12 | import java.time.temporal.ChronoUnit; 13 | 14 | /** 15 | * 年假处理器 16 | * 17 | * @author: lijile 18 | * @date: 2021/11/15 15:54 19 | * @version: 1.0 20 | */ 21 | @Component 22 | public class AnnualLeaveHandler implements ILeaveTypeHandler { 23 | @Override 24 | public void beforeSubmit(String username, LeaveFormDTO leaveForm) { 25 | int days = (int) leaveForm.getStartDate().until(leaveForm.getEndDate(), ChronoUnit.DAYS) + 1; 26 | // 检查剩余天数 27 | LeaveEmployee leaveEmployee = leaveEmployeeService.getOne(new QueryWrapper().eq("username", username)); 28 | if (leaveEmployee == null || leaveEmployee.getAnnualDays() < days) { 29 | throw new FlowException("剩余年假天数不足"); 30 | } 31 | 32 | // 更新剩余年假天数 33 | leaveEmployee.setAnnualDays(leaveEmployee.getAnnualDays() - days); 34 | leaveEmployeeService.updateById(leaveEmployee); 35 | } 36 | 37 | @Override 38 | public void afterRollback(String username, LeaveApply leaveApply) { 39 | // 恢复年假天数 40 | int leaveDay = leaveApply.getLeaveDay(); 41 | LeaveEmployee leaveEmployee = leaveEmployeeService.getOne(new QueryWrapper().eq("username", username)); 42 | leaveEmployee.setAnnualDays(leaveEmployee.getAnnualDays() + leaveDay); 43 | leaveEmployeeService.updateById(leaveEmployee); 44 | } 45 | 46 | @Autowired private ILeaveEmployeeService leaveEmployeeService; 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/sys/service/impl/SysUserServiceImpl.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.sys.service.impl; 2 | 3 | import cn.hutool.crypto.SecureUtil; 4 | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; 5 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 6 | import org.lecoder.easyflow.common.exception.FlowException; 7 | import org.lecoder.easyflow.modules.sys.constant.CacheConsts; 8 | import org.lecoder.easyflow.modules.sys.dto.LoginFormDTO; 9 | import org.lecoder.easyflow.modules.sys.dto.UserDTO; 10 | import org.lecoder.easyflow.modules.sys.entity.SysUser; 11 | import org.lecoder.easyflow.modules.sys.mapper.SysUserMapper; 12 | import org.lecoder.easyflow.modules.sys.service.ISysUserService; 13 | import org.springframework.beans.BeanUtils; 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | import org.springframework.cache.Cache; 16 | import org.springframework.cache.CacheManager; 17 | import org.springframework.stereotype.Service; 18 | 19 | /** 20 | *

21 | * 用户表 服务实现类 22 | *

23 | * 24 | * @author lijile 25 | * @since 2021-10-26 26 | */ 27 | @Service 28 | public class SysUserServiceImpl extends ServiceImpl implements ISysUserService { 29 | 30 | @Override 31 | public UserDTO login(LoginFormDTO loginFormDTO) { 32 | SysUser user = userMapper.selectOne(new LambdaQueryWrapper().eq(SysUser::getUsername, loginFormDTO.getUsername())); 33 | if (user == null || !user.getPassword().equals(SecureUtil.md5(loginFormDTO.getPassword()))) { 34 | throw new FlowException("用户名或密码错误!"); 35 | } 36 | UserDTO userDTO = new UserDTO(); 37 | BeanUtils.copyProperties(user, userDTO); 38 | return userDTO; 39 | } 40 | 41 | @Override 42 | public UserDTO getUserByToken(String token) { 43 | Cache cache = cacheManager.getCache(CacheConsts.USER_TOKEN); 44 | return cache.get(token, UserDTO.class); 45 | } 46 | 47 | @Autowired private SysUserMapper userMapper; 48 | 49 | @Autowired private CacheManager cacheManager; 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/common/web/CommonResult.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.common.web; 2 | 3 | import io.swagger.annotations.ApiModel; 4 | import io.swagger.annotations.ApiModelProperty; 5 | import lombok.Data; 6 | 7 | /** 8 | * 接口通用返回对象 9 | * @author: lijile 10 | * @date: 2021/10/25 13:50 11 | * @version: 1.0 12 | */ 13 | @Data 14 | @ApiModel(value = "接口返回对象") 15 | public class CommonResult { 16 | 17 | private static final int SUCCESS = 200; 18 | 19 | private static final int ERROR = -1; 20 | 21 | @ApiModelProperty("返回代码") 22 | private int code; 23 | 24 | @ApiModelProperty("返回提示消息") 25 | private String msg; 26 | 27 | @ApiModelProperty("返回数据对象") 28 | private T data; 29 | 30 | public static CommonResult notlogin() { 31 | CommonResult commonResult = new CommonResult(); 32 | commonResult.setCode(401); 33 | commonResult.setMsg("请先登录"); 34 | return commonResult; 35 | } 36 | 37 | public static CommonResult success() { 38 | CommonResult commonResult = new CommonResult(); 39 | commonResult.setCode(SUCCESS); 40 | commonResult.setMsg("success"); 41 | return commonResult; 42 | } 43 | 44 | public static CommonResult success(String msg) { 45 | CommonResult commonResult = new CommonResult(); 46 | commonResult.setCode(SUCCESS); 47 | commonResult.setMsg(msg); 48 | return commonResult; 49 | } 50 | 51 | public static CommonResult success(T data) { 52 | CommonResult commonResult = new CommonResult(); 53 | commonResult.setCode(SUCCESS); 54 | commonResult.setData(data); 55 | return commonResult; 56 | } 57 | 58 | public static CommonResult success(String msg, T data) { 59 | CommonResult commonResult = success(msg); 60 | commonResult.setData(data); 61 | return commonResult; 62 | } 63 | 64 | public static CommonResult fail(String msg) { 65 | CommonResult commonResult = new CommonResult(); 66 | commonResult.setCode(ERROR); 67 | commonResult.setMsg(msg); 68 | return commonResult; 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /easyflow-html/src/pages/admin/DefinitionList.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState, } from 'react'; 2 | import { Table, Button, Modal, } from 'antd'; 3 | import { connect, Link } from 'umi'; 4 | import { PageContainer } from '@ant-design/pro-layout'; 5 | import { PlusOutlined, ExclamationCircleOutlined, } from '@ant-design/icons'; 6 | import DefinitionForm from './components/DefinitionForm'; 7 | 8 | const DefinitionList = ({ definitionList, dispatch }) => { 9 | 10 | const [createModal, setCreateModal] = useState(); 11 | 12 | const columns = [ 13 | { 14 | title: 'ID', 15 | dataIndex: 'id', 16 | }, 17 | { 18 | title: '流程定义', 19 | render: (text, record) => {record.definitionCode}, 20 | }, 21 | { 22 | title: '流程名称', 23 | dataIndex: 'definitionName', 24 | }, 25 | { 26 | title: '操作', 27 | render: (text, record) => 28 | { 30 | Modal.confirm({ 31 | title: '确认删除该流程?', 32 | icon: , 33 | onOk() { 34 | dispatch({ 35 | type: 'admin/deleteDefinition', 36 | payload: { 37 | definitionCode: record.definitionCode, 38 | } 39 | }) 40 | }, 41 | }); 42 | }}>删除 43 | }, 44 | ]; 45 | 46 | useEffect(() => { 47 | dispatch({ 48 | type: 'admin/listDefinition', 49 | payload: {}, 50 | }); 51 | }, []); 52 | 53 | return ( 54 | } key="create" onClick={() => setCreateModal(true)}>新建流程 57 | ]}> 58 | 64 | { 65 | createModal && 66 | setCreateModal(false)} 68 | onOk={(form) => { 69 | dispatch({ 70 | type: 'admin/saveDefinition', 71 | payload: form, 72 | }); 73 | setCreateModal(false); 74 | }} /> 75 | } 76 | 77 | ) 78 | } 79 | export default connect(({ admin }) => ({ 80 | ...admin, 81 | }))(DefinitionList); -------------------------------------------------------------------------------- /doc/sql/data.sql: -------------------------------------------------------------------------------- 1 | -- 初始化测试用户,初始密码easyflow 2 | insert into sys_user(username, password, fullname, phone, email) values('xialuo', '9ec8f3a4a450276f4c4091f50d02102b', '夏洛', '13800138000', 'xialuo@qq.com'); 3 | insert into sys_user(username, password, fullname, phone, email) values('zhangsan', '9ec8f3a4a450276f4c4091f50d02102b', '张三', '13800138000', 'zhangsan@qq.com'); 4 | insert into sys_user(username, password, fullname, phone, email) values('lisi', '9ec8f3a4a450276f4c4091f50d02102b', '李四', '13800138000', 'lisi@qq.com'); 5 | insert into sys_user(username, password, fullname, phone, email) values('wangwu', '9ec8f3a4a450276f4c4091f50d02102b', '王五', '13800138000', 'wangwu@qq.com'); 6 | 7 | -- 流程定义 8 | insert into flow_definition(definition_code, definition_name) values('leave_common', '常规请假流程'); 9 | 10 | -- 流程定义节点(分支1) 11 | insert into flow_definition_node(definition_code, parent_code, node_code, node_name, condition_script, rel_class, priority) values('leave_common', null, 'dept_manager_1', '部门经理', '["事假", "年假"].indexOf(type)>=0', 'org.lecoder.easyflow.modules.core.node.DeptManager', 10); 12 | insert into flow_definition_node(definition_code, parent_code, node_code, node_name, condition_script, rel_class, priority) values('leave_common', 'dept_manager_1', 'hr_manager_1', '人事部经理', 'days >= 3', 'org.lecoder.easyflow.modules.core.node.HrManager', 10); 13 | insert into flow_definition_node(definition_code, parent_code, node_code, node_name, condition_script, rel_class, priority) values('leave_common', 'hr_manager_1', 'general_manager_1', '总经理', 'days >= 7', 'org.lecoder.easyflow.modules.core.node.GeneralManager', 10); 14 | 15 | -- 流程定义节点(分支2) 16 | insert into flow_definition_node(definition_code, parent_code, node_code, node_name, condition_script, rel_class, priority) values('leave_common', null, 'dept_manager_2', '部门经理', null, 'org.lecoder.easyflow.modules.core.node.DeptManager', 0); 17 | insert into flow_definition_node(definition_code, parent_code, node_code, node_name, condition_script, rel_class, priority) values('leave_common', 'dept_manager_2', 'hr_manager_2', '人事部经理', 'days >= 5', 'org.lecoder.easyflow.modules.core.node.HrManager', 0); 18 | 19 | 20 | -- 请假员工扩展信息 21 | insert into leave_employee(username, gender, annual_days) values('xialuo', 2, 10); 22 | -------------------------------------------------------------------------------- /easyflow-html/src/models/flow.js: -------------------------------------------------------------------------------- 1 | import * as api from '@/services/flow'; 2 | 3 | export default { 4 | namespace: 'flow', 5 | state: { 6 | instance: {}, 7 | nodeList: [], 8 | actions: [], 9 | applyForm: {}, 10 | 11 | condition: {}, 12 | pagination: { 13 | current: 1, 14 | pageSize: 10, 15 | total: 0, 16 | showSizeChanger: true, 17 | }, 18 | records: [], 19 | }, 20 | effects: { 21 | *list({ payload }, { call, put, select }) { 22 | const res = yield call(api.queryList, payload); 23 | const { pagination } = yield select((state) => state.flow); 24 | const { data } = res; 25 | yield put({ 26 | type: 'updateState', 27 | payload: { 28 | pagination: { 29 | ...pagination, 30 | current: data.current, 31 | total: data.total, 32 | pageSize: data.size, 33 | showTotal: (total) => `总共 ${total} 条`, 34 | }, 35 | records: data.records, 36 | }, 37 | }); 38 | }, 39 | *detail({ payload }, { call, put }) { 40 | const res = yield call(api.queryFlowDetail, payload); 41 | const { data } = res; 42 | yield put({ 43 | type: 'updateState', 44 | payload: { 45 | ...data, 46 | }, 47 | }); 48 | }, 49 | *approve({ payload }, { call, put }) { 50 | const { instanceCode, taskCode, form } = payload; 51 | const res = yield call(api.approve, taskCode, form); 52 | if (res) { 53 | yield put({ 54 | type: 'detail', 55 | payload: { 56 | instanceCode, 57 | }, 58 | }); 59 | } 60 | }, 61 | *changeNode({ payload }, { call, put }) { 62 | const { instanceCode, taskCode, username } = payload; 63 | const res = yield call(api.changeNode, { taskCode, username }); 64 | if (res) { 65 | yield put({ 66 | type: 'detail', 67 | payload: { 68 | instanceCode, 69 | }, 70 | }); 71 | } 72 | }, 73 | }, 74 | reducers: { 75 | updateState(state, { payload }) { 76 | return { 77 | ...state, 78 | ...payload, 79 | }; 80 | }, 81 | }, 82 | }; 83 | -------------------------------------------------------------------------------- /easyflow-html/src/service-worker.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-restricted-globals */ 2 | /* eslint-disable no-underscore-dangle */ 3 | /* globals workbox */ 4 | workbox.core.setCacheNameDetails({ 5 | prefix: 'antd-pro', 6 | suffix: 'v5', 7 | }); 8 | // Control all opened tabs ASAP 9 | workbox.clientsClaim(); 10 | 11 | /** 12 | * Use precaching list generated by workbox in build process. 13 | * https://developers.google.com/web/tools/workbox/reference-docs/latest/workbox.precaching 14 | */ 15 | workbox.precaching.precacheAndRoute(self.__precacheManifest || []); 16 | 17 | /** 18 | * Register a navigation route. 19 | * https://developers.google.com/web/tools/workbox/modules/workbox-routing#how_to_register_a_navigation_route 20 | */ 21 | workbox.routing.registerNavigationRoute('/index.html'); 22 | 23 | /** 24 | * Use runtime cache: 25 | * https://developers.google.com/web/tools/workbox/reference-docs/latest/workbox.routing#.registerRoute 26 | * 27 | * Workbox provides all common caching strategies including CacheFirst, NetworkFirst etc. 28 | * https://developers.google.com/web/tools/workbox/reference-docs/latest/workbox.strategies 29 | */ 30 | 31 | /** Handle API requests */ 32 | workbox.routing.registerRoute(/\/api\//, workbox.strategies.networkFirst()); 33 | 34 | /** Handle third party requests */ 35 | workbox.routing.registerRoute( 36 | /^https:\/\/gw\.alipayobjects\.com\//, 37 | workbox.strategies.networkFirst(), 38 | ); 39 | workbox.routing.registerRoute( 40 | /^https:\/\/cdnjs\.cloudflare\.com\//, 41 | workbox.strategies.networkFirst(), 42 | ); 43 | workbox.routing.registerRoute(/\/color.less/, workbox.strategies.networkFirst()); 44 | 45 | /** Response to client after skipping waiting with MessageChannel */ 46 | addEventListener('message', (event) => { 47 | const replyPort = event.ports[0]; 48 | const message = event.data; 49 | if (replyPort && message && message.type === 'skip-waiting') { 50 | event.waitUntil( 51 | self.skipWaiting().then( 52 | () => { 53 | replyPort.postMessage({ 54 | error: null, 55 | }); 56 | }, 57 | (error) => { 58 | replyPort.postMessage({ 59 | error, 60 | }); 61 | }, 62 | ), 63 | ); 64 | } 65 | }); 66 | -------------------------------------------------------------------------------- /easyflow-html/src/pages/flow/components/ChangeNodeForm/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useMemo, useRef } from 'react'; 2 | import { Modal, Form, Select, Spin } from 'antd'; 3 | import { request } from 'umi'; 4 | import debounce from 'lodash/debounce'; 5 | import { SERVER_PREFIX } from '@/utils/consts'; 6 | 7 | export default ({ taskCode, nodeList, onCancel, onOk }) => { 8 | const [form] = Form.useForm(); 9 | 10 | const [fetching, setFetching] = useState(false); 11 | const [options, setOptions] = useState([]); 12 | const fetchRef = useRef(0); 13 | const debounceFetcher = useMemo(() => { 14 | const loadOptions = (value) => { 15 | fetchRef.current += 1; 16 | const fetchId = fetchRef.current; 17 | setOptions([]); 18 | setFetching(true); 19 | request('/sys/user/search', { 20 | prefix: SERVER_PREFIX, 21 | credentials: 'include', 22 | params: { 23 | keyword: value, 24 | } 25 | }).then(res => { 26 | if (fetchId !== fetchRef.current) { 27 | return; 28 | } 29 | 30 | setOptions(res.data.map(user => ({ value: user.username, label: user.fullname }))); 31 | setFetching(false); 32 | }) 33 | }; 34 | 35 | return debounce(loadOptions, 1000); 36 | }, []); 37 | 38 | return ( 39 | 45 |
{ 51 | onOk(values); 52 | }}> 53 | 54 | 59 | 60 | 61 |
75 | dispatch({ 76 | type: 'flow/list', 77 | payload: { 78 | pageNo: p.current, 79 | pageSize: p.pageSize, 80 | ...condition, 81 | }, 82 | }) 83 | } 84 | /> 85 | 86 | 87 | ) 88 | } 89 | export default connect(({ flow }) => ({ 90 | ...flow, 91 | }))(MyTask); -------------------------------------------------------------------------------- /easyflow-html/src/components/NoticeIcon/NoticeList.less: -------------------------------------------------------------------------------- 1 | @import '~antd/es/style/themes/default.less'; 2 | 3 | .list { 4 | max-height: 400px; 5 | overflow: auto; 6 | &::-webkit-scrollbar { 7 | display: none; 8 | } 9 | .item { 10 | padding-right: 24px; 11 | padding-left: 24px; 12 | overflow: hidden; 13 | cursor: pointer; 14 | transition: all 0.3s; 15 | 16 | .meta { 17 | width: 100%; 18 | } 19 | 20 | .avatar { 21 | margin-top: 4px; 22 | background: @component-background; 23 | } 24 | .iconElement { 25 | font-size: 32px; 26 | } 27 | 28 | &.read { 29 | opacity: 0.4; 30 | } 31 | &:last-child { 32 | border-bottom: 0; 33 | } 34 | &:hover { 35 | background: @primary-1; 36 | } 37 | .title { 38 | margin-bottom: 8px; 39 | font-weight: normal; 40 | } 41 | .description { 42 | font-size: 12px; 43 | line-height: @line-height-base; 44 | } 45 | .datetime { 46 | margin-top: 4px; 47 | font-size: 12px; 48 | line-height: @line-height-base; 49 | } 50 | .extra { 51 | float: right; 52 | margin-top: -1.5px; 53 | margin-right: 0; 54 | color: @text-color-secondary; 55 | font-weight: normal; 56 | } 57 | } 58 | .loadMore { 59 | padding: 8px 0; 60 | color: @primary-6; 61 | text-align: center; 62 | cursor: pointer; 63 | &.loadedAll { 64 | color: rgba(0, 0, 0, 0.25); 65 | cursor: unset; 66 | } 67 | } 68 | } 69 | 70 | .notFound { 71 | padding: 73px 0 88px; 72 | color: @text-color-secondary; 73 | text-align: center; 74 | img { 75 | display: inline-block; 76 | height: 76px; 77 | margin-bottom: 16px; 78 | } 79 | } 80 | 81 | .bottomBar { 82 | height: 46px; 83 | color: @text-color; 84 | line-height: 46px; 85 | text-align: center; 86 | border-top: 1px solid @border-color-split; 87 | border-radius: 0 0 @border-radius-base @border-radius-base; 88 | transition: all 0.3s; 89 | div { 90 | display: inline-block; 91 | width: 50%; 92 | cursor: pointer; 93 | transition: all 0.3s; 94 | user-select: none; 95 | 96 | &:only-child { 97 | width: 100%; 98 | } 99 | &:not(:only-child):last-child { 100 | border-left: 1px solid @border-color-split; 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /easyflow-html/src/components/HeaderSearch/index.jsx: -------------------------------------------------------------------------------- 1 | import { SearchOutlined } from '@ant-design/icons'; 2 | import { AutoComplete, Input } from 'antd'; 3 | import useMergedState from 'rc-util/es/hooks/useMergedState'; 4 | import React, { useRef } from 'react'; 5 | import classNames from 'classnames'; 6 | import styles from './index.less'; 7 | 8 | const HeaderSearch = (props) => { 9 | const { 10 | className, 11 | defaultValue, 12 | onVisibleChange, 13 | placeholder, 14 | visible, 15 | defaultVisible, 16 | ...restProps 17 | } = props; 18 | const inputRef = useRef(null); 19 | const [value, setValue] = useMergedState(defaultValue, { 20 | value: props.value, 21 | onChange: props.onChange, 22 | }); 23 | const [searchMode, setSearchMode] = useMergedState(defaultVisible ?? false, { 24 | value: props.visible, 25 | onChange: onVisibleChange, 26 | }); 27 | const inputClass = classNames(styles.input, { 28 | [styles.show]: searchMode, 29 | }); 30 | return ( 31 |
{ 34 | setSearchMode(true); 35 | 36 | if (searchMode && inputRef.current) { 37 | inputRef.current.focus(); 38 | } 39 | }} 40 | onTransitionEnd={({ propertyName }) => { 41 | if (propertyName === 'width' && !searchMode) { 42 | if (onVisibleChange) { 43 | onVisibleChange(searchMode); 44 | } 45 | } 46 | }} 47 | > 48 | 54 | 61 | { 68 | if (e.key === 'Enter') { 69 | if (restProps.onSearch) { 70 | restProps.onSearch(value); 71 | } 72 | } 73 | }} 74 | onBlur={() => { 75 | setSearchMode(false); 76 | }} 77 | /> 78 | 79 |
80 | ); 81 | }; 82 | 83 | export default HeaderSearch; 84 | -------------------------------------------------------------------------------- /easyflow-html/mock/user.js: -------------------------------------------------------------------------------- 1 | const waitTime = (time = 100) => { 2 | return new Promise((resolve) => { 3 | setTimeout(() => { 4 | resolve(true); 5 | }, time); 6 | }); 7 | }; 8 | 9 | async function getFakeCaptcha(req, res) { 10 | await waitTime(1000); 11 | return res.json('captcha-xxx'); 12 | } 13 | 14 | const { ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION } = process.env; 15 | /** 16 | * 当前用户的权限,如果为空代表没登录 17 | * current user access, if is '', user need login 18 | * 如果是 pro 的预览,默认是有权限的 19 | */ 20 | 21 | let access = ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION === 'site' ? 'admin' : ''; 22 | 23 | const getAccess = () => { 24 | return access; 25 | }; // 代码中会兼容本地 service mock 以及部署站点的静态数据 26 | 27 | export default { 28 | // 支持值为 Object 和 Array 29 | 'GET /sys/user/currentUser': (req, res) => { 30 | if (!getAccess()) { 31 | res.status(401).send({ 32 | code: 401, 33 | msg: '请先登录!', 34 | }); 35 | return; 36 | } 37 | 38 | res.send({ 39 | code: 200, 40 | data: { 41 | fullname: '测试', 42 | }, 43 | }); 44 | }, 45 | 'POST /sys/user/login': async (req, res) => { 46 | const { password, username, type } = req.body; 47 | await waitTime(1000); 48 | 49 | if (password === 'easyflow' && username === 'test') { 50 | res.send({ 51 | status: 'ok', 52 | type, 53 | currentAuthority: 'admin', 54 | }); 55 | access = 'admin'; 56 | return; 57 | } 58 | 59 | res.send({ 60 | status: 'error', 61 | type, 62 | currentAuthority: 'guest', 63 | }); 64 | access = 'guest'; 65 | }, 66 | 'GET /api/404': (req, res) => { 67 | res.status(404).send({ 68 | timestamp: 1513932643431, 69 | status: 404, 70 | error: 'Not Found', 71 | message: 'No message available', 72 | path: '/base/category/list/2121212', 73 | }); 74 | }, 75 | 'GET /api/403': (req, res) => { 76 | res.status(403).send({ 77 | timestamp: 1513932555104, 78 | status: 403, 79 | error: 'Forbidden', 80 | message: 'Forbidden', 81 | path: '/base/category/list', 82 | }); 83 | }, 84 | 'GET /api/401': (req, res) => { 85 | res.status(401).send({ 86 | timestamp: 1513932555104, 87 | status: 401, 88 | error: 'Unauthorized', 89 | message: 'Unauthorized', 90 | path: '/base/category/list', 91 | }); 92 | }, 93 | 'GET /api/login/captcha': getFakeCaptcha, 94 | }; 95 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/sys/interceptor/LoginInterceptor.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.sys.interceptor; 2 | 3 | import cn.hutool.json.JSONUtil; 4 | import org.lecoder.easyflow.common.toolkit.NetworkUtils; 5 | import org.lecoder.easyflow.common.toolkit.RequestHolder; 6 | import org.lecoder.easyflow.common.toolkit.SpringContextHolder; 7 | import org.lecoder.easyflow.common.web.CommonResult; 8 | import org.lecoder.easyflow.modules.sys.dto.UserDTO; 9 | import org.lecoder.easyflow.modules.sys.service.ISysUserService; 10 | import org.springframework.util.StringUtils; 11 | import org.springframework.web.servlet.HandlerInterceptor; 12 | import org.springframework.web.servlet.ModelAndView; 13 | 14 | import javax.servlet.http.HttpServletRequest; 15 | import javax.servlet.http.HttpServletResponse; 16 | 17 | /** 18 | * 用户登录拦截器,负责给请求线程设置用户信息 19 | * 20 | * @author: lijile 21 | * @date: 2021/10/27 10:41 22 | * @version: 1.0 23 | */ 24 | public class LoginInterceptor implements HandlerInterceptor { 25 | @Override 26 | public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 27 | UserDTO currentUser = getUser(request); 28 | if (currentUser == null) { 29 | response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); 30 | CommonResult commonResult = CommonResult.notlogin(); 31 | response.setCharacterEncoding("UTF-8"); 32 | response.setHeader("Content-Type", "application/json"); 33 | response.getWriter().print(JSONUtil.toJsonStr(commonResult)); 34 | return false; 35 | } 36 | RequestHolder.setCurrentUser(currentUser); 37 | return true; 38 | } 39 | 40 | @Override 41 | public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { 42 | 43 | } 44 | 45 | @Override 46 | public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { 47 | RequestHolder.removeCurrentUser(); 48 | } 49 | 50 | private UserDTO getUser(HttpServletRequest request) { 51 | String token = NetworkUtils.getSessionToken(request); 52 | if (StringUtils.isEmpty(token)) { 53 | return null; 54 | } 55 | ISysUserService userService = SpringContextHolder.getBean(ISysUserService.class); 56 | return userService.getUserByToken(token); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/core/service/impl/FlowInstanceServiceImpl.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.core.service.impl; 2 | 3 | import cn.hutool.core.util.RandomUtil; 4 | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; 5 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 6 | import org.lecoder.easyflow.common.toolkit.RequestHolder; 7 | import org.lecoder.easyflow.modules.core.entity.FlowInstance; 8 | import org.lecoder.easyflow.modules.core.enums.FlowModuleEnum; 9 | import org.lecoder.easyflow.modules.core.mapper.FlowInstanceMapper; 10 | import org.lecoder.easyflow.modules.core.service.IFlowInstanceService; 11 | import org.lecoder.easyflow.modules.sys.dto.UserDTO; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.stereotype.Service; 14 | 15 | /** 16 | *

17 | * 流程实例表 服务实现类 18 | *

19 | * 20 | * @author lijile 21 | * @since 2021-10-25 22 | */ 23 | @Service 24 | public class FlowInstanceServiceImpl extends ServiceImpl implements IFlowInstanceService { 25 | 26 | @Override 27 | public FlowInstance saveInstance(String definitionCode) { 28 | FlowInstance flowInstance = new FlowInstance(); 29 | String instanceCode = RandomUtil.randomString(20); 30 | flowInstance.setInstanceCode(instanceCode); 31 | flowInstance.setDefinitionCode(definitionCode); 32 | flowInstanceMapper.insert(flowInstance); 33 | return flowInstanceMapper.selectOne(new LambdaQueryWrapper().eq(FlowInstance::getInstanceCode, instanceCode)); 34 | } 35 | 36 | @Override 37 | public FlowInstance saveInstance(FlowModuleEnum flowModuleEnum, String definitionCode) { 38 | UserDTO currentUser = RequestHolder.getCurrentUser(); 39 | FlowInstance flowInstance = new FlowInstance(); 40 | String instanceCode = RandomUtil.randomString(20); 41 | flowInstance.setInstanceCode(instanceCode); 42 | flowInstance.setDefinitionCode(definitionCode); 43 | flowInstance.setUsername(currentUser.getUsername()); 44 | flowInstance.setFullname(currentUser.getFullname()); 45 | flowInstance.setModuleId(flowModuleEnum.getModuleId()); 46 | flowInstance.setModuleName(flowModuleEnum.getModuleName()); 47 | flowInstanceMapper.insert(flowInstance); 48 | return flowInstanceMapper.selectOne(new LambdaQueryWrapper().eq(FlowInstance::getInstanceCode, instanceCode)); 49 | } 50 | 51 | @Autowired private FlowInstanceMapper flowInstanceMapper; 52 | } 53 | -------------------------------------------------------------------------------- /easyflow-html/src/pages/admin/components/DefinitionNodeForm/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState, useMemo, useRef } from 'react'; 2 | import { Modal, Input, Form, Select, Spin } from 'antd'; 3 | import { request } from 'umi'; 4 | import debounce from 'lodash/debounce'; 5 | import { SERVER_PREFIX } from '@/utils/consts'; 6 | 7 | export default ({ definitionNode, onCancel, onOk }) => { 8 | const [form] = Form.useForm(); 9 | 10 | const [fetching, setFetching] = useState(false); 11 | const [options, setOptions] = useState([]); 12 | const fetchRef = useRef(0); 13 | const debounceFetcher = useMemo(() => { 14 | const loadOptions = (value) => { 15 | fetchRef.current += 1; 16 | const fetchId = fetchRef.current; 17 | setOptions([]); 18 | setFetching(true); 19 | request('/core/admin/search-rel-class', { 20 | prefix: SERVER_PREFIX, 21 | credentials: 'include', 22 | params: { 23 | keyword: value, 24 | } 25 | }).then(res => { 26 | if (fetchId !== fetchRef.current) { 27 | return; 28 | } 29 | 30 | setOptions(res.data.map(item => ({ value: item.name, label: `${item.simpleName} - ${item.note}` }))); 31 | setFetching(false); 32 | }) 33 | }; 34 | 35 | return debounce(loadOptions, 1000); 36 | }, []); 37 | 38 | useEffect(() => { 39 | debounceFetcher(definitionNode.relClass); 40 | }, [definitionNode, debounceFetcher]); 41 | 42 | return ( 43 | 49 | { 56 | onOk({ 57 | ...values, 58 | nodeName: values.name, 59 | }); 60 | }} 61 | labelCol={{ span: 4 }} 62 | wrapperCol={{ span: 20 }} 63 | > 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 81 | 82 | 83 | 84 | ) 85 | } -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/common/config/SwaggerConfig.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.common.config; 2 | 3 | import org.lecoder.easyflow.EasyflowApplication; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.context.annotation.Profile; 7 | import springfox.documentation.builders.ApiInfoBuilder; 8 | import springfox.documentation.builders.PathSelectors; 9 | import springfox.documentation.builders.RequestHandlerSelectors; 10 | import springfox.documentation.service.*; 11 | import springfox.documentation.spi.DocumentationType; 12 | import springfox.documentation.spi.service.contexts.SecurityContext; 13 | import springfox.documentation.spring.web.plugins.Docket; 14 | 15 | import java.util.ArrayList; 16 | import java.util.Arrays; 17 | import java.util.List; 18 | 19 | /** 20 | * swagger配置 21 | * test:测试环境下启用 22 | * @author: lijile 23 | * @date: 2021/10/25 14:00 24 | * @version: 1.0 25 | */ 26 | @Profile({"test"}) 27 | @Configuration 28 | public class SwaggerConfig { 29 | 30 | @Bean 31 | public Docket createRestApi() { 32 | return new Docket(DocumentationType.OAS_30) 33 | .apiInfo(apiInfo()) 34 | .select() 35 | .apis(RequestHandlerSelectors.basePackage(EasyflowApplication.class.getPackage().getName())) 36 | .paths(PathSelectors.any()) 37 | .build() 38 | .securitySchemes(securitySchemes()) 39 | .securityContexts(securityContexts()); 40 | } 41 | 42 | private ApiInfo apiInfo() { 43 | return new ApiInfoBuilder() 44 | .title("简易流程审批系统") 45 | .description("简易流程审批系统API文档") 46 | .version("1.0.0") 47 | .build(); 48 | } 49 | 50 | private List securitySchemes() { 51 | List securitySchemeList = new ArrayList<>(); 52 | securitySchemeList.add(new ApiKey("Authorization", "Authorization", "header")); 53 | return securitySchemeList; 54 | } 55 | 56 | private List securityContexts() { 57 | return Arrays.asList(SecurityContext.builder() 58 | .securityReferences(defaultAuth()) 59 | .forPaths(PathSelectors.regex("/.*")) 60 | .build() 61 | ); 62 | } 63 | 64 | private List defaultAuth() { 65 | return Arrays.asList( 66 | new SecurityReference("Authorization", 67 | new AuthorizationScope[]{new AuthorizationScope("global", "accessEverything")})); 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /easyflow-html/src/app.jsx: -------------------------------------------------------------------------------- 1 | import { PageLoading } from '@ant-design/pro-layout'; 2 | import { history, Link } from 'umi'; 3 | import RightContent from '@/components/RightContent'; 4 | import Footer from '@/components/Footer'; 5 | import { currentUser as queryCurrentUser } from './services/api'; 6 | import { BookOutlined, LinkOutlined } from '@ant-design/icons'; 7 | import errorHandler from '@/utils/errorHandler'; 8 | import { ReactComponent as FlowLogo } from '/public/flow.svg' 9 | const isDev = process.env.NODE_ENV === 'development'; 10 | const loginPath = '/user/login'; 11 | /** 获取用户信息比较慢的时候会展示一个 loading */ 12 | 13 | export const dva = { 14 | config: { 15 | onError(e) { 16 | // 异常拦截 17 | e.preventDefault(); 18 | console.error(e.message); 19 | }, 20 | } 21 | }; 22 | 23 | export const request = { 24 | errorHandler, 25 | // 自定义端口规范 26 | errorConfig: { 27 | adaptor: res => { 28 | return { 29 | success: res?.code === 200, 30 | data: res.data, 31 | errorMessage: res.msg, 32 | }; 33 | }, 34 | }, 35 | middlewares: [], 36 | }; 37 | 38 | export const initialStateConfig = { 39 | loading: , 40 | }; 41 | /** 42 | * @see https://umijs.org/zh-CN/plugins/plugin-initial-state 43 | * */ 44 | 45 | export async function getInitialState() { 46 | const fetchUserInfo = async () => { 47 | try { 48 | const msg = await queryCurrentUser(); 49 | return msg.data; 50 | } catch (error) { 51 | history.push(loginPath); 52 | } 53 | 54 | return undefined; 55 | }; // 如果是登录页面,不执行 56 | 57 | if (history.location.pathname !== loginPath) { 58 | const currentUser = await fetchUserInfo(); 59 | return { 60 | fetchUserInfo, 61 | currentUser, 62 | settings: {}, 63 | }; 64 | } 65 | 66 | return { 67 | fetchUserInfo, 68 | settings: {}, 69 | }; 70 | } // ProLayout 支持的api https://procomponents.ant.design/components/layout 71 | 72 | export const layout = ({ initialState }) => { 73 | return { 74 | rightContentRender: () => , 75 | disableContentMargin: false, 76 | waterMarkProps: { 77 | content: initialState?.currentUser?.fullname, 78 | }, 79 | footerRender: () =>
, 80 | onPageChange: () => { 81 | const { location } = history; // 如果没有登录,重定向到 login 82 | 83 | if (!initialState?.currentUser && location.pathname !== loginPath) { 84 | history.push(loginPath); 85 | } 86 | }, 87 | menuHeaderRender: undefined, 88 | // 自定义 403 页面 89 | // unAccessible:
unAccessible
, 90 | ...initialState?.settings, 91 | logo: , 92 | }; 93 | }; 94 | -------------------------------------------------------------------------------- /easyflow-html/src/pages/admin/xflow/config-menu.js: -------------------------------------------------------------------------------- 1 | import { createCtxMenuConfig } from '@antv/xflow'; 2 | import { MenuItemType } from '@antv/xflow'; 3 | import { IconStore, XFlowNodeCommands, XFlowEdgeCommands } from '@antv/xflow'; 4 | import { DeleteOutlined, EditOutlined, PlusOutlined } from '@ant-design/icons'; 5 | 6 | /** 注册菜单依赖的icon */ 7 | IconStore.set('DeleteOutlined', DeleteOutlined); 8 | IconStore.set('EditOutlined', EditOutlined); 9 | IconStore.set('PlusOutlined', PlusOutlined); 10 | 11 | export const useMenuConfig = ({ onCreate, onUpdate, onDelete, onRoot }) => { 12 | const CREATE_NODE = { 13 | id: XFlowNodeCommands.ADD_NODE.id, 14 | label: '新建节点', 15 | iconName: 'PlusOutlined', 16 | onClick: async ({ target, commandService }) => { 17 | if (onCreate) { 18 | onCreate(target); 19 | } 20 | }, 21 | }; 22 | 23 | const UPDATE_NODE = { 24 | id: XFlowNodeCommands.UPDATE_NODE.id, 25 | label: '修改节点', 26 | iconName: 'EditOutlined', 27 | onClick: async ({ target, commandService, aa }) => { 28 | if (onUpdate) { 29 | onUpdate(target); 30 | } 31 | }, 32 | }; 33 | 34 | const DELETE_NODE = { 35 | id: XFlowNodeCommands.DEL_NODE.id, 36 | label: '删除节点', 37 | iconName: 'DeleteOutlined', 38 | onClick: async ({ target, commandService }) => { 39 | commandService.executeCommand(XFlowNodeCommands.DEL_NODE.id, { 40 | nodeConfig: { id: target.data.id }, 41 | }); 42 | if (onDelete) { 43 | onDelete(target); 44 | } 45 | }, 46 | }; 47 | 48 | const ROOT_NODE = { 49 | id: XFlowNodeCommands.UPDATE_NODE.id, 50 | label: '修改名称', 51 | iconName: 'EditOutlined', 52 | onClick: async ({ target, commandService }) => { 53 | if (onRoot) { 54 | onRoot(target); 55 | } 56 | }, 57 | }; 58 | 59 | const EMPTY_MENU = { 60 | id: 'EMPTY_MENU_ITEM', 61 | label: '暂无可用', 62 | isEnabled: false, 63 | }; 64 | 65 | return createCtxMenuConfig((config) => { 66 | config.setMenuModelService(async (data, model, modelService, toDispose) => { 67 | const { type } = data; 68 | const id = data.data?.cell?.id; 69 | switch (type) { 70 | case 'node': 71 | model.setValue({ 72 | id: 'root', 73 | type: MenuItemType.Root, 74 | submenu: 75 | id === 'root1' ? [ROOT_NODE, CREATE_NODE] : [CREATE_NODE, UPDATE_NODE, DELETE_NODE], 76 | }); 77 | break; 78 | default: 79 | model.setValue({ 80 | id: 'root', 81 | type: MenuItemType.Root, 82 | submenu: [EMPTY_MENU], 83 | }); 84 | break; 85 | } 86 | }); 87 | })(); 88 | }; 89 | -------------------------------------------------------------------------------- /src/main/java/org/lecoder/easyflow/modules/core/service/impl/FlowDefinitionServiceImpl.java: -------------------------------------------------------------------------------- 1 | package org.lecoder.easyflow.modules.core.service.impl; 2 | 3 | import cn.hutool.core.util.RandomUtil; 4 | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; 5 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 6 | import org.lecoder.easyflow.modules.core.dto.DefinitionFormDTO; 7 | import org.lecoder.easyflow.modules.core.entity.FlowDefinition; 8 | import org.lecoder.easyflow.modules.core.entity.FlowDefinitionNode; 9 | import org.lecoder.easyflow.modules.core.mapper.FlowDefinitionMapper; 10 | import org.lecoder.easyflow.modules.core.mapper.FlowDefinitionNodeMapper; 11 | import org.lecoder.easyflow.modules.core.service.IFlowDefinitionService; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.stereotype.Service; 14 | import org.springframework.transaction.annotation.Transactional; 15 | 16 | /** 17 | *

18 | * 流程定义表 服务实现类 19 | *

20 | * 21 | * @author lijile 22 | * @since 2021-10-25 23 | */ 24 | @Service 25 | public class FlowDefinitionServiceImpl extends ServiceImpl implements IFlowDefinitionService { 26 | 27 | @Autowired 28 | private FlowDefinitionNodeMapper flowDefinitionNodeMapper; 29 | 30 | @Override 31 | public FlowDefinition getByCode(String definitionCode) { 32 | LambdaQueryWrapper definitionLambdaQueryWrapper = new LambdaQueryWrapper(); 33 | definitionLambdaQueryWrapper.eq(FlowDefinition::getDefinitionCode, definitionCode); 34 | return getOne(definitionLambdaQueryWrapper); 35 | } 36 | 37 | @Override 38 | @Transactional(rollbackFor = Exception.class) 39 | public boolean saveDefinition(DefinitionFormDTO definitionForm) { 40 | FlowDefinition flowDefinition = new FlowDefinition(); 41 | flowDefinition.setDefinitionName(definitionForm.getDefinitionName()); 42 | flowDefinition.setDefinitionCode("def_" + RandomUtil.randomString(10)); 43 | // 回显 44 | definitionForm.setDefinitionCode(flowDefinition.getDefinitionCode()); 45 | return save(flowDefinition); 46 | } 47 | 48 | @Override 49 | @Transactional(rollbackFor = Exception.class) 50 | public boolean deleteDefinition(String definitionCode) { 51 | FlowDefinition flowDefinition = getByCode(definitionCode); 52 | LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); 53 | queryWrapper.eq(FlowDefinitionNode::getDefinitionCode, flowDefinition.getDefinitionCode()); 54 | flowDefinitionNodeMapper.delete(queryWrapper); 55 | return removeById(flowDefinition.getId()); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /easyflow-html/src/locales/zh-CN/settings.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'app.settings.menuMap.basic': '基本设置', 3 | 'app.settings.menuMap.security': '安全设置', 4 | 'app.settings.menuMap.binding': '账号绑定', 5 | 'app.settings.menuMap.notification': '新消息通知', 6 | 'app.settings.basic.avatar': '头像', 7 | 'app.settings.basic.change-avatar': '更换头像', 8 | 'app.settings.basic.email': '邮箱', 9 | 'app.settings.basic.email-message': '请输入您的邮箱!', 10 | 'app.settings.basic.nickname': '昵称', 11 | 'app.settings.basic.nickname-message': '请输入您的昵称!', 12 | 'app.settings.basic.profile': '个人简介', 13 | 'app.settings.basic.profile-message': '请输入个人简介!', 14 | 'app.settings.basic.profile-placeholder': '个人简介', 15 | 'app.settings.basic.country': '国家/地区', 16 | 'app.settings.basic.country-message': '请输入您的国家或地区!', 17 | 'app.settings.basic.geographic': '所在省市', 18 | 'app.settings.basic.geographic-message': '请输入您的所在省市!', 19 | 'app.settings.basic.address': '街道地址', 20 | 'app.settings.basic.address-message': '请输入您的街道地址!', 21 | 'app.settings.basic.phone': '联系电话', 22 | 'app.settings.basic.phone-message': '请输入您的联系电话!', 23 | 'app.settings.basic.update': '更新基本信息', 24 | 'app.settings.security.strong': '强', 25 | 'app.settings.security.medium': '中', 26 | 'app.settings.security.weak': '弱', 27 | 'app.settings.security.password': '账户密码', 28 | 'app.settings.security.password-description': '当前密码强度', 29 | 'app.settings.security.phone': '密保手机', 30 | 'app.settings.security.phone-description': '已绑定手机', 31 | 'app.settings.security.question': '密保问题', 32 | 'app.settings.security.question-description': '未设置密保问题,密保问题可有效保护账户安全', 33 | 'app.settings.security.email': '备用邮箱', 34 | 'app.settings.security.email-description': '已绑定邮箱', 35 | 'app.settings.security.mfa': 'MFA 设备', 36 | 'app.settings.security.mfa-description': '未绑定 MFA 设备,绑定后,可以进行二次确认', 37 | 'app.settings.security.modify': '修改', 38 | 'app.settings.security.set': '设置', 39 | 'app.settings.security.bind': '绑定', 40 | 'app.settings.binding.taobao': '绑定淘宝', 41 | 'app.settings.binding.taobao-description': '当前未绑定淘宝账号', 42 | 'app.settings.binding.alipay': '绑定支付宝', 43 | 'app.settings.binding.alipay-description': '当前未绑定支付宝账号', 44 | 'app.settings.binding.dingding': '绑定钉钉', 45 | 'app.settings.binding.dingding-description': '当前未绑定钉钉账号', 46 | 'app.settings.binding.bind': '绑定', 47 | 'app.settings.notification.password': '账户密码', 48 | 'app.settings.notification.password-description': '其他用户的消息将以站内信的形式通知', 49 | 'app.settings.notification.messages': '系统消息', 50 | 'app.settings.notification.messages-description': '系统消息将以站内信的形式通知', 51 | 'app.settings.notification.todo': '待办任务', 52 | 'app.settings.notification.todo-description': '待办任务将以站内信的形式通知', 53 | 'app.settings.open': '开', 54 | 'app.settings.close': '关', 55 | }; 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |

6 |

7 | 8 | 9 |

10 | 11 | ## 项目介绍 12 | easyflow 是一个简单、易用、高效的流程审批项目。 13 | 14 | 目前有不少的工作流引擎项目,例如JBPM、Activiti、Flowable等等,但是有个比较普遍存在的问题是,过于复杂,不易上手。 15 | 举个栗子,Activiti6.0以act_开头的核心数据表有28个之多,执行一个简单的流程涉及数据库操作太多,优化困难。 16 | 17 | easyflow参考了Activiti的优秀设计思路,取其精华,去掉不常用功能,核心数据库仅此5个,一个简单的工作流,只需要执行几行语句即可生成。支持在线编辑流程。 18 | 19 | 20 | 21 | 22 | ### 项目演示 23 | ![](https://raw.githubusercontent.com/lijile/easyflow/master/doc/images/flow_definition_v2.jpg) 24 | ![](https://raw.githubusercontent.com/lijile/easyflow/master/doc/images/task_list.jpg) 25 | ![](https://raw.githubusercontent.com/lijile/easyflow/master/doc/images/task_approve.jpg) 26 | 27 | 28 | 29 | ## 技术选型 30 | 31 | ### 后端技术架构 32 | 33 | | 技术 | 说明 | 官网 | 34 | | ------------ | --------------- | ---- | 35 | | Spring Boot | 容器+MVC | https://spring.io/projects/spring-boot | 36 | | Mybatis | ORM框架 | http://www.mybatis.org/mybatis-3/zh/index.html | 37 | | Mybatis Plus | Mybatis的增强版 | https://mp.baomidou.com/guide | 38 | | Swagger | 文档生产工具 | https://github.com/swagger-api/swagger-ui | 39 | 40 | 41 | ### 前端技术架构 42 | 43 | | 技术 | 说明 | 官网 | 44 | | ---------- | -------------- | -------------------------------------------- | 45 | | React | 前端框架 | https://react.docschina.org/ | 46 | | Umi | 阿里系前端框架 | https://umijs.org/zh-CN/docs/getting-started | 47 | | Ant Design | 前端组件 | https://ant.design/components/overview-cn/ | 48 | | Antv/xflow | 蚂蚁系图编辑引擎 | https://xflow.antv.vision/zh-CN | 49 | 50 | 51 | 52 | 53 | ## 环境搭建 54 | 55 | ### 开发工具 56 | 57 | 工具 | 说明 | 官网 58 | ----|----|---- 59 | IDEA | 开发IDE | https://www.jetbrains.com/idea/download 60 | RedisDesktop | redis客户端连接工具 | https://redisdesktop.com/download 61 | Navicat | 数据库连接工具 | http://www.formysql.com/xiazai.html 62 | MindMaster | 思维导图设计工具 | http://www.edrawsoft.cn/mindmaster 63 | PicPick | 屏幕取色工具 | https://picpick.app/zh/ 64 | 65 | ### 开发环境 66 | 67 | 工具 | 版本号 | 下载 68 | ----|----|---- 69 | JDK | 1.8 | https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html 70 | Mysql | 8.0 | https://www.mysql.com/ 71 | 72 | 73 | 74 | ## 搭建步骤 75 | 76 | ### Windows版 77 | - 本地安装开发环境中的所有工具并启动,具体参考[deploy-windows.md](doc/deploy/deploy_windows.md) 78 | -------------------------------------------------------------------------------- /doc/wiki/swagger2.md: -------------------------------------------------------------------------------- 1 | ## swagger2 + springboot使用文档 2 | 3 | ### 引入依赖 4 | ``` 5 | 6 | io.springfox 7 | springfox-swagger2 8 | 2.9.2 9 | 10 | 11 | 12 | io.springfox 13 | springfox-swagger-ui 14 | 2.9.2 15 | 16 | ``` 17 | 18 | ### 引入配置类 19 | ``` 20 | @Configuration 21 | @EnableSwagger2 22 | @Profile({"test"}) 23 | public class SwaggerConfig { 24 | 25 | @Bean 26 | public Docket createRestApi() { 27 | return new Docket(DocumentationType.SWAGGER_2) 28 | .apiInfo(apiInfo()) 29 | .select() 30 | .apis(RequestHandlerSelectors.basePackage("org.lecoder.easyflow")) 31 | .paths(PathSelectors.any()) 32 | .build(); 33 | } 34 | 35 | private ApiInfo apiInfo() { 36 | return new ApiInfoBuilder() 37 | .title("简易流程系统") 38 | .description("简易流程系统API文档") 39 | .version("1.0.0") 40 | .build(); 41 | } 42 | 43 | } 44 | ``` 45 | 46 | ### 如果需要在测试时,带上登录会话凭证,配置类改为 47 | ``` 48 | @Configuration 49 | @EnableSwagger2 50 | @Profile({"test"}) 51 | public class SwaggerConfig { 52 | 53 | @Bean 54 | public Docket createRestApi() { 55 | return new Docket(DocumentationType.SWAGGER_2) 56 | .apiInfo(apiInfo()) 57 | .select() 58 | .apis(RequestHandlerSelectors.basePackage("org.lecoder.easyflow")) 59 | .paths(PathSelectors.any()) 60 | .build() 61 | .securitySchemes(securitySchemes()) 62 | .securityContexts(securityContexts()); 63 | } 64 | 65 | private ApiInfo apiInfo() { 66 | return new ApiInfoBuilder() 67 | .title("简易流程系统") 68 | .description("简易流程系统API文档") 69 | .version("1.0.0") 70 | .build(); 71 | } 72 | 73 | private List securitySchemes() { 74 | return Arrays.asList(new ApiKey("Authorization", "Authorization", "header")); 75 | } 76 | 77 | private List securityContexts() { 78 | return Arrays.asList(SecurityContext.builder() 79 | .securityReferences(defaultAuth()) 80 | .forPaths(PathSelectors.regex("/.*")) 81 | .build() 82 | ); 83 | } 84 | 85 | private List defaultAuth() { 86 | return Arrays.asList( 87 | new SecurityReference("Authorization", 88 | new AuthorizationScope[]{new AuthorizationScope("global", "accessEverything")})); 89 | } 90 | 91 | } 92 | ``` 93 | 另外需要放开对应的静态页面,和登录地址一样,不验证用户登录 94 | ``` 95 | - /swagger-ui.html # swagger免登陆地址 96 | - /v2/** 97 | - /swagger-resources/** 98 | - /webjars/** 99 | ``` -------------------------------------------------------------------------------- /easyflow-html/src/components/NoticeIcon/NoticeList.jsx: -------------------------------------------------------------------------------- 1 | import { Avatar, List } from 'antd'; 2 | import React from 'react'; 3 | import classNames from 'classnames'; 4 | import styles from './NoticeList.less'; 5 | 6 | const NoticeList = ({ 7 | list = [], 8 | onClick, 9 | onClear, 10 | title, 11 | onViewMore, 12 | emptyText, 13 | showClear = true, 14 | clearText, 15 | viewMoreText, 16 | showViewMore = false, 17 | }) => { 18 | if (!list || list.length === 0) { 19 | return ( 20 |
21 | not found 25 |
{emptyText}
26 |
27 | ); 28 | } 29 | 30 | return ( 31 |
32 | { 36 | const itemCls = classNames(styles.item, { 37 | [styles.read]: item.read, 38 | }); // eslint-disable-next-line no-nested-ternary 39 | 40 | const leftIcon = item.avatar ? ( 41 | typeof item.avatar === 'string' ? ( 42 | 43 | ) : ( 44 | {item.avatar} 45 | ) 46 | ) : null; 47 | return ( 48 | { 52 | onClick?.(item); 53 | }} 54 | > 55 | 60 | {item.title} 61 |
{item.extra}
62 |
63 | } 64 | description={ 65 |
66 |
{item.description}
67 |
{item.datetime}
68 |
69 | } 70 | /> 71 | 72 | ); 73 | }} 74 | /> 75 |
76 | {showClear ? ( 77 |
78 | {clearText} {title} 79 |
80 | ) : null} 81 | {showViewMore ? ( 82 |
{ 84 | if (onViewMore) { 85 | onViewMore(e); 86 | } 87 | }} 88 | > 89 | {viewMoreText} 90 |
91 | ) : null} 92 |
93 | 94 | ); 95 | }; 96 | 97 | export default NoticeList; 98 | -------------------------------------------------------------------------------- /easyflow-html/src/components/RightContent/AvatarDropdown.jsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback } from 'react'; 2 | import { LogoutOutlined, SettingOutlined, UserOutlined } from '@ant-design/icons'; 3 | import { Avatar, Menu, Spin } from 'antd'; 4 | import { history, useModel } from 'umi'; 5 | import { stringify } from 'querystring'; 6 | import HeaderDropdown from '../HeaderDropdown'; 7 | import styles from './index.less'; 8 | import { logout } from '@/services/api'; 9 | 10 | /** 11 | * 退出登录,并且将当前的 url 保存 12 | */ 13 | const loginOut = async () => { 14 | await logout(); 15 | const { query = {}, pathname } = history.location; 16 | const { redirect } = query; // Note: There may be security issues, please note 17 | 18 | if (window.location.pathname !== '/user/login' && !redirect) { 19 | history.replace({ 20 | pathname: '/user/login', 21 | search: stringify({ 22 | redirect: pathname + window.location.search, 23 | }), 24 | }); 25 | } 26 | }; 27 | 28 | const AvatarDropdown = ({ menu }) => { 29 | const { initialState, setInitialState } = useModel('@@initialState'); 30 | const onMenuClick = useCallback( 31 | (event) => { 32 | const { key } = event; 33 | 34 | if (key === 'logout') { 35 | setInitialState((s) => ({ ...s, currentUser: undefined })); 36 | loginOut(); 37 | return; 38 | } 39 | 40 | history.push(`/account/${key}`); 41 | }, 42 | [setInitialState], 43 | ); 44 | const loading = ( 45 | 46 | 53 | 54 | ); 55 | 56 | if (!initialState) { 57 | return loading; 58 | } 59 | 60 | const { currentUser } = initialState; 61 | 62 | if (!currentUser || !currentUser.fullname) { 63 | return loading; 64 | } 65 | 66 | const menuHeaderDropdown = ( 67 | 68 | {menu && ( 69 | 70 | 71 | 个人中心 72 | 73 | )} 74 | {menu && ( 75 | 76 | 77 | 个人设置 78 | 79 | )} 80 | {menu && } 81 | 82 | 83 | 84 | 退出登录 85 | 86 | 87 | ); 88 | return ( 89 | 90 | 91 | } /> 92 | {currentUser.fullname} 93 | 94 | 95 | ); 96 | }; 97 | 98 | export default AvatarDropdown; 99 | -------------------------------------------------------------------------------- /easyflow-html/src/pages/leave/ApplyList.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { Card, Divider, Table } from 'antd'; 3 | import { connect, Link } from 'umi'; 4 | import { PageContainer } from '@ant-design/pro-layout'; 5 | import QueryFilter from './components/QueryFilter'; 6 | import ApplyForm from './components/ApplyForm'; 7 | import InstanceStatus from '@/components/InstanceStatus'; 8 | 9 | const ApplyList = ({ condition, pagination, records, dispatch }) => { 10 | 11 | const [createModalVisible, setCreateModalVisible] = useState(); 12 | 13 | const columns = [ 14 | { 15 | title: '流程编号', 16 | render: (text, record) => {record.instanceCode}, 17 | }, 18 | { 19 | title: '请假类型', 20 | dataIndex: 'type', 21 | }, 22 | { 23 | title: '状态', 24 | render: (text, record) => 25 | }, 26 | { 27 | title: '请假时间', 28 | render: (text, record) => `${record['startDate']} 至 ${record['endDate']}`, 29 | }, 30 | { 31 | title: '请假天数', 32 | dataIndex: 'leaveDay', 33 | }, 34 | { 35 | title: '操作', 36 | render: (text, record) => 查看, 37 | }, 38 | ]; 39 | 40 | useEffect(() => { 41 | dispatch({ 42 | type: 'leave/list', 43 | payload: { 44 | pageNo: 1, 45 | pageSize: 10, 46 | ...condition, 47 | }, 48 | }); 49 | }, []); 50 | 51 | return ( 52 | 53 | 54 | { 57 | const form = values; 58 | dispatch({ type: 'leave/updateState', payload: { condition: form } }); 59 | dispatch({ 60 | type: 'leave/list', 61 | payload: form, 62 | }); 63 | }} 64 | onCreate={() => setCreateModalVisible(true)} 65 | /> 66 | 67 |
73 | dispatch({ 74 | type: 'leave/list', 75 | payload: { 76 | pageNo: p.current, 77 | pageSize: p.pageSize, 78 | ...condition, 79 | }, 80 | }) 81 | } 82 | /> 83 | 84 | { 85 | createModalVisible && 86 | { 88 | dispatch({ 89 | type: 'leave/submit', 90 | payload: values, 91 | }); 92 | setCreateModalVisible(false); 93 | }} 94 | onCancel={() => setCreateModalVisible(false)} /> 95 | } 96 | 97 | ); 98 | }; 99 | export default connect(({ leave }) => ({ 100 | ...leave, 101 | }))(ApplyList); 102 | --------------------------------------------------------------------------------