diff --git a/examples/approval-process-designer-react/package.json b/examples/approval-process-designer-react/package.json index 3089d20..e5557d1 100644 --- a/examples/approval-process-designer-react/package.json +++ b/examples/approval-process-designer-react/package.json @@ -1,6 +1,6 @@ { "name": "@trionesdev/approval-process-designer-react-example", - "version": "0.1.0", + "version": "0.0.1-beta.1", "private": true, "homepage": ".", "dependencies": { @@ -12,9 +12,9 @@ "@types/node": "^16.18.68", "@types/react": "^18.2.45", "@types/react-dom": "^18.2.17", + "antd": "^5.11.5", "craco-babel-loader": "^1.0.4", "craco-less": "^2.0.0", - "antd": "^5.11.5", "react": "^18.2.0", "react-dom": "^18.2.0", "react-scripts": "5.0.1", @@ -23,7 +23,7 @@ }, "scripts": { "start": "craco start", - "build": "PUBLIC_URL=. && craco build", + "build": "cross-env PUBLIC_URL=. && craco build", "test": "craco test", "eject": "react-scripts eject" }, diff --git a/examples/approval-process-designer-react/src/App.tsx b/examples/approval-process-designer-react/src/App.tsx index 5498e7a..64dc05a 100644 --- a/examples/approval-process-designer-react/src/App.tsx +++ b/examples/approval-process-designer-react/src/App.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {useState} from 'react'; import './App.css'; import { ApprovalProcessDesigner, GlobalStore, @@ -10,10 +10,7 @@ import {Watermark} from "antd"; import * as Icons from "./activities/Icons" function App() { - const handleOnChange = (value: any) => { - console.log("[processNode]", value) - } - const processNode: IProcessNode = { + const [data,setData] = useState({ type: 'START', componentName: 'StartActivity', title: '发起人', @@ -30,7 +27,7 @@ function App() { componentName: 'CcActivity', title: '抄送人', }, - children: [ + conditionNodes: [ { type: 'CONDITION', componentName: 'ConditionActivity', @@ -51,13 +48,17 @@ function App() { ] } } + }) + const handleOnChange = (value: any) => { + console.log("[processNode]", value) + setData(value) } GlobalStore.registerIcons(Icons); return (
- + = TdCcActivity +export const CcActivity: ActivityFC = TdCcActivity CcActivity.Resource = createResource({ type: 'CC', diff --git a/examples/approval-process-designer-react/src/activities/ConditionActivity.tsx b/examples/approval-process-designer-react/src/activities/ConditionActivity.tsx index d3a1386..f0a4f41 100644 --- a/examples/approval-process-designer-react/src/activities/ConditionActivity.tsx +++ b/examples/approval-process-designer-react/src/activities/ConditionActivity.tsx @@ -1,12 +1,12 @@ import { ActivityFC, ConditionActivity as TdConditionActivity, - DesignerCore + DesignerCore, IActivity } from "@trionesdev/approval-process-designer-react"; import createResource = DesignerCore.createResource; -export const ConditionActivity: ActivityFC = TdConditionActivity +export const ConditionActivity: ActivityFC = TdConditionActivity ConditionActivity.Resource = createResource({ type: 'CONDITION', componentName: 'ConditionActivity' diff --git a/examples/approval-process-designer-react/src/activities/RouteActivity.tsx b/examples/approval-process-designer-react/src/activities/RouteActivity.tsx index 2c0645b..153ed57 100644 --- a/examples/approval-process-designer-react/src/activities/RouteActivity.tsx +++ b/examples/approval-process-designer-react/src/activities/RouteActivity.tsx @@ -1,7 +1,12 @@ -import {ActivityFC, DesignerCore, RouteActivity as TdRouteActivity} from "@trionesdev/approval-process-designer-react"; +import { + ActivityFC, + DesignerCore, + IActivity, + RouteActivity as TdRouteActivity +} from "@trionesdev/approval-process-designer-react"; import createResource = DesignerCore.createResource; -export const RouteActivity: ActivityFC = TdRouteActivity +export const RouteActivity: ActivityFC = TdRouteActivity RouteActivity.Resource = createResource({ icon: 'RouteActivityIcon', diff --git a/examples/approval-process-designer-react/src/activities/StartActivity.tsx b/examples/approval-process-designer-react/src/activities/StartActivity.tsx index 5a69e45..83e8810 100644 --- a/examples/approval-process-designer-react/src/activities/StartActivity.tsx +++ b/examples/approval-process-designer-react/src/activities/StartActivity.tsx @@ -1,7 +1,12 @@ -import {ActivityFC, DesignerCore, StartActivity as TdStartActivity} from "@trionesdev/approval-process-designer-react"; +import { + ActivityFC, + DesignerCore, + IActivity, + StartActivity as TdStartActivity +} from "@trionesdev/approval-process-designer-react"; import createResource = DesignerCore.createResource; -export const StartActivity: ActivityFC = TdStartActivity +export const StartActivity: ActivityFC = TdStartActivity StartActivity.Resource = createResource({ type: 'START', componentName: 'StartActivity', diff --git a/images/img.png b/images/img.png new file mode 100644 index 0000000..bb08823 Binary files /dev/null and b/images/img.png differ diff --git a/images/shuque_wx.jpg b/images/shuque_wx.jpg new file mode 100644 index 0000000..95fa86f Binary files /dev/null and b/images/shuque_wx.jpg differ diff --git a/lerna.json b/lerna.json index f6604bd..1de568d 100644 --- a/lerna.json +++ b/lerna.json @@ -1,4 +1,4 @@ { "$schema": "node_modules/lerna/schemas/lerna-schema.json", - "version": "0.0.0" + "version": "0.0.1-beta.1" } diff --git a/package.json b/package.json index 9840f23..dd15cca 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ }, "dependencies": {}, "devDependencies": { - "lerna": "^8.0.0" + "lerna": "^8.0.0", + "cross-env": "^7.0.3" } } diff --git a/packages/approval-process-designer-react/package.json b/packages/approval-process-designer-react/package.json index 6308942..0374cdc 100644 --- a/packages/approval-process-designer-react/package.json +++ b/packages/approval-process-designer-react/package.json @@ -1,6 +1,6 @@ { "name": "@trionesdev/approval-process-designer-react", - "version": "0.0.1", + "version": "0.0.1-beta.1", "description": "", "module": "dist/esm/index.js", "types": "dist/esm/index.d.ts", diff --git a/packages/approval-process-designer-react/src/activity/Activity.tsx b/packages/approval-process-designer-react/src/activity/Activity.tsx index e0c0039..984312c 100644 --- a/packages/approval-process-designer-react/src/activity/Activity.tsx +++ b/packages/approval-process-designer-react/src/activity/Activity.tsx @@ -144,7 +144,9 @@ export const Activity: FC = observer(({ } } - const handleRemove = () => { + const handleRemove = (e: any) => { + e.stopPropagation(); + e.preventDefault(); processNode?.remove() } diff --git a/packages/approval-process-designer-react/src/activity/CcActivity.tsx b/packages/approval-process-designer-react/src/activity/CcActivity.tsx index 0e0057a..9230edd 100644 --- a/packages/approval-process-designer-react/src/activity/CcActivity.tsx +++ b/packages/approval-process-designer-react/src/activity/CcActivity.tsx @@ -1,12 +1,8 @@ import React, {FC} from "react" -import {ProcessNode} from "../model"; import {Activity} from "./Activity"; +import {IActivity} from "../types"; -type CcActivityProps = { - processNode: ProcessNode - nextActivity: React.ReactNode - onClick?: (processNode: ProcessNode) => void -} +type CcActivityProps = IActivity export const CcActivity: FC = ({ processNode, diff --git a/packages/approval-process-designer-react/src/activity/CondiitionActivity.tsx b/packages/approval-process-designer-react/src/activity/CondiitionActivity.tsx index a440880..1355313 100644 --- a/packages/approval-process-designer-react/src/activity/CondiitionActivity.tsx +++ b/packages/approval-process-designer-react/src/activity/CondiitionActivity.tsx @@ -1,13 +1,12 @@ import styled from "@emotion/styled"; import React, {createRef, FC, useEffect, useState} from "react"; -import {ProcessNode} from "../model"; import classNames from "classnames"; import {AddActivityBox} from "./AddActivityBox"; import {BranchBox} from "./BranchBox"; import {CloseIcon, QuestionIcon} from "../Icons"; import {Tooltip} from "../components"; import {IconWidget} from "../widget/IconWidget"; -import {GlobalStore} from "../store"; +import {IActivity} from "../types"; const ConditionActivityStyled = styled('div')({ boxSizing: 'border-box', @@ -134,11 +133,7 @@ const ConditionActivityStyled = styled('div')({ } }) -type ConditionActivityProps = { - processNode?: ProcessNode - nextActivity?: React.ReactNode - onClick?: (processNode: ProcessNode) => void -} +type ConditionActivityProps = IActivity export const ConditionActivity: FC = ({ processNode, diff --git a/packages/approval-process-designer-react/src/activity/RouteActivity.tsx b/packages/approval-process-designer-react/src/activity/RouteActivity.tsx index 48e0efa..b966da7 100644 --- a/packages/approval-process-designer-react/src/activity/RouteActivity.tsx +++ b/packages/approval-process-designer-react/src/activity/RouteActivity.tsx @@ -1,7 +1,7 @@ import React, {FC} from "react" import {RouteBranches} from "./RouteBranches"; import styled from "@emotion/styled"; -import {ProcessNode} from "../model"; +import {IActivity} from "../types"; const RouteActivityStyled = styled('div')({ display: 'flex', @@ -40,10 +40,8 @@ const RouteActivityStyled = styled('div')({ } }) -type RouteActivityProps = { +type RouteActivityProps = IActivity & { children?: React.ReactNode, - processNode?: ProcessNode, - nextActivity?: React.ReactNode, } export const RouteActivity: FC = ({children, processNode, nextActivity}) => { diff --git a/packages/approval-process-designer-react/src/activity/RouteBranches.tsx b/packages/approval-process-designer-react/src/activity/RouteBranches.tsx index 30b0840..c16f82b 100644 --- a/packages/approval-process-designer-react/src/activity/RouteBranches.tsx +++ b/packages/approval-process-designer-react/src/activity/RouteBranches.tsx @@ -1,7 +1,6 @@ import styled from "@emotion/styled"; import React, {FC} from "react"; import {AddActivityBox} from "./AddActivityBox"; -import * as process from "process"; import {ProcessNode} from "../model"; const RouteBranchesStyled = styled('div')({ diff --git a/packages/approval-process-designer-react/src/activity/StartActivity.tsx b/packages/approval-process-designer-react/src/activity/StartActivity.tsx index 2c58807..6b2c8f6 100644 --- a/packages/approval-process-designer-react/src/activity/StartActivity.tsx +++ b/packages/approval-process-designer-react/src/activity/StartActivity.tsx @@ -1,12 +1,8 @@ import React, {FC} from "react" import {Activity} from "./Activity"; -import {ProcessNode} from "../model"; +import {IActivity} from "../types"; -type StartActivityProps = { - processNode: ProcessNode - nextActivity: React.ReactNode - onClick?: (processNode: ProcessNode) => void -} +type StartActivityProps = IActivity export const StartActivity: FC = ({ processNode, diff --git a/packages/approval-process-designer-react/src/container/ApprovalProcessDesigner.tsx b/packages/approval-process-designer-react/src/container/ApprovalProcessDesigner.tsx index 5debdb6..b02dd78 100644 --- a/packages/approval-process-designer-react/src/container/ApprovalProcessDesigner.tsx +++ b/packages/approval-process-designer-react/src/container/ApprovalProcessDesigner.tsx @@ -1,12 +1,13 @@ -import React, {FC, useEffect} from "react" +import React, {FC, useEffect, useMemo, useState} from "react" import {ApprovalProcessContext} from "../context"; import {ApprovalProcessEngine} from "../model/ApprovalProcessEngine"; -import {ProcessNode} from "../model"; +import {IProcessNode} from "../model"; +import _ from "lodash"; type ApprovalProcessDesignerProps = { children?: React.ReactNode; engine?: ApprovalProcessEngine - value?: any + value?: IProcessNode onChange?: (value: any) => void } @@ -16,20 +17,30 @@ export const ApprovalProcessDesigner: FC = ({ value, onChange }) => { - let scopeEngine = engine; - if (!scopeEngine) { - scopeEngine = new ApprovalProcessEngine(); - } + const [scopeValue, setScopeValue] = useState(value) + + + let designerEngine = useMemo(() => { + let scopeEngine = engine; + if (!engine) { + scopeEngine = new ApprovalProcessEngine({value: value}); + } + return scopeEngine + }, [engine]) + + designerEngine?.setOnchange((value: any) => { + setScopeValue(value) + onChange?.(value) + }) - scopeEngine?.setOnchange(onChange) useEffect(() => { - if (value) { - scopeEngine.process.from(value) + if (value && !_.isEqual(value, scopeValue)) { + designerEngine.process.from(value) } }, [value]) - return + return {children} } \ No newline at end of file diff --git a/packages/approval-process-designer-react/src/model/ApprovalProcessEngine.ts b/packages/approval-process-designer-react/src/model/ApprovalProcessEngine.ts index a3b5d57..30d7aca 100644 --- a/packages/approval-process-designer-react/src/model/ApprovalProcessEngine.ts +++ b/packages/approval-process-designer-react/src/model/ApprovalProcessEngine.ts @@ -1,11 +1,11 @@ -import {ProcessNode} from "./ProcessNode"; +import {IProcessNode, ProcessNode} from "./ProcessNode"; import {define, observable} from "@formily/reactive"; import {GlobalStore} from "../store"; import {DesignerCore} from "../util"; import _ from "lodash"; interface IApprovalProcessEngine { - + value?: IProcessNode } export class ApprovalProcessEngine { @@ -18,7 +18,10 @@ export class ApprovalProcessEngine { type: 'START', componentName: 'StartActivity', title: '开始' - }); + }) + if (engine?.value) { + this.process.from(engine.value) + } this.makeObservable() } @@ -29,9 +32,9 @@ export class ApprovalProcessEngine { }) } - handleChange = _.debounce((msg: any)=>{ + handleChange = _.debounce((msg: any) => { this.onChange?.(DesignerCore.transformToSchema(this.process)) - },100) + }, 100) setOnchange(fn: (value: any) => void) { this.onChange = fn diff --git a/packages/approval-process-designer-react/src/model/ProcessNode.ts b/packages/approval-process-designer-react/src/model/ProcessNode.ts index 9e25298..32ecfc1 100644 --- a/packages/approval-process-designer-react/src/model/ProcessNode.ts +++ b/packages/approval-process-designer-react/src/model/ProcessNode.ts @@ -13,7 +13,7 @@ export interface IProcessNode { type: ProcessNodeType componentName?: string nextNode?: IProcessNode - children?: IProcessNode[] + conditionNodes?: IProcessNode[] title?: string description?: string props?: any @@ -29,7 +29,7 @@ export class ProcessNode { componentName: string prevNodeId: string nextNode: ProcessNode - children: ProcessNode[] + conditionNodes: ProcessNode[] title: string description: string props: any @@ -45,7 +45,7 @@ export class ProcessNode { this.componentName = node.componentName || node.type this.prevNodeId = parentNode?.id this.nextNode = null - this.children = [] + this.conditionNodes = [] this.title = node.title this.description = node.description this.props = node.props @@ -64,12 +64,12 @@ export class ProcessNode { title: observable.ref, description: observable.ref, nextNode: observable.ref, - children: observable.shallow, + conditionNodes: observable.shallow, props: observable }) reaction(() => { - return this.prevNodeId + this.title + this.description + this.nextNode?.id + this.children.length + return this.prevNodeId + this.title + this.description + this.nextNode?.id + this.conditionNodes.length }, () => { if (!this.isSourceNode) { this.engine.handleChange(`${this.id} something changed`) @@ -89,19 +89,22 @@ export class ProcessNode { this.nextNode = null return } + + node.nextNode = this.nextNode - this.nextNode = node node.prevNodeId = this.id + this.nextNode = node + } - setChildren(nodes: ProcessNode[]) { + setConditionNodes(nodes: ProcessNode[]) { if (_.isEmpty(nodes)) { return } _.forEach(nodes, (node) => { node.prevNodeId = this.id }) - this.children = nodes + this.conditionNodes = nodes } from(node?: IProcessNode) { @@ -123,8 +126,8 @@ export class ProcessNode { if (node.nextNode) { this.nextNode = new ProcessNode(node.nextNode, this) } - if (node.children && node.children.length > 0) { - this.children = node.children?.map((node) => { + if (node.conditionNodes && node.conditionNodes.length > 0) { + this.conditionNodes = node.conditionNodes?.map((node) => { return new ProcessNode(node, this) }) || [] } @@ -143,18 +146,27 @@ export class ProcessNode { const condition1 = conditionResource.node.clone(node) const conditionDefault = conditionResource.node.clone(node) conditionDefault.props = _.assign(conditionDefault.props, {defaultCondition: true}) - node.setChildren([condition1, conditionDefault]) + node.setConditionNodes([condition1, conditionDefault]) } return node } + cloneDeep(node?: ProcessNode) { + if (!node) { + return + } + const cloneNode = _.cloneDeep(node) + ProcessNodes.set(cloneNode.id, cloneNode) + return cloneNode; + } + remove() { const parentNode = ProcessNodes.get(this.prevNodeId) if (this.type == "CONDITION") { //当前节点是条件节点 const linkedIds = this.collectLinkIds() - if (parentNode.children.length > 2) { //当分支超过2个时,只需要删除当前节点,否则,清除整个路由节点 - parentNode.children = _.filter(parentNode.children, (conditionNode: any) => { + if (parentNode.conditionNodes.length > 2) { //当分支超过2个时,只需要删除当前节点,否则,清除整个路由节点 + parentNode.conditionNodes = _.filter(parentNode.conditionNodes, (conditionNode: any) => { return conditionNode.id !== this.id }) } else { @@ -166,7 +178,10 @@ export class ProcessNode { ProcessNodes.delete(id) }) } else { - parentNode?.setNextNode(this.nextNode) + if (this.nextNode) { + this.nextNode.prevNodeId = this.prevNodeId + } + parentNode.nextNode = this.nextNode ProcessNodes.delete(this.id) } } @@ -180,8 +195,8 @@ export class ProcessNode { } const conditionActivity = GlobalStore.getConditionActivityResource()?.node.clone(this) if (conditionActivity) { - const newChildren = _.concat(this.children.slice(0, this.children.length - 1), conditionActivity, this.children.slice(this.children.length - 1)) - this.setChildren(newChildren) + const newChildren = _.concat(this.conditionNodes.slice(0, this.conditionNodes.length - 1), conditionActivity, this.conditionNodes.slice(this.conditionNodes.length - 1)) + this.setConditionNodes(newChildren) } } @@ -194,8 +209,8 @@ export class ProcessNode { ids.push(this.nextNode.id) ids = ids.concat(this.nextNode.collectLinkIds()) } - if (this.children && this.children.length > 0) { - this.children.forEach(child => { + if (this.conditionNodes && this.conditionNodes.length > 0) { + this.conditionNodes.forEach(child => { ids.push(child.id) ids = ids.concat(child.collectLinkIds()) }) @@ -208,7 +223,7 @@ export class ProcessNode { if (this.type === 'CONDITION') { const parentNode = ProcessNodes.get(this.prevNodeId) if (parentNode) { - return parentNode.children?.indexOf(this) || 0 + return parentNode.conditionNodes?.indexOf(this) || 0 } } return null @@ -226,6 +241,6 @@ export class ProcessNode { return false } const parentNode = ProcessNodes.get(this.prevNodeId) - return this.index === ((parentNode?.children?.length || 0) - 1) + return this.index === ((parentNode?.conditionNodes?.length || 0) - 1) } } \ No newline at end of file diff --git a/packages/approval-process-designer-react/src/util.ts b/packages/approval-process-designer-react/src/util.ts index 32b6096..b96403c 100644 --- a/packages/approval-process-designer-react/src/util.ts +++ b/packages/approval-process-designer-react/src/util.ts @@ -23,13 +23,14 @@ export namespace DesignerCore { } return { id: processNode.id, + prevNodeId: processNode.prevNodeId, type: processNode.type, componentName: processNode.componentName, title: processNode.title, description: processNode.description, props: processNode.props, nextNode: toSchema(processNode.nextNode), - children: processNode.children?.map(toSchema) || [] + conditionNodes: processNode.conditionNodes?.map(toSchema) || [] } } diff --git a/packages/approval-process-designer-react/src/widget/ActivityWidget.tsx b/packages/approval-process-designer-react/src/widget/ActivityWidget.tsx index 1f09b86..04fb542 100644 --- a/packages/approval-process-designer-react/src/widget/ActivityWidget.tsx +++ b/packages/approval-process-designer-react/src/widget/ActivityWidget.tsx @@ -1,4 +1,4 @@ -import React, {Component, FC} from "react"; +import React, {FC} from "react"; import {ProcessNode} from "../model"; import {observer} from "@formily/react"; import {useActivities} from "../hooks"; @@ -20,8 +20,8 @@ export const ActivityWidget: FC = observer(({ const Activity: ActivityFC = _.get(activities, [processNode.componentName]); const renderChildren = () => { - if (processNode.children.length > 0) { - return processNode.children.map((child, index) => + if (processNode.conditionNodes.length > 0) { + return processNode.conditionNodes.map((child, index) => ) } else { return [] diff --git a/packages/approval-process-designer-react/src/widget/AddActivityItemWidget.tsx b/packages/approval-process-designer-react/src/widget/AddActivityItemWidget.tsx index 9a0dd99..3b70f43 100644 --- a/packages/approval-process-designer-react/src/widget/AddActivityItemWidget.tsx +++ b/packages/approval-process-designer-react/src/widget/AddActivityItemWidget.tsx @@ -41,7 +41,6 @@ export const AddActivityItemWidget: FC = ({ const handleClick = () => { onClick?.(processNode) const activity = GlobalStore.getActivityResource(resource?.componentName) - debugger processNode.setNextNode(activity?.node.clone(processNode)) } return diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..150e5e4 --- /dev/null +++ b/readme.md @@ -0,0 +1,16 @@ +# 审批流设计器 + +> 参考钉钉的交互模式,开发的审批流设计器,支持自定义扩展 + + +![img.png](images/img.png) + +## 功能 +- 支持 发起,审批,抄送,条件分支 节点类型 +- 支持自定义节点类型 +- 支持扩展节点属性 + +#### 互相吹捧,共同进步 +
+ +
\ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 08a89e8..1b7d40c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5335,7 +5335,14 @@ create-require@^1.1.0: resolved "https://moensun-npm.pkg.coding.net/npm/moensun/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== -cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: +cross-env@^7.0.3: + version "7.0.3" + resolved "https://moensun-npm.pkg.coding.net/npm/moensun/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf" + integrity sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw== + dependencies: + cross-spawn "^7.0.1" + +cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://moensun-npm.pkg.coding.net/npm/moensun/cross-spawn/-/cross-spawn-7.0.3.tgz" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==