上周开始尝试着用uform去写一些简单的demo,然后提供给其他同事参考使用。以下算是一些总结。
先要搞清楚一个事,就是 uform
是一个form的解决方案,并不是UI组件。因此,我们在使用时,通常会结合 antd
或者其他UI库(需要自己去结合uform搞一套 @uform/xxx
)一起使用。
官方有提供了一套 @uform/antd
,所以我们就用这个来实现以下的一些场景。
先搞明白 labelCol
和 wrapperCol
比如下面的写法:
<SchemaForm
labelCol={5}
wrapperCol={8}
/>
我一开始的理解是, labelCol
占整个form的5份, wrapperCol
是占整个form的8份。就相当于 labelCol
是5/13, wrapperCol
是8/13。
其实不是的,它正确的理解是整个form是分成24份, labelCol
占了5份, wrapperCol
占了8份,也就是说剩下的11份是右边的留白空间。
表单项的内容为文字
比如这样的情况:
写法如下:
<Field
type="string"
title="测试"
name="test"
default="haha"
x-props = {{
editable: false
}}
/>
需要注意的是,在submit的时候, values
里面也会包含这个字段,可能需要自己进行过滤一下。
与 是否可编辑
差不多的,还有一个 是否可见
。也许你会这样来写:
<Field
type="string"
title="aa"
name="aa"
x-props = {{
visible: false
}}
/>
但经测试,是行不通的。。这个得写到 effect
里面的 onFormMount
事件里面,对单个字段或者批量的字段设置 visible
。
input表单项
写法如下:
<Field
type="string"
title="用户名"
name="username"
/>
规则rule
通常我们需要有一些规则,比如必填、字符个数、正则判断(比如只能是中文)、在输入的同时要校验后端是否存在了这个值(如果存在,则要提示错误)
方式就是通过 x-rules
,它的写法其实和 antd
的form那一套差不多,不过似乎增加了一种更为人性化的方式,那就是 function
:
x-rules = {[
(val) => {
if (value === "你指定的值") {
return "错误的message信息"
}
return "";
}
]}
return
内容为空字符串时,表示校验通过。
说一个具体场景吧:比如说上传文件,你可以校验上传的file size,当超过1M时,需要error,则可以采用上面的写法。
当然上面的是同步的,异步的写法,相信大家也猜到了,通过 Promise
:
x-rules = {[
val =>
new Promise(resolve => {
// 在这里可以发起请求
setTimeout(() => {
if (val === '1111') {
resolve('不允许重复')
} else {
resolve()
}
}, 300);
})
]}
resolve
空字符串时,则表示校验通过。
password
antd
的Input组件有提供 type
属性,当传入 password
时,自然会渲染成密码框。
所以我们可以通过 x-props
传入 type
属性即可。
不过呢, @uform/antd
提供了更好的UI:
写法如下:
<Field
type="password"
title="密码"
name="password"
x-props={{
checkStrength: true,
autoComplete: "new-password"
}}
/>
checkStrength
是用来显示 密码强度
那一块的内容的, autoComplete
则是为了解决控制台的 warning
:
表单项旁边有一些文字
如图所示:
这个东西,我翻了很多文档,硬是没找着怎么写,最后在钉钉群里面问了白玄,他给了我一个方案才搞定的:
import { Form } from 'antd';
const TextBox = createVirtualBox("text-box", ({ text, children, ...props }) => {
return <Form.Item {...props}>{children}<span style={{marginLeft: 10}}>{text}</span></Form.Item>
});
// ...
<TextBox
label="测试2"
labelCol={{ span: 5 }}
wrapperCol={{ span: 8 }}
required={true}
text="这是一段描述"
>
<Field
x-props={{autoComplete: "new-password"}}
type="string"
required
name="aa"
/>
</TextBox>
我以为这样就结束了,但事实上并没有,还有一些样式上的问题:
antd的label
后面的冒号是通过伪类来实现的,但是@uform/antd
的并不是,它是通过html来实现的,导致了有一些px的差距。
不过在解决问题的过程中,学到了伪类中的 content
的空格写法,要用unicode来处理: 00a0
。
Field的description属性
但我发现这个不同的type, description
所在的区域不太一样,譬如日期的:
有些奇怪,所以 不推荐
使用这个属性。
另外, title
属性是可以使用 jsx
的,所以像上面的密码后面的 ?
,其实也可以写在 title
里面,我个人是感觉上面(密码)的样式比较奇怪。
下拉Select
和 input
差不多,不同点在于它的 enum
属性为数组。
<Field
type="string"
title="应用列表"
name="application"
enum={[]}
x-props={{
placeholder: "请选择应用列表"
}}
/>
然后在 effect
的 onFormMount
事件里面去请求加载数据:
effects={($, { setFieldState }) => {
$('onFormMount').subscribe(() => {
setTimeout(() => {
setFieldState("application", state => {
state.props.enum = [
{label: "应用一", value: "1"},
{label: "应用二", value: "2"},
{label: "应用三", value: "3"},
{label: "应用四", value: "4"},
{label: "应用五", value: "5"},
{label: "应用六", value: "6"}
]
})
}, 300);
})
}}
可输入搜索下拉框
antd
的 Select
有实现,所以只要传入props即可:
<Field
type="string"
title="应用列表(可输入)"
name="application2"
enum={[]}
x-props={{
placeholder: "请选择应用列表",
showSearch: true,
optionFilterProp: "children",
filterOption: (input, option) => option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
}}
/>
多选
也是普通 Select
的扩展:
<Field
type="string"
title="应用列表(多选)"
name="application3"
enum={[]}
x-props={{
placeholder: "请选择应用列表",
mode: "multiple"
}}
/>
Checkbox
<Field
type="checkbox"
enum={[
{label: "吃饭", value: "1"},
{label: "睡觉", value: "2"},
]}
required
title="兴趣爱好"
name="hobby"
/>
Radio
<Field
type="radio"
enum={[
{label: "男", value: "1"},
{label: "女", value: "2"}
]}
title="性别"
name="sex"
x-rules={[
{required: true, message: "请选择性别"}
]}
/>
日期
<Field type="date" title="日期选择" name="date" />
这里有一个注意点,就是传入的值是一个字符串,在antd里面,我们的日期传入值必须是moment对象,但这里不是。。
说到 moment
,这里可能会有一个中英文的问题,所以需要在入口文件那里指定:
import moment from "moment";
moment.locale('zh-cn');
Input file
OK,这个可以来讲讲了。。如果你的组件是选择文件的时候,直接和后端交互,那么是没问题的。
但是如果你希望选择文件后,不与后端交互,和其他字段一起提交到后端,那么现有的方式是有问题。
问题在于: beforeUpload
需要 returnfalse
,但是这个会导致 required
不起作用,也就是说当你选择了一个文件之后,它也会提示未选择文件。
所以我就自己自定义了一个 my-file
的组件:
import React, { useState } from 'react';
import { Upload, Button, Icon } from 'antd';
import { registerFormField, connect } from '@uform/antd';
const SelectFile = React.forwardRef((props, ref) => {
const { onChange, accept, value = {} } = props;
const [fileList, setFileList] = useState(value.name ? [
{
uid: '-1',
name: value.name,
status: 'done',
url: value.url,
}
]: []);
return (
<Upload
fileList={fileList}
accept={accept}
beforeUpload={
(file) => {
setFileList([file]);
onChange(file);
return false;
}
}
onRemove={
() => {
setFileList([]);
onChange("");
}
}
>
<Button>
<Icon type="upload" /> Select File
</Button>
</Upload>
)
});
registerFormField("my-file", connect()(SelectFile));
当然上面的业务,我并没有做得比较通用,这是针对我之前写的业务代码的一个简单抽象(它适合只能上传单个文件)。
使用:
<Field
type="string"
title="单文件上传"
name="upload"
required
x-props={{
listType: 'text',
accept: ".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel"
}}
x-component="my-file"
x-rules={[
{required: true, message: "请上传文件"},
(file) => {
const { name } = file;
if (!name) {
return '';
}
const extname = path.extname(name);
if (extname !== '.xlsx' && extname !== '.xls') {
return '后缀名必须是excel格式';
}
}
]}
/>
列表
效果如下:
这个也需要自己自定义组件,可以参考着array和table来改。
其他
其实上周就差不多写了这些:
剩下的几个,大家翻翻文档就基本上搞定了。
感觉下来,除了样式这一块,其他近乎完美。
其实拿我个人来说,我最喜欢的就是:json schema和可视化平台。