1
头图

This article mainly introduces formilyjs, which is Ali's unified front-end form solution, with relatively complete form design and complex scene processing functions. Of course, this article mainly introduces its form design and display functions in a simple way, and applies it to our process. In the visual custom editing, as for the formilyjs custom development control, there will be an opportunity to explain it in a separate chapter.

designable visual form design

The official website of formilyjs provides the Formily designer , we now pull down this form designer and turn it into one of our pages:

Create a new src/playground directory,

 yarn add @designable/formily-antd @ant-design/pro-components

Go to the designer github , copy the relevant files in the playground and src directories and put them in playground/details , and adjust the reference structure (skip, see the code).

 // routes
{
  name: '表单模板',
  path: '/playground',
  icon: 'FormOutlined',
  component: '@/pages/playground',
},
{
  name: '表单设计',
  path: '/playground/details',
  component: '@/pages/playground/details',
  headerRender: false,
  footerRender: false,
  menuRender: false,
  hideInMenu: true,
},

List of form templates

New playground/index.tsx and playground/Preview.tsx , the former is used as a new form template, the latter is a preview after the form is designed

 // playground/index.tsx
import type { ActionType, ProColumns } from '@ant-design/pro-components';
import { ProTable } from '@ant-design/pro-components';
import { PageContainer } from '@ant-design/pro-layout';
import { FormItem, Input, FormDialog, FormLayout } from '@formily/antd';
import { createSchemaField } from '@formily/react';
import { Dropdown, Menu, Popconfirm, Space, Button } from 'antd';
import { FC, useRef, useState } from 'react';
import Preview from './Preview';
import { LgetItem, LsetItem } from '@/utils/storage';
import { uuidv4 } from '@antv/xflow';
import { history } from 'umi';

const SchemaField = createSchemaField({
  components: {
    FormItem,
    Input,
  },
});

const schema = {
  type: 'object',
  properties: {
    name: {
      type: 'string',
      title: '模板名',
      required: true,
      'x-decorator': 'FormItem',
      'x-component': 'Input',
    },
  },
};

const Playground: FC = () => {
  const [modalConfig, setModalConfig] = useState<{ [key: string]: any }>({});
  const previewRef = useRef<{ setVisible: (flag: boolean) => void }>(null);
  const actionRef = useRef<ActionType>();
  const columns: ProColumns<any>[] = [
    {
      dataIndex: 'name',
      title: '模板名',
    },
    {
      dataIndex: 'params',
      title: '是否配置',
      renderText(text, record, index, action) {
        return text ? '是' : '否';
      },
      search: false,
    },
    {
      title: '操作',
      valueType: 'option',
      render: (_, record) => {
        return [
          <a
            key="preview"
            onClick={() => {
              setModalConfig(record);
              previewRef.current?.setVisible(true);
            }}
          >
            预览
          </a>,
          <a key="edit" onClick={() => handleEdit(record)}>
            配置
          </a>,
          <a key="del" onClick={() => handleDel(record)}>
            删除
          </a>,
        ];
      },
    },
  ];

  const handleEdit = (record: any) => {
    history.push(`/playground/details?id=${record.id}`);
  };

  const handleDel = (record: any) => {
    const playgroundList = LgetItem('playgroundList') || [];
    LsetItem(
      'playgroundList',
      playgroundList.filter((s) => s.id !== record.id),
    );
    actionRef.current?.reload();
  };

  const handleAdd = () => {
    FormDialog('新增模板', () => {
      return (
        <FormLayout labelCol={6} wrapperCol={10}>
          <SchemaField schema={schema} />
        </FormLayout>
      );
    })
      .forOpen((payload, next) => {
        next({
          initialValues: {
            name: '',
          },
        });
      })
      .forConfirm((payload, next) => {
        const playgroundList = LgetItem('playgroundList') || [];
        playgroundList.push({
          name: payload.getFormState().values.name,
          id: uuidv4(),
        });
        LsetItem('playgroundList', playgroundList);
        actionRef.current?.reload();
        next(payload);
      })
      .forCancel((payload, next) => {
        next(payload);
      })
      .open()
      .then(console.log);
  };

  const getData = async (params: any) => {
    const data = LgetItem('playgroundList');
    return {
      data: data ?? [],
      success: true,
      total: data?.length ?? 0,
    };
  };

  return (
    <PageContainer>
      <ProTable
        columns={columns}
        actionRef={actionRef}
        request={async (params: any, _sorter: any, _filter: any) => {
          return await getData(params);
        }}
        rowKey="id"
        pagination={{
          showQuickJumper: true,
        }}
        toolBarRender={() => [
          <Button key="button" type="primary" onClick={handleAdd}>
            新增
          </Button>,
        ]}
      />
      <Preview previewRef={previewRef} modalConfig={modalConfig} />
    </PageContainer>
  );
};

export default Playground;
 // playground/Preview.tsx
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 * as aaa from '@formily/antd';
import { createSchemaField } from '@formily/react';
import { LgetItem } from '@/utils/storage';
import { createForm } from '@formily/core';
console.log(aaa);
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,
  },
});

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;

Here we simply store the created form data locally localStorage , click configure and jump to our design page,
Adjust the saving of the form design. After I use the designer to drag and drop the design, I store the data and find the form template corresponding to the id to design schema and assign it to params

 // details/service/schema.ts
import { Engine } from '@designable/core';
import {
  transformToSchema,
  transformToTreeNode,
} from '@designable/formily-transformer';
import { message } from 'antd';
import { LgetItem, LsetItem } from '@/utils/storage';

function fixUrlHash(url: string) {
  let fixedUrl = new URL(url);
  let search = fixedUrl.search;
  let hash = fixedUrl.hash;
  const position = fixedUrl.hash.indexOf('?');
  if (search.length <= 1 && position >= 0) {
    search = hash.slice(position);
    hash = hash.slice(0, position);
    fixedUrl.hash = hash;
    fixedUrl.search = search;
    fixedUrl.href = fixedUrl.toString();
  }
  return fixedUrl;
}

export const saveSchema = (designer: Engine) => {
  const url = fixUrlHash(window.location.href);
  const searchParams = new URLSearchParams(url.search);
  let playgroundList = LgetItem('playgroundList') || [];
  playgroundList = playgroundList.map((s) => {
    if (s.id === searchParams.get('id')) {
      return {
        ...s,
        params: transformToSchema(designer.getCurrentTree()),
      };
    }
    return s;
  });
  LsetItem('playgroundList', playgroundList);
  message.success('保存成功');
};

export const loadInitialSchema = (designer: Engine) => {
  const url = fixUrlHash(window.location.href);
  const searchParams = new URLSearchParams(url.search);
  const playgroundList = LgetItem('playgroundList') || [];
  const data = playgroundList.find((s) => s.id === searchParams.get('id'));
  try {
    designer.setCurrentTree(transformToTreeNode(data?.params || []));
  } catch {}
};

模板设计

Then go back to the template list page, click Preview to see if the form we designed is displayed normally

模板预览

In the next chapter, I will introduce some advanced configurations of the form designer, such as dynamic request, encapsulated interface injection, form linkage configuration, etc., so stay tuned.

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


陌路凡歌
7.9k 声望258 粉丝

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