|
@@ -0,0 +1,409 @@
|
|
|
+import { BaseEdgeModel, BaseNodeModel } from '@logicflow/core'
|
|
|
+
|
|
|
+/**
|
|
|
+ * 节点样式处理方法
|
|
|
+ * @param {*}} _this
|
|
|
+ * @param {*} style
|
|
|
+ * @returns
|
|
|
+ */
|
|
|
+export const nodeStyleHandle = (_this: BaseNodeModel, style: {
|
|
|
+ [x: string]: any;
|
|
|
+ r?: number;
|
|
|
+ fill?: string;
|
|
|
+ stroke?: string;
|
|
|
+ strokeWidth?: number;
|
|
|
+ }) : {
|
|
|
+ [x: string]: any;
|
|
|
+ r?: number;
|
|
|
+ fill?: string;
|
|
|
+ stroke?: string;
|
|
|
+ strokeWidth?: number;
|
|
|
+ } => {
|
|
|
+ if (_this.properties.state === 'active') {
|
|
|
+ style.stroke = '#00ff00'
|
|
|
+ } else if (_this.properties.state === 'history') {
|
|
|
+ style.stroke = '#ff0000'
|
|
|
+ }
|
|
|
+ return style
|
|
|
+}
|
|
|
+/**
|
|
|
+ * 边样式处理方法
|
|
|
+ * @param {*}} _this
|
|
|
+ * @param {*} style
|
|
|
+ * @returns
|
|
|
+ */
|
|
|
+export const edgeStyleHandle = (_this: BaseEdgeModel, style: {
|
|
|
+ [x: string]: any;
|
|
|
+ r?: number;
|
|
|
+ fill?: string;
|
|
|
+ stroke?: string;
|
|
|
+ strokeWidth?: number;
|
|
|
+ }):{
|
|
|
+ [x: string]: any;
|
|
|
+ r?: number;
|
|
|
+ fill?: string;
|
|
|
+ stroke?: string;
|
|
|
+ strokeWidth?: number;
|
|
|
+ } => {
|
|
|
+ if (_this.properties.state === 'active') {
|
|
|
+ style.stroke = '#00ff00'
|
|
|
+ } else if (_this.properties.state === 'history') {
|
|
|
+ style.stroke = '#ff0000'
|
|
|
+ }
|
|
|
+ return style
|
|
|
+}
|
|
|
+/**
|
|
|
+ * 解析xml成Dom对象
|
|
|
+ * @param {} xml
|
|
|
+ * @returns
|
|
|
+ */
|
|
|
+export const parseXml2Dom = (xml:string): any => {
|
|
|
+ let xmlDoc = null
|
|
|
+ if (window.DOMParser) {
|
|
|
+ const parser = new DOMParser()
|
|
|
+ xmlDoc = parser.parseFromString(xml, 'text/xml')
|
|
|
+ } else { // Internet Explorer
|
|
|
+ // eslint-disable-next-line no-undef
|
|
|
+ xmlDoc = new ActiveXObject('Microsoft.XMLDOM')
|
|
|
+ xmlDoc.async = false
|
|
|
+ xmlDoc.loadXML(xml)
|
|
|
+ }
|
|
|
+ return xmlDoc
|
|
|
+}
|
|
|
+// 节点标签
|
|
|
+const NODE_NAMES = ['start', 'task', 'decision', 'end', 'custom', 'join', 'fork', 'subProcess']
|
|
|
+// 流程节点属性
|
|
|
+const PROCESS_ATTR_KEYS = ['name', 'displayName', 'instanceUrl', 'expireTime', 'instanceNoClass']
|
|
|
+// 节点属性
|
|
|
+const NODE_ATTR_KEYS = ['name', 'displayName', 'form', 'assignee', 'assignmentHandler', 'taskType', 'performType',
|
|
|
+ 'preInterceptors', 'postInterceptors', 'reminderTime', 'reminderRepeat',
|
|
|
+ 'expireTime', 'autoExecute', 'callback', 'expr', 'handleClass',
|
|
|
+ 'clazz', 'methodName', 'args', 'layout', 'g']
|
|
|
+// 变迁节点属性
|
|
|
+const TRANSITION_ATTR_KEYS = ['name', 'displayName', 'to', 'expr', 'g']
|
|
|
+
|
|
|
+/**
|
|
|
+ * 将snaker的定义文件转成LogicFlow支持的数据格式
|
|
|
+ * @param {*} xml
|
|
|
+ * @returns
|
|
|
+ */
|
|
|
+export const snakerXml2LogicFlowJson = (xml: string): any => {
|
|
|
+ const graphData = {
|
|
|
+ nodes: [] as BaseNodeModel[],
|
|
|
+ edges: [] as BaseNodeModel[]
|
|
|
+ } as any
|
|
|
+ const xmlDoc = parseXml2Dom(xml)
|
|
|
+ const processDom = xmlDoc.getElementsByTagName('process')
|
|
|
+ if (!processDom.length) {
|
|
|
+ return graphData
|
|
|
+ }
|
|
|
+ let value = null
|
|
|
+ // 解析process属性
|
|
|
+ PROCESS_ATTR_KEYS.forEach(key => {
|
|
|
+ value = processDom[0].getAttribute(key)
|
|
|
+ if (value) {
|
|
|
+ graphData[key] = value
|
|
|
+ }
|
|
|
+ })
|
|
|
+ let nodeEles: any = null
|
|
|
+ let node: any = null
|
|
|
+ let lfNode: any = {}
|
|
|
+ // 解析节点
|
|
|
+ NODE_NAMES.forEach(key => {
|
|
|
+ nodeEles = processDom[0].getElementsByTagName(key)
|
|
|
+ if (nodeEles.length) {
|
|
|
+ for (let i = 0, len = nodeEles.length; i < len; i++) {
|
|
|
+ node = nodeEles[i]
|
|
|
+ lfNode = {
|
|
|
+ type: 'hotline:' + key,
|
|
|
+ properties: {}
|
|
|
+ }
|
|
|
+ // 处理节点
|
|
|
+ NODE_ATTR_KEYS.forEach(attrKey => {
|
|
|
+ value = node?.getAttribute(attrKey)
|
|
|
+ if (value) {
|
|
|
+ if (attrKey === 'name') {
|
|
|
+ lfNode.id = value
|
|
|
+ } else if (attrKey === 'layout') {
|
|
|
+ const attr = value.split(',')
|
|
|
+ if (attr.length === 4) {
|
|
|
+ lfNode.x = attr[0]
|
|
|
+ lfNode.y = attr[1]
|
|
|
+ lfNode.properties.width = attr[2] <= 0 ? 100 : attr[2]
|
|
|
+ lfNode.properties.height = attr[3] <= 0 ? 50 : attr[3]
|
|
|
+ }
|
|
|
+ } else if (attrKey === 'displayName') {
|
|
|
+ lfNode.text = value
|
|
|
+ } else {
|
|
|
+ lfNode.properties[attrKey] = value
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ // 处理扩展属性
|
|
|
+ if (key === 'task') {
|
|
|
+ const fieldEles = node.getElementsByTagName('field')
|
|
|
+ if (fieldEles.length) {
|
|
|
+ const fieldEle = fieldEles[0]
|
|
|
+ const field = <any>null
|
|
|
+ const attr = fieldEle.getElementsByTagName('attr')
|
|
|
+ for (let iii = 0; iii < attr.length; iii++) {
|
|
|
+ field[attr[iii].getAttribute('name')] = attr[iii].getAttribute('value')
|
|
|
+ }
|
|
|
+ lfNode.properties.field = field
|
|
|
+ }
|
|
|
+ }
|
|
|
+ graphData.nodes.push(lfNode)
|
|
|
+ // 处理边
|
|
|
+ let transitionEles = null
|
|
|
+ let transitionEle = null
|
|
|
+ let edge :any = {}
|
|
|
+ if (key !== 'end') {
|
|
|
+ transitionEles = node.getElementsByTagName('transition')
|
|
|
+ for (let j = 0, lenn = transitionEles.length; j < lenn; j++) {
|
|
|
+ transitionEle = transitionEles[j]
|
|
|
+ edge = {}
|
|
|
+ edge.id = transitionEle.getAttribute('name')
|
|
|
+ edge.type = 'hotline:transition'
|
|
|
+ edge.sourceNodeId = lfNode.id
|
|
|
+ edge.targetNodeId = transitionEle.getAttribute('to')
|
|
|
+ edge.text = {
|
|
|
+ value: transitionEle.getAttribute('displayName') ? transitionEle.getAttribute('displayName') : ''
|
|
|
+ }
|
|
|
+ edge.properties = {}
|
|
|
+ const expr = transitionEle.getAttribute('expr')
|
|
|
+ if (expr) {
|
|
|
+ edge.properties.expr = expr
|
|
|
+ }
|
|
|
+ const g = transitionEle.getAttribute('g')
|
|
|
+ if (g) {
|
|
|
+ const points = g.split(';')
|
|
|
+ if (points.length >= 2) {
|
|
|
+ edge.pointsList = []
|
|
|
+ points.forEach((item: any) => {
|
|
|
+ const pointArr = item.split(',')
|
|
|
+ edge.pointsList.push({
|
|
|
+ x: Number(pointArr[0]),
|
|
|
+ y: Number(pointArr[1])
|
|
|
+ })
|
|
|
+ })
|
|
|
+ edge.startPoint = edge.pointsList[0]
|
|
|
+ edge.endPoint = edge.pointsList[edge.pointsList.length - 1]
|
|
|
+ }
|
|
|
+ }
|
|
|
+ graphData.edges.push(edge)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ return graphData
|
|
|
+}
|
|
|
+/**
|
|
|
+ * 将LogicFlow的数据转成snaker的定义文件
|
|
|
+ * @param {*} data(...processInfo,nodes,edges)
|
|
|
+ * @returns
|
|
|
+ */
|
|
|
+export const logicFlowJsonToSnakerXml = (data: any): string => {
|
|
|
+ let xml = ''
|
|
|
+ // data的数据由流程定义文件信息+logicFlow数据构成
|
|
|
+ // 先构建成流程对象
|
|
|
+ const processObj = {
|
|
|
+ name: data.name, // 流程定义名称
|
|
|
+ displayName: data.displayName, // 流程定义显示名称
|
|
|
+ instanceUrl: data.instanceUrl, // 实例启动Url
|
|
|
+ expireTime: data.expireTime, // 期望完成时间
|
|
|
+ instanceNoClass: data.instanceNoClass // 实例编号生成类
|
|
|
+ }
|
|
|
+ /**
|
|
|
+ * 获取所有子流程
|
|
|
+ */
|
|
|
+ const getSubProcessNodes = () => {
|
|
|
+ return data.nodes.filter((node:any) => {
|
|
|
+ return node.type === 'hotline:subProcess'
|
|
|
+ })
|
|
|
+ }
|
|
|
+ /**
|
|
|
+ * 获取开始节点
|
|
|
+ * @returns
|
|
|
+ */
|
|
|
+ const getStartNode = () => {
|
|
|
+ const subProcessNodes = getSubProcessNodes()
|
|
|
+ return data.nodes.find((node:any) => {
|
|
|
+ // 是开始节点,且非子流程中的开始节点
|
|
|
+ return node.type === 'hotline:start' && !subProcessNodes.some((item: any) => {
|
|
|
+ return item.children.includes(node.id)
|
|
|
+ })
|
|
|
+ })
|
|
|
+ }
|
|
|
+ /**
|
|
|
+ * 获取当前节点的所有下一个节点集合
|
|
|
+ * @param {*} id 当前节点名称
|
|
|
+ * @returns
|
|
|
+ */
|
|
|
+ const getNextNodes = (id:string) => {
|
|
|
+ return data.edges.filter((edge:any) => {
|
|
|
+ return edge.sourceNodeId === id
|
|
|
+ }).map((edge:any) => {
|
|
|
+ return data.nodes.find((node:any) => {
|
|
|
+ return node.id === edge.targetNodeId
|
|
|
+ })
|
|
|
+ })
|
|
|
+ }
|
|
|
+ /**
|
|
|
+ * 获取节点所有输出边
|
|
|
+ * @param {*} id
|
|
|
+ * @returns
|
|
|
+ */
|
|
|
+ const getTransitions = (id:string) => {
|
|
|
+ return data.edges.filter((edge:any) => {
|
|
|
+ return edge.sourceNodeId === id
|
|
|
+ }).map(edge => {
|
|
|
+ return {
|
|
|
+ name: edge.id,
|
|
|
+ displayName: (edge.text instanceof String || edge.text === undefined) ? edge.text : edge.text.value,
|
|
|
+ to: edge.targetNodeId, // 目地节点id
|
|
|
+ expr: edge.properties.expr, // 表达式
|
|
|
+ g: edge.pointsList.map((point: { x: string; y: string; }) => { // 转换点集合
|
|
|
+ return point.x + ',' + point.y
|
|
|
+ }).join(';')
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ /**
|
|
|
+ * 构建节点属性
|
|
|
+ * @param {}} node
|
|
|
+ * @returns
|
|
|
+ */
|
|
|
+ const buildNode = (node: any) => {
|
|
|
+ let field
|
|
|
+ if (node.properties.field && Object.keys(node.properties.field).length) {
|
|
|
+ field = {
|
|
|
+ name: 'ext',
|
|
|
+ displayName: '扩展属性',
|
|
|
+ attr: Object.keys(node.properties.field).map(key => {
|
|
|
+ return {
|
|
|
+ name: key,
|
|
|
+ value: node.properties.field[key]
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return {
|
|
|
+ name: node.id,
|
|
|
+ displayName: (node.text instanceof String || node.text === undefined) ? node.text : node.text.value,
|
|
|
+ layout: node.x + ',' + node.y + ',' + (node.properties.nodeSize?.width || node.properties.width || (node.type === 'hotline:subProcess' ? '500' : '120')) + ',' + (node.properties.nodeSize?.height || node.properties.height || (node.type === 'hotline:subProcess' ? '300' : '80')),
|
|
|
+ ...node.properties,
|
|
|
+ transition: getTransitions(node.id),
|
|
|
+ field
|
|
|
+ }
|
|
|
+ }
|
|
|
+ /**
|
|
|
+ * 特殊字符转义
|
|
|
+ * @param {*} text
|
|
|
+ * @returns
|
|
|
+ */
|
|
|
+ const textEncode = (text: string) => {
|
|
|
+ text = text.replace(/&/g, '&')
|
|
|
+ .replace(/"/g, '"')
|
|
|
+ .replace(/</g, '<')
|
|
|
+ .replace(/>/g, '>')
|
|
|
+ return text
|
|
|
+ }
|
|
|
+ /**
|
|
|
+ * 递归构建节点属性
|
|
|
+ * @param {} node
|
|
|
+ */
|
|
|
+ const recursionBuildNode = (node: any) => {
|
|
|
+ const nodeName = node.type.replace('hotline:', '')
|
|
|
+ if (!processObj[nodeName + '_' + node.id]) {
|
|
|
+ processObj[nodeName + '_' + node.id] = buildNode(node)
|
|
|
+ const nextNodes = getNextNodes(node.id)
|
|
|
+ nextNodes.forEach(nextNode => {
|
|
|
+ recursionBuildNode(nextNode)
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+ const startNode = getStartNode()
|
|
|
+ if (!startNode) {
|
|
|
+ // 开始节点不存在,xml不合法
|
|
|
+ return ''
|
|
|
+ }
|
|
|
+ recursionBuildNode(startNode)
|
|
|
+ xml = '<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n'
|
|
|
+ xml += '<process '
|
|
|
+ Object.keys(processObj).forEach(key => {
|
|
|
+ const value = processObj[key]
|
|
|
+ if (PROCESS_ATTR_KEYS.includes(key) && value) {
|
|
|
+ xml += ' ' + key + '=' + '"' + textEncode(value) + '"'
|
|
|
+ }
|
|
|
+ })
|
|
|
+ xml += '>\n'
|
|
|
+ // 生成节点xml
|
|
|
+ Object.keys(processObj).forEach(key => {
|
|
|
+ const value = processObj[key]
|
|
|
+ const nodeName = key.split('_')[0]
|
|
|
+ if (NODE_NAMES.includes(nodeName)) {
|
|
|
+ xml += '\t<' + nodeName
|
|
|
+ // 构造属性
|
|
|
+ Object.keys(value).forEach(nodeAttrKey => {
|
|
|
+ if (NODE_ATTR_KEYS.includes(nodeAttrKey) && value[nodeAttrKey]) {
|
|
|
+ xml += ' ' + nodeAttrKey + '=' + '"' + textEncode(value[nodeAttrKey]) + '"'
|
|
|
+ }
|
|
|
+ })
|
|
|
+ xml += '>\n\t'
|
|
|
+ // 构建transition
|
|
|
+ if (value.transition) {
|
|
|
+ value.transition.forEach((tran: any) => {
|
|
|
+ xml += '\t<transition'
|
|
|
+ // transition属性
|
|
|
+ Object.keys(tran).forEach(tranAttrKey => {
|
|
|
+ if (TRANSITION_ATTR_KEYS.includes(tranAttrKey) && tran[tranAttrKey]) {
|
|
|
+ xml += ' ' + tranAttrKey + '=' + '"' + textEncode(tran[tranAttrKey]) + '"'
|
|
|
+ }
|
|
|
+ })
|
|
|
+ xml += '>'
|
|
|
+ xml += '</transition>\n'
|
|
|
+ })
|
|
|
+ }
|
|
|
+ if (value.field && nodeName === 'task') {
|
|
|
+ xml += '\t<field name="' + value.field.name + '" displayName="' + value.field.displayName + '">\n'
|
|
|
+ value.field.attr.forEach((attrItem: any) => {
|
|
|
+ xml += '\t\t<attr name="' + attrItem.name + '" value="' + textEncode(attrItem.value) + '"></attr>\n'
|
|
|
+ })
|
|
|
+ xml += '\t</field>\n'
|
|
|
+ }
|
|
|
+ xml += '\t</' + nodeName + '>\n'
|
|
|
+ }
|
|
|
+ })
|
|
|
+ xml += '</process>'
|
|
|
+ // 构建子流程模型数据
|
|
|
+ const buildSubProcessList = () => {
|
|
|
+ const subProcessNodes = getSubProcessNodes()
|
|
|
+ const subProcessModel = subProcessNodes.map(n => {
|
|
|
+ const subModel = {
|
|
|
+ name: n.id, // 流程定义名称
|
|
|
+ displayName: n.properties.displayName, // 流程定义显示名称
|
|
|
+ instanceUrl: n.properties.instanceUrl, // 实例启动Url
|
|
|
+ expireTime: n.properties.expireTime, // 期望完成时间
|
|
|
+ instanceNoClass: n.properties.instanceNoClass, // 实例编号生成类
|
|
|
+ nodes: data.nodes.filter((item: any) => n.children.includes(item.id)),
|
|
|
+ edges: data.edges.filter((item: any) => n.children.includes(item.id))
|
|
|
+ }
|
|
|
+ subModel.nodes.forEach((item: any) => {
|
|
|
+ data.edges.forEach((edge: any) => {
|
|
|
+ if (edge.sourceNodeId === item.id) {
|
|
|
+ subModel.edges.push(edge)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ })
|
|
|
+ return subModel
|
|
|
+ })
|
|
|
+ return subProcessModel
|
|
|
+ }
|
|
|
+ const subProcessList = buildSubProcessList()
|
|
|
+ subProcessList.forEach((processModel: any) => {
|
|
|
+ xml += logicFlowJsonToSnakerXml(processModel).replace('<?xml version="1.0" encoding="UTF-8" standalone="no"?>', '')
|
|
|
+ })
|
|
|
+ return xml
|
|
|
+}
|