xflow流程可视化-节点编辑关联表单模板
![作者站长头像](https://img.blogweb.cn/avatar/cc788e24a17249d0a8aa6d4567a5460f.webp)
站长
· 阅读数 40
前面两篇我们介绍了阿里的表单设计器及应用,本篇我们将回归我们的流程设计,将表单模板和流程可视化的编辑功能关联起来,修改表单模板就能修改节点或连线的属性编辑,达到真正的动态属性编辑效果。
通用表单渲染
我们把之前的表单模板的预览改造一下,提供一个formily渲染模板生成的函数。新建playground/temp-schemaField.tsx
:
import React from 'react';
import { createSchemaField } from '@formily/react';
import {
Form,
FormItem,
DatePicker,
Checkbox,
Cascader,
Editable,
Input,
NumberPicker,
Switch,
Password,
PreviewText,
Radio,
Reset,
Select,
Space,
Submit,
TimePicker,
Transfer,
TreeSelect,
Upload,
FormGrid,
FormLayout,
FormTab,
FormCollapse,
ArrayTable,
ArrayCards,
} from '@formily/antd';
import { Card, Slider, Rate } from 'antd';
const Text: React.FC<{
value?: string;
content?: string;
mode?: 'normal' | 'h1' | 'h2' | 'h3' | 'p';
}> = ({ value, mode, content, ...props }) => {
const tagName = mode === 'normal' || !mode ? 'div' : mode;
return React.createElement(tagName, props, value || content);
};
const TempSchemaField = (scope?: any) => {
const SchemaField = createSchemaField({
components: {
Space,
FormGrid,
FormLayout,
FormTab,
FormCollapse,
ArrayTable,
ArrayCards,
FormItem,
DatePicker,
Checkbox,
Cascader,
Editable,
Input,
Text,
NumberPicker,
Switch,
Password,
PreviewText,
Radio,
Reset,
Select,
Submit,
TimePicker,
Transfer,
TreeSelect,
Upload,
Card,
Slider,
Rate,
},
scope,
});
return SchemaField;
};
export default TempSchemaField;
原来的预览playground/Preview.tsx
修改为:
import { FC, Ref, useEffect, useImperativeHandle, useState } from 'react';
import { Modal } from 'antd';
import { request } from 'umi';
import { Form, Submit } from '@formily/antd';
import tempSchemaField from '@/pages/playground/temp-schemaField';
import { LgetItem } from '@/utils/storage';
import { createForm } from '@formily/core';
interface PreviewProps {
previewRef: Ref<{ setVisible: (flag: boolean) => void }>;
modalConfig: { [key: string]: any };
}
const Preview: FC<PreviewProps> = ({ previewRef, modalConfig }) => {
const [visible, setVisible] = useState(false);
const [params, setParams] = useState<{ [key: string]: any }>({});
const normalForm = createForm({});
const SchemaField = tempSchemaField({
$fetch: request,
});
useImperativeHandle(previewRef, () => ({
setVisible,
}));
useEffect(() => {
if (modalConfig && visible) {
const playgroundList = LgetItem('playgroundList') || [];
const data = playgroundList.find(
(s: { id: string }) => s.id === modalConfig.id,
);
setParams(data?.params || {});
}
request('/api/users', {
params: {
id: 1,
},
}).then((res) => {
console.log(res);
});
}, [modalConfig, visible]);
const handleCancel = () => {
setVisible(false);
};
return (
<Modal
title="模板预览"
visible={visible}
onCancel={handleCancel}
footer={null}
>
<Form form={normalForm} onAutoSubmit={console.log} {...params.form}>
<SchemaField schema={params.schema} />
<Submit block>保存</Submit>
</Form>
</Modal>
);
};
export default Preview;
节点编辑改造
import { FC, useEffect, useState } from 'react';
import { Button, Empty, message } from 'antd';
import { NsJsonSchemaForm, useXFlowApp } from '@antv/xflow';
import { Form, Submit } from '@formily/antd';
import { createForm } from '@formily/core';
import { set } from 'lodash';
import { request } from 'umi';
import tempSchemaField from '@/pages/playground/temp-schemaField';
import Header from '../Header';
import styles from './index.less';
import { LgetItem } from '@/utils/storage';
interface NodeComponentProps {
updateNode: any;
targetData: NsJsonSchemaForm.TargetData;
readOnly?: boolean;
}
const NodeComponent: FC<NodeComponentProps> = (props) => {
const [formilySchema, setFormilySchema] = useState<{ [key: string]: any }>(
{},
);
const { updateNode, targetData, readOnly = false } = props;
const SchemaField = tempSchemaField({
$fetch: request,
});
const xflowApp = useXFlowApp();
const form = createForm({
values: {
label: targetData?.label,
...targetData?.attrs?.attribute,
},
readOnly,
});
const onFinish = async (values: any) => {
const grap = await xflowApp.getGraphInstance();
const data: any = {
...targetData,
};
Object.keys(values).forEach((key: any) => {
if (key === 'label') {
set(data, key, values[key]);
} else {
set(data.attrs.attribute, key, values[key]);
}
});
updateNode(data);
message.success('暂存成功');
// 失去焦点
grap.resetSelection();
};
const delBtn = async () => {
const grap = await xflowApp.getGraphInstance();
grap.removeNode(targetData!.id);
};
useEffect(() => {
// 这里用接口获取节点id关联的表单模板,我简写了
if (targetData?.renderKey) {
const playgroundList = LgetItem('playgroundList') || [];
setFormilySchema(playgroundList[0]?.params ?? {});
}
}, [targetData]);
const { form: formProps, schema } = formilySchema;
return (
<div className={styles.nodeComponent}>
<Header title="节点编辑" />
<div className="formBox">
{schema && Object.keys(schema)?.length ? (
<Form {...formProps} form={form} onAutoSubmit={onFinish}>
<SchemaField schema={schema} />
{readOnly ? null : <Submit block>保存</Submit>}
</Form>
) : (
<Empty
image={Empty.PRESENTED_IMAGE_SIMPLE}
description="无表单模板"
/>
)}
</div>
{readOnly ? null : (
<div className="delBtn">
<Button size="large" block danger onClick={delBtn}>
删除节点
</Button>
</div>
)}
</div>
);
};
export default NodeComponent;
// index.less
.nodeComponent {
height: 100%;
padding: 0 16px;
display: flex;
flex-direction: column;
justify-content: space-between;
:global {
.formBox {
flex: 1;
overflow: auto;
padding-bottom: 16px;
box-sizing: border-box;
}
.delBtn {
flex: 0 0 40px;
height: 40px;
margin-bottom: 16px;
}
}
}
连线编辑改造
import { FC, useEffect, useState } from 'react';
import { Empty, Button } from 'antd';
import { useXFlowApp, NsJsonSchemaForm } from '@antv/xflow';
import { Form, Submit } from '@formily/antd';
import { createForm } from '@formily/core';
import { set } from 'lodash';
import { request } from 'umi';
import tempSchemaField from '@/pages/playground/temp-schemaField';
import Header from '../Header';
import styles from './index.less';
import { LgetItem } from '@/utils/storage';
interface EdgeComponentProps {
updateEdge: any;
targetData: NsJsonSchemaForm.TargetData;
readOnly?: boolean;
}
const EdgeComponent: FC<EdgeComponentProps> = (props) => {
const [formilySchema, setFormilySchema] = useState<{ [key: string]: any }>(
{},
);
const { updateEdge, targetData, readOnly = false } = props;
const SchemaField = tempSchemaField({
$fetch: request,
});
const xflowApp = useXFlowApp();
const form = createForm({
values: targetData!.attrs ?? {},
readOnly,
});
const onFinish = async (values: any) => {
const grap = await xflowApp.getGraphInstance();
const data: any = {
...targetData,
};
Object.keys(values).forEach((key: any) => {
if (!data.attrs?.attribute) {
set(data.attrs, 'attribute', {});
}
set(data.attrs.attribute, key, values[key]);
});
updateEdge(data);
grap.resetSelection();
};
const delBtn = async () => {
const grap = await xflowApp.getGraphInstance();
grap.removeEdge(targetData!.id);
};
useEffect(() => {
// 这里用接口获取节点id关联的表单模板,我简写了
const playgroundList = LgetItem('playgroundList') || [];
setFormilySchema(playgroundList[0]?.params ?? {});
}, []);
const { form: formProps, schema } = formilySchema;
return (
<div className={styles.edgeComponent}>
<Header title="连线编辑" />
<div className="formBox">
{schema && Object.keys(schema)?.length ? (
<Form {...formProps} form={form} onAutoSubmit={onFinish}>
<SchemaField schema={schema} />
{readOnly ? null : <Submit block>保存</Submit>}
</Form>
) : (
<Empty
image={Empty.PRESENTED_IMAGE_SIMPLE}
description="无表单模板"
/>
)}
</div>
{readOnly ? null : (
<div className="delBtn">
<Button size="large" block danger onClick={delBtn}>
删除连线
</Button>
</div>
)}
</div>
);
};
export default EdgeComponent;
去表单模板建立一个模板,并配置一下,回到流程图点击节点编辑一下,看是否与表单模板预览的展示一样,修改模板的表单配置,流程图的会同步修改:
好了,动态表单与自定义属性的添加相结合就完成了,一个主要功能较为齐全的流程可视化设计就已经完成了,但是还有一些细节上的东西需要打磨一下,接下来几篇为细节的优化补充说明,敬请期待。
本文地址:https://xuxin123.com/web/xflow-contact-formily 本文github地址:链接 github demo地址:链接