06月16, 2019

uform初实战

上周开始尝试着用uform去写一些简单的demo,然后提供给其他同事参考使用。以下算是一些总结。

先要搞清楚一个事,就是uform是一个form的解决方案,并不是UI组件。因此,我们在使用时,通常会结合antd或者其他UI库(需要自己去结合uform搞一套@uform/xxx)一起使用。

官方有提供了一套@uform/antd,所以我们就用这个来实现以下的一些场景。

先搞明白labelColwrapperCol

比如下面的写法:

<SchemaForm
  labelCol={5}
  wrapperCol={8}
/>

我一开始的理解是,labelCol占整个form的5份,wrapperCol是占整个form的8份。就相当于labelCol是5/13,wrapperCol是8/13。

其实不是的,它正确的理解是整个form是分成24份,labelCol占了5份,wrapperCol占了8份,也就是说剩下的11份是右边的留白空间。

表单项的内容为文字

比如这样的情况:

alt

写法如下:

<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

alt

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:

alt

写法如下:

<Field
  type="password"
  title="密码"
  name="password"
  x-props={{
    checkStrength: true,
    autoComplete: "new-password"
  }}
/>

checkStrength是用来显示密码强度那一块的内容的,autoComplete则是为了解决控制台的warning

alt

表单项旁边有一些文字

如图所示:

alt

这个东西,我翻了很多文档,硬是没找着怎么写,最后在钉钉群里面问了白玄,他给了我一个方案才搞定的:

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属性

alt

但我发现这个不同的type,description所在的区域不太一样,譬如日期的:

alt

有些奇怪,所以不推荐使用这个属性。

另外,title属性是可以使用jsx的,所以像上面的密码后面的?,其实也可以写在title里面,我个人是感觉上面(密码)的样式比较奇怪。

下拉Select

input差不多,不同点在于它的enum属性为数组。

<Field
  type="string"
  title="应用列表"
  name="application"
  enum={[]}
  x-props={{
    placeholder: "请选择应用列表"
  }}
/>

然后在effectonFormMount事件里面去请求加载数据:

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);
  })
}}

可输入搜索下拉框

antdSelect有实现,所以只要传入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需要return false,但是这个会导致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) => {

  console.log(props);

  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格式';
      }
    }
  ]}
/>

列表

效果如下:

alt

这个也需要自己自定义组件,可以参考着arraytable来改。

其他

其实上周就差不多写了这些:

alt

剩下的几个,大家翻翻文档就基本上搞定了。

感觉下来,诚如我上一篇所说,除了样式这一块,其他近乎完美。

其实拿我个人来说,我最喜欢的就是:json schema和可视化平台。

本文链接:www.my-fe.pub/post/uform-initial-combat.html

-- EOF --

Comments

评论加载中...

注:如果长时间无法加载,请针对 disq.us | disquscdn.com | disqus.com 启用代理。