本篇次要介绍formilyjs,它是阿里的对立前端表单解决方案,领有比拟残缺的表单设计和简单的场景解决性能,当然本篇次要通俗的介绍其表单设计及展现性能,将其利用到咱们的流程可视化自定义编辑中,至于formilyjs自定义开发控件的话后续有机会独自篇章进行阐明。

designable可视化表单设计

formilyjs官网提供了Formily 设计器,咱们当初把这个表单设计器拉下来变成咱们的一个页面:

新建src/playground目录,

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

去设计器github,把playgroundsrc目录下相干文件拷贝下来放入playground/details中,调整一下援用构造(略过,见代码)。

// 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,},

表单模板列表

新建playground/index.tsxplayground/Preview.tsx,前者用作新建表单模板,后者为表单设计后的预览

// playground/index.tsximport 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.tsximport { 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;

这里咱们简略的把建设的表单数据存储在本地localStorage中,点击配置后跳转到咱们的设计页面,
调整一下表单设计的保留,我应用设计器拖拽设计实现后,进行数据存储,找到对应id的表单模板将设计的schema赋值给params

// details/service/schema.tsimport { 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 {}};

而后回到模板列表页,点击预览查看咱们设计的表单是否失常显示

下一章我将介绍表单设计器的一些高级配置,比方动静申请,封装接口注入,表单联动配置等,敬请期待。

本文地址:链接
本文github地址:链接
github demo地址:链接