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
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。