本篇次要介绍 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 {}};
而后回到模板列表页,点击预览查看咱们设计的表单是否失常显示
下一章我将介绍表单设计器的一些高级配置,比方动静申请,封装接口注入,表单联动配置等,敬请期待。
本文地址:链接
本文 github 地址:链接
github demo 地址:链接