头图

In the previous two articles, we introduced Ali's form designer and application. In this article, we will return to our process design and associate the form template with the editing function of process visualization. Modifying the form template can modify the attribute editing of nodes or connections. Achieve true dynamic attribute editing effect.

Generic form rendering

Let's transform the preview of the previous form template and provide a function for formily rendering the template generation. New 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;

The original preview playground/Preview.tsx is modified to:

 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;

Node editing and transformation

 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;
    }
  }
}

Online editing and transformation

 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;

Go to the form template to create a template and configure it. Go back to the flowchart and click the node to edit it to see if it is the same as the form template preview. Modify the form configuration of the template, and the flowchart will be modified synchronously:
表单设计

表单设计

Well, the combination of the dynamic form and the addition of custom attributes has been completed, and a process visualization design with relatively complete main functions has been completed, but there are still some details that need to be polished. The next few articles are the details. Please look forward to optimizing the supplementary instructions.

Address of this article: https://xuxin123.com/web/xflow-contact-formily
This article github address: link
github demo address: link


陌路凡歌
7.9k 声望258 粉丝

人笨,多听,多写,多看书