本篇主要介绍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.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;
这里我们简单的把建立的表单数据存储在本地localStorage
中,点击配置后跳转到我们的设计页面,
调整一下表单设计的保存,我使用设计器拖拽设计完成后,进行数据存储,找到对应id的表单模板将设计的schema
赋值给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 {}
};
然后回到模板列表页,点击预览查看我们设计的表单是否正常显示
下一章我将介绍表单设计器的一些高级配置,比如动态请求,封装接口注入,表单联动配置等,敬请期待。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。