React + XFLOW搭建工作流项目

1、效果图:

2、项目技术栈:React +Xflow +Formily

3、项目结构

4、项目整体技术流程

**

**

  1. 技术方案主要参考xflow流程图解决方案
  2. 前端设计好流程图及节点的相关属性,保存时需要将原始json数据转换成后端所需要的XML数据,参考bpmn规范
  3. 点击设计流程后会进入xflow流程设计页面
  4. 右侧的属性面板为左侧节点拖拽到画布当中点击后的自定义属性,全部采用json-schema的形式
  5. 保存流程图数据
  6. 保存时转换成bpmn所需要的XML

5、技术点具体讲解

  • 工作流主页面设计

主画布FlowchartCanvas,主画布内置了一些常用的交互事件和默认配置,可通过config进行调整。使用通用流程图组件FlowchartNodePanel,除了内置的常用节点外,提供了便捷的自定义能力,可快速定制业务节点。

css 复制代码
  <XFlowContext.Provider value={{formSchema: formSchema}}>                <XFlow                  className={`flow-user-custom-clz ${pathname === '/previewFlow' ? 'preview-flow-clz' : ''}                   ${pathname === '/flowDetail' ? 'flow-detail-clz' : ''}`}                  commandConfig={commandConfig}                  onLoad={onLoad}                  meta={currentMeta}                >                    <FlowchartExtension />                    {/* 左侧操作栏 */}                    {/* 查看  预览不展示 */}                    <FlowchartNodePanel                        show={true}                        showOfficial={false}  // 是否展示通用面板                        registerNode={registerNode}                        defaultActiveKey={['start-list', 'task-list', 'gateway-list', 'end-list', 'process-list']}                        position={{ width: (pathname !== '/previewFlow' && pathname !== '/flowDetail') && 220, top: 88, bottom: 0, left: 0 }}                    />                     {/* 最顶层保存、预览栏 */}                    {/* 预览 保存 编辑流程名称 返回 */}                         <div className={'top-flow-operate'} style={{ cursor: 'pointer', zIndex: 100, position: 'absolute', top: '3px' }} >                        <span className={'flow-left-icon'} style={{ position: 'absolute', top: '12px',  display: 'inline-block', width: 16, height: 16 }} onClick={() => goBackPage()}>                            <LeftOutlined />                        </span>                        <CanvasService />                    </div>                    {/* 顶层流程操作栏 */}                    <CanvasToolbar                        className='xflow-workspace-toolbar-operate'                        layout='horizontal'                        config={toolbarConfigOperate}                        position={{ top: 0, left: 0, bottom: 0 }}                    />                    {/* 置前、框选等操作栏 */}                    {/* 预览和查看流程页不展示 */}                    {                        pathname !== '/flowDetail' && pathname !== '/previewFlow' &&                         <div className={'flow-customize-header'}>                            <p style={{                                 position: 'absolute',                                top: '10px',                                left: '10px',                                zIndex: 100,                                margin: '0px',                                fontWeight: 500,                                fontSize: '14px',                                lineHeight: '22px'                            }}>组件库</p>                            <CanvasToolbar                                layout='horizontal'//展示的方式                                config={toolbarConfig}                                position={{ top: 0, left: 219, right: 0, bottom: 0 }}                                style={{ borderLeft: '1px solid rgba(229, 230, 235, 1)' }}                            />                        </div>                    }                    {/* 右侧属性操作栏 预览页不展示 */}                    {                        pathname !== '/previewFlow' &&                         <FlowchartFormPanel                            style={{ border: '1px solid rgba(229, 230, 235, 1)', height: '100%' }}                            position={{ width: 320, top: pathname !== '/flowDetail' ? 93 : 52, bottom: 0, right: 0 }}                            controlMapService={controlMapService}//注册自定义Form组件                            formSchemaService={NsJsonForm.formSchemaService}//控制面板切换逻辑                        />                    }                    <KeyBindings config={keybindingConfig} />                    {/* 查看详情页不支持画布和节点拖拽 */}                    <FlowchartCanvas                      position={{ top: 40, left: 0, right: 0, bottom: 0 }}                      config={{                        interacting: detailPage ? false : true,                        grid: {                            visible: true,                        }, // 是否展示网格                      }}                    >                        <div className={'bottom-operate-container'}>                            <CanvasScaleToolbar                                 layout='horizontal'                                position={{ top: calcHeight, right: 340 }}                                className={'footer-canvas-operate'}                                style={{                                    left: 'auto',                                }}                            />                        </div>                        <CanvasContextMenu config={menuConfig} />                        <CanvasSnapline color='#faad14' />                        <CanvasNodePortTooltip />                     </FlowchartCanvas>                </XFlow>            </XFlowContext.Provider>
  • 左侧自定义组件(节点都是自定义)

用空启动事件举例:

arduino 复制代码
import {NsGraph} from "@antv/xflow-core";import {DefaultNodeConfig, NODE_HEIGHT, NODE_WIDTH, XFLOWTHEME} from "../../constants";import React from "react";/** * 空启动事件 * @param props * @constructor */export const NoneStartNode: NsGraph.INodeRender = (props) => {    const { size = { width: NODE_WIDTH, height: NODE_HEIGHT }, data = {} } = props;    const {        stroke = DefaultNodeConfig.stroke,        label = DefaultNodeConfig.label,        fill = DefaultNodeConfig.fill,        fontFill = DefaultNodeConfig.fontFill,        fontSize = DefaultNodeConfig.fontSize,        showNodeStyle = true,    } = data;    const { width, height } = size;    return (        <svg width={width} height={height} viewBox={`0 0 ${width} ${height}`} fill={fill === '#FFFFFF' ? 'none' : fill} xmlns="http://www.w3.org/2000/svg">          <svg width={width} height={height} viewBox={`0 0 ${width} ${height}`} xmlns="http://www.w3.org/2000/svg">             <circle cx="49" cy="24" r="9.9" stroke={stroke} />          </svg>          <text            x={width / 2}            y={height / 1.5}            fill={fontFill}            textAnchor="middle"            alignmentBaseline="middle"            fontSize={fontSize}            >                {label || '空启动事件'}            </text>            {/* <rect x="0.5" y="0.5" width="97" height="67" rx="1.5" stroke={stroke} opacity=".5" /> */}            <rect x="0.5" y="0.5" width="97" height="67" rx="1.5" stroke={stroke} opacity=".5"                 style={{ display: showNodeStyle ? 'block' : 'none' }} />        </svg>      );};
  • 右侧自定义属性

右侧的属性面板为左侧节点拖拽到画布当中点击后的自定义属性,全部采用json-schema的形式。

css 复制代码
/**
 * 自定义组件属性表单
 * @param nodeConfig
 * @param meta
 */
export const createComponentSchema = (nodeConfig: NsGraph.INodeConfig, meta: NsGraph.IGraphMeta):ISchema => {
    return {
        type: 'object',
        properties: {
            settingForm: {
                type: 'void',
                // 组件容器属性,会影响布局效果
                // 'x-decorator': 'FormItem',
                'x-component': 'FormCollapse',
                'x-component-props': {
                    'accordion': true,
                    'ghost': true
                },
                properties: {
                    styleProp: {
                        'x-component': 'FormCollapse.CollapsePanel',
                        'x-component-props': {
                            header: '样式',
                        },
                        ...StyleSchema
                    },
                    activityProp: {
                        'type': 'void',
                        'x-component': 'FormCollapse.CollapsePanel',
                        'x-component-props': {
                                    header: '组件属性',
                                },
                        'properties': {
                            ...BaseSchema,
                            ...getComponentSchema(nodeConfig, meta),
                            ...ListenerSchema
                        }
                    },
                    ListenerProp: {
                        'x-component': 'FormCollapse.CollapsePanel',
                        'x-component-props': {
                            header: '执行监听器',
                        },
                    }
                }
            }
        }
     };
};
  • 保存流程图数据

    typescript 复制代码
        toolbarGroup.push({
            tooltip: '保存',
            iconName: 'SaveOutlined',
            id: TOOLBAR_ITEMS.SAVE_GRAPH_DATA,
            onClick: async ({ commandService }) => {
                commandService.executeCommand<NsGraphCmd.SaveGraphData.IArgs>(
                    TOOLBAR_ITEMS.SAVE_GRAPH_DATA,
                    {
                        saveGraphDataService: async (meta, graphData) => {
                            console.log(graphData);
                            const data = BpmnAdapter.adapterOut({...graphData, meta});
    
                            // 下载XML文件
                            download('x-flow.xml', lfJson2Xml(data));
                            // 上传和部署流程图
                            const xmlData =  lfJson2Xml(data);
    
                            try {
                                // 上传流程图
                                const uploadRes:any = await uploadBpmnXml(xmlData);
                                if (uploadRes.meta.code !== 200) {
                                    throw uploadRes.meta.msg;
                                }
                                // 部署流程图
                                const deployRes: any = await deployMode(uploadRes.data.modelId);
                                if (deployRes.meta.code !== 200) {
                                    throw deployRes.meta.msg;
                                }
                                // 保存原始流程数据,用于下次回显
                                const saveRes: any = await addFlowResources(meta, graphData);
                                if (saveRes.meta.code !== 200) {
                                    throw saveRes.meta.msg;
                                }
    
                                return message.success('流程保存成功!')
                            } catch (err) {
                                return message.error(err);
                            }
                        },
                    },
                )
            },
        });
  • 保存数据转化

保存时转换成bpmn所需要的XML

ruby 复制代码
adapterOut(data) {    const bpmnProcessData = {      // '-id': `Process_${getBpmnId()}`,      '-id': `${data.meta.id ? data.meta.flowKey : `Process_${getBpmnId()}`}`,      '-name': `${data.meta.flowName}`,      '-isExecutable': 'true',    };    convertLf2ProcessData(bpmnProcessData, data);    const bpmnDiagramData = {      '-id': 'BPMNPlane_1',      '-bpmnElement': bpmnProcessData['-id'],    };    convertLf2DiagramData(bpmnDiagramData, data);    const bpmnData = {      'bpmn:definitions': {        '-id': `Definitions_${getBpmnId()}`,        '-xmlns:activiti': 'http://activiti.org/bpmn',        '-xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',        '-xmlns:bpmn': 'http://www.omg.org/spec/BPMN/20100524/MODEL',        '-xmlns:bpmndi': 'http://www.omg.org/spec/BPMN/20100524/DI',        '-xmlns:dc': 'http://www.omg.org/spec/DD/20100524/DC',        '-xmlns:di': 'http://www.omg.org/spec/DD/20100524/DI',        '-targetNamespace': 'http://bpmn.io/schema/bpmn',        '-exporter': 'bpmn-js (https://demo.bpmn.io)',        '-exporterVersion': '7.3.0',        // 转换信号定义        ...signalToJson(data?.meta?.signal),        // 转换消息定义        ...messageToJson(data?.meta?.message),        'bpmn:process': bpmnProcessData,        'bpmndi:BPMNDiagram': {          '-id': 'BPMNDiagram_1',          'bpmndi:BPMNPlane': bpmnDiagramData,        },      },    };    return bpmnData;  }
相关推荐
GIS程序媛—椰子28 分钟前
【Vue 全家桶】7、Vue UI组件库(更新中)
前端·vue.js
DogEgg_00134 分钟前
前端八股文(一)HTML 持续更新中。。。
前端·html
ZL不懂前端37 分钟前
Content Security Policy (CSP)
前端·javascript·面试
木舟100941 分钟前
ffmpeg重复回听音频流,时长叠加问题
前端
王大锤43911 小时前
golang通用后台管理系统07(后台与若依前端对接)
开发语言·前端·golang
我血条子呢1 小时前
[Vue]防止路由重复跳转
前端·javascript·vue.js
黎金安1 小时前
前端第二次作业
前端·css·css3
啦啦右一1 小时前
前端 | MYTED单篇TED词汇学习功能优化
前端·学习
半开半落1 小时前
nuxt3安装pinia报错500[vite-node] [ERR_LOAD_URL]问题解决
前端·javascript·vue.js·nuxt