上篇简要介绍了formilyjs以及基于其构建了表单设计器,实现了表单动态设计已经预览功能,本片将介绍表单设计器的一些高级配置,使其能更好的运用于我们的节点编辑以及连线编辑。
designable高级配置
请求及自定义数据注入
一般来说,我们页面的请求都是统一封装好的,在使用时我们如何把我们封装好的请求或者已经有的数据源注入到我们的
表单设计器中呢:
SchemaField 组件是专门用于解析JSON-Schema动态渲染表单的组件,在使用createSchemaField
时我们可以传入scope
来注入到全局作用域中链接:
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 api from '@/api';
import { createSchemaField } from '@formily/react';
import { LgetItem } from '@/utils/storage';
import { createForm } from '@formily/core';
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,
},
scope: {
$fetch: api,
selectList: [{ label: 'aaa', value: 'aaa' }, { label: 'bbb', value: 'bbb' }]
}
});
const Preview: FC = ({ 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 (
);
};
export default Preview;
在使用表单设计器时,便可以使用注入的scope
值,比如我们准备一个json,选择一个下拉控件,配置相应器,然后通过我们注入的请求获取这个json,展示对应的下拉值:
$effect(() => {
$self.loading = true
$fetch({
url: '/getSelectList',
method: 'get',
params: {}
}).then(res => {
$self.loading = false
// 当返回值不是label和value时转化一下
$self.dataSource = res.map(s => ({ label: res.name, value: res.id }))
}).catch(() => {
$self.loading = false
})
}, [])
这里的$fetch
就是我们注入的请求了
表单配置
- form提交的key为字段标识,可自定义修改,默认为随机字符串
- 标题为form表单的label
支持自定义搜索的,比如select,需要在组件属性下筛选器中配置表单式,才能支持label搜索
(inputValue, option) => { return option.label.indexOf(inputValue) !== -1; }
- 如果配置了响应器规则中和外部一样的配置,比如组件属性,那么响应器规则中的会覆盖外部的配置(外部的组件属性失效)
高级配置
高级配置在响应器规则中,这里主要针对于一些普通配置无法实现的功能进行说明,比如常见的联动,动态取值等场景进行说明。
$self
为当前选中的表单对象$form
为form对象$deps
为依赖对象(需在上方依赖字段配置来源字段)$observable
声明一个可观察对象$effect
和react的useEffect
使用类似$values
为提交的form表单对象
动态枚举值,假如我们有一个select控件,这个控件的值是接口返回的
$effect(() => { $self.loading = true $fetch({ url: '/getSelectList', method: 'get', params: {} }).then(res => { $self.loading = false // 当返回值不是label和value时转化一下 $self.dataSource = res.map(s => ({ label: res.name, value: res.id })) }).catch(() => { $self.loading = false }) }, [])
联动变化枚举值,比如我们有两个select,第一个select是
mechanism
机构,第一个select是user
人员,我们选择一个机构后,第二个select会根据第一个select的值来从接口中获取机构下的人员列表,这是一个比较常见的联动选择功能,那么放在表单设计器里面我们该如何实现呢(实现方式不唯一,这里提供思路)。// 第一个select,我们监听mechanism的变化,flag主要应用为跳过初次渲染(保证反显正常展示),当mechanism改版(即手动选值)后,清空user的取值。 $effect(() => { $self.loading = true $fetch({ url: '/getMechanismList', method: 'get', params: {} }).then(res => { $self.loading = false // 当返回值不是label和value时转化一下 $self.dataSource = res.map(s => ({ label: res.name, value: res.id })) }).catch(() => { $self.loading = false }) }, []) const state = $observable({ flag: false }); $effect(() => { if (state.flag) { $form.reset('user'); } state.flag = true; }, [$self.value])
当
mechanism
变化时,清空user
列表,发起请求获取user
列表$effect(() => { $self.dataSource = [] if ($deps.mechanism) { $self.loading = true $fetch({ url: '/getSelectList', method: 'get', params: { mechanism: $deps.mechanism } }).then(res => { $self.loading = false // 当返回值不是label和value时转化一下 $self.dataSource = res.map(s => ({ label: res.name, value: res.id })) }).catch(() => { $self.loading = false }) } }, [$deps.mechanism])
小试牛刀
我们使用mock数据简单做一个联动
// mock/api.ts
export default {
'GET /api/mechanism': [
{
value: '1',
label: '机构1',
},
{
value: '2',
label: '机构2',
},
],
'GET /api/users': (req, res) => {
// 添加跨域请求头
const query: any = req.query;
const user: any = {
'1': [
{
value: '1-1',
label: '机构1-人员1',
},
{
value: '1-2',
label: '机构1-人员2',
},
],
'2': [
{
value: '2-1',
label: '机构2-人员1',
},
{
value: '2-2',
label: '机构2-人员2',
},
],
};
res.send(user[query.id]);
},
};
我们定义了两mock接口,mechanism
和users
,前者返回一个机构列表,后者根据前者的id返回对应的列表,接着我们在上一期中的预览弹窗中注入请求,这里我们直接使用umi提供的request
。
// Preview.tsx
import { request } from 'umi';
...
const SchemaField = createSchemaField({
components: ...,
scope: {
$fetch: request,
},
});
最后我们在表单设计中添加请求
// mechanism
$effect(() => {
$self.loading = true
$fetch("/api/mechanism", {})
.then((res) => {
$self.loading = false
// 当返回值不是label和value时转化一下
$self.dataSource = res
})
.catch(() => {
$self.loading = false
})
}, [])
// users
$effect(() => {
$self.dataSource = []
if ($deps.mechanism) {
$self.loading = true
$fetch("/api/users", {
params: {
id: $deps.mechanism,
},
})
.then((res) => {
$self.loading = false
// 当返回值不是label和value时转化一下
$self.dataSource = res
})
.catch(() => {
$self.loading = false
})
}
}, [$deps.mechanism])
好了,本篇介绍的功能都实现了,那么下一篇我们将把流程可是化的编辑功能和我们的表单模板进行关联起来,修改表单模板就能修改节点或连线的属性编辑,达到真正的动态属性编辑效果,尽情期待。