GG-editor的使用心得

779 阅读4分钟

最近接到一个需求是需要在前端进行工作流流程图的设计,上网找了一圈轮子,最终还是选择了GG-editor,原因是这个界面看起来相对比较简洁,我也没什么专业编辑的需求。GG-editor是用蚂蚁金服的g6可视化引擎做的,而19年g6升级到了3.x版本,于是GG-editor的作者高力也把GG-editor重构升级到了3.x版本,但是由于3.x在本文写作时仍在重构阶段,文档残缺,所以我在使用了一段时间的新版本后仍然选择了2.0.4版本,之后若是升级我会再更一篇。

在使用3.x版本的时候我遇到了两个坑,一是undo命令会有bug,在2.0.4版本中,undo命令可以撤销位置移动操作,而在3.x版本中就不行了,在github上有issue但是作者并没有响应。二是如何保存流程图,在2.0.4版本中通过调用propsAPI.save()函数来实现(下面会贴代码),但是3.x版本中没有给出示例,我尝试在源码中全局搜索save函数结果没有搜到哈哈,不过我看最近作者在github的issue中回应说新版本需要用withEditorContext,和withPropsAPI用法一样 ,我没有具体尝试,之后若是升级会试一下。下面是在2.0.4版本中的保存示例,我自己加了一个弹窗确认:

import React from 'react';
import { connect } from 'dva';
import { Button, Modal, Form, Input, Icon } from 'antd';
import { withPropsAPI } from 'gg-editor';
import styles from './index.less'

const CollectionCreateForm = Form.create({ name: 'save_template' })(
  // eslint-disable-next-line
  class extends React.Component {
    render() {
      const { visible, onCancel, onSubmit, form, isSubmitting } = this.props;
      const { getFieldDecorator } = form;
      return (
        <Modal
          visible={visible}
          title="模板保存"
          okText="提交"
          onCancel={onCancel}
          confirmLoading={isSubmitting}
          onOk={onSubmit}
        >
          <Form layout="vertical">
            <Form.Item label="模板名称">
              {getFieldDecorator('name', {
                rules: [{ required: true, message: '请输入模板名称!' }],
              })(<Input />)}
            </Form.Item>
            <Form.Item label="描述">
              {getFieldDecorator('description')(<Input type="textarea" />)}
            </Form.Item>
          </Form>
        </Modal>
      );
    }
  },
);

@connect(({ workflow, loading }) => ({
  workflow,
  isSubmitting: loading.effects['workflow/submitWorkflowTemplate'],
}))
class Save extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      modalVisible: false,
    };
  }

  showModal = () => {
    this.setState({ modalVisible: true });
  };

  handleCancel = () => {
    this.setState({ modalVisible: false });
  };

  saveFormRef = formRef => {
    this.formRef = formRef;
  };

  handleSubmit = e => {
    const { form } = this.formRef.props;
    const { dispatch } = this.props;
    form.validateFields((err, values) => {
      if (err) {
        return;
      }

      const { propsAPI } = this.props;
      const data = propsAPI.save();

      //将数据传换为接口所需参数
      let taskList = [];
      if (data.hasOwnProperty('edges')) {
        const edges = data.edges;
        for (let i in edges) {
          taskList.push({
            'task_id': edges[i].id,
            'upstream': edges[i].source,
          });
        }
      }
      const params = {
        name: values.name,
        description: values.description,
        task: taskList,
      };

      //上传成功后清空表单关闭弹窗
      dispatch({
        type: 'workflow/submitWorkflowTemplate',
        payload: params,
      }).then(res => {
        if (res && res.success) {
          form.resetFields();
          this.setState({ modalVisible: false });
        }
      });

    });
  };

  render() {
    const { isSubmitting = false } = this.props;

    return (
      <div className={styles.actionGroup}>
        <div style={{ padding: 8 }}>
          <Button onClick={this.showModal}>
            <Icon type="save" />
            保存
          </Button>
        </div>

        <CollectionCreateForm
          wrappedComponentRef={this.saveFormRef}
          visible={this.state.modalVisible}
          onCancel={this.handleCancel}
          onSubmit={this.handleSubmit}
          isSubmitting={isSubmitting}
        />

      </div>
    );
  }
}

export default withPropsAPI(Save);

关键代码其实就两处,一是组件要外包withPropsAPI(3.x版本要换成withEditorContext),二是从props中获取propsAPI,然后调用propsAPI.save()函数;

还有一个非常常见的需求就是将左侧拖拽区改造成树状结构,我看网上有不少人问,这里我贴一下我的解决方案。我去翻了一下antd的文档,发现树节点的标题是接受组件传入的:

所以我就进行了如下尝试,一试居然成功了:

import React from 'react';
import { Tree, Icon } from 'antd';
import { ItemPanel, Item } from 'gg-editor';
import styles from './index.less';

const { TreeNode } = Tree;

const FlowItemPanel = () => {
  const onSelect = (keys, event) => {
    console.log('Trigger Select', keys, event);
  };

  const onExpand = () => {
    console.log('Trigger Expand');
  };
  return (
    <ItemPanel className={styles.itemPanel}>
      <div style={{ padding: '0 12px' }}>
        <Tree
          defaultExpandAll
          onSelect={onSelect}
          onExpand={onExpand}
          showIcon
        >
          <TreeNode title="模型节点" key="0-0">
            <TreeNode title={<Item
              type="node"
              size="184*40"
              shape="customModelNode"
              model={{
                label: '船舶检测模型',
                color_type: '#1890FF',
                type_icon_url: 'https://gw.alipayobjects.com/zos/rmsportal/czNEJAmyDpclFaSucYWB.svg',
                status: 'running',
              }}
              // src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iODgiIGhlaWdodD0iNTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPjxkZWZzPjxyZWN0IGlkPSJiIiB4PSIwIiB5PSIwIiB3aWR0aD0iODAiIGhlaWdodD0iNDgiIHJ4PSIyNCIvPjxmaWx0ZXIgeD0iLTguOCUiIHk9Ii0xMC40JSIgd2lkdGg9IjExNy41JSIgaGVpZ2h0PSIxMjkuMiUiIGZpbHRlclVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgaWQ9ImEiPjxmZU9mZnNldCBkeT0iMiIgaW49IlNvdXJjZUFscGhhIiByZXN1bHQ9InNoYWRvd09mZnNldE91dGVyMSIvPjxmZUdhdXNzaWFuQmx1ciBzdGREZXZpYXRpb249IjIiIGluPSJzaGFkb3dPZmZzZXRPdXRlcjEiIHJlc3VsdD0ic2hhZG93Qmx1ck91dGVyMSIvPjxmZUNvbXBvc2l0ZSBpbj0ic2hhZG93Qmx1ck91dGVyMSIgaW4yPSJTb3VyY2VBbHBoYSIgb3BlcmF0b3I9Im91dCIgcmVzdWx0PSJzaGFkb3dCbHVyT3V0ZXIxIi8+PGZlQ29sb3JNYXRyaXggdmFsdWVzPSIwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwLjA0IDAiIGluPSJzaGFkb3dCbHVyT3V0ZXIxIi8+PC9maWx0ZXI+PC9kZWZzPjxnIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCI+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNCAyKSI+PHVzZSBmaWxsPSIjMDAwIiBmaWx0ZXI9InVybCgjYSkiIHhsaW5rOmhyZWY9IiNiIi8+PHVzZSBmaWxsLW9wYWNpdHk9Ii45MiIgZmlsbD0iI0Y5RjBGRiIgeGxpbms6aHJlZj0iI2IiLz48cmVjdCBzdHJva2U9IiNCMzdGRUIiIHg9Ii41IiB5PSIuNSIgd2lkdGg9Ijc5IiBoZWlnaHQ9IjQ3IiByeD0iMjMuNSIvPjwvZz48dGV4dCBmb250LWZhbWlseT0iUGluZ0ZhbmdTQy1SZWd1bGFyLCBQaW5nRmFuZyBTQyIgZm9udC1zaXplPSIxMiIgZmlsbD0iIzAwMCIgZmlsbC1vcGFjaXR5PSIuNjUiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDQgMikiPjx0c3BhbiB4PSIyNCIgeT0iMjkiPk1vZGVsPC90c3Bhbj48L3RleHQ+PC9nPjwvc3ZnPg=="
            >
              <span className="panel-type-icon" />船舶检测模型
            </Item>} key="0-0-1" isLeaf />
            <TreeNode title={<Item
              type="node"
              size="184*40"
              shape="customModelNode"
              model={{
                label: '场景识别模型',
                color_type: '#1890FF',
                type_icon_url: 'https://gw.alipayobjects.com/zos/rmsportal/czNEJAmyDpclFaSucYWB.svg',
                status: 'running',
              }}
              // src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iODgiIGhlaWdodD0iNTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPjxkZWZzPjxyZWN0IGlkPSJiIiB4PSIwIiB5PSIwIiB3aWR0aD0iODAiIGhlaWdodD0iNDgiIHJ4PSIyNCIvPjxmaWx0ZXIgeD0iLTguOCUiIHk9Ii0xMC40JSIgd2lkdGg9IjExNy41JSIgaGVpZ2h0PSIxMjkuMiUiIGZpbHRlclVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgaWQ9ImEiPjxmZU9mZnNldCBkeT0iMiIgaW49IlNvdXJjZUFscGhhIiByZXN1bHQ9InNoYWRvd09mZnNldE91dGVyMSIvPjxmZUdhdXNzaWFuQmx1ciBzdGREZXZpYXRpb249IjIiIGluPSJzaGFkb3dPZmZzZXRPdXRlcjEiIHJlc3VsdD0ic2hhZG93Qmx1ck91dGVyMSIvPjxmZUNvbXBvc2l0ZSBpbj0ic2hhZG93Qmx1ck91dGVyMSIgaW4yPSJTb3VyY2VBbHBoYSIgb3BlcmF0b3I9Im91dCIgcmVzdWx0PSJzaGFkb3dCbHVyT3V0ZXIxIi8+PGZlQ29sb3JNYXRyaXggdmFsdWVzPSIwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwLjA0IDAiIGluPSJzaGFkb3dCbHVyT3V0ZXIxIi8+PC9maWx0ZXI+PC9kZWZzPjxnIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCI+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNCAyKSI+PHVzZSBmaWxsPSIjMDAwIiBmaWx0ZXI9InVybCgjYSkiIHhsaW5rOmhyZWY9IiNiIi8+PHVzZSBmaWxsLW9wYWNpdHk9Ii45MiIgZmlsbD0iI0Y5RjBGRiIgeGxpbms6aHJlZj0iI2IiLz48cmVjdCBzdHJva2U9IiNCMzdGRUIiIHg9Ii41IiB5PSIuNSIgd2lkdGg9Ijc5IiBoZWlnaHQ9IjQ3IiByeD0iMjMuNSIvPjwvZz48dGV4dCBmb250LWZhbWlseT0iUGluZ0ZhbmdTQy1SZWd1bGFyLCBQaW5nRmFuZyBTQyIgZm9udC1zaXplPSIxMiIgZmlsbD0iIzAwMCIgZmlsbC1vcGFjaXR5PSIuNjUiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDQgMikiPjx0c3BhbiB4PSIyNCIgeT0iMjkiPk1vZGVsPC90c3Bhbj48L3RleHQ+PC9nPjwvc3ZnPg=="
            >
              <span className="panel-type-icon" />场景识别模型
            </Item>} key="0-0-2" isLeaf />
          </TreeNode>
        </Tree>
      </div>
    </ItemPanel>
  );
};

export default FlowItemPanel;

当然这里的Item是我自己自定义的,直接复制粘贴怕是用不了。