├── README.md ├── server ├── .gitignore ├── README.md ├── doc │ ├── app.png │ ├── flow-data-demo.js │ ├── flow.png │ ├── form.png │ ├── start.png │ ├── test.sql │ └── todo.png ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── gyfish │ │ │ └── formflow │ │ │ ├── FormFlow.java │ │ │ ├── config │ │ │ ├── Constant.java │ │ │ ├── CorsConfig.java │ │ │ └── MongoConfig.java │ │ │ ├── controller │ │ │ ├── AppController.java │ │ │ ├── FlowController.java │ │ │ ├── FormController.java │ │ │ ├── IndexController.java │ │ │ ├── TaskController.java │ │ │ └── UserController.java │ │ │ ├── domain │ │ │ ├── App.java │ │ │ ├── User.java │ │ │ ├── flow │ │ │ │ ├── Flow.java │ │ │ │ ├── FlowAction.java │ │ │ │ ├── FlowNode.java │ │ │ │ ├── FlowStatus.java │ │ │ │ ├── Process.java │ │ │ │ └── Task.java │ │ │ └── form │ │ │ │ └── FormMeta.java │ │ │ ├── exception │ │ │ ├── AppException.java │ │ │ └── FlowException.java │ │ │ ├── flow │ │ │ ├── Engine.java │ │ │ ├── engine │ │ │ │ └── FlowEngine.java │ │ │ ├── event │ │ │ │ └── FlowEvent.java │ │ │ ├── filter │ │ │ │ ├── FlowFilter.java │ │ │ │ ├── FlowFilterChain.java │ │ │ │ └── GlobalFlowFilter.java │ │ │ └── listener │ │ │ │ └── FlowListener.java │ │ │ ├── service │ │ │ ├── AppService.java │ │ │ ├── FlowService.java │ │ │ ├── FormService.java │ │ │ ├── ProcessService.java │ │ │ ├── TaskService.java │ │ │ └── UserService.java │ │ │ ├── util │ │ │ ├── AppResponse.java │ │ │ └── BeanUtil.java │ │ │ └── vo │ │ │ ├── AppInfoVo.java │ │ │ ├── FlowQuery.java │ │ │ ├── FormQuery.java │ │ │ ├── TaskQuery.java │ │ │ ├── TaskVo.java │ │ │ ├── UserQuery.java │ │ │ └── UserVo.java │ └── resources │ │ └── application.yml │ └── test │ └── java │ └── com │ └── gyfish │ └── formflow │ ├── FormFlowTests.java │ ├── RedisTest.java │ └── util │ ├── BeanUtilTest.java │ ├── JsonTest.java │ ├── RunTimeTest.java │ └── StreamTest.java └── web ├── .gitignore ├── .prettierrc ├── README.md ├── babel.config.js ├── package-lock.json ├── package.json ├── public ├── Jerry.jpg ├── Tom.jpg ├── favicon.ico └── index.html ├── src ├── App.vue ├── apis │ ├── AppApi.ts │ ├── FlowApi.ts │ ├── TaskApi.ts │ ├── UserApi.ts │ ├── api.ts │ └── formApi.ts ├── assets │ ├── avatar1.jpg │ ├── form-flow.png │ ├── logo.png │ ├── yayi.jpg │ ├── yayi1.png │ └── yayi2.jpg ├── components │ ├── admin │ │ ├── AdminLayout.vue │ │ ├── AppInfo.vue │ │ ├── FlowAdmin.vue │ │ ├── FormAdmin.vue │ │ └── UserAdmin.vue │ ├── app-room │ │ ├── AppCard.vue │ │ └── NewCard.vue │ ├── client │ │ ├── ClientLayout.vue │ │ ├── Created.vue │ │ ├── Done.vue │ │ ├── Start.vue │ │ ├── TaskCard.vue │ │ └── Todo.vue │ ├── flow │ │ ├── FlowConfig.vue │ │ ├── FlowItem.vue │ │ ├── G6Register.ts │ │ ├── Maker.tsx │ │ ├── NodeCard.tsx │ │ └── index.ts │ ├── form │ │ ├── FormConfig.tsx │ │ ├── FormItem.tsx │ │ ├── configs │ │ │ ├── InputConfig.vue │ │ │ └── SelectConfig.vue │ │ ├── display │ │ │ └── CardView.vue │ │ ├── index.ts │ │ └── items │ │ │ ├── Input.vue │ │ │ └── Select.vue │ └── report │ │ └── AppReport.vue ├── main.ts ├── routes │ └── router.ts ├── shims-tsx.d.ts ├── shims-vue.d.ts ├── stores │ └── store.ts ├── styles │ ├── app-admin.scss │ ├── app-client.scss │ ├── app-store.scss │ ├── app-task.scss │ ├── flow-editor.scss │ ├── form-editor.scss │ ├── home.scss │ └── index.scss ├── typings │ └── index.d.ts ├── util │ ├── DateUtil.ts │ └── UuidUtil.ts └── views │ ├── AppRoom.vue │ ├── FlowEditor.vue │ ├── FormEditor.vue │ └── Home.vue └── tsconfig.json /README.md: -------------------------------------------------------------------------------- 1 | # form-flow 2 | 3 | 预览:http://47.94.198.39 4 | 5 | 目标是做一个自定义表单,自定义简单流程的东西,学习和实践。 6 | 7 | 项目结构 8 | ``` 9 | |-- server // 后台 10 | |-- web // 前端 11 | ``` 12 | 13 | App之间相互独立 14 | ![](server/doc/app.png) 15 | 16 | 目前支持自定义线性流程 17 | ![](server/doc/flow.png) 18 | 19 | 自定义表单 20 | ![](server/doc/form.png) 21 | 22 | 新建任务 23 | ![](server/doc/start.png) 24 | 25 | 处理任务 26 | ![](server/doc/todo.png) -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | /target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | 5 | ### STS ### 6 | .apt_generated 7 | .classpath 8 | .factorypath 9 | .project 10 | .settings 11 | .springBeans 12 | .sts4-cache 13 | 14 | ### IntelliJ IDEA ### 15 | .idea 16 | *.iws 17 | *.iml 18 | *.ipr 19 | 20 | ### NetBeans ### 21 | /nbproject/private/ 22 | /nbbuild/ 23 | /dist/ 24 | /nbdist/ 25 | /.nb-gradle/ 26 | /build/ 27 | 28 | ### VS Code ### 29 | .vscode/ 30 | -------------------------------------------------------------------------------- /server/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GyFish/form-flow/c8b07c4dab3f07e0ca3c0231a5ac4bbc0988060c/server/README.md -------------------------------------------------------------------------------- /server/doc/app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GyFish/form-flow/c8b07c4dab3f07e0ca3c0231a5ac4bbc0988060c/server/doc/app.png -------------------------------------------------------------------------------- /server/doc/flow-data-demo.js: -------------------------------------------------------------------------------- 1 | var data = { 2 | nodes: [{ 3 | id: 'node1', 4 | x: 100, 5 | y: 200, 6 | shape: 'circle', 7 | style: { 8 | fill: 'red' 9 | } 10 | //img: 'https://gw.alipayobjects.com/os/s/prod/antv/assets/image/logo-with-text-73b8a.svg' 11 | }, { 12 | id: 'node2', 13 | x: 300, 14 | y: 200, 15 | label: '萧庆', 16 | labelCfg: { 17 | position: 'bottom' 18 | }, 19 | shape: 'image', 20 | img: 'https://img2.bosszhipin.com/boss/avatar/avatar_13.png' 21 | }, { 22 | id: 'node3', 23 | x: 400, 24 | y: 100, 25 | shape: 'image', 26 | label: '语雀', 27 | labelCfg: { 28 | position: 'bottom' 29 | }, 30 | img: 'https://gw.alipayobjects.com/zos/rmsportal/XuVpGqBFxXplzvLjJBZB.svg' 31 | }, { 32 | id: 'node4', 33 | x: 400, 34 | y: 400, 35 | shape: 'image', 36 | img: '//img.alicdn.com/tfs/TB1_uT8a5ERMeJjSspiXXbZLFXa-143-59.png' 37 | }], 38 | edges: [{ 39 | id: 'edge1', 40 | target: 'node2', 41 | source: 'node1', 42 | style: { 43 | endArrow: true 44 | }, 45 | label: '你好,我好', 46 | labelCfg: { 47 | style: {stroke: 'white', lineWidth: 5} // 加白边框 48 | } 49 | }, { 50 | source: 'node2', 51 | target: 'node3', 52 | style: { 53 | endArrow: true 54 | }, 55 | shape: 'quadratic', 56 | label: '过去的线', 57 | labelCfg: { 58 | refY: -10, 59 | refX: 0, 60 | autoRotate: true, 61 | style: { 62 | fill: 'red' 63 | } 64 | } 65 | }, 66 | { 67 | source: 'node3', 68 | target: 'node2', 69 | style: { 70 | endArrow: true, 71 | stroke: 'red' 72 | }, 73 | size: 2, 74 | shape: 'quadratic', 75 | label: '回来的线', 76 | labelCfg: { 77 | refY: -10, 78 | refX: 0, 79 | autoRotate: true, 80 | style: { 81 | fill: 'red' 82 | } 83 | } 84 | }, { 85 | source: 'node3', 86 | target: 'node4', 87 | style: { 88 | endArrow: true, 89 | stroke: 'blue', 90 | lineDash: [2, 2] 91 | }, 92 | shape: 'my-edge', 93 | label: '随便连连\n换行', 94 | curveLevel: 4, 95 | labelCfg: { 96 | refY: -20, 97 | refX: 0, 98 | autoRotate: true, 99 | style: { 100 | fill: 'red' 101 | } 102 | } 103 | }] 104 | }; -------------------------------------------------------------------------------- /server/doc/flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GyFish/form-flow/c8b07c4dab3f07e0ca3c0231a5ac4bbc0988060c/server/doc/flow.png -------------------------------------------------------------------------------- /server/doc/form.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GyFish/form-flow/c8b07c4dab3f07e0ca3c0231a5ac4bbc0988060c/server/doc/form.png -------------------------------------------------------------------------------- /server/doc/start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GyFish/form-flow/c8b07c4dab3f07e0ca3c0231a5ac4bbc0988060c/server/doc/start.png -------------------------------------------------------------------------------- /server/doc/test.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Navicat MySQL Data Transfer 3 | 4 | Source Server : localhost 5 | Source Server Type : MySQL 6 | Source Server Version : 50725 7 | Source Host : localhost:3306 8 | Source Schema : test 9 | 10 | Target Server Type : MySQL 11 | Target Server Version : 50725 12 | File Encoding : 65001 13 | 14 | Date: 25/03/2019 16:45:20 15 | */ 16 | 17 | SET NAMES utf8mb4; 18 | SET FOREIGN_KEY_CHECKS = 0; 19 | 20 | -- ---------------------------- 21 | -- Table structure for app 22 | -- ---------------------------- 23 | DROP TABLE IF EXISTS `app`; 24 | CREATE TABLE `app` ( 25 | `id` int(32) NOT NULL, 26 | `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '应用名称', 27 | `form_id` int(32) NULL DEFAULT NULL COMMENT '关联表单id', 28 | `process_id` int(11) NULL DEFAULT NULL, 29 | PRIMARY KEY (`id`) USING BTREE 30 | ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; 31 | 32 | -- ---------------------------- 33 | -- Table structure for flow_action 34 | -- ---------------------------- 35 | DROP TABLE IF EXISTS `flow_action`; 36 | CREATE TABLE `flow_action` ( 37 | `id` int(11) NOT NULL AUTO_INCREMENT, 38 | `action_data` json NULL, 39 | `process_id` int(11) NULL DEFAULT NULL, 40 | PRIMARY KEY (`id`) USING BTREE 41 | ) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; 42 | 43 | -- ---------------------------- 44 | -- Table structure for flow_node 45 | -- ---------------------------- 46 | DROP TABLE IF EXISTS `flow_node`; 47 | CREATE TABLE `flow_node` ( 48 | `id` int(11) NOT NULL AUTO_INCREMENT, 49 | `node_data` json NULL, 50 | `process_id` int(11) NULL DEFAULT NULL, 51 | PRIMARY KEY (`id`) USING BTREE 52 | ) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; 53 | 54 | -- ---------------------------- 55 | -- Table structure for flow_status 56 | -- ---------------------------- 57 | DROP TABLE IF EXISTS `flow_status`; 58 | CREATE TABLE `flow_status` ( 59 | `id` int(11) NOT NULL, 60 | `value` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, 61 | `label` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, 62 | `process_id` int(11) NULL DEFAULT NULL, 63 | PRIMARY KEY (`id`) USING BTREE 64 | ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; 65 | 66 | -- ---------------------------- 67 | -- Table structure for form 68 | -- ---------------------------- 69 | DROP TABLE IF EXISTS `form`; 70 | CREATE TABLE `form` ( 71 | `id` int(32) NOT NULL AUTO_INCREMENT, 72 | `title` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '表单名称', 73 | `created_at` datetime(6) NULL DEFAULT NULL, 74 | `updated_at` datetime(6) NULL DEFAULT NULL, 75 | PRIMARY KEY (`id`) USING BTREE 76 | ) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; 77 | 78 | -- ---------------------------- 79 | -- Table structure for form_item 80 | -- ---------------------------- 81 | DROP TABLE IF EXISTS `form_item`; 82 | CREATE TABLE `form_item` ( 83 | `id` int(32) NOT NULL, 84 | `form_id` int(32) NULL DEFAULT NULL COMMENT '关联表单id', 85 | `prop` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '字段key', 86 | `type` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '类型', 87 | `label` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '字段名称', 88 | `placeholder` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '占位值', 89 | `icon` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '图标', 90 | `input_type` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '输入框类型', 91 | `created_at` datetime(6) NULL DEFAULT NULL, 92 | `updated_at` datetime(6) NULL DEFAULT NULL, 93 | PRIMARY KEY (`id`) USING BTREE 94 | ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; 95 | 96 | -- ---------------------------- 97 | -- Table structure for form_option 98 | -- ---------------------------- 99 | DROP TABLE IF EXISTS `form_option`; 100 | CREATE TABLE `form_option` ( 101 | `id` int(32) NOT NULL, 102 | `item_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '关联字段id', 103 | `label` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '选项名称', 104 | `value` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '选项key', 105 | PRIMARY KEY (`id`) USING BTREE 106 | ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; 107 | 108 | -- ---------------------------- 109 | -- Table structure for process 110 | -- ---------------------------- 111 | DROP TABLE IF EXISTS `process`; 112 | CREATE TABLE `process` ( 113 | `id` int(11) NOT NULL, 114 | PRIMARY KEY (`id`) USING BTREE 115 | ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; 116 | 117 | -- ---------------------------- 118 | -- Table structure for task 119 | -- ---------------------------- 120 | DROP TABLE IF EXISTS `task`; 121 | CREATE TABLE `task` ( 122 | `id` int(11) NOT NULL, 123 | PRIMARY KEY (`id`) USING BTREE 124 | ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; 125 | 126 | SET FOREIGN_KEY_CHECKS = 1; 127 | -------------------------------------------------------------------------------- /server/doc/todo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GyFish/form-flow/c8b07c4dab3f07e0ca3c0231a5ac4bbc0988060c/server/doc/todo.png -------------------------------------------------------------------------------- /server/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | org.springframework.boot 8 | spring-boot-starter-parent 9 | 2.1.3.RELEASE 10 | 11 | 12 | 13 | com.gyfish 14 | form-flow 15 | 0.0.1-SNAPSHOT 16 | 17 | form-flow 18 | some form, some flow 19 | 4.0.0 20 | 21 | 22 | 1.8 23 | 24 | 25 | 26 | 27 | 28 | org.springframework.cloud 29 | spring-cloud-dependencies 30 | Finchley.SR2 31 | pom 32 | import 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | org.springframework.boot 41 | spring-boot-starter-web 42 | 43 | 44 | org.springframework.boot 45 | spring-boot-starter-test 46 | test 47 | 48 | 49 | 50 | org.springframework.boot 51 | spring-boot-starter-data-redis 52 | 53 | 54 | org.springframework.boot 55 | spring-boot-starter-data-mongodb 56 | 57 | 58 | 59 | 60 | org.projectlombok 61 | lombok 62 | 63 | 64 | com.alibaba 65 | fastjson 66 | 1.2.38 67 | 68 | 69 | com.google.guava 70 | guava 71 | 22.0 72 | 73 | 74 | 75 | 76 | 77 | 78 | form-flow 79 | 80 | 81 | 82 | 83 | org.springframework.boot 84 | spring-boot-maven-plugin 85 | 2.0.2.RELEASE 86 | 87 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /server/src/main/java/com/gyfish/formflow/FormFlow.java: -------------------------------------------------------------------------------- 1 | package com.gyfish.formflow; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | /** 7 | * @author geyu 8 | */ 9 | @SpringBootApplication 10 | public class FormFlow { 11 | 12 | public static void main(String[] args) { 13 | SpringApplication.run(FormFlow.class, args); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /server/src/main/java/com/gyfish/formflow/config/Constant.java: -------------------------------------------------------------------------------- 1 | package com.gyfish.formflow.config; 2 | 3 | /** 4 | * @author geyu 5 | */ 6 | public class Constant { 7 | 8 | public static String TODO_SHOW_FIRST = "firstTask"; 9 | 10 | public static String TODO_SHOW_LAST = "lastTask"; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /server/src/main/java/com/gyfish/formflow/config/CorsConfig.java: -------------------------------------------------------------------------------- 1 | package com.gyfish.formflow.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.web.servlet.config.annotation.CorsRegistry; 6 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 7 | 8 | /** 9 | * @author geyu 10 | */ 11 | @Configuration 12 | public class CorsConfig { 13 | 14 | @Bean 15 | public WebMvcConfigurer corsConfigurer() { 16 | 17 | return new WebMvcConfigurer() { 18 | @Override 19 | public void addCorsMappings(CorsRegistry registry) { 20 | registry.addMapping("/**") 21 | .allowedOrigins("*") 22 | .allowCredentials(true) 23 | .allowedMethods("GET", "POST", "DELETE", "PUT","PATCH") 24 | // 预检请求的有效时间 25 | .maxAge(3600); 26 | } 27 | }; 28 | } 29 | } -------------------------------------------------------------------------------- /server/src/main/java/com/gyfish/formflow/config/MongoConfig.java: -------------------------------------------------------------------------------- 1 | package com.gyfish.formflow.config; 2 | 3 | import com.mongodb.MongoClientOptions; 4 | 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | 8 | /** 9 | * @author geyu 10 | */ 11 | @Configuration 12 | public class MongoConfig { 13 | 14 | @Bean 15 | public MongoClientOptions mongoOptions() { 16 | return MongoClientOptions 17 | .builder() 18 | .maxConnectionIdleTime(7 * 24 * 60 * 60 * 1000) 19 | .build(); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /server/src/main/java/com/gyfish/formflow/controller/AppController.java: -------------------------------------------------------------------------------- 1 | package com.gyfish.formflow.controller; 2 | 3 | import com.gyfish.formflow.util.AppResponse; 4 | import com.gyfish.formflow.service.AppService; 5 | import com.gyfish.formflow.vo.AppInfoVo; 6 | 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.web.bind.annotation.DeleteMapping; 9 | import org.springframework.web.bind.annotation.GetMapping; 10 | import org.springframework.web.bind.annotation.PostMapping; 11 | import org.springframework.web.bind.annotation.RequestBody; 12 | import org.springframework.web.bind.annotation.RequestMapping; 13 | import org.springframework.web.bind.annotation.RestController; 14 | 15 | /** 16 | * @author geyu 17 | */ 18 | @RestController 19 | public class AppController { 20 | 21 | private final AppService appService; 22 | 23 | @Autowired 24 | public AppController(AppService appService) { 25 | this.appService = appService; 26 | } 27 | 28 | @GetMapping("/app/appList") 29 | public Object appList() { 30 | 31 | return new AppResponse<>().ok(appService.findAll()); 32 | } 33 | 34 | @PostMapping("/app/saveApp") 35 | public AppResponse saveApp(@RequestBody AppInfoVo infoVo) { 36 | 37 | appService.saveApp(infoVo); 38 | return new AppResponse<>().ok("save appInfo ok!"); 39 | } 40 | 41 | @DeleteMapping("/app/deleteApp") 42 | public AppResponse deleteApp(String appId) { 43 | 44 | appService.deleteApp(appId); 45 | return new AppResponse<>().ok("delete appInfo ok!"); 46 | } 47 | 48 | 49 | } 50 | -------------------------------------------------------------------------------- /server/src/main/java/com/gyfish/formflow/controller/FlowController.java: -------------------------------------------------------------------------------- 1 | package com.gyfish.formflow.controller; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.gyfish.formflow.domain.flow.Flow; 5 | import com.gyfish.formflow.service.FlowService; 6 | import com.gyfish.formflow.util.AppResponse; 7 | import com.gyfish.formflow.vo.FlowQuery; 8 | 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.web.bind.annotation.DeleteMapping; 11 | import org.springframework.web.bind.annotation.GetMapping; 12 | import org.springframework.web.bind.annotation.PostMapping; 13 | import org.springframework.web.bind.annotation.RequestBody; 14 | import org.springframework.web.bind.annotation.RequestMapping; 15 | import org.springframework.web.bind.annotation.RestController; 16 | 17 | import java.util.List; 18 | 19 | import lombok.extern.slf4j.Slf4j; 20 | 21 | /** 22 | * 流程相关接口控制器 23 | * 24 | * @author geyu 25 | */ 26 | @RestController 27 | @RequestMapping("/flow") 28 | @Slf4j 29 | public class FlowController { 30 | 31 | private final FlowService flowService; 32 | 33 | @Autowired 34 | public FlowController(FlowService flowService) { 35 | this.flowService = flowService; 36 | } 37 | 38 | @PostMapping("/save") 39 | public Object save(@RequestBody Flow flow) { 40 | 41 | log.info("\n>> 保存流程信息,flow = {}", JSON.toJSONString(flow, true)); 42 | 43 | flowService.save(flow); 44 | 45 | return new AppResponse<>().ok("save flow ok!"); 46 | } 47 | 48 | @DeleteMapping("/delete") 49 | public Object delete(String id) { 50 | 51 | flowService.delete(id); 52 | 53 | return new AppResponse<>().ok("delete flow ok!"); 54 | } 55 | 56 | @PostMapping("/getList") 57 | public Object getList(@RequestBody FlowQuery flowQuery) { 58 | 59 | return new AppResponse<>().ok(flowService.getList(flowQuery)); 60 | } 61 | 62 | @GetMapping("/getByUser") 63 | public Object getByUser(String userId) { 64 | 65 | List flows = flowService.getByUser(userId); 66 | 67 | return new AppResponse<>().ok(flows); 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /server/src/main/java/com/gyfish/formflow/controller/FormController.java: -------------------------------------------------------------------------------- 1 | package com.gyfish.formflow.controller; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.gyfish.formflow.domain.form.FormMeta; 5 | import com.gyfish.formflow.service.FormService; 6 | import com.gyfish.formflow.util.AppResponse; 7 | import com.gyfish.formflow.vo.FormQuery; 8 | 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.web.bind.annotation.DeleteMapping; 11 | import org.springframework.web.bind.annotation.GetMapping; 12 | import org.springframework.web.bind.annotation.PostMapping; 13 | import org.springframework.web.bind.annotation.RequestBody; 14 | import org.springframework.web.bind.annotation.RequestMapping; 15 | import org.springframework.web.bind.annotation.ResponseBody; 16 | import org.springframework.web.bind.annotation.RestController; 17 | 18 | import lombok.extern.slf4j.Slf4j; 19 | 20 | /** 21 | * @author geyu 22 | */ 23 | @RestController 24 | @RequestMapping("/form") 25 | @Slf4j 26 | public class FormController { 27 | 28 | private final FormService formService; 29 | 30 | @Autowired 31 | public FormController(FormService formService) { 32 | this.formService = formService; 33 | } 34 | 35 | @PostMapping("/saveForm") 36 | public Object saveForm(@RequestBody FormMeta vo) { 37 | 38 | log.info("|保存表单设计| vo = {}", JSON.toJSONString(vo, true)); 39 | 40 | formService.saveForm(vo); 41 | 42 | log.info("|保存表单成功| id = {}"); 43 | 44 | return new AppResponse<>().ok("ok!", vo); 45 | } 46 | 47 | @DeleteMapping("/deleteForm") 48 | public Object deleteForm(String id) { 49 | 50 | log.info("|删除表单| id = {}", id); 51 | 52 | formService.deleteForm(id); 53 | 54 | return new AppResponse<>().ok("ok!"); 55 | } 56 | 57 | @GetMapping("/getFormById") 58 | public Object getFormById(String id) { 59 | 60 | log.info("|获取表单设计| id = {}", id); 61 | 62 | return new AppResponse<>().ok("ok!", formService.findById(id)); 63 | } 64 | 65 | @PostMapping("/getList") 66 | public Object getList(@RequestBody FormQuery formQuery) { 67 | 68 | log.info("|获取表单列表|"); 69 | 70 | return new AppResponse<>().ok(formService.getList(formQuery)); 71 | } 72 | 73 | 74 | } 75 | -------------------------------------------------------------------------------- /server/src/main/java/com/gyfish/formflow/controller/IndexController.java: -------------------------------------------------------------------------------- 1 | package com.gyfish.formflow.controller; 2 | 3 | import org.springframework.web.bind.annotation.GetMapping; 4 | import org.springframework.web.bind.annotation.RestController; 5 | 6 | import lombok.extern.slf4j.Slf4j; 7 | 8 | /** 9 | * @author geyu 10 | */ 11 | @RestController 12 | @Slf4j 13 | public class IndexController { 14 | 15 | @GetMapping("/") 16 | public String home() { 17 | 18 | log.info("/-- hello, i am ok!"); 19 | return "hello, i am ok!"; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /server/src/main/java/com/gyfish/formflow/controller/TaskController.java: -------------------------------------------------------------------------------- 1 | package com.gyfish.formflow.controller; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.gyfish.formflow.domain.flow.Task; 5 | import com.gyfish.formflow.service.TaskService; 6 | import com.gyfish.formflow.util.AppResponse; 7 | import com.gyfish.formflow.vo.TaskQuery; 8 | import com.gyfish.formflow.vo.TaskVo; 9 | 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.web.bind.annotation.GetMapping; 12 | import org.springframework.web.bind.annotation.PostMapping; 13 | import org.springframework.web.bind.annotation.RequestBody; 14 | import org.springframework.web.bind.annotation.RequestMapping; 15 | import org.springframework.web.bind.annotation.RestController; 16 | 17 | import java.util.List; 18 | 19 | import lombok.extern.slf4j.Slf4j; 20 | 21 | /** 22 | * @author geyu 23 | */ 24 | @RestController 25 | @RequestMapping("/task") 26 | @Slf4j 27 | public class TaskController { 28 | 29 | private final TaskService taskService; 30 | 31 | @Autowired 32 | public TaskController(TaskService taskService) { 33 | this.taskService = taskService; 34 | } 35 | 36 | @PostMapping("/start") 37 | public Object start(@RequestBody TaskVo vo) { 38 | 39 | taskService.start(vo); 40 | 41 | return new AppResponse<>().ok("创建成功!"); 42 | } 43 | 44 | @PostMapping("/commit") 45 | public Object commit(@RequestBody TaskVo vo) { 46 | 47 | taskService.commit(vo); 48 | 49 | return new AppResponse<>().ok("提交成功!"); 50 | } 51 | 52 | @GetMapping("/preTask") 53 | public Object preTask(String taskId) { 54 | 55 | return new AppResponse<>().ok("ok!", taskService.preTask(taskId)); 56 | } 57 | 58 | @GetMapping("/getByProcess") 59 | public Object getByProcess(String processId) { 60 | 61 | log.info("|查询流程内所有任务| processId = {}", processId); 62 | 63 | List task = taskService.getByProcess(processId); 64 | 65 | return new AppResponse<>().ok("ok!", task); 66 | } 67 | 68 | @PostMapping("/query") 69 | public Object query(@RequestBody TaskQuery vo) { 70 | 71 | log.info("|查询task| queryVo = {}", JSON.toJSONString(vo, true)); 72 | 73 | List result = taskService.query(vo); 74 | 75 | return new AppResponse<>().ok(result); 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /server/src/main/java/com/gyfish/formflow/controller/UserController.java: -------------------------------------------------------------------------------- 1 | package com.gyfish.formflow.controller; 2 | 3 | import com.gyfish.formflow.service.UserService; 4 | import com.gyfish.formflow.util.AppResponse; 5 | import com.gyfish.formflow.vo.UserQuery; 6 | import com.gyfish.formflow.vo.UserVo; 7 | 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.web.bind.annotation.DeleteMapping; 10 | import org.springframework.web.bind.annotation.PostMapping; 11 | import org.springframework.web.bind.annotation.RequestBody; 12 | import org.springframework.web.bind.annotation.RequestMapping; 13 | import org.springframework.web.bind.annotation.RestController; 14 | 15 | /** 16 | * 用户接口 17 | * 18 | * @author geyu 19 | */ 20 | @RestController 21 | @RequestMapping("/user") 22 | public class UserController { 23 | 24 | private final UserService userService; 25 | 26 | @Autowired 27 | public UserController(UserService userService) { 28 | this.userService = userService; 29 | } 30 | 31 | /** 32 | * 新增用户 33 | */ 34 | @PostMapping("/save") 35 | public Object addUser(@RequestBody UserVo userVo) { 36 | 37 | return new AppResponse<>().ok("保存用户成功!", userService.save(userVo)); 38 | } 39 | 40 | /** 41 | * 获取用户列表 42 | */ 43 | @PostMapping("/userList") 44 | public Object userList(@RequestBody UserQuery userQuery) { 45 | 46 | return new AppResponse<>().ok(userService.userList(userQuery)); 47 | } 48 | 49 | /** 50 | * 删除用户 51 | */ 52 | @DeleteMapping("/deleteUser") 53 | public Object deleteUser(String id) { 54 | 55 | userService.deleteUser(id); 56 | 57 | return new AppResponse<>().ok("删除用户成功!"); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /server/src/main/java/com/gyfish/formflow/domain/App.java: -------------------------------------------------------------------------------- 1 | package com.gyfish.formflow.domain; 2 | 3 | import org.springframework.data.annotation.Id; 4 | import org.springframework.data.mongodb.core.mapping.Document; 5 | 6 | import lombok.Data; 7 | 8 | /** 9 | * @author geyu 10 | */ 11 | @Data 12 | @Document 13 | public class App { 14 | 15 | @Id 16 | private String id; 17 | 18 | private String title; 19 | 20 | private String description; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /server/src/main/java/com/gyfish/formflow/domain/User.java: -------------------------------------------------------------------------------- 1 | package com.gyfish.formflow.domain; 2 | 3 | import org.springframework.data.annotation.Id; 4 | import org.springframework.data.mongodb.core.mapping.Document; 5 | 6 | import java.util.ArrayList; 7 | import java.util.Date; 8 | import java.util.List; 9 | 10 | import lombok.Data; 11 | 12 | /** 13 | * 应用的用户 14 | * 15 | * @author geyu 16 | */ 17 | @Data 18 | @Document("user") 19 | public class User { 20 | 21 | @Id 22 | private String id; 23 | 24 | private String appId; 25 | 26 | private String userNo; 27 | 28 | private String userName; 29 | 30 | private Date createTime; 31 | 32 | private List flowList; 33 | 34 | public void addFlow(String flowId) { 35 | if (this.flowList == null) { 36 | this.flowList = new ArrayList<>(); 37 | } 38 | this.flowList.add(flowId); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /server/src/main/java/com/gyfish/formflow/domain/flow/Flow.java: -------------------------------------------------------------------------------- 1 | package com.gyfish.formflow.domain.flow; 2 | 3 | import org.springframework.data.annotation.Id; 4 | import org.springframework.data.mongodb.core.mapping.Document; 5 | 6 | import java.util.Date; 7 | import java.util.List; 8 | 9 | import lombok.Data; 10 | 11 | /** 12 | * @author geyu 13 | */ 14 | @Data 15 | @Document("flow") 16 | public class Flow { 17 | 18 | @Id 19 | private String id; 20 | 21 | private String appId; 22 | 23 | private String uuid; 24 | 25 | private String title; 26 | 27 | /** 28 | * todo展示,可选 firstTask / lastTask 29 | */ 30 | private String todoShow; 31 | 32 | private Date createTime; 33 | 34 | private Date updateTime; 35 | 36 | private List nodes; 37 | 38 | } 39 | -------------------------------------------------------------------------------- /server/src/main/java/com/gyfish/formflow/domain/flow/FlowAction.java: -------------------------------------------------------------------------------- 1 | package com.gyfish.formflow.domain.flow; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * 流程的一步动作,包含过滤器和监听器,以及规则 7 | * 8 | * @author geyu 9 | */ 10 | @Data 11 | public class FlowAction { 12 | 13 | private int id; 14 | 15 | private int processId; 16 | 17 | private String actionName; 18 | 19 | FlowNode sourceNode; 20 | 21 | FlowNode targetNode; 22 | 23 | } 24 | -------------------------------------------------------------------------------- /server/src/main/java/com/gyfish/formflow/domain/flow/FlowNode.java: -------------------------------------------------------------------------------- 1 | package com.gyfish.formflow.domain.flow; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * 流程节点, 7 | * 8 | * @author geyu 9 | */ 10 | @Data 11 | public class FlowNode { 12 | 13 | private Integer pk; 14 | 15 | private String id; 16 | 17 | private String uuid; 18 | 19 | private String nextNodeId; 20 | 21 | private String formId; 22 | 23 | private String processId; 24 | 25 | //-------------------------------- 26 | 27 | private String nodeName; 28 | 29 | private String nodeType; 30 | 31 | private String status; 32 | 33 | private Boolean canFlowBack; 34 | 35 | //-------------------------------- 36 | 37 | private String handlerId; 38 | 39 | private String handlerName; 40 | 41 | private String handlerGroupId; 42 | 43 | private String handlerGroupName; 44 | 45 | 46 | } 47 | -------------------------------------------------------------------------------- /server/src/main/java/com/gyfish/formflow/domain/flow/FlowStatus.java: -------------------------------------------------------------------------------- 1 | package com.gyfish.formflow.domain.flow; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | 6 | /** 7 | * 流程状态 8 | * 9 | * @author geyu 10 | */ 11 | @Data 12 | @AllArgsConstructor 13 | public class FlowStatus { 14 | 15 | public static final FlowStatus NEW = new FlowStatus("NEW", "新建"); 16 | 17 | public static final FlowStatus TODO = new FlowStatus("TODO", "待处理"); 18 | 19 | public static final FlowStatus DOING = new FlowStatus("DOING", "处理中"); 20 | 21 | public static final FlowStatus TOCHECK = new FlowStatus("TOCHECK", "待审批"); 22 | 23 | public static final FlowStatus CHECKED = new FlowStatus("CHECKED", "已审批"); 24 | 25 | 26 | private String statusValue; 27 | 28 | private String statusLabel; 29 | 30 | } 31 | -------------------------------------------------------------------------------- /server/src/main/java/com/gyfish/formflow/domain/flow/Process.java: -------------------------------------------------------------------------------- 1 | package com.gyfish.formflow.domain.flow; 2 | 3 | import org.springframework.data.annotation.Id; 4 | import org.springframework.data.mongodb.core.mapping.Document; 5 | 6 | import java.util.Date; 7 | 8 | import lombok.Data; 9 | 10 | /** 11 | * 流程,运营管理 12 | * 13 | * @author geyu 14 | */ 15 | @Data 16 | @Document("process") 17 | public class Process { 18 | 19 | @Id 20 | private String id; 21 | 22 | private String appId; 23 | 24 | private String flowId; 25 | 26 | private String nodeId; 27 | 28 | private String processName; 29 | 30 | private String status; 31 | 32 | private String creator; 33 | 34 | private String handler; 35 | 36 | private String closer; 37 | 38 | private Date createTime; 39 | 40 | private Date closeTime; 41 | 42 | private Date updateTime; 43 | 44 | } 45 | -------------------------------------------------------------------------------- /server/src/main/java/com/gyfish/formflow/domain/flow/Task.java: -------------------------------------------------------------------------------- 1 | package com.gyfish.formflow.domain.flow; 2 | 3 | import org.springframework.data.annotation.Id; 4 | import org.springframework.data.mongodb.core.mapping.Document; 5 | 6 | import java.util.Date; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | import lombok.Data; 11 | 12 | /** 13 | * 流程的一个任务,描述当前到了哪个节点,该由谁处理 14 | * 15 | * @author geyu 16 | */ 17 | @Data 18 | @Document("task") 19 | public class Task { 20 | 21 | @Id 22 | private String id; 23 | 24 | private String appId; 25 | 26 | private String previousId; 27 | 28 | private String nextId; 29 | 30 | private String processId; 31 | 32 | private String processName; 33 | 34 | private String nodeId; 35 | 36 | private String formId; 37 | 38 | private String userId; 39 | 40 | private String userGroupId; 41 | 42 | private String taskName; 43 | 44 | private List formData; 45 | 46 | /** 47 | * TODO, DOING, DONE 48 | */ 49 | private String status; 50 | 51 | private Date createTime; 52 | 53 | private Date updateTime; 54 | 55 | } 56 | -------------------------------------------------------------------------------- /server/src/main/java/com/gyfish/formflow/domain/form/FormMeta.java: -------------------------------------------------------------------------------- 1 | package com.gyfish.formflow.domain.form; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | 5 | import org.springframework.data.annotation.Id; 6 | import org.springframework.data.mongodb.core.mapping.Document; 7 | 8 | import java.util.Date; 9 | import java.util.List; 10 | 11 | import lombok.Data; 12 | 13 | /** 14 | * @author geyu1 15 | */ 16 | @Data 17 | @Document("form") 18 | public class FormMeta { 19 | 20 | @Id 21 | private String id; 22 | 23 | private String appId; 24 | 25 | private String uuid; 26 | 27 | private String title; 28 | 29 | private String value; 30 | 31 | private Date createTime; 32 | 33 | private Date updateTime; 34 | 35 | private List items; 36 | 37 | } 38 | -------------------------------------------------------------------------------- /server/src/main/java/com/gyfish/formflow/exception/AppException.java: -------------------------------------------------------------------------------- 1 | package com.gyfish.formflow.exception; 2 | 3 | public class AppException extends RuntimeException { 4 | } 5 | -------------------------------------------------------------------------------- /server/src/main/java/com/gyfish/formflow/exception/FlowException.java: -------------------------------------------------------------------------------- 1 | package com.gyfish.formflow.exception; 2 | 3 | public class FlowException extends RuntimeException { 4 | 5 | 6 | 7 | 8 | } 9 | -------------------------------------------------------------------------------- /server/src/main/java/com/gyfish/formflow/flow/Engine.java: -------------------------------------------------------------------------------- 1 | package com.gyfish.formflow.flow; 2 | 3 | import com.gyfish.formflow.domain.User; 4 | import com.gyfish.formflow.domain.flow.FlowNode; 5 | 6 | import org.springframework.stereotype.Component; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * 定义模块应该提供的功能 12 | * 13 | * @author geyu 14 | */ 15 | @Component 16 | public interface Engine { 17 | 18 | List todo(User user); 19 | 20 | List done(User user); 21 | 22 | FlowNode run(FlowNode node); 23 | 24 | } 25 | -------------------------------------------------------------------------------- /server/src/main/java/com/gyfish/formflow/flow/engine/FlowEngine.java: -------------------------------------------------------------------------------- 1 | package com.gyfish.formflow.flow.engine; 2 | 3 | import com.gyfish.formflow.domain.flow.Task; 4 | import com.gyfish.formflow.flow.filter.FlowFilter; 5 | import com.gyfish.formflow.flow.filter.FlowFilterChain; 6 | import com.gyfish.formflow.flow.filter.GlobalFlowFilter; 7 | import com.gyfish.formflow.flow.listener.FlowListener; 8 | import com.gyfish.formflow.domain.flow.Process; 9 | import com.gyfish.formflow.domain.flow.FlowAction; 10 | import com.gyfish.formflow.domain.flow.FlowNode; 11 | import com.gyfish.formflow.flow.event.FlowEvent; 12 | 13 | import org.springframework.stereotype.Service; 14 | 15 | import java.util.List; 16 | import java.util.Map; 17 | 18 | /** 19 | * 引擎,我是谁,我在哪,我要做什么 20 | * 21 | * @author geyu 22 | */ 23 | @Service 24 | public class FlowEngine { 25 | 26 | // 事件对应的动作 27 | private Map eventMap; 28 | 29 | 30 | public Process getProcessById(int id) { 31 | 32 | return null; 33 | } 34 | 35 | public FlowNode getCurNode(int processId) { 36 | 37 | return new FlowNode(); 38 | } 39 | 40 | public FlowAction getActionById(int actionId) { 41 | 42 | return new FlowAction(); 43 | } 44 | 45 | public List getFlowFilersByAction() { 46 | 47 | return null; 48 | } 49 | 50 | public Task run(FlowAction action, FlowEvent e) { 51 | 52 | 53 | // 过滤 54 | new FlowFilterChain(action) 55 | .addFilter(new GlobalFlowFilter()) 56 | .doFilter(e); 57 | 58 | // 回调监听器 59 | 60 | // 触发 61 | 62 | // 回调各个 63 | 64 | return null; 65 | } 66 | 67 | 68 | } 69 | -------------------------------------------------------------------------------- /server/src/main/java/com/gyfish/formflow/flow/event/FlowEvent.java: -------------------------------------------------------------------------------- 1 | package com.gyfish.formflow.flow.event; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * 通过事件触发状态的变更,相当于 vo 的对象,作为 flow action 的参数 7 | * 8 | * @author geyu 9 | */ 10 | @Data 11 | public class FlowEvent { 12 | 13 | private int processId; 14 | 15 | private int actionId; 16 | 17 | } 18 | -------------------------------------------------------------------------------- /server/src/main/java/com/gyfish/formflow/flow/filter/FlowFilter.java: -------------------------------------------------------------------------------- 1 | package com.gyfish.formflow.flow.filter; 2 | 3 | import com.gyfish.formflow.flow.event.FlowEvent; 4 | 5 | public interface FlowFilter { 6 | 7 | void doFilter(FlowEvent e, FlowFilterChain chain); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /server/src/main/java/com/gyfish/formflow/flow/filter/FlowFilterChain.java: -------------------------------------------------------------------------------- 1 | package com.gyfish.formflow.flow.filter; 2 | 3 | import com.gyfish.formflow.domain.flow.FlowAction; 4 | import com.gyfish.formflow.flow.event.FlowEvent; 5 | 6 | /** 7 | * @author geyu 8 | */ 9 | public class FlowFilterChain { 10 | 11 | private int n = 0; 12 | 13 | private FlowAction flowAction; 14 | 15 | private FlowFilter[] filters = new FlowFilter[0]; 16 | 17 | public FlowFilterChain(FlowAction action) { 18 | this.flowAction = action; 19 | } 20 | 21 | public FlowFilterChain addFilter(FlowFilter flowFilter) { 22 | 23 | for (FlowFilter filter : filters) { 24 | if (filter == flowFilter) { 25 | return this; 26 | } 27 | } 28 | 29 | // 扩容 30 | if (n == filters.length) { 31 | FlowFilter[] newFilters = new FlowFilter[n + 10]; 32 | System.arraycopy(filters, 0, newFilters, 0, n); 33 | filters = newFilters; 34 | } 35 | 36 | filters[n++] = flowFilter; 37 | return this; 38 | } 39 | 40 | public void doFilter(FlowEvent e) { 41 | 42 | int pos = 0; 43 | if (pos >= n) { 44 | 45 | // flowAction.commit(e); 46 | } 47 | 48 | FlowFilter filter = filters[n++]; 49 | filter.doFilter(e, this); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /server/src/main/java/com/gyfish/formflow/flow/filter/GlobalFlowFilter.java: -------------------------------------------------------------------------------- 1 | package com.gyfish.formflow.flow.filter; 2 | 3 | import com.gyfish.formflow.flow.event.FlowEvent; 4 | 5 | public class GlobalFlowFilter implements FlowFilter { 6 | 7 | 8 | @Override 9 | public void doFilter(FlowEvent e, FlowFilterChain chain) { 10 | 11 | System.out.println("=== do sth global ==="); 12 | chain.doFilter(e); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /server/src/main/java/com/gyfish/formflow/flow/listener/FlowListener.java: -------------------------------------------------------------------------------- 1 | package com.gyfish.formflow.flow.listener; 2 | 3 | import com.gyfish.formflow.flow.event.FlowEvent; 4 | 5 | /** 6 | * 流程动作(action)的监听器,同时实现触发器功能 7 | */ 8 | public interface FlowListener { 9 | 10 | void flow(FlowEvent e); 11 | 12 | } 13 | -------------------------------------------------------------------------------- /server/src/main/java/com/gyfish/formflow/service/AppService.java: -------------------------------------------------------------------------------- 1 | package com.gyfish.formflow.service; 2 | 3 | import com.gyfish.formflow.domain.App; 4 | import com.gyfish.formflow.util.BeanUtil; 5 | import com.gyfish.formflow.vo.AppInfoVo; 6 | 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.data.mongodb.core.MongoTemplate; 9 | import org.springframework.stereotype.Service; 10 | 11 | /** 12 | * @author geyu 13 | */ 14 | @Service 15 | public class AppService { 16 | 17 | private final MongoTemplate mongoTemplate; 18 | 19 | @Autowired 20 | public AppService(MongoTemplate mongoTemplate) { 21 | this.mongoTemplate = mongoTemplate; 22 | } 23 | 24 | 25 | public Object findAll() { 26 | 27 | return mongoTemplate.findAll(App.class); 28 | } 29 | 30 | public void saveApp(AppInfoVo infoVo) { 31 | 32 | App app = BeanUtil.copy(infoVo, App.class); 33 | 34 | mongoTemplate.save(app); 35 | } 36 | 37 | public void deleteApp(String appId) { 38 | 39 | App app = new App(); 40 | app.setId(appId); 41 | 42 | mongoTemplate.remove(app); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /server/src/main/java/com/gyfish/formflow/service/FlowService.java: -------------------------------------------------------------------------------- 1 | package com.gyfish.formflow.service; 2 | 3 | import com.gyfish.formflow.domain.User; 4 | import com.gyfish.formflow.domain.flow.Flow; 5 | import com.gyfish.formflow.domain.flow.FlowNode; 6 | import com.gyfish.formflow.domain.flow.Process; 7 | import com.gyfish.formflow.vo.FlowQuery; 8 | 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.data.mongodb.core.MongoTemplate; 11 | import org.springframework.data.mongodb.core.query.Criteria; 12 | import org.springframework.data.mongodb.core.query.Query; 13 | import org.springframework.stereotype.Service; 14 | import org.springframework.transaction.annotation.Transactional; 15 | import org.springframework.util.CollectionUtils; 16 | import org.springframework.util.StringUtils; 17 | 18 | import java.util.Date; 19 | import java.util.List; 20 | 21 | import lombok.extern.slf4j.Slf4j; 22 | 23 | /** 24 | * @author geyu 25 | */ 26 | @Service 27 | @Slf4j 28 | public class FlowService { 29 | 30 | private final MongoTemplate mongoTemplate; 31 | 32 | private final UserService userService; 33 | 34 | @Autowired 35 | public FlowService(MongoTemplate mongoTemplate, UserService userService) { 36 | this.mongoTemplate = mongoTemplate; 37 | this.userService = userService; 38 | } 39 | 40 | @Transactional(rollbackFor = Exception.class) 41 | public void save(Flow meta) { 42 | 43 | // 先删除 44 | String id = meta.getId(); 45 | if (!StringUtils.isEmpty(id)) { 46 | mongoTemplate.remove(meta); 47 | } 48 | 49 | // 设置时间 50 | meta.setUpdateTime(new Date()); 51 | if (meta.getCreateTime() == null) { 52 | meta.setCreateTime(new Date()); 53 | } 54 | 55 | // 设置链表 56 | List nodes = meta.getNodes(); 57 | for (int i = 0; i < nodes.size(); i++) { 58 | FlowNode node = nodes.get(i); 59 | node.setUuid(meta.getUuid()); 60 | if (i < nodes.size() - 1) { 61 | FlowNode nextNode = nodes.get(i + 1); 62 | node.setNextNodeId(nextNode.getId()); 63 | } else { 64 | node.setNextNodeId("end"); 65 | } 66 | } 67 | 68 | mongoTemplate.save(meta); 69 | 70 | // 设置起始节点处理人权限 71 | FlowNode startNode = nodes.get(1); 72 | User user = userService.getById(startNode.getHandlerId()); 73 | 74 | user.addFlow(meta.getId()); 75 | userService.save(user); 76 | } 77 | 78 | public void delete(String id) { 79 | 80 | Flow meta = new Flow(); 81 | meta.setId(id); 82 | 83 | mongoTemplate.remove(meta); 84 | } 85 | 86 | public List getList(FlowQuery flowQuery) { 87 | 88 | Criteria criteria = Criteria.where("appId").is(flowQuery.getAppId()); 89 | Query query = new Query(criteria); 90 | 91 | return mongoTemplate.find(query, Flow.class); 92 | } 93 | 94 | public List getByUser(String userId) { 95 | 96 | User user = mongoTemplate.findById(userId, User.class); 97 | if (user == null) { 98 | return null; 99 | } 100 | 101 | List flowIds = user.getFlowList(); 102 | if (CollectionUtils.isEmpty(flowIds)) { 103 | return null; 104 | } 105 | 106 | Criteria criteria = Criteria.where("id").in(flowIds); 107 | Query query = new Query(criteria); 108 | 109 | return mongoTemplate.find(query, Flow.class); 110 | } 111 | 112 | 113 | FlowNode next(Process p) { 114 | 115 | Flow flow = mongoTemplate.findById(p.getFlowId(), Flow.class); 116 | 117 | if (flow == null) { 118 | log.error("没有这个流程!"); 119 | return null; 120 | } 121 | 122 | List nodes = flow.getNodes(); 123 | 124 | for (int i = 0; i < nodes.size(); i++) { 125 | if (nodes.get(i).getId().equals(p.getNodeId())) { 126 | if (i == nodes.size() - 1) { 127 | return null; 128 | } else { 129 | return nodes.get(i + 1); 130 | } 131 | } 132 | } 133 | 134 | return null; 135 | } 136 | 137 | Flow getById(String id) { 138 | 139 | return mongoTemplate.findById(id, Flow.class); 140 | } 141 | 142 | } 143 | -------------------------------------------------------------------------------- /server/src/main/java/com/gyfish/formflow/service/FormService.java: -------------------------------------------------------------------------------- 1 | package com.gyfish.formflow.service; 2 | 3 | import com.gyfish.formflow.domain.form.FormMeta; 4 | import com.gyfish.formflow.vo.FormQuery; 5 | 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.data.mongodb.core.MongoTemplate; 8 | import org.springframework.data.mongodb.core.query.Criteria; 9 | import org.springframework.data.mongodb.core.query.Query; 10 | import org.springframework.stereotype.Service; 11 | import org.springframework.transaction.annotation.Transactional; 12 | 13 | import java.util.Date; 14 | import java.util.List; 15 | 16 | import lombok.extern.slf4j.Slf4j; 17 | 18 | /** 19 | * @author geyu 20 | */ 21 | @Slf4j 22 | @Service 23 | public class FormService { 24 | 25 | private final MongoTemplate mongoTemplate; 26 | 27 | @Autowired 28 | public FormService(MongoTemplate mongoTemplate) { 29 | this.mongoTemplate = mongoTemplate; 30 | } 31 | 32 | public List getList(FormQuery formQuery) { 33 | 34 | Criteria criteria = Criteria.where("appId").is(formQuery.getAppId()); 35 | Query query = new Query(criteria); 36 | 37 | return mongoTemplate.find(query, FormMeta.class); 38 | } 39 | 40 | @Transactional(rollbackFor = Exception.class) 41 | public void saveForm(FormMeta vo) { 42 | 43 | log.info("save form in mongo..."); 44 | 45 | if (vo.getId() != null) { 46 | mongoTemplate.remove(vo); 47 | } else { 48 | vo.setCreateTime(new Date()); 49 | } 50 | 51 | vo.setUpdateTime(new Date()); 52 | 53 | mongoTemplate.save(vo); 54 | } 55 | 56 | public void deleteForm(String id) { 57 | 58 | FormMeta meta = new FormMeta(); 59 | meta.setId(id); 60 | 61 | mongoTemplate.remove(meta); 62 | } 63 | 64 | public FormMeta findById(String id) { 65 | 66 | return mongoTemplate.findById(id, FormMeta.class); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /server/src/main/java/com/gyfish/formflow/service/ProcessService.java: -------------------------------------------------------------------------------- 1 | package com.gyfish.formflow.service; 2 | 3 | import com.gyfish.formflow.domain.flow.Flow; 4 | import com.gyfish.formflow.domain.flow.FlowNode; 5 | import com.gyfish.formflow.domain.flow.FlowStatus; 6 | import com.gyfish.formflow.domain.flow.Process; 7 | import com.gyfish.formflow.domain.flow.Task; 8 | import com.gyfish.formflow.vo.TaskVo; 9 | 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.data.mongodb.core.MongoTemplate; 12 | import org.springframework.stereotype.Service; 13 | 14 | import java.util.Date; 15 | import java.util.List; 16 | 17 | /** 18 | * 流程 19 | * 20 | * @author geyu 21 | */ 22 | @Service 23 | public class ProcessService { 24 | 25 | private final MongoTemplate mongoTemplate; 26 | 27 | private final FlowService flowService; 28 | 29 | @Autowired 30 | public ProcessService(MongoTemplate mongoTemplate, FlowService flowService) { 31 | this.mongoTemplate = mongoTemplate; 32 | this.flowService = flowService; 33 | } 34 | 35 | public List getProcessList() { 36 | 37 | return null; 38 | } 39 | 40 | Process start(TaskVo vo) { 41 | 42 | Process p = new Process(); 43 | 44 | Flow meta = flowService.getById(vo.getFlowId()); 45 | 46 | p.setFlowId(meta.getId()); 47 | p.setProcessName(meta.getTitle()); 48 | p.setNodeId("startNode"); 49 | 50 | p.setCreator(vo.getUserId()); 51 | 52 | p.setCreateTime(new Date()); 53 | p.setUpdateTime(new Date()); 54 | 55 | p.setStatus(FlowStatus.NEW.getStatusValue()); 56 | 57 | return mongoTemplate.save(p); 58 | } 59 | 60 | public Process getById(String id) { 61 | 62 | return mongoTemplate.findById(id, Process.class); 63 | } 64 | 65 | Process getByTask(Task task) { 66 | 67 | return mongoTemplate.findById(task.getProcessId(), Process.class); 68 | } 69 | 70 | void end(Process p) { 71 | 72 | p.setStatus("END"); 73 | 74 | mongoTemplate.save(p); 75 | } 76 | 77 | Process commit(Task task) { 78 | 79 | Process p = this.getByTask(task); 80 | 81 | FlowNode node = flowService.next(p); 82 | 83 | p.setNodeId(node.getId()); 84 | p.setHandler(node.getHandlerId()); 85 | p.setUpdateTime(new Date()); 86 | 87 | return mongoTemplate.save(p); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /server/src/main/java/com/gyfish/formflow/service/TaskService.java: -------------------------------------------------------------------------------- 1 | package com.gyfish.formflow.service; 2 | 3 | import com.google.common.collect.Lists; 4 | 5 | import com.alibaba.fastjson.JSONObject; 6 | import com.gyfish.formflow.config.Constant; 7 | import com.gyfish.formflow.domain.flow.Flow; 8 | import com.gyfish.formflow.domain.flow.FlowNode; 9 | import com.gyfish.formflow.domain.flow.Process; 10 | import com.gyfish.formflow.domain.flow.Task; 11 | import com.gyfish.formflow.vo.TaskQuery; 12 | import com.gyfish.formflow.vo.TaskVo; 13 | 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | import org.springframework.data.domain.Sort; 16 | import org.springframework.data.mongodb.core.MongoTemplate; 17 | import org.springframework.data.mongodb.core.query.BasicQuery; 18 | import org.springframework.data.mongodb.core.query.Query; 19 | import org.springframework.stereotype.Service; 20 | 21 | import java.util.ArrayList; 22 | import java.util.Date; 23 | import java.util.List; 24 | 25 | import lombok.extern.slf4j.Slf4j; 26 | 27 | /** 28 | * 任务 29 | * 30 | * @author geyu 31 | */ 32 | @Service 33 | @Slf4j 34 | public class TaskService { 35 | 36 | private final MongoTemplate mongoTemplate; 37 | 38 | private final FlowService flowService; 39 | 40 | private final ProcessService processService; 41 | 42 | @Autowired 43 | public TaskService(MongoTemplate mongoTemplate, FlowService flowService, ProcessService processService) { 44 | this.mongoTemplate = mongoTemplate; 45 | this.flowService = flowService; 46 | this.processService = processService; 47 | } 48 | 49 | private Task save(Task task) { 50 | 51 | return mongoTemplate.save(task); 52 | } 53 | 54 | private Task saveTask(Process p, TaskVo vo) { 55 | 56 | Task task = new Task(); 57 | 58 | task.setCreateTime(new Date()); 59 | task.setUpdateTime(new Date()); 60 | 61 | task.setProcessId(p.getId()); 62 | task.setProcessName(p.getProcessName()); 63 | 64 | FlowNode node = flowService.next(p); 65 | if (node == null) { 66 | processService.end(p); 67 | return task; 68 | } 69 | 70 | task.setTaskName(node.getNodeName()); 71 | 72 | String userId = vo.getUserId(); 73 | if (userId != null) { 74 | task.setUserId(vo.getUserId()); 75 | task.setStatus("DONE"); 76 | task.setFormId(vo.getFormId()); 77 | task.setFormData(vo.getFormData()); 78 | } 79 | if (userId == null) { 80 | task.setFormId(node.getFormId()); 81 | task.setUserId(node.getHandlerId()); 82 | task.setStatus("TODO"); 83 | } 84 | 85 | return this.save(task); 86 | } 87 | 88 | /** 89 | * 新建任务,需要参数:flowId,userId,formId,formData 90 | */ 91 | public void start(TaskVo vo) { 92 | 93 | // 开启流程 94 | Process p1 = processService.start(vo); 95 | 96 | // 创建当前任务 97 | Task task = this.saveTask(p1, vo); 98 | 99 | // 前进一步 100 | Process p2 = processService.commit(task); 101 | 102 | // 生成下一个任务 103 | this.saveTask(p2, new TaskVo()); 104 | } 105 | 106 | /** 107 | * 提交任务,需要参数:taskId,formData 108 | */ 109 | public void commit(TaskVo vo) { 110 | 111 | // 更新当前 task 112 | Task task = this.updateTask(vo); 113 | 114 | // 更新流程状态 115 | Process p = processService.commit(task); 116 | 117 | // 生成下一个 task 118 | this.saveTask(p, new TaskVo()); 119 | } 120 | 121 | private Task updateTask(TaskVo vo) { 122 | 123 | Task task = this.getById(vo.getId()); 124 | 125 | task.setFormData(vo.getFormData()); 126 | 127 | task.setUpdateTime(new Date()); 128 | 129 | task.setStatus("DONE"); 130 | 131 | return mongoTemplate.save(task); 132 | } 133 | 134 | 135 | private Task getById(String id) { 136 | return mongoTemplate.findById(id, Task.class); 137 | } 138 | 139 | public List query(TaskQuery vo) { 140 | 141 | JSONObject criteria = new JSONObject(); 142 | 143 | if (vo.getUserId() != null) { 144 | criteria.put("userId", vo.getUserId()); 145 | } 146 | if (vo.getStatus() != null) { 147 | criteria.put("status", vo.getStatus()); 148 | } 149 | if (vo.getProcessId() != null) { 150 | criteria.put("processId", vo.getProcessId()); 151 | } 152 | 153 | Sort sort = new Sort(Sort.Direction.DESC, "createTime"); 154 | 155 | Query query = new BasicQuery(criteria.toJSONString()).with(sort); 156 | 157 | return mongoTemplate.find(query, Task.class); 158 | } 159 | 160 | public List getByProcess(String processId) { 161 | 162 | TaskQuery query = new TaskQuery(); 163 | 164 | query.setProcessId(processId); 165 | 166 | return this.query(query); 167 | } 168 | 169 | /** 170 | * 返回todo页面首部展示的任务 171 | */ 172 | public Task preTask(String taskId) { 173 | 174 | Task t = this.getById(taskId); 175 | List tasks = this.getByProcess(t.getProcessId()); 176 | 177 | Process p = processService.getById(t.getProcessId()); 178 | 179 | Flow f = flowService.getById(p.getFlowId()); 180 | 181 | // 先把自己移除 182 | tasks.remove(t); 183 | 184 | String show = f.getTodoShow(); 185 | 186 | List list = new ArrayList<>(); 187 | if (Constant.TODO_SHOW_LAST.equals(show)) { 188 | list = tasks; 189 | } 190 | 191 | if (Constant.TODO_SHOW_FIRST.equals(show)) { 192 | list = Lists.reverse(tasks); 193 | } 194 | 195 | return list.stream().findFirst().orElse(null); 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /server/src/main/java/com/gyfish/formflow/service/UserService.java: -------------------------------------------------------------------------------- 1 | package com.gyfish.formflow.service; 2 | 3 | import com.gyfish.formflow.domain.User; 4 | import com.gyfish.formflow.util.BeanUtil; 5 | import com.gyfish.formflow.vo.UserQuery; 6 | import com.gyfish.formflow.vo.UserVo; 7 | 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.data.domain.Sort; 10 | import org.springframework.data.mongodb.core.MongoTemplate; 11 | import org.springframework.data.mongodb.core.query.BasicQuery; 12 | import org.springframework.data.mongodb.core.query.Criteria; 13 | import org.springframework.data.mongodb.core.query.Query; 14 | import org.springframework.data.mongodb.core.query.TextQuery; 15 | import org.springframework.stereotype.Service; 16 | 17 | import java.util.Date; 18 | import java.util.List; 19 | 20 | import lombok.extern.slf4j.Slf4j; 21 | 22 | /** 23 | * 用户 24 | * 25 | * @author geyu 26 | */ 27 | @Service 28 | @Slf4j 29 | public class UserService { 30 | 31 | private final MongoTemplate mongoTemplate; 32 | 33 | @Autowired 34 | public UserService(MongoTemplate mongoTemplate) { 35 | this.mongoTemplate = mongoTemplate; 36 | } 37 | 38 | public Object save(UserVo userVo) { 39 | 40 | User user = BeanUtil.copy(userVo, User.class); 41 | 42 | if (user.getId() != null) { 43 | mongoTemplate.remove(user); 44 | } 45 | 46 | user.setCreateTime(new Date()); 47 | 48 | return mongoTemplate.save(user); 49 | } 50 | 51 | public void save(User user) { 52 | mongoTemplate.save(user); 53 | } 54 | 55 | public List userList(UserQuery userQuery) { 56 | 57 | Sort sort = new Sort(Sort.Direction.ASC, "createTime"); 58 | Criteria criteria = Criteria.where("appId").is(userQuery.getAppId()); 59 | 60 | Query query = new Query(criteria).with(sort); 61 | 62 | return mongoTemplate.find(query, User.class); 63 | } 64 | 65 | public void deleteUser(String id) { 66 | 67 | User user = new User(); 68 | user.setId(id); 69 | 70 | mongoTemplate.remove(user); 71 | } 72 | 73 | public User getById(String id) { 74 | 75 | return mongoTemplate.findById(id, User.class); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /server/src/main/java/com/gyfish/formflow/util/AppResponse.java: -------------------------------------------------------------------------------- 1 | package com.gyfish.formflow.util; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @author geyu 7 | */ 8 | @Data 9 | public class AppResponse { 10 | 11 | private int code; 12 | private String msg; 13 | private T data; 14 | 15 | public AppResponse() { 16 | } 17 | 18 | public AppResponse(int code, String msg, T data) { 19 | this.code = code; 20 | this.msg = msg; 21 | this.data = data; 22 | } 23 | 24 | public AppResponse ok() { 25 | return new AppResponse<>(0, "ok", null); 26 | } 27 | 28 | public AppResponse ok(T data) { 29 | return new AppResponse<>(0, "ok", data); 30 | } 31 | 32 | public AppResponse ok(String msg, T data) { 33 | return new AppResponse<>(0, msg, data); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /server/src/main/java/com/gyfish/formflow/util/BeanUtil.java: -------------------------------------------------------------------------------- 1 | package com.gyfish.formflow.util; 2 | 3 | import org.springframework.beans.BeanUtils; 4 | import org.springframework.beans.BeanWrapper; 5 | import org.springframework.beans.BeanWrapperImpl; 6 | import org.springframework.cglib.beans.BeanCopier; 7 | import org.springframework.cglib.core.Converter; 8 | 9 | import java.beans.PropertyDescriptor; 10 | import java.util.HashSet; 11 | import java.util.Set; 12 | 13 | import lombok.extern.slf4j.Slf4j; 14 | 15 | /** 16 | * 对象复制工具类 17 | * 18 | * @author geyu 19 | */ 20 | @Slf4j 21 | public class BeanUtil { 22 | 23 | /** 24 | * 基于 cglib 的 beanCopier,复制 bean,并返回指定类的实例 只会复制名称相同的属性,如果是 null 也会复制过去 25 | * 26 | * @param bean 要复制的对象 27 | * @param clazz 目标类的 class 28 | * @param 目标 bean 的类型 29 | * @return 目标对象 30 | */ 31 | public static T copy(Object bean, Class clazz) { 32 | 33 | BeanCopier copier = BeanCopier.create(bean.getClass(), clazz, true); 34 | 35 | T target = null; 36 | 37 | try { 38 | target = clazz.newInstance(); 39 | copier.copy(bean, target, (o, aClass, o1) -> o); 40 | } catch (Exception e) { 41 | log.error("=== bean util copy 异常 ==="); 42 | log.error("=== bean:{}", bean); 43 | log.error("=== clazz:{}", clazz); 44 | e.printStackTrace(); 45 | } 46 | 47 | return target; 48 | } 49 | 50 | /** 51 | * 基于 spring BeanUtils,对象 copy 52 | * 53 | * @param src 原对象 54 | * @param target 目标对象 55 | * @param copyNull 是否 copy null 属性 56 | */ 57 | public static void copy(Object src, Object target, boolean copyNull) { 58 | 59 | String[] ignoreProperties = copyNull ? null : getNullPropertyNames(src); 60 | 61 | BeanUtils.copyProperties(src, target, ignoreProperties); 62 | } 63 | 64 | private static String[] getNullPropertyNames(Object source) { 65 | 66 | final BeanWrapper src = new BeanWrapperImpl(source); 67 | PropertyDescriptor[] pds = src.getPropertyDescriptors(); 68 | 69 | Set emptyNames = new HashSet<>(); 70 | for (PropertyDescriptor pd : pds) { 71 | Object srcValue = src.getPropertyValue(pd.getName()); 72 | if (srcValue == null) emptyNames.add(pd.getName()); 73 | if (srcValue instanceof String && "".equals(srcValue)) emptyNames.add(pd.getName()); 74 | } 75 | String[] result = new String[emptyNames.size()]; 76 | return emptyNames.toArray(result); 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /server/src/main/java/com/gyfish/formflow/vo/AppInfoVo.java: -------------------------------------------------------------------------------- 1 | package com.gyfish.formflow.vo; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @author geyu 7 | */ 8 | @Data 9 | public class AppInfoVo { 10 | 11 | private String id; 12 | 13 | private String title; 14 | 15 | private String description; 16 | 17 | } 18 | -------------------------------------------------------------------------------- /server/src/main/java/com/gyfish/formflow/vo/FlowQuery.java: -------------------------------------------------------------------------------- 1 | package com.gyfish.formflow.vo; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @author geyu 7 | */ 8 | @Data 9 | public class FlowQuery { 10 | 11 | private String appId; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /server/src/main/java/com/gyfish/formflow/vo/FormQuery.java: -------------------------------------------------------------------------------- 1 | package com.gyfish.formflow.vo; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @author geyu 7 | */ 8 | @Data 9 | public class FormQuery { 10 | 11 | private String appId; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /server/src/main/java/com/gyfish/formflow/vo/TaskQuery.java: -------------------------------------------------------------------------------- 1 | package com.gyfish.formflow.vo; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @author geyu 7 | */ 8 | @Data 9 | public class TaskQuery { 10 | 11 | private String userId; 12 | 13 | private String status; 14 | 15 | private String processId; 16 | 17 | } 18 | -------------------------------------------------------------------------------- /server/src/main/java/com/gyfish/formflow/vo/TaskVo.java: -------------------------------------------------------------------------------- 1 | package com.gyfish.formflow.vo; 2 | 3 | import java.util.Date; 4 | import java.util.List; 5 | import java.util.Map; 6 | 7 | import lombok.Data; 8 | 9 | /** 10 | * @author geyu 11 | */ 12 | @Data 13 | public class TaskVo { 14 | 15 | private String id; 16 | 17 | private String taskName; 18 | 19 | private String userId; 20 | 21 | private Date createTime; 22 | 23 | private String status; 24 | 25 | 26 | private String processId; 27 | 28 | private String processName; 29 | 30 | 31 | private String flowId; 32 | 33 | private String flowTitle; 34 | 35 | 36 | private String nodeId; 37 | 38 | private String formId; 39 | 40 | private List formData; 41 | 42 | } 43 | -------------------------------------------------------------------------------- /server/src/main/java/com/gyfish/formflow/vo/UserQuery.java: -------------------------------------------------------------------------------- 1 | package com.gyfish.formflow.vo; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * 用户查询用到的类 7 | * 8 | * @author geyu 9 | */ 10 | @Data 11 | public class UserQuery { 12 | 13 | private String appId; 14 | 15 | } 16 | -------------------------------------------------------------------------------- /server/src/main/java/com/gyfish/formflow/vo/UserVo.java: -------------------------------------------------------------------------------- 1 | package com.gyfish.formflow.vo; 2 | 3 | import java.util.List; 4 | 5 | import lombok.Data; 6 | 7 | /** 8 | * 新建用户的 vo,对应 com.gyfish.formflow.domain.User 9 | * 10 | * @author geyu 11 | */ 12 | @Data 13 | public class UserVo { 14 | 15 | private String id; 16 | 17 | private String appId; 18 | 19 | private String userNo; 20 | 21 | private String userName; 22 | 23 | private List flowList; 24 | 25 | } 26 | -------------------------------------------------------------------------------- /server/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 7000 3 | servlet: 4 | context-path: /api 5 | 6 | spring: 7 | redis: 8 | host: formflow.gyfish.com 9 | port: 6379 10 | password: 123456 11 | data: 12 | mongodb: 13 | uri: mongodb://form_flow:form_flow@formflow.gyfish.com:27017/form_flow 14 | 15 | mybatis: 16 | mapper-locations: classpath:mapper/*.xml 17 | 18 | logging: 19 | level: 20 | com.gyfish.formflow.dao: debug 21 | -------------------------------------------------------------------------------- /server/src/test/java/com/gyfish/formflow/FormFlowTests.java: -------------------------------------------------------------------------------- 1 | package com.gyfish.formflow; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class FormFlowTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /server/src/test/java/com/gyfish/formflow/RedisTest.java: -------------------------------------------------------------------------------- 1 | package com.gyfish.formflow; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.context.SpringBootTest; 7 | import org.springframework.data.redis.core.StringRedisTemplate; 8 | import org.springframework.test.context.junit4.SpringRunner; 9 | 10 | import java.util.Set; 11 | 12 | @RunWith(SpringRunner.class) 13 | @SpringBootTest 14 | public class RedisTest { 15 | 16 | @Autowired 17 | private StringRedisTemplate stringRedisTemplate; 18 | 19 | /** 20 | * 测试并发增加 hash 的某个值 21 | */ 22 | @Test 23 | public void concurrentHashInc() { 24 | 25 | System.out.println("//~ commit --------------------"); 26 | long startTime = System.currentTimeMillis(); 27 | System.out.println("answerCount = " 28 | + stringRedisTemplate.opsForHash().get("state", "answerCount")); 29 | 30 | for (int i = 0; i < 8; i++) { 31 | new Thread(() -> { 32 | for (int j = 0; j < 5000; j++) { 33 | // HINCRBY key field increment 34 | stringRedisTemplate.opsForHash().increment("state", "answerCount", 2); 35 | } 36 | }).run(); 37 | } 38 | 39 | System.out.println("//~ end --------------------"); 40 | long endTime = System.currentTimeMillis(); 41 | System.out.println("answerCount = " 42 | + stringRedisTemplate.opsForHash().get("state", "answerCount")); 43 | 44 | System.out.println("cost = " + (endTime - startTime)); 45 | 46 | } 47 | 48 | 49 | } 50 | -------------------------------------------------------------------------------- /server/src/test/java/com/gyfish/formflow/util/BeanUtilTest.java: -------------------------------------------------------------------------------- 1 | package com.gyfish.formflow.util; 2 | 3 | import org.junit.Test; 4 | 5 | import lombok.Data; 6 | 7 | public class BeanUtilTest { 8 | 9 | @Test 10 | public void copyTest() { 11 | A1 a1 = new A1(); 12 | a1.setA("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); 13 | 14 | long t1 = System.currentTimeMillis(); 15 | System.out.println("commit"); 16 | 17 | for (int i = 0; i < 1e6; i++) { 18 | 19 | A2 a2 = BeanUtil.copy(a1, A2.class); 20 | } 21 | 22 | long t2 = System.currentTimeMillis(); 23 | System.out.print(t2 - t1); 24 | } 25 | 26 | @Test 27 | public void copyAtoB() { 28 | A1 a1 = new A1("a", null); 29 | A2 a2 = new A2("a2", "zzzzzzzz"); 30 | BeanUtil.copy(a1, a2, false); 31 | System.out.println(a2); 32 | } 33 | 34 | } 35 | 36 | @Data 37 | class A1 { 38 | String a; 39 | String b; 40 | 41 | public A1() { 42 | } 43 | 44 | public A1(String a, String b) { 45 | this.a = a; 46 | this.b = b; 47 | } 48 | } 49 | 50 | @Data 51 | class A2 { 52 | String a; 53 | String b; 54 | 55 | public A2() { 56 | } 57 | 58 | public A2(String a, String b) { 59 | this.a = a; 60 | this.b = b; 61 | } 62 | } -------------------------------------------------------------------------------- /server/src/test/java/com/gyfish/formflow/util/JsonTest.java: -------------------------------------------------------------------------------- 1 | package com.gyfish.formflow.util; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | 5 | import org.junit.Test; 6 | 7 | public class JsonTest { 8 | 9 | @Test 10 | public void testParse() { 11 | 12 | String s = "1-1-"; 13 | 14 | String r = JSON.parseObject(s, String.class); 15 | 16 | System.out.println(r); 17 | 18 | } 19 | 20 | @SuppressWarnings("unchecked") 21 | public T getByQuery(Object v, Class type) { 22 | 23 | 24 | if (v == null) { 25 | return null; 26 | } 27 | 28 | if (type.equals(String.class)) { 29 | return (T) String.valueOf(v); 30 | } 31 | 32 | if (type.equals(Integer.class)) { 33 | return (T) Integer.valueOf(String.valueOf(v)); 34 | } 35 | 36 | if (type.equals(Long.class)) { 37 | return (T) Long.valueOf(String.valueOf(v)); 38 | } 39 | 40 | return JSON.parseObject(JSON.toJSONString(v), type); 41 | } 42 | 43 | @Test 44 | public void testType() { 45 | int v = getByQuery(null, Integer.class); 46 | System.out.println(v); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /server/src/test/java/com/gyfish/formflow/util/RunTimeTest.java: -------------------------------------------------------------------------------- 1 | package com.gyfish.formflow.util; 2 | 3 | import org.junit.Test; 4 | 5 | public class RunTimeTest { 6 | 7 | 8 | /** 9 | * 获取系统核心数 10 | */ 11 | @Test 12 | public void getProcessors() { 13 | Integer n = Runtime.getRuntime().availableProcessors(); 14 | System.out.println(n); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /server/src/test/java/com/gyfish/formflow/util/StreamTest.java: -------------------------------------------------------------------------------- 1 | package com.gyfish.formflow.util; 2 | 3 | import org.junit.Test; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | import java.util.stream.Collectors; 8 | 9 | public class StreamTest { 10 | 11 | @Test 12 | public void filter() { 13 | 14 | List l1 = new ArrayList<>(); 15 | 16 | l1.add("a"); 17 | l1.add("b"); 18 | l1.add("c"); 19 | 20 | List l2 = l1.stream() 21 | .filter(e -> e.equals("a")) 22 | .collect(Collectors.toList()); 23 | 24 | System.out.println(l2); 25 | } 26 | 27 | @Test 28 | public void test() { 29 | String s = "1234567"; 30 | String a = s.substring(0, 5); 31 | System.out.println(a); 32 | } 33 | 34 | 35 | } 36 | -------------------------------------------------------------------------------- /web/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw* 22 | -------------------------------------------------------------------------------- /web/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "semi": false, 4 | "singleQuote": true, 5 | "printWidth": 130 6 | } -------------------------------------------------------------------------------- /web/README.md: -------------------------------------------------------------------------------- 1 | # 前端 2 | 3 | 参考 4 | - https://github.com/GavinZhuLei/vue-form-making 5 | - https://github.com/oOBobbyOo/vue-element-admin 6 | 7 | ### vuegraggable 8 | 9 | https://blog.csdn.net/zjiang1994/article/details/79809687 10 | 11 | ### TypeScript 12 | 13 | https://www.tslang.cn/docs/handbook/modules.html 14 | 15 | ### vue + ts 起手式 16 | 17 | https://segmentfault.com/a/1190000011744210?utm_source=tuicool&utm_medium=referral 18 | 19 | ### vuex踩坑 20 | 21 | https://github.com/vuejs/vuex/issues/231 22 | 23 | ### G6 文档 24 | 25 | https://www.yuque.com/antv/g6/api-g6 -------------------------------------------------------------------------------- /web/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dc-qa", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build" 8 | }, 9 | "dependencies": { 10 | "@antv/g6": "^2.2.6", 11 | "axios": "^0.19.0", 12 | "dayjs": "^1.8.14", 13 | "element-ui": "^2.9.2", 14 | "vue": "^2.5.21", 15 | "vue-class-component": "^6.0.0", 16 | "vue-property-decorator": "^7.0.0", 17 | "vue-router": "^3.0.1", 18 | "vuedraggable": "^2.17.0", 19 | "vuex": "^3.0.1", 20 | "vuex-class": "^0.3.1" 21 | }, 22 | "devDependencies": { 23 | "@vue/cli-plugin-babel": "^3.3.0", 24 | "@vue/cli-plugin-typescript": "^3.3.0", 25 | "@vue/cli-service": "^3.8.4", 26 | "node-sass": "^4.9.0", 27 | "sass-loader": "^7.0.1", 28 | "typescript": "^3.0.0", 29 | "vue-template-compiler": "^2.5.21" 30 | }, 31 | "postcss": { 32 | "plugins": { 33 | "autoprefixer": {} 34 | } 35 | }, 36 | "browserslist": [ 37 | "> 1%", 38 | "last 2 versions", 39 | "not ie <= 8" 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /web/public/Jerry.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GyFish/form-flow/c8b07c4dab3f07e0ca3c0231a5ac4bbc0988060c/web/public/Jerry.jpg -------------------------------------------------------------------------------- /web/public/Tom.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GyFish/form-flow/c8b07c4dab3f07e0ca3c0231a5ac4bbc0988060c/web/public/Tom.jpg -------------------------------------------------------------------------------- /web/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GyFish/form-flow/c8b07c4dab3f07e0ca3c0231a5ac4bbc0988060c/web/public/favicon.ico -------------------------------------------------------------------------------- /web/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | form-flow 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /web/src/App.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 32 | 33 | 54 | -------------------------------------------------------------------------------- /web/src/apis/AppApi.ts: -------------------------------------------------------------------------------- 1 | import Api from "./Api" 2 | 3 | export default class AppApi extends Api { 4 | constructor() { 5 | super() 6 | } 7 | 8 | // 保存表单结构数据 9 | async saveApp(appInfo: any) { 10 | const res: any = await this.http.post("/api/app/saveApp", appInfo) 11 | return super.extractData(res) 12 | } 13 | 14 | // 查询应用列表 15 | async appList() { 16 | const res: any = await this.http.get("/api/app/appList") 17 | return super.extractData(res) 18 | } 19 | 20 | // 删除 app 21 | async deleteApp(appId: any) { 22 | const res: any = await this.http.delete("/api/app/deleteApp", { 23 | params: { 24 | appId 25 | } 26 | }) 27 | return super.extractData(res) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /web/src/apis/FlowApi.ts: -------------------------------------------------------------------------------- 1 | import Api from './Api' 2 | 3 | export default class FlowApi extends Api { 4 | //~ ------------------------------------------------------ 5 | // 构造器 6 | constructor() { 7 | super() 8 | } 9 | 10 | //~ ------------------------------------------------------ 11 | // 保存流程结构数据 12 | async saveFlow(flowData: any) { 13 | const res: any = await this.http.post('/api/flow/save', flowData) 14 | return super.extractData(res) 15 | } 16 | 17 | // 删除流程结构数据 18 | async deleteFlow(id: string) { 19 | const res: any = await this.http.delete('/api/flow/delete?id=' + id) 20 | return super.extractMsg(res) 21 | } 22 | 23 | // 查询流程列表 24 | async getFlowList(flowQuery: any) { 25 | const res: any = await this.http.post('/api/flow/getList', flowQuery) 26 | return super.extractData(res) 27 | } 28 | 29 | // 查询用户可以开始的流程 30 | async getByUser(userId: string) { 31 | const res: any = await this.http.get('/api/flow/getByUser?userId=' + userId) 32 | return super.extractData(res) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /web/src/apis/TaskApi.ts: -------------------------------------------------------------------------------- 1 | import Api from './Api' 2 | 3 | export default class TaskApi extends Api { 4 | //~ ------------------------------------------------------ 5 | // 构造器 6 | constructor() { 7 | super() 8 | } 9 | 10 | //~ ------------------------------------------------------ 11 | // 新建 12 | async start(taskVo: any) { 13 | const res: any = await this.http.post('/api/task/start', taskVo) 14 | return super.extractData(res) 15 | } 16 | 17 | // 提交 18 | async commit(taskVo: any) { 19 | const res: any = await this.http.post('/api/task/commit', taskVo) 20 | return super.extractData(res) 21 | } 22 | 23 | // 查询 24 | async query(taskQueryVo: any) { 25 | const res: any = await this.http.post('/api/task/query', taskQueryVo) 26 | return super.extractData(res) 27 | } 28 | 29 | // 需要展示的任务 30 | async preTask(taskId: string) { 31 | const res: any = await this.http.get('/api/task/preTask?taskId=' + taskId) 32 | return super.extractData(res) 33 | } 34 | 35 | // 所有任务 36 | async getByProcess(processId: string) { 37 | const res: any = await this.http.get('/api/task/getByProcess?processId=' + processId) 38 | return super.extractData(res) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /web/src/apis/UserApi.ts: -------------------------------------------------------------------------------- 1 | import Api from "./Api" 2 | 3 | export default class UserApi extends Api { 4 | //~ ------------------------------------------------------ 5 | // 构造器 6 | constructor() { 7 | super() 8 | } 9 | 10 | //~ ------------------------------------------------------ 11 | // 新增用户 12 | async saveUser(userVo: any) { 13 | const res: any = await this.http.post("/api/user/save", userVo) 14 | return super.extractMsg(res) 15 | } 16 | 17 | // 查询用户 18 | async userList(userQuery: any) { 19 | const res: any = await this.http.post("/api/user/userList", userQuery) 20 | return super.extractData(res) 21 | } 22 | 23 | // 删除用户 24 | async deleteUser(id: any) { 25 | const res: any = await this.http.delete("/api/user/deleteUser", { 26 | params: { 27 | id 28 | } 29 | }) 30 | return super.extractMsg(res) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /web/src/apis/api.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosInstance } from 'axios' 2 | 3 | export default class Api { 4 | // axios 实例 5 | http: AxiosInstance 6 | 7 | constructor() { 8 | this.http = axios.create({ 9 | baseURL: this.getBaseURL() 10 | // timeout: 3000 11 | }) 12 | } 13 | 14 | getBaseURL() { 15 | switch (process.env.NODE_ENV) { 16 | case 'development': 17 | return 'http://localhost:7000/' 18 | case 'production': 19 | return 'http://47.94.198.39:7000/' 20 | } 21 | } 22 | 23 | extractData(res: any) { 24 | if (res.data.code == 0) return res.data.data 25 | } 26 | 27 | extractMsg(res: any) { 28 | if (res.data.code == 0) return res.data.msg 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /web/src/apis/formApi.ts: -------------------------------------------------------------------------------- 1 | import Api from './Api' 2 | 3 | export default class FormApi extends Api { 4 | //~ ------------------------------------------------------ 5 | // 构造器 6 | constructor() { 7 | super() 8 | } 9 | 10 | //~ ------------------------------------------------------ 11 | // 保存表单结构数据 12 | async saveForm(formDefinition: any) { 13 | const res: any = await this.http.post('/api/form/saveForm', formDefinition) 14 | return super.extractData(res) 15 | } 16 | 17 | // 保存表单结构数据 18 | async deleteForm(id: any) { 19 | const res: any = await this.http.delete('/api/form/deleteForm?id=' + id) 20 | return super.extractData(res) 21 | } 22 | 23 | // 查询表单列表 24 | async getFormList(formQuery: any) { 25 | const res: any = await this.http.post('/api/form/getList', formQuery) 26 | return super.extractData(res) 27 | } 28 | 29 | // 根据 id 查询 30 | async getFormById(id: string) { 31 | const res: any = await this.http.get('/api/form/getFormById?id=' + id) 32 | return super.extractData(res) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /web/src/assets/avatar1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GyFish/form-flow/c8b07c4dab3f07e0ca3c0231a5ac4bbc0988060c/web/src/assets/avatar1.jpg -------------------------------------------------------------------------------- /web/src/assets/form-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GyFish/form-flow/c8b07c4dab3f07e0ca3c0231a5ac4bbc0988060c/web/src/assets/form-flow.png -------------------------------------------------------------------------------- /web/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GyFish/form-flow/c8b07c4dab3f07e0ca3c0231a5ac4bbc0988060c/web/src/assets/logo.png -------------------------------------------------------------------------------- /web/src/assets/yayi.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GyFish/form-flow/c8b07c4dab3f07e0ca3c0231a5ac4bbc0988060c/web/src/assets/yayi.jpg -------------------------------------------------------------------------------- /web/src/assets/yayi1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GyFish/form-flow/c8b07c4dab3f07e0ca3c0231a5ac4bbc0988060c/web/src/assets/yayi1.png -------------------------------------------------------------------------------- /web/src/assets/yayi2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GyFish/form-flow/c8b07c4dab3f07e0ca3c0231a5ac4bbc0988060c/web/src/assets/yayi2.jpg -------------------------------------------------------------------------------- /web/src/components/admin/AdminLayout.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | -------------------------------------------------------------------------------- /web/src/components/admin/AppInfo.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | -------------------------------------------------------------------------------- /web/src/components/admin/FlowAdmin.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | -------------------------------------------------------------------------------- /web/src/components/admin/FormAdmin.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | -------------------------------------------------------------------------------- /web/src/components/admin/UserAdmin.vue: -------------------------------------------------------------------------------- 1 | 63 | 64 | -------------------------------------------------------------------------------- /web/src/components/app-room/AppCard.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 68 | 69 | -------------------------------------------------------------------------------- /web/src/components/app-room/NewCard.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 23 | -------------------------------------------------------------------------------- /web/src/components/client/ClientLayout.vue: -------------------------------------------------------------------------------- 1 | 51 | 52 | 74 | -------------------------------------------------------------------------------- /web/src/components/client/Created.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /web/src/components/client/Done.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 77 | -------------------------------------------------------------------------------- /web/src/components/client/Start.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 105 | -------------------------------------------------------------------------------- /web/src/components/client/TaskCard.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 42 | -------------------------------------------------------------------------------- /web/src/components/client/Todo.vue: -------------------------------------------------------------------------------- 1 | 59 | 60 | 131 | -------------------------------------------------------------------------------- /web/src/components/flow/FlowConfig.vue: -------------------------------------------------------------------------------- 1 | 79 | 80 | 155 | 156 | 157 | -------------------------------------------------------------------------------- /web/src/components/flow/FlowItem.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 38 | -------------------------------------------------------------------------------- /web/src/components/flow/G6Register.ts: -------------------------------------------------------------------------------- 1 | import G6 from '@antv/g6' 2 | 3 | export default class G6Register { 4 | // ~-------------------------------- 5 | register() { 6 | G6.registerNode('start', { 7 | draw(item: any) { 8 | const group = item.getGraphicGroup() 9 | const html = G6.Util.createDOM(`
`) 10 | return group.addShape('dom', { 11 | attrs: { 12 | x: 0, 13 | y: 0, 14 | width: 100, 15 | height: 28, 16 | html 17 | } 18 | }) 19 | }, 20 | anchor: { 21 | type: 'circle', 22 | points: [[0.5, 0], [1, 0.5], [0.5, 1], [0, 0.5]] 23 | } 24 | }) 25 | } 26 | 27 | node() { 28 | G6.registerNode( 29 | 'startNode', 30 | { 31 | label: '开始', 32 | color_type: '#1890FF', 33 | type_icon_url: 'https://gw.alipayobjects.com/zos/rmsportal/czNEJAmyDpclFaSucYWB.svg', 34 | state_icon_url: 'https://gw.alipayobjects.com/zos/rmsportal/MXXetJAxlqrbisIuZxDO.svg', 35 | // 设置锚点 36 | anchor: [ 37 | [ 38 | 0.5, 39 | 0, 40 | { 41 | type: 'input' 42 | } 43 | ], // 上面边的中点 44 | [ 45 | 0.5, 46 | 1, 47 | { 48 | type: 'output' 49 | } 50 | ] // 下边边的中点 51 | ] 52 | }, 53 | 'model' 54 | ) 55 | } 56 | 57 | model() { 58 | // 注册模型卡片基类 59 | G6.registerNode('model', { 60 | draw(item: any) { 61 | const group = item.getGraphicGroup() 62 | const model = item.getModel() 63 | const width = 238 64 | const height = 26 65 | const x = -width / 2 66 | const y = -height / 2 67 | const borderRadius = 3 68 | const keyShape = group.addShape('rect', { 69 | attrs: { 70 | x, 71 | y, 72 | width, 73 | height, 74 | radius: borderRadius, 75 | fill: '#ecf5ff', 76 | stroke: '#c6e2ff', 77 | lineWidth: 0.8 78 | } 79 | }) 80 | // 左侧色条 81 | group.addShape('path', { 82 | attrs: { 83 | path: [ 84 | ['M', x, y + borderRadius], 85 | ['L', x, y + height - borderRadius], 86 | ['A', borderRadius, borderRadius, 0, 0, 0, x + borderRadius, y + height], 87 | ['L', x + borderRadius, y], 88 | ['A', borderRadius, borderRadius, 0, 0, 0, x, y + borderRadius] 89 | ], 90 | fill: this.color_type 91 | } 92 | }) 93 | // 类型 logo 94 | group.addShape('image', { 95 | attrs: { 96 | img: this.type_icon_url, 97 | x: x + 10, 98 | y: y + 5, 99 | width: 20, 100 | height: 16 101 | } 102 | }) 103 | // 名称文本 104 | const label = model.label ? model.label : this.label 105 | group.addShape('text', { 106 | attrs: { 107 | text: label, 108 | x: x + 50, 109 | y: y + 9, 110 | textAlign: 'start', 111 | textBaseline: 'top', 112 | fill: 'rgba(0,0,0,0.65)' 113 | } 114 | }) 115 | return keyShape 116 | }, 117 | // 设置锚点 118 | anchor: [ 119 | [0.5, 0], // 上面边的中点 120 | [0.5, 1] // 下边边的中点 121 | ] 122 | }) 123 | } 124 | 125 | line() { 126 | G6.registerEdge('line', { 127 | draw(item: any) { 128 | const group = item.getGraphicGroup() 129 | const path = this.getPath(item) 130 | return group.addShape('path', { 131 | attrs: { 132 | path, 133 | stroke: 'red' 134 | } 135 | }) 136 | }, 137 | getPath(item: any) { 138 | const points = item.getPoints() 139 | return G6.Util.pointsToPolygon(points) 140 | }, 141 | endArrow: { 142 | path(item: any) { 143 | const keyShape = item.getKeyShape() 144 | let lineWidth = keyShape.attr('lineWidth') 145 | const width = 15 146 | const halfHeight = 4 147 | const radius = lineWidth * 10 148 | return [ 149 | ['M', -width, halfHeight], 150 | ['L', 0, 0], 151 | ['L', -width, -halfHeight], 152 | ['A', radius, radius, 0, 0, 1, -width, halfHeight], 153 | ['Z'] 154 | ] 155 | }, 156 | shorten(item: any) { 157 | const keyShape = item.getKeyShape() 158 | return keyShape.attr('lineWidth') * 3.1 159 | }, 160 | style(item: any) { 161 | const keyShape = item.getKeyShape() 162 | const { strokeOpacity, stroke } = keyShape.attr() 163 | return { 164 | fillOpacity: strokeOpacity, 165 | fill: stroke 166 | } 167 | } 168 | } 169 | }) 170 | } 171 | 172 | elButton(id: string) { 173 | G6.registerNode(id, { 174 | draw(item: any) { 175 | const group = item.getGraphicGroup() 176 | const html = G6.Util.createDOM(`
`) 177 | return group.addShape('dom', { 178 | attrs: { 179 | x: -50, 180 | y: -25, 181 | width: 100, 182 | height: 28, 183 | html 184 | } 185 | }) 186 | }, 187 | // 设置锚点 188 | anchor: { 189 | type: 'circle', 190 | points: [[0.5, 0], [1, 0.5], [0.5, 1], [0, 0.5]] 191 | } 192 | }) 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /web/src/components/flow/Maker.tsx: -------------------------------------------------------------------------------- 1 | import Vue, { CreateElement, VNode, RenderContext } from 'vue' 2 | import G6 from '@antv/g6' 3 | import NodeCard from '@/components/flow/NodeCard' 4 | import { FlowNode } from '.' 5 | 6 | export default class Maker { 7 | shape = 'shape' 8 | 9 | register(option: any) { 10 | const html = G6.Util.createDOM(`
`) 11 | G6.registerNode(this.shape, { 12 | draw(item: any) { 13 | const group = item.getGraphicGroup() 14 | return group.addShape('dom', { 15 | attrs: { 16 | x: 0, 17 | y: 0, 18 | width: option.width, 19 | height: option.height, 20 | html 21 | } 22 | }) 23 | }, 24 | anchor: [[0.5, 0], [0.5, 1]] 25 | }) 26 | return html 27 | } 28 | 29 | createDom(model: any, graph: any) { 30 | console.log(' createDom, model =', model) 31 | let g6Node = graph.add('node', { 32 | id: model.id, 33 | x: model.x, 34 | y: model.y, 35 | shape: this.shape 36 | }) 37 | console.log(' 创建 dom,添加 g6节点,g6Node =', g6Node) 38 | return g6Node 39 | } 40 | 41 | /** 42 | * 渲染 vnode 到创建好的 dom 上 43 | * 44 | * @param h 渲染函数 45 | * @param handler 回调函数集合 46 | */ 47 | mountNode(h: CreateElement, html: any, node: FlowNode, handler: any) { 48 | console.log(' 渲染 vnode 到创建好的 dom 上,node =', node) 49 | let vueNode = Vue.extend({ 50 | props: ['node'], 51 | render: () => { 52 | return new NodeCard().render(h, node, handler) 53 | } 54 | }) 55 | new vueNode({ 56 | propsData: { 57 | node 58 | } 59 | }).$mount(html) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /web/src/components/flow/NodeCard.tsx: -------------------------------------------------------------------------------- 1 | import { CreateElement, RenderContext, VNode } from 'vue' 2 | import { FlowNode } from '.' 3 | 4 | export default class NodeCard { 5 | render(h: CreateElement, node: FlowNode, handler: any): VNode { 6 | return ( 7 | 8 |
9 | 10 | {node.nodeName} 11 | handler.add(node)} trigger="click" style="float: right;"> 12 | 13 | 14 | 任务节点 15 | 审核节点 16 | 分支节点 17 | 18 | 19 | handler.delete(node)} 24 | /> 25 | {/* handler.edit(node)} 30 | /> */} 31 |
32 |
33 | 处理人:{node.handlerName} 34 |
35 |
36 | ) 37 | } 38 | 39 | isActive(node: any) { 40 | console.log('is active node = ', node) 41 | if (node.active) { 42 | return 'active' 43 | } else { 44 | return '' 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /web/src/components/flow/index.ts: -------------------------------------------------------------------------------- 1 | // G6 node,前两个是 g6 属性 2 | export interface ConfigModel { 3 | // 当前节点 g6 model 4 | model: any 5 | // 节点流程属性 6 | node: FlowNode 7 | // 当前配置栏标签页 8 | activeTab: string 9 | // 流程名称 10 | title: any 11 | // todo 详情展示哪个 task 卡片 12 | todoShow: any 13 | } 14 | 15 | // 节点 16 | export class FlowNode { 17 | // 节点 id 18 | id: any 19 | // 所属流程 id 20 | processId?: number 21 | // 节点名称 22 | nodeName?: string 23 | // 节点类型 24 | nodeType?: string 25 | // 节点状态 26 | status?: FlowStatus 27 | // g6 model 28 | model?: any 29 | // 处理人 id 30 | handlerId?: number 31 | // 处理人 name 32 | handlerName?: string 33 | // 处理人所在组 id 34 | handlerGroupId?: number 35 | // 处理人所在组 name 36 | handlerGroupName?: string 37 | // 关联表单 38 | formId?: number 39 | // 下一节点 40 | nextNodeId?: number 41 | // 是否可流回 42 | canFlowBack?: Boolean 43 | // 是否激活 44 | active?: any 45 | } 46 | 47 | // 流程动作,即流程图中的连线 48 | export interface FlowAction {} 49 | 50 | // 流程节点的状态 51 | export interface FlowStatus {} 52 | -------------------------------------------------------------------------------- /web/src/components/form/FormConfig.tsx: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import { Prop, Component } from 'vue-property-decorator' 3 | import InputConfig from './configs/InputConfig.vue' 4 | import SelectConfig from './configs/SelectConfig.vue' 5 | 6 | @Component({ components: { InputConfig } }) 7 | export default class FormConfig extends Vue { 8 | @Prop() 9 | item!: any 10 | 11 | render() { 12 | console.log('=== 渲染表单配置,item =', this.item) 13 | 14 | if (!this.item.itemType) return 15 | 16 | let config = this.getConfig() 17 | console.debug(' get config =', config) 18 | 19 | return ( 20 | 26 | ) 27 | } 28 | 29 | // 获取组件 30 | getConfig() { 31 | switch (this.item.itemType) { 32 | case 'el-input': 33 | return InputConfig 34 | case 'el-select': 35 | return SelectConfig 36 | } 37 | } 38 | 39 | // 把事件丢给上一层 40 | updateItem(value: any) { 41 | this.$emit('updateItem', value) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /web/src/components/form/FormItem.tsx: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import { Prop, Component } from 'vue-property-decorator' 3 | import Input from './items/Input.vue' 4 | import Select from './items/Select.vue' 5 | import { Item } from '.' 6 | 7 | @Component({ components: { Input } }) 8 | export default class FormItem extends Vue { 9 | // prop 10 | @Prop() item!: Item 11 | @Prop() curItem!: any 12 | 13 | render() { 14 | console.log('=== 渲染表单元素,item =', this.item) 15 | 16 | let component = this.getItem() 17 | console.debug(' get item =', component) 18 | 19 | return ( 20 |
21 | this.$emit('setActiveItem', this.item.uuid)} 28 | > 29 | 30 | 31 |
32 | ) 33 | } 34 | 35 | isActive() { 36 | let active = this.curItem ? this.item.uuid == this.curItem.uuid : false 37 | return { active } 38 | } 39 | 40 | getItem() { 41 | switch (this.item.itemType) { 42 | case 'el-input': 43 | return Input 44 | case 'el-select': 45 | return Select 46 | default: 47 | break 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /web/src/components/form/configs/InputConfig.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 37 | -------------------------------------------------------------------------------- /web/src/components/form/configs/SelectConfig.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 58 | -------------------------------------------------------------------------------- /web/src/components/form/display/CardView.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 27 | 28 | 35 | -------------------------------------------------------------------------------- /web/src/components/form/index.ts: -------------------------------------------------------------------------------- 1 | export class Item { 2 | idx: number = 0 3 | uuid: string = '' 4 | prop: string = '' 5 | value: string = '' 6 | itemType: string = '' 7 | label: string = '' 8 | inputType: string = '' 9 | placeholder: string = '' 10 | icon: string = '' 11 | prefixIcon: string = '' 12 | } 13 | 14 | export class BaseItem { 15 | static form = { 16 | inline: false 17 | } 18 | static items = [ 19 | { 20 | itemType: 'el-input', 21 | prop: 'input', 22 | label: '文本框', 23 | icon: 'el-icon-edit', 24 | type: 'text' 25 | }, 26 | { 27 | itemType: 'el-input', 28 | prop: 'input', 29 | label: '多行文本', 30 | icon: 'el-icon-tickets', 31 | type: 'textarea' 32 | }, 33 | { 34 | itemType: 'el-select', 35 | prop: 'input', 36 | label: '下拉框', 37 | icon: 'el-icon-arrow-down', 38 | options: [ 39 | { 40 | label: '西瓜', 41 | value: 'watermelon' 42 | }, 43 | { 44 | label: '可乐', 45 | value: 'cola' 46 | } 47 | ] 48 | }, 49 | { 50 | itemType: 'el-date-picker', 51 | prop: 'input', 52 | label: '日期选择器', 53 | icon: 'el-icon-time' 54 | } 55 | ] 56 | } 57 | 58 | export class FormConfigData { 59 | title: string = '' 60 | } 61 | -------------------------------------------------------------------------------- /web/src/components/form/items/Input.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 14 | -------------------------------------------------------------------------------- /web/src/components/form/items/Select.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 21 | -------------------------------------------------------------------------------- /web/src/components/report/AppReport.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | -------------------------------------------------------------------------------- /web/src/main.ts: -------------------------------------------------------------------------------- 1 | Vue.config.productionTip = false 2 | 3 | // 主页面 4 | import App from "./App.vue" 5 | 6 | // vue 7 | import Vue from "vue" 8 | 9 | // router 10 | import router from "./routes/router" 11 | 12 | // vuex 13 | import store from "./stores/store" 14 | 15 | // element 16 | import ElementUI from "element-ui" 17 | import "element-ui/lib/theme-chalk/index.css" 18 | Vue.use(ElementUI, { 19 | size: "mini" 20 | }) 21 | 22 | // 拖拽插件 23 | import draggable from "vuedraggable" 24 | Vue.use(draggable) 25 | 26 | // 外部样式 27 | import './styles/index.scss' 28 | 29 | 30 | 31 | new Vue({ 32 | router, 33 | store, 34 | render: h => h(App) 35 | }).$mount("#app") 36 | -------------------------------------------------------------------------------- /web/src/routes/router.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | Vue.use(Router) 4 | 5 | const routes = [ 6 | { 7 | path: '/', 8 | name: 'home', 9 | component: () => import('@/views/Home.vue') 10 | }, 11 | { 12 | path: '/formEditor', 13 | name: 'formEditor', 14 | component: () => import('@/views/FormEditor.vue') 15 | }, 16 | { 17 | path: '/flowEditor', 18 | name: 'flowEditor', 19 | props: true, 20 | component: () => import('@/views/FlowEditor.vue') 21 | }, 22 | { 23 | path: '/appRoom', 24 | name: 'AppRoom', 25 | component: () => import('@/views/AppRoom.vue') 26 | }, 27 | { 28 | path: '/appAdmin', 29 | name: 'AppAdmin', 30 | component: () => import('@/components/admin/AdminLayout.vue'), 31 | children: [ 32 | { 33 | path: 'appInfo', 34 | name: 'AppInfo', 35 | component: () => import('@/components/admin/AppInfo.vue') 36 | }, 37 | { 38 | path: 'userAdmin', 39 | name: 'UserAdmin', 40 | component: () => import('@/components/admin/UserAdmin.vue') 41 | }, 42 | { 43 | path: 'formAdmin', 44 | name: 'FormAdmin', 45 | component: () => import('@/components/admin/FormAdmin.vue') 46 | }, 47 | { 48 | path: 'flowAdmin', 49 | name: 'FlowAdmin', 50 | component: () => import('@/components/admin/FlowAdmin.vue') 51 | } 52 | ] 53 | }, 54 | { 55 | path: '/appReport', 56 | name: 'AppReport', 57 | component: () => import('@/components/report/AppReport.vue') 58 | }, 59 | { 60 | path: '/appClient', 61 | name: 'AppClient', 62 | props: true, 63 | component: () => import('@/components/client/ClientLayout.vue'), 64 | children: [ 65 | { 66 | path: 'start', 67 | name: 'Start', 68 | props: true, 69 | component: () => import('@/components/client/Start.vue') 70 | }, 71 | { 72 | path: 'created', 73 | name: 'Created', 74 | props: true, 75 | component: () => import('@/components/client/Created.vue') 76 | }, 77 | { 78 | path: 'todo', 79 | name: 'Todo', 80 | props: true, 81 | component: () => import('@/components/client/Todo.vue') 82 | }, 83 | { 84 | path: 'done', 85 | name: 'Done', 86 | props: true, 87 | component: () => import('@/components/client/Done.vue') 88 | } 89 | ] 90 | } 91 | ] 92 | 93 | export default new Router({ routes }) 94 | -------------------------------------------------------------------------------- /web/src/shims-tsx.d.ts: -------------------------------------------------------------------------------- 1 | import Vue, { VNode } from 'vue' 2 | 3 | declare global { 4 | namespace JSX { 5 | // tslint:disable no-empty-interface 6 | interface Element extends VNode {} 7 | // tslint:disable no-empty-interface 8 | interface ElementClass extends Vue {} 9 | interface IntrinsicElements { 10 | [elem: string]: any 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /web/src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import Vue from 'vue' 3 | export default Vue 4 | } 5 | -------------------------------------------------------------------------------- /web/src/stores/store.ts: -------------------------------------------------------------------------------- 1 | import Vue from "vue" 2 | import Vuex, { StoreOptions } from "vuex" 3 | Vue.use(Vuex) 4 | 5 | interface FormState { 6 | activeMenu: any // 当前激活的导航菜单 7 | activeIdx: any 8 | meta: any 9 | data: any 10 | arch: any // 存档,缓存创建好的表单和流程结构数据 11 | } 12 | 13 | const store: StoreOptions = { 14 | state: { 15 | meta: { 16 | form: { 17 | inline: false 18 | }, 19 | items: [ 20 | { 21 | id: null, 22 | prop: "input", // 字段 23 | value: "", // 值 24 | itemType: "el-input", // 类型 25 | label: "输入框", // 名称 26 | placeholder: "请输入...", // 占位值 27 | icon: "el-icon-edit", // 左侧栏图标 28 | prefixIcon: "", // 输入框前图标 29 | inputType: "text" 30 | }, 31 | { 32 | id: null, 33 | itemType: "el-input", 34 | prop: "text", 35 | label: "文本域", 36 | icon: "el-icon-tickets", 37 | inputType: "textarea" 38 | }, 39 | { 40 | id: null, 41 | itemType: "el-select", 42 | prop: "select", 43 | label: "下拉框", 44 | icon: "el-icon-arrow-down", 45 | options: [ 46 | { 47 | value: "k1", 48 | label: "选项一" 49 | }, 50 | { 51 | value: "k2", 52 | label: "选项二" 53 | } 54 | ] 55 | }, 56 | { 57 | id: null, 58 | itemType: "el-date-picker", 59 | prop: "date", 60 | label: "日期选择器", 61 | placeholder: "请选择...zhanweifu...", 62 | icon: "el-icon-time" 63 | } 64 | ] 65 | }, 66 | data: { 67 | form: {}, 68 | formItems: [], 69 | result: {}, 70 | table: [], 71 | flow: {} 72 | }, 73 | activeMenu: "/", 74 | activeIdx: -1, 75 | arch: {} 76 | }, 77 | mutations: { 78 | active(state, idx) { 79 | state.activeIdx = idx 80 | }, 81 | 82 | // 设置当前激活导航菜单 83 | setActiveMenu(state, menu) { 84 | state.activeMenu = menu 85 | }, 86 | 87 | // 更新表单结构数据 88 | updateFormItems(state, items) { 89 | state.data.formItems = items 90 | }, 91 | 92 | // 根据顺序id更新 93 | updateByIdx(state, { idx, item }) { 94 | let target = state.data.formItems[idx] 95 | // 判断是否有该字段,动态添加 96 | // 有点麻烦,不如在初始化时就指定数据 97 | // if (target.type == "el-input") 98 | // Vue.set(target, "prefixIcon", item.prefixIcon) 99 | Object.assign(target, item) 100 | }, 101 | 102 | removeByIdx(state, idx) { 103 | state.data.formItems.splice(idx, 1) 104 | }, 105 | 106 | // 表单填值后,更新结果值 107 | updateResult(state, { field, value }) { 108 | Vue.set(state.data.result, field, value) 109 | }, 110 | 111 | commitTable(state) { 112 | let { result, table } = state.data 113 | table.push(JSON.parse(JSON.stringify(result))) 114 | result = {} 115 | } 116 | } 117 | } 118 | 119 | export default new Vuex.Store(store) 120 | -------------------------------------------------------------------------------- /web/src/styles/app-admin.scss: -------------------------------------------------------------------------------- 1 | .app-admin { 2 | // 设置父容器与页面等高 3 | height: calc(100% - 36px); 4 | padding-top: 36px; 5 | 6 | .el-container { 7 | height: 100%; 8 | } 9 | 10 | // logo: Form Flow 11 | .logo-box { 12 | margin-top: 20px; 13 | margin-bottom: 10px; 14 | // border-bottom: solid 1px #e6e6e6; 15 | } 16 | 17 | .el-aside { 18 | height: 100%; 19 | width: 100%; 20 | } 21 | 22 | .el-menu { 23 | height: calc(100% - 160px); 24 | border: 0; 25 | .el-menu-item { 26 | height: 50px !important; 27 | line-height: 50px !important; 28 | } 29 | } 30 | 31 | .menu-bar { 32 | border-right: solid 1px #e6e6e6; 33 | width: 200px; 34 | } 35 | 36 | .admin-box { 37 | width: 100%; 38 | height: 100%; 39 | // 顶部工具栏 40 | .tool-bar { 41 | height: 50px !important; 42 | border-bottom: solid 1px #e6e6e6; 43 | // 标题 44 | .tool-bar-title { 45 | margin-top: 18px; 46 | float: left; 47 | } 48 | // 按钮组 49 | .tool-bar-button { 50 | margin-top: 11px; 51 | float: right; 52 | } 53 | } 54 | // 主页面 55 | .main-box { 56 | // .el-form-item { 57 | // height: 29px; 58 | // } 59 | .search-box { 60 | float: right; 61 | .el-input { 62 | width: 150px; 63 | padding-right: 10px; 64 | } 65 | } 66 | .list-box { 67 | .el-table { 68 | padding-top: 20px; 69 | width: 99%; 70 | height: 100%; 71 | .el-icon-delete { 72 | color: #f56c6c; 73 | } 74 | .el-icon-view { 75 | color: #409eff; 76 | } 77 | } 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /web/src/styles/app-client.scss: -------------------------------------------------------------------------------- 1 | .app-client { 2 | // 设置父容器与页面等高 3 | height: calc(100% - 36px); 4 | 5 | padding-top: 36px; 6 | 7 | .el-container { 8 | height: 100%; 9 | } 10 | 11 | .el-aside { 12 | height: 100%; 13 | } 14 | 15 | .el-menu { 16 | border: 0; 17 | } 18 | 19 | // 左侧菜单容器 20 | .left-aside { 21 | position: fixed; 22 | top: 36px; 23 | width: 60px; 24 | height: 100%; 25 | border-right: solid 1px #f5f5f5; 26 | background-color: #545c64; 27 | // 菜单高度 28 | .el-menu-item { 29 | height: 50px !important; 30 | } 31 | // 菜单宽度 32 | .el-menu--collapse { 33 | width: 100%; 34 | } 35 | // 菜单图标 36 | .el-tooltip { 37 | display: block !important; 38 | padding: 0 30% !important; 39 | [class^='el-icon-'] { 40 | font-size: 22px; 41 | } 42 | } 43 | // 头像框 44 | .avatar-box { 45 | height: 40px; 46 | padding-top: 20px; 47 | display: flex; 48 | align-items: center; 49 | justify-content: center; 50 | img { 51 | width: 60%; 52 | } 53 | } 54 | .name-box { 55 | height: 40px; 56 | text-align: center; 57 | color: aliceblue; 58 | } 59 | .setting-box { 60 | height: 60px; 61 | display: flex; 62 | align-items: center; 63 | justify-content: center; 64 | } 65 | } 66 | .app-box { 67 | width: 100%; 68 | } 69 | 70 | // 右侧主界面容器 71 | .main-box { 72 | margin-left: 410px; 73 | width: 100%; 74 | .el-select { 75 | width: 100%; 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /web/src/styles/app-store.scss: -------------------------------------------------------------------------------- 1 | .app-store { 2 | padding-top: 36px; 3 | // 卡片 4 | .app-card { 5 | $elCardHeight: 180px; 6 | .el-button { 7 | float: right; 8 | width: 50px; 9 | padding: 3px 0; 10 | margin-left: 3px; 11 | } 12 | .new-button { 13 | height: $elCardHeight - 20px * 2; 14 | display: flex; 15 | align-items: center; 16 | justify-content: center; 17 | .el-button { 18 | font-size: 200%; 19 | width: 100%; 20 | height: 100%; 21 | } 22 | } 23 | .el-card { 24 | height: $elCardHeight; 25 | margin: 10px 10px; 26 | } 27 | } 28 | 29 | // 弹窗 30 | } 31 | -------------------------------------------------------------------------------- /web/src/styles/app-task.scss: -------------------------------------------------------------------------------- 1 | // 中间功能列表容器 2 | .tool-box { 3 | position: fixed; 4 | left: 60px; 5 | width: 350px; 6 | height: 100%; 7 | border-right: solid 1px #f5f5f5; 8 | // 搜索框 9 | .search-box { 10 | .el-input { 11 | width: 84%; 12 | padding: 20px 5px 20px 10px; 13 | } 14 | .el-button--mini { 15 | padding: 7px 7px; 16 | } 17 | } 18 | // 流程列表 19 | .task-list { 20 | height: calc(100% - 68px); 21 | .el-table { 22 | height: 100%; 23 | // background-color: #f5f5f5; 24 | // 选中行的颜色 25 | .el-table__body tr.current-row > td { 26 | // background-color: #ebe9e7; 27 | } 28 | // 正常行的颜色 29 | .el-table__row { 30 | height: 50px; 31 | // background-color: #f5f5f5; 32 | } 33 | } 34 | } 35 | } 36 | 37 | .todo-box { 38 | margin-left: 410px; 39 | width: 100%; 40 | .content { 41 | .el-collapse { 42 | margin-bottom: 30px; 43 | } 44 | } 45 | } 46 | 47 | .task-card { 48 | .item { 49 | padding: 10px 0px; 50 | } 51 | .el-tag { 52 | margin-right: 5px; 53 | } 54 | .el-card { 55 | min-height: 65px; 56 | margin-bottom: 20px; 57 | } 58 | .el-icon-question { 59 | margin-right: 5px; 60 | color: #E6A23C; 61 | } 62 | .el-icon-success { 63 | margin-right: 5px; 64 | color: #67C23A; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /web/src/styles/flow-editor.scss: -------------------------------------------------------------------------------- 1 | .flow-editor { 2 | height: calc(100% - 36px); 3 | 4 | padding-top: 36px; 5 | 6 | .flow-item-container { 7 | .el-button { 8 | display: block; 9 | border-color: #c6e2ff; 10 | width: 80%; 11 | margin: 15px 10%; 12 | text-align: left; 13 | cursor: move; 14 | } 15 | } 16 | 17 | .el-container { 18 | height: 100%; 19 | } 20 | 21 | .el-aside { 22 | width: 20%; 23 | height: 100%; 24 | } 25 | 26 | .el-main { 27 | border-left: 1px solid #e6e6e6; 28 | border-right: 1px solid #e6e6e6; 29 | .flow-page { 30 | .card-container { 31 | height: 67px; 32 | width: 196px; 33 | .el-button { 34 | margin: 0; 35 | } 36 | .el-card__header { 37 | padding: 5px 10px; 38 | font-size: 12px; 39 | .el-dropdown { 40 | height: 12px; 41 | font-size: 6px; 42 | .el-button { 43 | padding: 1px 1px; 44 | vertical-align: middle; 45 | } 46 | } 47 | } 48 | .el-card__body { 49 | font-size: 12px; 50 | height: 40px; 51 | padding: 10px; 52 | } 53 | } 54 | .active { 55 | border: 1px solid #409eff; 56 | } 57 | } 58 | } 59 | 60 | // 中间,图 61 | .flow-page { 62 | background-color: #f9f9f9; 63 | min-height: 100%; 64 | } 65 | 66 | // 右侧配置 67 | .flow-config { 68 | height: 100%; 69 | .el-select { 70 | width: 100%; 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /web/src/styles/form-editor.scss: -------------------------------------------------------------------------------- 1 | .form-editor { 2 | .el-aside { 3 | width: 20%; 4 | height: 100%; 5 | } 6 | 7 | padding-top: 36px; 8 | 9 | .el-main { 10 | border-left: 1px solid #e6e6e6; 11 | border-right: 1px solid #e6e6e6; 12 | } 13 | 14 | height: calc(100% - 36px); 15 | .el-container { 16 | height: 100%; 17 | } 18 | 19 | .el-form { 20 | height: 100%; 21 | min-height: 100%; 22 | } 23 | 24 | .pad { 25 | min-height: 100%; 26 | } 27 | 28 | .metas-panel { 29 | .el-button { 30 | display: block; 31 | color: #409eff; 32 | border-color: #c6e2ff; 33 | background-color: #ecf5ff; 34 | width: 80%; 35 | margin: 15px 10%; 36 | text-align: left; 37 | cursor: move; 38 | } 39 | } 40 | 41 | .dynamic-item { 42 | .el-form-item { 43 | padding: 1%; 44 | margin: 0px 5px 0px 0px; 45 | } 46 | .active { 47 | border: 1px solid #409eff; 48 | } 49 | .el-form-item__label { 50 | cursor: move; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /web/src/styles/home.scss: -------------------------------------------------------------------------------- 1 | .home { 2 | height: 80%; 3 | padding-top: 36px; 4 | display: flex; 5 | align-items: center; 6 | justify-content: center; 7 | img { 8 | cursor: pointer; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /web/src/styles/index.scss: -------------------------------------------------------------------------------- 1 | @import "home.scss"; 2 | @import "app-store.scss"; 3 | @import "app-admin.scss"; 4 | @import "app-client.scss"; 5 | @import "app-task.scss"; 6 | @import "form-editor.scss"; 7 | @import "flow-editor.scss"; 8 | 9 | .el-dialog__header { 10 | border-bottom: 1px solid #ebeef5 11 | } 12 | .el-dialog__body { 13 | padding: 20px; 14 | } 15 | .el-dialog__footer { 16 | padding: 20px; 17 | padding-top: 0px; 18 | } -------------------------------------------------------------------------------- /web/src/typings/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'vuedraggable' 2 | declare module '@antv/g6-editor' 3 | declare module '@antv/g6' -------------------------------------------------------------------------------- /web/src/util/DateUtil.ts: -------------------------------------------------------------------------------- 1 | import dayjs from 'dayjs' 2 | 3 | export default class DateUtil { 4 | static format(row: any, column: any, cellValue: any, index: any) { 5 | return dayjs(cellValue).format('YYYY-MM-DD HH:mm:ss') 6 | } 7 | static formatTime(time: string) { 8 | return dayjs(time).format('YYYY.MM.DD HH:mm') 9 | } 10 | static formatDay(time: string) { 11 | return dayjs(time).format('MM.DD') 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /web/src/util/UuidUtil.ts: -------------------------------------------------------------------------------- 1 | export default class UuidUtil { 2 | static uuid() { 3 | var s: any = [] 4 | 5 | var hexDigits = '0123456789abcdef' 6 | 7 | for (var i = 0; i < 36; i++) { 8 | s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1) 9 | } 10 | 11 | // bits 12-15 of the time_hi_and_version field to 0010 12 | s[14] = '4' 13 | 14 | // bits 6-7 of the clock_seq_hi_and_reserved to 01 15 | s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1) 16 | 17 | s[8] = s[13] = s[18] = s[23] = '-' 18 | 19 | var uuid = s.join('') 20 | return uuid 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /web/src/views/AppRoom.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | -------------------------------------------------------------------------------- /web/src/views/FlowEditor.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 439 | -------------------------------------------------------------------------------- /web/src/views/FormEditor.vue: -------------------------------------------------------------------------------- 1 | 68 | 69 | 188 | -------------------------------------------------------------------------------- /web/src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 21 | -------------------------------------------------------------------------------- /web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "strict": true, 6 | "jsx": "preserve", 7 | "importHelpers": true, 8 | "moduleResolution": "node", 9 | "experimentalDecorators": true, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "sourceMap": true, 13 | "baseUrl": ".", 14 | "types": [ 15 | "webpack-env" 16 | ], 17 | "paths": { 18 | "@/*": [ 19 | "src/*" 20 | ] 21 | }, 22 | "lib": [ 23 | "esnext", 24 | "dom", 25 | "dom.iterable", 26 | "scripthost" 27 | ] 28 | }, 29 | "include": [ 30 | "src/**/*.ts", 31 | "src/**/*.tsx", 32 | "src/**/*.vue", 33 | "tests/**/*.ts", 34 | "tests/**/*.tsx" 35 | ], 36 | "exclude": [ 37 | "node_modules" 38 | ] 39 | } 40 | --------------------------------------------------------------------------------