使用 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><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(); }); } }
this.props.form.validateFields((err, fieldsValue) => { if (err) return; this.handleSubmit(fieldsValue); });
说明:因为之前遇到过使用 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>
<Input placeholder="请输入账号" disabled={account} maxLength="30" autoComplete="off" />
<InputNumber formatter={value => value} parser={value => parseInt(value) || ''} style={{ width: '100%' }} step={1} onChange={(val) => this.onChangeIpt(1, val)} />
说明: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>
说明:通过在 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>
说明:一般而言,表格 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> ); } },
查看元素可知,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; }
通过给 Select 组件新增 allowClear 属性。注意:allowClear 也会触发 onChange 方法,所以,也要单独处理一下,因为 value 和 element 为undefined。
<Select {...this.props} placeholder="请选择" allowClear={true} > ... </Select>
顺序
一般都是使用 rowkey 方法一解决(后台数据要保证没有重复);
使用irowKey
dataSource数据新增key
@Form.create({})
export default (Form.create({})(APP));
其实,只要编辑成功后,回调调用 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>
如果是 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));
一般应用场景,详情不需要底部按钮,新增和修改需要。
api
解决: 通过父组件传递一个空的字符串或者 {footer: null} 给 Modal 组件进行属性解构。
父组件需要传入的值
子组件 Modal
function mapStateToProps({ onlineCamera }) { return { favorites: onlineCamera.favorites, }; } export default connect(mapStateToProps)(Form.create()(TreeModal));
参考: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);
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 库 createFormimport 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) // !!!
结果
例如:表单字段,密码和确认密码,改变 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>
项目实例:对 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
这里首先需要明确,清空和重置是不同的概念,清空是把内容都清空掉,而重置是恢复 form 表单初始值。
例如:新增功能,清空和重置就是一样的效果,而对于编辑,清空就是把初始值都清空掉,重置就是恢复刚开始的初始值。
清空form.setFieldsValue({"fieldName": ""}); 重置
form.resetFields();
const disabledDate = (current) => { return current && current < moment().endOf('day'); }
const disabledDate = (current) => { return current && current < moment().subtract(1, 'day'); }
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>
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>