feat: add form
parent
ea7af037fc
commit
259fc4ee75
|
@ -40,9 +40,9 @@ const filterProps = (props, propsData = {}) => {
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
const getSlots = (ele) => {
|
const getSlots = (ele) => {
|
||||||
let componentOptions = ele.componentOptions
|
let componentOptions = ele.componentOptions || {}
|
||||||
if (ele.$vnode) {
|
if (ele.$vnode) {
|
||||||
componentOptions = ele.$vnode.componentOptions
|
componentOptions = ele.$vnode.componentOptions || {}
|
||||||
}
|
}
|
||||||
const children = componentOptions.children || []
|
const children = componentOptions.children || []
|
||||||
const slots = {}
|
const slots = {}
|
||||||
|
|
|
@ -0,0 +1,193 @@
|
||||||
|
import PropTypes from '../_util/vue-types'
|
||||||
|
import classNames from 'classnames'
|
||||||
|
import isRegExp from 'lodash/isRegExp'
|
||||||
|
import createDOMForm from '../vc-form/src/createDOMForm'
|
||||||
|
import createFormField from '../vc-form/src/createFormField'
|
||||||
|
import FormItem from './FormItem'
|
||||||
|
import { FIELD_META_PROP, FIELD_DATA_PROP } from './constants'
|
||||||
|
import { initDefaultProps } from '../_util/props-util'
|
||||||
|
|
||||||
|
export const FormCreateOption = {
|
||||||
|
onFieldsChange: PropTypes.func,
|
||||||
|
onValuesChange: PropTypes.func,
|
||||||
|
mapPropsToFields: PropTypes.func,
|
||||||
|
withRef: PropTypes.bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
// function create
|
||||||
|
export const WrappedFormUtils = {
|
||||||
|
/** 获取一组输入控件的值,如不传入参数,则获取全部组件的值 */
|
||||||
|
getFieldsValue: PropTypes.func,
|
||||||
|
/** 获取一个输入控件的值*/
|
||||||
|
getFieldValue: PropTypes.func,
|
||||||
|
/** 设置一组输入控件的值*/
|
||||||
|
setFieldsValue: PropTypes.func,
|
||||||
|
/** 设置一组输入控件的值*/
|
||||||
|
setFields: PropTypes.func,
|
||||||
|
/** 校验并获取一组输入域的值与 Error */
|
||||||
|
validateFields: PropTypes.func,
|
||||||
|
// validateFields(fieldNames: Array<string>, options: Object, callback: ValidateCallback): void;
|
||||||
|
// validateFields(fieldNames: Array<string>, callback: ValidateCallback): void;
|
||||||
|
// validateFields(options: Object, callback: ValidateCallback): void;
|
||||||
|
// validateFields(callback: ValidateCallback): void;
|
||||||
|
// validateFields(): void;
|
||||||
|
/** 与 `validateFields` 相似,但校验完后,如果校验不通过的菜单域不在可见范围内,则自动滚动进可见范围 */
|
||||||
|
validateFieldsAndScroll: PropTypes.func,
|
||||||
|
// validateFieldsAndScroll(fieldNames?: Array<string>, options?: Object, callback?: ValidateCallback): void;
|
||||||
|
// validateFieldsAndScroll(fieldNames?: Array<string>, callback?: ValidateCallback): void;
|
||||||
|
// validateFieldsAndScroll(options?: Object, callback?: ValidateCallback): void;
|
||||||
|
// validateFieldsAndScroll(callback?: ValidateCallback): void;
|
||||||
|
// validateFieldsAndScroll(): void;
|
||||||
|
/** 获取某个输入控件的 Error */
|
||||||
|
getFieldError: PropTypes.func,
|
||||||
|
getFieldsError: PropTypes.func,
|
||||||
|
/** 判断一个输入控件是否在校验状态*/
|
||||||
|
isFieldValidating: PropTypes.func,
|
||||||
|
isFieldTouched: PropTypes.func,
|
||||||
|
isFieldsTouched: PropTypes.func,
|
||||||
|
/** 重置一组输入控件的值与状态,如不传入参数,则重置所有组件 */
|
||||||
|
resetFields: PropTypes.func,
|
||||||
|
|
||||||
|
getFieldDecorator: PropTypes.func,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FormProps = {
|
||||||
|
layout: PropTypes.oneOf(['horizontal', 'inline', 'vertical']),
|
||||||
|
form: PropTypes.shape(WrappedFormUtils).loose,
|
||||||
|
// onSubmit: React.FormEventHandler<any>;
|
||||||
|
prefixCls: PropTypes.string,
|
||||||
|
hideRequiredMark: PropTypes.bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ValidationRule = {
|
||||||
|
/** validation error message */
|
||||||
|
message: PropTypes.string,
|
||||||
|
/** built-in validation type, available options: https://github.com/yiminghe/async-validator#type */
|
||||||
|
type: PropTypes.string,
|
||||||
|
/** indicates whether field is required */
|
||||||
|
required: PropTypes.boolean,
|
||||||
|
/** treat required fields that only contain whitespace as errors */
|
||||||
|
whitespace: PropTypes.boolean,
|
||||||
|
/** validate the exact length of a field */
|
||||||
|
len: PropTypes.number,
|
||||||
|
/** validate the min length of a field */
|
||||||
|
min: PropTypes.number,
|
||||||
|
/** validate the max length of a field */
|
||||||
|
max: PropTypes.number,
|
||||||
|
/** validate the value from a list of possible values */
|
||||||
|
enum: PropTypes.oneOfType([String, PropTypes.arrayOf(String)]),
|
||||||
|
/** validate from a regular expression */
|
||||||
|
pattern: PropTypes.custom(isRegExp),
|
||||||
|
/** transform a value before validation */
|
||||||
|
transform: PropTypes.func,
|
||||||
|
/** custom validate function (Note: callback must be called) */
|
||||||
|
validator: PropTypes.func,
|
||||||
|
}
|
||||||
|
|
||||||
|
// export type ValidateCallback = (errors: any, values: any) => void;
|
||||||
|
|
||||||
|
// export type GetFieldDecoratorOptions = {
|
||||||
|
// /** 子节点的值的属性,如 Checkbox 的是 'checked' */
|
||||||
|
// valuePropName?: string;
|
||||||
|
// /** 子节点的初始值,类型、可选值均由子节点决定 */
|
||||||
|
// initialValue?: any;
|
||||||
|
// /** 收集子节点的值的时机 */
|
||||||
|
// trigger?: string;
|
||||||
|
// /** 可以把 onChange 的参数转化为控件的值,例如 DatePicker 可设为:(date, dateString) => dateString */
|
||||||
|
// getValueFromEvent?: (...args: any[]) => any;
|
||||||
|
// /** 校验子节点值的时机 */
|
||||||
|
// validateTrigger?: string | string[];
|
||||||
|
// /** 校验规则,参见 [async-validator](https://github.com/yiminghe/async-validator) */
|
||||||
|
// rules?: ValidationRule[];
|
||||||
|
// /** 是否和其他控件互斥,特别用于 Radio 单选控件 */
|
||||||
|
// exclusive?: boolean;
|
||||||
|
// /** Normalize value to form component */
|
||||||
|
// normalize?: (value: any, prevValue: any, allValues: any) => any;
|
||||||
|
// /** Whether stop validate on first rule of error for this field. */
|
||||||
|
// validateFirst?: boolean;
|
||||||
|
// };
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'AForm',
|
||||||
|
props: initDefaultProps(FormProps, {
|
||||||
|
prefixCls: 'ant-form',
|
||||||
|
layout: 'horizontal',
|
||||||
|
hideRequiredMark: false,
|
||||||
|
}),
|
||||||
|
// static defaultProps = {
|
||||||
|
// prefixCls: 'ant-form',
|
||||||
|
// layout: 'horizontal',
|
||||||
|
// hideRequiredMark: false,
|
||||||
|
// onSubmit (e) {
|
||||||
|
// e.preventDefault()
|
||||||
|
// },
|
||||||
|
// };
|
||||||
|
|
||||||
|
// static propTypes = {
|
||||||
|
// prefixCls: PropTypes.string,
|
||||||
|
// layout: PropTypes.oneOf(['horizontal', 'inline', 'vertical']),
|
||||||
|
// children: PropTypes.any,
|
||||||
|
// onSubmit: PropTypes.func,
|
||||||
|
// hideRequiredMark: PropTypes.bool,
|
||||||
|
// };
|
||||||
|
|
||||||
|
Item: FormItem,
|
||||||
|
|
||||||
|
createFormField: createFormField,
|
||||||
|
|
||||||
|
create: (options = {}) => {
|
||||||
|
return createDOMForm({
|
||||||
|
fieldNameProp: 'id',
|
||||||
|
...options,
|
||||||
|
fieldMetaProp: FIELD_META_PROP,
|
||||||
|
fieldDataProp: FIELD_DATA_PROP,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// constructor (props) {
|
||||||
|
// super(props)
|
||||||
|
|
||||||
|
// warning(!props.form, 'It is unnecessary to pass `form` to `Form` after antd@1.7.0.')
|
||||||
|
// }
|
||||||
|
|
||||||
|
// shouldComponentUpdate(...args) {
|
||||||
|
// return PureRenderMixin.shouldComponentUpdate.apply(this, args);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// getChildContext () {
|
||||||
|
// const { layout } = this.props
|
||||||
|
// return {
|
||||||
|
// vertical: layout === 'vertical',
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
provide () {
|
||||||
|
return {
|
||||||
|
FormProps: this.$props,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onSubmit (e) {
|
||||||
|
const { $listeners } = this
|
||||||
|
if (!$listeners.submit) {
|
||||||
|
e.preventDefault()
|
||||||
|
} else {
|
||||||
|
this.$emit('submit', e)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const {
|
||||||
|
prefixCls, hideRequiredMark, layout, onSubmit, $slots,
|
||||||
|
} = this
|
||||||
|
|
||||||
|
const formClassName = classNames(prefixCls, {
|
||||||
|
[`${prefixCls}-horizontal`]: layout === 'horizontal',
|
||||||
|
[`${prefixCls}-vertical`]: layout === 'vertical',
|
||||||
|
[`${prefixCls}-inline`]: layout === 'inline',
|
||||||
|
[`${prefixCls}-hide-required-mark`]: hideRequiredMark,
|
||||||
|
})
|
||||||
|
|
||||||
|
return <form onSubmit={onSubmit} class={formClassName}>{$slots.default}</form>
|
||||||
|
},
|
||||||
|
}
|
|
@ -0,0 +1,333 @@
|
||||||
|
|
||||||
|
import PropTypes from '../_util/vue-types'
|
||||||
|
import classNames from 'classnames'
|
||||||
|
import Row from '../grid/Row'
|
||||||
|
import Col, { ColProps } from '../grid/Col'
|
||||||
|
import warning from '../_util/warning'
|
||||||
|
import { FIELD_META_PROP, FIELD_DATA_PROP } from './constants'
|
||||||
|
import { initDefaultProps, getComponentFromProp, filterEmpty, getSlotOptions, getSlots } from '../_util/props-util'
|
||||||
|
import getTransitionProps from '../_util/getTransitionProps'
|
||||||
|
import BaseMixin from '../_util/BaseMixin'
|
||||||
|
export const FormItemProps = {
|
||||||
|
id: PropTypes.string,
|
||||||
|
prefixCls: PropTypes.string,
|
||||||
|
label: PropTypes.any,
|
||||||
|
labelCol: PropTypes.shape(ColProps).loose,
|
||||||
|
wrapperCol: PropTypes.shape(ColProps).loose,
|
||||||
|
help: PropTypes.any,
|
||||||
|
extra: PropTypes.any,
|
||||||
|
validateStatus: PropTypes.oneOf(['', 'success', 'warning', 'error', 'validating']),
|
||||||
|
hasFeedback: PropTypes.bool,
|
||||||
|
required: PropTypes.bool,
|
||||||
|
colon: PropTypes.bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'AFormItem',
|
||||||
|
__ANT_FORM_ITEM: true,
|
||||||
|
mixins: [BaseMixin],
|
||||||
|
props: initDefaultProps(FormItemProps, {
|
||||||
|
hasFeedback: false,
|
||||||
|
prefixCls: 'ant-form',
|
||||||
|
colon: true,
|
||||||
|
}),
|
||||||
|
inject: {
|
||||||
|
FormProps: { default: {}},
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return { helpShow: false }
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
warning(
|
||||||
|
this.getControls(this.$slots.default, true).length <= 1,
|
||||||
|
'`Form.Item` cannot generate `validateStatus` and `help` automatically, ' +
|
||||||
|
'while there are more than one `getFieldDecorator` in it.',
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
// shouldComponentUpdate(...args: any[]) {
|
||||||
|
// return PureRenderMixin.shouldComponentUpdate.apply(this, args);
|
||||||
|
// }
|
||||||
|
methods: {
|
||||||
|
getHelpMsg () {
|
||||||
|
const help = getComponentFromProp(this, 'help')
|
||||||
|
const onlyControl = this.getOnlyControl()
|
||||||
|
if (help === undefined && onlyControl) {
|
||||||
|
const errors = this.getField().errors
|
||||||
|
return errors ? errors.map((e) => e.message).join(', ') : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
return help
|
||||||
|
},
|
||||||
|
|
||||||
|
getControls (childrenArray = [], recursively) {
|
||||||
|
let controls = []
|
||||||
|
for (let i = 0; i < childrenArray.length; i++) {
|
||||||
|
if (!recursively && controls.length > 0) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
const child = childrenArray[i]
|
||||||
|
if (!child.tag && child.text.trim() === '') {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getSlotOptions(child).__ANT_FORM_ITEM) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const attrs = child.data && child.data.attrs
|
||||||
|
if (!attrs) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const slots = getSlots(child)
|
||||||
|
if (FIELD_META_PROP in attrs) { // And means FIELD_DATA_PROP in chidl.props, too.
|
||||||
|
controls.push(child)
|
||||||
|
} else if (slots.default) {
|
||||||
|
controls = controls.concat(this.getControls(slots.default, recursively))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return controls
|
||||||
|
},
|
||||||
|
|
||||||
|
getOnlyControl () {
|
||||||
|
const child = this.getControls(this.$slots.default, false)[0]
|
||||||
|
return child !== undefined ? child : null
|
||||||
|
},
|
||||||
|
|
||||||
|
getChildAttr (prop) {
|
||||||
|
const child = this.getOnlyControl()
|
||||||
|
let data = {}
|
||||||
|
if (child.data) {
|
||||||
|
data = child.data
|
||||||
|
} else if (child.$vnode && child.$vnode.data) {
|
||||||
|
data = child.$vnode.data
|
||||||
|
}
|
||||||
|
return data[prop] || data.attrs[prop]
|
||||||
|
},
|
||||||
|
|
||||||
|
getId () {
|
||||||
|
return this.getChildAttr('id')
|
||||||
|
},
|
||||||
|
|
||||||
|
getMeta () {
|
||||||
|
return this.getChildAttr(FIELD_META_PROP)
|
||||||
|
},
|
||||||
|
|
||||||
|
getField () {
|
||||||
|
return this.getChildAttr(FIELD_DATA_PROP)
|
||||||
|
},
|
||||||
|
|
||||||
|
onHelpAnimEnd (_key, helpShow) {
|
||||||
|
this.setState({ helpShow })
|
||||||
|
},
|
||||||
|
|
||||||
|
renderHelp () {
|
||||||
|
const prefixCls = this.prefixCls
|
||||||
|
const help = this.getHelpMsg()
|
||||||
|
const children = help ? (
|
||||||
|
<div class={`${prefixCls}-explain`} key='help'>
|
||||||
|
{help}
|
||||||
|
</div>
|
||||||
|
) : null
|
||||||
|
const transitionProps = getTransitionProps('show-help', {
|
||||||
|
afterLeave: this.onHelpAnimEnd,
|
||||||
|
})
|
||||||
|
return (
|
||||||
|
<transition
|
||||||
|
{...transitionProps}
|
||||||
|
key='help'
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</transition>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
renderExtra () {
|
||||||
|
const { prefixCls } = this
|
||||||
|
const extra = getComponentFromProp(this, 'extra')
|
||||||
|
return extra ? (
|
||||||
|
<div class={`${prefixCls}-extra`}>{extra}</div>
|
||||||
|
) : null
|
||||||
|
},
|
||||||
|
|
||||||
|
getValidateStatus () {
|
||||||
|
const onlyControl = this.getOnlyControl()
|
||||||
|
if (!onlyControl) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
const field = this.getField()
|
||||||
|
if (field.validating) {
|
||||||
|
return 'validating'
|
||||||
|
}
|
||||||
|
if (field.errors) {
|
||||||
|
return 'error'
|
||||||
|
}
|
||||||
|
const fieldValue = 'value' in field ? field.value : this.getMeta().initialValue
|
||||||
|
if (fieldValue !== undefined && fieldValue !== null && fieldValue !== '') {
|
||||||
|
return 'success'
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
},
|
||||||
|
|
||||||
|
renderValidateWrapper (c1, c2, c3) {
|
||||||
|
const props = this.$props
|
||||||
|
const onlyControl = this.getOnlyControl
|
||||||
|
const validateStatus = (props.validateStatus === undefined && onlyControl)
|
||||||
|
? this.getValidateStatus()
|
||||||
|
: props.validateStatus
|
||||||
|
|
||||||
|
let classes = `${props.prefixCls}-item-control`
|
||||||
|
if (validateStatus) {
|
||||||
|
classes = classNames(`${props.prefixCls}-item-control`, {
|
||||||
|
'has-feedback': props.hasFeedback || validateStatus === 'validating',
|
||||||
|
'has-success': validateStatus === 'success',
|
||||||
|
'has-warning': validateStatus === 'warning',
|
||||||
|
'has-error': validateStatus === 'error',
|
||||||
|
'is-validating': validateStatus === 'validating',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div class={classes}>
|
||||||
|
<span class={`${props.prefixCls}-item-children`}>{c1}</span>
|
||||||
|
{c2}{c3}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
renderWrapper (children) {
|
||||||
|
const { prefixCls, wrapperCol = {}} = this
|
||||||
|
const { class: cls, style, id, on, ...restProps } = wrapperCol
|
||||||
|
const className = classNames(
|
||||||
|
`${prefixCls}-item-control-wrapper`,
|
||||||
|
cls,
|
||||||
|
)
|
||||||
|
const colProps = {
|
||||||
|
props: restProps,
|
||||||
|
class: className,
|
||||||
|
key: 'wrapper',
|
||||||
|
style,
|
||||||
|
id,
|
||||||
|
on,
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Col {...colProps}>
|
||||||
|
{children}
|
||||||
|
</Col>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
isRequired () {
|
||||||
|
const { required } = this
|
||||||
|
if (required !== undefined) {
|
||||||
|
return required
|
||||||
|
}
|
||||||
|
if (this.getOnlyControl()) {
|
||||||
|
const meta = this.getMeta() || {}
|
||||||
|
const validate = meta.validate || []
|
||||||
|
|
||||||
|
return validate.filter((item) => !!item.rules).some((item) => {
|
||||||
|
return item.rules.some((rule) => rule.required)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
|
||||||
|
// Resolve duplicated ids bug between different forms
|
||||||
|
// https://github.com/ant-design/ant-design/issues/7351
|
||||||
|
onLabelClick (e) {
|
||||||
|
const label = getComponentFromProp(this, 'label')
|
||||||
|
const id = this.id || this.getId()
|
||||||
|
if (!id) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const controls = document.querySelectorAll(`[id="${id}"]`)
|
||||||
|
if (controls.length !== 1) {
|
||||||
|
// Only prevent in default situation
|
||||||
|
// Avoid preventing event in `label={<a href="xx">link</a>}``
|
||||||
|
if (typeof label === 'string') {
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
const control = this.$el.querySelector(`[id="${id}"]`)
|
||||||
|
if (control && control.focus) {
|
||||||
|
control.focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
renderLabel () {
|
||||||
|
const { prefixCls, labelCol = {}, colon, id } = this
|
||||||
|
const label = getComponentFromProp(this, 'label')
|
||||||
|
const required = this.isRequired()
|
||||||
|
const { class: labelColClass, style: labelColStyle, id: labelColId, on, ...restProps } = labelCol
|
||||||
|
const labelColClassName = classNames(
|
||||||
|
`${prefixCls}-item-label`,
|
||||||
|
labelColClass,
|
||||||
|
)
|
||||||
|
const labelClassName = classNames({
|
||||||
|
[`${prefixCls}-item-required`]: required,
|
||||||
|
})
|
||||||
|
|
||||||
|
let labelChildren = label
|
||||||
|
// Keep label is original where there should have no colon
|
||||||
|
const haveColon = colon && this.FormProps.layout !== 'vertical'
|
||||||
|
// Remove duplicated user input colon
|
||||||
|
if (haveColon && typeof label === 'string' && label.trim() !== '') {
|
||||||
|
labelChildren = label.replace(/[:|:]\s*$/, '')
|
||||||
|
}
|
||||||
|
const colProps = {
|
||||||
|
props: restProps,
|
||||||
|
class: labelColClassName,
|
||||||
|
key: 'label',
|
||||||
|
style: labelColStyle,
|
||||||
|
id: labelColId,
|
||||||
|
on,
|
||||||
|
}
|
||||||
|
|
||||||
|
return label ? (
|
||||||
|
<Col {...colProps} >
|
||||||
|
<label
|
||||||
|
htmlFor={id || this.getId()}
|
||||||
|
class={labelClassName}
|
||||||
|
title={typeof label === 'string' ? label : ''}
|
||||||
|
onClick={this.onLabelClick}
|
||||||
|
>
|
||||||
|
{labelChildren}
|
||||||
|
</label>
|
||||||
|
</Col>
|
||||||
|
) : null
|
||||||
|
},
|
||||||
|
renderChildren () {
|
||||||
|
const { $slots } = this
|
||||||
|
return [
|
||||||
|
this.renderLabel(),
|
||||||
|
this.renderWrapper(
|
||||||
|
this.renderValidateWrapper(
|
||||||
|
filterEmpty($slots.default || []),
|
||||||
|
this.renderHelp(),
|
||||||
|
this.renderExtra(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
},
|
||||||
|
renderFormItem (children) {
|
||||||
|
const props = this.$props
|
||||||
|
const prefixCls = props.prefixCls
|
||||||
|
const itemClassName = {
|
||||||
|
[`${prefixCls}-item`]: true,
|
||||||
|
[`${prefixCls}-item-with-help`]: !!this.getHelpMsg() || this.helpShow,
|
||||||
|
[`${prefixCls}-item-no-colon`]: !props.colon,
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Row class={classNames(itemClassName)}>
|
||||||
|
{children}
|
||||||
|
</Row>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const children = this.renderChildren()
|
||||||
|
return this.renderFormItem(children)
|
||||||
|
},
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
export const FIELD_META_PROP = 'data-__meta'
|
||||||
|
export const FIELD_DATA_PROP = 'data-__field'
|
|
@ -0,0 +1,65 @@
|
||||||
|
<script>
|
||||||
|
import { Form, Icon, Input, Button, Checkbox } from 'antd'
|
||||||
|
const FormItem = Form.Item
|
||||||
|
|
||||||
|
const NormalLoginForm = {
|
||||||
|
methods: {
|
||||||
|
handleSubmit (e) {
|
||||||
|
e.preventDefault()
|
||||||
|
this.form.validateFields((err, values) => {
|
||||||
|
if (!err) {
|
||||||
|
console.log('Received values of form: ', values)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { getFieldDecorator } = this.form
|
||||||
|
return (
|
||||||
|
<Form id='components-form-demo-normal-login' onSubmit={this.handleSubmit} class='login-form'>
|
||||||
|
<FormItem>
|
||||||
|
{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>
|
||||||
|
{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>
|
||||||
|
{getFieldDecorator('remember', {
|
||||||
|
valuePropName: 'checked',
|
||||||
|
initialValue: true,
|
||||||
|
})(
|
||||||
|
<Checkbox>Remember me</Checkbox>
|
||||||
|
)}
|
||||||
|
<a class='login-form-forgot' href=''>Forgot password</a>
|
||||||
|
<Button type='primary' htmlType='submit' class='login-form-button'>
|
||||||
|
Log in
|
||||||
|
</Button>
|
||||||
|
Or <a href=''>register now!</a>
|
||||||
|
</FormItem>
|
||||||
|
</Form>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Form.create()(NormalLoginForm)
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
#components-form-demo-normal-login .login-form {
|
||||||
|
max-width: 300px;
|
||||||
|
}
|
||||||
|
#components-form-demo-normal-login .login-form-forgot {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
#components-form-demo-normal-login .login-form-button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,181 @@
|
||||||
|
---
|
||||||
|
category: Components
|
||||||
|
type: Data Entry
|
||||||
|
cols: 1
|
||||||
|
title: Form
|
||||||
|
---
|
||||||
|
|
||||||
|
Form is used to collect, validate, and submit the user input, usually contains various form items including checkbox, radio, input, select, and etc.
|
||||||
|
|
||||||
|
## Form
|
||||||
|
|
||||||
|
You can align the controls of a `form` using the `layout` prop:
|
||||||
|
|
||||||
|
- `horizontal`:to horizontally align the `label`s and controls of the fields. (Default)
|
||||||
|
- `vertical`:to vertically align the `label`s and controls of the fields.
|
||||||
|
- `inline`:to render form fields in one line.
|
||||||
|
|
||||||
|
## Form fields
|
||||||
|
|
||||||
|
A form consists of one or more form fields whose type includes input, textarea, checkbox, radio, select, tag, and more.
|
||||||
|
A form field is defined using `<Form.Item />`.
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
<Form.Item {...props}>
|
||||||
|
{children}
|
||||||
|
</Form.Item>
|
||||||
|
```
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
### Form
|
||||||
|
|
||||||
|
**more example [rc-form](http://react-component.github.io/form/)**。
|
||||||
|
|
||||||
|
| Property | Description | Type | Default Value |
|
||||||
|
| -------- | ----------- | ---- | ------------- |
|
||||||
|
| form | Decorated by `Form.create()` will be automatically set `this.props.form` property, so just pass to form, you don't need to set it by yourself after 1.7.0. | object | n/a |
|
||||||
|
| hideRequiredMark | Hide required mark of all form items | Boolean | false |
|
||||||
|
| layout | Define form layout(Support after 2.8) | 'horizontal'\|'vertical'\|'inline' | 'horizontal' |
|
||||||
|
| onSubmit | Defines a function will be called if form data validation is successful. | Function(e:Event) | |
|
||||||
|
|
||||||
|
### Form.create(options)
|
||||||
|
|
||||||
|
How to use:
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
class CustomizedForm extends React.Component {}
|
||||||
|
|
||||||
|
CustomizedForm = Form.create({})(CustomizedForm);
|
||||||
|
```
|
||||||
|
|
||||||
|
The following `options` are available:
|
||||||
|
|
||||||
|
| Property | Description | Type |
|
||||||
|
| -------- | ----------- | ---- |
|
||||||
|
| mapPropsToFields | Convert props to field value(e.g. reading the values from Redux store). And you must mark returned fields with [`Form.createFormField`](#Form.createFormField) | (props) => Object{ fieldName: FormField { value } } |
|
||||||
|
| validateMessages | Default validate message. And its format is similar with [newMessages](https://github.com/yiminghe/async-validator/blob/master/src/messages.js)'s returned value | Object { [nested.path]: String } |
|
||||||
|
| onFieldsChange | Specify a function that will be called when the value a `Form.Item` gets changed. Usage example: saving the field's value to Redux store. | Function(props, fields) |
|
||||||
|
| onValuesChange | A handler while value of any field is changed | (props, values) => void |
|
||||||
|
|
||||||
|
If the form has been decorated by `Form.create` then it has `this.props.form` property. `this.props.form` provides some APIs as follows:
|
||||||
|
|
||||||
|
> Note: Before using `getFieldsValue` `getFieldValue` `setFieldsValue` and so on, please make sure that corresponding field had been registered with `getFieldDecorator`.
|
||||||
|
|
||||||
|
| Method | Description | Type |
|
||||||
|
| ------ | ----------- | ---- |
|
||||||
|
| getFieldDecorator | Two-way binding for form, please read below for details. | |
|
||||||
|
| getFieldError | Get the error of a field. | Function(name) |
|
||||||
|
| getFieldsError | Get the specified fields' error. If you don't specify a parameter, you will get all fields' error. | Function(\[names: string\[]]) |
|
||||||
|
| getFieldsValue | Get the specified fields' values. If you don't specify a parameter, you will get all fields' values. | Function(\[fieldNames: string\[]]) |
|
||||||
|
| getFieldValue | Get the value of a field. | Function(fieldName: string) |
|
||||||
|
| isFieldsTouched | Check whether any of fields is touched by `getFieldDecorator`'s `options.trigger` event | (names?: string\[]) => boolean |
|
||||||
|
| isFieldTouched | Check whether a field is touched by `getFieldDecorator`'s `options.trigger` event | (name: string) => boolean |
|
||||||
|
| isFieldValidating | Check if the specified field is being validated. | Function(name) |
|
||||||
|
| resetFields | Reset the specified fields' value(to `initialValue`) and status. If you don't specify a parameter, all the fields will be reset. | Function(\[names: string\[]]) |
|
||||||
|
| setFields | Set the value and error of a field. [Code Sample](https://github.com/react-component/form/blob/3b9959b57ab30b41d8890ff30c79a7e7c383cad3/examples/server-validate.js#L74-L79) | Function({ [fieldName]: { value: any, errors: [Error] } }) |
|
||||||
|
| setFields | | Function(obj: object) |
|
||||||
|
| setFieldsValue | Set the value of a field.(Note: please don't use it in `componentWillReceiveProps`, otherwise, it will cause an endless loop, [more](https://github.com/ant-design/ant-design/issues/2985)) | Function({ [fieldName]: value } |
|
||||||
|
| validateFields | Validate the specified fields and get theirs values and errors. If you don't specify the parameter of fieldNames, you will vaildate all fields. | Function(\[fieldNames: string\[]], [options: object], callback: Function(errors, values)) |
|
||||||
|
| validateFieldsAndScroll | This function is similar to `validateFields`, but after validation, if the target field is not in visible area of form, form will be automatically scrolled to the target field area. | same as `validateFields` |
|
||||||
|
|
||||||
|
### this.props.form.validateFields/validateFieldsAndScroll(\[fieldNames: string\[]], [options: object], callback: Function(errors, values))
|
||||||
|
|
||||||
|
| Method | Description | Type | Default |
|
||||||
|
| ------ | ----------- | ---- | ------- |
|
||||||
|
| options.first | If `true`, every field will stop validation at first failed rule | boolean | false |
|
||||||
|
| options.firstFields | Those fields will stop validation at first failed rule | String\[] | \[] |
|
||||||
|
| options.force | Should validate validated field again when `validateTrigger` is been triggered again | boolean | false |
|
||||||
|
| options.scroll | Config scroll behavior of `validateFieldsAndScroll`, more: [dom-scroll-into-view's config](https://github.com/yiminghe/dom-scroll-into-view#function-parameter) | Object | {} |
|
||||||
|
|
||||||
|
### Form.createFormField
|
||||||
|
|
||||||
|
To mark the returned fields data in `mapPropsToFields`, [demo](#components-form-demo-global-state).
|
||||||
|
|
||||||
|
### this.props.form.getFieldDecorator(id, options)
|
||||||
|
|
||||||
|
After wrapped by `getFieldDecorator`, `value`(or other property defined by `valuePropName`) `onChange`(or other property defined by `trigger`) props will be added to form controls,the flow of form data will be handled by Form which will cause:
|
||||||
|
|
||||||
|
1. You shouldn't use `onChange` to collect data, but you still can listen to `onChange`(and so on) events.
|
||||||
|
2. You can not set value of form control via `value` `defaultValue` prop, and you should set default value with `initialValue` in `getFieldDecorator` instead.
|
||||||
|
3. You shouldn't call `setState` manually, please use `this.props.form.setFieldsValue` to change value programmatically.
|
||||||
|
|
||||||
|
#### Special attention
|
||||||
|
|
||||||
|
1. `getFieldDecorator` can not be used to decorate stateless component.
|
||||||
|
2. If you use `react@<15.3.0`, then, you can't use `getFieldDecorator` in stateless component: <https://github.com/facebook/react/pull/6534>
|
||||||
|
|
||||||
|
#### getFieldDecorator(id, options) parameters
|
||||||
|
|
||||||
|
| Property | Description | Type | Default Value |
|
||||||
|
| -------- | ----------- | ---- | ------------- |
|
||||||
|
| id | The unique identifier is required. support [nested fields format](https://github.com/react-component/form/pull/48). | string | |
|
||||||
|
| options.getValueFromEvent | Specify how to get value from event or other onChange arguments | function(..args) | [reference](https://github.com/react-component/form#option-object) |
|
||||||
|
| options.initialValue | You can specify initial value, type, optional value of children node. (Note: Because `Form` will test equality with `===` internaly, we recommend to use vairable as `initialValue`, instead of literal) | | n/a |
|
||||||
|
| options.normalize | Normalize value to form component, [a select-all example](https://codepen.io/afc163/pen/JJVXzG?editors=001) | function(value, prevValue, allValues): any | - |
|
||||||
|
| options.rules | Includes validation rules. Please refer to "Validation Rules" part for details. | object\[] | n/a |
|
||||||
|
| options.trigger | When to collect the value of children node | string | 'onChange' |
|
||||||
|
| options.validateFirst | Whether stop validate on first rule of error for this field. | boolean | false |
|
||||||
|
| options.validateTrigger | When to validate the value of children node. | string\|string\[] | 'onChange' |
|
||||||
|
| options.valuePropName | Props of children node, for example, the prop of Switch is 'checked'. | string | 'value' |
|
||||||
|
|
||||||
|
More option at [rc-form option](https://github.com/react-component/form#option-object)。
|
||||||
|
|
||||||
|
### Form.Item
|
||||||
|
|
||||||
|
Note:
|
||||||
|
|
||||||
|
- If Form.Item has multiple children that had been decorated by `getFieldDecorator`, `help` and `required` and `validateStatus` can't be generated automatically.
|
||||||
|
- Before `2.2.0`, form controls must be child of Form.Item, otherwise, you need to set `help`, `required` and `validateStatus` by yourself.
|
||||||
|
|
||||||
|
| Property | Description | Type | Default Value |
|
||||||
|
| -------- | ----------- | ---- | ------------- |
|
||||||
|
| colon | Used with `label`, whether to display `:` after label text. | boolean | true |
|
||||||
|
| extra | The extra prompt message. It is similar to help. Usage example: to display error message and prompt message at the same time. | string\|ReactNode | |
|
||||||
|
| hasFeedback | Used with `validateStatus`, this option specifies the validation status icon. Recommended to be used only with `Input`. | boolean | false |
|
||||||
|
| help | The prompt message. If not provided, the prompt message will be generated by the validation rule. | string\|ReactNode | |
|
||||||
|
| label | Label text | string\|ReactNode | |
|
||||||
|
| labelCol | The layout of label. You can set `span` `offset` to something like `{span: 3, offset: 12}` or `sm: {span: 3, offset: 12}` same as with `<Col>` | [object](https://ant.design/components/grid/#Col) | |
|
||||||
|
| required | Whether provided or not, it will be generated by the validation rule. | boolean | false |
|
||||||
|
| validateStatus | The validation status. If not provided, it will be generated by validation rule. options: 'success' 'warning' 'error' 'validating' | string | |
|
||||||
|
| wrapperCol | The layout for input controls, same as `labelCol` | [object](https://ant.design/components/grid/#Col) | |
|
||||||
|
|
||||||
|
### Validation Rules
|
||||||
|
|
||||||
|
| Property | Description | Type | Default Value |
|
||||||
|
| -------- | ----------- | ---- | ------------- |
|
||||||
|
| enum | validate a value from a list of possible values | string | - |
|
||||||
|
| len | validate an exact length of a field | number | - |
|
||||||
|
| max | validate a max length of a field | number | - |
|
||||||
|
| message | validation error message | string | - |
|
||||||
|
| min | validate a min length of a field | number | - |
|
||||||
|
| pattern | validate from a regular expression | RegExp | - |
|
||||||
|
| required | indicates whether field is required | boolean | `false` |
|
||||||
|
| transform | transform a value before validation | function(value) => transformedValue:any | - |
|
||||||
|
| type | built-in validation type, [available options](https://github.com/yiminghe/async-validator#type) | string | 'string' |
|
||||||
|
| validator | custom validate function (Note: [callback must be called](https://github.com/ant-design/ant-design/issues/5155)) | function(rule, value, callback) | - |
|
||||||
|
| whitespace | treat required fields that only contain whitespace as errors | boolean | `false` |
|
||||||
|
|
||||||
|
See more advanced usage at [async-validator](https://github.com/yiminghe/async-validator).
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.code-box-demo .ant-form:not(.ant-form-inline):not(.ant-form-vertical) {
|
||||||
|
max-width: 600px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
## Using in TypeScript
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
import { Form } from 'antd';
|
||||||
|
import { FormComponentProps } from 'antd/lib/form';
|
||||||
|
|
||||||
|
interface UserFormProps extends FormComponentProps {
|
||||||
|
age: number;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
class UserForm extends React.Component<UserFormProps, any> {
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
|
@ -0,0 +1,6 @@
|
||||||
|
import Form from './Form'
|
||||||
|
|
||||||
|
export { FormProps, FormComponentProps, FormCreateOption, ValidateCallback, ValidationRule } from './Form'
|
||||||
|
export { FormItemProps } from './FormItem'
|
||||||
|
|
||||||
|
export default Form
|
|
@ -0,0 +1,182 @@
|
||||||
|
---
|
||||||
|
category: Components
|
||||||
|
subtitle: 表单
|
||||||
|
type: Data Entry
|
||||||
|
cols: 1
|
||||||
|
title: Form
|
||||||
|
---
|
||||||
|
|
||||||
|
具有数据收集、校验和提交功能的表单,包含复选框、单选框、输入框、下拉选择框等元素。
|
||||||
|
|
||||||
|
## 表单
|
||||||
|
|
||||||
|
我们为 `form` 提供了以下三种排列方式:
|
||||||
|
|
||||||
|
- 水平排列:标签和表单控件水平排列;(默认)
|
||||||
|
- 垂直排列:标签和表单控件上下垂直排列;
|
||||||
|
- 行内排列:表单项水平行内排列。
|
||||||
|
|
||||||
|
## 表单域
|
||||||
|
|
||||||
|
表单一定会包含表单域,表单域可以是输入控件,标准表单域,标签,下拉菜单,文本域等。
|
||||||
|
|
||||||
|
这里我们封装了表单域 `<Form.Item />` 。
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
<Form.Item {...props}>
|
||||||
|
{children}
|
||||||
|
</Form.Item>
|
||||||
|
```
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
### Form
|
||||||
|
|
||||||
|
**更多示例参考 [rc-form](http://react-component.github.io/form/)**。
|
||||||
|
|
||||||
|
| 参数 | 说明 | 类型 | 默认值 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| form | 经 `Form.create()` 包装过的组件会自带 `this.props.form` 属性,直接传给 Form 即可。1.7.0 之后无需设置 | object | 无 |
|
||||||
|
| hideRequiredMark | 隐藏所有表单项的必选标记 | Boolean | false |
|
||||||
|
| layout | 表单布局(2.8 之后支持) | 'horizontal'\|'vertical'\|'inline' | 'horizontal' |
|
||||||
|
| onSubmit | 数据验证成功后回调事件 | Function(e:Event) | |
|
||||||
|
|
||||||
|
### Form.create(options)
|
||||||
|
|
||||||
|
使用方式如下:
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
class CustomizedForm extends React.Component {}
|
||||||
|
|
||||||
|
CustomizedForm = Form.create({})(CustomizedForm);
|
||||||
|
```
|
||||||
|
|
||||||
|
`options` 的配置项如下。
|
||||||
|
|
||||||
|
| 参数 | 说明 | 类型 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| mapPropsToFields | 把父组件的属性映射到表单项上(如:把 Redux store 中的值读出),需要对返回值中的表单域数据用 [`Form.createFormField`](#Form.createFormField) 标记 | (props) => Object{ fieldName: FormField { value } } |
|
||||||
|
| validateMessages | 默认校验信息,可用于把默认错误信息改为中文等,格式与 [newMessages](https://github.com/yiminghe/async-validator/blob/master/src/messages.js) 返回值一致 | Object { [nested.path]: String } |
|
||||||
|
| onFieldsChange | 当 `Form.Item` 子节点的值发生改变时触发,可以把对应的值转存到 Redux store | Function(props, fields) |
|
||||||
|
| onValuesChange | 任一表单域的值发生改变时的回调 | (props, values) => void |
|
||||||
|
|
||||||
|
经过 `Form.create` 包装的组件将会自带 `this.props.form` 属性,`this.props.form` 提供的 API 如下:
|
||||||
|
|
||||||
|
> 注意:使用 `getFieldsValue` `getFieldValue` `setFieldsValue` 等时,应确保对应的 field 已经用 `getFieldDecorator` 注册过了。
|
||||||
|
|
||||||
|
| 方法 | 说明 | 类型 |
|
||||||
|
| ------- | -------------------------------------- | -------- |
|
||||||
|
| getFieldDecorator | 用于和表单进行双向绑定,详见下方描述 | |
|
||||||
|
| getFieldError | 获取某个输入控件的 Error | Function(name) |
|
||||||
|
| getFieldsError | 获取一组输入控件的 Error ,如不传入参数,则获取全部组件的 Error | Function(\[names: string\[]]) |
|
||||||
|
| getFieldsValue | 获取一组输入控件的值,如不传入参数,则获取全部组件的值 | Function(\[fieldNames: string\[]]) |
|
||||||
|
| getFieldValue | 获取一个输入控件的值 | Function(fieldName: string) |
|
||||||
|
| isFieldsTouched | 判断是否任一输入控件经历过 `getFieldDecorator` 的值收集时机 `options.trigger` | (names?: string\[]) => boolean |
|
||||||
|
| isFieldTouched | 判断一个输入控件是否经历过 `getFieldDecorator` 的值收集时机 `options.trigger` | (name: string) => boolean |
|
||||||
|
| isFieldValidating | 判断一个输入控件是否在校验状态 | Function(name) |
|
||||||
|
| resetFields | 重置一组输入控件的值(为 `initialValue`)与状态,如不传入参数,则重置所有组件 | Function(\[names: string\[]]) |
|
||||||
|
| setFields | 设置一组输入控件的值与 Error。 [代码](https://github.com/react-component/form/blob/3b9959b57ab30b41d8890ff30c79a7e7c383cad3/examples/server-validate.js#L74-L79) | Function({ [fieldName]: { value: any, errors: [Error] } }) |
|
||||||
|
| setFieldsValue | 设置一组输入控件的值(注意:不要在 `componentWillReceiveProps` 内使用,否则会导致死循环,[更多](https://github.com/ant-design/ant-design/issues/2985)) | Function({ [fieldName]: value } |
|
||||||
|
| validateFields | 校验并获取一组输入域的值与 Error,若 fieldNames 参数为空,则校验全部组件 | Function(\[fieldNames: string\[]], [options: object], callback: Function(errors, values)) |
|
||||||
|
| validateFieldsAndScroll | 与 `validateFields` 相似,但校验完后,如果校验不通过的菜单域不在可见范围内,则自动滚动进可见范围 | 参考 `validateFields` |
|
||||||
|
|
||||||
|
### this.props.form.validateFields/validateFieldsAndScroll(\[fieldNames: string\[]], [options: object], callback: Function(errors, values))
|
||||||
|
|
||||||
|
| 参数 | 说明 | 类型 | 默认值 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| options.first | 若为 true,则每一表单域的都会在碰到第一个失败了的校验规则后停止校验 | boolean | false |
|
||||||
|
| options.firstFields | 指定表单域会在碰到第一个失败了的校验规则后停止校验 | String\[] | \[] |
|
||||||
|
| options.force | 对已经校验过的表单域,在 validateTrigger 再次被触发时是否再次校验 | boolean | false |
|
||||||
|
| options.scroll | 定义 validateFieldsAndScroll 的滚动行为,详细配置见 [dom-scroll-into-view config](https://github.com/yiminghe/dom-scroll-into-view#function-parameter) | Object | {} |
|
||||||
|
|
||||||
|
### Form.createFormField
|
||||||
|
|
||||||
|
用于标记 `mapPropsToFields` 返回的表单域数据,[例子](#components-form-demo-global-state)。
|
||||||
|
|
||||||
|
### this.props.form.getFieldDecorator(id, options)
|
||||||
|
|
||||||
|
经过 `getFieldDecorator` 包装的控件,表单控件会自动添加 `value`(或 `valuePropName` 指定的其他属性) `onChange`(或 `trigger` 指定的其他属性),数据同步将被 Form 接管,这会导致以下结果:
|
||||||
|
|
||||||
|
1. 你**不再需要也不应该**用 `onChange` 来做同步,但还是可以继续监听 `onChange` 等事件。
|
||||||
|
2. 你不能用控件的 `value` `defaultValue` 等属性来设置表单域的值,默认值可以用 `getFieldDecorator` 里的 `initialValue`。
|
||||||
|
3. 你不应该用 `setState`,可以使用 `this.props.form.setFieldsValue` 来动态改变表单值。
|
||||||
|
|
||||||
|
#### 特别注意
|
||||||
|
|
||||||
|
1. `getFieldDecorator` 不能用于装饰纯函数组件。
|
||||||
|
2. 如果使用的是 `react@<15.3.0`,则 `getFieldDecorator` 调用不能位于纯函数组件中: <https://github.com/facebook/react/pull/6534>
|
||||||
|
|
||||||
|
#### getFieldDecorator(id, options) 参数
|
||||||
|
|
||||||
|
| 参数 | 说明 | 类型 | 默认值 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| id | 必填输入控件唯一标志。支持嵌套式的[写法](https://github.com/react-component/form/pull/48)。 | string | |
|
||||||
|
| options.getValueFromEvent | 可以把 onChange 的参数(如 event)转化为控件的值 | function(..args) | [reference](https://github.com/react-component/form#option-object) |
|
||||||
|
| options.initialValue | 子节点的初始值,类型、可选值均由子节点决定(注意:由于内部校验时使用 `===` 判断是否变化,建议使用变量缓存所需设置的值而非直接使用字面量)) | | |
|
||||||
|
| options.normalize | 转换默认的 value 给控件,[一个选择全部的例子](https://codepen.io/afc163/pen/JJVXzG?editors=001) | function(value, prevValue, allValues): any | - |
|
||||||
|
| options.rules | 校验规则,参考下方文档 | object\[] | |
|
||||||
|
| options.trigger | 收集子节点的值的时机 | string | 'onChange' |
|
||||||
|
| options.validateFirst | 当某一规则校验不通过时,是否停止剩下的规则的校验 | boolean | false |
|
||||||
|
| options.validateTrigger | 校验子节点值的时机 | string\|string\[] | 'onChange' |
|
||||||
|
| options.valuePropName | 子节点的值的属性,如 Switch 的是 'checked' | string | 'value' |
|
||||||
|
|
||||||
|
更多参数请查看 [rc-form option](https://github.com/react-component/form#option-object)。
|
||||||
|
|
||||||
|
### Form.Item
|
||||||
|
|
||||||
|
注意:
|
||||||
|
|
||||||
|
- 一个 Form.Item 建议只放一个被 getFieldDecorator 装饰过的 child,当有多个被装饰过的 child 时,`help` `required` `validateStatus` 无法自动生成。
|
||||||
|
- `2.2.0` 之前,只有当表单域为 Form.Item 的子元素时,才会自动生成 `help` `required` `validateStatus`,嵌套情况需要自行设置。
|
||||||
|
|
||||||
|
| 参数 | 说明 | 类型 | 默认值 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| colon | 配合 label 属性使用,表示是否显示 label 后面的冒号 | boolean | true |
|
||||||
|
| extra | 额外的提示信息,和 help 类似,当需要错误信息和提示文案同时出现时,可以使用这个。 | string\|ReactNode | |
|
||||||
|
| hasFeedback | 配合 validateStatus 属性使用,展示校验状态图标,建议只配合 Input 组件使用 | boolean | false |
|
||||||
|
| help | 提示信息,如不设置,则会根据校验规则自动生成 | string\|ReactNode | |
|
||||||
|
| label | label 标签的文本 | string\|ReactNode | |
|
||||||
|
| labelCol | label 标签布局,同 `<Col>` 组件,设置 `span` `offset` 值,如 `{span: 3, offset: 12}` 或 `sm: {span: 3, offset: 12}` | [object](https://ant.design/components/grid/#Col) | |
|
||||||
|
| required | 是否必填,如不设置,则会根据校验规则自动生成 | boolean | false |
|
||||||
|
| validateStatus | 校验状态,如不设置,则会根据校验规则自动生成,可选:'success' 'warning' 'error' 'validating' | string | |
|
||||||
|
| wrapperCol | 需要为输入控件设置布局样式时,使用该属性,用法同 labelCol | [object](https://ant.design/components/grid/#Col) | |
|
||||||
|
|
||||||
|
### 校验规则
|
||||||
|
|
||||||
|
| 参数 | 说明 | 类型 | 默认值 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| enum | 枚举类型 | string | - |
|
||||||
|
| len | 字段长度 | number | - |
|
||||||
|
| max | 最大长度 | number | - |
|
||||||
|
| message | 校验文案 | string | - |
|
||||||
|
| min | 最小长度 | number | - |
|
||||||
|
| pattern | 正则表达式校验 | RegExp | - |
|
||||||
|
| required | 是否必选 | boolean | `false` |
|
||||||
|
| transform | 校验前转换字段值 | function(value) => transformedValue:any | - |
|
||||||
|
| type | 内建校验类型,[可选项](https://github.com/yiminghe/async-validator#type) | string | 'string' |
|
||||||
|
| validator | 自定义校验(注意,[callback 必须被调用](https://github.com/ant-design/ant-design/issues/5155)) | function(rule, value, callback) | - |
|
||||||
|
| whitespace | 必选时,空格是否会被视为错误 | boolean | `false` |
|
||||||
|
|
||||||
|
更多高级用法可研究 [async-validator](https://github.com/yiminghe/async-validator)。
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.code-box-demo .ant-form:not(.ant-form-inline):not(.ant-form-vertical) {
|
||||||
|
max-width: 600px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
## 在 TypeScript 中使用
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
import { Form } from 'antd';
|
||||||
|
import { FormComponentProps } from 'antd/lib/form';
|
||||||
|
|
||||||
|
interface UserFormProps extends FormComponentProps {
|
||||||
|
age: number;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
class UserForm extends React.Component<UserFormProps, any> {
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
|
@ -0,0 +1,5 @@
|
||||||
|
import '../../style/index.less'
|
||||||
|
import './index.less'
|
||||||
|
|
||||||
|
// style dependencies
|
||||||
|
import '../../grid/style'
|
|
@ -0,0 +1,624 @@
|
||||||
|
@import "../../style/themes/default";
|
||||||
|
@import "../../style/mixins/index";
|
||||||
|
@import "../../input/style/mixin";
|
||||||
|
@import "../../button/style/mixin";
|
||||||
|
@import "../../grid/style/mixin";
|
||||||
|
@import "./mixin";
|
||||||
|
|
||||||
|
@form-prefix-cls: ~"@{ant-prefix}-form";
|
||||||
|
@form-component-height: @input-height-base;
|
||||||
|
@form-component-max-height: @input-height-lg;
|
||||||
|
@form-feedback-icon-size: 14px;
|
||||||
|
@form-help-margin-top: (@form-component-height - @form-component-max-height) / 2 + 2px;
|
||||||
|
|
||||||
|
.@{form-prefix-cls} {
|
||||||
|
.reset-component;
|
||||||
|
.reset-form;
|
||||||
|
}
|
||||||
|
|
||||||
|
.@{form-prefix-cls}-item-required:before {
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 4px;
|
||||||
|
content: "*";
|
||||||
|
font-family: SimSun;
|
||||||
|
line-height: 1;
|
||||||
|
font-size: @font-size-base;
|
||||||
|
color: @label-required-color;
|
||||||
|
.@{form-prefix-cls}-hide-required-mark & {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Radio && Checkbox
|
||||||
|
input[type="radio"],
|
||||||
|
input[type="checkbox"] {
|
||||||
|
&[disabled],
|
||||||
|
&.disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// These classes are used directly on <label>s
|
||||||
|
.@{ant-prefix}-radio-inline,
|
||||||
|
.@{ant-prefix}-radio-vertical,
|
||||||
|
.@{ant-prefix}-checkbox-inline,
|
||||||
|
.@{ant-prefix}-checkbox-vertical {
|
||||||
|
&.disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// These classes are used on elements with <label> descendants
|
||||||
|
.@{ant-prefix}-radio,
|
||||||
|
.@{ant-prefix}-checkbox {
|
||||||
|
&.disabled {
|
||||||
|
label {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Form items
|
||||||
|
// You should wrap labels and controls in .@{form-prefix-cls}-item for optimum spacing
|
||||||
|
.@{form-prefix-cls}-item {
|
||||||
|
label {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
> .@{iconfont-css-prefix} {
|
||||||
|
vertical-align: top;
|
||||||
|
font-size: @font-size-base;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.reset-component;
|
||||||
|
margin-bottom: @form-item-margin-bottom;
|
||||||
|
vertical-align: top;
|
||||||
|
|
||||||
|
// nested FormItem
|
||||||
|
&-control > &:last-child,
|
||||||
|
& [class^="@{ant-prefix}-col-"] > &:only-child {
|
||||||
|
margin-bottom: -@form-item-margin-bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-control {
|
||||||
|
line-height: @form-component-max-height - 0.0001px; // https://github.com/ant-design/ant-design/issues/8220
|
||||||
|
position: relative;
|
||||||
|
.clearfix;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-children {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-with-help {
|
||||||
|
margin-bottom: @form-item-margin-bottom - @font-size-base * @line-height-base - @form-help-margin-top;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-label {
|
||||||
|
text-align: right;
|
||||||
|
vertical-align: middle;
|
||||||
|
line-height: @form-component-max-height - 0.0001px;
|
||||||
|
display: inline-block;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
label {
|
||||||
|
color: @label-color;
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
& when (@form-item-trailing-colon=true) {
|
||||||
|
content: ":";
|
||||||
|
}
|
||||||
|
& when not (@form-item-trailing-colon=true) {
|
||||||
|
content: " ";
|
||||||
|
}
|
||||||
|
margin: 0 8px 0 2px;
|
||||||
|
position: relative;
|
||||||
|
top: -0.5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.@{ant-prefix}-switch {
|
||||||
|
margin: 2px 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-no-colon &-label label:after {
|
||||||
|
content: " ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.@{form-prefix-cls}-explain,
|
||||||
|
.@{form-prefix-cls}-extra {
|
||||||
|
color: @text-color-secondary;
|
||||||
|
line-height: @line-height-base;
|
||||||
|
transition: color .15s @ease-out;
|
||||||
|
margin-top: @form-help-margin-top;
|
||||||
|
}
|
||||||
|
|
||||||
|
.@{form-prefix-cls}-extra {
|
||||||
|
padding-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.@{form-prefix-cls}-text {
|
||||||
|
display: inline-block;
|
||||||
|
padding-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.@{form-prefix-cls}-split {
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表单下的输入框尺寸唯一: 大尺寸
|
||||||
|
form {
|
||||||
|
.has-feedback {
|
||||||
|
.@{ant-prefix}-input {
|
||||||
|
padding-right: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fix overlapping between feedback icon and <Select>'s arrow.
|
||||||
|
// https://github.com/ant-design/ant-design/issues/4431
|
||||||
|
> .@{ant-prefix}-select .@{ant-prefix}-select-arrow,
|
||||||
|
> .@{ant-prefix}-select .@{ant-prefix}-select-selection__clear,
|
||||||
|
:not(.@{ant-prefix}-input-group-addon) > .@{ant-prefix}-select .@{ant-prefix}-select-arrow,
|
||||||
|
:not(.@{ant-prefix}-input-group-addon) > .@{ant-prefix}-select .@{ant-prefix}-select-selection__clear {
|
||||||
|
right: 28px;
|
||||||
|
}
|
||||||
|
> .@{ant-prefix}-select .@{ant-prefix}-select-selection-selected-value,
|
||||||
|
:not(.@{ant-prefix}-input-group-addon) > .@{ant-prefix}-select .@{ant-prefix}-select-selection-selected-value {
|
||||||
|
padding-right: 42px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.@{ant-prefix}-cascader-picker {
|
||||||
|
&-arrow {
|
||||||
|
margin-right: 17px;
|
||||||
|
}
|
||||||
|
&-clear {
|
||||||
|
right: 28px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fix issue: https://github.com/ant-design/ant-design/issues/7854
|
||||||
|
.@{ant-prefix}-input-search:not(.@{ant-prefix}-input-search-enter-button) {
|
||||||
|
.@{ant-prefix}-input-suffix {
|
||||||
|
right: 28px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fix issue: https://github.com/ant-design/ant-design/issues/4783
|
||||||
|
.@{ant-prefix}-calendar-picker,
|
||||||
|
.@{ant-prefix}-time-picker {
|
||||||
|
&-icon,
|
||||||
|
&-clear {
|
||||||
|
right: 28px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea.@{ant-prefix}-input {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
// input[type=file]
|
||||||
|
.@{ant-prefix}-upload {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="radio"],
|
||||||
|
input[type="checkbox"] {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Radios and checkboxes on same line
|
||||||
|
.@{ant-prefix}-radio-inline,
|
||||||
|
.@{ant-prefix}-checkbox-inline {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
font-weight: normal;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-left: 8px;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.@{ant-prefix}-checkbox-vertical,
|
||||||
|
.@{ant-prefix}-radio-vertical {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.@{ant-prefix}-checkbox-vertical + .@{ant-prefix}-checkbox-vertical,
|
||||||
|
.@{ant-prefix}-radio-vertical + .@{ant-prefix}-radio-vertical {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.@{ant-prefix}-input-number + .@{form-prefix-cls}-text {
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.@{ant-prefix}-select,
|
||||||
|
.@{ant-prefix}-cascader-picker {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't impact select inside input group
|
||||||
|
.@{ant-prefix}-input-group .@{ant-prefix}-select,
|
||||||
|
.@{ant-prefix}-input-group .@{ant-prefix}-cascader-picker {
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
// fix input with addon position. https://github.com/ant-design/ant-design/issues/8243
|
||||||
|
.@{ant-prefix}-input-group-wrapper {
|
||||||
|
vertical-align: middle;
|
||||||
|
position: relative;
|
||||||
|
top: -1px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Input combined with select
|
||||||
|
.@{ant-prefix}-input-group-wrap {
|
||||||
|
.@{ant-prefix}-select-selection {
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
&:hover {
|
||||||
|
border-color: @border-color-base;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.@{ant-prefix}-select-selection--single {
|
||||||
|
margin-left: -1px;
|
||||||
|
height: @input-height-lg;
|
||||||
|
background-color: #eee;
|
||||||
|
.@{ant-prefix}-select-selection__rendered {
|
||||||
|
padding-left: 8px;
|
||||||
|
padding-right: 25px;
|
||||||
|
line-height: 30px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.@{ant-prefix}-select-open .@{ant-prefix}-select-selection {
|
||||||
|
border-color: @border-color-base;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Form layout
|
||||||
|
//== Vertical Form
|
||||||
|
.make-vertical-layout-label() {
|
||||||
|
padding: @form-vertical-label-padding;
|
||||||
|
margin: @form-vertical-label-margin;
|
||||||
|
display: block;
|
||||||
|
text-align: left;
|
||||||
|
line-height: @line-height-base;
|
||||||
|
|
||||||
|
label:after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.make-vertical-layout() {
|
||||||
|
.@{form-prefix-cls}-item-label,
|
||||||
|
.@{form-prefix-cls}-item-control-wrapper {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.@{form-prefix-cls}-item-label {
|
||||||
|
.make-vertical-layout-label();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.@{form-prefix-cls}-vertical .@{form-prefix-cls}-item-label,
|
||||||
|
// when labelCol is 24, it is a vertical form
|
||||||
|
.@{ant-prefix}-col-24.@{form-prefix-cls}-item-label,
|
||||||
|
.@{ant-prefix}-col-xl-24.@{form-prefix-cls}-item-label {
|
||||||
|
.make-vertical-layout-label();
|
||||||
|
}
|
||||||
|
|
||||||
|
.@{form-prefix-cls}-vertical {
|
||||||
|
.@{form-prefix-cls}-item {
|
||||||
|
padding-bottom: 8px;
|
||||||
|
}
|
||||||
|
.@{form-prefix-cls}-item-control {
|
||||||
|
line-height: @line-height-base;
|
||||||
|
}
|
||||||
|
.@{form-prefix-cls}-explain,
|
||||||
|
.@{form-prefix-cls}-extra {
|
||||||
|
margin-top: 2px;
|
||||||
|
margin-bottom: -4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: @screen-xs-max) {
|
||||||
|
.make-vertical-layout();
|
||||||
|
.@{ant-prefix}-col-xs-24.@{form-prefix-cls}-item-label {
|
||||||
|
.make-vertical-layout-label();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: @screen-sm-max) {
|
||||||
|
.@{ant-prefix}-col-sm-24.@{form-prefix-cls}-item-label {
|
||||||
|
.make-vertical-layout-label();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: @screen-md-max) {
|
||||||
|
.@{ant-prefix}-col-md-24.@{form-prefix-cls}-item-label {
|
||||||
|
.make-vertical-layout-label();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: @screen-lg-max) {
|
||||||
|
.@{ant-prefix}-col-lg-24.@{form-prefix-cls}-item-label {
|
||||||
|
.make-vertical-layout-label();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: @screen-xl-max) {
|
||||||
|
.@{ant-prefix}-col-xl-24.@{form-prefix-cls}-item-label {
|
||||||
|
.make-vertical-layout-label();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//== Inline Form
|
||||||
|
.@{form-prefix-cls}-inline {
|
||||||
|
.@{form-prefix-cls}-item {
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 16px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
|
||||||
|
&-with-help {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .@{form-prefix-cls}-item-control-wrapper, > .@{form-prefix-cls}-item-label {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.@{form-prefix-cls}-text {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.has-feedback {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fix https://github.com/ant-design/ant-design/issues/1040
|
||||||
|
.@{form-prefix-cls}-explain {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validation state
|
||||||
|
.has-success,
|
||||||
|
.has-warning,
|
||||||
|
.has-error,
|
||||||
|
.is-validating {
|
||||||
|
&.has-feedback .@{form-prefix-cls}-item-children:after {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
right: 0;
|
||||||
|
visibility: visible;
|
||||||
|
pointer-events: none;
|
||||||
|
width: @form-component-height;
|
||||||
|
height: 20px;
|
||||||
|
line-height: 20px;
|
||||||
|
margin-top: -10px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: @form-feedback-icon-size;
|
||||||
|
animation: zoomIn .3s @ease-out-back;
|
||||||
|
.iconfont-font("");
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.has-success {
|
||||||
|
&.has-feedback .@{form-prefix-cls}-item-children:after {
|
||||||
|
animation-name: diffZoomIn1 !important;
|
||||||
|
content: '\e630';
|
||||||
|
color: @success-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.has-warning {
|
||||||
|
.form-control-validation(@warning-color; @warning-color;);
|
||||||
|
|
||||||
|
&.has-feedback .@{form-prefix-cls}-item-children:after {
|
||||||
|
content: '\e62c';
|
||||||
|
color: @warning-color;
|
||||||
|
animation-name: diffZoomIn3 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
//select
|
||||||
|
.@{ant-prefix}-select {
|
||||||
|
&-selection {
|
||||||
|
border-color: @warning-color;
|
||||||
|
}
|
||||||
|
&-open .@{ant-prefix}-select-selection,
|
||||||
|
&-focused .@{ant-prefix}-select-selection {
|
||||||
|
.active(@warning-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// arrow and icon
|
||||||
|
.@{ant-prefix}-calendar-picker-icon:after,
|
||||||
|
.@{ant-prefix}-time-picker-icon:after,
|
||||||
|
.@{ant-prefix}-picker-icon:after,
|
||||||
|
.@{ant-prefix}-select-arrow,
|
||||||
|
.@{ant-prefix}-cascader-picker-arrow {
|
||||||
|
color: @warning-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
//input-number, timepicker
|
||||||
|
.@{ant-prefix}-input-number,
|
||||||
|
.@{ant-prefix}-time-picker-input {
|
||||||
|
border-color: @warning-color;
|
||||||
|
&-focused,
|
||||||
|
&:focus {
|
||||||
|
.active(@warning-color);
|
||||||
|
}
|
||||||
|
&:not([disabled]):hover {
|
||||||
|
border-color: @warning-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.@{ant-prefix}-cascader-picker:focus .@{ant-prefix}-cascader-input {
|
||||||
|
.active(@warning-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.has-error {
|
||||||
|
.form-control-validation(@error-color; @error-color;);
|
||||||
|
|
||||||
|
&.has-feedback .@{form-prefix-cls}-item-children:after {
|
||||||
|
content: '\e62e';
|
||||||
|
color: @error-color;
|
||||||
|
animation-name: diffZoomIn2 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
//select
|
||||||
|
.@{ant-prefix}-select {
|
||||||
|
&-selection {
|
||||||
|
border-color: @error-color;
|
||||||
|
}
|
||||||
|
&-open .@{ant-prefix}-select-selection,
|
||||||
|
&-focused .@{ant-prefix}-select-selection {
|
||||||
|
.active(@error-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.@{ant-prefix}-select.@{ant-prefix}-select-auto-complete {
|
||||||
|
.@{ant-prefix}-input:focus {
|
||||||
|
border-color: @error-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.@{ant-prefix}-input-group-addon .@{ant-prefix}-select {
|
||||||
|
&-selection {
|
||||||
|
border-color: transparent;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// arrow and icon
|
||||||
|
.@{ant-prefix}-calendar-picker-icon:after,
|
||||||
|
.@{ant-prefix}-time-picker-icon:after,
|
||||||
|
.@{ant-prefix}-picker-icon:after,
|
||||||
|
.@{ant-prefix}-select-arrow,
|
||||||
|
.@{ant-prefix}-cascader-picker-arrow {
|
||||||
|
color: @error-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
//input-number, timepicker
|
||||||
|
.@{ant-prefix}-input-number,
|
||||||
|
.@{ant-prefix}-time-picker-input {
|
||||||
|
border-color: @error-color;
|
||||||
|
&-focused,
|
||||||
|
&:focus {
|
||||||
|
.active(@error-color);
|
||||||
|
}
|
||||||
|
&:not([disabled]):hover {
|
||||||
|
border-color: @error-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.@{ant-prefix}-mention-wrapper {
|
||||||
|
.@{ant-prefix}-mention-editor {
|
||||||
|
&,
|
||||||
|
&:not([disabled]):hover {
|
||||||
|
border-color: @error-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.@{ant-prefix}-mention-active:not([disabled]) .@{ant-prefix}-mention-editor,
|
||||||
|
.@{ant-prefix}-mention-editor:not([disabled]):focus {
|
||||||
|
.active(@error-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.@{ant-prefix}-cascader-picker:focus .@{ant-prefix}-cascader-input {
|
||||||
|
.active(@error-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-validating {
|
||||||
|
&.has-feedback .@{form-prefix-cls}-item-children:after {
|
||||||
|
display: inline-block;
|
||||||
|
animation: loadingCircle 1s infinite linear;
|
||||||
|
content: "\e64d";
|
||||||
|
color: @primary-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.@{ant-prefix}-advanced-search-form {
|
||||||
|
.@{form-prefix-cls}-item {
|
||||||
|
margin-bottom: @form-item-margin-bottom;
|
||||||
|
|
||||||
|
&-with-help {
|
||||||
|
margin-bottom: @form-item-margin-bottom - @font-size-base * @line-height-base - @form-help-margin-top;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.show-help-motion(@className, @keyframeName, @duration: @animation-duration-slow) {
|
||||||
|
.make-motion(@className, @keyframeName, @duration);
|
||||||
|
.@{className}-enter,
|
||||||
|
.@{className}-appear {
|
||||||
|
opacity: 0;
|
||||||
|
animation-timing-function: @ease-in-out;
|
||||||
|
}
|
||||||
|
.@{className}-leave {
|
||||||
|
animation-timing-function: @ease-in-out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.show-help-motion(show-help, antShowHelp, 0.15s);
|
||||||
|
|
||||||
|
@keyframes antShowHelpIn {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-5px);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes antShowHelpOut {
|
||||||
|
to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-5px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// need there different zoom animation
|
||||||
|
// otherwise won't trigger anim
|
||||||
|
@keyframes diffZoomIn1 {
|
||||||
|
0% {
|
||||||
|
transform: scale(0);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes diffZoomIn2 {
|
||||||
|
0% {
|
||||||
|
transform: scale(0);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes diffZoomIn3 {
|
||||||
|
0% {
|
||||||
|
transform: scale(0);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,106 @@
|
||||||
|
@import "../../input/style/mixin";
|
||||||
|
|
||||||
|
.form-control-validation(@text-color: @input-color; @border-color: @input-border-color; @background-color: @input-bg) {
|
||||||
|
.@{ant-prefix}-form-explain,
|
||||||
|
.@{ant-prefix}-form-split {
|
||||||
|
color: @text-color;
|
||||||
|
}
|
||||||
|
// 输入框的不同校验状态
|
||||||
|
.@{ant-prefix}-input {
|
||||||
|
&,
|
||||||
|
&:hover {
|
||||||
|
border-color: @border-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
.active(@border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not([disabled]):hover {
|
||||||
|
border-color: @border-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.@{ant-prefix}-calendar-picker-open .@{ant-prefix}-calendar-picker-input {
|
||||||
|
.active(@border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.@{ant-prefix}-input-prefix {
|
||||||
|
color: @text-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.@{ant-prefix}-input-group-addon {
|
||||||
|
color: @text-color;
|
||||||
|
border-color: @border-color;
|
||||||
|
background-color: @background-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.has-feedback {
|
||||||
|
color: @text-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset form styles
|
||||||
|
// -----------------------------
|
||||||
|
// Based on Bootstrap framework
|
||||||
|
.reset-form() {
|
||||||
|
legend {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
font-size: @font-size-lg;
|
||||||
|
line-height: inherit;
|
||||||
|
color: @text-color-secondary;
|
||||||
|
border: 0;
|
||||||
|
border-bottom: @border-width-base @border-style-base @border-color-base;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-size: @font-size-base;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="search"] {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Position radios and checkboxes better
|
||||||
|
input[type="radio"],
|
||||||
|
input[type="checkbox"] {
|
||||||
|
line-height: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="file"] {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make range inputs behave like textual form controls
|
||||||
|
input[type="range"] {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make multiple select elements height not fixed
|
||||||
|
select[multiple],
|
||||||
|
select[size] {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Focus for file, radio, and checkbox
|
||||||
|
input[type="file"]:focus,
|
||||||
|
input[type="radio"]:focus,
|
||||||
|
input[type="checkbox"]:focus {
|
||||||
|
outline: thin dotted;
|
||||||
|
outline: 5px auto -webkit-focus-ring-color; // lesshint duplicateProperty: false
|
||||||
|
outline-offset: -2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjust output element
|
||||||
|
output {
|
||||||
|
display: block;
|
||||||
|
padding-top: 15px;
|
||||||
|
font-size: @font-size-base;
|
||||||
|
line-height: @line-height-base;
|
||||||
|
color: @input-color;
|
||||||
|
}
|
||||||
|
}
|
|
@ -50,7 +50,7 @@ import { default as Divider } from './divider'
|
||||||
|
|
||||||
import { default as Dropdown } from './dropdown'
|
import { default as Dropdown } from './dropdown'
|
||||||
|
|
||||||
// import { default as Form } from './form'
|
import { default as Form } from './form'
|
||||||
|
|
||||||
import { default as Icon } from './icon'
|
import { default as Icon } from './icon'
|
||||||
|
|
||||||
|
@ -148,6 +148,8 @@ const components = [
|
||||||
Divider,
|
Divider,
|
||||||
Dropdown,
|
Dropdown,
|
||||||
Dropdown.Button,
|
Dropdown.Button,
|
||||||
|
Form,
|
||||||
|
Form.Item,
|
||||||
Icon,
|
Icon,
|
||||||
Input,
|
Input,
|
||||||
Input.Group,
|
Input.Group,
|
||||||
|
@ -237,6 +239,7 @@ export {
|
||||||
DatePicker,
|
DatePicker,
|
||||||
Divider,
|
Divider,
|
||||||
Dropdown,
|
Dropdown,
|
||||||
|
Form,
|
||||||
Icon,
|
Icon,
|
||||||
Input,
|
Input,
|
||||||
InputNumber,
|
InputNumber,
|
||||||
|
|
|
@ -11,7 +11,6 @@ export default {
|
||||||
default: 'text',
|
default: 'text',
|
||||||
type: String,
|
type: String,
|
||||||
},
|
},
|
||||||
id: [String, Number],
|
|
||||||
name: String,
|
name: String,
|
||||||
size: {
|
size: {
|
||||||
validator (value) {
|
validator (value) {
|
||||||
|
|
|
@ -43,3 +43,4 @@ import './transfer/style'
|
||||||
import './tree/style'
|
import './tree/style'
|
||||||
import './upload/style'
|
import './upload/style'
|
||||||
import './layout/style'
|
import './layout/style'
|
||||||
|
import './form/style'
|
||||||
|
|
|
@ -2,6 +2,7 @@ import AsyncValidator from 'async-validator'
|
||||||
import warning from 'warning'
|
import warning from 'warning'
|
||||||
import get from 'lodash/get'
|
import get from 'lodash/get'
|
||||||
import set from 'lodash/set'
|
import set from 'lodash/set'
|
||||||
|
import omit from 'lodash/omit'
|
||||||
import createFieldsStore from './createFieldsStore'
|
import createFieldsStore from './createFieldsStore'
|
||||||
import { cloneElement } from '../../_util/vnode'
|
import { cloneElement } from '../../_util/vnode'
|
||||||
import BaseMixin from '../../_util/BaseMixin'
|
import BaseMixin from '../../_util/BaseMixin'
|
||||||
|
@ -190,6 +191,8 @@ function createBaseForm (option = {}, mixins = []) {
|
||||||
originalEvents[key](...args)
|
originalEvents[key](...args)
|
||||||
triggerEvents(...args)
|
triggerEvents(...args)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
newEvents[key] = newProps.on[key]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return cloneElement(fieldElem, { ...newProps, on: newEvents })
|
return cloneElement(fieldElem, { ...newProps, on: newEvents })
|
||||||
|
@ -238,6 +241,7 @@ function createBaseForm (option = {}, mixins = []) {
|
||||||
// ref: name,
|
// ref: name,
|
||||||
}
|
}
|
||||||
const inputListeners = {}
|
const inputListeners = {}
|
||||||
|
const inputAttrs = {}
|
||||||
if (fieldNameProp) {
|
if (fieldNameProp) {
|
||||||
inputProps[fieldNameProp] = name
|
inputProps[fieldNameProp] = name
|
||||||
}
|
}
|
||||||
|
@ -261,18 +265,23 @@ function createBaseForm (option = {}, mixins = []) {
|
||||||
}
|
}
|
||||||
this.fieldsStore.setFieldMeta(name, meta)
|
this.fieldsStore.setFieldMeta(name, meta)
|
||||||
if (fieldMetaProp) {
|
if (fieldMetaProp) {
|
||||||
inputProps[fieldMetaProp] = meta
|
inputAttrs[fieldMetaProp] = meta
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fieldDataProp) {
|
if (fieldDataProp) {
|
||||||
inputProps[fieldDataProp] = this.fieldsStore.getField(name)
|
inputAttrs[fieldDataProp] = this.fieldsStore.getField(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: inputProps,
|
props: omit(inputProps, ['id']),
|
||||||
|
// id: inputProps.id,
|
||||||
domProps: {
|
domProps: {
|
||||||
value: inputProps.value,
|
value: inputProps.value,
|
||||||
},
|
},
|
||||||
|
attrs: {
|
||||||
|
...inputAttrs,
|
||||||
|
id: inputProps.id,
|
||||||
|
},
|
||||||
directives: [
|
directives: [
|
||||||
{
|
{
|
||||||
name: 'ant-ref',
|
name: 'ant-ref',
|
||||||
|
|
|
@ -19,7 +19,7 @@ import {
|
||||||
DatePicker,
|
DatePicker,
|
||||||
Divider,
|
Divider,
|
||||||
Dropdown,
|
Dropdown,
|
||||||
// Form,
|
Form,
|
||||||
Icon,
|
Icon,
|
||||||
Input,
|
Input,
|
||||||
InputNumber,
|
InputNumber,
|
||||||
|
@ -85,7 +85,8 @@ Vue.component(DatePicker.WeekPicker.name, DatePicker.WeekPicker)
|
||||||
Vue.component(Divider.name, Divider)
|
Vue.component(Divider.name, Divider)
|
||||||
Vue.component(Dropdown.name, Dropdown)
|
Vue.component(Dropdown.name, Dropdown)
|
||||||
Vue.component(Dropdown.Button.name, Dropdown.Button)
|
Vue.component(Dropdown.Button.name, Dropdown.Button)
|
||||||
// Vue.component(Form.name, Form)
|
Vue.component(Form.name, Form)
|
||||||
|
Vue.component(Form.Item.name, Form.Item)
|
||||||
Vue.component(Icon.name, Icon)
|
Vue.component(Icon.name, Icon)
|
||||||
Vue.component(Input.name, Input)
|
Vue.component(Input.name, Input)
|
||||||
Vue.component(Input.Group.name, Input.Group)
|
Vue.component(Input.Group.name, Input.Group)
|
||||||
|
|
|
@ -4,7 +4,7 @@ import Iframe from './components/iframe.vue'
|
||||||
const AsyncTestComp = () => {
|
const AsyncTestComp = () => {
|
||||||
const d = window.location.hash.replace('#', '')
|
const d = window.location.hash.replace('#', '')
|
||||||
return {
|
return {
|
||||||
component: import(`../components/vc-form/demo/${d}`),
|
component: import(`../components/form/demo/${d}`),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue