antd 常用知識點和小技巧總結(jié)

目錄

  • 1 form 表單中 FormItem 的布局
  • 2 form 表單,F(xiàn)ormItem 的 rules 中新增 validator宛逗,實時請求校驗
  • 3 利用 validator 和正則呼盆,驗證中文
  • 4 form.validateFields 直接獲取表單的值
  • 5 form 表單提交 htmlType年扩,改為 onClick
  • 6 Input 組件,利用 maxLength 屬性访圃,限制最大輸入內(nèi)容長度
  • 7 InputNumber 只能輸入數(shù)字
  • 8 menu 實現(xiàn)回縮效果注意點
  • 9 左側(cè)菜單調(diào)整寬度設(shè)置
  • 10 表格 Columns 字段 id 頁面不展示情況
  • 11 自定義 Modal
  • 12 Select 組件清除選框內(nèi)容
  • 13 antd厨幻、mobx @注入的順序
  • 14 解決 table 組件, key 警告
    14.1 方法一:使用 rowKey
    14.2 方法二:dataSource 數(shù)據(jù)新增 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() 設(shè)置樣式不同
  • 21 Form 表單實時校驗 _.debounce 應(yīng)用,和不同字段相互校驗影響
  • 22 Form 組件方法 getFieldsValue 獲取自定義組件的值
  • 23 Form 表單清空和重置的區(qū)別以及方法
  • 24 DatePicker 組件批糟,部分日期/時間為可選

1 form表單中FormItem的布局

使用 getFieldDecorator 包裹的輸入框或者 Select格了,必須是在最外層,也就是只有一層徽鼎,否則盛末,檢驗會一直不通過弹惦,所以,需要重新布局應(yīng)該在 getFieldDecorator 的外層添加父節(jié)點悄但,而不應(yīng)該在里面肤频。

例:

<FormItem
  {...formItemLayout}
  label="所屬應(yīng)用"
>
  <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}
    >選擇所屬應(yīng)用
    </Button>
    <a
      style={{ marginLeft: '8px' }}
      onClick={this.openNewAppDlg.bind(this)}
      className={`api-add ${
        this.store.data.apiId ? 'disabled' : ''
        }`}
    >
      +新增應(yīng)用
    </a>
  </div>
</FormItem>


2 form表單,F(xiàn)ormItem 的 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: '請輸入非空白內(nèi)容' }
    ],
    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('系統(tǒng)名稱已存在宵荒!');
      }
      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: '內(nèi)容不能為空' }
          ],
        })(
          <Input placeholder="請輸入賬號" disabled={account} maxLength="30" autoComplete="false" />)}
</FormItem>



usercodeValidator = (rule, value, callback) => {
    const { userData } = this.props;
    if (!value) {
      callback('內(nèi)容不能為空');
      return;
    }

  // >秽帧1取!中文驗證
    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 是官網(wǎng)使用的方式宵膨,具體問題本人當(dāng)時忘記截個圖了架谎。

<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 屬性辟躏,限制最大輸入內(nèi)容長度

<Input 
  placeholder="請輸入賬號" 
  disabled={account} 
  maxLength="30" 
  autoComplete="off" 
/>


7 InputNumber 只能輸入數(shù)字:

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


8 menu 實現(xiàn)回縮效果注意點

說明:menu 必須放在 Sider 中谷扣,才能實現(xiàn)縮回去的,這個有特定的布局捎琐。

<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 左側(cè)菜單調(diào)整寬度設(shè)置

說明:通過在 Sider 組件会涎,設(shè)置 width,調(diào)整菜單的寬度瑞凑,通過設(shè)置 collapsedWidth末秃,調(diào)整調(diào)整縮進的寬度。

<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 是在界面不展示的练慕,但是,對于有些邏輯的處理篱蝇,又是需要的贺待,可以使用相應(yīng)樣式隱藏的處理方式徽曲。

常規(guī)展示的情況:
{
  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 是在界面構(gòu)建完成之后,由 js 控制秃臣,動態(tài)的添加涧衙,所以想事先獲取 ant-modal-body 中 DOM 元素的節(jié)點是不可能的哪工,但是一般情況也不會去獲取它。
自定義 Modal弧哎,解決上述的問題雁比。

關(guān)鍵代碼:
說明:
1:因為我們使用的是 antd,所以撤嫩,下面的樣式是不需要引入的偎捎。這個跟 antd 的 Modal 樣式重復(fù)。
2:Modal 的隱藏和顯示序攘,是通過控制 class 為 ant-modal-mask 和 ant-modal-wrap 兩個 div 的顯示和隱藏茴她。

  • 通過給 ant-modal-mask 的 div,添加另外一個 className:ant-modal-mask-hidden程奠,來控制其隱藏丈牢,也可以通過 display 來控制。
  • 通過給 ant-modal-wrap 設(shè)置行內(nèi)樣式 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組件清除選框內(nèi)容

通過給 Select 組件新增 allowClear 屬性。注意:allowClear 也會觸發(fā) onChange 方法垫桂,所以晶姊,也要單獨處理一下,因為 value 和 element 為undefined伪货。

<Select {...this.props} placeholder="請選擇" allowClear={true} >
  ...
</Select>


13 antd们衙、mobx @注入的順序

順序

14 解決table組件, key警告

一般都是使用 rowkey 方法一解決(后臺數(shù)據(jù)要保證沒有重復(fù))碱呼;

14.1 方法一:使用 rowKey

使用irowKey

14.2 方法二:dataSource 數(shù)據(jù)新增 key

dataSource數(shù)據(jù)新增key

15 Form.create 方式

15.1 方式一:@注解

@Form.create({})

15.2 方式二:高階寫法

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

16 Form initialValue 值編輯后蒙挑,表單的值不改變問題

16.1 方法一

其實,只要編輯成功后愚臀,回調(diào)調(diào)用 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 底部按鈕

一般應(yīng)用場景欣鳖,詳情不需要底部按鈕,新增和修改需要茴厉。


api

解決:
通過父組件傳遞一個空的字符串或者 {footer: null} 給 Modal 組件進行屬性解構(gòu)泽台。

父組件需要傳入的值
子組件 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

關(guān)鍵代碼:

<Tree onRightClick={this.treeNodeonRightClick} >

// 實現(xiàn)這個方法 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}
                    />);


// 最后綁個菜單就可以實現(xiàn)了

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 方法都會執(zhí)行怀酷,所以變一下任意的 this.state 里面的狀態(tài)稻爬,就會執(zhí)行 render 方法 ,這樣 getNodeTreeRightClickMenu 方法放在 render 方法里來生成界面的一部分蜕依。就可以了

項目中實現(xiàn)關(guān)鍵代碼:

/*
 * @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'],
  }

  // 樹節(jié)點右鍵事件
  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,
      },
    });
  }

  // 右鍵節(jié)點頁面展示
  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() 設(shè)置樣式不同

  • 使用 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) // !!!

結(jié)果樣式
  • 直接使用 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) // !!!

結(jié)果

21 Form 表單實時校驗 _.debounce 應(yīng)用,和不同字段相互校驗影響

例如:表單字段样眠,密碼和確認密碼竞滓,改變 Password,如果與 Confirm Password 不一致吹缔,也會在 Confirm Password 做提示:

官網(wǎng)示例:注冊新用戶商佑,主要代碼

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>


實際項目例子,選擇所屬數(shù)據(jù)庫厢塘,校驗表名:

主要代碼:

import _ from "lodash";

// 寫入新表茶没,選擇數(shù)據(jù)庫,需要校驗已有的表名
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="所屬數(shù)據(jù)庫">
  {getFieldDecorator("targetDataSource", {
    rules: [
      {
        required: true,
        message: "請選擇所屬數(shù)據(jù)庫",
      },
      {
        validator: this.validateToTableName, // Wグ搿!格嘁!
      },
    ],
    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: "只支持英文字母笛求、數(shù)字、英文格式糕簿、下劃線",
      },
      {
        validator: this.validateTableExist, // L饺搿!懂诗!
      },
    ],
    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方法, 子組件調(diào)用此方法殃恒,可將值傳給父組件植旧,從而Form可拿到自定義組件的值 value屬性,獲得初始值

<Form.Item label="發(fā)送時間">
  {getFieldDecorator('range-time-picker', {
    rules: [{ required: false, message: '請輸入開始時間-結(jié)束時間' }],
  })(
    <RangePickerPage />
  )}
</Form.Item>

下面是對 antd RangePicker 進行封裝离唐,通過組件 RangePicker 本身的 onChange 方法病附,調(diào)用 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;  // V埂@龊浮较剃!
    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} // 9颈稹<冀 !
      />
    );
  }
}

export default RangePickerPage;


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

23 Form 表單清空和重置的區(qū)別以及方法

這里首先需要明確惰拱,清空和重置是不同的概念雌贱,清空是把內(nèi)容都清空掉,而重置是恢復(fù) form 表單初始值偿短。

例如:新增功能欣孤,清空和重置就是一樣的效果,而對于編輯昔逗,清空就是把初始值都清空掉降传,重置就是恢復(fù)剛開始的初始值。

  • 清空
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 當(dāng)前時間之后的時間點笔链,精確到小時

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
  // 當(dāng)日只能選擇當(dāng)前時間之后的時間點
  if (upgradeTime.date() === moment().date()) {
    return {
      disabledHours: () => range(0, hours + 1),
    };
  }
}


<Form.Item label="發(fā)送時間">
  {getFieldDecorator('pushTime', {
    rules: [{ required: false, message: '請輸入發(fā)送時間' }],
    initialValue: record.pushType === 0 ? null :
      (record.pushTime ? moment(record.pushTime, 'YYYY-MM-DD HH:mm:ss') : null), // 定時發(fā)送才顯示時間
  })(
    <DatePicker
      format="YYYY-MM-DD HH:mm:ss"
      disabledDate={disabledDate}
      disabledTime={disabledDateTime}
      style={{ width: "100%" }}
      
      onChange={(timer) => setUpgradeTime(timer)} // 6沃弧!鉴扫!
      showTime={{ defaultValue: moment(upgradeTime) }} // T拚怼!坪创!
    />,
  )}
</Form.Item>

24.4 當(dāng)前時間之后的時間點炕婶,精確到分

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
  // 當(dāng)日只能選擇當(dāng)前時間之后的時間點
  if (upgradeTime.date() === moment().date()) {
    return {
      disabledHours: () => range(0, hours), 
      disabledMinutes: () => range(0, minutes), // 精確到分
    };
  }
}


<Form.Item label="發(fā)送時間">
  {getFieldDecorator('pushTime', {
    rules: [{ required: false, message: '請輸入發(fā)送時間' }],
    initialValue: record.pushType === 0 ? null :
      (record.pushTime ? moment(record.pushTime, 'YYYY-MM-DD HH:mm:ss') : null), // 定時發(fā)送才顯示時間
  })(
    <DatePicker
      format="YYYY-MM-DD HH:mm:ss"
      disabledDate={disabledDate}
      disabledTime={disabledDateTime}
      style={{ width: "100%" }}
      
      onChange={(timer) => setUpgradeTime(timer)} // !@吃ぁ古话!
      showTime={{ defaultValue: moment(upgradeTime) }} // !K陪踩!
    />,
  )}
</Form.Item>


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市悉抵,隨后出現(xiàn)的幾起案子肩狂,更是在濱河造成了極大的恐慌,老刑警劉巖姥饰,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件傻谁,死亡現(xiàn)場離奇詭異,居然都是意外死亡列粪,警方通過查閱死者的電腦和手機审磁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門谈飒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人态蒂,你說我怎么就攤上這事杭措。” “怎么了钾恢?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵手素,是天一觀的道長。 經(jīng)常有香客問我瘩蚪,道長泉懦,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任疹瘦,我火速辦了婚禮崩哩,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘言沐。我一直安慰自己邓嘹,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布呢灶。 她就那樣靜靜地躺著吴超,像睡著了一般。 火紅的嫁衣襯著肌膚如雪鸯乃。 梳的紋絲不亂的頭發(fā)上鲸阻,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天,我揣著相機與錄音缨睡,去河邊找鬼鸟悴。 笑死,一個胖子當(dāng)著我的面吹牛奖年,可吹牛的內(nèi)容都是我干的细诸。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼陋守,長吁一口氣:“原來是場噩夢啊……” “哼震贵!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起水评,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤猩系,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后中燥,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體寇甸,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了拿霉。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片吟秩。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖绽淘,靈堂內(nèi)的尸體忽然破棺而出涵防,到底是詐尸還是另有隱情,我是刑警寧澤收恢,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布武学,位于F島的核電站祭往,受9級特大地震影響伦意,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜硼补,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一驮肉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧已骇,春花似錦离钝、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至鲤竹,卻和暖如春浪读,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背辛藻。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工碘橘, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人吱肌。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓痘拆,卻偏偏與公主長得像,于是被迫代替她去往敵國和親氮墨。 傳聞我的和親對象是個殘疾皇子纺蛆,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容