diff --git a/package.json b/package.json index d310022e..69912c22 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "file-saver": "^2.0.5", "html2canvas": "^1.4.1", "jquery": "^3.7.1", + "less": "^4.2.0", "lodash-es": "^4.17.21", "luckyexcel": "^1.0.1", "luckysheet": "^2.1.13", diff --git a/src/api/process-boot/workflow/form.ts b/src/api/process-boot/workflow/form.ts index 0d048887..d2342690 100644 --- a/src/api/process-boot/workflow/form.ts +++ b/src/api/process-boot/workflow/form.ts @@ -15,6 +15,16 @@ export const listWFForm = (data: any) => { }) } +/** + * 查询所有流程表单 + */ +export const listAllWFForm = () => { + return createAxios({ + url: MAPPING_PATH + '/listAll', + method: 'GET' + }) +} + /** * 根据id查询表单详细信息 */ diff --git a/src/api/user-boot/dept.ts b/src/api/user-boot/dept.ts index a5e15445..50639cf1 100644 --- a/src/api/user-boot/dept.ts +++ b/src/api/user-boot/dept.ts @@ -1,4 +1,5 @@ import request from '@/utils/request' + export function getAreaTree(data: any) { return request({ url: '/user-boot/dept/getAreaTree', @@ -22,6 +23,7 @@ export function addDept(data: any) { data: data }) } + export function updateDept(data: any) { return request({ url: '/user-boot/dept/update', @@ -46,3 +48,14 @@ export function selectPid(data: any) { }) } +/** + * 部门信息树 + */ +export function deptTreeSelector() { + return request({ + url: '/user-boot/dept/deptTreeSelector', + method: 'GET' + }) +} + + diff --git a/src/api/user-boot/user.ts b/src/api/user-boot/user.ts index 57c00d9a..e8b2b644 100644 --- a/src/api/user-boot/user.ts +++ b/src/api/user-boot/user.ts @@ -153,3 +153,12 @@ export function getUserByRoleType(data:any) { method: 'GET' }) } + +// 获取部门下所有用户 +export function listAllUserByDeptId(data:any) { + return request({ + url: '/user-boot/user/listAllUserByDeptId?deptId=' + data, + method: 'GET' + }) +} + diff --git a/src/api/workflow-boot/model.ts b/src/api/workflow-boot/model.ts new file mode 100644 index 00000000..94ab0acc --- /dev/null +++ b/src/api/workflow-boot/model.ts @@ -0,0 +1,47 @@ +import createAxios from '@/utils/request' +import { WORKFLOW_BOOT } from '@/utils/constantRequest' + +const MAPPING_PATH = WORKFLOW_BOOT + '/flw/model' + +/** + * 新增流程 + */ +export const addModel = (data:any) => { + return createAxios({ + url: MAPPING_PATH + '/add', + method: 'POST', + data: data + }) +} + + +/** + * 获取执行监听器选择器 + */ +export const executionListenerSelector = () => { + return createAxios({ + url: MAPPING_PATH + '/executionListenerSelector', + method: 'GET' + }) +} + +/** + * 获取自定义事件执行监听器选择器 + */ +export const executionListenerSelectorForCustomEvent = () => { + return createAxios({ + url: MAPPING_PATH + '/executionListenerSelectorForCustomEvent', + method: 'GET' + }) +} + +/** + * 获取任务监听器选择器 + */ +export const taskListenerSelector = () => { + return createAxios({ + url: MAPPING_PATH + '/taskListenerSelector', + method: 'GET' + }) +} + diff --git a/src/api/workflow-boot/template.ts b/src/api/workflow-boot/template.ts new file mode 100644 index 00000000..fbc3fed0 --- /dev/null +++ b/src/api/workflow-boot/template.ts @@ -0,0 +1,41 @@ +import createAxios from '@/utils/request' +import { WORKFLOW_BOOT } from '@/utils/constantRequest' + +const MAPPING_PATH = WORKFLOW_BOOT + '/flw/templateSn' + +/** + * 新增流程 + */ +export const addModel = (data:any) => { + return createAxios({ + url: MAPPING_PATH + '/add', + method: 'POST', + data: data + }) +} + + + +/** + * 获取流水号模板列表选择器 + */ +export const flwTemplateSnSelector = (data:any) => { + return createAxios({ + url: MAPPING_PATH + '/flwTemplateSnSelector', + method: 'POST', + data: data + }) +} + +/** + * 获取打印模板列表选择器 + */ +export const flwTemplatePrintSelector = (data:any) => { + return createAxios({ + url: MAPPING_PATH + '/flwTemplatePrintSelector', + method: 'POST', + data: data + }) +} + + diff --git a/src/components/Selector/iconMobileSelector.vue b/src/components/Selector/iconMobileSelector.vue new file mode 100644 index 00000000..7c37d457 --- /dev/null +++ b/src/components/Selector/iconMobileSelector.vue @@ -0,0 +1,142 @@ + + + + + + + {{ + iconItem.name + }} + + + + + + + + + + + + + + + + + + + diff --git a/src/components/Selector/iconSelector.vue b/src/components/Selector/iconSelector.vue new file mode 100644 index 00000000..01dfb3c1 --- /dev/null +++ b/src/components/Selector/iconSelector.vue @@ -0,0 +1,143 @@ + + + + + + + {{ + iconItem.name + }} + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/Selector/orgSelectorPlus.vue b/src/components/Selector/orgSelectorPlus.vue new file mode 100644 index 00000000..6c398782 --- /dev/null +++ b/src/components/Selector/orgSelectorPlus.vue @@ -0,0 +1,397 @@ + + + + + + + + + + + + + + + + + + + + 查询 + reset()"> 重置 + + + + + + + + 待选择列表 {{ tableRecordNum }} 条 + + 添加当前数据 + + + + + {{ $TOOL.dictTypeData('ORG_CATEGORY', record.category) }} + + + 添加 + + + + + + + + + + + + + 已选择: {{ selectedData.length }} + + 全部移除 + + + + + 移除 + + + + + + + + + + + + diff --git a/src/components/Selector/posSelectorPlus.vue b/src/components/Selector/posSelectorPlus.vue new file mode 100644 index 00000000..1d9d9b2a --- /dev/null +++ b/src/components/Selector/posSelectorPlus.vue @@ -0,0 +1,398 @@ + + + + + + + + + + + + + + + + + + + + 查询 + reset()"> 重置 + + + + + + + + 待选择列表 {{ tableRecordNum }} 条 + + 添加当前数据 + + + + + 添加 + + + {{ $TOOL.dictTypeData('POSITION_CATEGORY', record.category) }} + + + + + + + + + + + + + 已选择: {{ selectedData.length }} + + 全部移除 + + + + + 移除 + + + + + + + + + + + + diff --git a/src/components/Selector/roleSelectorPlus.vue b/src/components/Selector/roleSelectorPlus.vue new file mode 100644 index 00000000..afc641c8 --- /dev/null +++ b/src/components/Selector/roleSelectorPlus.vue @@ -0,0 +1,448 @@ + + + + + + + + + + + + + + + + + + + + 查询 + reset()"> 重置 + + + + + + + + 待选择列表 {{ tableRecordNum }} 条 + + 添加当前数据 + + + + + 添加 + + + {{ $TOOL.dictTypeData('ROLE_CATEGORY', record.category) }} + + + + + + + + + + + + + 已选择: {{ selectedData.length }} + + 全部移除 + + + + + 移除 + + + + + + + + + + + + diff --git a/src/components/Selector/userSelectorPlus.vue b/src/components/Selector/userSelectorPlus.vue new file mode 100644 index 00000000..f1e5abd6 --- /dev/null +++ b/src/components/Selector/userSelectorPlus.vue @@ -0,0 +1,399 @@ + + + + + + + + + + + + + + + + + + + + 查询 + 重置 + + + + + + + + 待选择列表 {{ tableRecordNum }} 条 + + 添加当前数据 + + + + + 添加 + + + {{ $TOOL.dictTypeData('ROLE_CATEGORY', record.category) }} + + + + + + + + + + + + + 已选择: {{ selectedData.length }} + + 全部移除 + + + + + 移除 + + + + + + + + + + + + diff --git a/src/components/XnFormContainer/index.vue b/src/components/XnFormContainer/index.vue new file mode 100644 index 00000000..4ccc3c4b --- /dev/null +++ b/src/components/XnFormContainer/index.vue @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + diff --git a/src/components/XnWorkflow/chart/cNodeWrap.vue b/src/components/XnWorkflow/chart/cNodeWrap.vue new file mode 100644 index 00000000..b822a3c2 --- /dev/null +++ b/src/components/XnWorkflow/chart/cNodeWrap.vue @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/components/XnWorkflow/chart/index.vue b/src/components/XnWorkflow/chart/index.vue new file mode 100644 index 00000000..3a00fd8c --- /dev/null +++ b/src/components/XnWorkflow/chart/index.vue @@ -0,0 +1,80 @@ + + + + + + 放大 + + + + + + 缩小 + + + + + + + + + + + + 流程结束 + + + + + + + + + diff --git a/src/components/XnWorkflow/chart/nodes/cAddNode.vue b/src/components/XnWorkflow/chart/nodes/cAddNode.vue new file mode 100644 index 00000000..41be677a --- /dev/null +++ b/src/components/XnWorkflow/chart/nodes/cAddNode.vue @@ -0,0 +1,5 @@ + + + + + diff --git a/src/components/XnWorkflow/chart/nodes/cExclusiveGateway.vue b/src/components/XnWorkflow/chart/nodes/cExclusiveGateway.vue new file mode 100644 index 00000000..888d8c1a --- /dev/null +++ b/src/components/XnWorkflow/chart/nodes/cExclusiveGateway.vue @@ -0,0 +1,141 @@ + + + + + + + + + + {{ item.title }} + 优先级{{ item.properties.configInfo.priorityLevel }} + + + {{ toText(childNode, index) }} + 请设置条件 + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/XnWorkflow/chart/nodes/cParallelGateway.vue b/src/components/XnWorkflow/chart/nodes/cParallelGateway.vue new file mode 100644 index 00000000..b697c5c5 --- /dev/null +++ b/src/components/XnWorkflow/chart/nodes/cParallelGateway.vue @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/XnWorkflow/chart/nodes/cServiceTask.vue b/src/components/XnWorkflow/chart/nodes/cServiceTask.vue new file mode 100644 index 00000000..d3c8fb4f --- /dev/null +++ b/src/components/XnWorkflow/chart/nodes/cServiceTask.vue @@ -0,0 +1,80 @@ + + + + + + {{ comment.userName }}于{{ comment.approveTime }} + {{ comment.operateText }},意见:{{ comment.comment }} + + + + + + + {{ childNode.title }} + + + {{ toText(childNode) }} + 未选择人员 + + + + + + + + + + diff --git a/src/components/XnWorkflow/chart/nodes/cStartEvent.vue b/src/components/XnWorkflow/chart/nodes/cStartEvent.vue new file mode 100644 index 00000000..755ee802 --- /dev/null +++ b/src/components/XnWorkflow/chart/nodes/cStartEvent.vue @@ -0,0 +1,24 @@ + + + + + diff --git a/src/components/XnWorkflow/chart/nodes/cStartTask.vue b/src/components/XnWorkflow/chart/nodes/cStartTask.vue new file mode 100644 index 00000000..143c81b9 --- /dev/null +++ b/src/components/XnWorkflow/chart/nodes/cStartTask.vue @@ -0,0 +1,76 @@ + + + + + + {{ comment.userName }}于{{ comment.approveTime }} + {{ comment.operateText }},意见:{{ comment.comment }} + + + + + + + {{ childNode.title }} + + + {{ toText(childNode) }} + + + + + + + + + + diff --git a/src/components/XnWorkflow/chart/nodes/cUserTask.vue b/src/components/XnWorkflow/chart/nodes/cUserTask.vue new file mode 100644 index 00000000..b8cb66e7 --- /dev/null +++ b/src/components/XnWorkflow/chart/nodes/cUserTask.vue @@ -0,0 +1,147 @@ + + + + + + {{ comment.userName }}于{{ comment.approveTime }} + {{ comment.operateText }},意见:{{ comment.comment }} + + + + + + + {{ childNode.title }} + + + {{ toText(childNode) }} + 未选择审批人 + + + + + + + + + + diff --git a/src/components/XnWorkflow/chart/zoom_helper.js b/src/components/XnWorkflow/chart/zoom_helper.js new file mode 100644 index 00000000..cdd265d9 --- /dev/null +++ b/src/components/XnWorkflow/chart/zoom_helper.js @@ -0,0 +1,39 @@ +const defaultZoomMargin = 360 +const perZoom = 0.25 // 每次放大缩小0.25倍 + +const ZoomHelper = { + // 处理全屏放大的样式 + getZoomStyles(zoom, MinZoom = 1, ZoomMargin = defaultZoomMargin, rightSpace = 0) { + const width = document.querySelector('.workflow-design').clientWidth + let style = {} + // 兼容Firefox浏览器的放大缩小 + if (zoom !== MinZoom) { + style = { + transform: `scale(${zoom})`, + 'transform-origin': '0 0', + width: (width - ZoomMargin - rightSpace) / zoom + 'px' + } + } + return style + }, + // 获取最大的放大倍数 + getMaxZoom() { + const width = window.innerWidth + const mediumWidth = 1600 + const smallScreenScale = 2.5 // 小屏幕下附件放大3倍会有样式问题, 所以取2.5 + const bigScreenScale = 3 // 大于1600的最大倍数为3 + const maxZoom = width > mediumWidth ? bigScreenScale : smallScreenScale + return maxZoom + }, + // 获取点击放大缩小之后生成的最终的放大倍数和样式 + getZoomData(zoomIn, zoom) { + const zoomResult = zoomIn ? zoom + perZoom : zoom - perZoom // 放大倍数加一次或者减少一次 + const zoomData = { + style: this.getZoomStyles(zoomResult), + zoom: zoomResult + } + return zoomData + } +} + +export default ZoomHelper diff --git a/src/components/XnWorkflow/color.text b/src/components/XnWorkflow/color.text new file mode 100644 index 00000000..3e8ee2b9 --- /dev/null +++ b/src/components/XnWorkflow/color.text @@ -0,0 +1,9 @@ +审批中 蓝色 #1890FF +已挂起 黄色 #FCC02E +已完成 绿色 #52C41A +已终止 黄色 #FF5A5A +已撤回 灰色 #BFBFBF +已拒绝 红色 #FF4D4F + +同意 绿色 #52C41A + diff --git a/src/components/XnWorkflow/customForm/404.vue b/src/components/XnWorkflow/customForm/404.vue new file mode 100644 index 00000000..9c47d30c --- /dev/null +++ b/src/components/XnWorkflow/customForm/404.vue @@ -0,0 +1,3 @@ + + + diff --git a/src/components/XnWorkflow/customForm/index.js b/src/components/XnWorkflow/customForm/index.js new file mode 100644 index 00000000..d286e97e --- /dev/null +++ b/src/components/XnWorkflow/customForm/index.js @@ -0,0 +1,28 @@ +/** + * 自定义表单导入 + * + * @author yubaoshan + * @date 2023-05-11 00:12:22 + */ +const modules = import.meta.glob('/src/views/flw/customform/**/**.vue') +const notFound = () => import(/* @vite-ignore */ `/src/components/XnWorkflow/customForm/404.vue`) + +// 直接渲染组件 +export const loadComponent = (component) => { + if (component) { + const link = modules[`/src/views/flw/customform/${component}.vue`] + return markRaw(defineAsyncComponent(link ? link : notFound)) + } else { + return markRaw(defineAsyncComponent(notFound)) + } +} + +// 给出判断,如果使用的地方取到是404,那么他的下一步就不走了 +export const verdictComponent = (component) => { + if (component) { + const link = modules[`/src/views/flw/customform/${component}.vue`] + return !!link + } else { + return false + } +} diff --git a/src/components/XnWorkflow/flowIndex.less b/src/components/XnWorkflow/flowIndex.less new file mode 100644 index 00000000..1ebfd205 --- /dev/null +++ b/src/components/XnWorkflow/flowIndex.less @@ -0,0 +1,425 @@ +.workflow-design { + overflow: auto; + width: 100%; + } + .workflow-design .box-scale { + display: inline-block; + position: relative; + width: 100%; + padding-top: 10px; + padding-bottom: 55px; + align-items: flex-start; + justify-content: center; + flex-wrap: wrap; + min-width: min-content; + } + .nodeLegal { + border: 2px solid red; + border-radius: 1px; + } + .workflow-design { + .node-wrap { + display: inline-flex; + width: 100%; + flex-flow: column wrap; + justify-content: flex-start; + align-items: center; + padding: 0px 50px; + position: relative; + z-index: 1; + } + .node-wrap-box { + display: inline-flex; + flex-direction: column; + position: relative; + width: 220px; + min-height: 72px; + flex-shrink: 0; + background: var(--node-wrap-box-color); + border-radius: 1px; + cursor: pointer; + box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.2); + } + .node-wrap-box::before { + background: var(--auto-judge-before-color); + content: ''; + position: absolute; + top: -12px; + left: 50%; + transform: translateX(-50%); + width: 0px; + border-style: solid; + border-width: 8px 6px 4px; + border-color: #cacaca transparent transparent; + } + .node-wrap-box.start-node:before { + content: none; + } + .node-wrap-box .title { + height: 24px; + line-height: 24px; + color: #fff; + padding-left: 16px; + padding-right: 30px; + border-radius: 2px 2px 0 0; + position: relative; + display: flex; + align-items: center; + } + .node-wrap-box .title .icon { + margin-right: 5px; + } + .node-wrap-box .title .close { + font-size: 15px; + position: absolute; + top: 50%; + transform: translateY(-50%); + right: 10px; + display: none; + } + .node-wrap-box .title .success { + font-size: 20px; + position: absolute; + top: 50%; + transform: translateY(-50%); + right: -25px; + display: block; + color: #00bb00; + } + .node-wrap-box .content { + position: relative; + padding: 15px; + } + .node-wrap-box .content .placeholder { + color: red; + } + .node-wrap-box:hover .close { + display: block; + } + .add-node-btn-box { + width: 240px; + display: inline-flex; + flex-shrink: 0; + position: relative; + z-index: 1; + } + .add-node-btn-box:before { + content: ''; + position: absolute; + top: 0px; + left: 0px; + right: 0px; + bottom: 0px; + z-index: -1; + margin: auto; + width: 2px; + height: 100%; + background-color: rgb(202, 202, 202); + } + .add-node-btn { + user-select: none; + width: 240px; + padding: 20px 0px 32px; + display: flex; + justify-content: center; + flex-shrink: 0; + flex-grow: 1; + } + .add-node-btn span { + } + .add-branch { + justify-content: center; + padding: 0px 10px; + position: absolute; + top: -16px; + left: 50%; + transform: translateX(-50%); + transform-origin: center center; + z-index: 1; + display: inline-flex; + align-items: center; + } + .branch-wrap { + display: inline-flex; + width: 100%; + } + .branch-box-wrap { + display: flex; + flex-flow: column wrap; + align-items: center; + min-height: 270px; + width: 100%; + flex-shrink: 0; + } + .col-box { + display: inline-flex; + flex-direction: column; + align-items: center; + position: relative; + background: var(--component-background); + } + // 分支 上面横线 + .branch-box { + display: flex; + overflow: visible; + min-height: 180px; + height: auto; + border-bottom: 2px solid #ccc; + border-top: 2px solid #ccc; + position: relative; + margin-top: 15px; + } + .branch-box .col-box::before { + content: ''; + position: absolute; + top: 0px; + left: 0px; + right: 0px; + bottom: 0px; + z-index: 0; + margin: auto; + width: 2px; + height: 100%; + background-color: rgb(202, 202, 202); + } + .condition-node { + display: inline-flex; + flex-direction: column; + min-height: 220px; + } + .condition-node-box { + padding-top: 30px; + padding-right: 50px; + padding-left: 50px; + justify-content: center; + align-items: center; + flex-grow: 1; + position: relative; + display: inline-flex; + flex-direction: column; + } + .auto-judge { + position: relative; + width: 220px; + min-height: 72px; + background: var(--node-wrap-box-color); + border-radius: 2px; + padding: 15px 15px; + cursor: pointer; + box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.2); + } + // 箭头框框 + .auto-judge::before { + content: ''; + position: absolute; + top: -12px; + left: 50%; + transform: translateX(-50%); + width: 0px; + border-style: solid; + border-width: 8px 6px 4px; + border-color: #cacaca transparent transparent; + background: var(--auto-judge-before-color); + } + .auto-judge .title { + line-height: 16px; + } + .auto-judge .title .node-title { + color: #15bc83; + } + .auto-judge .title .close { + font-size: 15px; + position: absolute; + top: 15px; + right: 15px; + color: #999; + display: none; + } + .auto-judge .title .success { + font-size: 15px; + position: absolute; + top: 15px; + right: 15px; + color: #999; + display: block; + } + .auto-judge .title .priority-title { + position: absolute; + top: 15px; + right: 15px; + color: #999; + } + .auto-judge .content { + position: relative; + padding-top: 15px; + } + .auto-judge .content .placeholder { + color: red; + } + .auto-judge:hover { + .close { + display: block; + } + .priority-title { + display: none; + } + } + .top-left-cover-line, + .top-right-cover-line { + position: absolute; + height: 3px; + width: 50%; + background-color: var(--component-background); + top: -2px; + } + .bottom-left-cover-line, + .bottom-right-cover-line { + position: absolute; + height: 3px; + width: 50%; + background-color: var(--component-background); + bottom: -2px; + } + .top-left-cover-line { + left: -1px; + } + .top-right-cover-line { + right: -1px; + } + .bottom-left-cover-line { + left: -1px; + } + .bottom-right-cover-line { + right: -1px; + } + .end-node { + border-radius: 50%; + font-size: 14px; + color: rgba(25, 31, 37, 0.4); + text-align: left; + } + // 结束的小点点 + .end-node-circle { + width: 10px; + height: 10px; + margin: auto; + border-radius: 50%; + background: #dbdcdc; + } + .end-node-text { + margin-top: 5px; + text-align: center; + } + .auto-judge:hover { + .sort-left { + display: flex; + } + .sort-right { + display: flex; + } + } + .auto-judge .sort-left { + position: absolute; + top: 0; + bottom: 0; + z-index: 1; + left: 0; + display: none; + justify-content: center; + align-items: center; + flex-direction: column; + } + .auto-judge .sort-right { + position: absolute; + top: 0; + bottom: 0; + z-index: 1; + right: 0; + display: none; + justify-content: center; + align-items: center; + flex-direction: column; + } + .auto-judge .sort-left:hover, + .auto-judge .sort-right:hover { + background: var(--auto-judge-before-color); + } + .auto-judge:after { + pointer-events: none; + content: ''; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + z-index: 2; + border-radius: 2px; + transition: all 0.1s; + } + .auto-judge:hover:after { + border: 1px solid #3296fa; + box-shadow: 0 0 6px 0 rgba(50, 150, 250, 0.3); + } + .node-wrap-box:after { + pointer-events: none; + content: ''; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + z-index: 2; + border-radius: 2px; + transition: all 0.1s; + } + .node-wrap-box:hover:after { + border: 1px solid #3296fa; + box-shadow: 0 0 6px 0 rgba(50, 150, 250, 0.3); + } + } + .tags-list { + margin-top: 15px; + width: 100%; + } + .add-node-popover-body { + height: 81px; + } + .add-node-popover-body li { + display: inline-block; + width: 80px; + text-align: center; + padding: 10px 0; + } + .add-node-popover-body li i { + border: 1px solid var(--el-border-color-light); + width: 40px; + height: 40px; + border-radius: 50%; + text-align: center; + line-height: 38px; + font-size: 18px; + cursor: pointer; + } + .add-node-popover-body li i:hover { + border: 1px solid #3296fa; + background: #3296fa; + color: #fff !important; + } + .add-node-popover-body li p { + font-size: 12px; + margin-top: 5px; + } + .node-wrap-drawer__title { + padding-right: 40px; + } + .node-wrap-drawer__title label { + cursor: pointer; + } + .node-wrap-drawer__title label:hover { + border-bottom: 1px dashed #409eff; + } + .node-wrap-drawer__title .node-wrap-drawer-title-edit { + color: #409eff; + margin-left: 10px; + vertical-align: middle; + } diff --git a/src/components/XnWorkflow/index.vue b/src/components/XnWorkflow/index.vue new file mode 100644 index 00000000..740ed0a9 --- /dev/null +++ b/src/components/XnWorkflow/index.vue @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + + + 全局配置 + + + + + + + + + + + + 流程结束 + + + + + + + + + diff --git a/src/components/XnWorkflow/nodeWrap.vue b/src/components/XnWorkflow/nodeWrap.vue new file mode 100644 index 00000000..de87ab30 --- /dev/null +++ b/src/components/XnWorkflow/nodeWrap.vue @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/components/XnWorkflow/nodes/addNode.vue b/src/components/XnWorkflow/nodes/addNode.vue new file mode 100644 index 00000000..16b14444 --- /dev/null +++ b/src/components/XnWorkflow/nodes/addNode.vue @@ -0,0 +1,152 @@ + + + + + + + + + + + + + + 审批节点 + + + + + + + + 抄送节点 + + + + + + + + 条件分支 + + + + + + + + 并行分支 + + + + + + + + + + + + + + diff --git a/src/components/XnWorkflow/nodes/addNodeNoButton.vue b/src/components/XnWorkflow/nodes/addNodeNoButton.vue new file mode 100644 index 00000000..41be677a --- /dev/null +++ b/src/components/XnWorkflow/nodes/addNodeNoButton.vue @@ -0,0 +1,5 @@ + + + + + diff --git a/src/components/XnWorkflow/nodes/common/previewCustomForm.vue b/src/components/XnWorkflow/nodes/common/previewCustomForm.vue new file mode 100644 index 00000000..c67bc320 --- /dev/null +++ b/src/components/XnWorkflow/nodes/common/previewCustomForm.vue @@ -0,0 +1,41 @@ + + + + + + + diff --git a/src/components/XnWorkflow/nodes/config/config.js b/src/components/XnWorkflow/nodes/config/config.js new file mode 100644 index 00000000..41574751 --- /dev/null +++ b/src/components/XnWorkflow/nodes/config/config.js @@ -0,0 +1,581 @@ +export default { + // 模型结构,就是每个节点,都有的 + nodeModel: { + // 节点 + node: { + id: '', // 节点id + title: '', // 节点名称 + type: '', // 节点类型 + dataLegal: false, // 信息是否完整 + // content:'', // 内容、备注 + properties: { + configInfo: {}, // 除条件路由和分合流节点外均有 + conditionInfo: [], // 条件信息, 条件节点特有 + participateInfo: [], // 参与人信息,开始节点、审批节点、抄送节点才有 + buttonInfo: [], // 按钮信息, 开始节点、审批节点、抄送节点才有 + fieldInfo: [], // 字段信息, 开始节点、审批节点、抄送节点才有 + commentList: [], // 流转记录内容 + formInfo: [], // 自定义表单 发起、审批节点都有 + executionListenerInfo: [], // 执行监听器信息,所有节点都有 + taskListenerInfo: [] // 任务监听器信息(审批节点特有) + // noticeInfo: [], // 通知信息, 开始节点、审批节点、抄送节点才有 + }, + childNode: {}, // 子节点 + conditionNodeList: [] // 条件子节点 + }, + // 通用按钮默认配置,不需要改动,除非自定义开发的时候,再加什么按钮 + buttonInfo: [ + { + key: 'SAVE', + label: '保存', + value: 'HIDE', + type: 'default', + icon: 'save-outlined' + }, + { + key: 'SUBMIT', + label: '提交', + value: 'HIDE', + type: 'primary', + icon: 'check-circle-outlined' + }, + { + key: 'PASS', + label: '同意', + value: 'HIDE', + type: 'primary', + icon: 'check-outlined' + }, + { + key: 'REJECT', + label: '拒绝', + value: 'HIDE', + type: 'danger', + icon: 'close-outlined' + }, + { + key: 'BACK', + label: '退回', + value: 'HIDE', + type: 'default', + icon: 'rollback-outlined' + }, + { + key: 'JUMP', + label: '跳转', + value: 'HIDE', + type: 'default', + icon: 'ungroup-outlined' + }, + { + key: 'ADD_SIGN', + label: '加签', + value: 'HIDE', + type: 'default', + icon: 'tags-outlined' + }, + { + key: 'PRINT', + label: '打印', + value: 'HIDE', + type: 'default', + icon: 'printer-outlined' + }, + { + key: 'TURN', + label: '转办', + value: 'HIDE', + type: 'default', + icon: 'user-switch-outlined' + } + ], + fieldInfo: [], + formInfo: [ + { + key: 'FORM_URL', + label: 'PC端表单组件', + value: '' + }, + { + key: 'MOBILE_FORM_URL', + label: '移动端表单组件', + value: '' + } + ], + executionListenerInfo: [], + taskListenerInfo: [], + noticeInfo: [] + }, + + // 各个节点的configInfo,他们长的都不一样 + nodeConfigInfo: { + // 全局配置 + processConfigInfo: { + // 基础配置 + processSnTemplateId: undefined, // 流水号模板 id + processPrintTemplateId: undefined, // 打印模板 id + processTitleTemplate: 'initiator 的 processName - startTime', // 标题模板 + processAbstractTemplate: '', // 摘要模板 + processEnableAutoDistinct: false, // 开启自动去重 + processAutoDistinctType: 'SAMPLE', // 自动去重类型 + processEnableRevoke: true, // 开启审批撤销 + processEnableCommentRequired: false, // 开启意见必填 + // 通知配置 + processEnableBackNotice: false, // 开启退回通知 + processEnableTodoNotice: false, // 开启待办通知 + processEnableCopyNotice: false, // 开启抄送通知 + processEnableCompleteNotice: false, // 开启完成通知 + // 通知的方式 + processBackNoticeChannel: ['MSG'], // 退回通知渠道 + processTodoNoticeChannel: ['MSG'], // 待办通知渠道 + processCopyNoticeChannel: ['MSG'], // 抄送通知渠道 + processCompleteNoticeChannel: ['MSG'], // 完成通知渠道 + // 通知配置对应的模板 + processBackNoticeTemplate: '您于 startTime 发起的 processName 被退回', // 退回通知模板 + processTodoNoticeTemplate: '由 initiator 发起的 processName 需要您审批', // 待办通知模板 + processCopyNoticeTemplate: '您收到一条由 initiator 发起的 processName 的抄送', // 抄送通知模板 + processCompleteNoticeTemplate: '您于 startTime 发起的 processName 审批通过', // 完成通知模板 + // 全局自定义表单 + processStartTaskFormUrl: '', // 全局PC申请单 + processStartTaskMobileFormUrl: '', // 全局移动端申请单 + processUserTaskFormUrl: '', // 全局PC审批单 + processUserTaskMobileFormUrl: '' // 全局移动端审批单 + }, + // 审核节点配置 + userTaskConfigInfo: { + userTaskType: 'ARTIFICIAL', // 任务节点类型 + userTaskRejectType: 'TO_START', // 审批退回类型 + userTaskMulApproveType: 'SEQUENTIAL', // 多人审批时类型 + userTaskEmptyApproveType: 'AUTO_COMPLETE', // 审批人为空时类型 + userTaskEmptyApproveUser: '', // 审批人为空时转交人id + userTaskEmptyApproveUserArray: [] // 审批人为空时转交人包含name,用来前端回显,后端不解析 + }, + // 条件中默认的配置 + conditionConfigInfo: { + priorityLevel: 1 // 优先级 默认1 + }, + // 抄送节点配置 + serviceTaseConfigInfo: {} + }, + + // 按钮相关配置 + button: { + // 发起人节点 默认选中按钮 + startTaskDefaultButtonkey: ['SAVE', 'SUBMIT'], + // 发起人节点 默认不让选的按钮 // 这个节点屏蔽下面配置的 + startTaskNoCheckedButtonkey: ['PASS', 'PRINT', 'REJECT', 'BACK', 'ADD_SIGN', 'TURN', 'JUMP'], + // 审批人节点 默认选中按钮 + userTaskDefaultButtonkey: ['PASS', 'REJECT'], + // 审批节点 默认不让选的 + userTaskNoCheckedButtonkey: ['SUBMIT', 'SAVE'], + // 抄送人节点 默认选中按钮 + serviceTaskDefaultButtonkey: ['SUBMIT'] + }, + // 字段相关配置 + field: { + // 其他节点中字段对象数据模型 + fieldModel: { + key: '', + label: '', + value: 'WRITE', // 默认 + required: false, // 必填 + extJson: '' // 额外扩展,暂无 + }, + // 审批节点中字段对象数据模型 + userTaskFieldModel: { + key: '', + label: '', + value: 'READ', // 默认设为只读 + required: false, // 必填 + extJson: '' // 额外扩展,暂无 + }, + // 字段列表中的字典 + fieldRadioList: [ + { + label: '可编辑', + value: 'WRITE' + }, + { + label: '只读', + value: 'READ' + }, + { + label: '隐藏', + value: 'HIDE' + } + ] + }, + // 监听器配置,不同的地方不同的选项 + listener: { + // 全局执行监听可以选择的 + processExecutionListenerInfo: [ + { + key: 'START', + label: '开始', + value: '', + extJson: '' + }, + { + key: 'END', + label: '完成', + value: '', + extJson: '' + }, + { + key: 'REJECT', + label: '拒绝', + value: '', + extJson: '' + }, + { + key: 'CLOSE', + label: '终止', + value: '', + extJson: '' + }, + { + key: 'REVOKE', + label: '撤回', + value: '', + extJson: '' + }, + { + key: 'DELETE', + label: '删除', + value: '', + extJson: '' + } + ], + // 条件执行监听可以选择的 + exclusiveGatewayExecutionListenerInfo: [ + { + key: 'TAKE', + label: '到达', + value: '', + extJson: '' + } + ], + // 开始节点执行监听可选择的 + startTaskExecutionListenerInfo: [ + { + key: 'START', + label: '开始', + value: '', + extJson: '' + }, + { + key: 'END', + label: '结束', + value: '', + extJson: '' + } + ], + // 审批节点执行监听可以选择的 + userTaskExecutionListenerInfo: [ + { + key: 'START', + label: '开始', + value: '', + extJson: '' + }, + { + key: 'END', + label: '结束', + value: '', + extJson: '' + } + ], + // 抄送节点执行监听可以选择的 + serviceTaskExecutionListenerInfo: [ + { + key: 'START', + label: '开始', + value: '', + extJson: '' + }, + { + key: 'END', + label: '结束', + value: '', + extJson: '' + } + ], + // 审批节点任务监听 + userTaskTaskListenerInfo: [ + { + key: 'CREATE', + label: '创建', + value: '', + extJson: '' + }, + { + key: 'ASSIGNMENT', + label: '分配', + value: '', + extJson: '' + }, + { + key: 'COMPLETE', + label: '完成', + value: '', + extJson: '' + }, + { + key: 'DELETE', + label: '删除', + value: '', + extJson: '' + }, + { + key: 'UPDATE', + label: '更新', + value: '', + extJson: '' + }, + { + key: 'TIMEOUT', + label: '超时', + value: '', + extJson: '' + } + ] + }, + // 通知方式字典 + noticeInfoList: [ + { + label: '短信', + value: 'SMS' + }, + { + label: '邮件', + value: 'EMAIL' + }, + { + label: '站内信', + value: 'MSG' + } + ], + // 模板默认自带字段 + templateDefaultFields: [ + { + label: '发起人', + value: 'initiator' + }, + { + label: '流程名称', + value: 'processName' + }, + { + label: '发起时间', + value: 'startTime' + }, + { + label: '表单字段', + value: 'disabled' // 这里表示在组件显示的时候就截至了,是下一梭子数组的标题哦 + } + ], + // 审批节点 + userTaskConfig: { + // 审批人员类型 + userSelectionTypeList: [ + { + label: '机构', + value: 'ORG' + }, + { + label: '角色', + value: 'ROLE' + }, + { + label: '职位', + value: 'POSITION' + }, + { + label: '部门主管', + value: 'ORG_LEADER' + }, + { + label: '上级主管', + value: 'SUPERVISOR' + }, + { + label: '表单内的人', + value: 'FORM_USER' + }, + { + label: '表单内的人上级主管', + value: 'FORM_USER_SUPERVISOR' + }, + { + label: '连续多级主管', + value: 'MUL_LEVEL_SUPERVISOR' + }, + { + label: '表单内的人连续多级主管', + value: 'FORM_USER_MUL_LEVEL_SUPERVISOR' + }, + { + label: '用户', + value: 'USER' + }, + { + label: '发起人', + value: 'INITIATOR' + } + ], + // 任务节点类型 + userTaskTypeList: [ + { + label: '人工审批', + value: 'ARTIFICIAL' + }, + { + label: '自动通过', + value: 'COMPLETE' + }, + { + label: '自动拒绝', + value: 'REJECT' + } + ], + // 审批退回类型 + userTaskRejectTypeList: [ + { + label: '开始节点', + value: 'TO_START' + }, + { + label: '自选节点', + value: 'USER_SELECT' + }, + { + label: '自动结束', + value: 'AUTO_END' + } + ], + // 多人审批时类型 + userTaskMulApproveTypeList: [ + { + label: '依次审批', + value: 'SEQUENTIAL' + }, + { + label: '会签(须所有审批人同意)', + value: 'COUNTERSIGN' + }, + { + label: '或签(一名审批人同意或拒绝即可)', + value: 'ORSIGN' + } + ], + // 审批人为空时类型 + userTaskEmptyApproveTypeList: [ + { + label: '自动通过', + value: 'AUTO_COMPLETE' + }, + { + label: '自动转交给某个人', + value: 'AUTO_TURN' + } + ], + // 主管层级 + levelSupervisorList: [ + { + label: '最高层主管', + value: '-1' + }, + { + label: '直接主管', + value: '1' + }, + { + label: '第2级主管', + value: '2' + }, + { + label: '第3级主管', + value: '3' + }, + { + label: '第4级主管', + value: '4' + }, + { + label: '第5级主管', + value: '5' + }, + { + label: '第6级主管', + value: '6' + }, + { + label: '第7级主管', + value: '7' + }, + { + label: '第8级主管', + value: '8' + }, + { + label: '第9级主管', + value: '9' + }, + { + label: '第10级主管', + value: '10' + } + ] + }, + // 条件节点 + exclusiveGatewayConfig: { + operatorList: [ + { + label: '等于', + value: '==' + }, + { + label: '不等于', + value: '!=' + }, + { + label: '大于', + value: '>' + }, + { + label: '大于等于', + value: '>=' + }, + { + label: '小于', + value: '<' + }, + { + label: '小于等于', + value: '<=' + }, + { + label: '包含', + value: 'include' + }, + { + label: '不包含', + value: 'notInclude' + } + ] + }, + // 抄送节点 + serviceTaskConfig: { + // 抄送人员类型 + userSelectionTypeList: [ + { + label: '用户', + value: 'USER' + }, + { + label: '表单内的人', + value: 'FORM_USER' + } + ] + } +} diff --git a/src/components/XnWorkflow/nodes/exclusiveGateway.vue b/src/components/XnWorkflow/nodes/exclusiveGateway.vue new file mode 100644 index 00000000..aeda4d32 --- /dev/null +++ b/src/components/XnWorkflow/nodes/exclusiveGateway.vue @@ -0,0 +1,487 @@ + + + + + 添加条件 + + + + + + + + + {{ item.title }} + 优先级{{ item.properties.configInfo.priorityLevel }} + + + + {{ toText(childNode, index) }} + 请设置条件 + + + + + + + + + + + + + + + + + + + + + + + {{ form.title }} + + + + + + + + + + + + + + 配置要执行的条件 + + + + + 增加条件组 + + + + + + + + + + + {{ formField.label }} + + + + + + + + + + + + + 注:自定义表单模式下条件选择完全放开,中文字段不可以使用大于、大于等于、小于、小于等于! + + 运算符 + + + + + 等于 + 不等于 + 大于 + 大于等于 + 小于 + 小于等于 + 包含 + 不包含 + + + + + + + 中文字段需判断等于,值必须加入英文双引号 + + 值 + + + + + + + + + 移除 + + + + + + 增加条件 + + + + + + + + + + + + + + + + 保存 + 取消 + + + + + + + + diff --git a/src/components/XnWorkflow/nodes/parallelGateway.vue b/src/components/XnWorkflow/nodes/parallelGateway.vue new file mode 100644 index 00000000..fe620f31 --- /dev/null +++ b/src/components/XnWorkflow/nodes/parallelGateway.vue @@ -0,0 +1,110 @@ + + + + + 添加并行 + + + + + + + + + + + + + + + + + + + diff --git a/src/components/XnWorkflow/nodes/prop/formUserSelector.vue b/src/components/XnWorkflow/nodes/prop/formUserSelector.vue new file mode 100644 index 00000000..098a57eb --- /dev/null +++ b/src/components/XnWorkflow/nodes/prop/formUserSelector.vue @@ -0,0 +1,107 @@ + + + + + + + + + + + diff --git a/src/components/XnWorkflow/nodes/prop/propButtonInfo.vue b/src/components/XnWorkflow/nodes/prop/propButtonInfo.vue new file mode 100644 index 00000000..8ed6dfa6 --- /dev/null +++ b/src/components/XnWorkflow/nodes/prop/propButtonInfo.vue @@ -0,0 +1,157 @@ + + + 参与者可以看见或操作哪些按钮 + + + + + + + + + + + {{ record.label }} + + + ”保存“按钮作用为发起节点保存操作,审批节点下无保存操作。 + + + ”提交“按钮作用为发起节点填写完申请单,提交流程到下一步。 + + ”撤回“按钮作用为发起节点发错内容,将其撤回。 + 按钮作用为审批节点同意该审核之操作。 + 按钮作用为审批节点进行拒绝之操作。 + 按钮作用为审批节点退回操作,可退回至任意节点。 + 按钮作用为审批节点加签操作。 + 按钮作用为审批节点转给他人办理操作。 + 按钮作用为审批节点可跳转至任意节点操作。 + + 按钮配置后,操作人到该节点可以进行管理员配置的全局打印模板,进行打印此流程进展的内容。 + + + + + + + {{ record.label }} + + + + + + + + + + + diff --git a/src/components/XnWorkflow/nodes/prop/propButtonInfo_README.md b/src/components/XnWorkflow/nodes/prop/propButtonInfo_README.md new file mode 100644 index 00000000..7267345d --- /dev/null +++ b/src/components/XnWorkflow/nodes/prop/propButtonInfo_README.md @@ -0,0 +1,94 @@ +propButtonInfo +==== + +> 工作流中配置按钮选择器 +> eg: 发起人、审批人、通知人 这三个节点使用 + +该组件由 [俞宝山](https://www.xiaonuo.vip) 封装 + + +### 使用方式 + +```vue + + + + + + + +``` + +### 事件 + +| 名称 | 说明 | 类型 | 默认值 | +| --------------------- | ----------------------------------- | ------ | ------ | +| selectedButtonKeyList | 调用界面使用此方法获取组件中选中的数据 | Array | [] | + +### 数据 + +| 名称 | 说明 | 类型 | 默认值 | +| ---------- | ------------------------------------------------------------------------------------ | ------ | ------ | +| buttonInfo | 传送数据为此模型节点中选中的按钮用show=true标识,示例:`selectedButtonInfo` | Array | [] | +| showButton | 哪个节点调用,哪个节点设置他必选的,不能改变的按钮数据,示例:`startTaskDefaultButtonkey ` | Array | [] | +| noChecked | 默认不让哪个按钮在本节点进行配置,示例:`startTaskNoCheckedButtonkey ` | Array | [] | diff --git a/src/components/XnWorkflow/nodes/prop/propFieldInfo.vue b/src/components/XnWorkflow/nodes/prop/propFieldInfo.vue new file mode 100644 index 00000000..ce1c175a --- /dev/null +++ b/src/components/XnWorkflow/nodes/prop/propFieldInfo.vue @@ -0,0 +1,141 @@ + + + 参与者可以看见或操作哪些字段 + + + + + * + + {{ record.label }} + + + + {{ radio.label }} + + + + + + + + + diff --git a/src/components/XnWorkflow/nodes/prop/propFieldInfo_README.md b/src/components/XnWorkflow/nodes/prop/propFieldInfo_README.md new file mode 100644 index 00000000..b54a4a42 --- /dev/null +++ b/src/components/XnWorkflow/nodes/prop/propFieldInfo_README.md @@ -0,0 +1,127 @@ +propFieldInfo +==== + +> 工作流中配置字段选择器 +> eg: 发起人、审批人这两个节点使用 + +该组件由 [俞宝山](https://www.xiaonuo.vip) 封装 + + +### 使用方式 + +```vue + + + + + + + +``` + +### 事件 + +| 名称 | 说明 | 类型 | 默认值 | +| --------------------- | ----------------------------------- | ------ | ------ | +| selectedFieldList | 调用界面使用此方法获取组件中选中的数据 | Array | [] | + +### 数据 + +| 名称 | 说明 | 类型 | 默认值 | +| ---------- | ------------------------------------------------------------------------------------ | ------ | ------ | +| form-field-list-value | 表单设计器中拖拉拽过后的字段数据,示例:`formFieldListValue` | Array | [] | +| field-info | 这个节点已经选好勾好的数据,回显的,示例:`fieldInfo ` | Array | [] | +| default-field-model | 默认这个节点勾选的字段配置,示例:`defaultFieldModel ` | Array | [] | diff --git a/src/components/XnWorkflow/nodes/prop/propListenerInfo.vue b/src/components/XnWorkflow/nodes/prop/propListenerInfo.vue new file mode 100644 index 00000000..717e5e2b --- /dev/null +++ b/src/components/XnWorkflow/nodes/prop/propListenerInfo.vue @@ -0,0 +1,158 @@ + + + 配置要执行的监听 + + + + + + 新增 + + + + + {{ listenerlabel(record.listenerType) }} + + + + 删除 + + + + + + + + + + + + + + + + + diff --git a/src/components/XnWorkflow/nodes/prop/propNoticeInfo.vue b/src/components/XnWorkflow/nodes/prop/propNoticeInfo.vue new file mode 100644 index 00000000..0d3e5499 --- /dev/null +++ b/src/components/XnWorkflow/nodes/prop/propNoticeInfo.vue @@ -0,0 +1,7 @@ + + + diff --git a/src/components/XnWorkflow/nodes/prop/propTag.vue b/src/components/XnWorkflow/nodes/prop/propTag.vue new file mode 100644 index 00000000..d2b6b26c --- /dev/null +++ b/src/components/XnWorkflow/nodes/prop/propTag.vue @@ -0,0 +1,48 @@ + + {{ tag }} + + + diff --git a/src/components/XnWorkflow/nodes/prop/templateGenerator.vue b/src/components/XnWorkflow/nodes/prop/templateGenerator.vue new file mode 100644 index 00000000..093f7867 --- /dev/null +++ b/src/components/XnWorkflow/nodes/prop/templateGenerator.vue @@ -0,0 +1,220 @@ + + + + + + + {{ fields.label }} + + + + 置入字段 + + + + + + + + + diff --git a/src/components/XnWorkflow/nodes/prop/templateGenerator_README.md b/src/components/XnWorkflow/nodes/prop/templateGenerator_README.md new file mode 100644 index 00000000..a9f7030d --- /dev/null +++ b/src/components/XnWorkflow/nodes/prop/templateGenerator_README.md @@ -0,0 +1,53 @@ +templateGenerator +==== + +> 模板编辑器 +> eg: 主要用于工作流中的各种模板,也可作为通知的 + +该组件由 [俞宝山](https://www.xiaonuo.vip) 封装 + + +### 使用方式 + +```vue + + + + + + + +``` + +### 数据 + +| 名称 | 说明 | 类型 | 默认值 | +| ---------- | ------------------------------------------------------------------------------------ | ------ | ------ | +| fieldInfoLis | 表单数据,下拉框中要选择的,示例:`fieldInfoLis` | Array | [] | +| inputValue | 组件中要回显的数据,示例:`inputValue ` | String | '' | diff --git a/src/components/XnWorkflow/nodes/serviceTask.vue b/src/components/XnWorkflow/nodes/serviceTask.vue new file mode 100644 index 00000000..099f244e --- /dev/null +++ b/src/components/XnWorkflow/nodes/serviceTask.vue @@ -0,0 +1,304 @@ + + + + + + {{ childNode.title }} + + + + {{ toText(childNode) }} + 请选择人员 + + + + + + + {{ form.title }} + + + + + + + + + + + + + 选择要抄送的人员 + + + + + + {{ userSelectionType.label }} + + + + + + + + + + 选择 + + + + + + + + + + + 选择 + + + + + + + + + + 单个字段可以采用:字段名,多个采用:字段名1,字段名2,字段名3 + + 人员变量: + + + + + + + + + + + + + 保存 + 取消 + + + + + + + + diff --git a/src/components/XnWorkflow/nodes/startEvent.vue b/src/components/XnWorkflow/nodes/startEvent.vue new file mode 100644 index 00000000..aa3ea78c --- /dev/null +++ b/src/components/XnWorkflow/nodes/startEvent.vue @@ -0,0 +1,48 @@ + + + + + diff --git a/src/components/XnWorkflow/nodes/startTask.vue b/src/components/XnWorkflow/nodes/startTask.vue new file mode 100644 index 00000000..b9fff9e5 --- /dev/null +++ b/src/components/XnWorkflow/nodes/startTask.vue @@ -0,0 +1,241 @@ + + + + + + {{ childNode.title }} + + + {{ toText(childNode) }} + 请配置字段与按钮 + + + + + + + + {{ form.title }} + + + + + + + + + + + + + + + + 参与者可以填写的表单 + + + + + + + 预览 + + + + + + + + + + + + + + + + + + + 保存 + 取消 + + + + + + + + + diff --git a/src/components/XnWorkflow/nodes/userTask.vue b/src/components/XnWorkflow/nodes/userTask.vue new file mode 100644 index 00000000..3e81ada6 --- /dev/null +++ b/src/components/XnWorkflow/nodes/userTask.vue @@ -0,0 +1,788 @@ + + + + + + {{ childNode.title }} + + + + {{ toText(childNode) }} + 请选择 + + + + + + + + + {{ form.title }} + + + + + + + + + + + + 选择审批人员类型 + + + + + 机构 + + + 角色 + + + 职位 + + + + + 部门主管 + + + 上级主管 + + + 连续多级主管 + + + + + 用户 + + + 表单内的人 + + + 表单内的人上级主管 + + + + + 表单内的人连续多级主管 + + + 发起人 + + + + + + + + 单个字段可以采用:字段名,多个采用:字段名1,字段名2,字段名3 + + 人员变量: + + + + + + + + + + 选择 + + + + + + + + + + + + 配置审批方式 + + + + + + + {{ userTaskType.label }} + + + + + {{ userTaskRejectType.label }} + + + + + + + {{ userTaskMulApproveType.label }} + + + + + + + {{ userTaskEmptyApproveType.label }} + + + + + 选择人员 + + {{ form.properties.configInfo.userTaskEmptyApproveUserArray[0].name }} + + + + + + + + + + + + 参与者可以使用的表单 + + + + + + + 预览 + + + + + + + + + + + + + + + + + + + 保存 + 取消 + + + + + + + + + + + + + diff --git a/src/components/XnWorkflow/nodes/utils/index.js b/src/components/XnWorkflow/nodes/utils/index.js new file mode 100644 index 00000000..eff34b0a --- /dev/null +++ b/src/components/XnWorkflow/nodes/utils/index.js @@ -0,0 +1,180 @@ +import { ElMessage } from 'element-plus' + +// 根据自定义的此节点定义的,转换表单的隐藏、必填、禁用 +export default { + // 设置字段显示与否 + convSettingsField(formJson, fieldInfo) { + // 递归遍历控件树 + const traverse = (array) => { + array.forEach((element) => { + if (element.type === 'grid' || element.type === 'tabs') { + // 栅格布局 and 标签页 + element.columns.forEach((item) => { + traverse(item.list) + }) + } else if (element.type === 'card') { + // 卡片布局 and 动态表格 + traverse(element.list) + } else if (element.type === 'table') { + // 表格布局 + element.trs.forEach((item) => { + item.tds.forEach((val) => { + traverse(val.list) + }) + }) + } else { + const type = element.type + if ((type !== 'alert') & (type !== 'text') & (type !== 'divider') & (type !== 'html')) { + const obj = fieldInfo.find((i) => i.key === element.model) + if (obj) { + element.options.hidden = obj.value === 'HIDE' // ? true : false + element.options.disabled = obj.value === 'READ' // ? true : false + } else { + ElMessage.warning('程序检测到功能字段配置发生了异常,依然能正常使用,请联系管理员进行流程重新配置部署!') + } + } + } + }) + } + traverse(formJson.list) + return formJson + }, + // 掏出所有字段,返回列表 + getListField(data) { + let result = [] + // 递归遍历控件树 + const traverse = (array) => { + array.forEach((element) => { + if (element.type === 'grid' || element.type === 'tabs') { + // 栅格布局 and 标签页 + element.columns.forEach((item) => { + traverse(item.list) + }) + } else if (element.type === 'card') { + // 卡片布局 and 动态表格 + traverse(element.list) + } else if (element.type === 'table') { + // 表格布局 + element.trs.forEach((item) => { + item.tds.forEach((val) => { + traverse(val.list) + }) + }) + } else { + const type = element.type + // 排除一些 + if ((type !== 'alert') & (type !== 'text') & (type !== 'divider') & (type !== 'batch') & (type !== 'html')) { + result.push(element) + } + } + }) + } + traverse(data) + return result + }, + // 取节点(用到按钮权限跟字段),并且给节点set一个json,也就是我们的审批记录 + getChildNode(modelJson, activityId, dataList) { + let result = {} + let traverse = (obj) => { + // obj.properties.commentList = [] + if (obj.type === 'exclusiveGateway' || obj.type === 'parallelGateway') { + // 网关下分2步走 + if (obj.conditionNodeList) { + obj.conditionNodeList.forEach((item) => { + traverse(item) + }) + } + if (obj.childNode) { + traverse(obj.childNode) + } + } else { + if (obj.id === activityId) { + result = obj + } else { + // 这里追加记录 + // if (dataList) { + // dataList.forEach((item) => { + // // 给对应的节点 + // if (item.activityId === obj.id) { + // obj.properties.commentList.push(item) + // } + // }) + // } + // 再穿下去 + if (obj.childNode) { + traverse(obj.childNode) + } + } + } + } + // 传入流程的这个 + traverse(modelJson) + return result + }, + // 遍历表单,将组件设为禁用 + convFormComponentsDisabled (formJson) { + // 递归遍历控件树 + const traverse = (array) => { + array.forEach((element) => { + if (element.type === 'grid' || element.type === 'tabs') { + // 栅格布局 and 标签页 + element.columns.forEach((item) => { + traverse(item.list) + }) + } else if (element.type === 'card') { + // 卡片布局 and 动态表格 + traverse(element.list) + } else if (element.type === 'table') { + // 表格布局 + element.trs.forEach((item) => { + item.tds.forEach((val) => { + traverse(val.list) + }) + }) + } else { + const type = element.type + if ((type !== 'alert') & (type !== 'text') & (type !== 'divider') & (type !== 'html')) { + element.options.disabled = true + } + } + }) + } + traverse(formJson.list) + return formJson + }, + // 将渲染图形的信息跟审批记录进行融合,每个节点的配置内添加commentList + coalesceDataListChildNode(modelJson, dataList) { // activityId, + // let result = {} + const traverse = (obj) => { + // obj.properties.commentList = [] + if (obj.type === 'exclusiveGateway' || obj.type === 'parallelGateway') { + // 网关下分2步走 + if (obj.conditionNodeList) { + obj.conditionNodeList.forEach((item) => { + traverse(item) + }) + } + if (obj.childNode) { + traverse(obj.childNode) + } + } else { + if (dataList) { + dataList.forEach((item) => { + // 给对应的节点 + if (item.activityId === obj.id) { + // 增加多个对象 + obj.properties.commentList.push(item) + } + }) + } + // 再穿下去 + if (obj.childNode) { + traverse(obj.childNode) + } + } + } + // 传入流程的这个 + traverse(modelJson) + return modelJson + } +} diff --git a/src/components/XnWorkflow/process.vue b/src/components/XnWorkflow/process.vue new file mode 100644 index 00000000..d49be85c --- /dev/null +++ b/src/components/XnWorkflow/process.vue @@ -0,0 +1,625 @@ + + + + + + + + + + + + + 配置使用该流程的人员 + + + + 选择机构 + + + + + + + 选择角色 + + + + + + + 选择职位 + + + + + + + 选择用户 + + + + + + + + 流程基础全局配置 + + + + + + + + + + + + + + 打印模板 + 自定义表单内提供打印方法 + + + + + + + + + + + + + + + + {{ autoDistinctType.label }} + + + + + + + + + + + + + + + + + + 配置通知事项 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 预设全局需要的表单 + + + + + + 预览 + + + + + + + + + + + + 预览 + + + + + + + + + + + + + + + + 保存 + 取消 + + + + + + + + + + + diff --git a/src/components/bpmnProcessDesigner/package/penal/PropertiesPanel.vue b/src/components/bpmnProcessDesigner/package/penal/PropertiesPanel.vue index 8b3aa2b2..06a6604f 100644 --- a/src/components/bpmnProcessDesigner/package/penal/PropertiesPanel.vue +++ b/src/components/bpmnProcessDesigner/package/penal/PropertiesPanel.vue @@ -1,20 +1,20 @@ - - + + - + 常规 - + - + 流转条件 - + - - + - + + 表单 + + + + + + + 任务(审批人) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 其他 - + - diff --git a/src/components/bpmnProcessDesigner/package/penal/task/task-components/UserTask.vue b/src/components/bpmnProcessDesigner/package/penal/task/task-components/UserTask.vue index c86f3568..e0a85f97 100644 --- a/src/components/bpmnProcessDesigner/package/penal/task/task-components/UserTask.vue +++ b/src/components/bpmnProcessDesigner/package/penal/task/task-components/UserTask.vue @@ -1,234 +1,182 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 选择表达式 - - - - + + + + + + + + + + - + + diff --git a/src/main.ts b/src/main.ts index 322ed6bc..f6dd98a9 100644 --- a/src/main.ts +++ b/src/main.ts @@ -24,6 +24,7 @@ VXETable.use(VXETablePluginExportXLSX, { }) window.XEUtils = XEUtils + const app = createApp(App) //开启离线地图 // app.use(BaiduMapOffline, { diff --git a/src/router/static.ts b/src/router/static.ts index fee57f7a..243f390c 100644 --- a/src/router/static.ts +++ b/src/router/static.ts @@ -124,6 +124,14 @@ export const adminBaseRoute = { meta: { title: pageTitle('router.formDesigner') } + }, + { + path: 'configSteps', + component: () => import('@/views/system/workflow/model/configSteps.vue'), + name: '模型设计器页面', + meta: { + title: pageTitle('router.formDesigner') + } } ] diff --git a/src/utils/constantRequest.ts b/src/utils/constantRequest.ts index cbe63d21..4eb2acd1 100644 --- a/src/utils/constantRequest.ts +++ b/src/utils/constantRequest.ts @@ -2,4 +2,7 @@ export const ADVANCE_BOOT = '/advance-boot' //技术监督 -export const PROCESS_BOOT = '/process-boot' \ No newline at end of file +export const PROCESS_BOOT = '/process-boot' + +//工作流模块 +export const WORKFLOW_BOOT = '/workflow-boot' \ No newline at end of file diff --git a/src/utils/formRules.js b/src/utils/formRules.js new file mode 100644 index 00000000..778b6a27 --- /dev/null +++ b/src/utils/formRules.js @@ -0,0 +1,51 @@ +/** + * Copyright [2022] [https://www.xiaonuo.vip] + * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改Snowy源码头部的版权声明。 + * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip + * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。 + * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +export const required = (message, trigger = ['blur', 'change']) => ({ + required: true, + message, + trigger +}) + +// 常用正则规则大全:https://any86.github.io/any-rule/ + +export const rules = { + phone: { + pattern: /^(13[0-9]|14[579]|15[0-3,5-9]|16[6]|17[0135678]|18[0-9]|19[89])\d{8}$/, + message: '请填写符合要求的11位手机号', + trigger: 'blur' + }, + email: { + pattern: /^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/, + message: '请填写正确的邮箱号', + trigger: 'blur' + }, + idCard: { + pattern: + /(^\d{8}(0\d|10|11|12)([0-2]\d|30|31)\d{3}$)|(^\d{6}(18|19|20)\d{2}(0[1-9]|10|11|12)([0-2]\d|30|31)\d{3}(\d|X|x)$)/, + message: '请填写符合要求的身份证号', + trigger: 'blur' + }, + lettersNum: { + pattern: /^[A-Za-z0-9]+$/, + message: '填写内容须是字母或数字组成', + trigger: 'blur' + }, + number: { + pattern: /^\d{1,}$/, + message: '填写内容必须是纯数字', + trigger: 'blur' + }, + price: { + pattern: /(?:^[1-9]([0-9]+)?(?:\.[0-9]{1,2})?$)|(?:^(?:0)$)|(?:^[0-9]\.[0-9](?:[0-9])?$)/, + message: '只支持正数金额', + trigger: 'blur' + } +} diff --git a/src/utils/tool.js b/src/utils/tool.js new file mode 100644 index 00000000..64c43afa --- /dev/null +++ b/src/utils/tool.js @@ -0,0 +1,161 @@ +/** + * Copyright [2022] [https://www.xiaonuo.vip] + * Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + * 1.请不要删除和修改根目录下的LICENSE文件。 + * 2.请不要删除和修改Snowy源码头部的版权声明。 + * 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。 + * 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip + * 5.不可二次分发开源参与同类竞品,如有想法可联系团队xiaonuobase@qq.com商议合作。 + * 6.若您的项目无法满足以上几点,需要更多功能代码,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +/* + * @Descripttion: 工具集 + * @version: 1.1 + * @LastEditors: yubaoshan + * @LastEditTime: 2022年4月19日10:58:41 + */ +const tool = {} + +// localStorage +tool.data = { + set(table, settings) { + const _set = JSON.stringify(settings) + return localStorage.setItem(table, _set) + }, + get(table) { + let data = localStorage.getItem(table) + try { + data = JSON.parse(data) + } catch (err) { + return null + } + return data + }, + remove(table) { + return localStorage.removeItem(table) + }, + + clear() { + return localStorage.clear() + } +} + +// sessionStorage +tool.session = { + set(table, settings) { + const _set = JSON.stringify(settings) + return sessionStorage.setItem(table, _set) + }, + get(table) { + let data = sessionStorage.getItem(table) + try { + data = JSON.parse(data) + } catch (err) { + return null + } + return data + }, + remove(table) { + return sessionStorage.removeItem(table) + }, + clear() { + return sessionStorage.clear() + } +} + +// 千分符 +tool.groupSeparator = (num) => { + num = `${num}` + if (!num.includes('.')) num += '.' + + return num + .replace(/(\d)(?=(\d{3})+\.)/g, ($0, $1) => { + return `${$1},` + }) + .replace(/\.$/, '') +} + +// 获取所有字典数组 +tool.dictDataAll = () => { + return tool.data.get('DICT_TYPE_TREE_DATA') +} + +// 字典翻译方法,界面插槽使用方法 {{ $TOOL.dictType('sex', record.sex) }} +tool.dictTypeData = (dictValue, value) => { + const dictTypeTree = tool.dictDataAll() + if (!dictTypeTree) { + return '需重新登录' + } + const tree = dictTypeTree.find((item) => item.dictValue === dictValue) + if (!tree) { + return '无此字典' + } + const children = tree.children + const dict = children.find((item) => item.dictValue === value) + return dict ? dict.dictLabel : '无此字典项' +} + +// 获取某个code下字典的列表,多用于字典下拉框 +tool.dictTypeList = (dictValue) => { + const dictTypeTree = tool.dictDataAll() + if (!dictTypeTree) { + return [] + } + const tree = dictTypeTree.find((item) => item.dictValue === dictValue) + if (tree && tree.children) { + return tree.children + } + return [] +} + +// 获取某个code下字典的列表,基于dictTypeList 改进,保留老的,逐步替换 +tool.dictList = (dictValue) => { + const dictTypeTree = tool.dictDataAll() + if (!dictTypeTree) { + return [] + } + const tree = dictTypeTree.find((item) => item.dictValue === dictValue) + if (tree) { + return tree.children.map((item) => { + return { + value: item['dictValue'], + label: item['name'] + } + }) + } + return [] +} + +// 树形翻译 需要指定最顶级的 parentValue 和当级的value +tool.translateTree = (parentValue, value) => { + const tree = tool.dictDataAll().find((item) => item.dictValue === parentValue) + const targetNode = findNodeByValue(tree, value) + return targetNode ? targetNode.dictLabel : '' +} +const findNodeByValue = (node, value) => { + if (node.dictValue === value) { + return node + } + if (node.children) { + for (let i = 0; i < node.children.length; i++) { + const result = findNodeByValue(node.children[i], value) + if (result) { + return result + } + } + } + return null +} + +// 生成UUID +tool.snowyUuid = () => { + let uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { + let r = (Math.random() * 16) | 0, + v = c === 'x' ? r : (r & 0x3) | 0x8 + return v.toString(16) + }) + // 首字符转换成字母 + return 'xn' + uuid.slice(2) +} + +export default tool diff --git a/src/views/pqs/process/definition/editor/index.vue b/src/views/pqs/process/definition/editor/index.vue index ddd574da..194a7678 100644 --- a/src/views/pqs/process/definition/editor/index.vue +++ b/src/views/pqs/process/definition/editor/index.vue @@ -90,12 +90,12 @@ onMounted(async () => { // 首次创建的 Model 模型,它是没有 bpmnXml,此时需要给它一个默认的 data.key = Math.random().toString(36).slice(-8) data.bpmnXml = ` - + - ` + ` } } else { await ModelApi.readXml(prop.model?.deploymentId).then((res: any) => { diff --git a/src/views/system/workflow/model/configSteps.vue b/src/views/system/workflow/model/configSteps.vue new file mode 100644 index 00000000..5b58776e --- /dev/null +++ b/src/views/system/workflow/model/configSteps.vue @@ -0,0 +1,363 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 后退 + + + + + + 继续 + + + + + + + + + + + + + + + + + + + + + + + + + + + 后退 + + + + + + 部署运行 + + + + + + + + + + + + + + + + 后退 + + + + + + 部署运行 + + + + + + + + + + diff --git a/src/views/system/workflow/model/index.vue b/src/views/system/workflow/model/index.vue index 329c5faa..dbe8e706 100644 --- a/src/views/system/workflow/model/index.vue +++ b/src/views/system/workflow/model/index.vue @@ -1,33 +1,33 @@ - - - - - - - - - - - - - - 新增 - - - - - - + + + + + + + + + + + + + + 新增 + + + + + + diff --git a/src/views/system/workflow/model_old/processDesigner.vue b/src/views/system/workflow/model_old/processDesigner.vue new file mode 100644 index 00000000..b7bdf758 --- /dev/null +++ b/src/views/system/workflow/model_old/processDesigner.vue @@ -0,0 +1,9 @@ + + 111 + + + + +
审批节点
抄送节点
条件分支
并行分支
+ + + 增加条件组 + +
选择人员
打印模板
自定义表单内提供打印方法