diff --git a/src/api/bpm/model/index.ts b/src/api/bpm/model/index.ts index 2b484a617c5ceb41affb32831632b380b59999ac..46728637576e2f8e58826c337a1f853988d6f466 100644 --- a/src/api/bpm/model/index.ts +++ b/src/api/bpm/model/index.ts @@ -30,7 +30,7 @@ export const getModelPage = async (params) => { return await request.get({ url: '/bpm/model/page', params }) } -export const getModel = async (id: number) => { +export const getModel = async (id: string) => { return await request.get({ url: '/bpm/model/get?id=' + id }) } @@ -38,6 +38,10 @@ export const updateModel = async (data: ModelVO) => { return await request.put({ url: '/bpm/model/update', data: data }) } +export const updateModelBpmn = async (data: ModelVO) => { + return await request.put({ url: '/bpm/model/update-bpmn', data: data }) +} + // 任务状态修改 export const updateModelState = async (id: number, state: number) => { const data = { diff --git a/src/api/bpm/processInstance/index.ts b/src/api/bpm/processInstance/index.ts index 9122b2b903214485738fd5204d5cdb26cf726334..3d6330a8ab8731620ad8d4696160fe5527c59280 100644 --- a/src/api/bpm/processInstance/index.ts +++ b/src/api/bpm/processInstance/index.ts @@ -1,6 +1,6 @@ import request from '@/config/axios' import { ProcessDefinitionVO } from '@/api/bpm/model' - +import { NodeType } from '@/components/SimpleProcessDesignerV2/src/consts' export type Task = { id: string name: string @@ -22,6 +22,35 @@ export type ProcessInstanceVO = { processDefinition?: ProcessDefinitionVO } +// 用户信息 +export type User = { + id: number, + nickname: string, + avatar: string +} + +// 审批任务信息 +export type ApprovalTaskInfo = { + id: number, + ownerUser: User, + assigneeUser: User, + status: number, + reason: string + +} + +// 审批节点信息 +export type ApprovalNodeInfo = { + id : number + name: string + nodeType: NodeType + status: number + startTime?: Date + endTime?: Date + candidateUserList?: User[] + tasks: ApprovalTaskInfo[] +} + export const getProcessInstanceMyPage = async (params: any) => { return await request.get({ url: '/bpm/process-instance/my-page', params }) } @@ -57,3 +86,14 @@ export const getProcessInstance = async (id: string) => { export const getProcessInstanceCopyPage = async (params: any) => { return await request.get({ url: '/bpm/process-instance/copy/page', params }) } + +// 获取审批详情 +export const getApprovalDetail = async (processInstanceId?:string, processDefinitionId?:string) => { + const param = processInstanceId ? '?processInstanceId='+ processInstanceId : '?processDefinitionId='+ processDefinitionId + return await request.get({ url: 'bpm/process-instance/get-approval-detail'+ param }) +} + +// 获取表单字段权限 +export const getFormFieldsPermission = async (params: any) => { + return await request.get({ url: '/bpm/process-instance/get-form-fields-permission', params }) +} diff --git a/src/api/bpm/simple/index.ts b/src/api/bpm/simple/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..6e1e995a8e1f1b91dedeec0c1074d65ed7455245 --- /dev/null +++ b/src/api/bpm/simple/index.ts @@ -0,0 +1,15 @@ +import request from '@/config/axios' + + +export const updateBpmSimpleModel = async (data) => { + return await request.post({ + url: '/bpm/model/simple/update', + data: data + }) +} + +export const getBpmSimpleModel = async (id) => { + return await request.get({ + url: '/bpm/model/simple/get?id=' + id + }) +} diff --git a/src/api/bpm/task/index.ts b/src/api/bpm/task/index.ts index f3cda9f7f623894510adccac09cf0f977fb9a942..d32b2e14ce6a499f249dd09ebdf6a688653a255d 100644 --- a/src/api/bpm/task/index.ts +++ b/src/api/bpm/task/index.ts @@ -1,5 +1,51 @@ import request from '@/config/axios' +/** + * 任务状态枚举 + */ +export enum TaskStatusEnum { + /** + * 未开始 + */ + NOT_START = -1, + + /** + * 待审批 + */ + WAIT = 0, + /** + * 审批中 + */ + RUNNING = 1, + /** + * 审批通过 + */ + APPROVE = 2, + + /** + * 审批不通过 + */ + REJECT = 3, + + /** + * 已取消 + */ + CANCEL = 4, + /** + * 已退回 + */ + RETURN = 5, + /** + * 委派中 + */ + DELEGATE = 6, + /** + * 审批通过中 + */ + APPROVING = 7, + +} + export type TaskVO = { id: number } diff --git a/src/components/SimpleProcessDesigner/src/addNode.vue b/src/components/SimpleProcessDesigner/src/addNode.vue deleted file mode 100644 index 6d09ae8a7a1e574d059ee690735bec6dcf286b8c..0000000000000000000000000000000000000000 --- a/src/components/SimpleProcessDesigner/src/addNode.vue +++ /dev/null @@ -1,237 +0,0 @@ -/* stylelint-disable order/properties-order */ - - - - - - - - - - 审批人 - - - - - - 抄送人 - - - - - - 条件分支 - - - - - - - - - - - - - diff --git a/src/components/SimpleProcessDesigner/src/nodeWrap.vue b/src/components/SimpleProcessDesigner/src/nodeWrap.vue deleted file mode 100644 index 3c9d5eb1c867489c8144259f18d1c6f9201046e7..0000000000000000000000000000000000000000 --- a/src/components/SimpleProcessDesigner/src/nodeWrap.vue +++ /dev/null @@ -1,297 +0,0 @@ - - - - - - - {{ nodeConfig.nodeName }} - - {{nodeConfig.type == 1?'':''}} - - {{ nodeConfig.nodeName }} - - - - - - 请选择{{defaultText}} - {{showText}} - - - - - - - - - - - - - 添加条件 - - - - - < - - - {{ item.nodeName }} - 优先级{{ item.priorityLevel }} - - - > - {{ conditionStr(nodeConfig, index) }} - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/components/SimpleProcessDesigner/src/util.ts b/src/components/SimpleProcessDesigner/src/util.ts deleted file mode 100644 index f4acd76c4929d1805e41e5137a99092a93b7eb1e..0000000000000000000000000000000000000000 --- a/src/components/SimpleProcessDesigner/src/util.ts +++ /dev/null @@ -1,165 +0,0 @@ -/** - * todo - */ -export const arrToStr = (arr?: [{ name: string }]) => { - if (arr) { - return arr - .map((item) => { - return item.name - }) - .toString() - } -} - -export const setApproverStr = (nodeConfig: any) => { - if (nodeConfig.settype == 1) { - if (nodeConfig.nodeUserList.length == 1) { - return nodeConfig.nodeUserList[0].name - } else if (nodeConfig.nodeUserList.length > 1) { - if (nodeConfig.examineMode == 1) { - return arrToStr(nodeConfig.nodeUserList) - } else if (nodeConfig.examineMode == 2) { - return nodeConfig.nodeUserList.length + '人会签' - } - } - } else if (nodeConfig.settype == 2) { - const level = - nodeConfig.directorLevel == 1 ? '直接主管' : '第' + nodeConfig.directorLevel + '级主管' - if (nodeConfig.examineMode == 1) { - return level - } else if (nodeConfig.examineMode == 2) { - return level + '会签' - } - } else if (nodeConfig.settype == 4) { - if (nodeConfig.selectRange == 1) { - return '发起人自选' - } else { - if (nodeConfig.nodeUserList.length > 0) { - if (nodeConfig.selectRange == 2) { - return '发起人自选' - } else { - return '发起人从' + nodeConfig.nodeUserList[0].name + '中自选' - } - } else { - return '' - } - } - } else if (nodeConfig.settype == 5) { - return '发起人自己' - } else if (nodeConfig.settype == 7) { - return '从直接主管到通讯录中级别最高的第' + nodeConfig.examineEndDirectorLevel + '个层级主管' - } -} - -export const copyerStr = (nodeConfig: any) => { - if (nodeConfig.nodeUserList.length != 0) { - return arrToStr(nodeConfig.nodeUserList) - } else { - if (nodeConfig.ccSelfSelectFlag == 1) { - return '发起人自选' - } - } -} -export const conditionStr = (nodeConfig, index) => { - const { conditionList, nodeUserList } = nodeConfig.conditionNodes[index] - if (conditionList.length == 0) { - return index == nodeConfig.conditionNodes.length - 1 && - nodeConfig.conditionNodes[0].conditionList.length != 0 - ? '其他条件进入此流程' - : '请设置条件' - } else { - let str = '' - for (let i = 0; i < conditionList.length; i++) { - const { - columnId, - columnType, - showType, - showName, - optType, - zdy1, - opt1, - zdy2, - opt2, - fixedDownBoxValue - } = conditionList[i] - if (columnId == 0) { - if (nodeUserList.length != 0) { - str += '发起人属于:' - str += - nodeUserList - .map((item) => { - return item.name - }) - .join('或') + ' 并且 ' - } - } - if (columnType == 'String' && showType == '3') { - if (zdy1) { - str += showName + '属于:' + dealStr(zdy1, JSON.parse(fixedDownBoxValue)) + ' 并且 ' - } - } - if (columnType == 'Double') { - if (optType != 6 && zdy1) { - const optTypeStr = ['', '<', '>', '≤', '=', '≥'][optType] - str += `${showName} ${optTypeStr} ${zdy1} 并且 ` - } else if (optType == 6 && zdy1 && zdy2) { - str += `${zdy1} ${opt1} ${showName} ${opt2} ${zdy2} 并且 ` - } - } - } - return str ? str.substring(0, str.length - 4) : '请设置条件' - } -} - -export const dealStr = (str: string, obj) => { - const arr = [] - const list = str.split(',') - for (const elem in obj) { - list.map((item) => { - if (item == elem) { - arr.push(obj[elem].value) - } - }) - } - return arr.join('或') -} - -export const removeEle = (arr, elem, key = 'id') => { - let includesIndex - arr.map((item, index) => { - if (item[key] == elem[key]) { - includesIndex = index - } - }) - arr.splice(includesIndex, 1) -} - -export const bgColors = ['87, 106, 149', '255, 148, 62', '50, 150, 250'] -export const placeholderList = ['发起人', '审核人', '抄送人'] -export const setTypes = [ - { value: 1, label: '指定成员' }, - { value: 2, label: '主管' }, - { value: 4, label: '发起人自选' }, - { value: 5, label: '发起人自己' }, - { value: 7, label: '连续多级主管' } -] - -export const selectModes = [ - { value: 1, label: '选一个人' }, - { value: 2, label: '选多个人' } -] - -export const selectRanges = [ - { value: 1, label: '全公司' }, - { value: 2, label: '指定成员' }, - { value: 3, label: '指定角色' } -] - -export const optTypes = [ - { value: '1', label: '小于' }, - { value: '2', label: '大于' }, - { value: '3', label: '小于等于' }, - { value: '4', label: '等于' }, - { value: '5', label: '大于等于' }, - { value: '6', label: '介于两个数之间' } -] diff --git a/src/components/SimpleProcessDesigner/theme/workflow.css b/src/components/SimpleProcessDesigner/theme/workflow.css deleted file mode 100644 index 888b1a82c991645c6ce32d718039f19a0b43735f..0000000000000000000000000000000000000000 --- a/src/components/SimpleProcessDesigner/theme/workflow.css +++ /dev/null @@ -1,1292 +0,0 @@ - -.clearfix { - zoom: 1 -} - -.clearfix:after, -.clearfix:before { - content: ""; - display: table -} - -.clearfix:after { - clear: both -} - -@font-face { - font-family: anticon; - font-display: fallback; - src: url("https://at.alicdn.com/t/font_148784_v4ggb6wrjmkotj4i.eot"); - src: url("https://at.alicdn.com/t/font_148784_v4ggb6wrjmkotj4i.woff") format("woff"), url("https://at.alicdn.com/t/font_148784_v4ggb6wrjmkotj4i.ttf") format("truetype"), url("https://at.alicdn.com/t/font_148784_v4ggb6wrjmkotj4i.svg#iconfont") format("svg") -} - -.anticon { - display: inline-block; - font-style: normal; - vertical-align: baseline; - text-align: center; - text-transform: none; - line-height: 1; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale -} - -.anticon:before { - display: block; - font-family: anticon!important -} -.anticon-close:before { - content: "\E633" -} -.anticon-right:before { - content: "\E61F" -} -.anticon-exclamation-circle{ - color: rgb(242, 86, 67) -} -.anticon-exclamation-circle:before { - content: "\E62C" -} - -.anticon-left:before { - content: "\E620" -} - -.anticon-close-circle:before { - content: "\E62E" -} - -.ant-btn { - line-height: 1.5; - display: inline-block; - font-weight: 400; - text-align: center; - touch-action: manipulation; - cursor: pointer; - background-image: none; - border: 1px solid transparent; - white-space: nowrap; - padding: 0 15px; - font-size: 14px; - border-radius: 4px; - height: 32px; - user-select: none; - transition: all .3s cubic-bezier(.645, .045, .355, 1); - position: relative; - color: rgba(0, 0, 0, .65); - background-color: #fff; - border-color: #d9d9d9 -} - -.ant-btn>.anticon { - line-height: 1 -} - -.ant-btn, -.ant-btn:active, -.ant-btn:focus { - outline: 0 -} - -.ant-btn>a:only-child { - color: currentColor -} - -.ant-btn>a:only-child:after { - content: ""; - position: absolute; - top: 0; - left: 0; - bottom: 0; - right: 0; - background: transparent -} - -.ant-btn:focus, -.ant-btn:hover { - color: #40a9ff; - background-color: #fff; - border-color: #40a9ff -} - -.ant-btn:focus>a:only-child, -.ant-btn:hover>a:only-child { - color: currentColor -} - -.ant-btn:focus>a:only-child:after, -.ant-btn:hover>a:only-child:after { - content: ""; - position: absolute; - top: 0; - left: 0; - bottom: 0; - right: 0; - background: transparent -} - -.ant-btn.active, -.ant-btn:active { - color: #096dd9; - background-color: #fff; - border-color: #096dd9 -} - -.ant-btn.active>a:only-child, -.ant-btn:active>a:only-child { - color: currentColor -} - -.ant-btn.active>a:only-child:after, -.ant-btn:active>a:only-child:after { - content: ""; - position: absolute; - top: 0; - left: 0; - bottom: 0; - right: 0; - background: transparent -} - -.ant-btn.active, -.ant-btn:active, -.ant-btn:focus, -.ant-btn:hover { - background: #fff; - text-decoration: none -} - -.ant-btn>i, -.ant-btn>span { - pointer-events: none -} - -.ant-btn:before { - position: absolute; - top: -1px; - left: -1px; - bottom: -1px; - right: -1px; - background: #fff; - opacity: .35; - content: ""; - border-radius: inherit; - z-index: 1; - transition: opacity .2s; - pointer-events: none; - display: none -} - -.ant-btn .anticon { - transition: margin-left .3s cubic-bezier(.645, .045, .355, 1) -} - -.ant-btn:active>span, -.ant-btn:focus>span { - position: relative -} - -.ant-btn>.anticon+span, -.ant-btn>span+.anticon { - margin-left: 8px -} - -.ant-input { - font-family: Chinese Quote, -apple-system, BlinkMacSystemFont, Segoe UI, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Helvetica Neue, Helvetica, Arial, sans-serif; - font-variant: tabular-nums; - box-sizing: border-box; - margin: 0; - padding: 0; - list-style: none; - position: relative; - display: inline-block; - padding: 4px 11px; - width: 100%; - height: 32px; - font-size: 14px; - line-height: 1.5; - color: rgba(0, 0, 0, .65); - background-color: #fff; - background-image: none; - border: 1px solid #d9d9d9; - border-radius: 4px; - transition: all .3s -} - -.ant-input::-moz-placeholder { - color: #bfbfbf; - opacity: 1 -} - -.ant-input:-ms-input-placeholder { - color: #bfbfbf -} - -.ant-input::-webkit-input-placeholder { - color: #bfbfbf -} - -.ant-input:focus, -.ant-input:hover { - border-color: #40a9ff; - border-right-width: 1px!important -} - -.ant-input:focus { - outline: 0; - box-shadow: 0 0 0 2px rgba(24, 144, 255, .2) -} - -textarea.ant-input { - max-width: 100%; - height: auto; - vertical-align: bottom; - transition: all .3s, height 0s; - min-height: 32px -} - -a, -abbr, -acronym, -address, -applet, -article, -aside, -audio, -b, -big, -blockquote, -body, -canvas, -caption, -center, -cite, -code, -dd, -del, -details, -dfn, -div, -dl, -dt, -em, -fieldset, -figcaption, -figure, -footer, -form, -h1, -h2, -h3, -h4, -h5, -h6, -header, -hgroup, -html, -i, -iframe, -img, -ins, -kbd, -label, -legend, -li, -mark, -menu, -nav, -object, -ol, -p, -pre, -q, -s, -samp, -section, -small, -span, -strike, -strong, -sub, -summary, -sup, -table, -tbody, -td, -tfoot, -th, -thead, -time, -tr, -tt, -u, -ul, -var, -video { - margin: 0; - padding: 0; - border: 0; - outline: 0; - font-size: 100%; - font: inherit; - vertical-align: baseline -} - -*, -:after, -:before { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box -} - -html { - font-family: sans-serif; - -ms-text-size-adjust: 100%; - -webkit-text-size-adjust: 100% -} - -body, -html { - font-size: 14px -} - -body { - font-family: Microsoft Yahei, Lucida Grande, Lucida Sans Unicode, Helvetica, Arial, Verdana, sans-serif; - line-height: 1.6; - background-color: #fff; - position: static!important; - -webkit-tap-highlight-color: rgba(0, 0, 0, 0) -} - -ol, -ul { - list-style-type: none -} - -b, -strong { - font-weight: 700 -} - -img { - border: 0 -} - -button, -input, -select, -textarea { - font-family: inherit; - font-size: 100%; - margin: 0 -} - -textarea { - overflow: auto; - vertical-align: top; - -webkit-appearance: none -} - -button, -input { - line-height: normal -} - -button, -select { - text-transform: none -} - -button, -html input[type=button], -input[type=reset], -input[type=submit] { - -webkit-appearance: button; - cursor: pointer -} - -input[type=search] { - -webkit-appearance: textfield; - -moz-box-sizing: content-box; - -webkit-box-sizing: content-box; - box-sizing: content-box -} - -input[type=search]::-webkit-search-cancel-button, -input[type=search]::-webkit-search-decoration { - -webkit-appearance: none -} - -button::-moz-focus-inner, -input::-moz-focus-inner { - border: 0; - padding: 0 -} - -table { - width: 100%; - border-spacing: 0; - border-collapse: collapse -} - -table, -td, -th { - border: 0 -} - -td, -th { - padding: 0; - vertical-align: top -} - -th { - font-weight: 700; - text-align: left -} - -thead th { - white-space: nowrap -} - -a { - text-decoration: none; - cursor: pointer; - color: #3296fa -} - -a:active, -a:hover { - outline: 0; - color: #3296fa -} - -small { - font-size: 80% -} - -body, -html { - font-size: 12px!important; - color: #191f25!important; - background: #f6f6f6!important -} - -.wrap { - display: -webkit-box; - display: -ms-flexbox; - display: flex; - -webkit-box-orient: vertical; - -webkit-box-direction: normal; - -ms-flex-direction: column; - flex-direction: column; - height: 100% -} - -@font-face { - font-family: IconFont; - src: url("//at.alicdn.com/t/font_135284_ph2thxxbzgf.eot"); - src: url("//at.alicdn.com/t/font_135284_ph2thxxbzgf.eot?#iefix") format("embedded-opentype"), url("//at.alicdn.com/t/font_135284_ph2thxxbzgf.woff") format("woff"), url("//at.alicdn.com/t/font_135284_ph2thxxbzgf.ttf") format("truetype"), url("//at.alicdn.com/t/font_135284_ph2thxxbzgf.svg#IconFont") format("svg") -} - -.iconfont { - font-family: IconFont!important; - font-size: 16px; - font-style: normal; - -webkit-font-smoothing: antialiased; - -webkit-text-stroke-width: .2px; - -moz-osx-font-smoothing: grayscale -} - -.fd-nav { - position: fixed; - top: 0; - left: 0; - right: 0; - z-index: 997; - width: 100%; - height: 60px; - font-size: 14px; - color: #fff; - background: #3296fa; - display: flex; - align-items: center -} - -.fd-nav>* { - flex: 1; - width: 100% -} - -.fd-nav .fd-nav-left { - display: -webkit-box; - display: flex; - align-items: center -} - -.fd-nav .fd-nav-center { - flex: none; - width: 600px; - text-align: center -} - -.fd-nav .fd-nav-right { - display: flex; - align-items: center; - justify-content: flex-end; - text-align: right -} - -.fd-nav .fd-nav-back { - display: inline-block; - width: 60px; - height: 60px; - font-size: 22px; - border-right: 1px solid #1583f2; - text-align: center; - cursor: pointer -} - -.fd-nav .fd-nav-back:hover { - background: #5af -} - -.fd-nav .fd-nav-back:active { - background: #1583f2 -} - -.fd-nav .fd-nav-back .anticon { - line-height: 60px -} - -.fd-nav .fd-nav-title { - width: 0; - flex: 1; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - padding: 0 15px -} - -.fd-nav a { - color: #fff; - margin-left: 12px -} - -.fd-nav .button-publish { - min-width: 80px; - margin-left: 4px; - margin-right: 15px; - color: #3296fa; - border-color: #fff -} - -.fd-nav .button-publish.ant-btn:focus, -.fd-nav .button-publish.ant-btn:hover { - color: #3296fa; - border-color: #fff; - box-shadow: 0 10px 20px 0 rgba(0, 0, 0, .3) -} - -.fd-nav .button-publish.ant-btn:active { - color: #3296fa; - background: #d6eaff; - box-shadow: none -} - -.fd-nav .button-preview { - min-width: 80px; - margin-left: 16px; - margin-right: 4px; - color: #fff; - border-color: #fff; - background: transparent -} - -.fd-nav .button-preview.ant-btn:focus, -.fd-nav .button-preview.ant-btn:hover { - color: #fff; - border-color: #fff; - background: #59acfc -} - -.fd-nav .button-preview.ant-btn:active { - color: #fff; - border-color: #fff; - background: #2186ef -} - -.fd-nav-content { - position: fixed; - top: 60px; - left: 0; - right: 0; - bottom: 0; - z-index: 1; - overflow-x: hidden; - overflow-y: auto; - padding-bottom: 30px -} - -.error-modal-desc { - font-size: 13px; - color: rgba(25, 31, 37, .56); - line-height: 22px; - margin-bottom: 14px -} - -.error-modal-list { - height: 200px; - overflow-y: auto; - margin-right: -25px; - padding-right: 25px -} - -.error-modal-item { - padding: 10px 20px; - line-height: 21px; - background: #f6f6f6; - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 8px; - border-radius: 4px -} - -.error-modal-item-label { - flex: none; - font-size: 15px; - color: rgba(25, 31, 37, .56); - padding-right: 10px -} - -.error-modal-item-content { - text-align: right; - flex: 1; - font-size: 13px; - color: #191f25 -} - -#body.blur { - -webkit-filter: blur(3px); - filter: blur(3px) -} - -.zoom { - display: flex; - position: fixed; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - -webkit-box-pack: justify; - -ms-flex-pack: justify; - justify-content: space-between; - height: 40px; - width: 125px; - right: 40px; - margin-top: 30px; - z-index: 10 -} - -.zoom .zoom-in, -.zoom .zoom-out { - width: 30px; - height: 30px; - background: #fff; - color: #c1c1cd; - cursor: pointer; - background-size: 100%; - background-repeat: no-repeat -} - -.zoom .zoom-out { - background-image: url(https://gw.alicdn.com/tfs/TB1s0qhBHGYBuNjy0FoXXciBFXa-90-90.png) -} - -.zoom .zoom-out.disabled { - opacity: .5 -} - -.zoom .zoom-in { - background-image: url(https://gw.alicdn.com/tfs/TB1UIgJBTtYBeNjy1XdXXXXyVXa-90-90.png) -} - -.zoom .zoom-in.disabled { - opacity: .5 -} - -.auto-judge:hover .editable-title, -.node-wrap-box:hover .editable-title { - border-bottom: 1px dashed #fff -} - -.auto-judge:hover .editable-title.editing, -.node-wrap-box:hover .editable-title.editing { - text-decoration: none; - border: 1px solid #d9d9d9 -} - -.auto-judge:hover .editable-title { - border-color: #15bc83 -} - -.editable-title { - line-height: 15px; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - border-bottom: 1px dashed transparent -} - -.editable-title:before { - content: ""; - position: absolute; - top: 0; - left: 0; - bottom: 0; - right: 40px -} - -.editable-title:hover { - border-bottom: 1px dashed #fff -} - -.editable-title-input { - flex: none; - height: 18px; - padding-left: 4px; - text-indent: 0; - font-size: 12px; - line-height: 18px; - z-index: 1 -} - -.editable-title-input:hover { - text-decoration: none -} - -.ant-btn { - position: relative -} - -.node-wrap-box { - display: -webkit-inline-box; - display: -ms-inline-flexbox; - display: inline-flex; - -webkit-box-orient: vertical; - -webkit-box-direction: normal; - -ms-flex-direction: column; - flex-direction: column; - position: relative; - width: 220px; - min-height: 72px; - -ms-flex-negative: 0; - flex-shrink: 0; - background: #fff; - border-radius: 4px; - cursor: pointer -} - -.node-wrap-box:after { - pointer-events: none; - content: ""; - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - z-index: 2; - border-radius: 4px; - border: 1px solid transparent; - transition: all .1s cubic-bezier(.645, .045, .355, 1); - box-shadow: 0 2px 5px 0 rgba(0, 0, 0, .1) -} - -.node-wrap-box.active:after, -.node-wrap-box:active:after, -.node-wrap-box:hover:after { - border: 1px solid #3296fa; - box-shadow: 0 0 6px 0 rgba(50, 150, 250, .3) -} - -.node-wrap-box.active .close, -.node-wrap-box:active .close, -.node-wrap-box:hover .close { - display: block -} - -.node-wrap-box.error:after { - border: 1px solid #f25643; - box-shadow: 0 2px 5px 0 rgba(0, 0, 0, .1) -} - -.node-wrap-box .title { - position: relative; - display: flex; - align-items: center; - padding-left: 16px; - padding-right: 30px; - width: 100%; - height: 24px; - line-height: 24px; - font-size: 12px; - color: #fff; - text-align: left; - background: #576a95; - border-radius: 4px 4px 0 0 -} - -.node-wrap-box .title .iconfont { - font-size: 12px; - margin-right: 5px -} - -.node-wrap-box .placeholder { - color: #bfbfbf -} - -.node-wrap-box .close { - display: none; - position: absolute; - right: 10px; - top: 50%; - transform: translateY(-50%); - width: 20px; - height: 20px; - font-size: 14px; - color: #fff; - border-radius: 50%; - text-align: center; - line-height: 20px -} - -.node-wrap-box .content { - position: relative; - font-size: 14px; - padding: 16px; - padding-right: 30px -} - -.node-wrap-box .content .text { - overflow: hidden; - text-overflow: ellipsis; - display: -webkit-box; - -webkit-line-clamp: 3; - -webkit-box-orient: vertical -} - -.node-wrap-box .content .arrow { - position: absolute; - right: 10px; - top: 50%; - transform: translateY(-50%); - width: 20px; - height: 14px; - font-size: 14px; - color: #979797 -} - -.start-node.node-wrap-box .content .text { - display: block; - white-space: nowrap -} - -.node-wrap-box:before { - content: ""; - position: absolute; - top: -12px; - left: 50%; - -webkit-transform: translateX(-50%); - transform: translateX(-50%); - width: 0; - height: 4px; - border-style: solid; - border-width: 8px 6px 4px; - border-color: #cacaca transparent transparent; - background: #f5f5f7 -} - -.node-wrap-box.start-node:before { - content: none -} - -.top-left-cover-line { - left: -1px -} - -.top-left-cover-line, -.top-right-cover-line { - position: absolute; - height: 8px; - width: 50%; - background-color: #f5f5f7; - top: -4px -} - -.top-right-cover-line { - right: -1px -} - -.bottom-left-cover-line { - left: -1px -} - -.bottom-left-cover-line, -.bottom-right-cover-line { - position: absolute; - height: 8px; - width: 50%; - background-color: #f5f5f7; - bottom: -4px -} - -.bottom-right-cover-line { - right: -1px -} - -.dingflow-design { - width: 100%; - background-color: #f5f5f7; - overflow: auto; - position: absolute; - bottom: 0; - left: 0; - right: 0; - top: 0 -} - -.dingflow-design .box-scale { - transform: scale(1); - display: inline-block; - position: relative; - width: 100%; - padding: 54.5px 0; - -webkit-box-align: start; - -ms-flex-align: start; - align-items: flex-start; - -webkit-box-pack: center; - -ms-flex-pack: center; - justify-content: center; - -ms-flex-wrap: wrap; - flex-wrap: wrap; - min-width: -webkit-min-content; - min-width: -moz-min-content; - min-width: min-content; - background-color: #f5f5f7; - transform-origin: 50% 0px 0px; -} - -.dingflow-design .node-wrap { - flex-direction: column; - -webkit-box-pack: start; - -ms-flex-pack: start; - justify-content: flex-start; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - -ms-flex-wrap: wrap; - flex-wrap: wrap; - -webkit-box-flex: 1; - -ms-flex-positive: 1; - padding: 0 50px; - position: relative -} - -.dingflow-design .branch-wrap, -.dingflow-design .node-wrap { - display: inline-flex; - width: 100% -} - -.dingflow-design .branch-box-wrap { - display: flex; - -webkit-box-orient: vertical; - -webkit-box-direction: normal; - -ms-flex-direction: column; - flex-direction: column; - -ms-flex-wrap: wrap; - flex-wrap: wrap; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - min-height: 270px; - width: 100%; - -ms-flex-negative: 0; - flex-shrink: 0 -} - -.dingflow-design .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 -} - -.dingflow-design .branch-box .col-box { - background: #f5f5f7 -} - -.dingflow-design .branch-box .col-box:before { - content: ""; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - z-index: 0; - margin: auto; - width: 2px; - height: 100%; - background-color: #cacaca -} - -.dingflow-design .add-branch { - border: none; - outline: none; - user-select: none; - justify-content: center; - font-size: 12px; - padding: 0 10px; - height: 30px; - line-height: 30px; - border-radius: 15px; - color: #3296fa; - background: #fff; - box-shadow: 0 2px 4px 0 rgba(0, 0, 0, .1); - position: absolute; - top: -16px; - left: 50%; - transform: translateX(-50%); - transform-origin: center center; - cursor: pointer; - z-index: 1; - display: inline-flex; - align-items: center; - -webkit-transition: all .3s cubic-bezier(.645, .045, .355, 1); - transition: all .3s cubic-bezier(.645, .045, .355, 1) -} - -.dingflow-design .add-branch:hover { - transform: translateX(-50%) scale(1.1); - box-shadow: 0 8px 16px 0 rgba(0, 0, 0, .1) -} - -.dingflow-design .add-branch:active { - transform: translateX(-50%); - box-shadow: none -} - -.dingflow-design .col-box { - display: inline-flex; - -webkit-box-orient: vertical; - -webkit-box-direction: normal; - flex-direction: column; - -webkit-box-align: center; - align-items: center; - position: relative -} - -.dingflow-design .condition-node { - min-height: 220px -} - -.dingflow-design .condition-node, -.dingflow-design .condition-node-box { - display: inline-flex; - -webkit-box-orient: vertical; - -webkit-box-direction: normal; - flex-direction: column; - -webkit-box-flex: 1 -} - -.dingflow-design .condition-node-box { - padding-top: 30px; - padding-right: 50px; - padding-left: 50px; - -webkit-box-pack: center; - justify-content: center; - -webkit-box-align: center; - align-items: center; - flex-grow: 1; - position: relative -} - -.dingflow-design .condition-node-box:before { - content: ""; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - margin: auto; - width: 2px; - height: 100%; - background-color: #cacaca -} - -.dingflow-design .auto-judge { - position: relative; - width: 220px; - min-height: 72px; - background: #fff; - border-radius: 4px; - padding: 14px 19px; - cursor: pointer -} - -.dingflow-design .auto-judge:after { - pointer-events: none; - content: ""; - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - z-index: 2; - border-radius: 4px; - border: 1px solid transparent; - transition: all .1s cubic-bezier(.645, .045, .355, 1); - box-shadow: 0 2px 5px 0 rgba(0, 0, 0, .1) -} - -.dingflow-design .auto-judge.active:after, -.dingflow-design .auto-judge:active:after, -.dingflow-design .auto-judge:hover:after { - border: 1px solid #3296fa; - box-shadow: 0 0 6px 0 rgba(50, 150, 250, .3) -} - -.dingflow-design .auto-judge.active .close, -.dingflow-design .auto-judge:active .close, -.dingflow-design .auto-judge:hover .close { - display: block -} - -.dingflow-design .auto-judge.error:after { - border: 1px solid #f25643; - box-shadow: 0 2px 5px 0 rgba(0, 0, 0, .1) -} - -.dingflow-design .auto-judge .title-wrapper { - position: relative; - font-size: 12px; - color: #15bc83; - text-align: left; - line-height: 16px -} - -.dingflow-design .auto-judge .title-wrapper .editable-title { - display: inline-block; - max-width: 120px; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis -} - -.dingflow-design .auto-judge .title-wrapper .priority-title { - display: inline-block; - float: right; - margin-right: 10px; - color: rgba(25, 31, 37, .56) -} - -.dingflow-design .auto-judge .placeholder { - color: #bfbfbf -} - -.dingflow-design .auto-judge .close { - display: none; - position: absolute; - right: -10px; - top: -10px; - width: 20px; - height: 20px; - font-size: 14px; - color: rgba(0, 0, 0, .25); - border-radius: 50%; - text-align: center; - line-height: 20px; - z-index: 2 -} - -.dingflow-design .auto-judge .content { - font-size: 14px; - color: #191f25; - text-align: left; - margin-top: 6px; - overflow: hidden; - text-overflow: ellipsis; - display: -webkit-box; - -webkit-line-clamp: 3; - -webkit-box-orient: vertical -} - -.dingflow-design .auto-judge .sort-left, -.dingflow-design .auto-judge .sort-right { - position: absolute; - top: 0; - bottom: 0; - display: none; - z-index: 1 -} - -.dingflow-design .auto-judge .sort-left { - left: 0; - border-right: 1px solid #f6f6f6 -} - -.dingflow-design .auto-judge .sort-right { - right: 0; - border-left: 1px solid #f6f6f6 -} - -.dingflow-design .auto-judge:hover .sort-left, -.dingflow-design .auto-judge:hover .sort-right { - display: flex; - align-items: center -} - -.dingflow-design .auto-judge .sort-left:hover, -.dingflow-design .auto-judge .sort-right:hover { - background: #efefef -} - -.dingflow-design .end-node { - border-radius: 50%; - font-size: 14px; - color: rgba(25, 31, 37, .4); - text-align: left -} - -.dingflow-design .end-node .end-node-circle { - width: 10px; - height: 10px; - margin: auto; - border-radius: 50%; - background: #dbdcdc -} - -.dingflow-design .end-node .end-node-text { - margin-top: 5px; - text-align: center -} - -.approval-setting { - border-radius: 2px; - margin: 20px 0; - position: relative; - background: #fff -} - -.ant-btn { - position: relative -} - - diff --git a/src/components/SimpleProcessDesignerV2/src/NodeHandler.vue b/src/components/SimpleProcessDesignerV2/src/NodeHandler.vue new file mode 100644 index 0000000000000000000000000000000000000000..629031b835d675b3e637e4cef349a7fa6a4a75fd --- /dev/null +++ b/src/components/SimpleProcessDesignerV2/src/NodeHandler.vue @@ -0,0 +1,168 @@ + + + + + + + + + + 审批人 + + + + + + 抄送 + + + + + + 条件分支 + + + + + + 并行分支 + + + + + + + + + + + + + diff --git a/src/components/SimpleProcessDesignerV2/src/ProcessNodeTree.vue b/src/components/SimpleProcessDesignerV2/src/ProcessNodeTree.vue new file mode 100644 index 0000000000000000000000000000000000000000..be2bc955e0ecc9a4aeea6893c49823f306790105 --- /dev/null +++ b/src/components/SimpleProcessDesignerV2/src/ProcessNodeTree.vue @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/components/SimpleProcessDesignerV2/src/SimpleProcessDesigner.vue b/src/components/SimpleProcessDesignerV2/src/SimpleProcessDesigner.vue new file mode 100644 index 0000000000000000000000000000000000000000..4da5deb2b2a5d32341f9ad3eb774800e770416c2 --- /dev/null +++ b/src/components/SimpleProcessDesignerV2/src/SimpleProcessDesigner.vue @@ -0,0 +1,215 @@ + + + + + + + + + {{ scaleValue }}% + + + + 保存 + + + + + + + + + 以下节点内容不完善,请修改后保存 + + {{ item.name }} : {{ NODE_DEFAULT_TEXT.get(item.type) }} + + + 知道了 + + + + + + diff --git a/src/components/SimpleProcessDesignerV2/src/consts.ts b/src/components/SimpleProcessDesignerV2/src/consts.ts new file mode 100644 index 0000000000000000000000000000000000000000..0364c5e64efc3054435ad4b0029c58a4d90e04dd --- /dev/null +++ b/src/components/SimpleProcessDesignerV2/src/consts.ts @@ -0,0 +1,544 @@ +// @ts-ignore +import { DictDataVO } from '@/api/system/dict/types' + +/** + * 节点类型 + */ +export enum NodeType { + /** + * 结束节点 + */ + END_EVENT_NODE = 1, + /** + * 发起人节点 + */ + START_USER_NODE = 10, + /** + * 审批人节点 + */ + USER_TASK_NODE = 11, + + /** + * 抄送人节点 + */ + COPY_TASK_NODE = 12, + + /** + * 条件节点 + */ + CONDITION_NODE = 50, + /** + * 条件分支节点 (对应排他网关) + */ + CONDITION_BRANCH_NODE = 51, + /** + * 并行分支节点 (对应并行网关) + */ + PARALLEL_BRANCH_NODE = 52, + + /** + * 包容分支节点 (对应包容网关) + */ + INCLUSIVE_BRANCH_NODE = 53 +} + +export enum NodeId { + /** + * 发起人节点 Id + */ + START_USER_NODE_ID = 'StartUserNode', + + /** + * 发起人节点 Id + */ + END_EVENT_NODE_ID = 'EndEvent' +} + +/** + * 节点结构定义 + */ +export interface SimpleFlowNode { + id: string + type: NodeType + name: string + showText?: string + // 孩子节点 + childNode?: SimpleFlowNode + // 条件节点 + conditionNodes?: SimpleFlowNode[] + // 审批类型 + approveType?: ApproveType + // 候选人策略 + candidateStrategy?: number + // 候选人参数 + candidateParam?: string + // 多人审批方式 + approveMethod?: ApproveMethodType + //通过比例 + approveRatio?: number + // 审批按钮设置 + buttonsSetting?: any[] + // 表单权限 + fieldsPermission?: Array> + // 审批任务超时处理 + timeoutHandler?: TimeoutHandler + // 审批任务拒绝处理 + rejectHandler?: RejectHandler + // 审批人为空的处理 + assignEmptyHandler?: AssignEmptyHandler + // 审批节点的审批人与发起人相同时,对应的处理类型 + assignStartUserHandlerType?: number + // 条件类型 + conditionType?: ConditionType + // 条件表达式 + conditionExpression?: string + // 条件组 + conditionGroups?: ConditionGroup + // 是否默认的条件 + defaultFlow?: boolean + +} +// 候选人策略枚举 ( 用于审批节点。抄送节点 ) +export enum CandidateStrategy { + /** + * 指定角色 + */ + ROLE = 10, + /** + * 部门成员 + */ + DEPT_MEMBER = 20, + /** + * 部门的负责人 + */ + DEPT_LEADER = 21, + /** + * 连续多级部门的负责人 + */ + MULTI_LEVEL_DEPT_LEADER = 23, + /** + * 指定岗位 + */ + POST = 22, + /** + * 指定用户 + */ + USER = 30, + /** + * 发起人自选 + */ + START_USER_SELECT = 35, + /** + * 发起人自己 + */ + START_USER = 36, + /** + * 发起人部门负责人 + */ + START_USER_DEPT_LEADER = 37, + /** + * 发起人连续多级部门的负责人 + */ + START_USER_MULTI_LEVEL_DEPT_LEADER = 38, + /** + * 指定用户组 + */ + USER_GROUP = 40, + /** + * 流程表达式 + */ + EXPRESSION = 60 +} + +// 多人审批方式类型枚举 ( 用于审批节点 ) +export enum ApproveMethodType { + /** + * 随机挑选一人审批 + */ + RANDOM_SELECT_ONE_APPROVE = 1, + + /** + * 多人会签(按通过比例) + */ + APPROVE_BY_RATIO = 2, + + /** + * 多人或签(通过只需一人,拒绝只需一人) + */ + ANY_APPROVE = 3, + /** + * 多人依次审批 + */ + SEQUENTIAL_APPROVE = 4 +} + +/** + * 审批拒绝结构定义 + */ +export type RejectHandler = { + // 审批拒绝类型 + type: RejectHandlerType + // 回退节点 Id + returnNodeId?: string +} + +/** + * 审批超时结构定义 + */ +export type TimeoutHandler = { + // 是否开启超时处理 + enable: boolean + // 超时执行的动作 + type?: number + // 超时时间设置 + timeDuration?: string + // 执行动作是自动提醒, 最大提醒次数 + maxRemindCount?: number +} + +/** + * 审批人为空的结构定义 + */ +export type AssignEmptyHandler = { + // 审批人为空的处理类型 + type: AssignEmptyHandlerType + // 指定用户的编号数组 + userIds?: number[] +} + +// 审批拒绝类型枚举 +export enum RejectHandlerType { + /** + * 结束流程 + */ + FINISH_PROCESS = 1, + /** + * 驳回到指定节点 + */ + RETURN_USER_TASK = 2 +} +// 用户任务超时处理类型枚举 +export enum TimeoutHandlerType { + /** + * 自动提醒 + */ + REMINDER = 1, + /** + * 自动同意 + */ + APPROVE = 2, + /** + * 自动拒绝 + */ + REJECT = 3 +} +// 用户任务的审批人为空时,处理类型枚举 +export enum AssignEmptyHandlerType { + /** + * 自动通过 + */ + APPROVE = 1, + /** + * 自动拒绝 + */ + REJECT = 2, + /** + * 指定人员审批 + */ + ASSIGN_USER, + /** + * 转交给流程管理员 + */ + ASSIGN_ADMIN = 4 +} +// 用户任务的审批人与发起人相同时,处理类型枚举 +export enum AssignStartUserHandlerType { + /** + * 由发起人对自己审批 + */ + START_USER_AUDIT = 1, + /** + * 自动跳过【参考飞书】:1)如果当前节点还有其他审批人,则交由其他审批人进行审批;2)如果当前节点没有其他审批人,则该节点自动通过 + */ + SKIP = 2, + /** + * 转交给部门负责人审批 + */ + ASSIGN_DEPT_LEADER = 3 +} + +// 用户任务的审批类型。 【参考飞书】 +export enum ApproveType { + /** + * 人工审批 + */ + USER = 1, + /** + * 自动通过 + */ + AUTO_APPROVE = 2, + /** + * 自动拒绝 + */ + AUTO_REJECT = 3 +} + +// 时间单位枚举 +export enum TimeUnitType { + /** + * 分钟 + */ + MINUTE = 1, + /** + * 小时 + */ + HOUR = 2, + /** + * 天 + */ + DAY = 3 +} + +// 条件配置类型 ( 用于条件节点配置 ) +export enum ConditionType { + /** + * 条件表达式 + */ + EXPRESSION = 1, + + /** + * 条件规则 + */ + RULE = 2 +} +/** + * 表单权限的枚举 + */ +export enum FieldPermissionType { + /** + * 只读 + */ + READ = '1', + /** + * 编辑 + */ + WRITE = '2', + /** + * 隐藏 + */ + NONE = '3' +} +/** + * 操作按钮权限结构定义 + */ +export type ButtonSetting = { + id: OperationButtonType + displayName: string + enable: boolean +} + +// 操作按钮类型枚举 (用于审批节点) +export enum OperationButtonType { + /** + * 通过 + */ + APPROVE = 1, + /** + * 拒绝 + */ + REJECT = 2, + /** + * 转办 + */ + TRANSFER = 3, + /** + * 委派 + */ + DELEGATE = 4, + /** + * 加签 + */ + ADD_SIGN = 5, + /** + * 回退 + */ + RETURN = 6 +} + +/** + * 条件规则结构定义 + */ +export type ConditionRule = { + type: number + opName: string + opCode: string + leftSide: string + rightSide: string +} + +/** + * 条件组结构定义 + */ +export type ConditionGroup = { + // 条件组的逻辑关系是否为且 + and: boolean + // 条件数组 + conditions: Condition[] +} + +/** + * 条件结构定义 + */ +export type Condition = { + // 条件规则的逻辑关系是否为且 + and: boolean + rules: ConditionRule[] +} + +export const NODE_DEFAULT_TEXT = new Map() +NODE_DEFAULT_TEXT.set(NodeType.USER_TASK_NODE, '请配置审批人') +NODE_DEFAULT_TEXT.set(NodeType.COPY_TASK_NODE, '请配置抄送人') +NODE_DEFAULT_TEXT.set(NodeType.CONDITION_NODE, '请设置条件') +NODE_DEFAULT_TEXT.set(NodeType.START_USER_NODE, '请设置发起人') + +export const NODE_DEFAULT_NAME = new Map() +NODE_DEFAULT_NAME.set(NodeType.USER_TASK_NODE, '审批人') +NODE_DEFAULT_NAME.set(NodeType.COPY_TASK_NODE, '抄送人') +NODE_DEFAULT_NAME.set(NodeType.CONDITION_NODE, '条件') +NODE_DEFAULT_NAME.set(NodeType.START_USER_NODE, '发起人') + +// 候选人策略。暂时不从字典中取。 后续可能调整。控制显示顺序 +export const CANDIDATE_STRATEGY: DictDataVO[] = [ + { label: '指定成员', value: CandidateStrategy.USER }, + { label: '指定角色', value: CandidateStrategy.ROLE }, + { label: '部门成员', value: CandidateStrategy.DEPT_MEMBER }, + { label: '部门负责人', value: CandidateStrategy.DEPT_LEADER }, + { label: '连续多级部门负责人', value: CandidateStrategy.MULTI_LEVEL_DEPT_LEADER }, + { label: '发起人自选', value: CandidateStrategy.START_USER_SELECT }, + { label: '发起人本人', value: CandidateStrategy.START_USER }, + { label: '发起人部门负责人', value: CandidateStrategy.START_USER_DEPT_LEADER }, + { label: '发起人连续部门负责人', value: CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER }, + { label: '用户组', value: CandidateStrategy.USER_GROUP }, + { label: '流程表达式', value: CandidateStrategy.EXPRESSION } +] +// 审批节点 的审批类型 +export const APPROVE_TYPE: DictDataVO[] = [ + { label: '人工审批', value: ApproveType.USER }, + { label: '自动通过', value: ApproveType.AUTO_APPROVE }, + { label: '自动拒绝', value: ApproveType.AUTO_REJECT } +] + +export const APPROVE_METHODS: DictDataVO[] = [ + { label: '按顺序依次审批', value: ApproveMethodType.SEQUENTIAL_APPROVE }, + { label: '会签(可同时审批,至少 % 人必须审批通过)', value: ApproveMethodType.APPROVE_BY_RATIO }, + { label: '或签(可同时审批,有一人通过即可)', value: ApproveMethodType.ANY_APPROVE }, + { label: '随机挑选一人审批', value: ApproveMethodType.RANDOM_SELECT_ONE_APPROVE } +] + +export const CONDITION_CONFIG_TYPES: DictDataVO[] = [ + { label: '条件表达式', value: ConditionType.EXPRESSION }, + { label: '条件规则', value: ConditionType.RULE } +] + +// 时间单位类型 +export const TIME_UNIT_TYPES: DictDataVO[] = [ + { label: '分钟', value: TimeUnitType.MINUTE }, + { label: '小时', value: TimeUnitType.HOUR }, + { label: '天', value: TimeUnitType.DAY } +] +// 超时处理执行动作类型 +export const TIMEOUT_HANDLER_TYPES: DictDataVO[] = [ + { label: '自动提醒', value: 1 }, + { label: '自动同意', value: 2 }, + { label: '自动拒绝', value: 3 } +] +export const REJECT_HANDLER_TYPES: DictDataVO[] = [ + { label: '终止流程', value: RejectHandlerType.FINISH_PROCESS }, + { label: '驳回到指定节点', value: RejectHandlerType.RETURN_USER_TASK } + // { label: '结束任务', value: RejectHandlerType.FINISH_TASK } +] +export const ASSIGN_EMPTY_HANDLER_TYPES: DictDataVO[] = [ + { label: '自动通过', value: 1 }, + { label: '自动拒绝', value: 2 }, + { label: '指定成员审批', value: 3 }, + { label: '转交给流程管理员', value: 4 } +] +export const ASSIGN_START_USER_HANDLER_TYPES: DictDataVO[] = [ + { label: '由发起人对自己审批', value: 1 }, + { label: '自动跳过', value: 2 }, + { label: '转交给部门负责人审批', value: 3 } +] + +// 比较运算符 +export const COMPARISON_OPERATORS: DictDataVO = [ + { + value: '==', + label: '等于' + }, + { + value: '!=', + label: '不等于' + }, + { + value: '>', + label: '大于' + }, + { + value: '>=', + label: '大于等于' + }, + { + value: '<', + label: '小于' + }, + { + value: '<=', + label: '小于等于' + } +] +// 审批操作按钮名称 +export const OPERATION_BUTTON_NAME = new Map() +OPERATION_BUTTON_NAME.set(OperationButtonType.APPROVE, '通过') +OPERATION_BUTTON_NAME.set(OperationButtonType.REJECT, '拒绝') +OPERATION_BUTTON_NAME.set(OperationButtonType.TRANSFER, '转办') +OPERATION_BUTTON_NAME.set(OperationButtonType.DELEGATE, '委派') +OPERATION_BUTTON_NAME.set(OperationButtonType.ADD_SIGN, '加签') +OPERATION_BUTTON_NAME.set(OperationButtonType.RETURN, '回退') + +// 默认的按钮权限设置 +export const DEFAULT_BUTTON_SETTING: ButtonSetting[] = [ + { id: OperationButtonType.APPROVE, displayName: '通过', enable: true }, + { id: OperationButtonType.REJECT, displayName: '拒绝', enable: true }, + { id: OperationButtonType.TRANSFER, displayName: '转办', enable: false }, + { id: OperationButtonType.DELEGATE, displayName: '委派', enable: false }, + { id: OperationButtonType.ADD_SIGN, displayName: '加签', enable: false }, + { id: OperationButtonType.RETURN, displayName: '回退', enable: false } +] + +// 发起人的按钮权限。暂时定死,不可以编辑 +export const START_USER_BUTTON_SETTING: ButtonSetting[] = [ + { id: OperationButtonType.APPROVE, displayName: '提交', enable: true }, + { id: OperationButtonType.REJECT, displayName: '拒绝', enable: false }, + { id: OperationButtonType.TRANSFER, displayName: '转办', enable: false }, + { id: OperationButtonType.DELEGATE, displayName: '委派', enable: false }, + { id: OperationButtonType.ADD_SIGN, displayName: '加签', enable: false }, + { id: OperationButtonType.RETURN, displayName: '回退', enable: false } +] + +export const MULTI_LEVEL_DEPT: DictDataVO = [ + { label: '第 1 级部门', 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 }, + { label: '第 11 级部门', value: 11 }, + { label: '第 12 级部门', value: 12 }, + { label: '第 13 级部门', value: 13 }, + { label: '第 14 级部门', value: 14 }, + { label: '第 15 级部门', value: 15 } +] diff --git a/src/components/SimpleProcessDesignerV2/src/index.ts b/src/components/SimpleProcessDesignerV2/src/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..a53dcf38d692df19cc9d6de92f12cc0c7f618bba --- /dev/null +++ b/src/components/SimpleProcessDesignerV2/src/index.ts @@ -0,0 +1,4 @@ +import SimpleProcessDesigner from './SimpleProcessDesigner.vue' +import '../theme/simple-process-designer.scss' + +export { SimpleProcessDesigner } \ No newline at end of file diff --git a/src/components/SimpleProcessDesignerV2/src/node.ts b/src/components/SimpleProcessDesignerV2/src/node.ts new file mode 100644 index 0000000000000000000000000000000000000000..816d9b1924706bb920f4a4909af7637bcdfa9500 --- /dev/null +++ b/src/components/SimpleProcessDesignerV2/src/node.ts @@ -0,0 +1,478 @@ +import { cloneDeep } from 'lodash-es' +import * as RoleApi from '@/api/system/role' +import * as DeptApi from '@/api/system/dept' +import * as PostApi from '@/api/system/post' +import * as UserApi from '@/api/system/user' +import * as UserGroupApi from '@/api/bpm/userGroup' +import { + SimpleFlowNode, + CandidateStrategy, + NodeType, + ApproveMethodType, + RejectHandlerType, + NODE_DEFAULT_NAME, + AssignStartUserHandlerType, + AssignEmptyHandlerType, + FieldPermissionType +} from './consts' +export function useWatchNode(props: { flowNode: SimpleFlowNode }): Ref { + const node = ref(props.flowNode) + watch( + () => props.flowNode, + (newValue) => { + node.value = newValue + } + ) + return node +} + +/** + * @description 表单数据权限配置,用于发起人节点 、审批节点、抄送节点 + */ +export function useFormFieldsPermission(defaultPermission: FieldPermissionType) { + // 字段权限配置. 需要有 field, title, permissioin 属性 + const fieldsPermissionConfig = ref>>([]) + + const formType = inject>('formType') // 表单类型 + + const formFields = inject>('formFields') // 流程表单字段 + + const getNodeConfigFormFields = (nodeFormFields?: Array>) => { + nodeFormFields = toRaw(nodeFormFields) + fieldsPermissionConfig.value = + cloneDeep(nodeFormFields) || getDefaultFieldsPermission(unref(formFields)) + } + // 默认的表单权限: 获取表单的所有字段,设置字段默认权限为只读 + const getDefaultFieldsPermission = (formFields?: string[]) => { + const defaultFieldsPermission: Array> = [] + if (formFields) { + formFields.forEach((fieldStr: string) => { + parseFieldsSetDefaultPermission(JSON.parse(fieldStr), defaultFieldsPermission) + }) + } + return defaultFieldsPermission + } + // 解析字段。赋给默认权限 + const parseFieldsSetDefaultPermission = ( + rule: Record, + fieldsPermission: Array>, + parentTitle: string = '' + ) => { + const { /**type,*/ field, title: tempTitle, children } = rule + if (field && tempTitle) { + let title = tempTitle + if (parentTitle) { + title = `${parentTitle}.${tempTitle}` + } + fieldsPermission.push({ + field, + title, + permission: defaultPermission + }) + // TODO 子表单 需要处理子表单字段 + // if (type === 'group' && rule.props?.rule && Array.isArray(rule.props.rule)) { + // // 解析子表单的字段 + // rule.props.rule.forEach((item) => { + // parseFieldsSetDefaultPermission(item, fieldsPermission, title) + // }) + // } + } + if (children && Array.isArray(children)) { + children.forEach((rule) => { + parseFieldsSetDefaultPermission(rule, fieldsPermission) + }) + } + } + + return { + formType, + fieldsPermissionConfig, + getNodeConfigFormFields + } +} +/** + * @description 获取表单的字段 + */ +export function useFormFields() { + // 解析后的表单字段 + const formFields = inject>('formFields') // 流程表单字段 + const parseFormFields = () => { + const parsedFormFields: Array> = [] + if (formFields) { + formFields.value.forEach((fieldStr: string) => { + parseField(JSON.parse(fieldStr), parsedFormFields) + }) + } + return parsedFormFields + } + // 解析字段。 + const parseField = ( + rule: Record, + parsedFormFields: Array>, + parentTitle: string = '' + ) => { + const { field, title: tempTitle, children, type } = rule + if (field && tempTitle) { + let title = tempTitle + if (parentTitle) { + title = `${parentTitle}.${tempTitle}` + } + parsedFormFields.push({ + field, + title, + type + }) + // TODO 子表单 需要处理子表单字段 + // if (type === 'group' && rule.props?.rule && Array.isArray(rule.props.rule)) { + // // 解析子表单的字段 + // rule.props.rule.forEach((item) => { + // parseFieldsSetDefaultPermission(item, fieldsPermission, title) + // }) + // } + } + if (children && Array.isArray(children)) { + children.forEach((rule) => { + parseField(rule, parsedFormFields) + }) + } + } + + return parseFormFields() +} + +export type UserTaskFormType = { + //candidateParamArray: any[] + candidateStrategy: CandidateStrategy + approveMethod: ApproveMethodType + roleIds?: number[] // 角色 + deptIds?: number[] // 部门 + deptLevel?: number // 部门层级 + userIds?: number[] // 用户 + userGroups?: number[] // 用户组 + postIds?: number[] // 岗位 + expression?: string // 流程表达式 + approveRatio?: number + rejectHandlerType?: RejectHandlerType + returnNodeId?: string + timeoutHandlerEnable?: boolean + timeoutHandlerType?: number + assignEmptyHandlerType?: AssignEmptyHandlerType + assignEmptyHandlerUserIds?: number[] + assignStartUserHandlerType?: AssignStartUserHandlerType + timeDuration?: number + maxRemindCount?: number + buttonsSetting: any[] +} + +export type CopyTaskFormType = { + // candidateParamArray: any[] + candidateStrategy: CandidateStrategy + roleIds?: number[] // 角色 + deptIds?: number[] // 部门 + deptLevel?: number // 部门层级 + userIds?: number[] // 用户 + userGroups?: number[] // 用户组 + postIds?: number[] // 岗位 + expression?: string // 流程表达式 +} + +/** + * @description 节点表单数据。 用于审批节点、抄送节点 + */ +export function useNodeForm(nodeType: NodeType) { + const roleOptions = inject>('roleList') // 角色列表 + const postOptions = inject>('postList') // 岗位列表 + const userOptions = inject>('userList') // 用户列表 + const deptOptions = inject>('deptList') // 部门列表 + const userGroupOptions = inject>('userGroupList') // 用户组列表 + const deptTreeOptions = inject('deptTree') // 部门树 + const configForm = ref() + if (nodeType === NodeType.USER_TASK_NODE) { + configForm.value = { + candidateStrategy: CandidateStrategy.USER, + approveMethod: ApproveMethodType.SEQUENTIAL_APPROVE, + approveRatio: 100, + rejectHandlerType: RejectHandlerType.FINISH_PROCESS, + assignStartUserHandlerType: AssignStartUserHandlerType.START_USER_AUDIT, + returnNodeId: '', + timeoutHandlerEnable: false, + timeoutHandlerType: 1, + timeDuration: 6, // 默认 6小时 + maxRemindCount: 1, // 默认 提醒 1次 + buttonsSetting: [] + } + } else { + configForm.value = { + candidateStrategy: CandidateStrategy.USER + } + } + + const getShowText = (): string => { + let showText = '' + // 指定成员 + if (configForm.value?.candidateStrategy === CandidateStrategy.USER) { + if (configForm.value?.userIds!.length > 0) { + const candidateNames: string[] = [] + userOptions?.value.forEach((item) => { + if (configForm.value?.userIds!.includes(item.id)) { + candidateNames.push(item.nickname) + } + }) + showText = `指定成员:${candidateNames.join(',')}` + } + } + // 指定角色 + if (configForm.value?.candidateStrategy === CandidateStrategy.ROLE) { + if (configForm.value.roleIds!.length > 0) { + const candidateNames: string[] = [] + roleOptions?.value.forEach((item) => { + if (configForm.value?.roleIds!.includes(item.id)) { + candidateNames.push(item.name) + } + }) + showText = `指定角色:${candidateNames.join(',')}` + } + } + // 指定部门 + if ( + configForm.value?.candidateStrategy === CandidateStrategy.DEPT_MEMBER || + configForm.value?.candidateStrategy === CandidateStrategy.DEPT_LEADER || + configForm.value?.candidateStrategy === CandidateStrategy.MULTI_LEVEL_DEPT_LEADER + ) { + if (configForm.value?.deptIds!.length > 0) { + const candidateNames: string[] = [] + deptOptions?.value.forEach((item) => { + if (configForm.value?.deptIds!.includes(item.id!)) { + candidateNames.push(item.name) + } + }) + if (configForm.value.candidateStrategy === CandidateStrategy.DEPT_MEMBER) { + showText = `部门成员:${candidateNames.join(',')}` + } else if (configForm.value.candidateStrategy === CandidateStrategy.DEPT_LEADER) { + showText = `部门的负责人:${candidateNames.join(',')}` + } else { + showText = `多级部门的负责人:${candidateNames.join(',')}` + } + } + } + + // 指定岗位 + if (configForm.value?.candidateStrategy === CandidateStrategy.POST) { + if (configForm.value.postIds!.length > 0) { + const candidateNames: string[] = [] + postOptions?.value.forEach((item) => { + if (configForm.value?.postIds!.includes(item.id!)) { + candidateNames.push(item.name) + } + }) + showText = `指定岗位: ${candidateNames.join(',')}` + } + } + // 指定用户组 + if (configForm.value?.candidateStrategy === CandidateStrategy.USER_GROUP) { + if (configForm.value?.userGroups!.length > 0) { + const candidateNames: string[] = [] + userGroupOptions?.value.forEach((item) => { + if (configForm.value?.userGroups!.includes(item.id)) { + candidateNames.push(item.name) + } + }) + showText = `指定用户组: ${candidateNames.join(',')}` + } + } + + // 发起人自选 + if (configForm.value?.candidateStrategy === CandidateStrategy.START_USER_SELECT) { + showText = `发起人自选` + } + // 发起人自己 + if (configForm.value?.candidateStrategy === CandidateStrategy.START_USER) { + showText = `发起人自己` + } + // 发起人的部门负责人 + if (configForm.value?.candidateStrategy === CandidateStrategy.START_USER_DEPT_LEADER) { + showText = `发起人的部门负责人` + } + // 发起人的部门负责人 + if ( + configForm.value?.candidateStrategy === CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER + ) { + showText = `发起人连续部门负责人` + } + // 流程表达式 + if (configForm.value?.candidateStrategy === CandidateStrategy.EXPRESSION) { + showText = `流程表达式:${configForm.value.expression}` + } + return showText + } + + /** + * 处理候选人参数的赋值 + */ + const handleCandidateParam = () => { + let candidateParam: undefined | string = undefined + if (!configForm.value) { + return candidateParam + } + switch (configForm.value.candidateStrategy) { + case CandidateStrategy.USER: + candidateParam = configForm.value.userIds!.join(',') + break + case CandidateStrategy.ROLE: + candidateParam = configForm.value.roleIds!.join(',') + break + case CandidateStrategy.POST: + candidateParam = configForm.value.postIds!.join(',') + break + case CandidateStrategy.USER_GROUP: + candidateParam = configForm.value.userGroups!.join(',') + break + case CandidateStrategy.EXPRESSION: + candidateParam = configForm.value.expression! + break + case CandidateStrategy.DEPT_MEMBER: + case CandidateStrategy.DEPT_LEADER: + candidateParam = configForm.value.deptIds!.join(',') + break + // 发起人部门负责人 + case CandidateStrategy.START_USER_DEPT_LEADER: + case CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER: + candidateParam = configForm.value.deptLevel + '' + break + // 指定连续多级部门的负责人 + case CandidateStrategy.MULTI_LEVEL_DEPT_LEADER: { + // 候选人参数格式: | 分隔 。左边为部门(多个部门用 , 分隔)。 右边为部门层级 + const deptIds = configForm.value.deptIds!.join(',') + candidateParam = deptIds.concat('|' + configForm.value.deptLevel + '') + break + } + default: + break + } + return candidateParam + } + /** + * 解析候选人参数 + */ + const parseCandidateParam = ( + candidateStrategy: CandidateStrategy, + candidateParam: string | undefined + ) => { + if (!configForm.value || !candidateParam) { + return + } + switch (candidateStrategy) { + case CandidateStrategy.USER: { + configForm.value.userIds = candidateParam.split(',').map((item) => +item) + break + } + case CandidateStrategy.ROLE: + configForm.value.roleIds = candidateParam.split(',').map((item) => +item) + break + case CandidateStrategy.POST: + configForm.value.postIds = candidateParam.split(',').map((item) => +item) + break + case CandidateStrategy.USER_GROUP: + configForm.value.userGroups = candidateParam.split(',').map((item) => +item) + break + case CandidateStrategy.EXPRESSION: + configForm.value.expression = candidateParam + break + case CandidateStrategy.DEPT_MEMBER: + case CandidateStrategy.DEPT_LEADER: + configForm.value.deptIds = candidateParam.split(',').map((item) => +item) + break + // 发起人部门负责人 + case CandidateStrategy.START_USER_DEPT_LEADER: + case CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER: + configForm.value.deptLevel = +candidateParam + break + // 指定连续多级部门的负责人 + case CandidateStrategy.MULTI_LEVEL_DEPT_LEADER: { + // 候选人参数格式: | 分隔 。左边为部门(多个部门用 , 分隔)。 右边为部门层级 + const paramArray = candidateParam.split('|') + configForm.value.deptIds = paramArray[0].split(',').map((item) => +item) + configForm.value.deptLevel = +paramArray[1] + break + } + default: + break + } + } + return { + configForm, + roleOptions, + postOptions, + userOptions, + userGroupOptions, + deptTreeOptions, + handleCandidateParam, + parseCandidateParam, + getShowText + } +} + +/** + * @description 抽屉配置 + */ +export function useDrawer() { + // 抽屉配置是否可见 + const settingVisible = ref(false) + // 关闭配置抽屉 + const closeDrawer = () => { + settingVisible.value = false + } + // 打开配置抽屉 + const openDrawer = () => { + settingVisible.value = true + } + return { + settingVisible, + closeDrawer, + openDrawer + } +} + +/** + * @description 节点名称配置 + */ +export function useNodeName(nodeType: NodeType) { + // 节点名称 + const nodeName = ref() + // 节点名称输入框 + const showInput = ref(false) + // 点击节点名称编辑图标 + const clickIcon = () => { + showInput.value = true + } + // 节点名称输入框失去焦点 + const blurEvent = () => { + showInput.value = false + nodeName.value = nodeName.value || (NODE_DEFAULT_NAME.get(nodeType) as string) + } + return { + nodeName, + showInput, + clickIcon, + blurEvent + } +} + +export function useNodeName2(node: Ref, nodeType: NodeType) { + // 显示节点名称输入框 + const showInput = ref(false) + // 节点名称输入框失去焦点 + const blurEvent = () => { + showInput.value = false + node.value.name = node.value.name || (NODE_DEFAULT_NAME.get(nodeType) as string) + } + // 点击节点标题进行输入 + const clickTitle = () => { + showInput.value = true + } + return { + showInput, + clickTitle, + blurEvent + } +} diff --git a/src/components/SimpleProcessDesignerV2/src/nodes-config/ConditionNodeConfig.vue b/src/components/SimpleProcessDesignerV2/src/nodes-config/ConditionNodeConfig.vue new file mode 100644 index 0000000000000000000000000000000000000000..3a193d0f607dacdbb8639f66e23050c4a8b988b6 --- /dev/null +++ b/src/components/SimpleProcessDesignerV2/src/nodes-config/ConditionNodeConfig.vue @@ -0,0 +1,419 @@ + + + + + + {{ currentNode.name }} + + + + + + + 其它条件不满足进入此分支(该分支不可编辑和删除) + + + + + + {{ dict.label }} + + + + + + + + + + + 条件组关系 + + + + + + + + + + + 条件组 + + 规则关系 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 确 定 + 取 消 + + + + + + + diff --git a/src/components/SimpleProcessDesignerV2/src/nodes-config/CopyTaskNodeConfig.vue b/src/components/SimpleProcessDesignerV2/src/nodes-config/CopyTaskNodeConfig.vue new file mode 100644 index 0000000000000000000000000000000000000000..af2d4989571667685eb5eac8c51d6d83fd21941b --- /dev/null +++ b/src/components/SimpleProcessDesignerV2/src/nodes-config/CopyTaskNodeConfig.vue @@ -0,0 +1,307 @@ + + + + + + + {{ nodeName }} + + + + + + + + + + + + {{ dict.label }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 字段权限 + + 字段名称 + + 只读 + 可编辑 + 隐藏 + + + + {{ item.title }} + + + + + + + + + + + + + + + + + + + 确 定 + 取 消 + + + + + + + diff --git a/src/components/SimpleProcessDesignerV2/src/nodes-config/StartUserNodeConfig.vue b/src/components/SimpleProcessDesignerV2/src/nodes-config/StartUserNodeConfig.vue new file mode 100644 index 0000000000000000000000000000000000000000..5bf64b4a2acfa5d06cc79f8c86b792be728661f5 --- /dev/null +++ b/src/components/SimpleProcessDesignerV2/src/nodes-config/StartUserNodeConfig.vue @@ -0,0 +1,136 @@ + + + + + + + {{ nodeName }} + + + + + + + 待实现 + + + + 字段权限 + + 字段名称 + + 只读 + 可编辑 + 隐藏 + + + + {{ item.title }} + + + + + + + + + + + + + + + + + + + 确 定 + 取 消 + + + + + + + diff --git a/src/components/SimpleProcessDesignerV2/src/nodes-config/UserTaskNodeConfig.vue b/src/components/SimpleProcessDesignerV2/src/nodes-config/UserTaskNodeConfig.vue new file mode 100644 index 0000000000000000000000000000000000000000..969cf48bc7df03b7a1b3907a6eba4f0bdc72ceb9 --- /dev/null +++ b/src/components/SimpleProcessDesignerV2/src/nodes-config/UserTaskNodeConfig.vue @@ -0,0 +1,901 @@ + + + + + + + {{ nodeName }} + + + + + + 审批类型 : + + + {{ item.label }} + + + + + + + + + + + {{ dict.label }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{ item.label }} + + + + + + + + + + 审批人拒绝时 + + + + + + + + + + + + + + + + 审批人超时未处理时 + + + + + + + + + + 当超过 + + + + + + + 未处理 + + + + + + 审批人为空时 + + + + + + + + + + + + + + + + 审批人与提交人为同一人时 + + + + + + + + + + + + + + + 操作按钮 + + 操作按钮 + 显示名称 + 启用 + + + {{ OPERATION_BUTTON_NAME.get(item.id) }} + + + {{ item.displayName }} + + + + + + + + + + 字段权限 + + 字段名称 + + 只读 + 可编辑 + 隐藏 + + + + {{ item.title }} + + + + + + + + + + + + + + + + + + + 确 定 + 取 消 + + + + + + + + diff --git a/src/components/SimpleProcessDesignerV2/src/nodes/CopyTaskNode.vue b/src/components/SimpleProcessDesignerV2/src/nodes/CopyTaskNode.vue new file mode 100644 index 0000000000000000000000000000000000000000..14a85a295e273115ad44a6cdb5dc02b1b07518d8 --- /dev/null +++ b/src/components/SimpleProcessDesignerV2/src/nodes/CopyTaskNode.vue @@ -0,0 +1,79 @@ + + + + + + + + + {{ currentNode.name }} + + + + + {{ currentNode.showText }} + + + {{ NODE_DEFAULT_TEXT.get(NodeType.COPY_TASK_NODE) }} + + + + + + + + + + + + + + + + + diff --git a/src/components/SimpleProcessDesignerV2/src/nodes/EndEventNode.vue b/src/components/SimpleProcessDesignerV2/src/nodes/EndEventNode.vue new file mode 100644 index 0000000000000000000000000000000000000000..3278e7b8c98d50213b8a781633122e35b578626e --- /dev/null +++ b/src/components/SimpleProcessDesignerV2/src/nodes/EndEventNode.vue @@ -0,0 +1,13 @@ + + + + 结束 + + + + + diff --git a/src/components/SimpleProcessDesignerV2/src/nodes/ExclusiveNode.vue b/src/components/SimpleProcessDesignerV2/src/nodes/ExclusiveNode.vue new file mode 100644 index 0000000000000000000000000000000000000000..385a81ff0646c834d577d124cf505e912f3d1979 --- /dev/null +++ b/src/components/SimpleProcessDesignerV2/src/nodes/ExclusiveNode.vue @@ -0,0 +1,207 @@ + + + + 添加条件 + + + + + + + + + + + + + + + + + {{ item.name }} + 优先级{{ index + 1 }} + + + + {{ item.showText }} + + + {{ NODE_DEFAULT_TEXT.get(NodeType.CONDITION_NODE) }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/SimpleProcessDesignerV2/src/nodes/ParallelNode.vue b/src/components/SimpleProcessDesignerV2/src/nodes/ParallelNode.vue new file mode 100644 index 0000000000000000000000000000000000000000..3cdd8f8fe5555969e1d315072f37c71825e7b001 --- /dev/null +++ b/src/components/SimpleProcessDesignerV2/src/nodes/ParallelNode.vue @@ -0,0 +1,181 @@ + + + + 添加分支 + + + + + + + + + + + + + + + + + {{ item.name }} + 无优先级 + + + + {{ item.showText }} + + + {{ NODE_DEFAULT_TEXT.get(NodeType.CONDITION_NODE) }} + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/SimpleProcessDesignerV2/src/nodes/StartUserNode.vue b/src/components/SimpleProcessDesignerV2/src/nodes/StartUserNode.vue new file mode 100644 index 0000000000000000000000000000000000000000..5111678161302e6a1cbf55818d215dbf942fea1c --- /dev/null +++ b/src/components/SimpleProcessDesignerV2/src/nodes/StartUserNode.vue @@ -0,0 +1,69 @@ + + + + + + + + + {{ currentNode.name }} + + + + + {{ currentNode.showText }} + + + {{ NODE_DEFAULT_TEXT.get(NodeType.START_USER_NODE) }} + + + + + + + + + + + + diff --git a/src/components/SimpleProcessDesignerV2/src/nodes/UserTaskNode.vue b/src/components/SimpleProcessDesignerV2/src/nodes/UserTaskNode.vue new file mode 100644 index 0000000000000000000000000000000000000000..c817cbe27e7b3ecd70c2245edb43c825b739fbcc --- /dev/null +++ b/src/components/SimpleProcessDesignerV2/src/nodes/UserTaskNode.vue @@ -0,0 +1,88 @@ + + + + + + + + + {{ currentNode.name }} + + + + + {{ currentNode.showText }} + + + {{ NODE_DEFAULT_TEXT.get(NodeType.USER_TASK_NODE) }} + + + + + + + + + + + + + + + diff --git a/src/components/SimpleProcessDesignerV2/src/utils.ts b/src/components/SimpleProcessDesignerV2/src/utils.ts new file mode 100644 index 0000000000000000000000000000000000000000..854d70324cf73343a42bc83999d0a32321226c12 --- /dev/null +++ b/src/components/SimpleProcessDesignerV2/src/utils.ts @@ -0,0 +1,33 @@ +import { TimeUnitType, ApproveType, APPROVE_TYPE } from './consts' + +// 获取条件节点默认的名称 +export const getDefaultConditionNodeName = (index: number, defaultFlow: boolean | undefined): string => { + if (defaultFlow) { + return '其它情况' + } + return '条件' + (index + 1) +} + +export const convertTimeUnit = (strTimeUnit: string) => { + if (strTimeUnit === 'M') { + return TimeUnitType.MINUTE + } + if (strTimeUnit === 'H') { + return TimeUnitType.HOUR + } + if (strTimeUnit === 'D') { + return TimeUnitType.DAY + } + return TimeUnitType.HOUR +} + +export const getApproveTypeText = (approveType: ApproveType): string => { + let approveTypeText = '' + APPROVE_TYPE.forEach((item) => { + if (item.value === approveType) { + approveTypeText = item.label + return + } + }) + return approveTypeText +} diff --git a/src/components/SimpleProcessDesignerV2/theme/iconfont.ttf b/src/components/SimpleProcessDesignerV2/theme/iconfont.ttf new file mode 100644 index 0000000000000000000000000000000000000000..bb85b35fc9a288c272108219197ba72fc40ccce9 Binary files /dev/null and b/src/components/SimpleProcessDesignerV2/theme/iconfont.ttf differ diff --git a/src/components/SimpleProcessDesignerV2/theme/iconfont.woff b/src/components/SimpleProcessDesignerV2/theme/iconfont.woff new file mode 100644 index 0000000000000000000000000000000000000000..94befbd1898e85a0687043275b23b0667e74fd29 Binary files /dev/null and b/src/components/SimpleProcessDesignerV2/theme/iconfont.woff differ diff --git a/src/components/SimpleProcessDesignerV2/theme/iconfont.woff2 b/src/components/SimpleProcessDesignerV2/theme/iconfont.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..e8f95c8c307663dec7a55cd4775adfc07a72de70 Binary files /dev/null and b/src/components/SimpleProcessDesignerV2/theme/iconfont.woff2 differ diff --git a/src/components/SimpleProcessDesignerV2/theme/simple-process-designer.scss b/src/components/SimpleProcessDesignerV2/theme/simple-process-designer.scss new file mode 100644 index 0000000000000000000000000000000000000000..fd2ab0edfa89afea44888f334a74bcdd377092d3 --- /dev/null +++ b/src/components/SimpleProcessDesignerV2/theme/simple-process-designer.scss @@ -0,0 +1,714 @@ +.simple-flow-canvas { + position: absolute; + inset: 0; + z-index: 1; + overflow: auto; + background-color: #fafafa; + user-select: none; + + .simple-flow-container { + position: relative; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + + .top-area-container { + position: sticky; + inset: 0; + display: flex; + width: 100%; + height: 42px; + z-index: 1; + // padding: 4px 0; + background-color: #fff; + justify-content: flex-end; + align-items: center; + + .top-actions { + display: flex; + margin: 4px; + margin-right: 8px; + align-items: center; + + .canvas-control { + font-size: 16px; + + .control-scale-group { + display: inline-flex; + align-items: center; + margin-right: 8px; + + .control-scale-button { + display: inline-flex; + width: 28px; + height: 28px; + padding: 2px; + text-align: center; + cursor: pointer; + justify-content: center; + align-items: center; + } + + .control-scale-label { + margin: 0 4px; + font-size: 14px; + } + } + } + } + } + + .scale-container { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + margin-top: 16px; + background-color: #fafafa; + transform-origin: 50% 0 0; + transform: scale(1); + transition: transform 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); + + // 节点容器 定义节点宽度 + .node-container { + width: 200px; + } + // 节点 + .node-box { + position: relative; + display: flex; + min-height: 70px; + padding: 5px 10px 8px; + cursor: pointer; + background-color: #fff; + flex-direction: column; + border: 2px solid transparent; + // border-color: #0089ff; + border-radius: 8px; + // border-color: #0089ff; + box-shadow: 0 1px 4px 0 rgba(10, 30, 65, 0.16); + transition: all 0.1s cubic-bezier(0.645, 0.045, 0.355, 1); + + &:hover { + border-color: #0089ff; + .node-toolbar { + opacity: 1; + } + + .branch-node-move { + display: flex; + } + } + + // 普通节点标题 + .node-title-container { + display: flex; + padding: 4px; + cursor: pointer; + border-radius: 4px 4px 0 0; + align-items: center; + + .node-title-icon { + display: flex; + align-items: center; + + &.user-task { + color: #ff943e; + } + &.copy-task { + color: #3296fa; + } + &.start-user { + color: #676565; + } + } + + .node-title { + margin-left: 4px; + font-size: 14px; + font-weight: 600; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + color: #1f1f1f; + line-height: 18px; + &:hover { + border-bottom: 1px dashed #f60; + } + } + } + + // 条件节点标题 + .branch-node-title-container { + display: flex; + padding: 4px 0; + cursor: pointer; + border-radius: 4px 4px 0 0; + align-items: center; + justify-content: space-between; + + .input-max-width { + max-width: 115px !important; + } + + .branch-title { + font-size: 13px; + font-weight: 600; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + color: #f60; + &:hover { + border-bottom: 1px dashed #000; + } + } + + .branch-priority { + min-width: 50px; + font-size: 13px; + } + } + + .node-content { + display: flex; + min-height: 32px; + padding: 4px 8px; + margin-top: 4px; + line-height: 32px; + justify-content: space-between; + align-items: center; + color: #111f2c; + background: rgba(0, 0, 0, 0.03); + border-radius: 4px; + + .node-text { + display: -webkit-box; + overflow: hidden; + font-size: 14px; + line-height: 24px; + text-overflow: ellipsis; + word-break: break-all; + -webkit-line-clamp: 2; /* 这将限制文本显示为两行 */ + -webkit-box-orient: vertical; + } + } + + //条件节点内容 + .branch-node-content { + display: flex; + min-height: 32px; + padding: 4px 8px; + margin-top: 4px; + line-height: 32px; + align-items: center; + color: #111f2c; + border-radius: 4px; + + .branch-node-text { + overflow: hidden; + font-size: 14px; + line-height: 24px; + text-overflow: ellipsis; + word-break: break-all; + -webkit-line-clamp: 2; /* 这将限制文本显示为两行 */ + -webkit-box-orient: vertical; + } + } + + // 节点操作 :删除 + .node-toolbar { + opacity: 0; + position: absolute; + top: -20px; + right: 0px; + display: flex; + + .toolbar-icon { + text-align: center; + vertical-align: middle; + } + } + + // 条件节点左右移动 + .branch-node-move { + position: absolute; + width: 10px; + cursor: pointer; + display: none; + align-items: center; + height: 100%; + justify-content: center; + } + + .move-node-left { + left: -2px; + top: 0px; + background: rgba(126, 134, 142, 0.08); + border-top-left-radius: 8px; + border-bottom-left-radius: 8px; + } + + .move-node-right { + right: -2px; + top: 0px; + background: rgba(126, 134, 142, 0.08); + border-top-right-radius: 6px; + border-bottom-right-radius: 6px; + } + } + + .node-config-error { + border-color: #ff5219 !important; + } + // 普通节点包装 + .node-wrapper { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + } + // 节点连线处理 + .node-handler-wrapper { + position: relative; + display: flex; + height: 70px; + align-items: center; + user-select: none; + justify-content: center; + flex-direction: column; + + &::before { + position: absolute; + top: 0; + right: 0; + left: 0; + // bottom: 5px; + bottom: 0px; + z-index: 0; + width: 2px; + height: 100%; + // height: calc(100% - 5px); + margin: auto; + background-color: #dedede; + content: ''; + } + + .node-handler { + .add-icon { + position: relative; + top: -5px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + width: 25px; + height: 25px; + color: #fff; + background-color: #0089ff; + border-radius: 50%; + + &:hover { + transform: scale(1.1); + } + } + } + + .node-handler-arrow { + position: absolute; + bottom: 0; + left: 50%; + display: flex; + transform: translateX(-50%); + } + } + + // 条件节点包装 + .branch-node-wrapper { + position: relative; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + margin-top: 16px; + + .branch-node-container { + position: relative; + display: flex; + + &::before { + position: absolute; + height: 100%; + width: 4px; + background-color: #fafafa; + content: ''; + left: 50%; + transform: translate(-50%); + } + + .branch-node-add { + position: absolute; + top: -18px; + left: 50%; + z-index: 1; + height: 36px; + padding: 0 10px; + font-size: 12px; + line-height: 36px; + color: #222; + cursor: pointer; + background: #fff; + border: 2px solid #dedede; + border-radius: 18px; + transform: translateX(-50%); + transform-origin: center center; + transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); + } + + .branch-node-item { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + min-width: 280px; + padding: 40px 40px 0; + background: transparent; + border-top: 2px solid #dedede; + border-bottom: 2px solid #dedede; + + &::before { + position: absolute; + width: 2px; + height: 100%; + margin: auto; + inset: 0; + background-color: #dedede; + content: ''; + } + } + // 覆盖条件节点第一个节点左上角的线 + .branch-line-first-top { + position: absolute; + top: -5px; + left: -1px; + width: 50%; + height: 7px; + background-color: #fafafa; + content: ''; + } + // 覆盖条件节点第一个节点左下角的线 + .branch-line-first-bottom { + position: absolute; + bottom: -5px; + left: -1px; + width: 50%; + height: 7px; + background-color: #fafafa; + content: ''; + } + // 覆盖条件节点最后一个节点右上角的线 + .branch-line-last-top { + position: absolute; + top: -5px; + right: -1px; + width: 50%; + height: 7px; + background-color: #fafafa; + content: ''; + } + // 覆盖条件节点最后一个节点右下角的线 + .branch-line-last-bottom { + position: absolute; + right: -1px; + bottom: -5px; + width: 50%; + height: 7px; + background-color: #fafafa; + content: ''; + } + } + } + + .node-fixed-name { + display: inline-block; + width: auto; + padding: 0 4px; + overflow: hidden; + text-align: center; + text-overflow: ellipsis; + white-space: nowrap; + } + // 开始节点包装 + .start-node-wrapper { + position: relative; + margin-top: 16px; + + .start-node-container { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + + .start-node-box { + display: flex; + justify-content: center; + align-items: center; + width: 90px; + height: 36px; + padding: 3px 4px; + color: #212121; + cursor: pointer; + // background: #2c2c2c; + background: #fafafa; + border-radius: 30px; + box-shadow: 0 1px 5px 0 rgba(10, 30, 65, 0.08); + box-sizing: border-box; + } + } + } + + // 结束节点包装 + .end-node-wrapper { + margin-bottom: 16px; + + .end-node-box { + display: flex; + justify-content: center; + align-items: center; + width: 80px; + height: 36px; + color: #212121; + // background: #6e6e6e; + background: #fafafa; + border-radius: 30px; + box-shadow: 0 1px 5px 0 rgba(10, 30, 65, 0.08); + box-sizing: border-box; + } + } + + // 可编辑的 title 输入框 + .editable-title-input { + height: 20px; + max-width: 145px; + line-height: 20px; + font-size: 12px; + margin-left: 4px; + border: 1px solid #d9d9d9; + border-radius: 4px; + transition: all 0.3s; + + &:focus { + border-color: #40a9ff; + outline: 0; + box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2); + } + } + } + } +} + +// 配置节点头部 +.config-header { + display: flex; + flex-direction: column; + + .node-name { + display: flex; + height: 24px; + line-height: 24px; + font-size: 16px; + cursor: pointer; + align-items: center; + } + + .divide-line { + width: 100%; + height: 1px; + margin-top: 16px; + background: #eee; + } + + .config-editable-input { + height: 24px; + max-width: 510px; + font-size: 16px; + line-height: 24px; + border: 1px solid #d9d9d9; + border-radius: 4px; + transition: all 0.3s; + + &:focus { + border-color: #40a9ff; + outline: 0; + box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2); + } + } +} + +// 表单字段权限 +.field-setting-pane { + display: flex; + flex-direction: column; + font-size: 14px; + + .field-setting-desc { + padding-right: 8px; + margin-bottom: 16px; + font-size: 16px; + font-weight: 700; + } + + .field-permit-title { + display: flex; + justify-content: space-between; + align-items: center; + height: 45px; + padding-left: 12px; + line-height: 45px; + background-color: #f8fafc0a; + border: 1px solid #1f38581a; + + .first-title { + text-align: left !important; + } + + .other-titles { + display: flex; + justify-content: space-between; + } + + .setting-title-label { + display: inline-block; + width: 110px; + padding: 5px 0; + font-size: 13px; + font-weight: 700; + color: #000; + text-align: center; + } + } + + .field-setting-item { + align-items: center; + display: flex; + justify-content: space-between; + height: 38px; + padding-left: 12px; + border: 1px solid #1f38581a; + border-top: 0; + + .field-setting-item-label { + display: inline-block; + width: 110px; + min-height: 16px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + cursor: text; + } + + .field-setting-item-group { + display: flex; + justify-content: space-between; + + .item-radio-wrap { + display: inline-block; + width: 110px; + text-align: center; + } + } + } +} + +// 节点连线气泡卡片样式 +.handler-item-wrapper { + display: flex; + cursor: pointer; + + .handler-item { + margin-right: 8px; + } + + .handler-item-icon { + width: 80px; + height: 80px; + background: #fff; + border: 1px solid #e2e2e2; + border-radius: 50%; + transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); + user-select: none; + text-align: center; + + &:hover { + background: #e2e2e2; + box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.1); + } + + .icon-size { + font-size: 35px; + line-height: 80px; + } + } + + .approve { + color: #ff943e; + } + .copy { + color: #3296fa; + } + + .condition { + color: #15bc83; + } + + .handler-item-text { + margin-top: 4px; + width: 80px; + text-align: center; + } +} + +// iconfont 样式 +@font-face { + font-family: 'iconfont'; /* Project id 4495938 */ + src: + url('iconfont.woff2?t=1724339470412') format('woff2'), + url('iconfont.woff?t=1724339470412') format('woff'), + url('iconfont.ttf?t=1724339470412') format('truetype'); +} + +.iconfont { + font-family: 'iconfont' !important; + font-size: 16px; + font-style: normal; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.icon-start-user:before { + content: '\e679'; +} + +.icon-inclusive:before { + content: '\e602'; +} + +.icon-copy:before { + content: '\e7eb'; +} + +.icon-handle:before { + content: '\e61c'; +} + +.icon-exclusive:before { + content: '\e717'; +} + +.icon-approve:before { + content: '\e715'; +} + +.icon-parallel:before { + content: '\e688'; +} diff --git a/src/directives/index.ts b/src/directives/index.ts index 89cc8ba142a7a7f38c14f959fecb128024eef30e..1b99988054beff90015c787d3bf36e1ca5a71b4e 100644 --- a/src/directives/index.ts +++ b/src/directives/index.ts @@ -11,3 +11,14 @@ export const setupAuth = (app: App) => { hasRole(app) hasPermi(app) } + +/** + * 导出指令:v-mountedFocus + */ +export const setupMountedFocus = (app: App) => { + app.directive('mountedFocus', { + mounted(el) { + el.focus() + } + }) +} diff --git a/src/main.ts b/src/main.ts index 76c72473fe2ff3f42e234665b10dc723ed6a5b6d..874f7668d9e765c4605b7e70f44bce056801f831 100644 --- a/src/main.ts +++ b/src/main.ts @@ -28,8 +28,8 @@ import '@/plugins/animate.css' // 路由 import router, { setupRouter } from '@/router' -// 权限 -import { setupAuth } from '@/directives' +// 指令 +import { setupAuth, setupMountedFocus } from '@/directives' import { createApp } from 'vue' @@ -58,7 +58,9 @@ const setupAll = async () => { setupRouter(app) + // directives 指令 setupAuth(app) + setupMountedFocus(app) await router.isReady() diff --git a/src/router/modules/remaining.ts b/src/router/modules/remaining.ts index cba8359c5f5027977d31c544ecda6e2c4f021c6f..11e4a7daae3663fa36249046a41a4de06e634bba 100644 --- a/src/router/modules/remaining.ts +++ b/src/router/modules/remaining.ts @@ -292,7 +292,7 @@ const remainingRouter: AppRouteRecordRaw[] = [ }, { path: 'process-instance/detail', - component: () => import('@/views/bpm/processInstance/detail/index.vue'), + component: () => import('@/views/bpm/processInstance/detail/index_new.vue'), name: 'BpmProcessInstanceDetail', meta: { noCache: true, @@ -300,7 +300,14 @@ const remainingRouter: AppRouteRecordRaw[] = [ canTo: true, title: '流程详情', activeMenu: '/bpm/task/my' - } + }, + props: route => ( + { + id: route.query.id, + taskId: route.query.taskId, + activityId: route.query.activityId + } + ) }, { path: 'oa/leave/create', diff --git a/src/store/modules/simpleWorkflow.ts b/src/store/modules/bpm/simpleWorkflow.ts similarity index 79% rename from src/store/modules/simpleWorkflow.ts rename to src/store/modules/bpm/simpleWorkflow.ts index cf98538d7138f522d1d49d900dc144b7e84c266e..2942951da1d3031a8ce4740a8612469d53672734 100644 --- a/src/store/modules/simpleWorkflow.ts +++ b/src/store/modules/bpm/simpleWorkflow.ts @@ -1,4 +1,4 @@ -import { store } from '../index' +import { store } from '../../index' import { defineStore } from 'pinia' export const useWorkFlowStore = defineStore('simpleWorkflow', { @@ -6,15 +6,15 @@ export const useWorkFlowStore = defineStore('simpleWorkflow', { tableId: '', isTried: false, promoterDrawer: false, - flowPermission1: {}, approverDrawer: false, approverConfig1: {}, copyerDrawer: false, - copyerConfig1: {}, + copyerConfig: {}, conditionDrawer: false, conditionsConfig1: { conditionNodes: [] - } + }, + userTaskConfig: {} }), actions: { setTableId(payload) { @@ -26,26 +26,26 @@ export const useWorkFlowStore = defineStore('simpleWorkflow', { setPromoter(payload) { this.promoterDrawer = payload }, - setFlowPermission(payload) { - this.flowPermission1 = payload - }, - setApprover(payload) { + setApproverDrawer(payload) { this.approverDrawer = payload }, setApproverConfig(payload) { this.approverConfig1 = payload }, - setCopyer(payload) { + setCopyerDrawer(payload) { this.copyerDrawer = payload }, setCopyerConfig(payload) { - this.copyerConfig1 = payload + this.copyerConfig = payload }, setCondition(payload) { this.conditionDrawer = payload }, setConditionsConfig(payload) { this.conditionsConfig1 = payload + }, + setUserTaskConfig(payload) { + this.userTaskConfig = payload } } }) diff --git a/src/utils/constants.ts b/src/utils/constants.ts index cfa785b02b4616bfb83678d0e34ed49c960a8c8e..874f5da0395743897dd9b93ad9524dd345829404 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -437,3 +437,15 @@ export const ErpBizType = { SALE_OUT: 21, SALE_RETURN: 22 } + +// ========== BPM 模块 ========== + +export const BpmModelType = { + BPMN: 10, // BPMN 设计器 + SIMPLE: 20 // 简易设计器 +} + +export const BpmModelFormType = { + NORMAL: 10, // 流程表单 + CUSTOM: 20 // 业务表单 +} diff --git a/src/utils/dict.ts b/src/utils/dict.ts index ebf7fd9b36b274c5ef2a05309a5ac0289898982d..1134152b69ffc40cf0ffb55682bff11f76b4babe 100644 --- a/src/utils/dict.ts +++ b/src/utils/dict.ts @@ -143,6 +143,7 @@ export enum DICT_TYPE { INFRA_OPERATE_TYPE = 'infra_operate_type', // ========== BPM 模块 ========== + BPM_MODEL_TYPE = 'bpm_model_type', BPM_MODEL_FORM_TYPE = 'bpm_model_form_type', BPM_TASK_CANDIDATE_STRATEGY = 'bpm_task_candidate_strategy', BPM_PROCESS_INSTANCE_STATUS = 'bpm_process_instance_status', diff --git a/src/views/bpm/model/ModelForm.vue b/src/views/bpm/model/ModelForm.vue index 324f14cab5a7ab92ead9cb3f2ac9192045ae77ce..d9c4c03263f4ef4a344da27cb3d2b28dcfffa2f7 100644 --- a/src/views/bpm/model/ModelForm.vue +++ b/src/views/bpm/model/ModelForm.vue @@ -8,12 +8,7 @@ label-width="110px" > - + - + - - + + - - - - - {{ dict.label }} - - - - - - - - - + + + {{ dict.label }} + + + + + + + {{ dict.label }} + + + + + + + + + + + - - + + + + + + + + + + + - - - - + + + + - - - - - - + + + + + + + 确 定 @@ -125,45 +155,62 @@ diff --git a/src/views/bpm/model/editor/index.vue b/src/views/bpm/model/editor/index.vue index 29bca71cf1ab31266abae4b99f772b8c7c8ca43b..3e773691a7b213db2357176ffc2fa56a39cc5ed4 100644 --- a/src/views/bpm/model/editor/index.vue +++ b/src/views/bpm/model/editor/index.vue @@ -58,17 +58,17 @@ const initModeler = (item) => { } /** 添加/修改模型 */ -const save = async (bpmnXml) => { +const save = async (bpmnXml: string) => { const data = { ...model.value, bpmnXml: bpmnXml // bpmnXml 只是初始化流程图,后续修改无法通过它获得 } as unknown as ModelApi.ModelVO // 提交 if (data.id) { - await ModelApi.updateModel(data) + await ModelApi.updateModelBpmn(data) message.success('修改成功') } else { - await ModelApi.createModel(data) + await ModelApi.updateModelBpmn(data) message.success('新增成功') } // 跳转回去 diff --git a/src/views/bpm/model/index.vue b/src/views/bpm/model/index.vue index 4ebf22e896c7c6b49fedb46e133996873972accd..4534ec616ed41de1cb7bc5d7b5629e8ce9b4cc96 100644 --- a/src/views/bpm/model/index.vue +++ b/src/views/bpm/model/index.vue @@ -58,10 +58,7 @@ @click="openForm('create')" v-hasPermi="['bpm:model:create']" > - 新建流程 - - - 导入流程 + 新建 @@ -70,21 +67,34 @@ - - + + - - {{ scope.row.name }} - + - + - + + 全部可见 + + + {{ scope.row.startUsers[0].nickname }} + + + + {{ scope.row.startUsers[0].nickname }}等 {{ scope.row.startUsers.length }} 人可见 + + - - + + 暂无表单 - - - - - - v{{ scope.row.processDefinition.version }} - - 未部署 - - - - - - - - - - - {{ formatDate(scope.row.processDefinition.deploymentTime) }} - - - + + + + {{ formatDate(scope.row.processDefinition.deploymentTime) }} + + + v{{ scope.row.processDefinition.version }} + + 未部署 + + 已停用 + + - + - 修改流程 + 修改 - 设计流程 - - - 仿钉钉设计流程 + 设计 - 发布流程 - - - 流程定义 + 发布 - handleCommand(command, scope.row)" + v-hasPermi="['bpm:process-definition:query', 'bpm:model:update', 'bpm:model:delete']" > - 删除 - + 更多 + + + + 历史 + + + {{ scope.row.processDefinition.suspensionState === 1 ? '停用' : '启用' }} + + + 删除 + + + + @@ -215,41 +211,29 @@ - - - - - - - - @@ -299,10 +365,11 @@ defineExpose({ loadRunningTask }) .btn-container { > div { + display: flex; margin: 0 15px; cursor: pointer; - display: flex; align-items: center; + &:hover { color: #6db5ff; } diff --git a/src/views/bpm/processInstance/detail/ProcessInstanceTimeline.vue b/src/views/bpm/processInstance/detail/ProcessInstanceTimeline.vue index 17b858bcaf8308f77ee694b75895d734c8d7ba69..afb778c4c96cd9a8a7db7fa931bf4833d62b7488 100644 --- a/src/views/bpm/processInstance/detail/ProcessInstanceTimeline.vue +++ b/src/views/bpm/processInstance/detail/ProcessInstanceTimeline.vue @@ -1,9 +1,111 @@ + - + + {{ activity.name }} - {{ activity.assigneeUser.nickname }} + + + + + + + + + {{ task.assigneeUser.nickname.substring(0, 1) }} + + + + {{ task.ownerUser.nickname.substring(0, 1) }} + + + + + + + + + + {{ task.assigneeUser.nickname }} + + + {{ task.ownerUser.nickname }} + + + + + + + + + + + + + + {{ user.nickname.substring(0, 1) }} + + + + + + + + + + {{ user.nickname }} + + + + + + + + + {{ getApprovalNodeTime(activity) }} + + + + - - - - - - - - - diff --git a/src/views/bpm/processInstance/detail/index.vue b/src/views/bpm/processInstance/detail/index.vue index eafb7eabe609b759248f00c83d66a7fe09af7069..96ac488990d0ce90bd2672228f24d9e7e299d071 100644 --- a/src/views/bpm/processInstance/detail/index.vue +++ b/src/views/bpm/processInstance/detail/index.vue @@ -56,29 +56,73 @@ - + + - 通过 + + {{ + item.buttonsSetting?.[OperationButtonType.APPROVE]?.displayName || + OPERATION_BUTTON_NAME.get(OperationButtonType.APPROVE) + }} - + - 不通过 + {{ + item.buttonsSetting?.[OperationButtonType.REJECT].displayName || + OPERATION_BUTTON_NAME.get(OperationButtonType.REJECT) + }} - + - 转办 + {{ + item.buttonsSetting?.[OperationButtonType.TRANSFER]?.displayName || + OPERATION_BUTTON_NAME.get(OperationButtonType.TRANSFER) + }} - + - 委派 + {{ + item.buttonsSetting?.[OperationButtonType.DELEGATE]?.displayName || + OPERATION_BUTTON_NAME.get(OperationButtonType.DELEGATE) + }} - + - 加签 + {{ + item.buttonsSetting?.[OperationButtonType.ADD_SIGN]?.displayName || + OPERATION_BUTTON_NAME.get(OperationButtonType.ADD_SIGN) + }} - + - 回退 + {{ + item.buttonsSetting?.[OperationButtonType.RETURN]?.displayName || + OPERATION_BUTTON_NAME.get(OperationButtonType.RETURN) + }} @@ -147,6 +191,10 @@ import TaskSignCreateForm from './dialog/TaskSignCreateForm.vue' import { registerComponent } from '@/utils/routerHelper' import { isEmpty } from '@/utils/is' import * as UserApi from '@/api/system/user' +import { + OperationButtonType, + OPERATION_BUTTON_NAME +} from '@/components/SimpleProcessDesignerV2/src/consts' defineOptions({ name: 'BpmProcessInstanceDetail' }) @@ -200,7 +248,11 @@ const handleAudit = async (task, pass) => { // 1.2 校验表单 const elForm = unref(auditFormRef) if (!elForm) return - const valid = await elForm.validate() + let valid = await elForm.validate() + if (!valid) return + // 校验申请表单 + if (!fApi.value) return + valid = await fApi.value.validate() if (!valid) return // 2.1 提交审批 @@ -216,6 +268,9 @@ const handleAudit = async (task, pass) => { await formCreateApi.validate() data.variables = approveForms.value[index].value } + // 获取表单可编辑字段的值 + data.variables = getWritableValueOfForm(task.fieldsPermission) + await TaskApi.approveTask(data) message.success('审批通过成功') } else { @@ -251,11 +306,11 @@ const handleSign = async (task: any) => { } /** 获得详情 */ -const getDetail = () => { - // 1. 获得流程实例相关 +const getDetail = async () => { + // 1. 获得流程任务列表(审批记录)。 需要先获取任务,表单的权限设置需要根据任务来设置 + await getTaskList() + // 2. 获得流程实例相关 getProcessInstance() - // 2. 获得流程任务列表(审批记录) - getTaskList() } /** 加载流程实例 */ @@ -273,16 +328,29 @@ const getProcessInstance = async () => { // 设置表单信息 const processDefinition = data.processDefinition if (processDefinition.formType === 10) { - setConfAndFields2( - detailForm, - processDefinition.formConf, - processDefinition.formFields, - data.formVariables - ) + if (detailForm.value.rule.length > 0) { + detailForm.value.value = data.formVariables + } else { + setConfAndFields2( + detailForm, + processDefinition.formConf, + processDefinition.formFields, + data.formVariables + ) + } nextTick().then(() => { fApi.value?.btn.show(false) fApi.value?.resetBtn.show(false) fApi.value?.disabled(true) + // 设置表单权限。后续需要改造成。只处理一个运行中的任务 + if (runningTasks.value.length > 0) { + const task = runningTasks.value.at(0) + if (task.fieldsPermission) { + Object.keys(task.fieldsPermission).forEach((item) => { + setFieldPermission(item, task.fieldsPermission[item]) + }) + } + } }) } else { // 注意:data.processDefinition.formCustomViewPath 是组件的全路径,例如说:/crm/contract/detail/index.vue @@ -353,6 +421,7 @@ const loadRunningTask = (tasks) => { if (!task.assigneeUser || task.assigneeUser.id !== userId) { return } + // 2.3 添加到处理任务 runningTasks.value.push({ ...task }) auditForms.value.push({ @@ -371,6 +440,35 @@ const loadRunningTask = (tasks) => { }) } +/** + * 设置表单权限 + */ +const setFieldPermission = (field: string, permission: string) => { + if (permission === '1') { + fApi.value?.disabled(true, field) + } + if (permission === '2') { + fApi.value?.disabled(false, field) + } + if (permission === '3') { + fApi.value?.hidden(true, field) + } +} +/** + * 获取可以编辑字段的值 + */ +const getWritableValueOfForm = (fieldsPermission: Object) => { + const fieldsValue = {} + if (fieldsPermission && fApi.value) { + Object.keys(fieldsPermission).forEach((item) => { + if (fieldsPermission[item] === '2') { + fieldsValue[item] = fApi.value.getValue(item) + } + }) + } + return fieldsValue +} + /** 初始化 */ const userOptions = ref([]) // 用户列表 onMounted(async () => { diff --git a/src/views/bpm/processInstance/detail/index_new.vue b/src/views/bpm/processInstance/detail/index_new.vue index 4529c3555473d9cc19e94d2a438cd4cc22f98837..b2035a19586ed589defac3ec92042ee1b30f85bf 100644 --- a/src/views/bpm/processInstance/detail/index_new.vue +++ b/src/views/bpm/processInstance/detail/index_new.vue @@ -1,88 +1,106 @@ - - 编号:{{ id }} - - - {{ processInstance.name }} - - + + + + 编号:{{ id }} + + + {{ processInstance.name }} + + - - - - {{ processInstance?.startUser?.nickname }} - - {{ formatDate(processInstance.startTime) }} 提交 - + + + + {{ processInstance?.startUser?.nickname }} + + {{ formatDate(processInstance.startTime) }} 提交 + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + - - - - - - - - - - - - - - - - - 流转评论 - + + + + + + + 流转评论 + + + + + + + + \ No newline at end of file + diff --git a/src/views/bpm/task/copy/index.vue b/src/views/bpm/task/copy/index.vue index 93f55c6824ab31a7948f11b579fce6daf7743ce1..045e2074660be02de3679eceef4a013394362007 100644 --- a/src/views/bpm/task/copy/index.vue +++ b/src/views/bpm/task/copy/index.vue @@ -111,11 +111,16 @@ const getList = async () => { /** 处理审批按钮 */ const handleAudit = (row: any) => { + const query = { + id: row.processInstanceId, + activityId: undefined + } + if (row.activityId) { + query.activityId = row.activityId + } push({ name: 'BpmProcessInstanceDetail', - query: { - id: row.processInstanceId - } + query: query }) } diff --git a/src/views/bpm/task/done/index.vue b/src/views/bpm/task/done/index.vue index a51371997751b1cf03e1e0f81c853546b3a1cebd..987682120cf4a57cc4757e09ed2e4a742778311c 100644 --- a/src/views/bpm/task/done/index.vue +++ b/src/views/bpm/task/done/index.vue @@ -158,7 +158,8 @@ const handleAudit = (row: any) => { push({ name: 'BpmProcessInstanceDetail', query: { - id: row.processInstance.id + id: row.processInstance.id, + taskId: row.id } }) } diff --git a/src/views/bpm/task/todo/index.vue b/src/views/bpm/task/todo/index.vue index 670fc683e7adc2548eb46edd576aec2c444e3082..bf32ecb16a709e91811149141ca2ba2c32186346 100644 --- a/src/views/bpm/task/todo/index.vue +++ b/src/views/bpm/task/todo/index.vue @@ -140,7 +140,8 @@ const handleAudit = (row: any) => { push({ name: 'BpmProcessInstanceDetail', query: { - id: row.processInstance.id + id: row.processInstance.id, + taskId: row.id } }) }
审批人
抄送人
条件分支