本篇次要介绍formilyjs,它是阿里的对立前端表单解决方案,领有比拟残缺的表单设计和简单的场景解决性能,当然本篇次要通俗的介绍其表单设计及展现性能,将其利用到咱们的流程可视化自定义编辑中,至于formilyjs自定义开发控件的话后续有机会独自篇章进行阐明。
designable可视化表单设计
formilyjs官网提供了Formily 设计器,咱们当初把这个表单设计器拉下来变成咱们的一个页面:
新建src/playground
目录,
yarn add @designable/formily-antd @ant-design/pro-components
去设计器github,把playground
和src
目录下相干文件拷贝下来放入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.tsx
和playground/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地址:链接