美文网首页ReactNative
antd 常用知识点和小技巧总结

antd 常用知识点和小技巧总结

作者: 林ze宏 | 来源:发表于2018-08-30 17:03 被阅读0次

    目录

    • 1 form 表单中 FormItem 的布局
    • 2 form 表单,FormItem 的 rules 中新增 validator,实时请求校验
    • 3 利用 validator 和正则,验证中文
    • 4 form.validateFields 直接获取表单的值
    • 5 form 表单提交 htmlType,改为 onClick
    • 6 Input 组件,利用 maxLength 属性,限制最大输入内容长度
    • 7 InputNumber 只能输入数字
    • 8 menu 实现回缩效果注意点
    • 9 左侧菜单调整宽度设置
    • 10 表格 Columns 字段 id 页面不展示情况
    • 11 自定义 Modal
    • 12 Select 组件清除选框内容
    • 13 antd、mobx @注入的顺序
    • 14 解决 table 组件, key 警告
      14.1 方法一:使用 rowKey
      14.2 方法二:dataSource 数据新增 key
    • 15 Form.create 方式
      15.1 方式一:@ 注解
      15.2 方式二:高阶写法
    • 16 Form initialValue 值编辑后,表单的值不改变问题
      16.1 方法一
      16.2 方法二
    • 17 Modal 是否显示 footer 底部按钮
    • 18 有 connect 和 Form 表单
    • 19 Tree 树组件增加右键菜单
    • 20 直接使用 rc-form 库 createForm,与 antd Form 的 Form.create() 设置样式不同
    • 21 Form 表单实时校验 _.debounce 应用,和不同字段相互校验影响
    • 22 Form 组件方法 getFieldsValue 获取自定义组件的值
    • 23 Form 表单清空和重置的区别以及方法
    • 24 DatePicker 组件,部分日期/时间为可选

    1 form表单中FormItem的布局

    使用 getFieldDecorator 包裹的输入框或者 Select,必须是在最外层,也就是只有一层,否则,检验会一直不通过,所以,需要重新布局应该在 getFieldDecorator 的外层添加父节点,而不应该在里面。

    例:

    <FormItem
      {...formItemLayout}
      label="所属应用"
    >
      <div>
        {getFieldDecorator('apiwgAppName', {
          rules: [{ required: false, message: '请选择' }],
        initialValue: apiwgAppName || ""
        })(
          <Input disabled={this.store.data.apiId ? true : false}
            className="control-special" readOnly style={{ width: "70%" }}
            onClick={this.showModal.bind(this, "apiwgApp")} />
        )}
        <Button className="btn-modal" type="primary" onClick={this.showModal.bind(this, "apiwgApp")}
          disabled={this.store.data.apiId ? true : false}
        >选择所属应用
        </Button>
        <a
          style={{ marginLeft: '8px' }}
          onClick={this.openNewAppDlg.bind(this)}
          className={`api-add ${
            this.store.data.apiId ? 'disabled' : ''
            }`}
        >
          +新增应用
        </a>
      </div>
    </FormItem>
    
    
    

    2 form表单,FormItem 的 rules 中新增 validator,实时请求校验

    <FormItem
      labelCol={{ span: 8 }}
      wrapperCol={{ span: 15 }}
      label="菜单名称"
    >
      {form.getFieldDecorator('menuName', {
        rules: [
          { required: true, message: '菜单名称不能为空' },
          { type: 'string', max: 30, message: '菜单名称过长' },
          { validator: this.handleCheckName }, 
          { whitespace: true, message: '请输入非空白内容' }
        ],
        initialValue: this.props.menuSysData.menuName,
      })(
        <Input
          // placeholder="请输入菜单名称"
          disabled={disableFlag}
        />
      )}
    </FormItem>
    
    
    
    // 实时校验
      handleCheckName = (rule, value, callback) => {
        const { checkName, actionType } = this.state;
        if (!this.trim(value) || (checkName && actionType === 'M' && this.trim(value) === checkName)) {
          callback();
          return;
        }
        let params = {
          menuName: value,
          state: "00A"
        };
        MenuSysService.checkMenuName(params).then(result => {
          if (!result || !result.resultObject) {
            return;
          }
          let code = result.resultObject.code;
          if (code && code > 0) {
            callback('系统名称已存在!');
          }
          callback();
        });
      }
    
    
    

    3 利用 validator 和正则,验证中文

    <FormItem
            hasFeedback={!disableFlag}
            labelCol={{ span: 6 }}
            wrapperCol={{ span: 15 }}
            label="账号" >
            {form.getFieldDecorator('userCode', {
              initialValue: '',
              rules: [
                { required: !disableFlag, validator: this.usercodeValidator },
                { type: 'string', max: 30, message: '账号过长' },
                { whitespace: true, message: '内容不能为空' }
              ],
            })(
              <Input placeholder="请输入账号" disabled={account} maxLength="30" autoComplete="false" />)}
    </FormItem>
    
    
    
    usercodeValidator = (rule, value, callback) => {
        const { userData } = this.props;
        if (!value) {
          callback('内容不能为空');
          return;
        }
    
      // !!!中文验证
        const reg = /[\u4E00-\u9FA5]{1,4}/;   /*定义验证表达式*/
        if (reg.test(value)) { /*进行验证*/
          callback('账号不能为中文');
          return;
        }
    
    
        if (userData.userCode === value) {
          callback();
        }
        else {
          let params = {
            userCode: value + "",  // 查一下有没有这个编码
            useState: '10301'
          };
          SysUserMgService.checkUserCode(params).then(result => {
            if (!result || result.code !== '0') {
              callback(result.message);
              return;
            }
            if (result.resultObject && result.resultObject.num !== 0) {
              callback('该账号已存在');
              return;
            }
            callback();
          });
        }
      }
    
    
    

    4 form.validateFields 直接获取表单的值

    this.props.form.validateFields((err, fieldsValue) => {
         if (err) return;
         this.handleSubmit(fieldsValue);
     });
    
    
    

    5 form 表单提交 htmlType,改为 onClick

    说明:因为之前遇到过使用 htmlType 提交表单会有问题,但是改为 onClick 后,就没问题了,所以,也记录一下。
    htmlType 是官网使用的方式,具体问题本人当时忘记截个图了。

    <Form layout="inline" onSubmit={this.handleSubmit}>
            <FormItem
              validateStatus={userNameError ? 'error' : ''}
              help={userNameError || ''}
            >
              {getFieldDecorator('userName', {
                rules: [{ required: true, message: 'Please input your username!' }],
              })(
                <Input prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />} placeholder="Username" />
              )}
            </FormItem>
            <FormItem
              validateStatus={passwordError ? 'error' : ''}
              help={passwordError || ''}
            >
              {getFieldDecorator('password', {
                rules: [{ required: true, message: 'Please input your Password!' }],
              })(
                <Input prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />} type="password" placeholder="Password" />
              )}
            </FormItem>
            <FormItem>
              <Button
                type="primary"
                htmlType="submit"
                disabled={hasErrors(getFieldsError())}
              >
                Log in
              </Button>
            </FormItem>
          </Form>
    
    
    
    // 改变后:
    <Form layout="inline" >
            <FormItem
              validateStatus={userNameError ? 'error' : ''}
              help={userNameError || ''}
            >
              {getFieldDecorator('userName', {
                rules: [{ required: true, message: 'Please input your username!' }],
              })(
                <Input prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />} placeholder="Username" />
              )}
            </FormItem>
            <FormItem
              validateStatus={passwordError ? 'error' : ''}
              help={passwordError || ''}
            >
              {getFieldDecorator('password', {
                rules: [{ required: true, message: 'Please input your Password!' }],
              })(
                <Input prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />} type="password" placeholder="Password" />
              )}
            </FormItem>
            <FormItem>
              <Button
                type="primary"
                disabled={hasErrors(getFieldsError())}
                onClick={() => this.handleSubmit()}
              >
                Log in
              </Button>
            </FormItem>
          </Form>
    
    
    

    6 Input 组件,利用 maxLength 属性,限制最大输入内容长度

    <Input 
      placeholder="请输入账号" 
      disabled={account} 
      maxLength="30" 
      autoComplete="off" 
    />
    
    
    

    7 InputNumber 只能输入数字:

    <InputNumber
         formatter={value => value}
         parser={value => parseInt(value) || ''}
         style={{ width: '100%' }}
         step={1}
         onChange={(val) => this.onChangeIpt(1, val)}
     />
    
    
    

    8 menu 实现回缩效果注意点

    说明:menu 必须放在 Sider 中,才能实现缩回去的,这个有特定的布局。

    <Sider 
      style={{ background: '#1D2023', height: '100%' }}
      trigger={null} 
      collapsible 
      collapsed={this.state.collapsed} 
      width={140} 
      collapsedWidth={40} 
    >
        <BaseMenu
           toggle={this.toggle}
           collapsed={this.state.collapsed}
           history={history}
           location={location}
         />
    </Sider>
    
    
    

    9 左侧菜单调整宽度设置

    说明:通过在 Sider 组件,设置 width,调整菜单的宽度,通过设置 collapsedWidth,调整调整缩进的宽度。

    <Sider 
      style={{ background: '#1D2023', height: '100%' }}
      trigger={null} 
      collapsible 
      collapsed={this.state.collapsed} 
      width={140} 
      collapsedWidth={40} 
    >
        <BaseMenu
           toggle={this.toggle}
           collapsed={this.state.collapsed}
           history={history}
           location={location}
         />
    </Sider>
    
    
    

    10 表格 Columns 字段 id 页面不展示情况

    说明:一般而言,表格 Columns 字段 id 是在界面不展示的,但是,对于有些逻辑的处理,又是需要的,可以使用相应样式隐藏的处理方式。

    常规展示的情况:
    {
      title: '序号',
      dataIndex: 'algoId',
      key: 'algoId'
    },
    
    不展示id字段:
    {
      title: '',
      dataIndex: 'algoId',
      key: 'algoId',
      width: 0,
      render: item => {
        return (
          <span style={{ display: 'none' }} title={item}>
            {item}
          </span>
        );
      }
    },
    
    
    

    11 自定义 Modal

    查看元素可知,Modal 是在界面构建完成之后,由 js 控制,动态的添加,所以想事先获取 ant-modal-body 中 DOM 元素的节点是不可能的,但是一般情况也不会去获取它。
    自定义 Modal,解决上述的问题。

    关键代码:
    说明:
    1:因为我们使用的是 antd,所以,下面的样式是不需要引入的。这个跟 antd 的 Modal 样式重复。
    2:Modal 的隐藏和显示,是通过控制 class 为 ant-modal-mask 和 ant-modal-wrap 两个 div 的显示和隐藏。

    • 通过给 ant-modal-mask 的 div,添加另外一个 className:ant-modal-mask-hidden,来控制其隐藏,也可以通过 display 来控制。
    • 通过给 ant-modal-wrap 设置行内样式 display: none,来控制其隐藏。不过,也可以使用 className,随便都可以。
    界面布局:
    
    <div className="ant-modal-mask" ></div>
    <div tabIndex="-1" className="ant-modal-wrap " role="dialog" aria-labelledby="rcDialogTitle0" style={{}}>
      <div role="document" className="ant-modal" style={{ width: '920px' }}>
        <div className="ant-modal-content">
          <div className="ant-modal-header">
           ...
          </div>
          <div className="ant-modal-body" style={{ background: 'rgb(16, 16, 17)' }}>
            ...
          </div>
        </div>
      </div>
      <div tabIndex="0" style={{ width: '0px', height: '0px', overflow: 'hidden' }}>
        sentinel
        </div>
    </div>
    
    
    样式:
    .ant-modal-mask { // 遮罩层
      position: fixed;
      top: 0;
      right: 0;
      left: 0;
      bottom: 0;
      background-color: rgba(0, 0, 0, 0.65);
      height: 100%;
      z-index: 1000;
      filter: alpha(opacity=50);
    }
    
    .ant-modal-wrap {
      position: fixed;
      overflow: auto;
      top: 0;
      right: 0;
      bottom: 0;
      left: 0;
      z-index: 1000;
      -webkit-overflow-scrolling: touch;
      outline: 0;
    }
    
    .ant-modal {
      font-family: "Chinese Quote", -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
      font-size: 14px;
      font-variant: tabular-nums;
      line-height: 1.5;
      color: rgba(0, 0, 0, 0.65);
      -webkit-box-sizing: border-box;
      box-sizing: border-box;
      margin: 0;
      padding: 0;
      list-style: none;
      position: relative;
      width: 920px;
      margin: 0 auto;
      top: 100px;
      padding-bottom: 24px;
    }
    
    
    

    12 Select组件清除选框内容

    通过给 Select 组件新增 allowClear 属性。注意:allowClear 也会触发 onChange 方法,所以,也要单独处理一下,因为 value 和 element 为undefined。

    <Select {...this.props} placeholder="请选择" allowClear={true} >
      ...
    </Select>
    
    
    

    13 antd、mobx @注入的顺序

    顺序

    14 解决table组件, key警告

    一般都是使用 rowkey 方法一解决(后台数据要保证没有重复);

    14.1 方法一:使用 rowKey

    使用irowKey

    14.2 方法二:dataSource 数据新增 key

    dataSource数据新增key

    15 Form.create 方式

    15.1 方式一:@注解

    @Form.create({})
    
    

    15.2 方式二:高阶写法

    export default (Form.create({})(APP));
    
    

    16 Form initialValue 值编辑后,表单的值不改变问题

    16.1 方法一

    其实,只要编辑成功后,回调调用 form.resetFields(),就可以了,如果
    是使用 modal 框弹出的表单,就可以直接使用 destroyOnClose = {true} 属性。

    import React from 'react';
    import { Input, Modal, Form } from 'antd';
    import styles from './UserModal.less';
    
    const FormItem = Form.Item;
    
    const UserModal = ({ currentItem, dispatch, form, visible }) => {
    
      function handleOk() {
        form.validateFields((err, fieldsValue) => {
          if (err) return;
          dispatch({
            type: 'demo/update',
            payload: {
              currentItem: fieldsValue
            }
          });
        });
      }
    
      function handleCancel() {
        dispatch({
          type: 'demo/hideModal'
        })
      }
    
      const formItemLayout = {
        labelCol: {
          xs: { span: 24 },
          sm: { span: 4 },
        },
        wrapperCol: {
          xs: { span: 24 },
          sm: { span: 20 },
        },
      };
    
      const { getFieldDecorator } = form;
    
      return (
        <div className={styles.root}>
          <Modal
            title="编辑"
            visible={visible}
            onOk={() => handleOk()}
            onCancel={() => handleCancel()}
            destroyOnClose={true}
          >
            <Form>
              <FormItem
                {...formItemLayout}
                label="用户名"
              >
                {getFieldDecorator('name', {
                  initialValue: currentItem.name,
                  rules: [{
                    required: true, message: 'Please input your name!',
                  }],
                })(
                  <Input placeholder="请输入用户名" />
                )}
              </FormItem>
              <FormItem
                {...formItemLayout}
                label="年龄"
              >
                {getFieldDecorator('age', {
                  initialValue: currentItem.age,
                  rules: [{
                    required: true, message: 'Please input your age!',
                  }],
                })(
                  <Input placeholder="请输入年龄" />
                )}
              </FormItem>
              <FormItem
                {...formItemLayout}
                label="地址"
              >
                {getFieldDecorator('address', {
                  initialValue: currentItem.address,
                  rules: [{
                    required: true, message: 'Please input your address!',
                  }],
                })(
                  <Input placeholder="请输入地址" />
                )}
              </FormItem>
            </Form>
          </Modal>
        </div>
      )
    }
    
    export default (Form.create({})(UserModal));
    
    
    主要代码:destroyOnClose={true}
    <Modal
            title="编辑"
            visible={visible}
            onOk={() => handleOk()}
            onCancel={() => handleCancel()}
            destroyOnClose={true}
          >
    ...
    </Modal>
    
    

    16.2 方法二

    如果是 class 类,可以使用钩子。

      componentDidUpdate = (prevProps, prevState) => {
        if (!prevProps.visible) {
          prevProps.form.resetFields();
        }
      };
    
    

    代码参考:

    import React from 'react';
    import { Input, Modal, Form } from 'antd';
    import styles from './UserModal.less';
    
    const FormItem = Form.Item;
    
    @Form.create({})
    class UserModal extends React.PureComponent {
    
      componentDidUpdate = (prevProps, prevState) => {
        if (!prevProps.visible) {
          prevProps.form.resetFields();
        }
      };
    
      handleOk = () => {
        const { dispatch, form } = this.props;
        form.validateFields((err, fieldsValue) => {
          if (err) return;
          dispatch({
            type: 'demo/update',
            payload: {
              currentItem: fieldsValue
            }
          });
        });
      }
    
      handleCancel = () => {
        const { dispatch } = this.props;
        dispatch({
          type: 'demo/hideModal'
        })
      }
    
      render() {
        const { currentItem, form, visible } = this.props;
        const formItemLayout = {
          labelCol: {
            xs: { span: 24 },
            sm: { span: 4 },
          },
          wrapperCol: {
            xs: { span: 24 },
            sm: { span: 20 },
          },
        };
        const { getFieldDecorator } = form;
        return (
          <div className={styles.root}>
            <Modal
              title="编辑"
              visible={visible}
              onOk={() => this.handleOk()}
              onCancel={() => this.handleCancel()}
            >
              <Form>
                <FormItem
                  {...formItemLayout}
                  label="用户名"
                >
                  {getFieldDecorator('name', {
                    initialValue: currentItem.name,
                    rules: [{
                      required: true, message: 'Please input your name!',
                    }],
                  })(
                    <Input placeholder="请输入用户名" />
                  )}
                </FormItem>
                <FormItem
                  {...formItemLayout}
                  label="年龄"
                >
                  {getFieldDecorator('age', {
                    initialValue: currentItem.age,
                    rules: [{
                      required: true, message: 'Please input your age!',
                    }],
                  })(
                    <Input placeholder="请输入年龄" />
                  )}
                </FormItem>
                <FormItem
                  {...formItemLayout}
                  label="地址"
                >
                  {getFieldDecorator('address', {
                    initialValue: currentItem.address,
                    rules: [{
                      required: true, message: 'Please input your address!',
                    }],
                  })(
                    <Input placeholder="请输入地址" />
                  )}
                </FormItem>
              </Form>
            </Modal>
          </div>
        )
      }
    }
    
    export default UserModal;
    // export default (Form.create({})(UserModal));
    
    
    
    

    17 Modal 是否显示 footer 底部按钮

    一般应用场景,详情不需要底部按钮,新增和修改需要。


    api

    解决:
    通过父组件传递一个空的字符串或者 {footer: null} 给 Modal 组件进行属性解构。

    父组件需要传入的值 子组件 Modal

    18 有 connect 和 Form 表单

    function mapStateToProps({ onlineCamera }) {
      return {
        favorites: onlineCamera.favorites,
      };
    }
    
    export default connect(mapStateToProps)(Form.create()(TreeModal));
    
    

    19 Tree 树组件增加右键菜单

    参考:
    https://github.com/ant-design/ant-design/issues/5151

    关键代码:

    <Tree onRightClick={this.treeNodeonRightClick} >

    // 实现这个方法 treeNodeonRightClick

    treeNodeonRightClick(e) {
            this.setState({
                rightClickNodeTreeItem: {
                    pageX: e.event.pageX,
                    pageY: e.event.pageY,
                    id: e.node.props['data-key'],
                    categoryName: e.node.props['data-title']
                }
            });
        }
    
    
    

    // id 和 categoryName 是生成时绑上去的

    <TreeNode
                            key={item.id}
                            title={title}
                            data-key={item.id}
                            data-title={item.categoryName}
                        />);
    
    
    

    // 最后绑个菜单就可以实现了

    getNodeTreeRightClickMenu() {
            const {pageX, pageY} = {...this.state.rightClickNodeTreeItem};
            const tmpStyle = {
                position: 'absolute',
                left: `${pageX - 220}px`,
                top: `${pageY - 70}px`
            };
            const menu = (
                <Menu
                    onClick={this.handleMenuClick}
                    style={tmpStyle}
                    className={style.categs_tree_rightmenu}
                >
                    <Menu.Item key='1'><Icon type='plus-circle'/>{'加同级'}</Menu.Item>
                    <Menu.Item key='2'><Icon type='plus-circle-o'/>{'加下级'}</Menu.Item>
                    <Menu.Item key='4'><Icon type='edit'/>{'修改'}</Menu.Item>
                    <Menu.Item key='3'><Icon type='minus-circle-o'/>{'删除目录'}</Menu.Item>
                </Menu>
            );
            return (this.state.rightClickNodeTreeItem == null) ? '' : menu;
        }
    
    
    

    getNodeTreeRightClickMenu 方法放在 render 中:

    getNodeTreeRightClickMenu 方法是放在生成主界面的方法里( render ),因为每一次 state 的变化后,render 方法都会执行,所以变一下任意的 this.state 里面的状态,就会执行 render 方法 ,这样 getNodeTreeRightClickMenu 方法放在 render 方法里来生成界面的一部分。就可以了

    项目中实现关键代码:

    /*
     * @Author: lin.zehong 
     * @Date: 2018-12-02 22:13:59 
     * @Last Modified by: lin.zehong
     * @Last Modified time: 2018-12-19 16:36:27
     * @Desc: 收藏夹--树 
     */
    import React from 'react';
    import { connect } from 'dva';
    import { Tree, Menu } from 'antd';
    import Zcon from 'zteui-icon';
    import styles from './TreeCollect.less';
    
    const { TreeNode } = Tree;
    
    class TreeCollect extends React.Component {
      state = {
        expandedKeys: ['-1'],
      }
    
      // 树节点右键事件
      treeNodeonRightClick = ({ event, node }) => {
        event.persist();
        const { offsetLeft, _isCollapsed } = this.props;
        const menuWidth = _isCollapsed ? 80 : 200;
        const { favorites, favoritesDetail } = node.props;
        this.changefavorites(favorites);
        const hasChild = !!(favorites && favorites.scjId); // 收藏夹
        this.setState({
          rightClickNodeTreeItem: {
            pageX: event.pageX - offsetLeft - 16 - menuWidth,
            pageY: event.target.offsetTop + 28,
            key: node.props.eventKey,
            id: node.props.eventKey,
            title: node.props.title,
            favorites,
            favoritesDetail,
            hasChild,
          },
        });
      }
    
      // 右键节点页面展示
      getNodeTreeRightClickMenu = () => {
        const { rightClickNodeTreeItem } = this.state;
        const { pageX, pageY, hasChild, key } = { ...rightClickNodeTreeItem };
        const tmpStyle = {
          position: 'absolute',
          left: `${pageX}px`,
          top: `${pageY}px`,
          boxShadow: '2px 2px 10px #333333',
        };
        const menuHasNode = (
          <Menu
            onClick={this.handleMenuClick}
            style={tmpStyle}
            className={styles.categs_tree_rightmenu}
          >
            <Menu.Item key='1'>自动巡查</Menu.Item>
            <Menu.Item key='2'>重命名</Menu.Item>
            <Menu.Item key='3'>添加同级目录</Menu.Item>
            <Menu.Item key='4'>添加子目录</Menu.Item>
            <Menu.Item key='5'>删除</Menu.Item>
          </Menu>
        );
        const menuRoot = (
          <Menu
            onClick={this.handleMenuClick}
            style={tmpStyle}
            className={styles.categs_tree_rightmenu}
          >
            <Menu.Item key='1'>自动巡查</Menu.Item>
            <Menu.Item key='2'>重命名</Menu.Item>
            <Menu.Item key='4'>添加子目录</Menu.Item>
          </Menu>
        );
        const menuNoNode = (
          <Menu
            onClick={this.handleMenuClick}
            style={tmpStyle}
            className={styles.categs_tree_rightmenu}
          >
            <Menu.Item key='6'>取消收藏</Menu.Item>
          </Menu>
        );
    
        const menu = hasChild ? (key === "-1" ? menuRoot : menuHasNode) : menuNoNode;
    
        return (rightClickNodeTreeItem == null) ? '' : menu;
      }
    
      // 隐藏右键菜单
      hideTreeRight = () => {
        this.setState({ rightClickNodeTreeItem: null });
      }
    
    
      render() {
        const { expandedKeys, selectedKeys } = this.state;
        const { isExpand, gData } = this.props;
        const loop = data => data.map((item) => {
          if (item.children && item.favorites) {
            return <TreeNode key={item.key} icon={<Zcon type="thing" />} title={item.title} favorites={item.favorites}>{loop(item.children)}</TreeNode>;
          }
          return <TreeNode key={item.favoritesDetail.sxtxxId} title={item.title} favoritesDetail={item.favoritesDetail} />;
        });
        return (
          <div className={`${styles.root} ${isExpand ? '' : styles.hideTree}`} onClick={() => this.hideTreeRight()}>
            <Tree
              showIcon
              className="draggable-tree"
              defaultExpandedKeys={expandedKeys}
              selectedKeys={selectedKeys}
              onRightClick={this.treeNodeonRightClick}
              onSelect={this.onSelect}
            >
              {loop(gData)}
            </Tree>
            {this.getNodeTreeRightClickMenu()}
          </div>
        );
      }
    }
    
    function mapStateToProps({ onlineCamera, publicModel }) {
      return {
        gData: onlineCamera.collectTree,
        cameraNum: onlineCamera.cameraNum,
        inspectionCamera: onlineCamera.inspectionCamera,
        _isCollapsed: publicModel._isCollapsed,
      };
    }
    
    export default connect(mapStateToProps)(TreeCollect);
    
    
    
    

    20 直接使用 rc-form 库 createForm,与 antd Form 的 Form.create() 设置样式不同

    • 使用 antd Form 的 Form.create()
    import React from 'react'
    import PropTypes from 'prop-types'
    import { Form, Button } from 'antd'
    
    class BalloonContent extends React.Component {
      render() {
        const { form } = this.props;
        return (
          <div>
            <Form
              size='medium'
              className={Styles.wrapForm}
            >
              <Form.Item
                label="算子输出"
              >
                {form.getFieldDecorator('stdioOutput', {
                  rules: [
                    {
                      required: true,
                      message: '输出不能为空',
                    },
                  ],
                })(<Input />)}
              </Form.Item>
            </Form>
          </div>
        )
      }
    }
    
    export default Form.create()(BalloonContent) // !!!
    
    
    结果样式
    • 直接使用 rc-form 库 createForm
    import React from 'react'
    import PropTypes from 'prop-types'
    import { Form, Button } from 'antd'
    import { createForm } from 'rc-form'
    
    class BalloonContent extends React.Component {
      render() {
        const { form } = this.props;
        const { getFieldDecorator, getFieldError } = form ;
        const stdioOutputError = getFieldError('stdioOutput'); // !!!
    
        return (
          <div>
            <Form
              size='medium'
              className={Styles.wrapForm}
            >
              <Form.Item
                label="算子输出"
                required // !!!
                validateState={stdioOutputError ? 'error' : 'success'} // !!!
                help={stdioOutputError} // !!!
              >
                {form.getFieldDecorator('stdioOutput', {
                  rules: [
                    {
                      required: true,
                      message: '输出不能为空',
                    },
                  ],
                })(<Input />)}
              </Form.Item>
            </Form>
          </div>
        )
      }
    }
    
    export default createForm ()(BalloonContent) // !!!
    
    
    结果

    21 Form 表单实时校验 _.debounce 应用,和不同字段相互校验影响

    例如:表单字段,密码和确认密码,改变 Password,如果与 Confirm Password 不一致,也会在 Confirm Password 做提示:

    官网示例:注册新用户,主要代码
    
    compareToFirstPassword = (rule, value, callback) => {
      const { form } = this.props;
      if (value && value !== form.getFieldValue('password')) {
        callback('Two passwords that you enter is inconsistent!');
      } else {
        callback();
      }
    };
    
    validateToNextPassword = (rule, value, callback) => {
      const { form } = this.props;
      if (value && this.state.confirmDirty) {
        form.validateFields(['confirm'], { force: true });
      }
      callback();
    };
    
    
    <Form.Item label="Password" hasFeedback>
      {getFieldDecorator('password', {
        rules: [
          {
            required: true,
            message: 'Please input your password!',
          },
          {
            validator: this.validateToNextPassword,
          },
        ],
    })(<Input.Password />)}
    </Form.Item>
    
    <Form.Item label="Confirm Password" hasFeedback>
      {getFieldDecorator('confirm', {
        rules: [
          {
            required: true,
            message: 'Please confirm your password!',
          },
          {
            validator: this.compareToFirstPassword,
          },
        ],
    })(<Input.Password onBlur={this.handleConfirmBlur} />)}
    </Form.Item>
    
    
    

    实际项目例子,选择所属数据库,校验表名:

    主要代码:
    
    import _ from "lodash";
    
    // 写入新表,选择数据库,需要校验已有的表名
    validateToTableName = (rule, value, callback) => {
      const { form: { getFieldValue, validateFields }} = this.props;
      const targetTableCode = getFieldValue("targetTableCode");
      if (targetTableCode) {
        validateFields(['targetTableCode'], { force: true });
      }
      callback();
    };
    
    // 写入新表,校验表名
    // eslint-disable-next-line
    validateTableExist = _.debounce((rule, value, callback) => {
      const { form: { getFieldValue }, dispatch } = this.props;
      const targetDataSource = getFieldValue("targetDataSource");
      const targetTableCode = getFieldValue("targetTableCode");
      dispatch({
        type: "applyDetail/tableExist",
        payload: {
          dataSourceCode: targetDataSource,
          table: targetTableCode,
        },
      }).then(result => {
        if (result) {
          callback("该表名已存在");
        } else {
          callback();
        }
      })
    }, 500);
    
    
    
    
    <Form.Item label="所属数据库">
      {getFieldDecorator("targetDataSource", {
        rules: [
          {
            required: true,
            message: "请选择所属数据库",
          },
          {
            validator: this.validateToTableName, // !!!
          },
        ],
        initialValue:
          exchangeFormat.targetDataSource ||
          (dataSourceList.length > 0
            ? dataSourceList[0].code
            : undefined),
      })(dataBaseComponent({ className: styles.formInput }))}
    </Form.Item>
    
    <Form.Item label="表名">
      {getFieldDecorator("targetTableCode", {
        rules: [
          {
            required: true,
            message: "请输入新表表名",
          },
          {
            pattern: checkBackEndTableName,
            message: "只支持英文字母、数字、英文格式、下划线",
          },
          {
            validator: this.validateTableExist, // !!!
          },
        ],
        initialValue:
          (exchangeFormat.formatType === WRITE_IN_NEW_TABLE
            ? exchangeFormat.targetTableCode
            : undefined) || undefined,
      })(
        <Input
          className={styles.formInput}
          disabled={disabled}
          placeholder="请输入"
        />
      )}
    </Form.Item>
    
    
    
    

    22 Form 组件方法 getFieldsValue 获取自定义组件的值

    项目实例:对 antd RangePicker 抽取完独立组件后,form 表单获取不到值

    自定义组件被 getFieldsValue 包裹,会获得以下属性:

    onChange方法, 子组件调用此方法,可将值传给父组件,从而Form可拿到自定义组件的值 value属性,获得初始值

    <Form.Item label="发送时间">
      {getFieldDecorator('range-time-picker', {
        rules: [{ required: false, message: '请输入开始时间-结束时间' }],
      })(
        <RangePickerPage />
      )}
    </Form.Item>
    
    

    下面是对 antd RangePicker 进行封装,通过组件 RangePicker 本身的 onChange 方法,调用 this.props.onChange(子组件不用传 onChange 方法,自定义组件被 getFieldsValue 包裹,会自动获取 onChage 属性),则通过 form.validateFields 可以获取到值。

    /*
     * Author: lin.zehong 
     * Date: 2019-10-04 09:14:52 
     * Last Modified by:   lin.zehong 
     * Last Modified time: 2019-10-04 09:14:52 
     * Desc: 对 antd RangePicker 进行封装
     */
    import React from "react";
    import moment from "moment";
    import { DatePicker } from "antd";
    
    const { RangePicker } = DatePicker;
    
    class RangePickerPage extends React.Component {
    
      range = (start, end) => {
        const result = [];
        for (let i = start; i < end; i += 1) {
          result.push(i);
        }
        return result;
      }
    
      disabledDate = (current) => {
        // Can not select days before today and today
        return current && current < moment().endOf('day');
      }
    
      disabledRangeTime = (_, type) => {
        if (type === 'start') {
          return {
            disabledHours: () => this.range(0, 60).splice(4, 20),
            disabledMinutes: () => this.range(30, 60),
            disabledSeconds: () => [55, 56],
          };
        }
        return {
          disabledHours: () => this.range(0, 60).splice(20, 4),
          disabledMinutes: () => this.range(0, 31),
          disabledSeconds: () => [55, 56],
        };
      }
    
      onChange = (dates, dateStrings) => {
        const { onChange } = this.props;  // !!!
        onChange(dateStrings);
      }
    
      render() {
        return (
          <RangePicker
            allowClear
            disabledDate={this.disabledDate}
            disabledTime={this.disabledRangeTime}
            showTime={{
              hideDisabledOptions: true,
              defaultValue: [moment('00:00:00', 'HH:mm:ss'), moment('11:59:59', 'HH:mm:ss')],
            }}
            format="YYYY-MM-DD HH:mm:ss"
            onChange={this.onChange} // !!!
          />
        );
      }
    }
    
    export default RangePickerPage;
    
    
    

    参考:https://juejin.im/post/5c9c6c08e51d4503e514eaac

    23 Form 表单清空和重置的区别以及方法

    这里首先需要明确,清空和重置是不同的概念,清空是把内容都清空掉,而重置是恢复 form 表单初始值。

    例如:新增功能,清空和重置就是一样的效果,而对于编辑,清空就是把初始值都清空掉,重置就是恢复刚开始的初始值。

    • 清空
    form.setFieldsValue({"fieldName": ""});
    
    
    • 重置
    form.resetFields();
    
    

    24 DatePicker 组件,部分日期/时间为可选

    24.1 不能选择今天之前的日期,包括今天的日期也不可以选择

      const disabledDate = (current) => {
        return current && current < moment().endOf('day');
      }
    
    

    24.2 不能选择今天之前的日期,今天日期可以选择

      const disabledDate = (current) => {
        return current && current < moment().subtract(1, 'day');
      }
    
    

    24.3 当前时间之后的时间点,精确到小时

    const [upgradeTime, setUpgradeTime] = useState(moment('00:00:00', 'HH:mm:ss'))
    
    
    const disabledDate = (current) => {
      return current && current < moment().subtract(1, 'day'); // 今天可以选择
    }
    
    
    const disabledDateTime = () => {
      const hours = moment().hours(); // 0~23
      // 当日只能选择当前时间之后的时间点
      if (upgradeTime.date() === moment().date()) {
        return {
          disabledHours: () => range(0, hours + 1),
        };
      }
    }
    
    
    <Form.Item label="发送时间">
      {getFieldDecorator('pushTime', {
        rules: [{ required: false, message: '请输入发送时间' }],
        initialValue: record.pushType === 0 ? null :
          (record.pushTime ? moment(record.pushTime, 'YYYY-MM-DD HH:mm:ss') : null), // 定时发送才显示时间
      })(
        <DatePicker
          format="YYYY-MM-DD HH:mm:ss"
          disabledDate={disabledDate}
          disabledTime={disabledDateTime}
          style={{ width: "100%" }}
          
          onChange={(timer) => setUpgradeTime(timer)} // !!!
          showTime={{ defaultValue: moment(upgradeTime) }} // !!!
        />,
      )}
    </Form.Item>
    
    

    24.4 当前时间之后的时间点,精确到分

    const [upgradeTime, setUpgradeTime] = useState(moment('00:00:00', 'HH:mm:ss'))
    
    
    const disabledDate = (current) => {
      return current && current < moment().subtract(1, 'day'); // 今天可以选择
    }
    
    
    const disabledDateTime = () => {
      const hours = moment().hours(); // 0~23
      const minutes = moment().minutes(); // 0~59
      // 当日只能选择当前时间之后的时间点
      if (upgradeTime.date() === moment().date()) {
        return {
          disabledHours: () => range(0, hours), 
          disabledMinutes: () => range(0, minutes), // 精确到分
        };
      }
    }
    
    
    <Form.Item label="发送时间">
      {getFieldDecorator('pushTime', {
        rules: [{ required: false, message: '请输入发送时间' }],
        initialValue: record.pushType === 0 ? null :
          (record.pushTime ? moment(record.pushTime, 'YYYY-MM-DD HH:mm:ss') : null), // 定时发送才显示时间
      })(
        <DatePicker
          format="YYYY-MM-DD HH:mm:ss"
          disabledDate={disabledDate}
          disabledTime={disabledDateTime}
          style={{ width: "100%" }}
          
          onChange={(timer) => setUpgradeTime(timer)} // !!!
          showTime={{ defaultValue: moment(upgradeTime) }} // !!!
        />,
      )}
    </Form.Item>
    
    
    

    相关文章

      网友评论

        本文标题:antd 常用知识点和小技巧总结

        本文链接:https://www.haomeiwen.com/subject/yeqdwftx.html