最近接到一个需求是需要在前端进行工作流流程图的设计,上网找了一圈轮子,最终还是选择了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是我自己自定义的,直接复制粘贴怕是用不了。