2
头图

The last part briefly introduced formilyjs and built a form designer based on it, and realized the dynamic design and preview function of the form. This video will introduce some advanced configurations of the form designer, so that they can be better used in our node editing and connection. line editing.

designable advanced configuration

Request and custom data injection

Generally speaking, the requests of our pages are uniformly encapsulated. How do we inject our encapsulated requests or existing data sources into our form designer when using them:
The SchemaField component is a component specially used for parsing JSON-Schema to dynamically render forms. When using createSchemaField we can pass in scope to inject the link into the global scope:

 import { FC, Ref, useEffect, useImperativeHandle, useState } from 'react';
import { Modal } from 'antd';
import {
  FormItem,
  Input,
  Form,
  Submit,
  ArrayBase,
  ArrayCards,
  ArrayCollapse,
  ArrayItems,
  ArrayTable,
  ArrayTabs,
  BaseItem,
  Cascader,
  Checkbox,
  DatePicker,
  Editable,
  FormButtonGroup,
  FormCollapse,
  FormGrid,
  FormTab,
  GridColumn,
  NumberPicker,
  Password,
  PreviewText,
  Radio,
  Reset,
  Select,
  SelectTable,
  Space,
  Switch,
  TimePicker,
  Transfer,
  TreeSelect,
  Upload,
} from '@formily/antd';
import api from '@/api';
import { createSchemaField } from '@formily/react';
import { LgetItem } from '@/utils/storage';
import { createForm } from '@formily/core';

interface PreviewProps {
  previewRef: Ref<{ setVisible: (flag: boolean) => void }>;
  modalConfig: { [key: string]: any };
}

const SchemaField = createSchemaField({
  components: {
    Input,
    ArrayBase,
    ArrayCards,
    ArrayCollapse,
    ArrayItems,
    ArrayTable,
    ArrayTabs,
    BaseItem,
    Cascader,
    Checkbox,
    DatePicker,
    Editable,
    Form,
    FormButtonGroup,
    FormCollapse,
    FormGrid,
    FormItem,
    FormTab,
    GridColumn,
    NumberPicker,
    Password,
    PreviewText,
    Radio,
    Reset,
    Select,
    SelectTable,
    Space,
    Submit,
    Switch,
    TimePicker,
    Transfer,
    TreeSelect,
    Upload,
  },
  scope: {
    $fetch: api,
    selectList: [{ label: 'aaa', value: 'aaa' }, { label: 'bbb', value: 'bbb' }]
  }
});

const Preview: FC<PreviewProps> = ({ previewRef, modalConfig }) => {
  const [visible, setVisible] = useState(false);
  useImperativeHandle(previewRef, () => ({
    setVisible,
  }));

  const [params, setParams] = useState({});
  const normalForm = createForm({});
  useEffect(() => {
    if (modalConfig && visible) {
      const playgroundList = LgetItem('playgroundList') || [];
      const data = playgroundList.find((s) => s.id === modalConfig.id);
      setParams(data?.params || {});
    }
  }, [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;

When using the form designer, you can use the injected scope value. For example, we prepare a json, select a drop-down control, configure the corresponding device, and then obtain the json through our injected request to display the corresponding drop-down value:

 $effect(() => {
        $self.loading = true
        $fetch({
            url: '/getSelectList',
            method: 'get',
            params: {}
        }).then(res => {
            $self.loading = false
            // 当返回值不是label和value时转化一下
            $self.dataSource = res.map(s => ({ label: res.name, value: res.id }))
        }).catch(() => {
            $self.loading = false
        })
    }, [])

Here $fetch is the request we injected

Form configuration

  1. The key submitted by the form is the field identifier, which can be customized and modified. The default is a random string.
  2. The title is the label of the form form
  3. For custom search, such as select, you need to configure the form format in the filter under component properties to support label search

     (inputValue, option) => {
        return option.label.indexOf(inputValue) !== -1;
    }

    搜索

  4. If the same configuration in the responder rule as the external configuration is configured, such as component properties, then the external configuration in the responder rule will be overwritten (the external component properties will fail)

Advanced configuration

The advanced configuration is in the responder rules. Here, it mainly describes some functions that cannot be realized by ordinary configuration, such as common linkage, dynamic value and other scenarios.

  • $self is the currently selected form object
  • $form is the form object
  • $deps is the dependency object (the source field needs to be configured in the dependency field above)
  • $observable declare an observable
  • $effect and react's useEffect use something like
  • $values is the submitted form object
  1. Dynamic enumeration value, if we have a select control, the value of this control is returned by the interface

     $effect(() => {
        $self.loading = true
        $fetch({
            url: '/getSelectList',
            method: 'get',
            params: {}
        }).then(res => {
            $self.loading = false
            // 当返回值不是label和value时转化一下
            $self.dataSource = res.map(s => ({ label: res.name, value: res.id }))
        }).catch(() => {
            $self.loading = false
        })
    }, [])
  2. Linkage changes the enumeration value. For example, we have two choices. The first choice is mechanism organization. The first choice is user personnel. select will obtain the list of personnel under the organization from the interface according to the value of the first select. This is a relatively common linkage selection function, so how do we implement it in the form designer (the implementation method is not unique, here is the ideas).

     // 第一个select,我们监听mechanism的变化,flag主要应用为跳过初次渲染(保证反显正常展示),当mechanism改版(即手动选值)后,清空user的取值。
    $effect(() => {
        $self.loading = true
        $fetch({
            url: '/getMechanismList',
            method: 'get',
            params: {}
        }).then(res => {
            $self.loading = false
            // 当返回值不是label和value时转化一下
            $self.dataSource = res.map(s => ({ label: res.name, value: res.id }))
        }).catch(() => {
            $self.loading = false
        })
    }, [])
    const state = $observable({ flag: false });
    $effect(() => {
        if (state.flag) {
            $form.reset('user');
        }
        state.flag = true;
    }, [$self.value])

    When mechanism changes, clear the user list and initiate a request to get the user list

     $effect(() => {
            $self.dataSource = []
            if ($deps.mechanism) {
                $self.loading = true
                    $fetch({
                    url: '/getSelectList',
                    method: 'get',
                    params: {
                        mechanism: $deps.mechanism
                    }
                }).then(res => {
                    $self.loading = false
                    // 当返回值不是label和value时转化一下
                    $self.dataSource = res.map(s => ({ label: res.name, value: res.id }))
                }).catch(() => {
                    $self.loading = false
                })
            }
        }, [$deps.mechanism])

Small scale chopper

We use mock data to simply do a linkage

 // mock/api.ts
export default {
  'GET /api/mechanism': [
    {
      value: '1',
      label: '机构1',
    },
    {
      value: '2',
      label: '机构2',
    },
  ],
  'GET /api/users': (req, res) => {
    // 添加跨域请求头
    const query: any = req.query;
    const user: any = {
      '1': [
        {
          value: '1-1',
          label: '机构1-人员1',
        },
        {
          value: '1-2',
          label: '机构1-人员2',
        },
      ],
      '2': [
        {
          value: '2-1',
          label: '机构2-人员1',
        },
        {
          value: '2-2',
          label: '机构2-人员2',
        },
      ],
    };
    res.send(user[query.id]);
  },
};

We define two mock interfaces, mechanism and users , the former returns a list of institutions, the latter returns the corresponding list according to the id of the former, and then we preview the pop-up window in the previous issue In the injection request, here we directly use the one provided by umi request .

 // Preview.tsx
import { request } from 'umi';
...

const SchemaField = createSchemaField({
  components: ...,
  scope: {
    $fetch: request,
  },
});

Finally we add the request in the form design

 // mechanism
$effect(() => {
  $self.loading = true
  $fetch("/api/mechanism", {})
    .then((res) => {
      $self.loading = false
      // 当返回值不是label和value时转化一下
      $self.dataSource = res
    })
    .catch(() => {
      $self.loading = false
    })
}, [])

// users
$effect(() => {
  $self.dataSource = []
  if ($deps.mechanism) {
    $self.loading = true
    $fetch("/api/users", {
      params: {
        id: $deps.mechanism,
      },
    })
      .then((res) => {
        $self.loading = false
        // 当返回值不是label和value时转化一下
        $self.dataSource = res
      })
      .catch(() => {
        $self.loading = false
      })
  }
}, [$deps.mechanism])

表单设计

表单设计

表单设计

Finally, go to the form template to preview:
预览

Well, the functions introduced in this article have been implemented, then in the next article, we will associate the editing function of process visualization with our form template. Modifying the form template can modify the attribute editing of nodes or connections to achieve real The dynamic attribute editing effect of , please look forward to it.

Address of this article: link
This article github address: link
github demo address: link


陌路凡歌
7.9k 声望258 粉丝

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