头图

前面两篇我们介绍了阿里的表单设计器及应用,本篇我们将回归我们的流程设计,将表单模板和流程可视化的编辑功能关联起来,修改表单模板就能修改节点或连线的属性编辑,达到真正的动态属性编辑效果。

通用表单渲染

我们把之前的表单模板的预览改造一下,提供一个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地址:链接


陌路凡歌
7.9k 声望258 粉丝

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