├── 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 |
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 extends Payload>[] 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 |
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 | 
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 |
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 extends ILeaveTypeHandler> 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 extends IFlowBaseService> 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 |
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 |
27 |
32 |
33 |
34 |
35 |
36 |
37 | }
39 | type="primary"
40 | htmlType="submit"
41 | style={{ marginRight: 20 }}
42 | >
43 | 搜索
44 |
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 |
27 |
32 |
33 |
34 |
35 |
36 |
37 | }
39 | type="primary"
40 | htmlType="submit"
41 | style={{ marginRight: 20 }}
42 | >
43 | 搜索
44 |
45 | } onClick={onCreate} style={{ marginRight: 20 }}>
46 | 新建
47 |
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 |
54 |
59 |
60 |
61 | : null}
66 | options={options}
67 | />
68 |
69 |
70 |
71 | )
72 | }
--------------------------------------------------------------------------------
/src/main/java/org/lecoder/easyflow/modules/leave/controller/LeaveController.java:
--------------------------------------------------------------------------------
1 | package org.lecoder.easyflow.modules.leave.controller;
2 |
3 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
4 | import io.swagger.annotations.Api;
5 | import io.swagger.annotations.ApiOperation;
6 | import org.lecoder.easyflow.common.web.CommonResult;
7 | import org.lecoder.easyflow.modules.leave.dto.ApplyListDTO;
8 | import org.lecoder.easyflow.modules.leave.dto.LeaveFormDTO;
9 | import org.lecoder.easyflow.modules.leave.dto.QueryFilterDTO;
10 | import org.lecoder.easyflow.modules.leave.entity.LeaveApply;
11 | import org.lecoder.easyflow.modules.leave.service.ILeaveService;
12 | import org.springframework.beans.factory.annotation.Autowired;
13 | import org.springframework.web.bind.annotation.*;
14 |
15 | import javax.validation.Valid;
16 |
17 | /**
18 | * 请假控制器
19 | *
20 | * @author: lijile
21 | * @date: 2021/10/27 14:18
22 | * @version: 1.0
23 | */
24 | @Api(tags = "leave.LeaveController")
25 | @RestController
26 | @RequestMapping("/leave")
27 | public class LeaveController {
28 |
29 | /**
30 | * 提交请假表单
31 | * @author: lijile
32 | * @date: 2021/10/27 14:35
33 | * @param leaveForm
34 | * @return
35 | */
36 | @ApiOperation("提交请假申请")
37 | @PostMapping("/submit")
38 | public CommonResult submit(@Valid @RequestBody LeaveFormDTO leaveForm) {
39 | LeaveApply leaveApply = leaveService.submit(leaveForm);
40 | return CommonResult.success(leaveApply);
41 | }
42 |
43 | /**
44 | * 申请列表
45 | * @author: lijile
46 | * @date: 2021/10/29 14:27
47 | * @param queryFilter
48 | * @param pageNo
49 | * @param pageSize
50 | * @return
51 | */
52 | @ApiOperation("申请列表")
53 | @GetMapping("/apply-list")
54 | public CommonResult applyList(QueryFilterDTO queryFilter,
55 | @RequestParam(defaultValue = "1") Integer pageNo,
56 | @RequestParam(defaultValue = "10") Integer pageSize) {
57 | Page page = new Page<>(pageNo, pageSize);
58 | return CommonResult.success(leaveService.applyList(page, queryFilter));
59 | }
60 |
61 | @Autowired private ILeaveService leaveService;
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/src/test/java/org/lecoder/easyflow/core/FlowApiServiceTest.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.FlowInstanceNode;
7 | import org.lecoder.easyflow.modules.core.enums.FlowModuleEnum;
8 | import org.lecoder.easyflow.modules.core.service.IFlowApiService;
9 | import org.springframework.beans.factory.annotation.Autowired;
10 | import org.springframework.boot.test.context.SpringBootTest;
11 | import org.springframework.test.context.junit4.SpringRunner;
12 |
13 | import java.math.BigDecimal;
14 | import java.util.HashMap;
15 | import java.util.List;
16 | import java.util.Map;
17 |
18 | /**
19 | * 流程接口测试
20 | *
21 | * @author: lijile
22 | * @date: 2021/10/25 16:31
23 | * @version: 1.0
24 | */
25 | @SpringBootTest
26 | @RunWith(SpringRunner.class)
27 | public class FlowApiServiceTest {
28 |
29 | @Test
30 | public void start() {
31 | Map variables = new HashMap<>();
32 | variables.put("type", "事假");
33 | variables.put("days", new BigDecimal(3));
34 | String instanceCode = flowApiService.start(FlowModuleEnum.LEAVE, "leave_common", variables);
35 | Assert.assertNotNull(instanceCode);
36 | }
37 |
38 | @Test
39 | public void agree() {
40 | flowApiService.agree("n5b9ow0g0kir1raa1j1j", "");
41 | }
42 |
43 | @Test
44 | public void preview() {
45 | Map variables = new HashMap<>();
46 | variables.put("type", "事假");
47 | variables.put("days", new BigDecimal(7));
48 | List instanceNodeList = flowApiService.preview("leave_common", variables);
49 | Assert.assertTrue(instanceNodeList.size() > 0);
50 | for (int i = 0; i < instanceNodeList.size(); i++) {
51 | FlowInstanceNode instanceNode = instanceNodeList.get(i);
52 | System.out.print("======");
53 | for (int j = 0; j < i; j++) {
54 | System.out.print("===");
55 | }
56 | System.out.print(instanceNode.getNodeId() + ":");
57 | System.out.print(instanceNode.getNodeName() + ":");
58 | System.out.print(instanceNode.getFullname());
59 | System.out.println();
60 | }
61 | }
62 |
63 | @Autowired private IFlowApiService flowApiService;
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/easyflow-html/src/pages/flow/MyTask.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } 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 InstanceStatus from '@/components/InstanceStatus';
7 | import NodeStatus from '@/components/NodeStatus';
8 | import { formatDate } from '@/utils/basic';
9 |
10 | const MyTask = ({ condition, pagination, records, dispatch }) => {
11 |
12 | const columns = [
13 | {
14 | title: '流程编号',
15 | render: (text, record) => {record.instanceCode},
16 | },
17 | {
18 | title: '模块',
19 | dataIndex: 'moduleName',
20 | },
21 | {
22 | title: '申请人',
23 | dataIndex: 'fullname',
24 | },
25 | {
26 | title: '申请状态',
27 | render: (text, record) =>
28 | },
29 | {
30 | title: '审批状态',
31 | render: (text, record) =>
32 | },
33 | {
34 | title: '申请时间',
35 | render: (text, record) => formatDate(record['applyTime']),
36 | },
37 | {
38 | title: '操作',
39 | render: (text, record) => 查看,
40 | },
41 | ];
42 |
43 | useEffect(() => {
44 | dispatch({
45 | type: 'flow/list',
46 | payload: {
47 | pageNo: 1,
48 | pageSize: 10,
49 | ...condition,
50 | },
51 | });
52 | }, []);
53 |
54 | return (
55 |
56 |
57 | {
60 | const form = values;
61 | dispatch({ type: 'flow/updateState', payload: { condition: form } });
62 | dispatch({
63 | type: 'flow/list',
64 | payload: form,
65 | });
66 | }}
67 | />
68 |
69 |
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 |
65 |
66 |
67 |
68 |
69 |
70 |
71 | : null}
76 | options={options}
77 | />
78 |
79 |
80 |
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 | 
24 | 
25 | 
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 |

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