diff --git a/examples/approval-process-designer-react/src/App.tsx b/examples/approval-process-designer-react/src/App.tsx index 3b8d08b..5498e7a 100644 --- a/examples/approval-process-designer-react/src/App.tsx +++ b/examples/approval-process-designer-react/src/App.tsx @@ -1,47 +1,51 @@ import React from 'react'; import './App.css'; import { - ApprovalProcessDesigner, + ApprovalProcessDesigner, GlobalStore, IProcessNode, ProcessWidget, StudioPanel } from "@trionesdev/approval-process-designer-react"; -import {ApprovalActivity, ConditionActivity, RouteActivity, StartActivity,CcActivity} from "./activities"; +import {ApprovalActivity, ConditionActivity, RouteActivity, StartActivity, CcActivity} from "./activities"; +import {Watermark} from "antd"; +import * as Icons from "./activities/Icons" function App() { - + const handleOnChange = (value: any) => { + console.log("[processNode]", value) + } const processNode: IProcessNode = { type: 'START', componentName: 'StartActivity', - title: '开始', - nextNode:{ + title: '发起人', + nextNode: { type: 'APPROVAL', componentName: 'ApprovalActivity', title: '审批', - nextNode:{ - type:'ROUTE', - componentName:'RouteActivity', - title:'路由', - nextNode:{ + nextNode: { + type: 'ROUTE', + componentName: 'RouteActivity', + title: '路由', + nextNode: { type: 'CC', componentName: 'CcActivity', title: '抄送人', }, - children:[ + children: [ { - type:'CONDITION', - componentName:'ConditionActivity', - title:'条件1', - nextNode:{ + type: 'CONDITION', + componentName: 'ConditionActivity', + title: '条件1', + nextNode: { type: 'APPROVAL', componentName: 'ApprovalActivity', title: '审批人', } }, { - type:'CONDITION', - componentName:'ConditionActivity', - props:{ - defaultCondition:true, + type: 'CONDITION', + componentName: 'ConditionActivity', + props: { + defaultCondition: true, } } ] @@ -49,14 +53,23 @@ function App() { } } + GlobalStore.registerIcons(Icons); return ( - <> - - - - - - +
+ + + + + + + +
); } diff --git a/examples/approval-process-designer-react/src/activities/ApprovalActivity.tsx b/examples/approval-process-designer-react/src/activities/ApprovalActivity.tsx index 2028962..a889b2d 100644 --- a/examples/approval-process-designer-react/src/activities/ApprovalActivity.tsx +++ b/examples/approval-process-designer-react/src/activities/ApprovalActivity.tsx @@ -8,6 +8,7 @@ import createResource = DesignerCore.createResource; export const ApprovalActivity : ActivityFC = TdApprovalActivity ApprovalActivity.Resource = createResource({ + icon:'ApprovalActivityIcon', type: 'APPROVAL', componentName:'ApprovalActivity', title:'审批人', diff --git a/examples/approval-process-designer-react/src/activities/CcActivity.tsx b/examples/approval-process-designer-react/src/activities/CcActivity.tsx index e73794c..9a0791e 100644 --- a/examples/approval-process-designer-react/src/activities/CcActivity.tsx +++ b/examples/approval-process-designer-react/src/activities/CcActivity.tsx @@ -5,7 +5,7 @@ export const CcActivity: ActivityFC = TdCcActivity CcActivity.Resource = createResource({ type: 'CC', - icon: '', + icon: 'CcActivityIcon', componentName: 'CcActivity', title: '抄送人', addable: true diff --git a/examples/approval-process-designer-react/src/activities/ConditionActivity.tsx b/examples/approval-process-designer-react/src/activities/ConditionActivity.tsx index 72571c7..d3a1386 100644 --- a/examples/approval-process-designer-react/src/activities/ConditionActivity.tsx +++ b/examples/approval-process-designer-react/src/activities/ConditionActivity.tsx @@ -9,7 +9,5 @@ import createResource = DesignerCore.createResource; export const ConditionActivity: ActivityFC = TdConditionActivity ConditionActivity.Resource = createResource({ type: 'CONDITION', - icon: '', - componentName: 'ConditionActivity', - title:'条件' + componentName: 'ConditionActivity' }) \ No newline at end of file diff --git a/examples/approval-process-designer-react/src/activities/Icons.tsx b/examples/approval-process-designer-react/src/activities/Icons.tsx new file mode 100644 index 0000000..8899306 --- /dev/null +++ b/examples/approval-process-designer-react/src/activities/Icons.tsx @@ -0,0 +1,43 @@ +export const ApprovalActivityIcon = ( + + 审批人 + + + + + + + +) +export const RouteActivityIcon = ( + + 条件分支 + + + + + + + +) + +export const CcActivityIcon = ( + + 抄送人 + + + + + + + +) \ No newline at end of file diff --git a/examples/approval-process-designer-react/src/activities/RouteActivity.tsx b/examples/approval-process-designer-react/src/activities/RouteActivity.tsx index d21db09..2c0645b 100644 --- a/examples/approval-process-designer-react/src/activities/RouteActivity.tsx +++ b/examples/approval-process-designer-react/src/activities/RouteActivity.tsx @@ -4,6 +4,7 @@ import createResource = DesignerCore.createResource; export const RouteActivity: ActivityFC = TdRouteActivity RouteActivity.Resource = createResource({ + icon: 'RouteActivityIcon', type: 'ROUTE', componentName: 'RouteActivity', title: '条件分支', diff --git a/packages/approval-process-designer-react/src/activity/Activity.tsx b/packages/approval-process-designer-react/src/activity/Activity.tsx index 33b50e2..08125a2 100644 --- a/packages/approval-process-designer-react/src/activity/Activity.tsx +++ b/packages/approval-process-designer-react/src/activity/Activity.tsx @@ -5,6 +5,7 @@ import {ProcessNode} from "../model"; import {CloseIcon, RightIcon} from "../Icons"; import {AddActivityBox} from "./AddActivityBox"; import {IconWidget} from "../widget/IconWidget"; +import {observer} from "@formily/react"; const ActivityStyled = styled('div')({ boxSizing: 'border-box', @@ -109,22 +110,34 @@ export type ActivityProps = { closeable?: boolean onClick?: () => void } -export const Activity: FC = ({ - children, - processNode, - titleStyle, - titleEditable, - onChange, - closeable, - onClick, - }) => { +export const Activity: FC = observer(({ + children, + processNode, + titleStyle, + titleEditable, + onChange, + closeable, + onClick, + }) => { const inputRef = createRef() const [editing, setEditing] = useState(false) + + const handleSave = (value: any) => { + processNode.title = value + setEditing(false) + } + const handleInputBlur = (e: any) => { if (onChange) { onChange(e.target.value) } - setEditing(false) + handleSave(e.target.value) + } + + const handleKeyDown = (e: any) => { + if (e.keyCode == 13) { + handleSave(e.target.value) + } } const handleRemove = () => { @@ -143,7 +156,8 @@ export const Activity: FC = ({
- {editing ? : + {editing ? : { e.stopPropagation(); @@ -162,4 +176,4 @@ export const Activity: FC = ({
-} \ No newline at end of file +}) \ No newline at end of file diff --git a/packages/approval-process-designer-react/src/activity/CondiitionActivity.tsx b/packages/approval-process-designer-react/src/activity/CondiitionActivity.tsx index a4dd514..64c45b9 100644 --- a/packages/approval-process-designer-react/src/activity/CondiitionActivity.tsx +++ b/packages/approval-process-designer-react/src/activity/CondiitionActivity.tsx @@ -194,7 +194,7 @@ export const ConditionActivity: FC = ({ defaultValue={processNode?.title || `条件${(index || 0) + 1}`} onBlur={handleInputBlur}/> : <> setEditing(true)}>{processNode?.title} + onClick={() => setEditing(true)}>{processNode?.title || `条件${(index || 0) + 1}`} 优先级{(index || 0) + 1} diff --git a/packages/approval-process-designer-react/src/activity/RouteActivity.tsx b/packages/approval-process-designer-react/src/activity/RouteActivity.tsx index cdf5cc0..48e0efa 100644 --- a/packages/approval-process-designer-react/src/activity/RouteActivity.tsx +++ b/packages/approval-process-designer-react/src/activity/RouteActivity.tsx @@ -53,7 +53,7 @@ export const RouteActivity: FC = ({children, processNode, ne } return <> - + {children} diff --git a/packages/approval-process-designer-react/src/container/ApprovalProcessDesigner.tsx b/packages/approval-process-designer-react/src/container/ApprovalProcessDesigner.tsx index 62eaf3b..5debdb6 100644 --- a/packages/approval-process-designer-react/src/container/ApprovalProcessDesigner.tsx +++ b/packages/approval-process-designer-react/src/container/ApprovalProcessDesigner.tsx @@ -7,21 +7,25 @@ type ApprovalProcessDesignerProps = { children?: React.ReactNode; engine?: ApprovalProcessEngine value?: any + onChange?: (value: any) => void } export const ApprovalProcessDesigner: FC = ({ children, engine, - value + value, + onChange }) => { let scopeEngine = engine; if (!scopeEngine) { scopeEngine = new ApprovalProcessEngine(); } + scopeEngine?.setOnchange(onChange) + useEffect(() => { if (value) { - scopeEngine.processNode.from(value) + scopeEngine.process.from(value) } }, [value]) diff --git a/packages/approval-process-designer-react/src/context.tsx b/packages/approval-process-designer-react/src/context.tsx index 9ced82d..d7af28b 100644 --- a/packages/approval-process-designer-react/src/context.tsx +++ b/packages/approval-process-designer-react/src/context.tsx @@ -1,7 +1,10 @@ import {createContext} from "react"; import {ApprovalProcessEngine} from "./model/ApprovalProcessEngine"; import {IActivities} from "./types"; +import {ProcessNode} from "./model"; export const ApprovalProcessContext = createContext(null) -export const ActivitiesContext = createContext(null) \ No newline at end of file +export const ActivitiesContext = createContext(null) + +export const ProcessNodeContext = createContext(null) \ No newline at end of file diff --git a/packages/approval-process-designer-react/src/hooks/index.tsx b/packages/approval-process-designer-react/src/hooks/index.tsx index fab19a0..02ad761 100644 --- a/packages/approval-process-designer-react/src/hooks/index.tsx +++ b/packages/approval-process-designer-react/src/hooks/index.tsx @@ -1,2 +1,4 @@ export * from "./useProcessEngine" -export * from "./useActivities" \ No newline at end of file +export * from "./useActivities" +export * from "./useProcess" +export * from "./useProcessNode" \ No newline at end of file diff --git a/packages/approval-process-designer-react/src/hooks/useProcess.tsx b/packages/approval-process-designer-react/src/hooks/useProcess.tsx new file mode 100644 index 0000000..218038a --- /dev/null +++ b/packages/approval-process-designer-react/src/hooks/useProcess.tsx @@ -0,0 +1,5 @@ +import {useProcessEngine} from "./useProcessEngine"; + +export const useProcess = () => { + return useProcessEngine().process +} \ No newline at end of file diff --git a/packages/approval-process-designer-react/src/hooks/useProcessNode.tsx b/packages/approval-process-designer-react/src/hooks/useProcessNode.tsx index 3eeba09..f688dc2 100644 --- a/packages/approval-process-designer-react/src/hooks/useProcessNode.tsx +++ b/packages/approval-process-designer-react/src/hooks/useProcessNode.tsx @@ -1,5 +1,6 @@ -import {useProcessEngine} from "./useProcessEngine"; +import {useContext} from "react"; +import {ProcessNodeContext} from "../context"; export const useProcessNode = () => { - return useProcessEngine().processNode + return useContext(ProcessNodeContext) } \ No newline at end of file diff --git a/packages/approval-process-designer-react/src/index.ts b/packages/approval-process-designer-react/src/index.ts index eedbe0d..e48baf9 100644 --- a/packages/approval-process-designer-react/src/index.ts +++ b/packages/approval-process-designer-react/src/index.ts @@ -4,4 +4,5 @@ export * from "./activity" export * from "./widget" export * from "./panel" export * from "./types" -export * from "./util" \ No newline at end of file +export * from "./util" +export * from "./store" \ 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 89f0af8..a3b5d57 100644 --- a/packages/approval-process-designer-react/src/model/ApprovalProcessEngine.ts +++ b/packages/approval-process-designer-react/src/model/ApprovalProcessEngine.ts @@ -1,16 +1,20 @@ import {ProcessNode} from "./ProcessNode"; -import {action, define, observable} from "@formily/reactive"; +import {define, observable} from "@formily/reactive"; import {GlobalStore} from "../store"; +import {DesignerCore} from "../util"; +import _ from "lodash"; interface IApprovalProcessEngine { } export class ApprovalProcessEngine { - processNode: ProcessNode; + process: ProcessNode; + onChange?: (value: any) => void constructor(engine?: IApprovalProcessEngine) { - this.processNode = new ProcessNode({ + this.process = new ProcessNode({ + engine: this, type: 'START', componentName: 'StartActivity', title: '开始' @@ -20,11 +24,19 @@ export class ApprovalProcessEngine { makeObservable() { define(this, { - processNode: observable, + process: observable, addableActivityResources: observable.computed }) } + handleChange = _.debounce((msg: any)=>{ + this.onChange?.(DesignerCore.transformToSchema(this.process)) + },100) + + setOnchange(fn: (value: any) => void) { + this.onChange = fn + } + get addableActivityResources() { return GlobalStore.getAddableActivityResources() } diff --git a/packages/approval-process-designer-react/src/model/ProcessNode.ts b/packages/approval-process-designer-react/src/model/ProcessNode.ts index b42bcc7..3248b63 100644 --- a/packages/approval-process-designer-react/src/model/ProcessNode.ts +++ b/packages/approval-process-designer-react/src/model/ProcessNode.ts @@ -1,11 +1,14 @@ -import {define, observable} from "@formily/reactive"; +import {autorun, define, observable, observe, reaction} from "@formily/reactive"; import randomstring from "randomstring" import _ from "lodash"; import {GlobalStore} from "../store"; +import {ApprovalProcessEngine} from "./ApprovalProcessEngine"; export type ProcessNodeType = 'START' | 'ROUTE' | 'CONDITION' | 'APPROVAL' | 'CC' | 'END' export interface IProcessNode { + engine?: ApprovalProcessEngine + isSourceNode?: boolean id?: string type: ProcessNodeType componentName?: string @@ -19,6 +22,8 @@ export interface IProcessNode { const ProcessNodes = new Map() export class ProcessNode { + engine: ApprovalProcessEngine + isSourceNode: boolean id: string type: ProcessNodeType componentName: string @@ -30,6 +35,8 @@ export class ProcessNode { props: any constructor(node: IProcessNode, parentNode?: ProcessNode) { + this.engine = node.engine + this.isSourceNode = node.isSourceNode this.id = node.id || `Activity_${randomstring.generate({ length: 10, charset: 'alphabetic' @@ -42,6 +49,7 @@ export class ProcessNode { this.title = node.title this.description = node.description this.props = node.props + this.engine = parentNode?.engine ProcessNodes.set(this.id, this) if (node) { @@ -52,10 +60,28 @@ export class ProcessNode { makeObservable() { define(this, { + prevNodeId: observable.ref, + title: observable.ref, + description: observable.ref, nextNode: observable.ref, children: observable.shallow, props: observable }) + + reaction(() => { + return this.prevNodeId + this.title + this.description + this.nextNode?.id + this.children.length + }, () => { + if (!this.isSourceNode) { + this.engine.handleChange(`${this.id} something changed`) + } + }) + + observe(this.props, (change) => { + if (!this.isSourceNode) { + this.engine.handleChange(`${this.id} props changed`) + } + }) + } setNextNode(node: ProcessNode) { @@ -85,7 +111,14 @@ export class ProcessNode { ProcessNodes.set(node.id, this) this.id = node.id } + this.type = node.type + this.componentName = node.componentName || node.type + this.title = node.title + this.description = node.description this.props = node.props ?? {} + if (node.engine) { + this.engine = node.engine + } if (node.nextNode) { this.nextNode = new ProcessNode(node.nextNode, this) diff --git a/packages/approval-process-designer-react/src/util.ts b/packages/approval-process-designer-react/src/util.ts index 6c6d381..32b6096 100644 --- a/packages/approval-process-designer-react/src/util.ts +++ b/packages/approval-process-designer-react/src/util.ts @@ -6,6 +6,7 @@ export namespace DesignerCore { export function createResource(resource: IResourceCreator): IResource { return _.assign(resource, { node: new ProcessNode({ + isSourceNode: true, type: resource.type, componentName: resource.componentName, title: resource.title, @@ -14,4 +15,24 @@ export namespace DesignerCore { }) }) } + + export function transformToSchema(processNode: ProcessNode) { + function toSchema(processNode: ProcessNode) { + if (!processNode) { + return null + } + return { + id: processNode.id, + type: processNode.type, + componentName: processNode.componentName, + title: processNode.title, + description: processNode.description, + props: processNode.props, + nextNode: toSchema(processNode.nextNode), + children: processNode.children?.map(toSchema) || [] + } + } + + return toSchema(processNode) + } } \ No newline at end of file diff --git a/packages/approval-process-designer-react/src/widget/ActivityWidget.tsx b/packages/approval-process-designer-react/src/widget/ActivityWidget.tsx index 7ca9c6d..c8ddeef 100644 --- a/packages/approval-process-designer-react/src/widget/ActivityWidget.tsx +++ b/packages/approval-process-designer-react/src/widget/ActivityWidget.tsx @@ -4,6 +4,7 @@ import {observer} from "@formily/react"; import {useActivities} from "../hooks"; import {ActivityFC} from "../types"; import _ from "lodash" +import {ProcessNodeContext} from "../context"; type ActivityWidgetProps = { processNode: ProcessNode; @@ -43,5 +44,5 @@ export const ActivityWidget: FC = observer(({ } } - return <>{handleRender()} + return {handleRender()} }) \ No newline at end of file diff --git a/packages/approval-process-designer-react/src/widget/AddActivityItemWidget.tsx b/packages/approval-process-designer-react/src/widget/AddActivityItemWidget.tsx index 2566441..9a0dd99 100644 --- a/packages/approval-process-designer-react/src/widget/AddActivityItemWidget.tsx +++ b/packages/approval-process-designer-react/src/widget/AddActivityItemWidget.tsx @@ -17,10 +17,14 @@ const ActivityCardWidgetStyled = styled('div')({ padding: '10px', boxSizing: 'border-box', background: 'rgba(17, 31, 44, 0.02)', + gap: '8px', [`&:hover`]: { background: '#FFFFFF', border: '1px solid #ecedef', boxShadow: '0 2px 8px 0 rgba(17, 31, 44, 0.08)' + }, + '.activity-icon': { + fontSize: '28px' } }) @@ -41,7 +45,7 @@ export const AddActivityItemWidget: FC = ({ processNode.setNextNode(activity?.node.clone(processNode)) } return - +
{resource?.title}
} \ No newline at end of file diff --git a/packages/approval-process-designer-react/src/widget/ProcessWidget.tsx b/packages/approval-process-designer-react/src/widget/ProcessWidget.tsx index 84e368d..b51bd61 100644 --- a/packages/approval-process-designer-react/src/widget/ProcessWidget.tsx +++ b/packages/approval-process-designer-react/src/widget/ProcessWidget.tsx @@ -1,7 +1,7 @@ import {IActivities} from "../types"; import React, {FC, useEffect} from "react"; import {ActivityWidget} from "./ActivityWidget"; -import {useProcessNode} from "../hooks/useProcessNode"; +import {useProcess} from "../hooks/useProcess"; import {ActivitiesContext} from "../context"; import {EndActivity} from "../activity"; import styled from "@emotion/styled"; @@ -20,7 +20,7 @@ type ProcessWidgetProps = { export const ProcessWidget: FC = ({ activities }) => { - const processNode = useProcessNode() + const processNode = useProcess() GlobalStore.registerActivityResources(activities)