import nodeConfig from "./config.js"; const isEmpty = data => data === null || data === undefined || data === '' const isEmptyArray = data => Array.isArray(data) ? data.length === 0 : true export class NodeUtils { /** * 根据自增数生成64进制id * @returns 64进制id字符串 */ static idGenerator() { let qutient = (new Date() - new Date('2020-08-01')) qutient += Math.ceil(Math.random() * 1000) // 防止重複 const chars = '0123456789ABCDEFGHIGKLMNOPQRSTUVWXYZabcdefghigklmnopqrstuvwxyz'; const charArr = chars.split("") const radix = chars.length; const res = [] do { let mod = qutient % radix; qutient = (qutient - mod) / radix; res.push(charArr[mod]) } while (qutient); return res.join('') } /** * 判断节点类型 * @param {Node} node - 节点数据 * @returns Boolean */ static isConditionNode(node) { return node && node.type === 'condition' } static isCopyNode(node) { return node && node.type === 'copy' } static isStartNode(node) { return node && node.type === 'start' } static isApproverNode(node) { return node && node.type === 'approver' } static isTimerNode(node) { return node && node.type === 'timer' } static isSubFlowNode(node) { return node && node.type === 'subFlow' } static isInterflowNode(node) { return node && node.type === 'approver' && node.isInterflow } /** * 创建指定节点 * @param { String } type - 节点类型 * @param { Object } previousNodeId - 父节点id * @returns { Object } 节点数据 */ static createNode(type, previousNodeId) { let res = JSON.parse(JSON.stringify(nodeConfig[type])) res.nodeId = this.idGenerator() res.prevId = previousNodeId return res } /** * 获取指定节点的父节点(前一个节点) * @param { String } prevId - 父节点id * @param { Object } processData - 流程图全部数据 * @returns { Object } 父节点 */ static getPreviousNode(prevId, processData) { if (processData.nodeId === prevId) return processData if (processData.childNode) { let r1 = this.getPreviousNode(prevId, processData.childNode) if (r1) { return r1 } } if (processData.conditionNodes) { for (let c of processData.conditionNodes) { let r2 = this.getPreviousNode(prevId, c) if (r2) { return r2 } } } } /** * 删除节点 * @param { Object } nodeData - 被删除节点的数据 * @param { Object } processData - 流程图的所有节点数据 */ static deleteNode(nodeData, processData, checkEmpty = true) { let prevNode = this.getPreviousNode(nodeData.prevId, processData) if (checkEmpty && prevNode.type === 'empty') { if (this.isConditionNode(nodeData) || this.isInterflowNode(nodeData)) { const willDelBranch = prevNode.conditionNodes.length === 2 const target = willDelBranch ? prevNode : nodeData this.deleteNode(target, processData, willDelBranch) } else { if (isEmptyArray(prevNode.conditionNodes)) { this.deleteNode(prevNode, processData) } this.deleteNode(nodeData, processData, false) } // this.deleteNode( prevNode, processData ) // !this.isConditionNode(nodeData) && this.deleteNode(nodeData, processData) return } let concatChild = (prev, delNode) => { prev.childNode = delNode.childNode isEmptyArray(prev.conditionNodes) && (prev.conditionNodes = delNode.conditionNodes) prev.childNode && (prev.childNode.prevId = prev.nodeId) prev.conditionNodes && prev.conditionNodes.forEach(c => c.prevId = prev.nodeId); } if (this.isConditionNode(nodeData) || this.isInterflowNode(nodeData)) { let cons = prevNode.conditionNodes let index = cons.findIndex(c => c.nodeId === nodeData.nodeId) if (cons.length > 2) { cons.splice(index, 1) } else { let anotherCon = cons[+(!index)] delete prevNode.conditionNodes delete prevNode.conditionType if (prevNode.childNode) { let endNode = anotherCon while (endNode.childNode) { endNode = endNode.childNode } endNode.childNode = prevNode.childNode endNode.childNode.prevId = endNode.nodeId } concatChild(prevNode, anotherCon) if (prevNode.childNode && prevNode.childNode.type === 'empty') { this.deleteNode(prevNode.childNode, prevNode) } } // 重新编排优先级 cons.forEach((c, i) => c.properties.priority = i) return } concatChild(prevNode, nodeData) } // TODO: // static copyNode ( nodeData, processData ) { // let prevNode = this.getPreviousNode( nodeData.prevId, processData ) // let index = prevNode.conditionNodes.findIndex( c => c.nodeId === nodeData.nodeId ) // } /** * 添加审计节点(普通节点 approver) * @param { Object } data - 目标节点数据,在该数据节点之后添加审计节点 * @param { Object } isBranchAction - 目标节点数据,是否是条件分支 * @param { Object } newChildNode - 传入的新的节点 用户操作均为空 删除操作/添加抄送人 会传入该参数 以模拟添加节点 */ static addApprovalNode(data, isBranchAction, newChildNode = undefined) { let oldChildNode = data.childNode; newChildNode = newChildNode || this.createNode("approver", data.nodeId) data.childNode = newChildNode if (oldChildNode) { newChildNode.childNode = oldChildNode oldChildNode.prevId = newChildNode.nodeId } let conditionNodes = data.conditionNodes if (Array.isArray(conditionNodes) && !isBranchAction && conditionNodes.length) { newChildNode.conditionNodes = conditionNodes.map(c => { c.prevId = newChildNode.nodeId return c }) delete data.conditionNodes } if (oldChildNode && oldChildNode.type === 'empty' && newChildNode.type !== 'empty' && oldChildNode.conditionNodes.length === 0) { this.deleteNode(oldChildNode, data) } } /** * 添加空节点 * @param { Object } data - 空节点的父级节点 * @return { Object } emptyNode - 空节点数据 */ static addEmptyNode(data) { let emptyNode = this.createNode('empty', data.nodeId) this.addApprovalNode(data, true, emptyNode) return emptyNode } static addCopyNode(data, isBranchAction) { // 复用addApprovalNode 因为抄送人和审批人基本一致 this.addApprovalNode(data, isBranchAction, this.createNode('copy', data.nodeId)) } static addTimerNode(data, isBranchAction) { // 复用addApprovalNode 因为和审批人基本一致 this.addApprovalNode(data, isBranchAction, this.createNode('timer', data.nodeId)) } static addSubFlowNode(data, isBranchAction) { // 复用addApprovalNode 因为和审批人基本一致 this.addApprovalNode(data, isBranchAction, this.createNode('subFlow', data.nodeId)) } /** * 添加条件节点 condition 通过点击添加条件进入该操作 * @param { Object } data - 目标节点所在分支数据,在该分支最后添加条件节点 */ static appendConditionNode(data) { const conditions = data.conditionNodes let node = this.createNode('condition', data.nodeId) let defaultNodeIndex = conditions.findIndex(node => node.properties.isDefault) node.properties.priority = conditions.length if (defaultNodeIndex > -1) { conditions.splice(-1, 0, node) // 插在倒数第二个 //更新优先级 node.properties.priority = conditions.length - 2 conditions[conditions.length - 1].properties.priority = conditions.length - 1 } else { conditions.push(node) } this.setDefaultCondition(node, data) } static appendInterflowNode(data) { const conditions = data.conditionNodes let node = this.createNode('interflow', data.nodeId) let defaultNodeIndex = conditions.findIndex(node => node.properties.isDefault) node.properties.priority = conditions.length if (defaultNodeIndex > -1) { conditions.splice(-1, 0, node) // 插在倒数第二个 //更新优先级 node.properties.priority = conditions.length - 2 conditions[conditions.length - 1].properties.priority = conditions.length - 1 } else { conditions.push(node) } } /** * 添加条件分支 branch * @param { Object } data - 目标节点所在节点数据,在该节点最后添加分支节点 */ static appendBranch(data, isBottomBtnOfBranch) { // isBottomBtnOfBranch 用户点击的是分支树下面的按钮 let nodeData = data // 由于conditionNodes是数组 不能添加下级分支 故在两个分支树之间添加一个不会显示的正常节点 兼容此种情况 if (Array.isArray(data.conditionNodes) && data.conditionNodes.length) { if (isBottomBtnOfBranch) { // 添加一个模拟用的空白节点并返回这个节点,作为新分支的父节点 nodeData = this.addEmptyNode(nodeData, true) } else { let emptyNode = this.addEmptyNode(nodeData, true) emptyNode.conditionNodes = nodeData.conditionNodes emptyNode.conditionType = "condition" emptyNode.conditionNodes.forEach(n => { n.prevId = emptyNode.nodeId }) } } let conditionNodes = [ this.createNode("condition", nodeData.nodeId), this.createNode("condition", nodeData.nodeId) ].map((c, i) => { c.properties.priority = i; return c }) nodeData.conditionNodes = conditionNodes nodeData.conditionType = "condition" } static appendInterflowBranch(data, isBottomBtnOfBranch) { // isBottomBtnOfBranch 用户点击的是分支树下面的按钮 let nodeData = data // 由于conditionNodes是数组 不能添加下级分支 故在两个分支树之间添加一个不会显示的正常节点 兼容此种情况 if (Array.isArray(data.conditionNodes) && data.conditionNodes.length) { if (isBottomBtnOfBranch) { // 添加一个模拟用的空白节点并返回这个节点,作为新分支的父节点 nodeData = this.addEmptyNode(nodeData, true) } else { let emptyNode = this.addEmptyNode(nodeData, true) emptyNode.conditionNodes = nodeData.conditionNodes emptyNode.conditionType = "interflow" emptyNode.conditionNodes.forEach(n => { n.prevId = emptyNode.nodeId }) } } let conditionNodes = [ this.createNode("interflow", nodeData.nodeId), this.createNode("interflow", nodeData.nodeId) ] nodeData.conditionNodes = conditionNodes nodeData.conditionType = "interflow" } /** * 重设节点优先级(条件节点) * @param {Node} cnode - 当前节点 * @param {Number} oldPriority - 替换前的优先级(在数组中的顺序) * @param {Node} processData - 整个流程图节点数据 */ static resortPrioByCNode(cnode, oldPriority, processData) { // 当前节点为默认节点 取消修改优先级 if (cnode.properties.isDefault) { cnode.properties.priority = oldPriority return } let prevNode = this.getPreviousNode(cnode.prevId, processData) let newPriority = cnode.properties.priority // 替换节点为默认节点 取消修改优先级 if (prevNode.conditionNodes[newPriority].properties.isDefault) { cnode.properties.priority = oldPriority return } let delNode = prevNode.conditionNodes.splice(newPriority, 1, cnode)[0] delNode.properties.priority = oldPriority prevNode.conditionNodes[oldPriority] = delNode } /** * 提升条件节点优先级——排序在前 * @param { Object } data - 目标节点数据 * @param { Object } processData - 流程图的所有节点数据 */ static increasePriority(data, processData) { if (data.properties.isDefault) { // 默认节点不能修改优先级 return } // 分支节点数据 包含该分支所有的条件节点 let prevNode = this.getPreviousNode(data.prevId, processData) let branchData = prevNode.conditionNodes let index = branchData.findIndex(c => c === data) if (index) { // 和前一个数组项交换位置 Array.prototype.splice会返回包含被删除的项的集合(数组) branchData[index - 1].properties.priority = index branchData[index].properties.priority = index - 1 branchData[index - 1] = branchData.splice(index, 1, branchData[index - 1])[0] } } /** * 降低条件节点优先级——排序在后 * @param { Object } data - 目标节点数据 * @param { Object } processData - 流程图的所有节点数据 */ static decreasePriority(data, processData) { // 分支节点数据 包含该分支所有的条件节点 let prevNode = this.getPreviousNode(data.prevId, processData) let branchData = prevNode.conditionNodes let index = branchData.findIndex(c => c.nodeId === data.nodeId) if (index < branchData.length - 1) { let lastNode = branchData[index + 1] if (lastNode.properties.isDefault) { // 默认节点不能修改优先级 return } // 和后一个数组项交换位置 Array.prototype.splice会返回包含被删除的项的集合(数组) lastNode.properties.priority = index branchData[index].properties.priority = index + 1 branchData[index + 1] = branchData.splice(index, 1, branchData[index + 1])[0] } } /** * 当有其他条件节点设置条件后 检查并设置最后一个节点为默认节点 * @param {Node} cnode - 当前节点 * @param {Node} processData - 整个流程图节点数据或父级节点数据 */ static setDefaultCondition(cnode, processData) { const DEFAULT_TEXT = '其他情况进入此流程' const conditions = this.getPreviousNode(cnode.prevId, processData).conditionNodes const hasCondition = node => node.properties && (node.properties.initiator || !isEmptyArray(node.properties.conditions)) const clearDefault = node => { node.properties.isDefault = false node.content === DEFAULT_TEXT && (node.content = '请设置条件') } const setDefault = node => { node.properties.isDefault = true node.content = DEFAULT_TEXT } let count = 0 conditions.slice(0, -1).forEach(node => { hasCondition(node) && count++ clearDefault(node) }) const lastNode = conditions[conditions.length - 1] count > 0 && !hasCondition(lastNode) ? setDefault(lastNode) : clearDefault(lastNode) } /** * 校验单个节点必填项完整性 * @param {Node} node - 节点数据 */ static checkNode(node, parent) { let valid = true const props = node.properties this.isStartNode(node) && !props.initiator && (valid = false) this.isConditionNode(node) && !props.isDefault && isEmptyArray(props.conditions) && (valid = false) this.isSubFlowNode(node) && (!props.flowId || props.initiateType === 1 && (isEmptyArray(props.initiator) && isEmptyArray(props.initiatePos) && isEmptyArray(props.initiateRole))) && (valid = false) const customSettings = [6, 8] this.isApproverNode(node) && (!props.assigneeType || customSettings.includes(props.assigneeType)) && (isEmptyArray(props.approvers) && isEmptyArray(props.approverPos) && isEmptyArray(props.approverRole)) && (valid = false) return valid } /** * 判断所有节点是否信息完整 * @param {Node} processData - 整个流程图数据 * @returns {Boolean} */ static checkAllNode(processData) { let valid = true const loop = (node, callback, parent) => { !this.checkNode(node, parent) && callback() if (node.childNode) loop(node.childNode, callback, parent) if (!isEmptyArray(node.conditionNodes)) { node.conditionNodes.forEach(n => loop(n, callback, node)) } } loop(processData, () => valid = false) return valid } } /** * 添模拟数据 */ export function getMockData() { let startNode = NodeUtils.createNode("start"); startNode.childNode = NodeUtils.createNode("approver", startNode.nodeId) return startNode; }