├── src ├── App.vue ├── easy-process │ ├── config │ │ ├── provide-keys.js │ │ ├── default-node-type.js │ │ ├── event-keys.js │ │ ├── gateway-type.js │ │ ├── node-component.js │ │ └── node-config.js │ ├── assets │ │ └── icons │ │ │ ├── ep-subtract.svg │ │ │ ├── ep-exclusive-gateway.svg │ │ │ ├── ep-start.svg │ │ │ ├── ep-close.svg │ │ │ ├── ep-plus.svg │ │ │ ├── ep-edit.svg │ │ │ ├── ep-terminate.svg │ │ │ ├── ep-left.svg │ │ │ ├── ep-right.svg │ │ │ ├── ep-condition.svg │ │ │ ├── ep-tips.svg │ │ │ ├── ep-task.svg │ │ │ ├── ep-gateway.svg │ │ │ ├── ep-parallel-gateway.svg │ │ │ └── ep-setting.svg │ ├── tools │ │ ├── common-tools.js │ │ ├── z-index-tools.js │ │ ├── node-tools.js │ │ ├── process-ctrl.js │ │ └── validator.js │ ├── components │ │ ├── svg-icon │ │ │ ├── index.js │ │ │ └── ep-svg-icon.vue │ │ ├── button │ │ │ └── ep-button.vue │ │ └── drawer │ │ │ └── ep-drawer.vue │ ├── node │ │ ├── end │ │ │ └── end-node.vue │ │ ├── node-wrap.vue │ │ ├── base │ │ │ ├── base-drawer.vue │ │ │ ├── add-node.vue │ │ │ └── base-node.vue │ │ └── gateway │ │ │ └── gateway-node.vue │ ├── index.js │ └── ep-designer.vue ├── views │ ├── node │ │ ├── terminate │ │ │ └── terminate-node.vue │ │ ├── start │ │ │ ├── start-drawer.vue │ │ │ └── start-node.vue │ │ ├── notify │ │ │ ├── notify-drawer.vue │ │ │ └── notify-node.vue │ │ ├── task │ │ │ ├── task-drawer.vue │ │ │ └── task-node.vue │ │ └── condition │ │ │ ├── condition-drawer.vue │ │ │ └── condition-node.vue │ ├── test.vue │ └── index.vue ├── router │ └── index.js └── main.js ├── vite └── plugins │ ├── setup-extend.js │ ├── auto-import.js │ ├── svg-icon.js │ ├── copy.js │ ├── index.js │ └── compression.js ├── .gitignore ├── index.html ├── LICENSE ├── package.json ├── vite.config.js ├── public └── mock │ └── data.json └── README.md /src/App.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | -------------------------------------------------------------------------------- /vite/plugins/setup-extend.js: -------------------------------------------------------------------------------- 1 | import setupExtend from 'vite-plugin-vue-setup-extend' 2 | 3 | export default function createSetupExtend() { 4 | return setupExtend() 5 | } 6 | -------------------------------------------------------------------------------- /src/easy-process/config/provide-keys.js: -------------------------------------------------------------------------------- 1 | // 依赖注入数据的key 2 | 3 | // 流程控制器KEY 4 | export const KEY_PROCESS_CTRL = 'processCtrl' 5 | 6 | // 节点验证器KEY 7 | export const KEY_VALIDATOR = 'validator' 8 | 9 | // 流程数据KEY 10 | export const KEY_PROCESS_DATA = 'processData' -------------------------------------------------------------------------------- /src/easy-process/config/default-node-type.js: -------------------------------------------------------------------------------- 1 | // 网关节点 2 | export const GATEWAY = 'gateway' 3 | // 条件节点 4 | export const CONDITION = 'condition' 5 | // 开始节点 6 | export const START = 'start' 7 | // 任务节点 8 | export const TASK = 'task' 9 | // 终止节点 10 | export const TERMINATE = 'terminate' 11 | 12 | -------------------------------------------------------------------------------- /vite/plugins/auto-import.js: -------------------------------------------------------------------------------- 1 | import autoImport from 'unplugin-auto-import/vite' 2 | 3 | export default function createAutoImport() { 4 | return autoImport({ 5 | imports: [ 6 | 'vue', 7 | 'vue-router', 8 | 'pinia' 9 | ], 10 | dts: false 11 | }) 12 | } 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | dist-lib 14 | *.local 15 | 16 | 17 | # Editor directories and files 18 | .vscode/* 19 | !.vscode/extensions.json 20 | .idea 21 | .DS_Store 22 | *.suo 23 | *.ntvs* 24 | *.njsproj 25 | *.sln 26 | *.sw? 27 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Easy Process 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/easy-process/assets/icons/ep-subtract.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/easy-process/tools/common-tools.js: -------------------------------------------------------------------------------- 1 | import {v4 as uuidv4 } from 'uuid' 2 | 3 | /** 4 | * 获取UUID 5 | * @returns {*} 6 | */ 7 | export const getUUID = () =>{ 8 | return uuidv4() 9 | } 10 | 11 | /** 12 | * 复制对象(深拷贝) 13 | * @param source 14 | */ 15 | export const copy = (source) => { 16 | if(source) { 17 | const str = JSON.stringify(source); 18 | return JSON.parse(str); 19 | } 20 | return null; 21 | } -------------------------------------------------------------------------------- /vite/plugins/svg-icon.js: -------------------------------------------------------------------------------- 1 | import { createSvgIconsPlugin } from 'vite-plugin-svg-icons' 2 | import path from 'path' 3 | 4 | export default function createSvgIcon(isBuild) { 5 | return createSvgIconsPlugin({ 6 | iconDirs: [path.resolve(process.cwd(), 'src/easy-process/assets/icons')], 7 | symbolId: 'icon-[dir]-[name]', 8 | customDomId: '__EASY_PROCESS_SVG_ICONS__', 9 | svgoOptions: isBuild 10 | }) 11 | } 12 | -------------------------------------------------------------------------------- /src/easy-process/assets/icons/ep-exclusive-gateway.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/easy-process/components/svg-icon/index.js: -------------------------------------------------------------------------------- 1 | let iconPrefix = '#icon-' 2 | 3 | 4 | /** 5 | * 加载图标配置 6 | * @param iconConfig 7 | */ 8 | export const loadIconConfig = (iconConfig) => { 9 | let {prefix} = iconConfig || {} 10 | setIconPrefix(prefix) 11 | } 12 | 13 | /** 14 | * 设置图标前缀 15 | * @param prefix 16 | */ 17 | const setIconPrefix = (prefix) => { 18 | if (prefix) { 19 | iconPrefix = prefix 20 | } 21 | } 22 | 23 | /** 24 | * 获取图标前缀 25 | * @returns {string} 26 | */ 27 | export const getIconPrefix = () => { 28 | return iconPrefix 29 | } -------------------------------------------------------------------------------- /src/easy-process/assets/icons/ep-start.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/easy-process/config/event-keys.js: -------------------------------------------------------------------------------- 1 | // 绑定事件的key 2 | 3 | // 设计器加载完毕KEY 4 | export const ON_DESIGNER_MOUNTED = 'on_designer_mounted' 5 | // 新增节点之前KEY 6 | export const ON_PRE_ADD_NODE = 'on_pre_add_node' 7 | // 新增节点之后KEY 8 | export const ON_AFTER_ADD_NODE = 'on_after_add_node' 9 | // 移除节点之前KEY 10 | export const ON_PRE_REMOVE_NODE = 'on_pre_remove_node' 11 | // 移除节点之后KEY 12 | export const ON_AFTER_REMOVE_NODE = 'on_after_remove_node' 13 | // 更新节点配置之前KEY 14 | export const ON_PRE_UPDATE_NODE_CONFIG = 'on_pre_update_node_config' 15 | // 更新节点配置之后KEY 16 | export const ON_AFTER_UPDATE_NODE_CONFIG = 'on_after_update_node_config' 17 | -------------------------------------------------------------------------------- /src/easy-process/assets/icons/ep-close.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/views/node/terminate/terminate-node.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 24 | 25 | 28 | -------------------------------------------------------------------------------- /vite/plugins/copy.js: -------------------------------------------------------------------------------- 1 | import copy from 'rollup-plugin-copy' 2 | 3 | export default function createCopy() { 4 | return copy({ 5 | hook: 'closeBundle', 6 | targets: [ 7 | { src: 'src/easy-process/assets/*', dest: 'dist/assets' }, 8 | { src: 'src/easy-process/config/event-keys.js', dest: 'dist/config' }, 9 | { src: 'src/easy-process/config/gateway-type.js', dest: 'dist/config' }, 10 | { src: 'src/easy-process/config/default-node-type.js', dest: 'dist/config' }, 11 | { src: 'src/easy-process/config/provide-keys.js', dest: 'dist/config' }, 12 | ] 13 | }) 14 | } -------------------------------------------------------------------------------- /src/easy-process/assets/icons/ep-plus.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/views/node/start/start-drawer.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 24 | 25 | 28 | -------------------------------------------------------------------------------- /src/views/node/notify/notify-drawer.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 24 | 25 | 28 | -------------------------------------------------------------------------------- /src/views/node/task/task-drawer.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 24 | 25 | 28 | -------------------------------------------------------------------------------- /src/views/node/condition/condition-drawer.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 24 | 25 | 28 | -------------------------------------------------------------------------------- /vite/plugins/index.js: -------------------------------------------------------------------------------- 1 | import vue from '@vitejs/plugin-vue' 2 | 3 | import createAutoImport from './auto-import' 4 | import createSvgIcon from './svg-icon' 5 | import createCompression from './compression' 6 | import createSetupExtend from './setup-extend' 7 | import createCopy from './copy' 8 | 9 | export default function createVitePlugins(viteEnv, isBuild = false) { 10 | const vitePlugins = [vue()] 11 | vitePlugins.push(createAutoImport()) 12 | vitePlugins.push(createSetupExtend()) 13 | vitePlugins.push(createSvgIcon(isBuild)) 14 | vitePlugins.push(createCopy()) 15 | isBuild && vitePlugins.push(...createCompression(viteEnv)) 16 | return vitePlugins 17 | } 18 | -------------------------------------------------------------------------------- /src/easy-process/config/gateway-type.js: -------------------------------------------------------------------------------- 1 | const EXCLUSIVE = 'exclusive' 2 | const PARALLEL = 'parallel' 3 | 4 | const GATEWAY_TYPE_LIST = [ 5 | { 6 | type: EXCLUSIVE, 7 | name: '排他网关', 8 | icon: 'ep-exclusive-gateway', 9 | desc: '按照分支顺序(从左向右)进行计算,选择第一个条件计算为true的分支继续流程,未设置条件时默认通过。' 10 | }, 11 | { 12 | type: PARALLEL, 13 | name: '并行网关', 14 | icon: 'ep-parallel-gateway', 15 | desc: '计算所有分支条件,符合条件的分支可并列执行,未设置条件时默认通过。' 16 | } 17 | ] 18 | 19 | const GATEWAY_TYPE_MAP = new Map() 20 | GATEWAY_TYPE_LIST.forEach(item => { 21 | GATEWAY_TYPE_MAP.set(item.type, item) 22 | }) 23 | 24 | export {GATEWAY_TYPE_LIST, GATEWAY_TYPE_MAP, EXCLUSIVE, PARALLEL} 25 | 26 | -------------------------------------------------------------------------------- /src/easy-process/assets/icons/ep-edit.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/easy-process/tools/z-index-tools.js: -------------------------------------------------------------------------------- 1 | let zIndex = 2000 2 | let zIndexFunction = null 3 | 4 | /** 5 | * 加载zIndex配置 6 | */ 7 | export const loadZIndexConfig = ({initZIndexValue, bindZIndexFunction}) => { 8 | if (bindZIndexFunction) { 9 | if (typeof bindZIndexFunction === 'function') { 10 | zIndexFunction = bindZIndexFunction 11 | } else { 12 | throw new Error('zIndexFunction must be a function') 13 | } 14 | } else if (initZIndexValue) { 15 | zIndex = initZIndexValue 16 | } 17 | } 18 | 19 | /** 20 | * 获取下一个zIndex 21 | */ 22 | export const nextZIndex = () => { 23 | if (zIndexFunction && typeof zIndexFunction === 'function') { 24 | return zIndexFunction() 25 | } 26 | return zIndex++; 27 | } -------------------------------------------------------------------------------- /src/easy-process/assets/icons/ep-terminate.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/easy-process/assets/icons/ep-left.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/easy-process/assets/icons/ep-right.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import { createWebHistory, createRouter } from 'vue-router' 2 | 3 | // 公共路由 4 | export const constantRoutes = [ 5 | { 6 | path: '', 7 | redirect: '/index', 8 | children: [ 9 | { 10 | path: '/index', 11 | component: () => import('@/views/index'), 12 | name: 'Index' 13 | } 14 | ] 15 | }, 16 | { 17 | path: '/test', 18 | children: [ 19 | { 20 | path: '/test', 21 | component: () => import('@/views/test'), 22 | name: 'Test' 23 | } 24 | ] 25 | }, 26 | ] 27 | 28 | const router = createRouter({ 29 | history: createWebHistory(), 30 | routes: constantRoutes, 31 | scrollBehavior(to, from, savedPosition) { 32 | if (savedPosition) { 33 | return savedPosition 34 | } else { 35 | return { top: 0 } 36 | } 37 | }, 38 | }); 39 | 40 | export default router; 41 | -------------------------------------------------------------------------------- /src/views/node/start/start-node.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 34 | 35 | 38 | -------------------------------------------------------------------------------- /src/views/node/notify/notify-node.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 34 | 35 | 38 | -------------------------------------------------------------------------------- /src/views/node/task/task-node.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 34 | 35 | 38 | -------------------------------------------------------------------------------- /src/easy-process/tools/node-tools.js: -------------------------------------------------------------------------------- 1 | import {nodeConfig} from '../config/node-config.js' 2 | import {START, GATEWAY, CONDITION} from '../config/default-node-type.js' 3 | import {getUUID} from '../tools/common-tools.js' 4 | 5 | /** 6 | * 创建节点 7 | * @param nodeType 8 | */ 9 | export const createNode = (nodeType = START) => { 10 | let config = nodeConfig[nodeType] 11 | let node = generateNodeObj(config) 12 | if (nodeType === GATEWAY) { 13 | let conditionConfig = nodeConfig[CONDITION] 14 | node.branchList = [generateNodeObj(conditionConfig), generateNodeObj(conditionConfig)] 15 | } 16 | return node 17 | } 18 | 19 | const generateNodeObj = (config) => { 20 | return { 21 | // 生成临时节点ID 22 | tmpNodeId: getUUID(), 23 | nodeName: config.nodeName, 24 | nodeType: config.nodeType, 25 | config: config.config || {}, 26 | childNode: null 27 | } 28 | } -------------------------------------------------------------------------------- /src/easy-process/assets/icons/ep-condition.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vite/plugins/compression.js: -------------------------------------------------------------------------------- 1 | import compression from 'vite-plugin-compression' 2 | 3 | export default function createCompression(env) { 4 | const { VITE_BUILD_COMPRESS } = env 5 | const plugin = [] 6 | if (VITE_BUILD_COMPRESS) { 7 | const compressList = VITE_BUILD_COMPRESS.split(',') 8 | if (compressList.includes('gzip')) { 9 | // #使用gzip解压缩静态文件 10 | plugin.push( 11 | compression({ 12 | ext: '.gz', 13 | deleteOriginFile: false 14 | }) 15 | ) 16 | } 17 | if (compressList.includes('brotli')) { 18 | plugin.push( 19 | compression({ 20 | ext: '.br', 21 | algorithm: 'brotliCompress', 22 | deleteOriginFile: false 23 | }) 24 | ) 25 | } 26 | } 27 | return plugin 28 | } 29 | -------------------------------------------------------------------------------- /src/easy-process/assets/icons/ep-tips.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/easy-process/components/svg-icon/ep-svg-icon.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 34 | 35 | 44 | -------------------------------------------------------------------------------- /src/easy-process/node/end/end-node.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 29 | 30 | 50 | -------------------------------------------------------------------------------- /src/views/test.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 34 | 35 | 42 | -------------------------------------------------------------------------------- /src/easy-process/assets/icons/ep-task.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 gaoyang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/views/node/condition/condition-node.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 47 | 48 | 53 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "qx-easy-process", 3 | "private": false, 4 | "version": "3.0.10", 5 | "type": "module", 6 | "description": "easy-process流程设计器,基于vue3 + vite4实现,具备低代码、快速应用及扩展的特点。【工作流】【流程引擎】", 7 | "keywords": [ 8 | "process", 9 | "process designer", 10 | "qx-easy-process", 11 | "流程引擎", 12 | "流程设计器", 13 | "工作流" 14 | ], 15 | "main": "dist/easy-process.umd.js", 16 | "module": "dist/easy-process.es.js", 17 | "files": [ 18 | "dist" 19 | ], 20 | "license": "MIT", 21 | "author": { 22 | "name": "GaoYang", 23 | "email": "331607151@qq.com" 24 | }, 25 | "scripts": { 26 | "dev": "vite", 27 | "build": "vite build", 28 | "preview": "vite preview" 29 | }, 30 | "dependencies": { 31 | "fast-glob": "^3.3.3", 32 | "less": "^4.1.3", 33 | "less-loader": "^11.1.0", 34 | "uuid": "^9.0.0", 35 | "vue": "^3.2.47", 36 | "vue-router": "4.1.4" 37 | }, 38 | "devDependencies": { 39 | "@vitejs/plugin-vue": "^4.1.0", 40 | "sass": "1.56.1", 41 | "unplugin-auto-import": "0.11.4", 42 | "vite": "^4.3.9", 43 | "vite-plugin-compression": "0.5.1", 44 | "vite-plugin-svg-icons": "2.0.1", 45 | "vite-plugin-vue-setup-extend": "0.4.0", 46 | "rollup-plugin-copy": "^3.5.0" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/easy-process/tools/process-ctrl.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 流程控制器 4 | * @returns {{}} 5 | */ 6 | export const createProcessCtrl = () => { 7 | let ctrl = {}; 8 | // 节点信息 9 | ctrl.nodes = new Map() 10 | // 事件绑定 11 | ctrl.events = new Map() 12 | 13 | // 添加节点 14 | ctrl.addNode = (nodeId, nodeConfig) => { 15 | ctrl.nodes.set(nodeId, nodeConfig) 16 | } 17 | 18 | // 获取节点 19 | ctrl.getNode = (nodeId) => { 20 | return ctrl.nodes.get(nodeId) 21 | } 22 | 23 | // 移除节点 24 | ctrl.removeNode = (nodeId) => { 25 | ctrl.nodes.delete(nodeId) 26 | } 27 | 28 | // 绑定事件 29 | ctrl.bindEvent = (key, callback) => { 30 | if (key && callback && typeof callback === 'function') { 31 | ctrl.events.set(key, callback) 32 | } 33 | } 34 | 35 | // 解除绑定事件 36 | ctrl.unbindEvent = (key) => { 37 | ctrl.events.delete(key) 38 | } 39 | 40 | // 获取绑定事件 41 | ctrl.getBindEvent = (key) => { 42 | return ctrl.events.get(key) 43 | } 44 | // 触发事件 45 | ctrl.triggerEvent = (key, params) => { 46 | let f = ctrl.getBindEvent(key) 47 | if (f) { 48 | return f(params) 49 | } 50 | return null 51 | } 52 | // 触发事件结果是否为真 53 | ctrl.eventReturnIsTrue = (result) => { 54 | if (typeof result === 'boolean') { 55 | return result 56 | } 57 | return true 58 | } 59 | 60 | return ctrl 61 | } 62 | -------------------------------------------------------------------------------- /src/easy-process/assets/icons/ep-gateway.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/easy-process/config/node-component.js: -------------------------------------------------------------------------------- 1 | import { shallowRef, defineAsyncComponent } from 'vue' 2 | export const nodeComponents = shallowRef({}); 3 | export const drawerComponents = shallowRef({}); 4 | 5 | const nodePattern = /([^/]+)-node\.vue$/i 6 | const drawerPattern = /([^/]+)-drawer\.vue$/i 7 | 8 | export const initNodes = (extNodes) => { 9 | // 加载本地节点组件 10 | initComponents(import.meta.glob('../node/*/*.vue')) 11 | // 加载扩展节点组件 12 | if (extNodes) { 13 | initComponents(extNodes) 14 | } 15 | } 16 | 17 | const initComponents = (components) => { 18 | Object.keys(components).forEach(key => { 19 | loadNodeComponents(components, key); 20 | loadDrawerComponents(components, key); 21 | }) 22 | } 23 | 24 | const loadNodeComponents = (components, key) => { 25 | let nodeType = getNodeType(nodePattern, key) 26 | if (nodeType) { 27 | let component = components[key] 28 | nodeComponents.value[nodeType] = defineAsyncComponent(component) 29 | } 30 | } 31 | const loadDrawerComponents = (components, key) => { 32 | let nodeType = getNodeType(drawerPattern, key) 33 | if (nodeType) { 34 | let component = components[key] 35 | drawerComponents.value[nodeType] = defineAsyncComponent(component) 36 | } 37 | } 38 | 39 | const getNodeType = (pattern, path) => { 40 | let result = pattern.exec(path) 41 | if (result !== null) { 42 | return result[1] 43 | } 44 | return null 45 | } 46 | 47 | export const getNodeComponent = () => { 48 | 49 | } -------------------------------------------------------------------------------- /src/easy-process/assets/icons/ep-parallel-gateway.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/easy-process/tools/validator.js: -------------------------------------------------------------------------------- 1 | import { reactive } from "vue"; 2 | export const createValidator = () => { 3 | let validator = {}; 4 | validator.nodeRules = new Map() 5 | validator.results = reactive(new Map()) 6 | 7 | // 注册节点验证规则 8 | validator.register = (nodeId, validatorFun) => { 9 | if(validatorFun && validatorFun instanceof Function) { 10 | validator.nodeRules.set(nodeId, validatorFun) 11 | } 12 | } 13 | 14 | // 移除节点验证规则 15 | validator.remove = (nodeId) => { 16 | validator.nodeRules.delete(nodeId) 17 | validator.results.delete(nodeId) 18 | } 19 | 20 | // 验证节点 21 | validator.validate = () => { 22 | let valid = true 23 | let messages = [] 24 | validator.nodeRules.forEach((value, key) => { 25 | let fun = value 26 | if(fun && fun instanceof Function) { 27 | let result = fun() 28 | if(!result.valid) { 29 | valid = false 30 | validator.results.set(key, result) 31 | messages.push(result.message) 32 | } else { 33 | validator.results.delete(key) 34 | } 35 | } 36 | }) 37 | return {valid, messages} 38 | } 39 | 40 | // 获取指定节点的结果 41 | validator.getResult = (nodeId) => { 42 | let result = validator.results.get(nodeId) 43 | if(result) { 44 | return result 45 | } 46 | } 47 | 48 | return validator 49 | } 50 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './App.vue' 3 | import router from './router' 4 | 5 | // svg图标 6 | import 'virtual:svg-icons-register' 7 | // easy-process 8 | import ProcessDesigner from '@/easy-process/index' 9 | 10 | const app = createApp(App) 11 | app.use(router) 12 | app.use(ProcessDesigner, { 13 | nodeImplPath: import.meta.glob('@/views/node/**'), 14 | nodeConfig: [ 15 | { 16 | "nodeType": "start", 17 | "nodeName": "发起人", 18 | "config": { 19 | name: '张三' 20 | }, 21 | }, 22 | { 23 | "nodeType": "task", 24 | "nodeName": "审批人", 25 | }, 26 | { 27 | "nodeType": "terminate", 28 | "hasDrawer": false, // 禁用节点配置功能 29 | }, 30 | { 31 | "nodeType": "notify", 32 | "nodeName": "抄送", 33 | "color": "#FFFFFF", // 节点标题颜色 34 | "bgColor": "#8225e4", // 节点标题背景颜色 35 | "hasDrawer": true, // 节点是否可以进行配置 36 | "icon": { // 图标 37 | "name": "ep-terminate", // 图标名 38 | "color": "#8225e4" // 颜色 39 | }, 40 | "config": { 41 | name: '主管' 42 | }, 43 | } 44 | ], 45 | zIndexConfig: { 46 | initZIndexValue: 1000, 47 | bindZIndexFunction: () => { 48 | if (!window.zIndex) { 49 | window.zIndex = 500 50 | } 51 | return window.zIndex++ 52 | } 53 | }, 54 | iconConfig: { 55 | prefix: '#icon-' 56 | } 57 | }) 58 | app.mount('#app') 59 | -------------------------------------------------------------------------------- /src/easy-process/components/button/ep-button.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 19 | 20 | -------------------------------------------------------------------------------- /src/easy-process/index.js: -------------------------------------------------------------------------------- 1 | import {initNodes} from './config/node-component.js' 2 | import {loadNodeConfig} from './config/node-config.js' 3 | import {loadZIndexConfig} from './tools/z-index-tools.js' 4 | import {loadIconConfig} from './components/svg-icon/index.js' 5 | 6 | const requireGlobalComponent = import.meta.glob([ 7 | './ep-designer.vue', 8 | './components/svg-icon/ep-svg-icon.vue', 9 | ], { eager: true }); 10 | 11 | /** 12 | * 安装组件 13 | * @param app 14 | * @param options 15 | */ 16 | const install = (app, options) => { 17 | console.log("run install") 18 | if (install.installed) { 19 | return; 20 | } 21 | 22 | install.installed; 23 | 24 | // 加载配置 25 | loadOptions(app, options) 26 | 27 | //注册全局组件 28 | registerGlobalComponent(app, options); 29 | 30 | }; 31 | 32 | /** 33 | * 注册全局组件 34 | * @param app 35 | * @param options 36 | */ 37 | const registerGlobalComponent = (app, options) => { 38 | Object.keys(requireGlobalComponent).forEach((fileName) => { 39 | const component = requireGlobalComponent[fileName]; 40 | //获取组件名 41 | const componentName = component.default.name; 42 | app.component(componentName, component.default || component); 43 | }); 44 | } 45 | 46 | /** 47 | * 加载配置 48 | * @param app 49 | * @param options 50 | */ 51 | const loadOptions = (app, options) => { 52 | let {nodeImplPath, nodeConfig, zIndexConfig, iconConfig} = options; 53 | // 加载节点组件 54 | initNodes(nodeImplPath) 55 | // 加载节点配置 56 | loadNodeConfig(nodeConfig) 57 | // 加载z-index配置 58 | loadZIndexConfig(zIndexConfig) 59 | // 加载图标配置 60 | loadIconConfig(iconConfig) 61 | } 62 | 63 | //环境检测 64 | if (typeof window !== "undefined" && window.Vue) { 65 | install(window.Vue); 66 | } 67 | 68 | //对外暴露install方法 69 | export default { 70 | install, 71 | } -------------------------------------------------------------------------------- /src/easy-process/node/node-wrap.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 67 | 68 | 71 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig, loadEnv } from 'vite' 2 | import path from 'path' 3 | import createVitePlugins from './vite/plugins' 4 | 5 | // https://vitejs.dev/config/ 6 | export default defineConfig(({ mode, command }) => { 7 | const env = loadEnv(mode, process.cwd()) 8 | const { VITE_APP_ENV } = env 9 | return { 10 | // 部署生产环境和开发环境下的URL。 11 | // 默认情况下,vite 会假设你的应用是被部署在一个域名的根路径上 12 | // 例如 https://www.quxiou.vip/。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.quxiou.vip/admin/,则设置 baseUrl 为 /admin/。 13 | base: '/', 14 | plugins: createVitePlugins(env, command === 'build'), 15 | build: { 16 | outDir: "dist", // 打包输出目录 17 | lib: { 18 | // 指定库入口文件 19 | entry: path.resolve(__dirname, "src/easy-process/index.js"), 20 | // 指定库的名称 21 | name: "easy-process", 22 | fileName: (format) => `easy-process.${format}.js` // 输出文件名格式化函数。例如,'my-vue-plugin.es.js' 和 'my-vue-plugin.umd.js'。 23 | }, 24 | rollupOptions: { 25 | external: ["vue"], // 防止将vue打包进库 26 | output: { 27 | globals: { 28 | vue: "Vue", 29 | }, 30 | assetFileNames: (assetInfo) => { 31 | if (assetInfo.name.endsWith(".css")) { 32 | return "css/[name][extname]"; 33 | } 34 | return "[name].[hash][extname]"; 35 | }, 36 | }, 37 | }, 38 | }, 39 | resolve: { 40 | // https://cn.vitejs.dev/config/#resolve-alias 41 | alias: { 42 | // 设置路径 43 | '~': path.resolve(__dirname, './'), 44 | // 设置别名 45 | '@': path.resolve(__dirname, './src') 46 | }, 47 | // https://cn.vitejs.dev/config/#resolve-extensions 48 | extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue'] 49 | }, 50 | // vite 相关配置 51 | server: { 52 | port: 80, 53 | host: true, 54 | open: true, 55 | }, 56 | optimizeDeps: { 57 | exclude: ["fsevents"], 58 | }, 59 | //fix:error:stdin>:7356:1: warning: "@charset" must be the first rule in the file 60 | css: { 61 | postcss: { 62 | plugins: [ 63 | { 64 | postcssPlugin: 'internal:charset-removal', 65 | AtRule: { 66 | charset: (atRule) => { 67 | if (atRule.name === 'charset') { 68 | atRule.remove(); 69 | } 70 | } 71 | } 72 | } 73 | ] 74 | } 75 | } 76 | } 77 | }) 78 | -------------------------------------------------------------------------------- /public/mock/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "nodeConfig": { 3 | "tmpNodeId": "ab173ea9-d776-4b8a-8e84-ee6d6fe07b44", 4 | "nodeName": "发起人", 5 | "nodeType": "start", 6 | "config": { 7 | "name": "张三" 8 | }, 9 | "childNode": { 10 | "tmpNodeId": "1632f624-8b04-4b62-93fc-7cef5b124189", 11 | "nodeName": "网关", 12 | "nodeType": "gateway", 13 | "config": { }, 14 | "childNode": { 15 | "tmpNodeId": "c4787616-3666-406e-bb10-75eca8a72a86", 16 | "nodeName": "抄送", 17 | "nodeType": "notify", 18 | "config": { 19 | "name": "HR" 20 | }, 21 | "childNode": null 22 | }, 23 | "branchList": [ 24 | { 25 | "tmpNodeId": "f34a892f-e3a5-4503-ac95-fadf8f72cd20", 26 | "nodeName": "条件", 27 | "nodeType": "condition", 28 | "config": { 29 | "days": "3" 30 | }, 31 | "childNode": { 32 | "tmpNodeId": "6af25285-ad47-4b19-ba20-401d5660a49b", 33 | "nodeName": "审批人", 34 | "nodeType": "task", 35 | "config": { 36 | "name": "项目经理" 37 | }, 38 | "childNode": null 39 | } 40 | }, 41 | { 42 | "tmpNodeId": "46c9248b-64b4-491e-848c-b208ebb09d18", 43 | "nodeName": "条件", 44 | "nodeType": "condition", 45 | "config": { 46 | "days": "5" 47 | }, 48 | "childNode": { 49 | "tmpNodeId": "3e83d699-7f9d-4814-83de-f073f05f0e7a", 50 | "nodeName": "审批人", 51 | "nodeType": "task", 52 | "config": { 53 | "name": "项目经理" 54 | }, 55 | "childNode": { 56 | "tmpNodeId": "0d17edf9-1acb-4b35-a1a8-a049c0dea37d", 57 | "nodeName": "审批人", 58 | "nodeType": "task", 59 | "config": { 60 | "name": "总监" 61 | }, 62 | "childNode": null 63 | } 64 | } 65 | }, 66 | { 67 | "tmpNodeId": "80569c58-6b66-4bfc-bb4b-cee6ca376650", 68 | "nodeName": "条件", 69 | "nodeType": "condition", 70 | "config": { }, 71 | "childNode": { 72 | "tmpNodeId": "8d0f7822-36df-49b0-8dcd-19d11fce6429", 73 | "nodeName": "终止", 74 | "nodeType": "terminate", 75 | "config": { }, 76 | "childNode": null 77 | } 78 | } 79 | ], 80 | "gatewayType": "exclusive" 81 | } 82 | } 83 | } -------------------------------------------------------------------------------- /src/easy-process/node/base/base-drawer.vue: -------------------------------------------------------------------------------- 1 | 14 | 80 | 81 | 87 | -------------------------------------------------------------------------------- /src/views/index.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 82 | 83 | 124 | -------------------------------------------------------------------------------- /src/easy-process/config/node-config.js: -------------------------------------------------------------------------------- 1 | import { GATEWAY, CONDITION, START, TASK, TERMINATE } from "./default-node-type.js" 2 | import {copy} from "@/easy-process/tools/common-tools.js"; 3 | 4 | // 节点配置 5 | const nodeConfig = {} 6 | 7 | // 路由节点配置 8 | nodeConfig[GATEWAY] = { 9 | "nodeType": GATEWAY, 10 | "nodeName": "网关", // 节点标题 11 | "enable": true, // 节点是否可用 12 | "canAdd": true, // 节点是否可以增加 13 | "hasDrawer": false, // 节点是否可以进行配置 14 | "icon": { // 图标 15 | "name": "ep-gateway", // 图标名 16 | "color": "#3CB371" // 颜色 17 | }, 18 | "branchList": [] 19 | } 20 | 21 | // 条件节点配置 22 | nodeConfig[CONDITION] = { 23 | "nodeType": CONDITION, 24 | "nodeName": "条件", // 节点标题 25 | "color": "#FFFFFF", // 节点标题颜色 26 | "bgColor": "#3CB371", // 节点标题背景颜色 27 | "enable": true, // 节点是否可用 28 | "canAdd": false, // 节点是否可以增加 29 | "canRemoved": true, // 节点是否能够移除 30 | "hasDrawer": true, // 节点是否可以进行配置 31 | "icon": { // 图标 32 | "name": "ep-condition", // 图标名 33 | "color": "#3CB371" // 颜色 34 | } 35 | } 36 | 37 | // 开始节点配置 38 | nodeConfig[START] = { 39 | "nodeType": START, 40 | "nodeName": "开始", // 节点标题 41 | "color": "#FFFFFF", // 节点标题颜色 42 | "bgColor": "#1e83e9", // 节点标题背景颜色 43 | "enable": true, // 节点是否可用 44 | "canAdd": false, // 节点是否可以增加 45 | "canRemoved": false, // 节点是否能够移除 46 | "hasDrawer": true, // 节点是否可以进行配置 47 | "icon": { // 图标 48 | "name": "ep-start", // 图标名 49 | "color": "#1e83e9" // 颜色 50 | } 51 | } 52 | 53 | // 任务节点配置 54 | nodeConfig[TASK] = { 55 | "nodeType": TASK, 56 | "nodeName": "任务", // 节点标题 57 | "color": "#FFFFFF", // 节点标题颜色 58 | "bgColor": "#FF8C00", // 节点标题背景颜色 59 | "enable": true, // 节点是否可用 60 | "canAdd": true, // 节点是否可以增加 61 | "canRemoved": true, // 节点是否能够移除 62 | "hasDrawer": true, // 节点是否可以进行配置 63 | "icon": { // 图标 64 | "name": "ep-task", // 图标名 65 | "color": "#FF8C00" // 颜色 66 | } 67 | } 68 | 69 | // 终止节点配置 70 | nodeConfig[TERMINATE] = { 71 | "nodeType": TERMINATE, 72 | "nodeName": "终止", // 节点标题 73 | "color": "#FFFFFF", // 节点标题颜色 74 | "bgColor": "#F56C6C", // 节点标题背景颜色 75 | "enable": true, // 节点是否可用 76 | "canAdd": true, // 节点是否可以增加 77 | "canRemoved": true, // 节点是否能够移除 78 | "hasDrawer": true, // 节点是否可以进行配置 79 | "icon": { // 图标 80 | "name": "ep-terminate", // 图标名 81 | "color": "#F56C6C" // 颜色 82 | } 83 | } 84 | 85 | /** 86 | * 加载节点配置 87 | * @param externalConfig 88 | */ 89 | const loadNodeConfig = (externalConfig) => { 90 | if (externalConfig && externalConfig.length > 0) { 91 | externalConfig.forEach(item => { 92 | if (!item.nodeType) { 93 | throw new Error('加载自定义配置:nodeType节点类型不能为空') 94 | } 95 | if (!nodeConfig[item.nodeType] && !item.nodeName) { 96 | throw new Error('加载自定义配置:nodeName节点类型不能为空') 97 | } 98 | let node = nodeConfig[item.nodeType] || copy(nodeDefaultValue) 99 | nodeConfig[item.nodeType] = { ...node, ...item} 100 | }) 101 | } 102 | } 103 | 104 | // 默认节点值 105 | const nodeDefaultValue = { 106 | "enable": true, // 节点是否可用 107 | "color": "#FFFFFF", // 节点标题颜色 108 | "bgColor": "#8225e4", // 节点标题背景颜色 109 | "canAdd": true, // 节点是否可以增加 110 | "canRemoved": true, // 节点是否能够移除 111 | "hasDrawer": true, // 节点是否可以进行配置 112 | "icon": { // 图标 113 | "name": "task", // 图标名 114 | "color": "#8225e4" // 颜色 115 | } 116 | } 117 | 118 | export {nodeConfig, loadNodeConfig} -------------------------------------------------------------------------------- /src/easy-process/components/drawer/ep-drawer.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 84 | 85 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Easy Process V3 2 | Easy Process 流程设计器,基于vue3 + vite4 实现,提供易用的流程管理框架和组件,支持快速集成与定制,适用于多种应用场景。 3 | 4 | 5 | ## 开源许可协议 6 | - MIT 7 | 8 | 9 | ## 体验入口 10 | [http://process.uncode.vip](http://process.uncode.vip) 11 | 12 | 13 | 14 | ## 传送门 15 | **项目源码:**[easy-process](https://gitee.com/quxiu-code/easy-process) 16 | 17 | **示例源码:**[easy-process-demo](https://gitee.com/quxiu-code/easy-process-demo) 18 | 19 | **使用手册:** [http://docs.process.uncode.vip](http://docs.process.uncode.vip) 20 | 21 | 22 | ## 开发环境 23 | 24 | ``` 25 | Node >= 14 26 | ``` 27 | 28 | ## 技术选型 29 | 30 | - vue3 31 | - vite4 32 | 33 | 34 | 35 | ## 安装 36 | 37 | 38 | 39 | ### 安装qx-easy-process 40 | 41 | ```shell 42 | npm install qx-easy-process 43 | ``` 44 | 45 | 46 | 47 | ### 安装vite-plugin-svg-icons 48 | 49 | ```shell 50 | npm install vite-plugin-svg-icons -D 51 | ``` 52 | 53 | 54 | 55 | 56 | 57 | ## 快速开始 58 | 59 | ### 导入组件 60 | 61 | ```js 62 | // main.js 63 | import { createApp } from 'vue' 64 | import App from './App.vue' 65 | 66 | // easy-process 67 | import EasyProcess from 'qx-easy-process' 68 | import 'qx-easy-process/dist/css/style.css' 69 | 70 | // svg图标 71 | import 'virtual:svg-icons-register' 72 | 73 | const app = createApp(App) 74 | 75 | app.use(EasyProcess, { 76 | // 指定节点视图实现和节点配置实现目录 77 | nodeImplPath: import.meta.glob('src/views/node/**') 78 | }) 79 | app.mount('#app') 80 | ``` 81 | 82 | 83 | 84 | ### 配置vite-plugin-svg-icons 85 | 86 | ```js 87 | // vite.config.js 88 | import { defineConfig } from 'vite'; 89 | import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'; 90 | import path from 'path'; 91 | 92 | export default defineConfig({ 93 | plugins: [ 94 | createSvgIconsPlugin({ 95 | // 指定qx-easy-process图标文件夹 96 | iconDirs: [path.resolve(process.cwd(), 'node_modules/qx-easy-process/dist/assets/icons')], 97 | // 指定symbolId格式 98 | symbolId: 'icon-[dir]-[name]', 99 | }) 100 | ], 101 | }); 102 | ``` 103 | 104 | 105 | 106 | ### 实现节点组件 107 | 108 | 为了提高可扩展性,内置的各类型节点仅是定义了其存在,并未实现节点在流程设计器中展示的内容及配置内容,需要使用者自行实现。 109 | 110 | 注意:**网关节点** 为内置逻辑组件不需要对其实现节点视图和节点配置组件。 111 | 112 | 113 | 114 | #### 节点命名规范 115 | 116 | **节点视图组件文件格式:** [nodeType]-node.vue 117 | 118 | **节点配置组件文件格式:** [nodeType]-drawer.vue 119 | 120 | 121 | 122 | #### 实现节点视图组件 123 | 124 | 以**开始节点**为例,开始节点的**nodeType=start**,则在/src/views/node/start目录下创建start-node.vue文件, 内容如下: 125 | 126 | ```vue 127 | // start-node.vue 128 | 134 | 135 | 161 | 162 | 165 | 166 | ``` 167 | 168 | #### 实现节点配置组件 169 | 170 | 以开始节点为例,开始节点的nodeType=start,则在/src/views/node/start目录下创建start-drawer.vue文件, 内容如下: 171 | 172 | ```vue 173 | 179 | 180 | 196 | 197 | 200 | 201 | ``` 202 | 203 | 204 | 205 | ### 使用组件 206 | 207 | ```vue 208 | 209 | ``` 210 | 211 | processData:流程数据,为空时会默认创建一个开始节点 212 | 213 | 214 | 215 | ### 获取流程数据结果 216 | 217 | ```js 218 | import {getCurrentInstance} from "vue"; 219 | const { proxy } = getCurrentInstance(); 220 | 221 | let result = proxy.$refs.process.getResult(); 222 | ``` 223 | 224 | 225 | 226 | ## 截图 227 | 228 | ![1.png](https://file.uncode.vip/easy-process/image/demo/1.png) 229 | 230 | ![1.png](https://file.uncode.vip/easy-process/image/demo/2.png) 231 | 232 | ![1.png](https://file.uncode.vip/easy-process/image/demo/3.png) 233 | 234 | ![1.png](https://file.uncode.vip/easy-process/image/demo/4.png) 235 | 236 | ![1.png](https://file.uncode.vip/easy-process/image/demo/5.png) 237 | 238 | 239 | 240 | -------------------------------------------------------------------------------- /src/easy-process/ep-designer.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 176 | 219 | -------------------------------------------------------------------------------- /src/easy-process/assets/icons/ep-setting.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/easy-process/node/gateway/gateway-node.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 88 | 89 | 251 | -------------------------------------------------------------------------------- /src/easy-process/node/base/add-node.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 116 | 117 | 241 | -------------------------------------------------------------------------------- /src/easy-process/node/base/base-node.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 231 | 232 | 425 | --------------------------------------------------------------------------------