mirror of
https://gitee.com/ant-design-vue/ant-design-vue.git
synced 2024-11-30 02:57:50 +08:00
Merge branch 'master' into unit-test
# Conflicts: # package-lock.json # package.json
This commit is contained in:
commit
0c11e6baca
1
.gitignore
vendored
1
.gitignore
vendored
@ -55,6 +55,7 @@ typings/
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.vscode
|
||||
.env
|
||||
.idea
|
||||
.DS_Store
|
||||
|
42
.vscode/settings.json
vendored
42
.vscode/settings.json
vendored
@ -1,42 +0,0 @@
|
||||
// 将设置放入此文件中以覆盖默认值和用户设置。
|
||||
{
|
||||
"eslint.enable": true,
|
||||
"eslint.options": {
|
||||
"extensions": [
|
||||
".js",
|
||||
".jsx",
|
||||
".vue"
|
||||
],
|
||||
"configFile": ".eslintrc"
|
||||
},
|
||||
"eslint.validate": [
|
||||
{
|
||||
"language": "html",
|
||||
"autoFix": true
|
||||
},
|
||||
{
|
||||
"language": "vue",
|
||||
"autoFix": true
|
||||
},
|
||||
{
|
||||
"language": "javascript",
|
||||
"autoFix": true
|
||||
},
|
||||
{
|
||||
"language": "javascriptreact",
|
||||
"autoFix": true
|
||||
}
|
||||
],
|
||||
"emmet.syntaxProfiles": {
|
||||
"vue-html": "html",
|
||||
"vue": "html"
|
||||
},
|
||||
"eslint.autoFixOnSave": true,
|
||||
"vetur.validation.template": true,
|
||||
"vetur.format.html.wrap_line_length": 60,
|
||||
"vetur.format.js.InsertSpaceBeforeFunctionParenthesis": true,
|
||||
"stylefmt.config": {
|
||||
"fix": true
|
||||
},
|
||||
"editor.tabSize": 2
|
||||
}
|
@ -2,6 +2,33 @@
|
||||
|
||||
---
|
||||
|
||||
## 0.5.1
|
||||
|
||||
`2018-05-10`
|
||||
|
||||
- 🐞 `Table`: Fix `customRow` events not working[#16](https://github.com/vueComponent/ant-design/issues/16)
|
||||
|
||||
## 0.5.0
|
||||
|
||||
`2018-05-08`
|
||||
|
||||
- 🌟 `Form`: add Form component
|
||||
- 💄 `Upload.Dragger`: Modify `name` name to `a-upload-dragger`
|
||||
- 🐞 `Upload`: Fix `name` prop not working
|
||||
|
||||
## 0.4.3
|
||||
|
||||
`2018-05-02`
|
||||
|
||||
- 🐞 Fix component style loss problem
|
||||
- 🌟 site add babel-polyfill
|
||||
|
||||
## 0.4.2
|
||||
|
||||
`2018-04-24`
|
||||
|
||||
- 🐞 fix menu click bug
|
||||
|
||||
## 0.4.1
|
||||
|
||||
#### bug
|
||||
|
@ -2,6 +2,34 @@
|
||||
|
||||
---
|
||||
|
||||
## 0.5.1
|
||||
|
||||
`2018-05-10`
|
||||
|
||||
- 🐞 `Table`: 修复 `customRow` 自定义事件不生效问题[#16](https://github.com/vueComponent/ant-design/issues/16)
|
||||
|
||||
## 0.5.0
|
||||
|
||||
`2018-05-08`
|
||||
|
||||
- 🌟 `Form `新增Form表单组件
|
||||
- 💄 `Upload.Dragger`: 修改组件name名称为`a-upload-dragger`
|
||||
- 🐞 `Upload`: 修复Upload name属性失效问题
|
||||
|
||||
|
||||
## 0.4.3
|
||||
|
||||
`2018-05-02`
|
||||
|
||||
- 🐞 修复组件样式丢失问题
|
||||
- 🌟 站点添加babel-polyfill
|
||||
|
||||
## 0.4.2
|
||||
|
||||
`2018-04-24`
|
||||
|
||||
- 🐞 修复menu 非 inline 模式下的 click bug
|
||||
|
||||
## 0.4.1
|
||||
|
||||
#### bug
|
||||
|
@ -13,6 +13,7 @@
|
||||
|
||||
Ant Design 3.X 的 Vue 实现,开发和服务于企业级后台产品。
|
||||
|
||||
[官网国内镜像](http://tangjinzhou.gitee.io/ant-design/docs/vue/introduce-cn/)
|
||||
|
||||
[README in English](README.md)
|
||||
|
||||
|
@ -14,12 +14,14 @@
|
||||
|
||||
An enterprise-class UI components based on Ant Design 3.X and Vue.
|
||||
|
||||
[官网国内镜像](http://tangjinzhou.gitee.io/ant-design/docs/vue/introduce-cn/)
|
||||
|
||||
[中文 README](README-zh_CN.md)
|
||||
|
||||
## Features
|
||||
|
||||
- An enterprise-class UI design system for desktop applications.
|
||||
- A set of high-quality React components out of the box.
|
||||
- A set of high-quality Vue components out of the box.
|
||||
- Shared [Ant Design of React](https://ant.design/docs/spec/introduce) design resources.
|
||||
|
||||
|
||||
|
@ -7,6 +7,7 @@ module.exports = function (modules) {
|
||||
require.resolve('babel-plugin-transform-es3-property-literals'),
|
||||
require.resolve('babel-plugin-transform-object-assign'),
|
||||
require.resolve('babel-plugin-transform-object-rest-spread'),
|
||||
require.resolve('babel-plugin-transform-class-properties'),
|
||||
]
|
||||
plugins.push([require.resolve('babel-plugin-transform-runtime'), {
|
||||
polyfill: false,
|
||||
|
@ -78,12 +78,12 @@ function babelify (js, modules) {
|
||||
let stream = js.pipe(babel(babelConfig))
|
||||
.pipe(through2.obj(function z (file, encoding, next) {
|
||||
this.push(file.clone())
|
||||
if (file.path.match(/\/style\/index\.js/)) {
|
||||
if (file.path.match(/\/style\/index\.(js|jsx)$/)) {
|
||||
const content = file.contents.toString(encoding)
|
||||
file.contents = Buffer.from(content
|
||||
.replace(/\/style\/?'/g, '/style/css\'')
|
||||
.replace(/\.less/g, '.css'))
|
||||
file.path = file.path.replace(/index\.js/, 'css.js')
|
||||
file.path = file.path.replace(/index\.(js|jsx)$/, 'css.js')
|
||||
this.push(file)
|
||||
next()
|
||||
} else {
|
||||
@ -238,15 +238,25 @@ function pub (done) {
|
||||
gulp.task('dist', ['compile'], (done) => {
|
||||
dist(done)
|
||||
})
|
||||
gulp.task('compile', ['compile-with-es'], () => {
|
||||
gulp.task('compile', ['compile-with-es'], (done) => {
|
||||
compile()
|
||||
.on('finish', function () {
|
||||
done()
|
||||
})
|
||||
})
|
||||
gulp.task('compile-with-es', () => {
|
||||
gulp.task('compile-with-es', (done) => {
|
||||
compile(false)
|
||||
.on('finish', function () {
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
gulp.task('pub', ['check-git', 'compile'], (done) => {
|
||||
if (!process.env.GITHUB_TOKEN) {
|
||||
console.log('no GitHub token found, skip')
|
||||
} else {
|
||||
pub(done)
|
||||
}
|
||||
})
|
||||
|
||||
function reportError () {
|
||||
|
@ -1,6 +1,8 @@
|
||||
|
||||
import Vue from 'vue'
|
||||
import PropTypes from './vue-types'
|
||||
import antRefDirective from './antRefDirective'
|
||||
Vue.use(antRefDirective)
|
||||
|
||||
export default {
|
||||
props: {
|
||||
|
12
components/_util/antRefDirective.js
Normal file
12
components/_util/antRefDirective.js
Normal file
@ -0,0 +1,12 @@
|
||||
export default {
|
||||
install: (Vue, options) => {
|
||||
Vue.directive('ant-ref', {
|
||||
bind: function (el, binding, vnode) {
|
||||
binding.value(vnode)
|
||||
},
|
||||
unbind: function (el, binding, vnode) {
|
||||
binding.value()
|
||||
},
|
||||
})
|
||||
},
|
||||
}
|
@ -1,5 +1,10 @@
|
||||
import isPlainObject from 'lodash/isPlainObject'
|
||||
|
||||
function getType (fn) {
|
||||
const match = fn && fn.toString().match(/^\s*function (\w+)/)
|
||||
return match ? match[1] : ''
|
||||
}
|
||||
|
||||
const camelizeRE = /-(\w)/g
|
||||
const camelize = (str) => {
|
||||
return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '')
|
||||
@ -40,9 +45,9 @@ const filterProps = (props, propsData = {}) => {
|
||||
return res
|
||||
}
|
||||
const getSlots = (ele) => {
|
||||
let componentOptions = ele.componentOptions
|
||||
let componentOptions = ele.componentOptions || {}
|
||||
if (ele.$vnode) {
|
||||
componentOptions = ele.$vnode.componentOptions
|
||||
componentOptions = ele.$vnode.componentOptions || {}
|
||||
}
|
||||
const children = componentOptions.children || []
|
||||
const slots = {}
|
||||
@ -67,8 +72,11 @@ const getOptionProps = (instance) => {
|
||||
const props = (Ctor.options || {}).props || {}
|
||||
const res = {}
|
||||
for (const [k, v] of Object.entries(props)) {
|
||||
if (v.default !== undefined) {
|
||||
res[k] = typeof v.default === 'function' ? v.default() : v.default
|
||||
const def = v.default
|
||||
if (def !== undefined) {
|
||||
res[k] = typeof def === 'function' && getType(v.type) !== 'Function'
|
||||
? def.call(instance)
|
||||
: def
|
||||
}
|
||||
}
|
||||
return { ...res, ...propsData }
|
||||
|
@ -60,7 +60,7 @@ export function cloneElement (n, nodeProps, deep) {
|
||||
return null
|
||||
}
|
||||
const node = cloneVNode(ele, deep)
|
||||
const { props = {}, key, on = {}, children } = nodeProps
|
||||
const { props = {}, key, on = {}, children, directives = [] } = nodeProps
|
||||
const data = node.data || {}
|
||||
let cls = {}
|
||||
let style = {}
|
||||
@ -101,6 +101,7 @@ export function cloneElement (n, nodeProps, deep) {
|
||||
class: cls,
|
||||
domProps: { ...data.domProps, ...domProps },
|
||||
scopedSlots: { ...data.scopedSlots, ...scopedSlots },
|
||||
directives: [...(data.directives || []), ...directives],
|
||||
})
|
||||
|
||||
if (node.componentOptions) {
|
||||
|
@ -4,7 +4,7 @@ import Select, { AbstractSelectProps, SelectValue } from '../select'
|
||||
import Input from '../input'
|
||||
import InputElement from './InputElement'
|
||||
import PropTypes from '../_util/vue-types'
|
||||
import { getComponentFromProp, getOptionProps, filterEmpty } from '../_util/props-util'
|
||||
import { getComponentFromProp, getOptionProps, filterEmpty, isValidElement, getEvents, getStyle, getClass } from '../_util/props-util'
|
||||
|
||||
const DataSourceItemObject = PropTypes.shape({
|
||||
value: String,
|
||||
@ -24,7 +24,7 @@ const AutoCompleteProps = {
|
||||
...AbstractSelectProps,
|
||||
value: SelectValue,
|
||||
defaultValue: SelectValue,
|
||||
dataSource: PropTypes.arrayOf(DataSourceItemType),
|
||||
dataSource: PropTypes.array,
|
||||
optionLabelProp: String,
|
||||
dropdownMatchSelectWidth: PropTypes.bool,
|
||||
// onChange?: (value: SelectValue) => void;
|
||||
@ -57,6 +57,13 @@ export default {
|
||||
const { $slots } = this
|
||||
const children = filterEmpty($slots.default)
|
||||
const element = children.length ? children[0] : <Input />
|
||||
console.log(element)
|
||||
const eleProps = {
|
||||
props: getOptionProps(element),
|
||||
on: getEvents(element),
|
||||
style: getStyle(element),
|
||||
class: getClass(element),
|
||||
}
|
||||
return (
|
||||
<InputElement>{element}</InputElement>
|
||||
)
|
||||
@ -97,6 +104,9 @@ export default {
|
||||
options = childArray
|
||||
} else {
|
||||
options = dataSource ? dataSource.map((item) => {
|
||||
if (isValidElement(item)) {
|
||||
return item
|
||||
}
|
||||
switch (typeof item) {
|
||||
case 'string':
|
||||
return <Option key={item}>{item}</Option>
|
||||
|
@ -7,7 +7,7 @@ import omit from 'omit.js'
|
||||
import KeyCode from '../_util/KeyCode'
|
||||
import Input from '../input'
|
||||
import Icon from '../icon'
|
||||
import { hasProp, filterEmpty, getOptionProps } from '../_util/props-util'
|
||||
import { hasProp, filterEmpty, getOptionProps, getStyle, getClass, getAttrs } from '../_util/props-util'
|
||||
import BaseMixin from '../_util/BaseMixin'
|
||||
|
||||
const CascaderOptionType = PropTypes.shape({
|
||||
@ -83,6 +83,7 @@ function defaultSortFilteredOption (a, b, inputValue) {
|
||||
const defaultDisplayRender = ({ labels }) => labels.join(' / ')
|
||||
|
||||
export default {
|
||||
inheritAttrs: false,
|
||||
name: 'ACascader',
|
||||
mixins: [BaseMixin],
|
||||
props: CascaderProps,
|
||||
@ -280,6 +281,7 @@ export default {
|
||||
[`${prefixCls}-picker-arrow-expand`]: sPopupVisible,
|
||||
})
|
||||
const pickerCls = classNames(
|
||||
getClass(this),
|
||||
`${prefixCls}-picker`, {
|
||||
[`${prefixCls}-picker-with-value`]: inputValue,
|
||||
[`${prefixCls}-picker-disabled`]: disabled,
|
||||
@ -345,11 +347,13 @@ export default {
|
||||
keydown: this.handleKeyDown,
|
||||
change: showSearch ? this.handleInputChange : noop,
|
||||
},
|
||||
attrs: getAttrs(this),
|
||||
}
|
||||
const children = filterEmpty($slots.default)
|
||||
const input = children.length ? children : (
|
||||
<span
|
||||
class={pickerCls}
|
||||
style={getStyle(this)}
|
||||
>
|
||||
{ showSearch ? <span class={`${prefixCls}-picker-label`}>
|
||||
{this.getLabel()}
|
||||
|
@ -1,6 +1,8 @@
|
||||
|
||||
import hasProp from '../_util/props-util'
|
||||
import classNames from 'classnames'
|
||||
import hasProp, { getClass, getStyle } from '../_util/props-util'
|
||||
import PropTypes from '../_util/vue-types'
|
||||
export default {
|
||||
inheritAttrs: false,
|
||||
name: 'ACheckbox',
|
||||
props: {
|
||||
prefixCls: {
|
||||
@ -14,6 +16,7 @@ export default {
|
||||
value: [String, Number, Boolean],
|
||||
name: String,
|
||||
indeterminate: Boolean,
|
||||
type: PropTypes.string.def('checkbox'),
|
||||
},
|
||||
model: {
|
||||
prop: 'checked',
|
||||
@ -52,14 +55,13 @@ export default {
|
||||
handleChange (event) {
|
||||
const targetChecked = event.target.checked
|
||||
this.$emit('input', targetChecked)
|
||||
const { name, value, checked, checkboxGroupContext, sChecked } = this
|
||||
const { checked, checkboxGroupContext } = this
|
||||
if ((checked === undefined && !checkboxGroupContext) || (checkboxGroupContext && checkboxGroupContext.sValue === undefined)) {
|
||||
this.sChecked = targetChecked
|
||||
}
|
||||
const target = {
|
||||
name,
|
||||
value,
|
||||
checked: !sChecked,
|
||||
...this.$props,
|
||||
checked: targetChecked,
|
||||
}
|
||||
this.$emit('change', {
|
||||
target,
|
||||
@ -103,9 +105,13 @@ export default {
|
||||
onChange = () => checkboxGroupContext.toggleOption({ value: props.value })
|
||||
disabled = props.disabled || checkboxGroupContext.disabled
|
||||
}
|
||||
const classString = classNames(getClass(this), {
|
||||
[`${prefixCls}-wrapper`]: true,
|
||||
})
|
||||
return (
|
||||
<label
|
||||
class={`${prefixCls}-wrapper`}
|
||||
class={classString}
|
||||
style={getStyle(this)}
|
||||
onMouseenter={this.onMouseEnter}
|
||||
onMouseleave={this.onMouseLeave}
|
||||
>
|
||||
|
193
components/form/Form.jsx
Executable file
193
components/form/Form.jsx
Executable file
@ -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>
|
||||
},
|
||||
}
|
339
components/form/FormItem.jsx
Normal file
339
components/form/FormItem.jsx
Normal file
@ -0,0 +1,339 @@
|
||||
|
||||
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) {
|
||||
return undefined
|
||||
}
|
||||
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 () {
|
||||
this.setState({ helpShow: false })
|
||||
},
|
||||
|
||||
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,
|
||||
})
|
||||
if (children) {
|
||||
this.setState({ helpShow: true })
|
||||
}
|
||||
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
|
||||
for={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)
|
||||
},
|
||||
}
|
2
components/form/constants.jsx
Normal file
2
components/form/constants.jsx
Normal file
@ -0,0 +1,2 @@
|
||||
export const FIELD_META_PROP = 'data-__meta'
|
||||
export const FIELD_DATA_PROP = 'data-__field'
|
134
components/form/demo/advanced-search.vue
Normal file
134
components/form/demo/advanced-search.vue
Normal file
@ -0,0 +1,134 @@
|
||||
<cn>
|
||||
#### 高级搜索
|
||||
三列栅格式的表单排列方式,常用于数据表格的高级搜索。
|
||||
有部分定制的样式代码,由于输入标签长度不确定,需要根据具体情况自行调整。
|
||||
</cn>
|
||||
|
||||
<us>
|
||||
#### Advanced search
|
||||
Three columns layout is often used for advanced searching of data table.
|
||||
Because the width of label is not fixed, you may need to adjust it by customizing its style.
|
||||
</us>
|
||||
|
||||
|
||||
<script>
|
||||
import { Form } from 'vue-antd-ui'
|
||||
|
||||
const AdvancedSearchForm = {
|
||||
data () {
|
||||
return {
|
||||
expand: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleSearch (e) {
|
||||
e.preventDefault()
|
||||
this.form.validateFields((error, values) => {
|
||||
console.log('error', error)
|
||||
console.log('Received values of form: ', values)
|
||||
})
|
||||
},
|
||||
|
||||
handleReset () {
|
||||
this.form.resetFields()
|
||||
},
|
||||
|
||||
toggle () {
|
||||
this.expand = !this.expand
|
||||
},
|
||||
|
||||
// To generate mock Form.Item
|
||||
getFields () {
|
||||
const count = this.expand ? 10 : 6
|
||||
const { getFieldDecorator } = this.form
|
||||
const children = []
|
||||
for (let i = 0; i < 10; i++) {
|
||||
children.push(
|
||||
<a-col span={8} key={i} style={{ display: i < count ? 'block' : 'none' }}>
|
||||
<a-form-item label={`Field ${i}`}>
|
||||
{getFieldDecorator(`field-${i}`, {
|
||||
rules: [{
|
||||
required: true,
|
||||
message: 'Input something!',
|
||||
}],
|
||||
})(
|
||||
<a-input placeholder='placeholder' />
|
||||
)}
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
)
|
||||
}
|
||||
return children
|
||||
},
|
||||
},
|
||||
|
||||
render () {
|
||||
return (
|
||||
<a-form
|
||||
class='ant-advanced-search-form'
|
||||
onSubmit={this.handleSearch}
|
||||
>
|
||||
<a-row gutter={24}>{this.getFields()}</a-row>
|
||||
<a-row>
|
||||
<a-col span={24} style={{ textAlign: 'right' }}>
|
||||
<a-button type='primary' htmlType='submit'>Search</a-button>
|
||||
<a-button style={{ marginLeft: '8px' }} onClick={this.handleReset}>
|
||||
Clear
|
||||
</a-button>
|
||||
<a style={{ marginLeft: '8px', fontSize: '12px' }} onClick={this.toggle}>
|
||||
Collapse <a-icon type={this.expand ? 'up' : 'down'} />
|
||||
</a>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
const WrappedAdvancedSearchForm = Form.create()(AdvancedSearchForm)
|
||||
|
||||
export default {
|
||||
render () {
|
||||
return (
|
||||
<div id='components-form-demo-advanced-search'>
|
||||
<WrappedAdvancedSearchForm />
|
||||
<div class='search-result-list'>Search Result List</div>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
</script>
|
||||
<style>
|
||||
.ant-advanced-search-form {
|
||||
padding: 24px;
|
||||
background: #fbfbfb;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.ant-advanced-search-form .ant-form-item {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.ant-advanced-search-form .ant-form-item-control-wrapper {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
#components-form-demo-advanced-search .ant-form {
|
||||
max-width: none;
|
||||
}
|
||||
#components-form-demo-advanced-search .search-result-list {
|
||||
margin-top: 16px;
|
||||
border: 1px dashed #e9e9e9;
|
||||
border-radius: 6px;
|
||||
background-color: #fafafa;
|
||||
min-height: 200px;
|
||||
text-align: center;
|
||||
padding-top: 80px;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
|
82
components/form/demo/coordinated.vue
Normal file
82
components/form/demo/coordinated.vue
Normal file
@ -0,0 +1,82 @@
|
||||
<cn>
|
||||
#### 表单联动
|
||||
使用 `setFieldsValue` 来动态设置其他控件的值。
|
||||
</cn>
|
||||
|
||||
<us>
|
||||
#### Coordinated Controls
|
||||
Use `setFieldsValue` to set other control's value programmaticly.
|
||||
</us>
|
||||
|
||||
|
||||
<script>
|
||||
import { Form } from 'vue-antd-ui'
|
||||
|
||||
const CoordinatedForm = {
|
||||
methods: {
|
||||
handleSubmit (e) {
|
||||
e.preventDefault()
|
||||
this.form.validateFields((err, values) => {
|
||||
if (!err) {
|
||||
console.log('Received values of form: ', values)
|
||||
}
|
||||
})
|
||||
},
|
||||
handleSelectChange (value) {
|
||||
console.log(value)
|
||||
this.form.setFieldsValue({
|
||||
note: `Hi, ${value === 'male' ? 'man' : 'lady'}!`,
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
render () {
|
||||
const { getFieldDecorator } = this.form
|
||||
return (
|
||||
<a-form onSubmit={this.handleSubmit}>
|
||||
<a-form-item
|
||||
label='Note'
|
||||
labelCol={{ span: 5 }}
|
||||
wrapperCol={{ span: 12 }}
|
||||
>
|
||||
{getFieldDecorator('note', {
|
||||
rules: [{ required: true, message: 'Please input your note!' }],
|
||||
})(
|
||||
<a-input />
|
||||
)}
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label='Gender'
|
||||
labelCol={{ span: 5 }}
|
||||
wrapperCol={{ span: 12 }}
|
||||
>
|
||||
{getFieldDecorator('gender', {
|
||||
rules: [{ required: true, message: 'Please select your gender!' }],
|
||||
})(
|
||||
<a-select
|
||||
placeholder='Select a option and change input text above'
|
||||
onChange={this.handleSelectChange}
|
||||
>
|
||||
<a-select-option value='male'>male</a-select-option>
|
||||
<a-select-option value='female'>female</a-select-option>
|
||||
</a-select>
|
||||
)}
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
wrapperCol={{ span: 12, offset: 5 }}
|
||||
>
|
||||
<a-button type='primary' htmlType='submit'>
|
||||
Submit
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
export default Form.create()(CoordinatedForm)
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
129
components/form/demo/customized-form-controls.vue
Normal file
129
components/form/demo/customized-form-controls.vue
Normal file
@ -0,0 +1,129 @@
|
||||
<cn>
|
||||
#### 自定义表单控件
|
||||
自定义或第三方的表单控件,也可以与 Form 组件一起使用。只要该组件遵循以下的约定:
|
||||
> * 提供受控属性 `value` 或其它与 [`valuePropName`](/ant-design/components/form-cn/#getFieldDecorator(id,-options)-参数) 的值同名的属性。
|
||||
> * 提供 `onChange` 事件或 [`trigger`](/ant-design/components/form-cn/#getFieldDecorator(id,-options)-参数) 的值同名的事件。
|
||||
> * 不能是函数式组件。
|
||||
</cn>
|
||||
|
||||
<us>
|
||||
#### Customized Form Controls
|
||||
Customized or third-party form controls can be used in Form, too. Controls must follow these conventions:
|
||||
> * It has a controlled property `value` or other name which is equal to the value of [`valuePropName`](/ant-design/components/form/#getFieldDecorator(id,-options)-parameters).
|
||||
> * It has event `onChange` or an event which name is equal to the value of [`trigger`](/ant-design/components/form/#getFieldDecorator(id,-options)-parameters).
|
||||
> * It must be a class component.
|
||||
</us>
|
||||
|
||||
|
||||
<script>
|
||||
import { Form } from 'vue-antd-ui'
|
||||
|
||||
const hasProp = (instance, prop) => {
|
||||
const $options = instance.$options || {}
|
||||
const propsData = $options.propsData || {}
|
||||
return prop in propsData
|
||||
}
|
||||
const PriceInput = {
|
||||
props: ['value'],
|
||||
data () {
|
||||
const value = this.value || {}
|
||||
return {
|
||||
number: value.number || 0,
|
||||
currency: value.currency || 'rmb',
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value (val = {}) {
|
||||
this.number = val.number || 0
|
||||
this.currency = val.currency || 'rmb'
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleNumberChange (e) {
|
||||
const number = parseInt(e.target.value || 0, 10)
|
||||
if (isNaN(number)) {
|
||||
return
|
||||
}
|
||||
if (!hasProp(this, 'value')) {
|
||||
this.number = number
|
||||
}
|
||||
this.triggerChange({ number })
|
||||
},
|
||||
handleCurrencyChange (currency) {
|
||||
if (!hasProp(this, 'value')) {
|
||||
this.currency = currency
|
||||
}
|
||||
this.triggerChange({ currency })
|
||||
},
|
||||
triggerChange (changedValue) {
|
||||
// Should provide an event to pass value to Form.
|
||||
this.$emit('change', Object.assign({}, this.$data, changedValue))
|
||||
},
|
||||
},
|
||||
|
||||
render () {
|
||||
const { number, currency } = this
|
||||
return (
|
||||
<span>
|
||||
<a-input
|
||||
type='text'
|
||||
value={number}
|
||||
onChange={this.handleNumberChange}
|
||||
style={{ width: '65%', marginRight: '3%' }}
|
||||
/>
|
||||
<a-select
|
||||
value={currency}
|
||||
style={{ width: '32%' }}
|
||||
onChange={this.handleCurrencyChange}
|
||||
>
|
||||
<a-select-option value='rmb'>RMB</a-select-option>
|
||||
<a-select-option value='dollar'>Dollar</a-select-option>
|
||||
</a-select>
|
||||
</span>
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
const Demo = {
|
||||
methods: {
|
||||
handleSubmit (e) {
|
||||
e.preventDefault()
|
||||
this.form.validateFields((err, values) => {
|
||||
if (!err) {
|
||||
console.log('Received values of form: ', values)
|
||||
}
|
||||
})
|
||||
},
|
||||
checkPrice (rule, value, callback) {
|
||||
if (value.number > 0) {
|
||||
callback()
|
||||
return
|
||||
}
|
||||
callback('Price must greater than zero!')
|
||||
},
|
||||
},
|
||||
|
||||
render () {
|
||||
const { getFieldDecorator } = this.form
|
||||
return (
|
||||
<a-form layout='inline' onSubmit={this.handleSubmit}>
|
||||
<a-form-item label='Price'>
|
||||
{getFieldDecorator('price', {
|
||||
initialValue: { number: 0, currency: 'rmb' },
|
||||
rules: [{ validator: this.checkPrice }],
|
||||
})(<PriceInput />)}
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button type='primary' htmlType='submit'>Submit</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
export default Form.create()(Demo)
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
144
components/form/demo/dynamic-form-item.vue
Normal file
144
components/form/demo/dynamic-form-item.vue
Normal file
@ -0,0 +1,144 @@
|
||||
<cn>
|
||||
#### 动态增减表单项
|
||||
动态增加、减少表单项。
|
||||
</cn>
|
||||
|
||||
<us>
|
||||
#### Dynamic Form Item
|
||||
Add or remove form items dynamically.
|
||||
</us>
|
||||
|
||||
|
||||
<script>
|
||||
import { Form } from 'vue-antd-ui'
|
||||
|
||||
let uuid = 0
|
||||
const DynamicFieldSet = {
|
||||
methods: {
|
||||
remove (k) {
|
||||
const { form } = this
|
||||
// can use data-binding to get
|
||||
const keys = form.getFieldValue('keys')
|
||||
// We need at least one passenger
|
||||
if (keys.length === 1) {
|
||||
return
|
||||
}
|
||||
|
||||
// can use data-binding to set
|
||||
form.setFieldsValue({
|
||||
keys: keys.filter(key => key !== k),
|
||||
})
|
||||
},
|
||||
|
||||
add () {
|
||||
const { form } = this
|
||||
// can use data-binding to get
|
||||
const keys = form.getFieldValue('keys')
|
||||
const nextKeys = keys.concat(uuid)
|
||||
uuid++
|
||||
// can use data-binding to set
|
||||
// important! notify form to detect changes
|
||||
form.setFieldsValue({
|
||||
keys: nextKeys,
|
||||
})
|
||||
},
|
||||
|
||||
handleSubmit (e) {
|
||||
e.preventDefault()
|
||||
this.form.validateFields((err, values) => {
|
||||
if (!err) {
|
||||
console.log('Received values of form: ', values)
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
render () {
|
||||
const { getFieldDecorator, getFieldValue } = this.form
|
||||
const formItemLayout = {
|
||||
labelCol: {
|
||||
xs: { span: 24 },
|
||||
sm: { span: 4 },
|
||||
},
|
||||
wrapperCol: {
|
||||
xs: { span: 24 },
|
||||
sm: { span: 20 },
|
||||
},
|
||||
}
|
||||
const formItemLayoutWithOutLabel = {
|
||||
wrapperCol: {
|
||||
xs: { span: 24, offset: 0 },
|
||||
sm: { span: 20, offset: 4 },
|
||||
},
|
||||
}
|
||||
getFieldDecorator('keys', { initialValue: [] })
|
||||
const keys = getFieldValue('keys')
|
||||
const formItems = keys.map((k, index) => {
|
||||
return (
|
||||
<a-form-item
|
||||
{...{ props: (index === 0 ? formItemLayout : formItemLayoutWithOutLabel) }}
|
||||
label={index === 0 ? 'Passengers' : ''}
|
||||
required={false}
|
||||
key={k}
|
||||
>
|
||||
{getFieldDecorator(`names[${k}]`, {
|
||||
validateTrigger: ['onChange', 'onBlur'],
|
||||
rules: [{
|
||||
required: true,
|
||||
whitespace: true,
|
||||
message: "Please input passenger's name or delete this field.",
|
||||
}],
|
||||
})(
|
||||
<a-input placeholder='passenger name' style={{ width: '60%', marginRight: '8px' }} />
|
||||
)}
|
||||
{keys.length > 1 ? (
|
||||
<a-icon
|
||||
class='dynamic-delete-button'
|
||||
type='minus-circle-o'
|
||||
disabled={keys.length === 1}
|
||||
onClick={() => this.remove(k)}
|
||||
/>
|
||||
) : null}
|
||||
</a-form-item>
|
||||
)
|
||||
})
|
||||
return (
|
||||
<a-form onSubmit={this.handleSubmit}>
|
||||
{formItems}
|
||||
<a-form-item {...{ props: formItemLayoutWithOutLabel }}>
|
||||
<a-button type='dashed' onClick={this.add} style={{ width: '60%' }}>
|
||||
<a-icon type='plus' /> Add field
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
<a-form-item {...{ props: formItemLayoutWithOutLabel }}>
|
||||
<a-button type='primary' htmlType='submit'>Submit</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
export default Form.create()(DynamicFieldSet)
|
||||
</script>
|
||||
<style>
|
||||
.dynamic-delete-button {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
top: 4px;
|
||||
font-size: 24px;
|
||||
color: #999;
|
||||
transition: all .3s;
|
||||
}
|
||||
.dynamic-delete-button:hover {
|
||||
color: #777;
|
||||
}
|
||||
.dynamic-delete-button[disabled] {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
|
||||
|
94
components/form/demo/dynamic-rule.vue
Normal file
94
components/form/demo/dynamic-rule.vue
Normal file
@ -0,0 +1,94 @@
|
||||
<cn>
|
||||
#### 动态校验规则
|
||||
根据不同情况执行不同的校验规则。
|
||||
</cn>
|
||||
|
||||
<us>
|
||||
#### Dynamic Rules
|
||||
Perform different check rules according to different situations.
|
||||
</us>
|
||||
|
||||
|
||||
<script>
|
||||
import { Form } from 'vue-antd-ui'
|
||||
|
||||
const formItemLayout = {
|
||||
labelCol: { span: 4 },
|
||||
wrapperCol: { span: 8 },
|
||||
}
|
||||
const formTailLayout = {
|
||||
labelCol: { span: 4 },
|
||||
wrapperCol: { span: 8, offset: 4 },
|
||||
}
|
||||
const DynamicRule = {
|
||||
data () {
|
||||
return {
|
||||
checkNick: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
check () {
|
||||
this.form.validateFields(
|
||||
(err) => {
|
||||
if (!err) {
|
||||
console.info('success')
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
handleChange (e) {
|
||||
this.checkNick = e.target.checked
|
||||
this.$nextTick(() => {
|
||||
this.form.validateFields(['nickname'], { force: true })
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
render () {
|
||||
const { getFieldDecorator } = this.form
|
||||
return (
|
||||
<div>
|
||||
<a-form-item {...{ props: formItemLayout }} label='Name'>
|
||||
{getFieldDecorator('username', {
|
||||
rules: [{
|
||||
required: true,
|
||||
message: 'Please input your name',
|
||||
}],
|
||||
})(
|
||||
<a-input placeholder='Please input your name' />
|
||||
)}
|
||||
</a-form-item>
|
||||
<a-form-item {...{ props: formItemLayout }} label='Nickname'>
|
||||
{getFieldDecorator('nickname', {
|
||||
rules: [{
|
||||
required: this.checkNick,
|
||||
message: 'Please input your nickname',
|
||||
}],
|
||||
})(
|
||||
<a-input placeholder='Please input your nickname' />
|
||||
)}
|
||||
</a-form-item>
|
||||
<a-form-item {...{ props: formTailLayout }}>
|
||||
<a-checkbox
|
||||
value={this.checkNick}
|
||||
onChange={this.handleChange}
|
||||
>
|
||||
Nickname is required
|
||||
</a-checkbox>
|
||||
</a-form-item>
|
||||
<a-form-item {...{ props: formTailLayout }}>
|
||||
<a-button type='primary' onClick={this.check}>
|
||||
Check
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
export default Form.create()(DynamicRule)
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
105
components/form/demo/form-in-modal.vue
Normal file
105
components/form/demo/form-in-modal.vue
Normal file
@ -0,0 +1,105 @@
|
||||
<cn>
|
||||
#### 弹出层中的新建表单
|
||||
当用户访问一个展示了某个列表的页面,想新建一项但又不想跳转页面时,可以用 Modal 弹出一个表单,用户填写必要信息后创建新的项。
|
||||
</cn>
|
||||
|
||||
<us>
|
||||
#### Form in Modal to Create
|
||||
When user visit a page with a list of items, and want to create a new item. The page can popup a form in Modal, then let user fill in the form to create an item.
|
||||
</us>
|
||||
|
||||
|
||||
<script>
|
||||
import { Form } from 'vue-antd-ui'
|
||||
|
||||
const CollectionCreateForm = Form.create()(
|
||||
{
|
||||
props: ['visible'],
|
||||
render () {
|
||||
const { visible, form } = this
|
||||
const { getFieldDecorator } = form
|
||||
return (
|
||||
<a-modal
|
||||
visible={visible}
|
||||
title='Create a new collection'
|
||||
okText='Create'
|
||||
onCancel={() => { this.$emit('cancel') }}
|
||||
onOk={() => { this.$emit('create') }}
|
||||
>
|
||||
<a-form layout='vertical'>
|
||||
<a-form-item label='Title'>
|
||||
{getFieldDecorator('title', {
|
||||
rules: [{ required: true, message: 'Please input the title of collection!' }],
|
||||
})(
|
||||
<a-input />
|
||||
)}
|
||||
</a-form-item>
|
||||
<a-form-item label='Description'>
|
||||
{getFieldDecorator('description')(<a-input type='textarea' />)}
|
||||
</a-form-item>
|
||||
<a-form-item className='collection-create-form_last-form-item'>
|
||||
{getFieldDecorator('modifier', {
|
||||
initialValue: 'public',
|
||||
})(
|
||||
<a-radio-group>
|
||||
<a-radio value='public'>Public</a-radio>
|
||||
<a-radio value='private'>Private</a-radio>
|
||||
</a-radio-group>
|
||||
)}
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
)
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
visible: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
showModal () {
|
||||
this.visible = true
|
||||
},
|
||||
handleCancel () {
|
||||
this.visible = false
|
||||
},
|
||||
handleCreate () {
|
||||
const form = this.formRef.form
|
||||
form.validateFields((err, values) => {
|
||||
if (err) {
|
||||
return
|
||||
}
|
||||
|
||||
console.log('Received values of form: ', values)
|
||||
form.resetFields()
|
||||
this.visible = false
|
||||
})
|
||||
},
|
||||
saveFormRef (formRef) {
|
||||
this.formRef = formRef
|
||||
},
|
||||
},
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div>
|
||||
<a-button type='primary' onClick={this.showModal}>New Collection</a-button>
|
||||
<CollectionCreateForm
|
||||
wrappedComponentRef={this.saveFormRef}
|
||||
visible={this.visible}
|
||||
onCancel={this.handleCancel}
|
||||
onCreate={this.handleCreate}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
92
components/form/demo/global-state.vue
Normal file
92
components/form/demo/global-state.vue
Normal file
@ -0,0 +1,92 @@
|
||||
<cn>
|
||||
#### 表单数据存储于上层组件
|
||||
通过使用 `onFieldsChange` 与 `mapPropsToFields`,可以把表单的数据存储到上层组件。
|
||||
**注意:**
|
||||
`mapPropsToFields` 里面返回的表单域数据必须使用 `Form.createFormField` 包装。
|
||||
上层组件传递的属性,必须在`Form.create({ props: ...})`的props中声明。
|
||||
</cn>
|
||||
|
||||
<us>
|
||||
#### Store Form Data into Upper Component
|
||||
We can store form data into upper component.
|
||||
**Note:**
|
||||
You must wrap field data with `Form.createFormField` in `mapPropsToFields`.
|
||||
The properties passed by the upper component must be declared in the props of `Form.create({ props: ...})`.
|
||||
</us>
|
||||
|
||||
|
||||
<script>
|
||||
import { Form } from 'vue-antd-ui'
|
||||
|
||||
const CustomizedForm = Form.create({
|
||||
props: ['username'], // must declare like vue `props` https://vuejs.org/v2/api/#props
|
||||
onFieldsChange (instance, changedFields) {
|
||||
instance.$emit('change', changedFields)
|
||||
},
|
||||
mapPropsToFields (props) {
|
||||
return {
|
||||
username: Form.createFormField({
|
||||
...props.username,
|
||||
value: props.username.value,
|
||||
}),
|
||||
}
|
||||
},
|
||||
onValuesChange (_, values) {
|
||||
console.log(values)
|
||||
},
|
||||
})({
|
||||
render () {
|
||||
const { getFieldDecorator } = this.form
|
||||
return (
|
||||
<a-form layout='inline'>
|
||||
<a-form-item label='Username'>
|
||||
{getFieldDecorator('username', {
|
||||
rules: [{ required: true, message: 'Username is required!' }],
|
||||
})(<a-input />)}
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
)
|
||||
},
|
||||
|
||||
})
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
fields: {
|
||||
username: {
|
||||
value: 'benjycui',
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleFormChange (changedFields) {
|
||||
this.fields = { ...this.fields, ...changedFields }
|
||||
},
|
||||
},
|
||||
|
||||
render () {
|
||||
const fields = this.fields
|
||||
return (
|
||||
<div id='components-form-demo-global-state'>
|
||||
<CustomizedForm {...{ props: fields }} onChange={this.handleFormChange} />
|
||||
<pre class='language-bash'>
|
||||
{JSON.stringify(fields, null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
#components-form-demo-global-state .language-bash {
|
||||
max-width: 400px;
|
||||
border-radius: 6px;
|
||||
margin-top: 24px;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
|
82
components/form/demo/horizontal-login.vue
Normal file
82
components/form/demo/horizontal-login.vue
Normal file
@ -0,0 +1,82 @@
|
||||
<cn>
|
||||
#### 水平登录栏
|
||||
水平登录栏,常用在顶部导航栏中。
|
||||
</cn>
|
||||
|
||||
<us>
|
||||
#### Horizontal Login Form
|
||||
Horizontal login form is often used in navigation bar.
|
||||
</us>
|
||||
|
||||
|
||||
<script>
|
||||
import { Form } from 'vue-antd-ui'
|
||||
|
||||
function hasErrors (fieldsError) {
|
||||
return Object.keys(fieldsError).some(field => fieldsError[field])
|
||||
}
|
||||
|
||||
const HorizontalLoginForm = {
|
||||
mounted () {
|
||||
// To disabled submit button at the beginning.
|
||||
this.form.validateFields()
|
||||
},
|
||||
methods: {
|
||||
handleSubmit (e) {
|
||||
e.preventDefault()
|
||||
this.form.validateFields((err, values) => {
|
||||
if (!err) {
|
||||
console.log('Received values of form: ', values)
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
render () {
|
||||
const { getFieldDecorator, getFieldsError, getFieldError, isFieldTouched } = this.form
|
||||
|
||||
// Only show error after a field is touched.
|
||||
const userNameError = isFieldTouched('userName') && getFieldError('userName')
|
||||
const passwordError = isFieldTouched('password') && getFieldError('password')
|
||||
return (
|
||||
<a-form layout='inline' onSubmit={this.handleSubmit}>
|
||||
<a-form-item
|
||||
validateStatus={userNameError ? 'error' : ''}
|
||||
help={userNameError || ''}
|
||||
>
|
||||
{getFieldDecorator('userName', {
|
||||
rules: [{ required: true, message: 'Please input your username!' }],
|
||||
})(
|
||||
<a-input prefix={<a-icon type='user' style={{ color: 'rgba(0,0,0,.25)' }} />} placeholder='Username' />
|
||||
)}
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
validateStatus={passwordError ? 'error' : ''}
|
||||
help={passwordError || ''}
|
||||
>
|
||||
{getFieldDecorator('password', {
|
||||
rules: [{ required: true, message: 'Please input your Password!' }],
|
||||
})(
|
||||
<a-input prefix={<a-icon type='lock' style={{ color: 'rgba(0,0,0,.25)' }} />} type='password' placeholder='Password' />
|
||||
)}
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button
|
||||
type='primary'
|
||||
htmlType='submit'
|
||||
disabled={hasErrors(getFieldsError())}
|
||||
>
|
||||
Log in
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
export default Form.create()(HorizontalLoginForm)
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
146
components/form/demo/index.vue
Normal file
146
components/form/demo/index.vue
Normal file
@ -0,0 +1,146 @@
|
||||
<script>
|
||||
import AdvancedSearch from './advanced-search'
|
||||
import AdvancedSearchString from '!raw-loader!./advanced-search'
|
||||
import Coordinated from './coordinated'
|
||||
import CoordinatedString from '!raw-loader!./coordinated'
|
||||
import CustomizedFormControls from './customized-form-controls'
|
||||
import CustomizedFormControlsString from '!raw-loader!./customized-form-controls'
|
||||
import DynamicFormItem from './dynamic-form-item'
|
||||
import DynamicFormItemString from '!raw-loader!./dynamic-form-item'
|
||||
import DynamicRule from './dynamic-rule'
|
||||
import DynamicRuleString from '!raw-loader!./dynamic-rule'
|
||||
import FormInModal from './form-in-modal'
|
||||
import FormInModalString from '!raw-loader!./form-in-modal'
|
||||
import GlobalState from './global-state'
|
||||
import GlobalStateString from '!raw-loader!./global-state'
|
||||
import HorizontalLogin from './horizontal-login'
|
||||
import HorizontalLoginString from '!raw-loader!./horizontal-login'
|
||||
import Layout from './layout'
|
||||
import LayoutString from '!raw-loader!./layout'
|
||||
import NormalLogin from './normal-login'
|
||||
import NormalLoginString from '!raw-loader!./normal-login'
|
||||
import Register from './register'
|
||||
import RegisterString from '!raw-loader!./register'
|
||||
import TimeRelatedControls from './time-related-controls'
|
||||
import TimeRelatedControlsString from '!raw-loader!./time-related-controls'
|
||||
import ValidateOther from './validate-other'
|
||||
import ValidateOtherString from '!raw-loader!./validate-other'
|
||||
import ValidateStatic from './validate-static'
|
||||
import ValidateStaticString from '!raw-loader!./validate-static'
|
||||
import WithoutFormCreate from './without-form-create'
|
||||
import WithoutFormCreateString from '!raw-loader!./without-form-create'
|
||||
|
||||
import CN from '../index.zh-CN'
|
||||
import US from '../index.en-US'
|
||||
|
||||
const md = {
|
||||
cn: `# Form 表单
|
||||
具有数据收集、校验和提交功能的表单,包含复选框、单选框、输入框、下拉选择框等元素。
|
||||
|
||||
## 表单
|
||||
|
||||
我们为 \`form\` 提供了以下三种排列方式:
|
||||
|
||||
- 水平排列:标签和表单控件水平排列;(默认)
|
||||
- 垂直排列:标签和表单控件上下垂直排列;
|
||||
- 行内排列:表单项水平行内排列。
|
||||
|
||||
## 表单域
|
||||
|
||||
表单一定会包含表单域,表单域可以是输入控件,标准表单域,标签,下拉菜单,文本域等。
|
||||
|
||||
这里我们封装了表单域 \`<Form.Item />\` 。
|
||||
|
||||
**注意:** 如果使用 \`Form.create\` 处理表单使其具有自动收集数据并校验的功能,建议使用\`jsx\`
|
||||
## 代码演示
|
||||
`,
|
||||
us: `# 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 />\`.
|
||||
|
||||
**Note:** If you use \`Form.create\` to process forms with automatic data collection and validation, we recommend using \`jsx\`
|
||||
## Examples
|
||||
`,
|
||||
}
|
||||
|
||||
export default {
|
||||
category: 'Components',
|
||||
subtitle: '表单',
|
||||
type: 'Data Entry',
|
||||
cols: 1,
|
||||
title: 'Form',
|
||||
render () {
|
||||
return (
|
||||
<div>
|
||||
<md cn={md.cn} us={md.us} />
|
||||
<demo-container code={AdvancedSearchString}>
|
||||
<AdvancedSearch />
|
||||
</demo-container>
|
||||
<demo-container code={CoordinatedString}>
|
||||
<Coordinated />
|
||||
</demo-container>
|
||||
<demo-container code={CustomizedFormControlsString}>
|
||||
<CustomizedFormControls />
|
||||
</demo-container>
|
||||
<demo-container code={DynamicFormItemString}>
|
||||
<DynamicFormItem />
|
||||
</demo-container>
|
||||
<demo-container code={DynamicRuleString}>
|
||||
<DynamicRule />
|
||||
</demo-container>
|
||||
<demo-container code={FormInModalString}>
|
||||
<FormInModal />
|
||||
</demo-container>
|
||||
<demo-container code={GlobalStateString}>
|
||||
<GlobalState />
|
||||
</demo-container>
|
||||
<demo-container code={HorizontalLoginString}>
|
||||
<HorizontalLogin />
|
||||
</demo-container>
|
||||
<demo-container code={LayoutString}>
|
||||
<Layout />
|
||||
</demo-container>
|
||||
<demo-container code={NormalLoginString}>
|
||||
<NormalLogin />
|
||||
</demo-container>
|
||||
<demo-container code={RegisterString}>
|
||||
<Register />
|
||||
</demo-container>
|
||||
<demo-container code={TimeRelatedControlsString}>
|
||||
<TimeRelatedControls />
|
||||
</demo-container>
|
||||
<demo-container code={ValidateStaticString}>
|
||||
<ValidateStatic />
|
||||
</demo-container>
|
||||
<demo-container code={WithoutFormCreateString}>
|
||||
<WithoutFormCreate />
|
||||
</demo-container>
|
||||
<demo-container code={ValidateOtherString}>
|
||||
<ValidateOther />
|
||||
</demo-container>
|
||||
<api>
|
||||
<CN slot='cn' />
|
||||
<US />
|
||||
</api>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.code-box-demo .ant-form:not(.ant-form-inline):not(.ant-form-vertical) {
|
||||
max-width: 600px;
|
||||
}
|
||||
</style>
|
81
components/form/demo/layout.vue
Normal file
81
components/form/demo/layout.vue
Normal file
@ -0,0 +1,81 @@
|
||||
<cn>
|
||||
#### 表单布局
|
||||
表单有三种布局。
|
||||
</cn>
|
||||
|
||||
<us>
|
||||
#### Form Layout
|
||||
There are three layout for form: `horizontal`, `vertical`, `inline`.
|
||||
</us>
|
||||
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<a-form :layout="formLayout">
|
||||
<a-form-item
|
||||
label='Form Layout'
|
||||
:labelCol="formItemLayout.labelCol"
|
||||
:wrapperCol="formItemLayout.wrapperCol"
|
||||
>
|
||||
<a-radio-group defaultValue='horizontal' @change="handleFormLayoutChange">
|
||||
<a-radio-button value='horizontal'>Horizontal</a-radio-button>
|
||||
<a-radio-button value='vertical'>Vertical</a-radio-button>
|
||||
<a-radio-button value='inline'>Inline</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label='Field A'
|
||||
:labelCol="formItemLayout.labelCol"
|
||||
:wrapperCol="formItemLayout.wrapperCol"
|
||||
>
|
||||
<a-input placeholder='input placeholder' />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label='Field B'
|
||||
:labelCol="formItemLayout.labelCol"
|
||||
:wrapperCol="formItemLayout.wrapperCol"
|
||||
>
|
||||
<a-input placeholder='input placeholder' />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
:wrapperCol="buttonItemLayout.wrapperCol"
|
||||
>
|
||||
<a-button type='primary'>Submit</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
formLayout: 'horizontal',
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleFormLayoutChange (e) {
|
||||
this.formLayout = e.target.value
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
formItemLayout () {
|
||||
const { formLayout } = this
|
||||
return formLayout === 'horizontal' ? {
|
||||
labelCol: { span: 4 },
|
||||
wrapperCol: { span: 14 },
|
||||
} : {}
|
||||
},
|
||||
buttonItemLayout () {
|
||||
const { formLayout } = this
|
||||
return formLayout === 'horizontal' ? {
|
||||
wrapperCol: { span: 14, offset: 4 },
|
||||
} : {}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
79
components/form/demo/normal-login.vue
Normal file
79
components/form/demo/normal-login.vue
Normal file
@ -0,0 +1,79 @@
|
||||
<cn>
|
||||
#### 登录框
|
||||
普通的登录框,可以容纳更多的元素。
|
||||
</cn>
|
||||
|
||||
<us>
|
||||
#### Login Form
|
||||
Normal login form which can contain more elements.
|
||||
</us>
|
||||
|
||||
|
||||
<script>
|
||||
import { Form } from 'vue-antd-ui'
|
||||
|
||||
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 (
|
||||
<a-form id='components-form-demo-normal-login' onSubmit={this.handleSubmit} class='login-form'>
|
||||
<a-form-item>
|
||||
{getFieldDecorator('userName', {
|
||||
rules: [{ required: true, message: 'Please input your username!' }],
|
||||
})(
|
||||
<a-input prefix={<a-icon type='user' style={{ color: 'rgba(0,0,0,.25)' }} />} placeholder='Username' />
|
||||
)}
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
{getFieldDecorator('password', {
|
||||
rules: [{ required: true, message: 'Please input your Password!' }],
|
||||
})(
|
||||
<a-input prefix={<a-icon type='lock' style={{ color: 'rgba(0,0,0,.25)' }} />} type='password' placeholder='Password' />
|
||||
)}
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
{getFieldDecorator('remember', {
|
||||
valuePropName: 'checked',
|
||||
initialValue: true,
|
||||
})(
|
||||
<a-checkbox>Remember me</a-checkbox>
|
||||
)}
|
||||
<a class='login-form-forgot' href=''>Forgot password</a>
|
||||
<a-button type='primary' htmlType='submit' class='login-form-button'>
|
||||
Log in
|
||||
</a-button>
|
||||
Or <a href=''>register now!</a>
|
||||
</a-form-item>
|
||||
</a-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>
|
||||
|
||||
|
||||
|
||||
|
260
components/form/demo/register.vue
Normal file
260
components/form/demo/register.vue
Normal file
@ -0,0 +1,260 @@
|
||||
<cn>
|
||||
#### 注册新用户
|
||||
用户填写必须的信息以注册新用户。
|
||||
</cn>
|
||||
|
||||
<us>
|
||||
#### Registration
|
||||
Fill in this form to create a new account for you.
|
||||
</us>
|
||||
|
||||
|
||||
<script>
|
||||
import { Form } from 'vue-antd-ui'
|
||||
|
||||
const residences = [{
|
||||
value: 'zhejiang',
|
||||
label: 'Zhejiang',
|
||||
children: [{
|
||||
value: 'hangzhou',
|
||||
label: 'Hangzhou',
|
||||
children: [{
|
||||
value: 'xihu',
|
||||
label: 'West Lake',
|
||||
}],
|
||||
}],
|
||||
}, {
|
||||
value: 'jiangsu',
|
||||
label: 'Jiangsu',
|
||||
children: [{
|
||||
value: 'nanjing',
|
||||
label: 'Nanjing',
|
||||
children: [{
|
||||
value: 'zhonghuamen',
|
||||
label: 'Zhong Hua Men',
|
||||
}],
|
||||
}],
|
||||
}]
|
||||
|
||||
const RegistrationForm = {
|
||||
data () {
|
||||
return {
|
||||
confirmDirty: false,
|
||||
autoCompleteResult: [],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleSubmit (e) {
|
||||
e.preventDefault()
|
||||
this.form.validateFieldsAndScroll((err, values) => {
|
||||
if (!err) {
|
||||
console.log('Received values of form: ', values)
|
||||
}
|
||||
})
|
||||
},
|
||||
handleConfirmBlur (e) {
|
||||
const value = e.target.value
|
||||
this.confirmDirty = this.confirmDirty || !!value
|
||||
},
|
||||
compareToFirstPassword (rule, value, callback) {
|
||||
const form = this.form
|
||||
if (value && value !== form.getFieldValue('password')) {
|
||||
callback('Two passwords that you enter is inconsistent!')
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
},
|
||||
validateToNextPassword (rule, value, callback) {
|
||||
const form = this.form
|
||||
if (value && this.confirmDirty) {
|
||||
form.validateFields(['confirm'], { force: true })
|
||||
}
|
||||
callback()
|
||||
},
|
||||
handleWebsiteChange (value) {
|
||||
let autoCompleteResult
|
||||
if (!value) {
|
||||
autoCompleteResult = []
|
||||
} else {
|
||||
autoCompleteResult = ['.com', '.org', '.net'].map(domain => `${value}${domain}`)
|
||||
}
|
||||
this.autoCompleteResult = autoCompleteResult
|
||||
},
|
||||
},
|
||||
|
||||
render () {
|
||||
const { getFieldDecorator } = this.form
|
||||
const { autoCompleteResult } = this
|
||||
|
||||
const formItemLayout = {
|
||||
labelCol: {
|
||||
xs: { span: 24 },
|
||||
sm: { span: 8 },
|
||||
},
|
||||
wrapperCol: {
|
||||
xs: { span: 24 },
|
||||
sm: { span: 16 },
|
||||
},
|
||||
}
|
||||
const tailFormItemLayout = {
|
||||
wrapperCol: {
|
||||
xs: {
|
||||
span: 24,
|
||||
offset: 0,
|
||||
},
|
||||
sm: {
|
||||
span: 16,
|
||||
offset: 8,
|
||||
},
|
||||
},
|
||||
}
|
||||
const prefixSelector = getFieldDecorator('prefix', {
|
||||
initialValue: '86',
|
||||
})(
|
||||
<a-select style={{ width: '70px' }}>
|
||||
<a-select-option value='86'>+86</a-select-option>
|
||||
<a-select-option value='87'>+87</a-select-option>
|
||||
</a-select>
|
||||
)
|
||||
|
||||
const websiteOptions = autoCompleteResult.map(website => (
|
||||
<a-select-option key={website}>{website}</a-select-option>
|
||||
))
|
||||
|
||||
return (
|
||||
<a-form onSubmit={this.handleSubmit}>
|
||||
<a-form-item
|
||||
{...{ props: formItemLayout }}
|
||||
label='E-mail'
|
||||
>
|
||||
{getFieldDecorator('email', {
|
||||
rules: [{
|
||||
type: 'email', message: 'The input is not valid E-mail!',
|
||||
}, {
|
||||
required: true, message: 'Please input your E-mail!',
|
||||
}],
|
||||
})(
|
||||
<a-input />
|
||||
)}
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
{...{ props: formItemLayout }}
|
||||
label='Password'
|
||||
>
|
||||
{getFieldDecorator('password', {
|
||||
rules: [{
|
||||
required: true, message: 'Please input your password!',
|
||||
}, {
|
||||
validator: this.validateToNextPassword,
|
||||
}],
|
||||
})(
|
||||
<a-input type='password' />
|
||||
)}
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
{...{ props: formItemLayout }}
|
||||
label='Confirm Password'
|
||||
>
|
||||
{getFieldDecorator('confirm', {
|
||||
rules: [{
|
||||
required: true, message: 'Please confirm your password!',
|
||||
}, {
|
||||
validator: this.compareToFirstPassword,
|
||||
}],
|
||||
})(
|
||||
<a-input type='password' onBlur={this.handleConfirmBlur} />
|
||||
)}
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
{...{ props: formItemLayout }}
|
||||
label={(
|
||||
<span>
|
||||
Nickname
|
||||
<a-tooltip title='What do you want others to call you?'>
|
||||
<a-icon type='question-circle-o' />
|
||||
</a-tooltip>
|
||||
</span>
|
||||
)}
|
||||
>
|
||||
{getFieldDecorator('nickname', {
|
||||
rules: [{ required: true, message: 'Please input your nickname!', whitespace: true }],
|
||||
})(
|
||||
<a-input />
|
||||
)}
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
{...{ props: formItemLayout }}
|
||||
label='Habitual Residence'
|
||||
>
|
||||
{getFieldDecorator('residence', {
|
||||
initialValue: ['zhejiang', 'hangzhou', 'xihu'],
|
||||
rules: [{ type: 'array', required: true, message: 'Please select your habitual residence!' }],
|
||||
})(
|
||||
<a-cascader options={residences} />
|
||||
)}
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
{...{ props: formItemLayout }}
|
||||
label='Phone Number'
|
||||
>
|
||||
{getFieldDecorator('phone', {
|
||||
rules: [{ required: true, message: 'Please input your phone number!' }],
|
||||
})(
|
||||
<a-input addonBefore={prefixSelector} style={{ width: '100%' }} />
|
||||
)}
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
{...{ props: formItemLayout }}
|
||||
label='Website'
|
||||
>
|
||||
{getFieldDecorator('website', {
|
||||
rules: [{ required: true, message: 'Please input website!' }],
|
||||
})(
|
||||
<a-auto-complete
|
||||
dataSource={websiteOptions}
|
||||
onChange={this.handleWebsiteChange}
|
||||
placeholder='website'
|
||||
>
|
||||
<a-input />
|
||||
</a-auto-complete>
|
||||
)}
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
{...{ props: formItemLayout }}
|
||||
label='Captcha'
|
||||
extra='We must make sure that your are a human.'
|
||||
>
|
||||
<a-row gutter={8}>
|
||||
<a-col span={12}>
|
||||
{getFieldDecorator('captcha', {
|
||||
rules: [{ required: true, message: 'Please input the captcha you got!' }],
|
||||
})(
|
||||
<a-input />
|
||||
)}
|
||||
</a-col>
|
||||
<a-col span={12}>
|
||||
<a-button>Get captcha</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
<a-form-item {...{ props: tailFormItemLayout }}>
|
||||
{getFieldDecorator('agreement', {
|
||||
valuePropName: 'checked',
|
||||
})(
|
||||
<a-checkbox>I have read the <a href=''>agreement</a></a-checkbox>
|
||||
)}
|
||||
</a-form-item>
|
||||
<a-form-item {...{ props: tailFormItemLayout }}>
|
||||
<a-button type='primary' htmlType='submit'>Register</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
export default Form.create()(RegistrationForm)
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
131
components/form/demo/time-related-controls.vue
Normal file
131
components/form/demo/time-related-controls.vue
Normal file
@ -0,0 +1,131 @@
|
||||
<cn>
|
||||
#### 时间类控件
|
||||
时间类组件的 `value` 为 `moment` 类型,所以在提交前需要预处理。
|
||||
</cn>
|
||||
|
||||
<us>
|
||||
#### Time-related Controls
|
||||
the `value` of time-related components is `moment`. So, we need to pre-process those values.
|
||||
</us>
|
||||
|
||||
|
||||
<script>
|
||||
import { Form } from 'vue-antd-ui'
|
||||
|
||||
const TimeRelatedForm = {
|
||||
methods: {
|
||||
handleSubmit (e) {
|
||||
e.preventDefault()
|
||||
|
||||
this.form.validateFields((err, fieldsValue) => {
|
||||
if (err) {
|
||||
return
|
||||
}
|
||||
|
||||
// Should format date value before submit.
|
||||
const rangeValue = fieldsValue['range-picker']
|
||||
const rangeTimeValue = fieldsValue['range-time-picker']
|
||||
const values = {
|
||||
...fieldsValue,
|
||||
'date-picker': fieldsValue['date-picker'].format('YYYY-MM-DD'),
|
||||
'date-time-picker': fieldsValue['date-time-picker'].format('YYYY-MM-DD HH:mm:ss'),
|
||||
'month-picker': fieldsValue['month-picker'].format('YYYY-MM'),
|
||||
'range-picker': [rangeValue[0].format('YYYY-MM-DD'), rangeValue[1].format('YYYY-MM-DD')],
|
||||
'range-time-picker': [
|
||||
rangeTimeValue[0].format('YYYY-MM-DD HH:mm:ss'),
|
||||
rangeTimeValue[1].format('YYYY-MM-DD HH:mm:ss'),
|
||||
],
|
||||
'time-picker': fieldsValue['time-picker'].format('HH:mm:ss'),
|
||||
}
|
||||
console.log('Received values of form: ', values)
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
render () {
|
||||
const { getFieldDecorator } = this.form
|
||||
const formItemLayout = {
|
||||
labelCol: {
|
||||
xs: { span: 24 },
|
||||
sm: { span: 8 },
|
||||
},
|
||||
wrapperCol: {
|
||||
xs: { span: 24 },
|
||||
sm: { span: 16 },
|
||||
},
|
||||
}
|
||||
const config = {
|
||||
rules: [{ type: 'object', required: true, message: 'Please select time!' }],
|
||||
}
|
||||
const rangeConfig = {
|
||||
rules: [{ type: 'array', required: true, message: 'Please select time!' }],
|
||||
}
|
||||
return (
|
||||
<a-form onSubmit={this.handleSubmit}>
|
||||
<a-form-item
|
||||
{...{ props: formItemLayout }}
|
||||
label='DatePicker'
|
||||
>
|
||||
{getFieldDecorator('date-picker', config)(
|
||||
<a-date-picker />
|
||||
)}
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
{...{ props: formItemLayout }}
|
||||
label='DatePicker[showTime]'
|
||||
>
|
||||
{getFieldDecorator('date-time-picker', config)(
|
||||
<a-date-picker showTime format='YYYY-MM-DD HH:mm:ss' />
|
||||
)}
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
{...{ props: formItemLayout }}
|
||||
label='MonthPicker'
|
||||
>
|
||||
{getFieldDecorator('month-picker', config)(
|
||||
<a-monthPicker />
|
||||
)}
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
{...{ props: formItemLayout }}
|
||||
label='RangePicker'
|
||||
>
|
||||
{getFieldDecorator('range-picker', rangeConfig)(
|
||||
<a-range-picker />
|
||||
)}
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
{...{ props: formItemLayout }}
|
||||
label='RangePicker[showTime]'
|
||||
>
|
||||
{getFieldDecorator('range-time-picker', rangeConfig)(
|
||||
<a-range-picker showTime format='YYYY-MM-DD HH:mm:ss' />
|
||||
)}
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
{...{ props: formItemLayout }}
|
||||
label='TimePicker'
|
||||
>
|
||||
{getFieldDecorator('time-picker', config)(
|
||||
<a-time-picker />
|
||||
)}
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
wrapperCol={{
|
||||
xs: { span: 24, offset: 0 },
|
||||
sm: { span: 16, offset: 8 },
|
||||
}}
|
||||
>
|
||||
<a-button type='primary' htmlType='submit'>Submit</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
export default Form.create()(TimeRelatedForm)
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
205
components/form/demo/validate-other.vue
Normal file
205
components/form/demo/validate-other.vue
Normal file
@ -0,0 +1,205 @@
|
||||
<cn>
|
||||
#### 校验其他组件
|
||||
以上演示没有出现的表单控件对应的校验演示。
|
||||
</cn>
|
||||
|
||||
<us>
|
||||
#### Other Form Controls
|
||||
Demostration for validataion configuration for form controls which are not show in the above demos.
|
||||
</us>
|
||||
|
||||
|
||||
<script>
|
||||
import { Form } from 'vue-antd-ui'
|
||||
|
||||
const Demo = {
|
||||
methods: {
|
||||
handleSubmit (e) {
|
||||
e.preventDefault()
|
||||
this.form.validateFields((err, values) => {
|
||||
if (!err) {
|
||||
console.log('Received values of form: ', values)
|
||||
}
|
||||
})
|
||||
},
|
||||
normFile (e) {
|
||||
console.log('Upload event:', e)
|
||||
if (Array.isArray(e)) {
|
||||
return e
|
||||
}
|
||||
return e && e.fileList
|
||||
},
|
||||
},
|
||||
|
||||
render () {
|
||||
const { getFieldDecorator } = this.form
|
||||
const formItemLayout = {
|
||||
labelCol: { span: 6 },
|
||||
wrapperCol: { span: 14 },
|
||||
}
|
||||
return (
|
||||
<a-form id='components-form-demo-validate-other' onSubmit={this.handleSubmit}>
|
||||
<a-form-item
|
||||
{...{ props: formItemLayout }}
|
||||
label='Plain Text'
|
||||
>
|
||||
<span class='ant-form-text'>China</span>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
{...{ props: formItemLayout }}
|
||||
label='Select'
|
||||
hasFeedback
|
||||
>
|
||||
{getFieldDecorator('select', {
|
||||
rules: [
|
||||
{ required: true, message: 'Please select your country!' },
|
||||
],
|
||||
})(
|
||||
<a-select placeholder='Please select a country'>
|
||||
<a-select-option value='china'>China</a-select-option>
|
||||
<a-select-option value='use'>U.S.A</a-select-option>
|
||||
</a-select>
|
||||
)}
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
{...{ props: formItemLayout }}
|
||||
label='Select[multiple]'
|
||||
>
|
||||
{getFieldDecorator('select-multiple', {
|
||||
rules: [
|
||||
{ required: true, message: 'Please select your favourite colors!', type: 'array' },
|
||||
],
|
||||
})(
|
||||
<a-select mode='multiple' placeholder='Please select favourite colors'>
|
||||
<a-select-option value='red'>Red</a-select-option>
|
||||
<a-select-option value='green'>Green</a-select-option>
|
||||
<a-select-option value='blue'>Blue</a-select-option>
|
||||
</a-select>
|
||||
)}
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
{...{ props: formItemLayout }}
|
||||
label='InputNumber'
|
||||
>
|
||||
{getFieldDecorator('input-number', { initialValue: 3 })(
|
||||
<a-input-number min={1} max={10} />
|
||||
)}
|
||||
<span class='ant-form-text'> machines</span>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
{...{ props: formItemLayout }}
|
||||
label='Switch'
|
||||
>
|
||||
{getFieldDecorator('switch', { valuePropName: 'checked' })(
|
||||
<a-switch />
|
||||
)}
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
{...{ props: formItemLayout }}
|
||||
label='Slider'
|
||||
>
|
||||
{getFieldDecorator('slider')(
|
||||
<a-slider marks={{ 0: 'A', 20: 'B', 40: 'C', 60: 'D', 80: 'E', 100: 'F' }} />
|
||||
)}
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
{...{ props: formItemLayout }}
|
||||
label='Radio.Group'
|
||||
>
|
||||
{getFieldDecorator('radio-group')(
|
||||
<a-radio-group>
|
||||
<a-radio value='a'>item 1</a-radio>
|
||||
<a-radio value='b'>item 2</a-radio>
|
||||
<a-radio value='c'>item 3</a-radio>
|
||||
</a-radio-group>
|
||||
)}
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
{...{ props: formItemLayout }}
|
||||
label='Radio.Button'
|
||||
>
|
||||
{getFieldDecorator('radio-button')(
|
||||
<a-radio-group>
|
||||
<a-radio-button value='a'>item 1</a-radio-button>
|
||||
<a-radio-button value='b'>item 2</a-radio-button>
|
||||
<a-radio-button value='c'>item 3</a-radio-button>
|
||||
</a-radio-group>
|
||||
)}
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
{...{ props: formItemLayout }}
|
||||
label='Rate'
|
||||
>
|
||||
{getFieldDecorator('rate', {
|
||||
initialValue: 3.5,
|
||||
})(
|
||||
<a-rate allowHalf/>
|
||||
)}
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
{...{ props: formItemLayout }}
|
||||
label='Upload'
|
||||
extra='longgggggggggggggggggggggggggggggggggg'
|
||||
>
|
||||
{getFieldDecorator('upload', {
|
||||
valuePropName: 'fileList',
|
||||
getValueFromEvent: this.normFile,
|
||||
})(
|
||||
<a-upload name='logo' action='/upload.do' listType='picture'>
|
||||
<a-button>
|
||||
<a-icon type='upload' /> Click to upload
|
||||
</a-button>
|
||||
</a-upload>
|
||||
)}
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
{...{ props: formItemLayout }}
|
||||
label='Dragger'
|
||||
>
|
||||
<div class='dropbox'>
|
||||
{getFieldDecorator('dragger', {
|
||||
valuePropName: 'fileList',
|
||||
getValueFromEvent: this.normFile,
|
||||
})(
|
||||
<a-upload-dragger name='files' action='/upload.do'>
|
||||
<p class='ant-upload-drag-icon'>
|
||||
<a-icon type='inbox' />
|
||||
</p>
|
||||
<p class='ant-upload-text'>Click or drag file to this area to upload</p>
|
||||
<p class='ant-upload-hint'>Support for a single or bulk upload.</p>
|
||||
</a-upload-dragger>
|
||||
)}
|
||||
</div>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
wrapperCol={{ span: 12, offset: 6 }}
|
||||
>
|
||||
<a-button type='primary' htmlType='submit'>Submit</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
export default Form.create()(Demo)
|
||||
</script>
|
||||
<style>
|
||||
#components-form-demo-validate-other .dropbox {
|
||||
height: 180px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
|
178
components/form/demo/validate-static.vue
Normal file
178
components/form/demo/validate-static.vue
Normal file
@ -0,0 +1,178 @@
|
||||
<cn>
|
||||
#### 自定义校验
|
||||
我们提供了 `validateStatus` `help` `hasFeedback` 等属性,你可以不需要使用 `Form.create` 和 `getFieldDecorator`,自己定义校验的时机和内容。
|
||||
1. `validateStatus`: 校验状态,可选 'success', 'warning', 'error', 'validating'。
|
||||
2. `hasFeedback`:用于给输入框添加反馈图标。
|
||||
3. `help`:设置校验文案。
|
||||
</cn>
|
||||
|
||||
<us>
|
||||
#### Customized Validation
|
||||
We provide properties like `validateStatus` `help` `hasFeedback` to customize your own validate status and message, without using `Form.create` and `getFieldDecorator`.
|
||||
1. `validateStatus`: validate status of form components which could be 'success', 'warning', 'error', 'validating'.
|
||||
2. `hasFeedback`: display feed icon of input control
|
||||
3. `help`: display validate message.
|
||||
</us>
|
||||
|
||||
|
||||
<template>
|
||||
<a-form>
|
||||
<a-form-item
|
||||
:labelCol="labelCol"
|
||||
:wrapperCol="wrapperCol"
|
||||
label='Fail'
|
||||
validateStatus='error'
|
||||
help='Should be combination of numbers & alphabets'
|
||||
>
|
||||
<a-input placeholder='unavailable choice' id='error' />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
:labelCol="labelCol"
|
||||
:wrapperCol="wrapperCol"
|
||||
label='Warning'
|
||||
validateStatus='warning'
|
||||
>
|
||||
<a-input placeholder='Warning' id='warning' />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
:labelCol="labelCol"
|
||||
:wrapperCol="wrapperCol"
|
||||
label='Validating'
|
||||
hasFeedback
|
||||
validateStatus='validating'
|
||||
help='The information is being validated...'
|
||||
>
|
||||
<a-input placeholder="I'm the content is being validated" id='validating' />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
:labelCol="labelCol"
|
||||
:wrapperCol="wrapperCol"
|
||||
label='Success'
|
||||
hasFeedback
|
||||
validateStatus='success'
|
||||
>
|
||||
<a-input placeholder="I'm the content" id='success' />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
:labelCol="labelCol"
|
||||
:wrapperCol="wrapperCol"
|
||||
label='Warning'
|
||||
hasFeedback
|
||||
validateStatus='warning'
|
||||
>
|
||||
<a-input placeholder='Warning' id='warning' />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
:labelCol="labelCol"
|
||||
:wrapperCol="wrapperCol"
|
||||
label='Fail'
|
||||
hasFeedback
|
||||
validateStatus='error'
|
||||
help='Should be combination of numbers & alphabets'
|
||||
>
|
||||
<a-input placeholder='unavailable choice' id='error' />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
:labelCol="labelCol"
|
||||
:wrapperCol="wrapperCol"
|
||||
label='Success'
|
||||
hasFeedback
|
||||
validateStatus='success'
|
||||
>
|
||||
<a-date-picker style="width: 100%" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
:labelCol="labelCol"
|
||||
:wrapperCol="wrapperCol"
|
||||
label='Warning'
|
||||
hasFeedback
|
||||
validateStatus='warning'
|
||||
>
|
||||
<a-time-picker style="width: 100%" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
:labelCol="labelCol"
|
||||
:wrapperCol="wrapperCol"
|
||||
label='Error'
|
||||
hasFeedback
|
||||
validateStatus='error'
|
||||
>
|
||||
<a-select defaultValue='1'>
|
||||
<a-select-option value='1'>Option 1</a-select-option>
|
||||
<a-select-option value='2'>Option 2</a-select-option>
|
||||
<a-select-option value='3'>Option 3</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
:labelCol="labelCol"
|
||||
:wrapperCol="wrapperCol"
|
||||
label='Validating'
|
||||
hasFeedback
|
||||
validateStatus='validating'
|
||||
help='The information is being validated...'
|
||||
>
|
||||
<a-cascader :defaultValue="['1']" :options="[]" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
label='inline'
|
||||
:labelCol="labelCol"
|
||||
:wrapperCol="wrapperCol"
|
||||
>
|
||||
<a-col :span="11">
|
||||
<a-form-item validateStatus='error' help='Please select the correct date'>
|
||||
<a-date-picker style="width: 100%"/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="2">
|
||||
<span :style="{ display: 'inline-block', width: '100%', textAlign: 'center' }">
|
||||
-
|
||||
</span>
|
||||
</a-col>
|
||||
<a-col :span="11">
|
||||
<a-form-item>
|
||||
<a-date-picker style="width: 100%"/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
:labelCol="labelCol"
|
||||
:wrapperCol="wrapperCol"
|
||||
label='Success'
|
||||
hasFeedback
|
||||
validateStatus='success'
|
||||
>
|
||||
<a-input-number style="width: 100%" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
labelCol: {
|
||||
xs: { span: 24 },
|
||||
sm: { span: 5 },
|
||||
},
|
||||
wrapperCol: {
|
||||
xs: { span: 24 },
|
||||
sm: { span: 12 },
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
67
components/form/demo/without-form-create.vue
Normal file
67
components/form/demo/without-form-create.vue
Normal file
@ -0,0 +1,67 @@
|
||||
<cn>
|
||||
#### 自行处理表单数据
|
||||
使用 `Form.create` 处理后的表单具有自动收集数据并校验的功能,但如果您不需要这个功能,或者默认的行为无法满足业务需求,可以选择不使用 `Form.create` 并自行处理数据。
|
||||
</cn>
|
||||
|
||||
<us>
|
||||
#### Handle Form Data Manually
|
||||
`Form.create` will collect and validate form data automatically. But if you don't need this feature or the default behaviour cannot satisfy your business, you can drop `Form.create` and handle form data manually.
|
||||
</us>
|
||||
|
||||
|
||||
<template>
|
||||
<a-form>
|
||||
<a-form-item
|
||||
:labelCol="labelCol"
|
||||
:wrapperCol="wrapperCol"
|
||||
label="Prime between 8 & 12"
|
||||
:validateStatus="number.validateStatus"
|
||||
:help="number.errorMsg || tips"
|
||||
>
|
||||
<a-input-number
|
||||
:min="8"
|
||||
:max="12"
|
||||
:value="number.value"
|
||||
@change="handleNumberChange"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
<script>
|
||||
function validatePrimeNumber (number) {
|
||||
if (number === 11) {
|
||||
return {
|
||||
validateStatus: 'success',
|
||||
errorMsg: null,
|
||||
}
|
||||
}
|
||||
return {
|
||||
validateStatus: 'error',
|
||||
errorMsg: 'The prime between 8 and 12 is 11!',
|
||||
}
|
||||
}
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
labelCol: { span: 7 },
|
||||
wrapperCol: { span: 12 },
|
||||
number: {
|
||||
value: 11,
|
||||
},
|
||||
tips: 'A prime is a natural number greater than 1 that has no positive divisors other than 1 and itself.',
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleNumberChange (value) {
|
||||
this.number = {
|
||||
...validatePrimeNumber(value),
|
||||
value,
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
141
components/form/index.en-US.md
Normal file
141
components/form/index.en-US.md
Normal file
@ -0,0 +1,141 @@
|
||||
|
||||
````html
|
||||
<Form.Item {...props}>
|
||||
{children}
|
||||
</Form.Item>
|
||||
````
|
||||
|
||||
## API
|
||||
|
||||
### Form
|
||||
|
||||
| Property | Description | Type | Default Value |
|
||||
| -------- | ----------- | ---- | ------------- |
|
||||
| form | Decorated by `Form.create()` will be automatically set `this.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' |
|
||||
|
||||
### Events
|
||||
| Events Name | Description | Arguments |
|
||||
| --- | --- | --- |
|
||||
| submit | Defines a function will be called if form data validation is successful. | Function(e:Event) |
|
||||
|
||||
### Form.create(options)
|
||||
|
||||
How to use:
|
||||
|
||||
```jsx
|
||||
const CustomizedForm = {}
|
||||
|
||||
CustomizedForm = Form.create({})(CustomizedForm);
|
||||
```
|
||||
|
||||
The following `options` are available:
|
||||
|
||||
| Property | Description | Type |
|
||||
| -------- | ----------- | ---- |
|
||||
| props | declare props on form(和[like vue props]( https://vuejs.org/v2/api/#props)) | {} |
|
||||
| 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.form` property. `this.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. | Function({ [fieldName]: { value: any, errors: [Error] } }) |
|
||||
| setFields | | Function(obj: object) |
|
||||
| setFieldsValue | Set the value of a field. | 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.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.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 `v-model` manually, please use `this.form.setFieldsValue` to change value programmatically.
|
||||
|
||||
#### Special attention
|
||||
|
||||
1. `getFieldDecorator` can not be used to decorate stateless component.
|
||||
2. you can't use `getFieldDecorator` in stateless component: <https://vuejs.org/v2/api/#functional>
|
||||
|
||||
#### 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 can be referenced 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.
|
||||
|
||||
| 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](/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](/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).
|
||||
|
6
components/form/index.jsx
Normal file
6
components/form/index.jsx
Normal file
@ -0,0 +1,6 @@
|
||||
import Form from './Form'
|
||||
|
||||
export { FormProps, FormComponentProps, FormCreateOption, ValidateCallback, ValidationRule } from './Form'
|
||||
export { FormItemProps } from './FormItem'
|
||||
|
||||
export default Form
|
141
components/form/index.zh-CN.md
Normal file
141
components/form/index.zh-CN.md
Normal file
@ -0,0 +1,141 @@
|
||||
|
||||
````html
|
||||
<Form.Item {...props}>
|
||||
{children}
|
||||
</Form.Item>
|
||||
````
|
||||
|
||||
## API
|
||||
|
||||
### Form
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 |
|
||||
| --- | --- | --- | --- |
|
||||
| form | 经 `Form.create()` 包装过的组件会自带 `this.form` 属性,直接传给 Form 即可。无需手动设置 | object | 无 |
|
||||
| hideRequiredMark | 隐藏所有表单项的必选标记 | Boolean | false |
|
||||
| layout | 表单布局 | 'horizontal'\|'vertical'\|'inline' | 'horizontal' |
|
||||
|
||||
### 事件
|
||||
| 事件名称 | 说明 | 回调参数 |
|
||||
| --- | --- | --- |
|
||||
| submit | 数据验证成功后回调事件 | Function(e:Event) |
|
||||
|
||||
### Form.create(options)
|
||||
|
||||
使用方式如下:
|
||||
|
||||
```jsx
|
||||
const CustomizedForm = {}
|
||||
|
||||
CustomizedForm = Form.create({})(CustomizedForm);
|
||||
```
|
||||
|
||||
`options` 的配置项如下。
|
||||
|
||||
| 参数 | 说明 | 类型 |
|
||||
| --- | --- | --- |
|
||||
| props | 父组件需要映射到表单项上的属性声明(和[vue组件props一致]( https://vuejs.org/v2/api/#props)) | {} |
|
||||
| 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.form` 属性,`this.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。 | Function({ [fieldName]: { value: any, errors: [Error] } }) |
|
||||
| setFieldsValue | 设置一组输入控件的值 | Function({ [fieldName]: value } |
|
||||
| validateFields | 校验并获取一组输入域的值与 Error,若 fieldNames 参数为空,则校验全部组件 | Function(\[fieldNames: string\[]], [options: object], callback: Function(errors, values)) |
|
||||
| validateFieldsAndScroll | 与 `validateFields` 相似,但校验完后,如果校验不通过的菜单域不在可见范围内,则自动滚动进可见范围 | 参考 `validateFields` |
|
||||
|
||||
### this.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.form.getFieldDecorator(id, options)
|
||||
|
||||
经过 `getFieldDecorator` 包装的控件,表单控件会自动添加 `value`(或 `valuePropName` 指定的其他属性) `onChange`(或 `trigger` 指定的其他属性),数据同步将被 Form 接管,这会导致以下结果:
|
||||
|
||||
1. 你**不再需要也不应该**用 `onChange` 来做同步,但还是可以继续监听 `onChange` 等事件。
|
||||
2. 你不能用控件的 `value` `defaultValue` 等属性来设置表单域的值,默认值可以用 `getFieldDecorator` 里的 `initialValue`。
|
||||
3. 你不应该用 `v-modle`,可以使用 `this.form.setFieldsValue` 来动态改变表单值。
|
||||
|
||||
#### 特别注意
|
||||
|
||||
1. `getFieldDecorator` 不能用于装饰纯函数组件。
|
||||
2. `getFieldDecorator` 调用不能位于纯函数组件中 <https://cn.vuejs.org/v2/api/#functional>。
|
||||
|
||||
#### getFieldDecorator(id, options) 参数
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 |
|
||||
| --- | --- | --- | --- |
|
||||
| id | 必填输入控件唯一标志。支持嵌套式的写法。 | 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` 无法自动生成。
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 |
|
||||
| --- | --- | --- | --- |
|
||||
| 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](/ant-design/components/grid-cn/#Col) | |
|
||||
| required | 是否必填,如不设置,则会根据校验规则自动生成 | boolean | false |
|
||||
| validateStatus | 校验状态,如不设置,则会根据校验规则自动生成,可选:'success' 'warning' 'error' 'validating' | string | |
|
||||
| wrapperCol | 需要为输入控件设置布局样式时,使用该属性,用法同 labelCol | [object](/ant-design/components/grid-cn/#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)。
|
||||
|
||||
|
5
components/form/style/index.js
Normal file
5
components/form/style/index.js
Normal file
@ -0,0 +1,5 @@
|
||||
import '../../style/index.less'
|
||||
import './index.less'
|
||||
|
||||
// style dependencies
|
||||
import '../../grid/style'
|
624
components/form/style/index.less
Normal file
624
components/form/style/index.less
Normal file
@ -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);
|
||||
}
|
||||
}
|
106
components/form/style/mixin.less
Normal file
106
components/form/style/mixin.less
Normal file
@ -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 Form } from './form'
|
||||
import { default as Form } from './form'
|
||||
|
||||
import { default as Icon } from './icon'
|
||||
|
||||
@ -148,6 +148,8 @@ const components = [
|
||||
Divider,
|
||||
Dropdown,
|
||||
Dropdown.Button,
|
||||
Form,
|
||||
Form.Item,
|
||||
Icon,
|
||||
Input,
|
||||
Input.Group,
|
||||
@ -237,6 +239,7 @@ export {
|
||||
DatePicker,
|
||||
Divider,
|
||||
Dropdown,
|
||||
Form,
|
||||
Icon,
|
||||
Input,
|
||||
InputNumber,
|
||||
|
@ -7,7 +7,10 @@ export const InputNumberProps = {
|
||||
prefixCls: PropTypes.string,
|
||||
min: PropTypes.number,
|
||||
max: PropTypes.number,
|
||||
value: PropTypes.number,
|
||||
value: PropTypes.oneOfType([
|
||||
PropTypes.number,
|
||||
PropTypes.string,
|
||||
]),
|
||||
step: PropTypes.oneOfType([
|
||||
PropTypes.number,
|
||||
PropTypes.string,
|
||||
|
@ -1,8 +1,8 @@
|
||||
|
||||
import classNames from 'classnames'
|
||||
import TextArea from './TextArea'
|
||||
import omit from 'omit.js'
|
||||
import inputProps from './inputProps'
|
||||
import { hasProp, getComponentFromProp } from '../_util/props-util'
|
||||
import { hasProp, getComponentFromProp, getStyle, getClass } from '../_util/props-util'
|
||||
|
||||
function fixControlledValue (value) {
|
||||
if (typeof value === 'undefined' || value === null) {
|
||||
@ -12,6 +12,7 @@ function fixControlledValue (value) {
|
||||
}
|
||||
|
||||
export default {
|
||||
inheritAttrs: false,
|
||||
name: 'AInput',
|
||||
props: {
|
||||
...inputProps,
|
||||
@ -96,10 +97,15 @@ export default {
|
||||
[wrapperClassName]: (addonBefore || addonAfter),
|
||||
}
|
||||
|
||||
const groupClassName = classNames(`${props.prefixCls}-group-wrapper`, {
|
||||
[`${props.prefixCls}-group-wrapper-sm`]: props.size === 'small',
|
||||
[`${props.prefixCls}-group-wrapper-lg`]: props.size === 'large',
|
||||
})
|
||||
if (addonBefore || addonAfter) {
|
||||
return (
|
||||
<span
|
||||
class={`${props.prefixCls}-group-wrapper`}
|
||||
class={groupClassName}
|
||||
style={getStyle(this)}
|
||||
>
|
||||
<span class={className}>
|
||||
{addonBefore}
|
||||
@ -118,7 +124,7 @@ export default {
|
||||
)
|
||||
},
|
||||
renderLabeledIcon (children) {
|
||||
const { prefixCls } = this.$props
|
||||
const { prefixCls, size } = this.$props
|
||||
let prefix = getComponentFromProp(this, 'prefix')
|
||||
let suffix = getComponentFromProp(this, 'suffix')
|
||||
if (!prefix && !suffix) {
|
||||
@ -136,10 +142,14 @@ export default {
|
||||
{suffix}
|
||||
</span>
|
||||
) : null
|
||||
|
||||
const affixWrapperCls = classNames(getClass(this), `${prefixCls}-affix-wrapper`, {
|
||||
[`${prefixCls}-affix-wrapper-sm`]: size === 'small',
|
||||
[`${prefixCls}-affix-wrapper-lg`]: size === 'large',
|
||||
})
|
||||
return (
|
||||
<span
|
||||
class={`${prefixCls}-affix-wrapper`}
|
||||
class={affixWrapperCls}
|
||||
style={getStyle(this)}
|
||||
>
|
||||
{prefix}
|
||||
{children}
|
||||
@ -167,7 +177,7 @@ export default {
|
||||
keydown: handleKeyDown,
|
||||
input: handleChange,
|
||||
},
|
||||
class: getInputClassName(),
|
||||
class: classNames(getInputClassName(), getClass(this)),
|
||||
ref: 'input',
|
||||
}
|
||||
return this.renderLabeledIcon(
|
||||
|
@ -11,7 +11,6 @@ export default {
|
||||
default: 'text',
|
||||
type: String,
|
||||
},
|
||||
id: [String, Number],
|
||||
name: String,
|
||||
size: {
|
||||
validator (value) {
|
||||
|
@ -11,7 +11,7 @@
|
||||
<template>
|
||||
<a-locale-provider :locale="fr_FR">
|
||||
<App />
|
||||
<a-locale-provider>
|
||||
</a-locale-provider>
|
||||
</template>
|
||||
<script>
|
||||
import fr_FR from 'antd/lib/locale-provider/fr_FR';
|
||||
@ -34,7 +34,7 @@ Note: if you need to use antd's UMD dist file, please use `antd/dist/antd-with-l
|
||||
<template>
|
||||
<a-locale-provider :locale="locales.fr_FR">
|
||||
<App />
|
||||
<a-locale-provider>
|
||||
</a-locale-provider>
|
||||
</template>
|
||||
<script>
|
||||
const { LocaleProvider, locales } = window.antd;
|
||||
|
@ -10,7 +10,7 @@ LocaleProvider 使用 Vue 的 [provide/inject](https://cn.vuejs.org/v2/api/#prov
|
||||
<template>
|
||||
<a-locale-provider :locale="zh_CN">
|
||||
<App />
|
||||
<a-locale-provider>
|
||||
</a-locale-provider>
|
||||
</template>
|
||||
<script>
|
||||
import zh_CN from 'antd/lib/locale-provider/zh_CN';
|
||||
@ -33,7 +33,7 @@ export default {
|
||||
<template>
|
||||
<a-locale-provider :locale="locales.en_US">
|
||||
<App />
|
||||
<a-locale-provider>
|
||||
</a-locale-provider>
|
||||
</template>
|
||||
<script>
|
||||
const { LocaleProvider, locales } = window.antd;
|
||||
|
@ -231,6 +231,9 @@ export default {
|
||||
menuProps.on.click = this.handleClick
|
||||
menuProps.props.openTransitionName = menuOpenAnimation
|
||||
} else {
|
||||
menuProps.on.click = (e) => {
|
||||
this.$emit('click', e)
|
||||
}
|
||||
menuProps.props.openAnimation = menuOpenAnimation
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,9 @@
|
||||
import Vue from 'vue'
|
||||
import ConfirmDialog from './ConfirmDialog'
|
||||
|
||||
import antRefDirective from '../_util/antRefDirective'
|
||||
Vue.use(antRefDirective)
|
||||
|
||||
export default function confirm (config) {
|
||||
const div = document.createElement('div')
|
||||
const el = document.createElement('div')
|
||||
|
@ -54,14 +54,14 @@ export default {
|
||||
handleChange (event) {
|
||||
const targetChecked = event.target.checked
|
||||
this.$emit('input', targetChecked)
|
||||
const { name, value, radioGroupContext, stateChecked } = this
|
||||
const { name, value, radioGroupContext } = this
|
||||
if ((!hasProp(this, 'checked') && !radioGroupContext) || (radioGroupContext && radioGroupContext.value === undefined)) {
|
||||
this.stateChecked = targetChecked
|
||||
}
|
||||
const target = {
|
||||
name,
|
||||
value,
|
||||
checked: !stateChecked,
|
||||
checked: targetChecked,
|
||||
}
|
||||
if (this.radioGroupContext) {
|
||||
this.radioGroupContext.handleChange({ target })
|
||||
|
@ -43,3 +43,4 @@ import './transfer/style'
|
||||
import './tree/style'
|
||||
import './upload/style'
|
||||
import './layout/style'
|
||||
import './form/style'
|
||||
|
@ -51,7 +51,7 @@ export default function createTableRow (Component = 'tr') {
|
||||
}
|
||||
|
||||
return (
|
||||
<Component class={className}>
|
||||
<Component class={className} {...{ on: this.$listeners }}>
|
||||
{this.$slots.default}
|
||||
</Component>
|
||||
)
|
||||
|
@ -10,6 +10,8 @@ import Popup from './Popup'
|
||||
import { getAlignFromPlacement, getPopupClassNameFromAlign, noop } from './utils'
|
||||
import BaseMixin from '../_util/BaseMixin'
|
||||
import { cloneElement } from '../_util/vnode'
|
||||
import antRefDirective from '../_util/antRefDirective'
|
||||
Vue.use(antRefDirective)
|
||||
|
||||
function returnEmptyString () {
|
||||
return ''
|
||||
|
@ -3,7 +3,7 @@ import Upload from './Upload'
|
||||
import { UploadProps } from './interface'
|
||||
|
||||
export default {
|
||||
name: 'ADragger',
|
||||
name: 'AUploadDragger',
|
||||
props: UploadProps,
|
||||
render () {
|
||||
const props = getOptionProps(this)
|
||||
|
@ -11,13 +11,13 @@ Classic mode. File selection dialog pops up when upload button is clicked.
|
||||
|
||||
```html
|
||||
<template>
|
||||
<a-dragger name="file" :multiple="true" action="//jsonplaceholder.typicode.com/posts/" @change="handleChange">
|
||||
<a-upload-dragger name="file" :multiple="true" action="//jsonplaceholder.typicode.com/posts/" @change="handleChange">
|
||||
<p class="ant-upload-drag-icon">
|
||||
<a-icon type="inbox" />
|
||||
</p>
|
||||
<p class="ant-upload-text">Click or drag file to this area to upload</p>
|
||||
<p class="ant-upload-hint">Support for a single or bulk upload. Strictly prohibit from uploading company data or other band files</p>
|
||||
</a-dragger>
|
||||
</a-upload-dragger>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
|
1
components/vc-checkbox/index.js
Normal file
1
components/vc-checkbox/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './src/'
|
130
components/vc-checkbox/src/Checkbox.jsx
Normal file
130
components/vc-checkbox/src/Checkbox.jsx
Normal file
@ -0,0 +1,130 @@
|
||||
|
||||
import PropTypes from '../../_util/vue-types'
|
||||
import classNames from 'classnames'
|
||||
import { getOptionProps, hasProp, initDefaultProps } from '../../_util/props-util'
|
||||
import BaseMixin from '../../_util/BaseMixin'
|
||||
|
||||
export default {
|
||||
name: 'Checkbox',
|
||||
mixins: [BaseMixin],
|
||||
props: initDefaultProps({
|
||||
prefixCls: PropTypes.string,
|
||||
name: PropTypes.string,
|
||||
id: PropTypes.string,
|
||||
type: PropTypes.string,
|
||||
defaultChecked: PropTypes.oneOfType([PropTypes.number, PropTypes.bool]),
|
||||
checked: PropTypes.oneOfType([PropTypes.number, PropTypes.bool]),
|
||||
disabled: PropTypes.bool,
|
||||
// onFocus: PropTypes.func,
|
||||
// onBlur: PropTypes.func,
|
||||
// onChange: PropTypes.func,
|
||||
// onClick: PropTypes.func,
|
||||
// tabIndex: PropTypes.string,
|
||||
// readOnly: PropTypes.bool,
|
||||
// autoFocus: PropTypes.bool,
|
||||
value: PropTypes.any,
|
||||
}, {
|
||||
prefixCls: 'rc-checkbox',
|
||||
type: 'checkbox',
|
||||
defaultChecked: false,
|
||||
}),
|
||||
data () {
|
||||
const checked = hasProp(this, 'checked') ? this.checked : this.defaultChecked
|
||||
return {
|
||||
sChecked: checked,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
checked (val) {
|
||||
this.sChecked = val
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
focus () {
|
||||
this.$refs.input.focus()
|
||||
},
|
||||
|
||||
blur () {
|
||||
this.$refs.input.blur()
|
||||
},
|
||||
|
||||
handleChange (e) {
|
||||
const props = getOptionProps(this)
|
||||
if (props.disabled) {
|
||||
return
|
||||
}
|
||||
if (!('checked' in props)) {
|
||||
this.sChecked = e.target.checked
|
||||
}
|
||||
|
||||
props.onChange({
|
||||
target: {
|
||||
...props,
|
||||
checked: e.target.checked,
|
||||
},
|
||||
stopPropagation () {
|
||||
e.stopPropagation()
|
||||
},
|
||||
preventDefault () {
|
||||
e.preventDefault()
|
||||
},
|
||||
nativeEvent: e.nativeEvent,
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
render () {
|
||||
const {
|
||||
prefixCls,
|
||||
name,
|
||||
id,
|
||||
type,
|
||||
disabled,
|
||||
readOnly,
|
||||
tabIndex,
|
||||
onClick,
|
||||
onFocus,
|
||||
onBlur,
|
||||
autoFocus,
|
||||
value,
|
||||
...others
|
||||
} = this
|
||||
|
||||
const globalProps = Object.keys(others).reduce((prev, key) => {
|
||||
if (key.substr(0, 5) === 'aria-' || key.substr(0, 5) === 'data-' || key === 'role') {
|
||||
prev[key] = others[key]
|
||||
}
|
||||
return prev
|
||||
}, {})
|
||||
|
||||
const { checked } = this.state
|
||||
const classString = classNames(prefixCls, {
|
||||
[`${prefixCls}-checked`]: checked,
|
||||
[`${prefixCls}-disabled`]: disabled,
|
||||
})
|
||||
|
||||
return (
|
||||
<span class={classString}>
|
||||
<input
|
||||
name={name}
|
||||
id={id}
|
||||
type={type}
|
||||
readOnly={readOnly}
|
||||
disabled={disabled}
|
||||
tabIndex={tabIndex}
|
||||
class={`${prefixCls}-input`}
|
||||
checked={!!checked}
|
||||
onClick={onClick}
|
||||
onFocus={onFocus}
|
||||
onBlur={onBlur}
|
||||
onChange={this.handleChange}
|
||||
autoFocus={autoFocus}
|
||||
ref={this.saveInput}
|
||||
value={value}
|
||||
{...globalProps}
|
||||
/>
|
||||
<span class={`${prefixCls}-inner`} />
|
||||
</span>
|
||||
)
|
||||
},
|
||||
}
|
3
components/vc-checkbox/src/index.js
Normal file
3
components/vc-checkbox/src/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
import Checkbox from './Checkbox'
|
||||
|
||||
export default Checkbox
|
123
components/vc-form/demo/async-init.js
Normal file
123
components/vc-form/demo/async-init.js
Normal file
@ -0,0 +1,123 @@
|
||||
/* eslint react/no-multi-comp:0, no-console:0 */
|
||||
|
||||
import { createForm } from '../index'
|
||||
import { regionStyle, errorStyle } from './styles'
|
||||
import BaseMixin from '../../_util/BaseMixin'
|
||||
|
||||
const Email = {
|
||||
props: {
|
||||
form: Object,
|
||||
},
|
||||
methods: {
|
||||
checkSpecial (rule, value, callback) {
|
||||
setTimeout(() => {
|
||||
if (value === 'yiminghe@gmail.com') {
|
||||
callback('can not be!')
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}, 1000)
|
||||
},
|
||||
},
|
||||
|
||||
render () {
|
||||
const { getFieldProps, getFieldError, isFieldValidating } = this.form
|
||||
const errors = getFieldError('email')
|
||||
return (<div style={ regionStyle }>
|
||||
<div>email validate onBlur</div>
|
||||
<div>
|
||||
<input {...getFieldProps('email', {
|
||||
initialValue: '',
|
||||
validateFirst: true,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
type: 'email',
|
||||
message: '错误的 email 格式',
|
||||
},
|
||||
this.checkSpecial,
|
||||
],
|
||||
validateTrigger: 'blur',
|
||||
})}
|
||||
/></div>
|
||||
<div style={errorStyle}>
|
||||
{errors ? errors.join(',') : null}
|
||||
</div>
|
||||
<div style={errorStyle}>
|
||||
{isFieldValidating('email') ? 'validating' : null}
|
||||
</div>
|
||||
</div>)
|
||||
},
|
||||
}
|
||||
|
||||
const Form = {
|
||||
mixins: [BaseMixin],
|
||||
props: {
|
||||
form: Object,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
loading: true,
|
||||
}
|
||||
},
|
||||
|
||||
mounted () {
|
||||
setTimeout(() => {
|
||||
this.setState({
|
||||
loading: false,
|
||||
}, () => {
|
||||
setTimeout(() => {
|
||||
this.form.setFieldsInitialValue({
|
||||
email: 'xx@gmail.com',
|
||||
})
|
||||
}, 1000)
|
||||
})
|
||||
}, 1000)
|
||||
},
|
||||
methods: {
|
||||
onSubmit (e) {
|
||||
e.preventDefault()
|
||||
this.form.submit((callback) => {
|
||||
setTimeout(() => {
|
||||
this.form.validateFields((error, values) => {
|
||||
if (!error) {
|
||||
console.log('ok', values)
|
||||
} else {
|
||||
console.log('error', error, values)
|
||||
}
|
||||
callback()
|
||||
})
|
||||
}, 1000)
|
||||
})
|
||||
},
|
||||
|
||||
reset (e) {
|
||||
e.preventDefault()
|
||||
this.form.resetFields()
|
||||
},
|
||||
},
|
||||
|
||||
render () {
|
||||
if (this.loading) {
|
||||
return <b>loading</b>
|
||||
}
|
||||
const { form } = this
|
||||
const disabled = form.isFieldsValidating() || form.isSubmitting()
|
||||
return (<div style={{ margin: 20 }}>
|
||||
<h2>async init field</h2>
|
||||
<form onSubmit={this.onSubmit}>
|
||||
<Email form={ form }/>
|
||||
|
||||
<div style={ regionStyle }>
|
||||
<button disabled={disabled} type='submit'>submit</button>
|
||||
{disabled ? <span style={{ color: 'red' }}>disabled</span> : null}
|
||||
<button disabled={disabled} onClick={this.reset}>reset</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>)
|
||||
},
|
||||
}
|
||||
|
||||
export default createForm()(Form)
|
201
components/vc-form/demo/dynamic-fields.js
Normal file
201
components/vc-form/demo/dynamic-fields.js
Normal file
@ -0,0 +1,201 @@
|
||||
/* eslint react/no-multi-comp:0, no-console:0 */
|
||||
|
||||
import { createForm } from '../index'
|
||||
import BaseMixin from '../../_util/BaseMixin'
|
||||
|
||||
const Form1 = {
|
||||
mixins: [BaseMixin],
|
||||
props: {
|
||||
form: Object,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
useInput: true,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onSubmit (e) {
|
||||
e.preventDefault()
|
||||
this.form.validateFields((error, values) => {
|
||||
if (!error) {
|
||||
console.log('ok', values)
|
||||
} else {
|
||||
console.log('error', error, values)
|
||||
}
|
||||
})
|
||||
},
|
||||
changeUseInput (e) {
|
||||
this.setState({
|
||||
useInput: e.target.checked,
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
render () {
|
||||
const { getFieldError, getFieldDecorator } = this.form
|
||||
|
||||
return (
|
||||
<form onSubmit={this.onSubmit}>
|
||||
<h2>situation 1</h2>
|
||||
{this.useInput ? getFieldDecorator('name', {
|
||||
initialValue: '',
|
||||
rules: [{
|
||||
required: true,
|
||||
message: 'What\'s your name 1?',
|
||||
}],
|
||||
})(<input/>) : null}
|
||||
<span>text content</span>
|
||||
{this.useInput ? null : getFieldDecorator('name', {
|
||||
initialValue: '',
|
||||
rules: [{
|
||||
required: true,
|
||||
message: 'What\'s your name 2?',
|
||||
}],
|
||||
})(<input />)}
|
||||
<div>
|
||||
<label>
|
||||
<input type='checkbox' checked={this.useInput} onInput={this.changeUseInput} />
|
||||
change input
|
||||
</label>
|
||||
{(getFieldError('name') || []).join(', ')}
|
||||
</div>
|
||||
<button>Submit</button>
|
||||
</form>
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
const Form2 = {
|
||||
mixins: [BaseMixin],
|
||||
props: {
|
||||
form: Object,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
useInput: true,
|
||||
}
|
||||
},
|
||||
beforeMount () {
|
||||
const { getFieldDecorator } = this.form
|
||||
this.nameDecorator = getFieldDecorator('name', {
|
||||
initialValue: '',
|
||||
rules: [{
|
||||
required: true,
|
||||
message: 'What\'s your name?',
|
||||
}],
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
onSubmit (e) {
|
||||
e.preventDefault()
|
||||
this.form.validateFields((error, values) => {
|
||||
if (!error) {
|
||||
console.log('ok', values)
|
||||
} else {
|
||||
console.log('error', error, values)
|
||||
}
|
||||
})
|
||||
},
|
||||
changeUseInput (e) {
|
||||
this.setState({
|
||||
useInput: e.target.checked,
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
render () {
|
||||
const { getFieldError } = this.form
|
||||
return (
|
||||
<form onSubmit={this.onSubmit}>
|
||||
<h2>situation 2</h2>
|
||||
{this.useInput ? this.nameDecorator(<input />) : null}
|
||||
<span>text content</span>
|
||||
{this.useInput ? null : this.nameDecorator(<input />)}
|
||||
<div>
|
||||
<label>
|
||||
<input type='checkbox' checked={this.useInput} onInput={this.changeUseInput} />
|
||||
change input
|
||||
</label>
|
||||
{(getFieldError('name') || []).join(', ')}
|
||||
</div>
|
||||
<button>Submit</button>
|
||||
</form>
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
const Form3 = {
|
||||
mixins: [BaseMixin],
|
||||
props: {
|
||||
form: Object,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
useInput: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onSubmit (e) {
|
||||
e.preventDefault()
|
||||
this.form.validateFields((error, values) => {
|
||||
if (!error) {
|
||||
console.log('ok', values)
|
||||
} else {
|
||||
console.log('error', error, values)
|
||||
}
|
||||
})
|
||||
},
|
||||
changeUseInput (e) {
|
||||
this.setState({
|
||||
useInput: e.target.checked,
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
render () {
|
||||
const { getFieldError, getFieldDecorator } = this.form
|
||||
return (
|
||||
<form onSubmit={this.onSubmit}>
|
||||
<h2>situation 3</h2>
|
||||
{getFieldDecorator('name', {
|
||||
initialValue: '',
|
||||
rules: [{
|
||||
required: true,
|
||||
message: 'What\'s your name 1?',
|
||||
}],
|
||||
})(<input />)}
|
||||
{this.useInput ? null : getFieldDecorator('name2', {
|
||||
initialValue: '',
|
||||
rules: [{
|
||||
required: true,
|
||||
message: 'What\'s your name 2?',
|
||||
}],
|
||||
})(<input />)}
|
||||
<div>
|
||||
<label>
|
||||
<input type='checkbox' checked={this.useInput} onInput={this.changeUseInput} />
|
||||
Hide second input
|
||||
</label>
|
||||
{(getFieldError('name') || []).join(', ')}
|
||||
</div>
|
||||
<button>Submit</button>
|
||||
</form>
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
const WrappedForm1 = createForm()(Form1)
|
||||
const WrappedForm2 = createForm()(Form2)
|
||||
const WrappedForm3 = createForm()(Form3)
|
||||
|
||||
export default {
|
||||
render () {
|
||||
return (
|
||||
<div>
|
||||
<WrappedForm1 />
|
||||
<WrappedForm2 />
|
||||
<WrappedForm3 />
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}
|
131
components/vc-form/demo/dynamic.js
Normal file
131
components/vc-form/demo/dynamic.js
Normal file
@ -0,0 +1,131 @@
|
||||
/* eslint react/no-multi-comp:0, no-console:0 */
|
||||
|
||||
import { createForm } from '../index'
|
||||
import { regionStyle, errorStyle } from './styles'
|
||||
|
||||
const Email = {
|
||||
props: {
|
||||
form: Object,
|
||||
hidden: Boolean,
|
||||
},
|
||||
render () {
|
||||
const { hidden, form } = this
|
||||
const { getFieldProps, getFieldError, isFieldValidating } = form
|
||||
const errors = getFieldError('email')
|
||||
const style = {
|
||||
...regionStyle,
|
||||
display: hidden ? 'none' : '',
|
||||
}
|
||||
return (<div style={ style }>
|
||||
<div>email:
|
||||
<input {...getFieldProps('email', {
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
type: 'email',
|
||||
message: '错误的 email 格式',
|
||||
},
|
||||
],
|
||||
hidden,
|
||||
})}
|
||||
/></div>
|
||||
|
||||
{errors ? <div style={errorStyle}>{errors.join(',')}</div> : null}
|
||||
|
||||
{isFieldValidating('email') ? <div style={errorStyle}>validating</div> : null}
|
||||
</div>)
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
const User = {
|
||||
props: {
|
||||
form: Object,
|
||||
},
|
||||
render () {
|
||||
const { getFieldProps, getFieldError, isFieldValidating } = this.form
|
||||
const errors = getFieldError('user')
|
||||
return (<div style={ regionStyle }>
|
||||
<div>user:
|
||||
<input {...getFieldProps('user', {
|
||||
initialValue: 'x',
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
{errors ? <div style={errorStyle}>{errors.join(',')}</div> : null}
|
||||
|
||||
{isFieldValidating('user') ? <div style={errorStyle}>validating</div> : null}
|
||||
</div>)
|
||||
},
|
||||
}
|
||||
|
||||
const Form = {
|
||||
props: {
|
||||
form: Object,
|
||||
},
|
||||
methods: {
|
||||
onSubmit (e) {
|
||||
e.preventDefault()
|
||||
this.form.validateFields((error, values) => {
|
||||
if (!error) {
|
||||
console.log('ok', values)
|
||||
} else {
|
||||
console.log('error', error, values)
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
render () {
|
||||
const { form } = this
|
||||
const { getFieldProps, getFieldValue } = form
|
||||
return (<div style={{ margin: 20 }}>
|
||||
<h2>overview</h2>
|
||||
<form onSubmit={this.onSubmit}>
|
||||
<div style={ regionStyle }>
|
||||
<div>
|
||||
<label>remove/add user:
|
||||
<input
|
||||
type='checkbox'
|
||||
{...getFieldProps('remove_user', {
|
||||
// initialValue:true,
|
||||
valuePropName: 'checked',
|
||||
})}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{ getFieldValue('remove_user') ? null : <User form={ form }/>}
|
||||
|
||||
<div style={ regionStyle }>
|
||||
<div>
|
||||
<label>hide/show email:
|
||||
<input
|
||||
type='checkbox'
|
||||
{...getFieldProps('hide_email', {
|
||||
// initialValue:true,
|
||||
valuePropName: 'checked',
|
||||
})}
|
||||
/></label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Email form={ form } hidden={!!getFieldValue('hide_email')}/>
|
||||
|
||||
<div style={ regionStyle }>
|
||||
<button>submit</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>)
|
||||
},
|
||||
}
|
||||
|
||||
export default createForm()(Form)
|
83
components/vc-form/demo/file-input.js
Normal file
83
components/vc-form/demo/file-input.js
Normal file
@ -0,0 +1,83 @@
|
||||
/* eslint react/no-multi-comp:0, no-console:0 */
|
||||
|
||||
import { createForm } from '../index'
|
||||
import { regionStyle, errorStyle } from './styles'
|
||||
|
||||
function getFileValueProps (value) {
|
||||
if (value && value.target) {
|
||||
return {
|
||||
value: value.target.value,
|
||||
}
|
||||
}
|
||||
return {
|
||||
value,
|
||||
}
|
||||
}
|
||||
|
||||
function getValueFromFileEvent ({ target }) {
|
||||
return {
|
||||
target,
|
||||
}
|
||||
}
|
||||
|
||||
const Form = {
|
||||
props: {
|
||||
form: Object,
|
||||
},
|
||||
methods: {
|
||||
onSubmit (e) {
|
||||
e.preventDefault()
|
||||
this.form.validateFields((error, values) => {
|
||||
console.log(error, values)
|
||||
if (!error) {
|
||||
console.log('校验通过')
|
||||
}
|
||||
})
|
||||
},
|
||||
checkSize (rule, value, callback) {
|
||||
if (value && value.target) {
|
||||
const files = value.target.files
|
||||
if (files[0]) {
|
||||
callback(files[0].size > 1000000 ? 'file size must be less than 1M' : undefined)
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
render () {
|
||||
const { getFieldProps, getFieldError } = this.form
|
||||
const errors = getFieldError('attachment')
|
||||
return (<div
|
||||
style={ regionStyle }
|
||||
>
|
||||
<div>attachment:</div>
|
||||
<div>
|
||||
<input type='file' {...getFieldProps('attachment', {
|
||||
getValueProps: getFileValueProps,
|
||||
getValueFromEvent: getValueFromFileEvent,
|
||||
rules: [this.checkSize],
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
<div style={errorStyle}>
|
||||
{(errors) ? errors.join(',') : null}
|
||||
</div>
|
||||
<button onClick={this.onSubmit}>submit</button>
|
||||
</div>)
|
||||
},
|
||||
}
|
||||
|
||||
const NewForm = createForm()(Form)
|
||||
|
||||
export default {
|
||||
render () {
|
||||
return (<div>
|
||||
<h2>input[type="file"]</h2>
|
||||
<NewForm />
|
||||
</div>)
|
||||
},
|
||||
}
|
55
components/vc-form/demo/getFieldDecorator.js
Normal file
55
components/vc-form/demo/getFieldDecorator.js
Normal file
@ -0,0 +1,55 @@
|
||||
/* eslint react/no-multi-comp:0, no-console:0 */
|
||||
|
||||
import { createForm } from '../index'
|
||||
|
||||
const Form = {
|
||||
props: {
|
||||
form: Object,
|
||||
},
|
||||
|
||||
beforeMount () {
|
||||
this.nameDecorator = this.form.getFieldDecorator('name', {
|
||||
initialValue: '',
|
||||
rules: [{
|
||||
required: true,
|
||||
message: 'What\'s your name?',
|
||||
}],
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
onSubmit (e) {
|
||||
e.preventDefault()
|
||||
this.form.validateFields((error, values) => {
|
||||
if (!error) {
|
||||
console.log('ok', values)
|
||||
} else {
|
||||
console.log('error', error, values)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
onChange (e) {
|
||||
console.log(e.target.value)
|
||||
},
|
||||
},
|
||||
|
||||
render () {
|
||||
const { getFieldError } = this.form
|
||||
|
||||
return (
|
||||
<form onSubmit={this.onSubmit}>
|
||||
{this.nameDecorator(
|
||||
<input
|
||||
onInput={this.onChange}
|
||||
/>
|
||||
)}
|
||||
<div style={{ color: 'red' }}>
|
||||
{(getFieldError('name') || []).join(', ')}
|
||||
</div>
|
||||
<button>Submit</button>
|
||||
</form>
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
export default createForm()(Form)
|
66
components/vc-form/demo/input-array.jsx
Normal file
66
components/vc-form/demo/input-array.jsx
Normal file
@ -0,0 +1,66 @@
|
||||
/* eslint no-console:0 */
|
||||
|
||||
import { createForm } from '../index'
|
||||
import { regionStyle } from './styles'
|
||||
|
||||
let uuid = 0
|
||||
|
||||
const Form = {
|
||||
props: {
|
||||
form: Object,
|
||||
},
|
||||
methods: {
|
||||
remove (k) {
|
||||
const { form } = this
|
||||
// can use data-binding to get
|
||||
let keys = form.getFieldValue('keys')
|
||||
keys = keys.filter((key) => {
|
||||
return key !== k
|
||||
})
|
||||
// can use data-binding to set
|
||||
form.setFieldsValue({
|
||||
keys,
|
||||
})
|
||||
},
|
||||
add () {
|
||||
uuid++
|
||||
const { form } = this
|
||||
// can use data-binding to get
|
||||
let keys = form.getFieldValue('keys')
|
||||
keys = keys.concat(uuid)
|
||||
// can use data-binding to set
|
||||
// important! notify form to detect changes
|
||||
form.setFieldsValue({
|
||||
keys,
|
||||
})
|
||||
},
|
||||
submit (e) {
|
||||
e.preventDefault()
|
||||
console.log(this.form.getFieldsValue())
|
||||
},
|
||||
},
|
||||
|
||||
render () {
|
||||
const { getFieldProps, getFieldValue } = this.form
|
||||
getFieldProps('keys', {
|
||||
initialValue: [],
|
||||
})
|
||||
const inputs = getFieldValue('keys').map((k) => {
|
||||
return (<div key={k} style={ regionStyle }>
|
||||
<input {...getFieldProps(`name${k}`)}/>
|
||||
<a
|
||||
onClick={this.remove.bind(this, k)}
|
||||
>delete</a></div>)
|
||||
})
|
||||
return (<div>
|
||||
{inputs}
|
||||
<div style={ regionStyle }>
|
||||
<button onClick={this.submit}>submit</button>
|
||||
|
||||
<button onClick={this.add}>add</button>
|
||||
</div>
|
||||
</div>)
|
||||
},
|
||||
}
|
||||
|
||||
export default createForm()(Form)
|
84
components/vc-form/demo/modal.js
Normal file
84
components/vc-form/demo/modal.js
Normal file
@ -0,0 +1,84 @@
|
||||
/* eslint react/no-multi-comp:0, no-console:0 */
|
||||
|
||||
import BaseMixin from '../../_util/BaseMixin'
|
||||
import createDOMForm from '../src/createDOMForm'
|
||||
import { Modal } from 'antd'
|
||||
import { regionStyle, errorStyle } from './styles'
|
||||
|
||||
const Form = {
|
||||
mixins: [BaseMixin],
|
||||
props: {
|
||||
form: Object,
|
||||
},
|
||||
|
||||
data () {
|
||||
return {
|
||||
visible: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onSubmit (e) {
|
||||
e.preventDefault()
|
||||
this.form.validateFieldsAndScroll((error, values) => {
|
||||
if (!error) {
|
||||
console.log('ok', values)
|
||||
} else {
|
||||
console.log('error', error, values)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
onCancel () {
|
||||
this.setState({
|
||||
visible: false,
|
||||
})
|
||||
},
|
||||
|
||||
open () {
|
||||
this.setState({
|
||||
visible: true,
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
render () {
|
||||
const { getFieldProps, getFieldError } = this.form
|
||||
return (<div style={{ margin: '20px' }}>
|
||||
<h2>modal</h2>
|
||||
<Modal
|
||||
visible={this.visible}
|
||||
bodyStyle={{
|
||||
height: '200px',
|
||||
overflow: 'auto',
|
||||
}}
|
||||
onCancel={this.onCancel}
|
||||
title='modal'
|
||||
>
|
||||
<div ref='dialogContent'>
|
||||
<form onSubmit={this.onSubmit}>
|
||||
<input
|
||||
{...getFieldProps('required', {
|
||||
rules: [{
|
||||
required: true,
|
||||
message: '必填',
|
||||
}],
|
||||
})}
|
||||
/>
|
||||
<div style={errorStyle}>
|
||||
{getFieldError('required') ? getFieldError('required').join(',')
|
||||
: <b style={{ visibility: 'hidden' }}>1</b>}
|
||||
</div>
|
||||
<div style={{ marginTop: '300px' }}>
|
||||
<button>submit</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</Modal>
|
||||
<div style={ regionStyle }>
|
||||
<button onClick={this.open}>open</button>
|
||||
</div>
|
||||
</div>)
|
||||
},
|
||||
}
|
||||
|
||||
export default createDOMForm()(Form)
|
194
components/vc-form/demo/nested-field.js
Normal file
194
components/vc-form/demo/nested-field.js
Normal file
@ -0,0 +1,194 @@
|
||||
/* eslint react/no-multi-comp:0, no-console:0 */
|
||||
|
||||
import createForm from '../src/createDOMForm'
|
||||
|
||||
const Form = {
|
||||
props: {
|
||||
form: Object,
|
||||
},
|
||||
methods: {
|
||||
onSubmit (e) {
|
||||
e.preventDefault()
|
||||
console.log('Values of member[0].name.firstname and a[0][1].b.c[0]')
|
||||
console.log(this.form.getFieldsValue(['member[0].name.firstname', 'a[0][1].b.c[0]']))
|
||||
console.log('Values of all fields')
|
||||
console.log(this.form.getFieldsValue())
|
||||
|
||||
this.form.validateFieldsAndScroll((error, values) => {
|
||||
if (!error) {
|
||||
console.log('ok', values)
|
||||
} else {
|
||||
console.log('error', error, values)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
onChange (e) {
|
||||
console.log(e.target.value)
|
||||
},
|
||||
|
||||
setField () {
|
||||
this.form.setFieldsValue({
|
||||
member: [
|
||||
{
|
||||
name: {
|
||||
firstname: 'm1 first',
|
||||
lastname: 'm1 last',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: {
|
||||
firstname: 'm2 first',
|
||||
lastname: 'm2 last',
|
||||
},
|
||||
},
|
||||
],
|
||||
a: [
|
||||
[undefined, {
|
||||
b: {
|
||||
c: ['Value of a[0][1].b.c[0]'],
|
||||
},
|
||||
}],
|
||||
],
|
||||
w: {
|
||||
x: {
|
||||
y: {
|
||||
z: ['Value of w.x.y.z[0]'],
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
},
|
||||
|
||||
resetFields () {
|
||||
console.log('reset')
|
||||
this.form.resetFields()
|
||||
},
|
||||
},
|
||||
|
||||
render () {
|
||||
const { getFieldDecorator, getFieldError } = this.form
|
||||
|
||||
return (
|
||||
<form onSubmit={this.onSubmit}>
|
||||
<div>Member 0 firstname</div>
|
||||
{getFieldDecorator('member[0].name.firstname', {
|
||||
initialValue: '',
|
||||
rules: [{
|
||||
required: true,
|
||||
message: 'What\'s the member_0 firstname?',
|
||||
}],
|
||||
})(
|
||||
<input
|
||||
onInput={this.onChange}
|
||||
/>
|
||||
)}
|
||||
<div style={{ color: 'red' }}>
|
||||
{(getFieldError('member[0].name.firstname') || []).join(', ')}
|
||||
</div>
|
||||
|
||||
<div>Member 0 lastname</div>
|
||||
{getFieldDecorator('member[0].name.lastname', {
|
||||
initialValue: '',
|
||||
rules: [{
|
||||
required: true,
|
||||
message: 'What\'s the member_0 lastname?',
|
||||
}],
|
||||
})(
|
||||
<input
|
||||
onInput={this.onChange}
|
||||
/>
|
||||
)}
|
||||
<div style={{ color: 'red' }}>
|
||||
{(getFieldError('member[0].name.firstname') || []).join(', ')}
|
||||
</div>
|
||||
|
||||
<div>Member 1 firstname</div>
|
||||
{getFieldDecorator('member[1].name.firstname', {
|
||||
initialValue: '',
|
||||
rules: [{
|
||||
required: true,
|
||||
message: 'What\'s the member_1 fistname?',
|
||||
}],
|
||||
})(
|
||||
<input
|
||||
onInput={this.onChange}
|
||||
/>
|
||||
)}
|
||||
<div style={{ color: 'red' }}>
|
||||
{(getFieldError('member[1].name.firstname') || []).join(', ')}
|
||||
</div>
|
||||
|
||||
<div>Member 1 lastname</div>
|
||||
{getFieldDecorator('member[1].name.lastname', {
|
||||
initialValue: '',
|
||||
rules: [{
|
||||
required: true,
|
||||
message: 'What\'s the member_1 lastname?',
|
||||
}],
|
||||
})(
|
||||
<input
|
||||
onInput={this.onChange}
|
||||
/>
|
||||
)}
|
||||
<div style={{ color: 'red' }}>
|
||||
{(getFieldError('member[1].name.firstname') || []).join(', ')}
|
||||
</div>
|
||||
|
||||
<div>a[0][1].b.c[0]</div>
|
||||
{getFieldDecorator('a[0][1].b.c[0]', {
|
||||
initialValue: '',
|
||||
rules: [{
|
||||
required: true,
|
||||
message: 'What\'s a[0][1].b.c[0]?',
|
||||
}],
|
||||
})(
|
||||
<input
|
||||
onInput={this.onChange}
|
||||
/>
|
||||
)}
|
||||
<div style={{ color: 'red' }}>
|
||||
{(getFieldError('a[0][1].b.c[0]') || []).join(', ')}
|
||||
</div>
|
||||
|
||||
<div>w.x.y.z[0]</div>
|
||||
{getFieldDecorator('w.x.y.z[0]', {
|
||||
initialValue: '',
|
||||
rules: [{
|
||||
required: true,
|
||||
message: 'What\'s w.x.y.z[0]?',
|
||||
}],
|
||||
})(
|
||||
<input
|
||||
onInput={this.onChange}
|
||||
/>
|
||||
)}
|
||||
<div style={{ color: 'red' }}>
|
||||
{(getFieldError('w.x.y.z[0]') || []).join(', ')}
|
||||
</div>
|
||||
|
||||
<button onClick={this.setField}>Set field</button>
|
||||
<button onClick={this.resetFields}>Reset fields</button>
|
||||
<button>Submit</button>
|
||||
</form>
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
const NewForm = createForm({
|
||||
onFieldsChange (_, changedFields, allFields) {
|
||||
console.log('onFieldsChange: ', changedFields, allFields)
|
||||
},
|
||||
onValuesChange (_, changedValues, allValues) {
|
||||
console.log('onValuesChange: ', changedValues, allValues)
|
||||
},
|
||||
})(Form)
|
||||
|
||||
export default {
|
||||
render () {
|
||||
return (<div>
|
||||
<h2>setFieldsValue</h2>
|
||||
<NewForm />
|
||||
</div>)
|
||||
},
|
||||
}
|
151
components/vc-form/demo/normalize.js
Normal file
151
components/vc-form/demo/normalize.js
Normal file
@ -0,0 +1,151 @@
|
||||
/* eslint react/no-multi-comp:0, no-console:0 */
|
||||
|
||||
import { createForm } from '../index'
|
||||
import { regionStyle, errorStyle } from './styles'
|
||||
|
||||
const CustomInput = {
|
||||
props: {
|
||||
form: Object,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
data: [],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
checkUpper (rule, value = '', callback) {
|
||||
if (value !== value.toUpperCase()) {
|
||||
callback(new Error('need to be upper!'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
},
|
||||
toUpper (v, prev) {
|
||||
if (v === prev) {
|
||||
return v
|
||||
}
|
||||
return v.toUpperCase()
|
||||
},
|
||||
},
|
||||
|
||||
render () {
|
||||
const { getFieldProps, getFieldError } = this.form
|
||||
const errors = getFieldError('upper')
|
||||
return (<div style={ regionStyle }>
|
||||
<div>upper normalize</div>
|
||||
<div>
|
||||
<input {...getFieldProps('upper', {
|
||||
normalize: this.toUpper,
|
||||
rules: [{
|
||||
validator: this.checkUpper,
|
||||
}],
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
<div style={errorStyle}>
|
||||
{(errors) ? errors.join(',') : null}
|
||||
</div>
|
||||
</div>)
|
||||
},
|
||||
}
|
||||
|
||||
const MaxMin = {
|
||||
props: {
|
||||
form: Object,
|
||||
},
|
||||
methods: {
|
||||
normalizeMin (value, prevValue, allValues) {
|
||||
console.log('normalizeMin', allValues.min, allValues.max)
|
||||
const previousAllValues = this.form.getFieldsValue()
|
||||
if (allValues.max !== previousAllValues.max) {
|
||||
// max changed
|
||||
if (value === '' || Number(allValues.max) < Number(value)) {
|
||||
return allValues.max
|
||||
}
|
||||
}
|
||||
return value
|
||||
},
|
||||
normalizeMax (value, prevValue, allValues) {
|
||||
console.log('normalizeMax', allValues.min, allValues.max)
|
||||
const previousAllValues = this.form.getFieldsValue()
|
||||
if (allValues.min !== previousAllValues.min) {
|
||||
// min changed
|
||||
if (value === '' || Number(allValues.min) > Number(value)) {
|
||||
return allValues.min
|
||||
}
|
||||
}
|
||||
return value
|
||||
},
|
||||
},
|
||||
|
||||
render () {
|
||||
const { getFieldProps } = this.form
|
||||
return (<div style={ regionStyle }>
|
||||
<div>min: <select
|
||||
{...getFieldProps('min', {
|
||||
normalize: this.normalizeMin,
|
||||
initialValue: '',
|
||||
})}
|
||||
>
|
||||
<option value=''>empty</option>
|
||||
<option value='1'>1</option>
|
||||
<option value='2'>2</option>
|
||||
<option value='3'>3</option>
|
||||
<option value='4'>4</option>
|
||||
<option value='5'>5</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>max: <select
|
||||
{...getFieldProps('max', {
|
||||
initialValue: '',
|
||||
normalize: this.normalizeMax,
|
||||
})}
|
||||
>
|
||||
<option value=''>empty</option>
|
||||
<option value='1'>1</option>
|
||||
<option value='2'>2</option>
|
||||
<option value='3'>3</option>
|
||||
<option value='4'>4</option>
|
||||
<option value='5'>5</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>)
|
||||
},
|
||||
}
|
||||
|
||||
const Form = {
|
||||
// props: {
|
||||
// form: Object,
|
||||
// },
|
||||
methods: {
|
||||
onSubmit (e) {
|
||||
e.preventDefault()
|
||||
this.form.validateFields((error, values) => {
|
||||
if (!error) {
|
||||
console.log('ok', values)
|
||||
} else {
|
||||
console.log('error', error, values)
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
render () {
|
||||
const { form } = this
|
||||
return (<div style={{ margin: '20px' }}>
|
||||
<h2>normalize</h2>
|
||||
<form onSubmit={this.onSubmit}>
|
||||
<CustomInput form={ form }/>
|
||||
|
||||
<MaxMin form={ form }/>
|
||||
|
||||
<div style={ regionStyle }>
|
||||
<button>submit</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>)
|
||||
},
|
||||
}
|
||||
|
||||
export default createForm()(Form)
|
||||
|
259
components/vc-form/demo/overview.js
Normal file
259
components/vc-form/demo/overview.js
Normal file
@ -0,0 +1,259 @@
|
||||
/* eslint react/no-multi-comp:0, no-console:0 */
|
||||
|
||||
import createDOMForm from '../src/createDOMForm'
|
||||
import { DatePicker, Select } from 'antd'
|
||||
import { regionStyle, errorStyle } from './styles'
|
||||
const { Option } = Select
|
||||
|
||||
const Email = {
|
||||
props: {
|
||||
form: Object,
|
||||
},
|
||||
render () {
|
||||
const { getFieldProps, getFieldError, isFieldValidating } = this.form
|
||||
const errors = getFieldError('email')
|
||||
return (<div style={ regionStyle }>
|
||||
<div>email sync validate</div>
|
||||
<div>
|
||||
<input {...getFieldProps('email', {
|
||||
initialValue: '',
|
||||
rules: [
|
||||
{
|
||||
type: 'email',
|
||||
message: <b key='err'>错误的 email 格式</b>,
|
||||
},
|
||||
],
|
||||
})}
|
||||
/></div>
|
||||
<div style={errorStyle}>
|
||||
{errors}
|
||||
</div>
|
||||
<div style={errorStyle}>
|
||||
{isFieldValidating('email') ? 'validating' : null}
|
||||
</div>
|
||||
</div>)
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
const User = {
|
||||
props: {
|
||||
form: Object,
|
||||
},
|
||||
methods: {
|
||||
userExists (rule, value, callback) {
|
||||
setTimeout(() => {
|
||||
if (value === '1') {
|
||||
callback([new Error('are you kidding?')])
|
||||
} else if (value === 'yiminghe') {
|
||||
callback([new Error('forbid yiminghe')])
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}, 300)
|
||||
},
|
||||
},
|
||||
|
||||
render () {
|
||||
const { getFieldProps, getFieldError, isFieldValidating } = this.form
|
||||
const errors = getFieldError('user')
|
||||
return (<div style={ regionStyle }>
|
||||
<div><span style={{ color: 'red' }}>*</span> user async validate</div>
|
||||
<div>
|
||||
<input {...getFieldProps('user', {
|
||||
initialValue: '',
|
||||
validateFirst: true,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
validator: this.userExists,
|
||||
},
|
||||
],
|
||||
})}
|
||||
/></div>
|
||||
<div style={errorStyle}>
|
||||
{(errors) ? errors.join(',') : null}
|
||||
</div>
|
||||
<div style={errorStyle}>
|
||||
{isFieldValidating('user') ? 'validating' : null}
|
||||
</div>
|
||||
</div>)
|
||||
},
|
||||
}
|
||||
|
||||
const CustomInput = {
|
||||
props: {
|
||||
form: Object,
|
||||
},
|
||||
render () {
|
||||
const { getFieldProps, getFieldError, isFieldValidating } = this.form
|
||||
const errors = getFieldError('select')
|
||||
return (<div style={ regionStyle }>
|
||||
<div><span style={{ color: 'red' }}>*</span> custom select sync validate</div>
|
||||
<div><Select
|
||||
placeholder='please select'
|
||||
style={{ width: '200px' }}
|
||||
{...getFieldProps('select', {
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
})}
|
||||
>
|
||||
<Option value='1'>1</Option>
|
||||
<Option value='2'>2</Option>
|
||||
</Select></div>
|
||||
<div style={errorStyle}>
|
||||
{(errors) ? errors.join(',') : null}
|
||||
</div>
|
||||
<div style={errorStyle}>
|
||||
{isFieldValidating('select') ? 'validating' : null}
|
||||
</div>
|
||||
</div>)
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
const DateInput = {
|
||||
props: {
|
||||
form: Object,
|
||||
},
|
||||
render () {
|
||||
const { getFieldProps, getFieldError } = this.form
|
||||
const errors = getFieldError('date')
|
||||
return (<div style={ regionStyle }>
|
||||
<div><span style={{ color: 'red' }}>*</span> DateInput sync validate</div>
|
||||
<div style={{ width: '200px' }}>
|
||||
<DatePicker
|
||||
placeholder='please select'
|
||||
{...getFieldProps('date', {
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
<div style={errorStyle}>
|
||||
{(errors) ? errors.join(',') : null}
|
||||
</div>
|
||||
</div>)
|
||||
},
|
||||
}
|
||||
|
||||
function toNumber (v) {
|
||||
if (v === undefined) {
|
||||
return v
|
||||
}
|
||||
if (v === '') {
|
||||
return undefined
|
||||
}
|
||||
if (v && v.trim() === '') {
|
||||
return NaN
|
||||
}
|
||||
return Number(v)
|
||||
}
|
||||
|
||||
const NumberInput = {
|
||||
props: {
|
||||
form: Object,
|
||||
},
|
||||
render () {
|
||||
const { getFieldProps, getFieldError } = this.form
|
||||
const errors = getFieldError('number')
|
||||
return (<div style={ regionStyle }>
|
||||
<div>number input</div>
|
||||
<div>
|
||||
<input
|
||||
{...getFieldProps('number', {
|
||||
initialValue: '1',
|
||||
rules: [{
|
||||
transform: toNumber,
|
||||
type: 'number',
|
||||
}],
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
<div style={errorStyle}>
|
||||
{(errors) ? errors.join(',') : null}
|
||||
</div>
|
||||
</div>)
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
const Form = {
|
||||
methods: {
|
||||
onSubmit (e) {
|
||||
console.log('submit')
|
||||
e.preventDefault()
|
||||
this.form.validateFieldsAndScroll({ scroll: { offsetTop: 20 }}, (error, values) => {
|
||||
if (!error) {
|
||||
console.log('ok', values)
|
||||
} else {
|
||||
console.log('error', error, values)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
reset (e) {
|
||||
e.preventDefault()
|
||||
this.form.resetFields()
|
||||
},
|
||||
},
|
||||
|
||||
render () {
|
||||
const { form } = this
|
||||
const { getFieldProps, getFieldError } = form
|
||||
return (<div style={{ margin: '20px' }}>
|
||||
<h2>overview</h2>
|
||||
<form onSubmit={this.onSubmit}>
|
||||
<User form={ form } saveRef={this.saveRef}/>
|
||||
|
||||
<NumberInput form={ form }/>
|
||||
|
||||
<Email form={ form }/>
|
||||
|
||||
<CustomInput form={ form }/>
|
||||
|
||||
<DateInput form={ form }/>
|
||||
|
||||
<div style={ regionStyle }>
|
||||
<div>normal required input</div>
|
||||
<div>
|
||||
<input
|
||||
{...getFieldProps('normal', {
|
||||
initialValue: '',
|
||||
rules: [{
|
||||
required: true,
|
||||
}],
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
<div style={errorStyle}>
|
||||
{(getFieldError('normal')) ? getFieldError('normal').join(',') : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={ regionStyle }>
|
||||
<button onClick={this.reset}>reset</button>
|
||||
|
||||
<input type='submit' value='submit'/>
|
||||
</div>
|
||||
</form>
|
||||
</div>)
|
||||
},
|
||||
}
|
||||
|
||||
export default createDOMForm({
|
||||
validateMessages: {
|
||||
required (field) {
|
||||
return `${field} 必填`
|
||||
},
|
||||
},
|
||||
})(Form)
|
85
components/vc-form/demo/parallel-form.js
Normal file
85
components/vc-form/demo/parallel-form.js
Normal file
@ -0,0 +1,85 @@
|
||||
/* eslint react/no-multi-comp:0, no-console:0 */
|
||||
|
||||
import { createForm } from '../index'
|
||||
import { regionStyle } from './styles'
|
||||
import { Switch } from 'antd'
|
||||
|
||||
const TopForm = {
|
||||
props: {
|
||||
form: Object,
|
||||
},
|
||||
render () {
|
||||
const { getFieldProps } = this.form
|
||||
return (<div style={ regionStyle }>
|
||||
<div>has email? </div>
|
||||
<div>
|
||||
<Switch {...getFieldProps('on', {
|
||||
initialValue: true,
|
||||
valuePropName: 'checked',
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
</div>)
|
||||
},
|
||||
}
|
||||
|
||||
const BottomForm = {
|
||||
props: {
|
||||
form: Object,
|
||||
on: Boolean,
|
||||
},
|
||||
render () {
|
||||
const { form } = this
|
||||
const on = form.getFieldValue('on')
|
||||
const style = {
|
||||
...regionStyle,
|
||||
display: on ? 'block' : 'none',
|
||||
}
|
||||
return (<div style={ style }>
|
||||
<div>email: </div>
|
||||
<div>
|
||||
<input {...form.getFieldProps('email', {
|
||||
rules: [{
|
||||
type: 'email',
|
||||
}],
|
||||
hidden: !on,
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
</div>)
|
||||
},
|
||||
}
|
||||
|
||||
const Form = {
|
||||
props: {
|
||||
form: Object,
|
||||
},
|
||||
methods: {
|
||||
onSubmit (e) {
|
||||
e.preventDefault()
|
||||
console.log(this.form.getFieldsValue())
|
||||
},
|
||||
},
|
||||
|
||||
render () {
|
||||
const { form } = this
|
||||
return (<div>
|
||||
<TopForm form={ form }/>
|
||||
<BottomForm form={ form }/>
|
||||
<div style={ regionStyle }>
|
||||
<button onClick={this.onSubmit}>submit</button>
|
||||
</div>
|
||||
</div>)
|
||||
},
|
||||
}
|
||||
|
||||
const NewForm = createForm()(Form)
|
||||
|
||||
export default {
|
||||
render () {
|
||||
return (<div>
|
||||
<h2>parallel form</h2>
|
||||
<NewForm />
|
||||
</div>)
|
||||
},
|
||||
}
|
91
components/vc-form/demo/start-end-date.js
Normal file
91
components/vc-form/demo/start-end-date.js
Normal file
@ -0,0 +1,91 @@
|
||||
/* eslint no-console:0 */
|
||||
|
||||
import { DatePicker } from 'antd'
|
||||
import createDOMForm from '../src/createDOMForm'
|
||||
import { regionStyle, errorStyle } from './styles'
|
||||
|
||||
const Form = {
|
||||
props: {
|
||||
form: Object,
|
||||
},
|
||||
methods: {
|
||||
onSubmit (e) {
|
||||
console.log('submit')
|
||||
e.preventDefault()
|
||||
this.form.validateFieldsAndScroll((error, values) => {
|
||||
if (!error) {
|
||||
console.log('ok', values)
|
||||
} else {
|
||||
console.log('error', error, values)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
reset (e) {
|
||||
e.preventDefault()
|
||||
this.form.resetFields()
|
||||
},
|
||||
|
||||
checkStart (rule, value, callback) {
|
||||
const { validateFields } = this.form
|
||||
validateFields(['end'], {
|
||||
force: true,
|
||||
})
|
||||
callback()
|
||||
},
|
||||
|
||||
checkEnd (rule, value, callback) {
|
||||
const end = value
|
||||
const { getFieldValue } = this.form
|
||||
const start = getFieldValue('start')
|
||||
if (!end || !start) {
|
||||
callback('please select both start and end time')
|
||||
} else if (end.valueOf() < start.valueOf()) {
|
||||
callback('start time should be less than end time')
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
render () {
|
||||
const { form } = this
|
||||
const { getFieldProps, getFieldError } = form
|
||||
return (<div style={{ margin: 20 }}>
|
||||
<h2>startTime and endTime validation</h2>
|
||||
<form onSubmit={this.onSubmit}>
|
||||
<div style={ regionStyle }>
|
||||
<div>start: </div>
|
||||
<div>
|
||||
<DatePicker {...getFieldProps('start', {
|
||||
rules: [this.checkStart],
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={ regionStyle }>
|
||||
<div>end: </div>
|
||||
<div>
|
||||
<DatePicker {...getFieldProps('end', {
|
||||
rules: [this.checkEnd],
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={errorStyle}>
|
||||
{getFieldError('end') ? getFieldError('end').join(',') : ''}
|
||||
</div>
|
||||
|
||||
<div style={ regionStyle }>
|
||||
<button onClick={this.reset}>reset</button>
|
||||
|
||||
<input type='submit' value='submit'/>
|
||||
</div>
|
||||
</form>
|
||||
</div>)
|
||||
},
|
||||
}
|
||||
|
||||
export default createDOMForm()(Form)
|
11
components/vc-form/demo/styles.js
Normal file
11
components/vc-form/demo/styles.js
Normal file
@ -0,0 +1,11 @@
|
||||
export const regionStyle = {
|
||||
border: '1px solid red',
|
||||
marginTop: '10px',
|
||||
padding: '10px',
|
||||
}
|
||||
|
||||
export const errorStyle = {
|
||||
color: 'red',
|
||||
marginTop: '10px',
|
||||
padding: '10px',
|
||||
}
|
120
components/vc-form/demo/suggest.js
Normal file
120
components/vc-form/demo/suggest.js
Normal file
@ -0,0 +1,120 @@
|
||||
/* eslint react/no-multi-comp:0, no-console:0 */
|
||||
|
||||
import { createForm } from '../index'
|
||||
import { Select } from 'antd'
|
||||
import { regionStyle, errorStyle } from './styles'
|
||||
import { mergeProps } from '../../_util/props-util'
|
||||
const emailTpl = ['@gmail.com', '@outlook.com', '@qq.com']
|
||||
const { Option } = Select
|
||||
const CustomInput = {
|
||||
props: {
|
||||
form: Object,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
data: [],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onChange (v) {
|
||||
if (v.indexOf('@') === -1) {
|
||||
this.data = emailTpl.map(m => v + m)
|
||||
} else if (this.data.length) {
|
||||
this.data = []
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
render () {
|
||||
const { getFieldProps, getFieldError, isFieldValidating } = this.form
|
||||
const errors = getFieldError('select')
|
||||
return (<div style={ regionStyle }>
|
||||
<div>custom select sync validate</div>
|
||||
<div>
|
||||
<Select
|
||||
{
|
||||
...mergeProps(
|
||||
{
|
||||
props: {
|
||||
placeholder: 'please select',
|
||||
mode: 'combobox',
|
||||
filterOption: false,
|
||||
},
|
||||
style: {
|
||||
width: '200px',
|
||||
},
|
||||
}, getFieldProps('select', {
|
||||
change: this.onChange,
|
||||
rules: [
|
||||
{
|
||||
type: 'email',
|
||||
},
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
}))
|
||||
}
|
||||
>
|
||||
{this.data.map((d) => {
|
||||
return <Option key={d} value={d}>{d}</Option>
|
||||
})}
|
||||
</Select>
|
||||
</div>
|
||||
<div style={errorStyle}>
|
||||
{(errors) ? errors.join(',')
|
||||
: <b
|
||||
style={{
|
||||
visibility: 'hidden',
|
||||
}}
|
||||
>
|
||||
1
|
||||
</b>}
|
||||
</div>
|
||||
<div style={errorStyle}>
|
||||
{isFieldValidating('select') ? 'validating' : <b
|
||||
style={{
|
||||
visibility: 'hidden',
|
||||
}}
|
||||
>
|
||||
1
|
||||
</b>}
|
||||
</div>
|
||||
</div>)
|
||||
},
|
||||
}
|
||||
|
||||
const Form = {
|
||||
props: {
|
||||
form: Object,
|
||||
},
|
||||
methods: {
|
||||
onSubmit (e) {
|
||||
e.preventDefault()
|
||||
this.form.validateFields((error, values) => {
|
||||
if (!error) {
|
||||
console.log('ok', values)
|
||||
} else {
|
||||
console.log('error', error, values)
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
render () {
|
||||
const { form } = this
|
||||
return (<div style={{ margin: '20px' }}>
|
||||
<h2>suggest</h2>
|
||||
<form onSubmit={this.onSubmit}>
|
||||
<CustomInput form={ form }/>
|
||||
|
||||
<div style={ regionStyle }>
|
||||
<button>submit</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>)
|
||||
},
|
||||
}
|
||||
|
||||
export default createForm()(Form)
|
||||
|
132
components/vc-form/demo/validateFirst.js
Normal file
132
components/vc-form/demo/validateFirst.js
Normal file
@ -0,0 +1,132 @@
|
||||
/* eslint react/no-multi-comp:0, no-console:0 */
|
||||
|
||||
import { createForm } from '../index'
|
||||
import { regionStyle, errorStyle } from './styles'
|
||||
|
||||
const Email = {
|
||||
props: {
|
||||
form: Object,
|
||||
},
|
||||
render () {
|
||||
const { getFieldProps, getFieldError, isFieldValidating } = this.form
|
||||
const errors = getFieldError('email')
|
||||
return (<div style={ regionStyle }>
|
||||
<div>email sync validate</div>
|
||||
<div>
|
||||
<input {...getFieldProps('email', {
|
||||
initialValue: '',
|
||||
validateFirst: true,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
type: 'email',
|
||||
message: '错误的 email 格式',
|
||||
},
|
||||
],
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
<div style={errorStyle}>
|
||||
{errors ? errors.join(',') : null}
|
||||
</div>
|
||||
<div style={errorStyle}>
|
||||
{isFieldValidating('email') ? 'validating' : null}
|
||||
</div>
|
||||
</div>)
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
const User = {
|
||||
props: {
|
||||
form: Object,
|
||||
},
|
||||
methods: {
|
||||
userExists (rule, value, callback) {
|
||||
setTimeout(() => {
|
||||
if (value === '1') {
|
||||
callback([new Error('are you kidding?')])
|
||||
} else if (value === 'yiminghe') {
|
||||
callback([new Error('forbid yiminghe')])
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}, 300)
|
||||
},
|
||||
},
|
||||
|
||||
render () {
|
||||
const { getFieldProps, getFieldError, isFieldValidating } = this.form
|
||||
const errors = getFieldError('user')
|
||||
return (<div style={ regionStyle }>
|
||||
<div>user async validate</div>
|
||||
<div>
|
||||
<input {...getFieldProps('user', {
|
||||
initialValue: '',
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
min: 2,
|
||||
},
|
||||
{
|
||||
validator: this.userExists,
|
||||
},
|
||||
],
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
<div style={errorStyle}>
|
||||
{(errors) ? errors.join(',') : null}
|
||||
</div>
|
||||
<div style={errorStyle}>
|
||||
{isFieldValidating('user') ? 'validating' : null}
|
||||
</div>
|
||||
</div>)
|
||||
},
|
||||
}
|
||||
|
||||
const Form = {
|
||||
methods: {
|
||||
onSubmit (e) {
|
||||
console.log('submit')
|
||||
e.preventDefault()
|
||||
this.form.validateFields({
|
||||
// firstFields: false,
|
||||
}, (error, values) => {
|
||||
if (!error) {
|
||||
console.log('ok', values)
|
||||
} else {
|
||||
console.log('error', error, values)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
reset (e) {
|
||||
e.preventDefault()
|
||||
this.form.resetFields()
|
||||
},
|
||||
},
|
||||
|
||||
render () {
|
||||
const { form } = this
|
||||
return (<div style={{ margin: '20px' }}>
|
||||
<h2>validateFirst</h2>
|
||||
<form onSubmit={this.onSubmit}>
|
||||
<User form={ form }/>
|
||||
|
||||
<Email form={ form }/>
|
||||
|
||||
<div style={ regionStyle }>
|
||||
<button onClick={this.reset}>reset</button>
|
||||
|
||||
<input type='submit' value='submit'/>
|
||||
</div>
|
||||
</form>
|
||||
</div>)
|
||||
},
|
||||
}
|
||||
|
||||
export default createForm()(Form)
|
||||
|
112
components/vc-form/demo/validateTrigger.js
Normal file
112
components/vc-form/demo/validateTrigger.js
Normal file
@ -0,0 +1,112 @@
|
||||
/* eslint react/no-multi-comp:0, no-console:0 */
|
||||
|
||||
import { createForm } from '../index'
|
||||
import { regionStyle, errorStyle } from './styles'
|
||||
|
||||
const Email = {
|
||||
props: {
|
||||
form: Object,
|
||||
},
|
||||
render () {
|
||||
const { getFieldProps, getFieldError, isFieldValidating } = this.form
|
||||
const errors = getFieldError('email')
|
||||
return (<div style={ regionStyle }>
|
||||
<div>email validate onBlur && onChange</div>
|
||||
<div>
|
||||
<input {...getFieldProps('email', {
|
||||
validate: [{
|
||||
trigger: 'blur',
|
||||
rules: [{
|
||||
required: true,
|
||||
}],
|
||||
}, {
|
||||
trigger: ['blur', 'input'],
|
||||
rules: [{
|
||||
type: 'email',
|
||||
message: '错误的 email 格式',
|
||||
}],
|
||||
}],
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
<div style={errorStyle}>
|
||||
{errors ? errors.join(',') : null}
|
||||
</div>
|
||||
<div style={errorStyle}>
|
||||
{isFieldValidating('email') ? 'validating' : null}
|
||||
</div>
|
||||
</div>)
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
const User = {
|
||||
props: {
|
||||
form: Object,
|
||||
},
|
||||
render () {
|
||||
const { getFieldProps, getFieldError, isFieldValidating } = this.form
|
||||
const errors = getFieldError('user')
|
||||
return (<div style={ regionStyle }>
|
||||
<div>user validate on submit</div>
|
||||
<div>
|
||||
<input {...getFieldProps('user', {
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
min: 5,
|
||||
},
|
||||
],
|
||||
validateTrigger: null,
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
<div style={errorStyle}>
|
||||
{(errors) ? errors.join(',') : null}
|
||||
</div>
|
||||
<div style={errorStyle}>
|
||||
{isFieldValidating('user') ? 'validating' : null}
|
||||
</div>
|
||||
</div>)
|
||||
},
|
||||
}
|
||||
|
||||
const Form = {
|
||||
props: {
|
||||
form: Object,
|
||||
},
|
||||
methods: {
|
||||
onSubmit (e) {
|
||||
e.preventDefault()
|
||||
this.form.validateFields((error, values) => {
|
||||
if (!error) {
|
||||
console.log('ok', values)
|
||||
} else {
|
||||
console.log('error', error, values)
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
render () {
|
||||
const { form } = this
|
||||
return (<div style={{ margin: '20px' }}>
|
||||
<h2>use validateTrigger config</h2>
|
||||
<form onSubmit={this.onSubmit}>
|
||||
<User form={ form }/>
|
||||
|
||||
<Email form={ form }/>
|
||||
|
||||
<div style={ regionStyle }>
|
||||
<button>submit</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>)
|
||||
},
|
||||
}
|
||||
|
||||
export default createForm()(Form)
|
||||
|
3
components/vc-form/index.js
Normal file
3
components/vc-form/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
// export this package's api
|
||||
import { createForm, createFormField } from './src/'
|
||||
export { createForm, createFormField }
|
589
components/vc-form/src/createBaseForm.jsx
Normal file
589
components/vc-form/src/createBaseForm.jsx
Normal file
@ -0,0 +1,589 @@
|
||||
import AsyncValidator from 'async-validator'
|
||||
import warning from 'warning'
|
||||
import get from 'lodash/get'
|
||||
import set from 'lodash/set'
|
||||
import omit from 'lodash/omit'
|
||||
import createFieldsStore from './createFieldsStore'
|
||||
import { cloneElement } from '../../_util/vnode'
|
||||
import BaseMixin from '../../_util/BaseMixin'
|
||||
import { getOptionProps, getEvents } from '../../_util/props-util'
|
||||
import PropTypes from '../../_util/vue-types'
|
||||
|
||||
import {
|
||||
argumentContainer,
|
||||
identity,
|
||||
normalizeValidateRules,
|
||||
getValidateTriggers,
|
||||
getValueFromEvent,
|
||||
hasRules,
|
||||
getParams,
|
||||
isEmptyObject,
|
||||
flattenArray,
|
||||
} from './utils'
|
||||
|
||||
const DEFAULT_TRIGGER = 'change'
|
||||
|
||||
function createBaseForm (option = {}, mixins = []) {
|
||||
const {
|
||||
validateMessages,
|
||||
onFieldsChange,
|
||||
onValuesChange,
|
||||
mapProps = identity,
|
||||
mapPropsToFields,
|
||||
fieldNameProp,
|
||||
fieldMetaProp,
|
||||
fieldDataProp,
|
||||
formPropName = 'form',
|
||||
props = {},
|
||||
} = option
|
||||
|
||||
return function decorate (WrappedComponent) {
|
||||
let formProps = {}
|
||||
if (Array.isArray(props)) {
|
||||
props.forEach((prop) => {
|
||||
formProps[prop] = PropTypes.any
|
||||
})
|
||||
} else {
|
||||
formProps = props
|
||||
}
|
||||
const Form = {
|
||||
mixins: [BaseMixin, ...mixins],
|
||||
props: {
|
||||
...formProps,
|
||||
wrappedComponentRef: PropTypes.func.def(() => {}),
|
||||
},
|
||||
data () {
|
||||
const fields = mapPropsToFields && mapPropsToFields(this.$props)
|
||||
this.fieldsStore = createFieldsStore(fields || {})
|
||||
|
||||
this.instances = {}
|
||||
this.cachedBind = {}
|
||||
this.clearedFieldMetaCache = {};
|
||||
// HACK: https://github.com/ant-design/ant-design/issues/6406
|
||||
['getFieldsValue',
|
||||
'getFieldValue',
|
||||
'setFieldsInitialValue',
|
||||
'getFieldsError',
|
||||
'getFieldError',
|
||||
'isFieldValidating',
|
||||
'isFieldsValidating',
|
||||
'isFieldsTouched',
|
||||
'isFieldTouched'].forEach(key => {
|
||||
this[key] = (...args) => {
|
||||
return this.fieldsStore[key](...args)
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
submitting: false,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'$props': {
|
||||
handler: function (nextProps) {
|
||||
if (mapPropsToFields) {
|
||||
this.fieldsStore.updateFields(mapPropsToFields(nextProps))
|
||||
}
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
mounted () {
|
||||
this.wrappedComponentRef(this.$refs.WrappedComponent)
|
||||
},
|
||||
methods: {
|
||||
onCollectCommon (name, action, args) {
|
||||
const fieldMeta = this.fieldsStore.getFieldMeta(name)
|
||||
if (fieldMeta[action]) {
|
||||
fieldMeta[action](...args)
|
||||
} else if (fieldMeta.originalProps && fieldMeta.originalProps[action]) {
|
||||
fieldMeta.originalProps[action](...args)
|
||||
}
|
||||
const value = fieldMeta.getValueFromEvent
|
||||
? fieldMeta.getValueFromEvent(...args)
|
||||
: getValueFromEvent(...args)
|
||||
if (onValuesChange && value !== this.fieldsStore.getFieldValue(name)) {
|
||||
const valuesAll = this.fieldsStore.getAllValues()
|
||||
const valuesAllSet = {}
|
||||
valuesAll[name] = value
|
||||
Object.keys(valuesAll).forEach(key => set(valuesAllSet, key, valuesAll[key]))
|
||||
onValuesChange(this, set({}, name, value), valuesAllSet)
|
||||
}
|
||||
const field = this.fieldsStore.getField(name)
|
||||
return ({ name, field: { ...field, value, touched: true }, fieldMeta })
|
||||
},
|
||||
|
||||
onCollect (name_, action, ...args) {
|
||||
const { name, field, fieldMeta } = this.onCollectCommon(name_, action, args)
|
||||
const { validate } = fieldMeta
|
||||
const newField = {
|
||||
...field,
|
||||
dirty: hasRules(validate),
|
||||
}
|
||||
this.setFields({
|
||||
[name]: newField,
|
||||
})
|
||||
},
|
||||
|
||||
onCollectValidate (name_, action, ...args) {
|
||||
const { field, fieldMeta } = this.onCollectCommon(name_, action, args)
|
||||
const newField = {
|
||||
...field,
|
||||
dirty: true,
|
||||
}
|
||||
this.validateFieldsInternal([newField], {
|
||||
action,
|
||||
options: {
|
||||
firstFields: !!fieldMeta.validateFirst,
|
||||
},
|
||||
})
|
||||
},
|
||||
|
||||
getCacheBind (name, action, fn) {
|
||||
if (!this.cachedBind[name]) {
|
||||
this.cachedBind[name] = {}
|
||||
}
|
||||
const cache = this.cachedBind[name]
|
||||
if (!cache[action]) {
|
||||
cache[action] = fn.bind(this, name, action)
|
||||
}
|
||||
return cache[action]
|
||||
},
|
||||
|
||||
recoverClearedField (name) {
|
||||
if (this.clearedFieldMetaCache[name]) {
|
||||
this.fieldsStore.setFields({
|
||||
[name]: this.clearedFieldMetaCache[name].field,
|
||||
})
|
||||
this.fieldsStore.setFieldMeta(name, this.clearedFieldMetaCache[name].meta)
|
||||
delete this.clearedFieldMetaCache[name]
|
||||
}
|
||||
},
|
||||
|
||||
getFieldDecorator (name, fieldOption) {
|
||||
const { props, ...restProps } = this.getFieldProps(name, fieldOption)
|
||||
return (fieldElem) => {
|
||||
const fieldMeta = this.fieldsStore.getFieldMeta(name)
|
||||
const originalProps = getOptionProps(fieldElem)
|
||||
const originalEvents = getEvents(fieldElem)
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
const valuePropName = fieldMeta.valuePropName
|
||||
warning(
|
||||
!(valuePropName in originalProps),
|
||||
`\`getFieldDecorator\` will override \`${valuePropName}\`, ` +
|
||||
`so please don't set \`${valuePropName}\` directly ` +
|
||||
`and use \`setFieldsValue\` to set it.`
|
||||
)
|
||||
const defaultValuePropName =
|
||||
`default${valuePropName[0].toUpperCase()}${valuePropName.slice(1)}`
|
||||
warning(
|
||||
!(defaultValuePropName in originalProps),
|
||||
`\`${defaultValuePropName}\` is invalid ` +
|
||||
`for \`getFieldDecorator\` will set \`${valuePropName}\`,` +
|
||||
` please use \`option.initialValue\` instead.`
|
||||
)
|
||||
}
|
||||
fieldMeta.originalProps = originalProps
|
||||
// fieldMeta.ref = fieldElem.data && fieldElem.data.ref
|
||||
const newProps = {
|
||||
props: {
|
||||
...props,
|
||||
...this.fieldsStore.getFieldValuePropValue(fieldMeta),
|
||||
},
|
||||
...restProps,
|
||||
}
|
||||
newProps.domProps.value = newProps.props.value
|
||||
const newEvents = {}
|
||||
Object.keys(newProps.on).forEach((key) => {
|
||||
if (originalEvents[key]) {
|
||||
const triggerEvents = newProps.on[key]
|
||||
newEvents[key] = (...args) => {
|
||||
originalEvents[key](...args)
|
||||
triggerEvents(...args)
|
||||
}
|
||||
} else {
|
||||
newEvents[key] = newProps.on[key]
|
||||
}
|
||||
})
|
||||
return cloneElement(fieldElem, { ...newProps, on: newEvents })
|
||||
}
|
||||
},
|
||||
|
||||
getFieldProps (name, usersFieldOption = {}) {
|
||||
if (!name) {
|
||||
throw new Error('Must call `getFieldProps` with valid name string!')
|
||||
}
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
warning(
|
||||
this.fieldsStore.isValidNestedFieldName(name),
|
||||
'One field name cannot be part of another, e.g. `a` and `a.b`.'
|
||||
)
|
||||
warning(
|
||||
!('exclusive' in usersFieldOption),
|
||||
'`option.exclusive` of `getFieldProps`|`getFieldDecorator` had been remove.'
|
||||
)
|
||||
}
|
||||
|
||||
delete this.clearedFieldMetaCache[name]
|
||||
|
||||
const fieldOption = {
|
||||
name,
|
||||
trigger: DEFAULT_TRIGGER,
|
||||
valuePropName: 'value',
|
||||
validate: [],
|
||||
...usersFieldOption,
|
||||
}
|
||||
|
||||
const {
|
||||
rules,
|
||||
trigger,
|
||||
validateTrigger = trigger,
|
||||
validate,
|
||||
} = fieldOption
|
||||
|
||||
const fieldMeta = this.fieldsStore.getFieldMeta(name)
|
||||
if ('initialValue' in fieldOption) {
|
||||
fieldMeta.initialValue = fieldOption.initialValue
|
||||
}
|
||||
|
||||
const inputProps = {
|
||||
...this.fieldsStore.getFieldValuePropValue(fieldOption),
|
||||
// ref: name,
|
||||
}
|
||||
const inputListeners = {}
|
||||
const inputAttrs = {}
|
||||
if (fieldNameProp) {
|
||||
inputProps[fieldNameProp] = name
|
||||
}
|
||||
|
||||
const validateRules = normalizeValidateRules(validate, rules, validateTrigger)
|
||||
const validateTriggers = getValidateTriggers(validateRules)
|
||||
validateTriggers.forEach((action) => {
|
||||
if (inputListeners[action]) return
|
||||
inputListeners[action] = this.getCacheBind(name, action, this.onCollectValidate)
|
||||
})
|
||||
|
||||
// make sure that the value will be collect
|
||||
if (trigger && validateTriggers.indexOf(trigger) === -1) {
|
||||
inputListeners[trigger] = this.getCacheBind(name, trigger, this.onCollect)
|
||||
}
|
||||
|
||||
const meta = {
|
||||
...fieldMeta,
|
||||
...fieldOption,
|
||||
validate: validateRules,
|
||||
}
|
||||
this.fieldsStore.setFieldMeta(name, meta)
|
||||
if (fieldMetaProp) {
|
||||
inputAttrs[fieldMetaProp] = meta
|
||||
}
|
||||
|
||||
if (fieldDataProp) {
|
||||
inputAttrs[fieldDataProp] = this.fieldsStore.getField(name)
|
||||
}
|
||||
|
||||
return {
|
||||
props: omit(inputProps, ['id']),
|
||||
// id: inputProps.id,
|
||||
domProps: {
|
||||
value: inputProps.value,
|
||||
},
|
||||
attrs: {
|
||||
...inputAttrs,
|
||||
id: inputProps.id,
|
||||
},
|
||||
directives: [
|
||||
{
|
||||
name: 'ant-ref',
|
||||
value: this.getCacheBind(name, `${name}__ref`, this.saveRef),
|
||||
},
|
||||
],
|
||||
on: inputListeners,
|
||||
}
|
||||
},
|
||||
|
||||
getFieldInstance (name) {
|
||||
return this.instances[name]
|
||||
},
|
||||
|
||||
getRules (fieldMeta, action) {
|
||||
const actionRules = fieldMeta.validate.filter((item) => {
|
||||
return !action || item.trigger.indexOf(action) >= 0
|
||||
}).map((item) => item.rules)
|
||||
return flattenArray(actionRules)
|
||||
},
|
||||
|
||||
setFields (maybeNestedFields, callback) {
|
||||
const fields = this.fieldsStore.flattenRegisteredFields(maybeNestedFields)
|
||||
this.fieldsStore.setFields(fields)
|
||||
if (onFieldsChange) {
|
||||
const changedFields = Object.keys(fields)
|
||||
.reduce((acc, name) => set(acc, name, this.fieldsStore.getField(name)), {})
|
||||
onFieldsChange(this, changedFields, this.fieldsStore.getNestedAllFields())
|
||||
}
|
||||
this.$forceUpdate()
|
||||
this.$nextTick(() => {
|
||||
callback && callback()
|
||||
})
|
||||
},
|
||||
|
||||
resetFields (ns) {
|
||||
const newFields = this.fieldsStore.resetFields(ns)
|
||||
if (Object.keys(newFields).length > 0) {
|
||||
this.setFields(newFields)
|
||||
}
|
||||
if (ns) {
|
||||
const names = Array.isArray(ns) ? ns : [ns]
|
||||
names.forEach(name => delete this.clearedFieldMetaCache[name])
|
||||
} else {
|
||||
this.clearedFieldMetaCache = {}
|
||||
}
|
||||
},
|
||||
|
||||
setFieldsValue (changedValues, callback) {
|
||||
const { fieldsMeta } = this.fieldsStore
|
||||
const values = this.fieldsStore.flattenRegisteredFields(changedValues)
|
||||
const newFields = Object.keys(values).reduce((acc, name) => {
|
||||
const isRegistered = fieldsMeta[name]
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
warning(
|
||||
isRegistered,
|
||||
'Cannot use `setFieldsValue` until ' +
|
||||
'you use `getFieldDecorator` or `getFieldProps` to register it.'
|
||||
)
|
||||
}
|
||||
if (isRegistered) {
|
||||
const value = values[name]
|
||||
acc[name] = {
|
||||
value,
|
||||
}
|
||||
}
|
||||
return acc
|
||||
}, {})
|
||||
this.setFields(newFields, callback)
|
||||
if (onValuesChange) {
|
||||
const allValues = this.fieldsStore.getAllValues()
|
||||
onValuesChange(this, changedValues, allValues)
|
||||
}
|
||||
},
|
||||
|
||||
saveRef (name, _, component) {
|
||||
if (!component) {
|
||||
// after destroy, delete data
|
||||
this.clearedFieldMetaCache[name] = {
|
||||
field: this.fieldsStore.getField(name),
|
||||
meta: this.fieldsStore.getFieldMeta(name),
|
||||
}
|
||||
this.fieldsStore.clearField(name)
|
||||
delete this.instances[name]
|
||||
delete this.cachedBind[name]
|
||||
return
|
||||
}
|
||||
this.recoverClearedField(name)
|
||||
// const fieldMeta = this.fieldsStore.getFieldMeta(name)
|
||||
// if (fieldMeta) {
|
||||
// const ref = fieldMeta.ref
|
||||
// if (ref) {
|
||||
// if (typeof ref === 'string') {
|
||||
// throw new Error(`can not set ref string for ${name}`)
|
||||
// }
|
||||
// ref(component)
|
||||
// }
|
||||
// }
|
||||
this.instances[name] = component
|
||||
},
|
||||
|
||||
validateFieldsInternal (fields, {
|
||||
fieldNames,
|
||||
action,
|
||||
options = {},
|
||||
}, callback) {
|
||||
const allRules = {}
|
||||
const allValues = {}
|
||||
const allFields = {}
|
||||
const alreadyErrors = {}
|
||||
fields.forEach((field) => {
|
||||
const name = field.name
|
||||
if (options.force !== true && field.dirty === false) {
|
||||
if (field.errors) {
|
||||
set(alreadyErrors, name, { errors: field.errors })
|
||||
}
|
||||
return
|
||||
}
|
||||
const fieldMeta = this.fieldsStore.getFieldMeta(name)
|
||||
const newField = {
|
||||
...field,
|
||||
}
|
||||
newField.errors = undefined
|
||||
newField.validating = true
|
||||
newField.dirty = true
|
||||
allRules[name] = this.getRules(fieldMeta, action)
|
||||
allValues[name] = newField.value
|
||||
allFields[name] = newField
|
||||
})
|
||||
this.setFields(allFields)
|
||||
// in case normalize
|
||||
Object.keys(allValues).forEach((f) => {
|
||||
allValues[f] = this.fieldsStore.getFieldValue(f)
|
||||
})
|
||||
if (callback && isEmptyObject(allFields)) {
|
||||
callback(isEmptyObject(alreadyErrors) ? null : alreadyErrors,
|
||||
this.fieldsStore.getFieldsValue(fieldNames))
|
||||
return
|
||||
}
|
||||
const validator = new AsyncValidator(allRules)
|
||||
if (validateMessages) {
|
||||
validator.messages(validateMessages)
|
||||
}
|
||||
validator.validate(allValues, options, (errors) => {
|
||||
const errorsGroup = {
|
||||
...alreadyErrors,
|
||||
}
|
||||
if (errors && errors.length) {
|
||||
errors.forEach((e) => {
|
||||
const fieldName = e.field
|
||||
const field = get(errorsGroup, fieldName)
|
||||
if (typeof field !== 'object' || Array.isArray(field)) {
|
||||
set(errorsGroup, fieldName, { errors: [] })
|
||||
}
|
||||
const fieldErrors = get(errorsGroup, fieldName.concat('.errors'))
|
||||
fieldErrors.push(e)
|
||||
})
|
||||
}
|
||||
const expired = []
|
||||
const nowAllFields = {}
|
||||
Object.keys(allRules).forEach((name) => {
|
||||
const fieldErrors = get(errorsGroup, name)
|
||||
const nowField = this.fieldsStore.getField(name)
|
||||
// avoid concurrency problems
|
||||
if (nowField.value !== allValues[name]) {
|
||||
expired.push({
|
||||
name,
|
||||
})
|
||||
} else {
|
||||
nowField.errors = fieldErrors && fieldErrors.errors
|
||||
nowField.value = allValues[name]
|
||||
nowField.validating = false
|
||||
nowField.dirty = false
|
||||
nowAllFields[name] = nowField
|
||||
}
|
||||
})
|
||||
this.setFields(nowAllFields)
|
||||
if (callback) {
|
||||
if (expired.length) {
|
||||
expired.forEach(({ name }) => {
|
||||
const fieldErrors = [{
|
||||
message: `${name} need to revalidate`,
|
||||
field: name,
|
||||
}]
|
||||
set(errorsGroup, name, {
|
||||
expired: true,
|
||||
errors: fieldErrors,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
callback(isEmptyObject(errorsGroup) ? null : errorsGroup,
|
||||
this.fieldsStore.getFieldsValue(fieldNames))
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
validateFields (ns, opt, cb) {
|
||||
const { names, callback, options } = getParams(ns, opt, cb)
|
||||
const fieldNames = names
|
||||
? this.fieldsStore.getValidFieldsFullName(names)
|
||||
: this.fieldsStore.getValidFieldsName()
|
||||
const fields = fieldNames
|
||||
.filter(name => {
|
||||
const fieldMeta = this.fieldsStore.getFieldMeta(name)
|
||||
return hasRules(fieldMeta.validate)
|
||||
}).map((name) => {
|
||||
const field = this.fieldsStore.getField(name)
|
||||
field.value = this.fieldsStore.getFieldValue(name)
|
||||
return field
|
||||
})
|
||||
if (!fields.length) {
|
||||
if (callback) {
|
||||
callback(null, this.fieldsStore.getFieldsValue(fieldNames))
|
||||
}
|
||||
return
|
||||
}
|
||||
if (!('firstFields' in options)) {
|
||||
options.firstFields = fieldNames.filter((name) => {
|
||||
const fieldMeta = this.fieldsStore.getFieldMeta(name)
|
||||
return !!fieldMeta.validateFirst
|
||||
})
|
||||
}
|
||||
this.validateFieldsInternal(fields, {
|
||||
fieldNames,
|
||||
options,
|
||||
}, callback)
|
||||
},
|
||||
|
||||
isSubmitting () {
|
||||
if (process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test') {
|
||||
warning(
|
||||
false,
|
||||
'`isSubmitting` is deprecated. ' +
|
||||
'Actually, it\'s more convenient to handle submitting status by yourself.'
|
||||
)
|
||||
}
|
||||
return this.submitting
|
||||
},
|
||||
|
||||
submit (callback) {
|
||||
if (process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test') {
|
||||
warning(
|
||||
false,
|
||||
'`submit` is deprecated.' +
|
||||
'Actually, it\'s more convenient to handle submitting status by yourself.'
|
||||
)
|
||||
}
|
||||
const fn = () => {
|
||||
this.setState({
|
||||
submitting: false,
|
||||
})
|
||||
}
|
||||
this.setState({
|
||||
submitting: true,
|
||||
})
|
||||
callback(fn)
|
||||
},
|
||||
},
|
||||
|
||||
render () {
|
||||
const { $listeners, $slots } = this
|
||||
const formProps = {
|
||||
[formPropName]: this.getForm(),
|
||||
}
|
||||
const props = getOptionProps(this)
|
||||
const wrappedComponentProps = {
|
||||
props: mapProps.call(this, {
|
||||
...formProps,
|
||||
...props,
|
||||
}),
|
||||
on: $listeners,
|
||||
ref: 'WrappedComponent',
|
||||
}
|
||||
return <WrappedComponent {...wrappedComponentProps}>{$slots.default}</WrappedComponent>
|
||||
},
|
||||
}
|
||||
if (Array.isArray(WrappedComponent.props)) {
|
||||
const newProps = {}
|
||||
WrappedComponent.props.forEach((prop) => {
|
||||
newProps[prop] = PropTypes.any
|
||||
})
|
||||
newProps[formPropName] = Object
|
||||
WrappedComponent.props = newProps
|
||||
} else {
|
||||
WrappedComponent.props = WrappedComponent.props || {}
|
||||
if (!(formPropName in WrappedComponent.props)) {
|
||||
WrappedComponent.props[formPropName] = Object
|
||||
}
|
||||
}
|
||||
return argumentContainer(Form, WrappedComponent)
|
||||
}
|
||||
}
|
||||
|
||||
export default createBaseForm
|
107
components/vc-form/src/createDOMForm.jsx
Normal file
107
components/vc-form/src/createDOMForm.jsx
Normal file
@ -0,0 +1,107 @@
|
||||
import scrollIntoView from 'dom-scroll-into-view'
|
||||
import has from 'lodash/has'
|
||||
import createBaseForm from './createBaseForm'
|
||||
import { mixin as formMixin } from './createForm'
|
||||
import { getParams } from './utils'
|
||||
|
||||
function computedStyle (el, prop) {
|
||||
const getComputedStyle = window.getComputedStyle
|
||||
const style =
|
||||
// If we have getComputedStyle
|
||||
getComputedStyle
|
||||
// Query it
|
||||
// TODO: From CSS-Query notes, we might need (node, null) for FF
|
||||
? getComputedStyle(el)
|
||||
|
||||
// Otherwise, we are in IE and use currentStyle
|
||||
: el.currentStyle
|
||||
if (style) {
|
||||
return style[
|
||||
// Switch to camelCase for CSSOM
|
||||
// DEV: Grabbed from jQuery
|
||||
// https://github.com/jquery/jquery/blob/1.9-stable/src/css.js#L191-L194
|
||||
// https://github.com/jquery/jquery/blob/1.9-stable/src/core.js#L593-L597
|
||||
prop.replace(/-(\w)/gi, (word, letter) => {
|
||||
return letter.toUpperCase()
|
||||
})
|
||||
]
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
function getScrollableContainer (n) {
|
||||
let node = n
|
||||
let nodeName
|
||||
/* eslint no-cond-assign:0 */
|
||||
while ((nodeName = node.nodeName.toLowerCase()) !== 'body') {
|
||||
const overflowY = computedStyle(node, 'overflowY')
|
||||
// https://stackoverflow.com/a/36900407/3040605
|
||||
if (
|
||||
node !== n &&
|
||||
(overflowY === 'auto' || overflowY === 'scroll') &&
|
||||
node.scrollHeight > node.clientHeight
|
||||
) {
|
||||
return node
|
||||
}
|
||||
node = node.parentNode
|
||||
}
|
||||
return nodeName === 'body' ? node.ownerDocument : node
|
||||
}
|
||||
|
||||
const mixin = {
|
||||
methods: {
|
||||
getForm () {
|
||||
return {
|
||||
...formMixin.methods.getForm.call(this),
|
||||
validateFieldsAndScroll: this.validateFieldsAndScroll,
|
||||
}
|
||||
},
|
||||
|
||||
validateFieldsAndScroll (ns, opt, cb) {
|
||||
const { names, callback, options } = getParams(ns, opt, cb)
|
||||
|
||||
const newCb = (error, values) => {
|
||||
if (error) {
|
||||
const validNames = this.fieldsStore.getValidFieldsName()
|
||||
let firstNode
|
||||
let firstTop
|
||||
for (const name of validNames) {
|
||||
if (has(error, name)) {
|
||||
const instance = this.getFieldInstance(name)
|
||||
if (instance) {
|
||||
const node = instance.$el || instance.elm
|
||||
const top = node.getBoundingClientRect().top
|
||||
if (firstTop === undefined || firstTop > top) {
|
||||
firstTop = top
|
||||
firstNode = node
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (firstNode) {
|
||||
const c = options.container || getScrollableContainer(firstNode)
|
||||
scrollIntoView(firstNode, c, {
|
||||
onlyScrollIfNeeded: true,
|
||||
...options.scroll,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof callback === 'function') {
|
||||
callback(error, values)
|
||||
}
|
||||
}
|
||||
|
||||
return this.validateFields(names, options, newCb)
|
||||
},
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
function createDOMForm (option) {
|
||||
return createBaseForm({
|
||||
...option,
|
||||
}, [mixin])
|
||||
}
|
||||
|
||||
export default createDOMForm
|
261
components/vc-form/src/createFieldsStore.jsx
Normal file
261
components/vc-form/src/createFieldsStore.jsx
Normal file
@ -0,0 +1,261 @@
|
||||
import set from 'lodash/set'
|
||||
import createFormField, { isFormField } from './createFormField'
|
||||
import {
|
||||
flattenFields,
|
||||
getErrorStrs,
|
||||
startsWith,
|
||||
} from './utils'
|
||||
|
||||
function partOf (a, b) {
|
||||
return b.indexOf(a) === 0 && ['.', '['].indexOf(b[a.length]) !== -1
|
||||
}
|
||||
|
||||
class FieldsStore {
|
||||
constructor (fields) {
|
||||
this.fields = this.flattenFields(fields)
|
||||
this.fieldsMeta = {}
|
||||
}
|
||||
|
||||
updateFields (fields) {
|
||||
this.fields = this.flattenFields(fields)
|
||||
}
|
||||
|
||||
flattenFields (fields) {
|
||||
return flattenFields(
|
||||
fields,
|
||||
(_, node) => isFormField(node),
|
||||
'You must wrap field data with `createFormField`.'
|
||||
)
|
||||
}
|
||||
|
||||
flattenRegisteredFields (fields) {
|
||||
const validFieldsName = this.getAllFieldsName()
|
||||
return flattenFields(
|
||||
fields,
|
||||
path => validFieldsName.indexOf(path) >= 0,
|
||||
'You cannot set field before registering it.'
|
||||
)
|
||||
}
|
||||
|
||||
setFieldsInitialValue = (initialValues) => {
|
||||
const flattenedInitialValues = this.flattenRegisteredFields(initialValues)
|
||||
const fieldsMeta = this.fieldsMeta
|
||||
Object.keys(flattenedInitialValues).forEach(name => {
|
||||
if (fieldsMeta[name]) {
|
||||
this.setFieldMeta(name, {
|
||||
...this.getFieldMeta(name),
|
||||
initialValue: flattenedInitialValues[name],
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
setFields (fields) {
|
||||
const fieldsMeta = this.fieldsMeta
|
||||
const nowFields = {
|
||||
...this.fields,
|
||||
...fields,
|
||||
}
|
||||
const nowValues = {}
|
||||
Object.keys(fieldsMeta)
|
||||
.forEach((f) => { nowValues[f] = this.getValueFromFields(f, nowFields) })
|
||||
Object.keys(nowValues).forEach((f) => {
|
||||
const value = nowValues[f]
|
||||
const fieldMeta = this.getFieldMeta(f)
|
||||
if (fieldMeta && fieldMeta.normalize) {
|
||||
const nowValue =
|
||||
fieldMeta.normalize(value, this.getValueFromFields(f, this.fields), nowValues)
|
||||
if (nowValue !== value) {
|
||||
nowFields[f] = {
|
||||
...nowFields[f],
|
||||
value: nowValue,
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
this.fields = nowFields
|
||||
}
|
||||
|
||||
resetFields (ns) {
|
||||
const { fields } = this
|
||||
const names = ns
|
||||
? this.getValidFieldsFullName(ns)
|
||||
: this.getAllFieldsName()
|
||||
return names.reduce((acc, name) => {
|
||||
const field = fields[name]
|
||||
if (field && 'value' in field) {
|
||||
acc[name] = {}
|
||||
}
|
||||
return acc
|
||||
}, {})
|
||||
}
|
||||
|
||||
setFieldMeta (name, meta) {
|
||||
this.fieldsMeta[name] = meta
|
||||
}
|
||||
|
||||
getFieldMeta (name) {
|
||||
this.fieldsMeta[name] = this.fieldsMeta[name] || {}
|
||||
return this.fieldsMeta[name]
|
||||
}
|
||||
|
||||
getValueFromFields (name, fields) {
|
||||
const field = fields[name]
|
||||
if (field && 'value' in field) {
|
||||
return field.value
|
||||
}
|
||||
const fieldMeta = this.getFieldMeta(name)
|
||||
return fieldMeta && fieldMeta.initialValue
|
||||
}
|
||||
|
||||
getAllValues = () => {
|
||||
const { fieldsMeta, fields } = this
|
||||
return Object.keys(fieldsMeta)
|
||||
.reduce((acc, name) => set(acc, name, this.getValueFromFields(name, fields)), {})
|
||||
}
|
||||
|
||||
getValidFieldsName () {
|
||||
const { fieldsMeta } = this
|
||||
return fieldsMeta
|
||||
? Object.keys(fieldsMeta).filter(name => !this.getFieldMeta(name).hidden)
|
||||
: []
|
||||
}
|
||||
|
||||
getAllFieldsName () {
|
||||
const { fieldsMeta } = this
|
||||
return fieldsMeta ? Object.keys(fieldsMeta) : []
|
||||
}
|
||||
|
||||
getValidFieldsFullName (maybePartialName) {
|
||||
const maybePartialNames = Array.isArray(maybePartialName)
|
||||
? maybePartialName : [maybePartialName]
|
||||
return this.getValidFieldsName()
|
||||
.filter(fullName => maybePartialNames.some(partialName => (
|
||||
fullName === partialName || (
|
||||
startsWith(fullName, partialName) &&
|
||||
['.', '['].indexOf(fullName[partialName.length]) >= 0
|
||||
)
|
||||
)))
|
||||
}
|
||||
|
||||
getFieldValuePropValue (fieldMeta) {
|
||||
const { name, getValueProps, valuePropName } = fieldMeta
|
||||
const field = this.getField(name)
|
||||
const fieldValue = 'value' in field
|
||||
? field.value : fieldMeta.initialValue
|
||||
if (getValueProps) {
|
||||
return getValueProps(fieldValue)
|
||||
}
|
||||
return { [valuePropName]: fieldValue }
|
||||
}
|
||||
|
||||
getField (name) {
|
||||
return {
|
||||
...this.fields[name],
|
||||
name,
|
||||
}
|
||||
}
|
||||
|
||||
getNotCollectedFields () {
|
||||
return this.getValidFieldsName()
|
||||
.filter(name => !this.fields[name])
|
||||
.map(name => ({
|
||||
name,
|
||||
dirty: false,
|
||||
value: this.getFieldMeta(name).initialValue,
|
||||
}))
|
||||
.reduce((acc, field) => set(acc, field.name, createFormField(field)), {})
|
||||
}
|
||||
|
||||
getNestedAllFields () {
|
||||
return Object.keys(this.fields)
|
||||
.reduce(
|
||||
(acc, name) => set(acc, name, createFormField(this.fields[name])),
|
||||
this.getNotCollectedFields()
|
||||
)
|
||||
}
|
||||
|
||||
getFieldMember (name, member) {
|
||||
return this.getField(name)[member]
|
||||
}
|
||||
|
||||
getNestedFields (names, getter) {
|
||||
const fields = names || this.getValidFieldsName()
|
||||
return fields.reduce((acc, f) => set(acc, f, getter(f)), {})
|
||||
}
|
||||
|
||||
getNestedField (name, getter) {
|
||||
const fullNames = this.getValidFieldsFullName(name)
|
||||
if (
|
||||
fullNames.length === 0 || // Not registered
|
||||
(fullNames.length === 1 && fullNames[0] === name) // Name already is full name.
|
||||
) {
|
||||
return getter(name)
|
||||
}
|
||||
const isArrayValue = fullNames[0][name.length] === '['
|
||||
const suffixNameStartIndex = isArrayValue ? name.length : name.length + 1
|
||||
return fullNames
|
||||
.reduce(
|
||||
(acc, fullName) => set(
|
||||
acc,
|
||||
fullName.slice(suffixNameStartIndex),
|
||||
getter(fullName)
|
||||
),
|
||||
isArrayValue ? [] : {}
|
||||
)
|
||||
}
|
||||
|
||||
getFieldsValue = (names) => {
|
||||
return this.getNestedFields(names, this.getFieldValue)
|
||||
}
|
||||
|
||||
getFieldValue = (name) => {
|
||||
const { fields } = this
|
||||
return this.getNestedField(name, (fullName) => this.getValueFromFields(fullName, fields))
|
||||
}
|
||||
|
||||
getFieldsError = (names) => {
|
||||
return this.getNestedFields(names, this.getFieldError)
|
||||
}
|
||||
|
||||
getFieldError = (name) => {
|
||||
return this.getNestedField(
|
||||
name,
|
||||
(fullName) => getErrorStrs(this.getFieldMember(fullName, 'errors'))
|
||||
)
|
||||
}
|
||||
|
||||
isFieldValidating = (name) => {
|
||||
return this.getFieldMember(name, 'validating')
|
||||
}
|
||||
|
||||
isFieldsValidating = (ns) => {
|
||||
const names = ns || this.getValidFieldsName()
|
||||
return names.some((n) => this.isFieldValidating(n))
|
||||
}
|
||||
|
||||
isFieldTouched = (name) => {
|
||||
return this.getFieldMember(name, 'touched')
|
||||
}
|
||||
|
||||
isFieldsTouched = (ns) => {
|
||||
const names = ns || this.getValidFieldsName()
|
||||
return names.some((n) => this.isFieldTouched(n))
|
||||
}
|
||||
|
||||
// @private
|
||||
// BG: `a` and `a.b` cannot be use in the same form
|
||||
isValidNestedFieldName (name) {
|
||||
const names = this.getAllFieldsName()
|
||||
return names.every(n => !partOf(n, name) && !partOf(name, n))
|
||||
}
|
||||
|
||||
clearField (name) {
|
||||
delete this.fields[name]
|
||||
delete this.fieldsMeta[name]
|
||||
}
|
||||
}
|
||||
|
||||
export default function createFieldsStore (fields) {
|
||||
return new FieldsStore(fields)
|
||||
}
|
34
components/vc-form/src/createForm.jsx
Normal file
34
components/vc-form/src/createForm.jsx
Normal file
@ -0,0 +1,34 @@
|
||||
import createBaseForm from './createBaseForm'
|
||||
|
||||
export const mixin = {
|
||||
methods: {
|
||||
getForm () {
|
||||
return {
|
||||
getFieldsValue: this.fieldsStore.getFieldsValue,
|
||||
getFieldValue: this.fieldsStore.getFieldValue,
|
||||
getFieldInstance: this.getFieldInstance,
|
||||
setFieldsValue: this.setFieldsValue,
|
||||
setFields: this.setFields,
|
||||
setFieldsInitialValue: this.fieldsStore.setFieldsInitialValue,
|
||||
getFieldDecorator: this.getFieldDecorator,
|
||||
getFieldProps: this.getFieldProps,
|
||||
getFieldsError: this.fieldsStore.getFieldsError,
|
||||
getFieldError: this.fieldsStore.getFieldError,
|
||||
isFieldValidating: this.fieldsStore.isFieldValidating,
|
||||
isFieldsValidating: this.fieldsStore.isFieldsValidating,
|
||||
isFieldsTouched: this.fieldsStore.isFieldsTouched,
|
||||
isFieldTouched: this.fieldsStore.isFieldTouched,
|
||||
isSubmitting: this.isSubmitting,
|
||||
submit: this.submit,
|
||||
validateFields: this.validateFields,
|
||||
resetFields: this.resetFields,
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
function createForm (options) {
|
||||
return createBaseForm(options, [mixin])
|
||||
}
|
||||
|
||||
export default createForm
|
16
components/vc-form/src/createFormField.jsx
Normal file
16
components/vc-form/src/createFormField.jsx
Normal file
@ -0,0 +1,16 @@
|
||||
class Field {
|
||||
constructor (fields) {
|
||||
Object.assign(this, fields)
|
||||
}
|
||||
}
|
||||
|
||||
export function isFormField (obj) {
|
||||
return obj instanceof Field
|
||||
}
|
||||
|
||||
export default function createFormField (field) {
|
||||
if (isFormField(field)) {
|
||||
return field
|
||||
}
|
||||
return new Field(field)
|
||||
}
|
9
components/vc-form/src/index.jsx
Normal file
9
components/vc-form/src/index.jsx
Normal file
@ -0,0 +1,9 @@
|
||||
// export this package's api
|
||||
import createForm from './createForm'
|
||||
import createFormField from './createFormField'
|
||||
import formShape from './propTypes'
|
||||
import Vue from 'vue'
|
||||
import antRefDirective from '../../_util/antRefDirective'
|
||||
Vue.use(antRefDirective)
|
||||
|
||||
export { createForm, createFormField, formShape }
|
24
components/vc-form/src/propTypes.js
Normal file
24
components/vc-form/src/propTypes.js
Normal file
@ -0,0 +1,24 @@
|
||||
import PropTypes from '../../_util/vue-types'
|
||||
|
||||
const formShape = PropTypes.shape({
|
||||
getFieldsValue: PropTypes.func,
|
||||
getFieldValue: PropTypes.func,
|
||||
getFieldInstance: PropTypes.func,
|
||||
setFieldsValue: PropTypes.func,
|
||||
setFields: PropTypes.func,
|
||||
setFieldsInitialValue: PropTypes.func,
|
||||
getFieldDecorator: PropTypes.func,
|
||||
getFieldProps: PropTypes.func,
|
||||
getFieldsError: PropTypes.func,
|
||||
getFieldError: PropTypes.func,
|
||||
isFieldValidating: PropTypes.func,
|
||||
isFieldsValidating: PropTypes.func,
|
||||
isFieldsTouched: PropTypes.func,
|
||||
isFieldTouched: PropTypes.func,
|
||||
isSubmitting: PropTypes.func,
|
||||
submit: PropTypes.func,
|
||||
validateFields: PropTypes.func,
|
||||
resetFields: PropTypes.func,
|
||||
}).loose
|
||||
|
||||
export default formShape
|
152
components/vc-form/src/utils.js
Normal file
152
components/vc-form/src/utils.js
Normal file
@ -0,0 +1,152 @@
|
||||
function getDisplayName (WrappedComponent) {
|
||||
return WrappedComponent.name || 'WrappedComponent'
|
||||
}
|
||||
|
||||
export function argumentContainer (Container, WrappedComponent) {
|
||||
/* eslint no-param-reassign:0 */
|
||||
Container.name = `Form_${getDisplayName(WrappedComponent)}`
|
||||
Container.WrappedComponent = WrappedComponent
|
||||
Container.props = { ...Container.props, ...WrappedComponent.props }
|
||||
return Container
|
||||
}
|
||||
|
||||
export function identity (obj) {
|
||||
return obj
|
||||
}
|
||||
|
||||
export function flattenArray (arr) {
|
||||
return Array.prototype.concat.apply([], arr)
|
||||
}
|
||||
|
||||
export function treeTraverse (path = '', tree, isLeafNode, errorMessage, callback) {
|
||||
if (isLeafNode(path, tree)) {
|
||||
callback(path, tree)
|
||||
} else if (tree === undefined) {
|
||||
return
|
||||
} else if (Array.isArray(tree)) {
|
||||
tree.forEach((subTree, index) => treeTraverse(
|
||||
`${path}[${index}]`,
|
||||
subTree,
|
||||
isLeafNode,
|
||||
errorMessage,
|
||||
callback
|
||||
))
|
||||
} else { // It's object and not a leaf node
|
||||
if (typeof tree !== 'object') {
|
||||
console.error(errorMessage)
|
||||
return
|
||||
}
|
||||
Object.keys(tree).forEach(subTreeKey => {
|
||||
const subTree = tree[subTreeKey]
|
||||
treeTraverse(
|
||||
`${path}${path ? '.' : ''}${subTreeKey}`,
|
||||
subTree,
|
||||
isLeafNode,
|
||||
errorMessage,
|
||||
callback
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export function flattenFields (maybeNestedFields, isLeafNode, errorMessage) {
|
||||
const fields = {}
|
||||
treeTraverse(undefined, maybeNestedFields, isLeafNode, errorMessage, (path, node) => {
|
||||
fields[path] = node
|
||||
})
|
||||
return fields
|
||||
}
|
||||
|
||||
export function normalizeValidateRules (validate, rules, validateTrigger) {
|
||||
const validateRules = validate.map((item) => {
|
||||
const newItem = {
|
||||
...item,
|
||||
trigger: item.trigger || [],
|
||||
}
|
||||
if (typeof newItem.trigger === 'string') {
|
||||
newItem.trigger = [newItem.trigger]
|
||||
}
|
||||
return newItem
|
||||
})
|
||||
if (rules) {
|
||||
validateRules.push({
|
||||
trigger: validateTrigger ? [].concat(validateTrigger) : [],
|
||||
rules,
|
||||
})
|
||||
}
|
||||
return validateRules
|
||||
}
|
||||
|
||||
export function getValidateTriggers (validateRules) {
|
||||
return validateRules
|
||||
.filter(item => !!item.rules && item.rules.length)
|
||||
.map(item => item.trigger)
|
||||
.reduce((pre, curr) => pre.concat(curr), [])
|
||||
}
|
||||
|
||||
export function getValueFromEvent (e) {
|
||||
// To support custom element
|
||||
if (!e || !e.target) {
|
||||
return e
|
||||
}
|
||||
const { target } = e
|
||||
return target.type === 'checkbox' ? target.checked : target.value
|
||||
}
|
||||
|
||||
export function getErrorStrs (errors) {
|
||||
if (errors) {
|
||||
return errors.map((e) => {
|
||||
if (e && e.message) {
|
||||
return e.message
|
||||
}
|
||||
return e
|
||||
})
|
||||
}
|
||||
return errors
|
||||
}
|
||||
|
||||
export function getParams (ns, opt, cb) {
|
||||
let names = ns
|
||||
let options = opt
|
||||
let callback = cb
|
||||
if (cb === undefined) {
|
||||
if (typeof names === 'function') {
|
||||
callback = names
|
||||
options = {}
|
||||
names = undefined
|
||||
} else if (Array.isArray(names)) {
|
||||
if (typeof options === 'function') {
|
||||
callback = options
|
||||
options = {}
|
||||
} else {
|
||||
options = options || {}
|
||||
}
|
||||
} else {
|
||||
callback = options
|
||||
options = names || {}
|
||||
names = undefined
|
||||
}
|
||||
}
|
||||
return {
|
||||
names,
|
||||
options,
|
||||
callback,
|
||||
}
|
||||
}
|
||||
|
||||
export function isEmptyObject (obj) {
|
||||
return Object.keys(obj).length === 0
|
||||
}
|
||||
|
||||
export function hasRules (validate) {
|
||||
if (validate) {
|
||||
return validate.some((item) => {
|
||||
return item.rules && item.rules.length
|
||||
})
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
export function startsWith (str, prefix) {
|
||||
return str.lastIndexOf(prefix, 0) === 0
|
||||
}
|
@ -491,8 +491,9 @@ export default {
|
||||
const inputDisplayValueFormat = this.formatWrapper(inputDisplayValue)
|
||||
const isUpDisabled = !!upDisabledClass || disabled || readOnly
|
||||
const isDownDisabled = !!downDisabledClass || disabled || readOnly
|
||||
const { mouseenter = noop, mouseleave = noop, mouseover = noop, mouseout = noop } = this.$listeners
|
||||
const contentProps = {
|
||||
on: this.$listeners,
|
||||
on: { mouseenter, mouseleave, mouseover, mouseout },
|
||||
class: classes,
|
||||
}
|
||||
const upHandlerProps = {
|
||||
@ -578,7 +579,7 @@ export default {
|
||||
step={this.step}
|
||||
name={this.name}
|
||||
id={this.id}
|
||||
onChange={this.onChange}
|
||||
onInput={this.onChange}
|
||||
ref='inputRef'
|
||||
value={inputDisplayValueFormat}
|
||||
pattern={this.pattern}
|
||||
|
@ -7,6 +7,9 @@ import createChainedFunction from '../_util/createChainedFunction'
|
||||
import getTransitionProps from '../_util/getTransitionProps'
|
||||
import Notice from './Notice'
|
||||
|
||||
import antRefDirective from '../_util/antRefDirective'
|
||||
Vue.use(antRefDirective)
|
||||
|
||||
let seed = 0
|
||||
const now = Date.now()
|
||||
|
||||
|
@ -6,7 +6,7 @@ import classes from 'component-classes'
|
||||
import { Item as MenuItem, ItemGroup as MenuItemGroup } from '../vc-menu'
|
||||
import warning from 'warning'
|
||||
import Option from './Option'
|
||||
import { hasProp, getSlotOptions, getPropsData, getValueByProp as getValue, getComponentFromProp, getEvents, getClass } from '../_util/props-util'
|
||||
import { hasProp, getSlotOptions, getPropsData, getValueByProp as getValue, getComponentFromProp, getEvents, getClass, getStyle, getAttrs } from '../_util/props-util'
|
||||
import getTransitionProps from '../_util/getTransitionProps'
|
||||
import { cloneElement } from '../_util/vnode'
|
||||
import BaseMixin from '../_util/BaseMixin'
|
||||
@ -45,6 +45,7 @@ function chaining (...fns) {
|
||||
}
|
||||
}
|
||||
export default {
|
||||
inheritAttrs: false,
|
||||
name: 'Select',
|
||||
mixins: [BaseMixin],
|
||||
props: {
|
||||
@ -706,9 +707,10 @@ export default {
|
||||
},
|
||||
_getInputElement () {
|
||||
const props = this.$props
|
||||
const attrs = getAttrs(this)
|
||||
const inputElement = props.getInputElement
|
||||
? props.getInputElement()
|
||||
: <input id={props.id} autoComplete='off'/>
|
||||
: <input id={attrs.id} autoComplete='off'/>
|
||||
const inputCls = classnames(getClass(inputElement), {
|
||||
[`${props.prefixCls}-search__field`]: true,
|
||||
})
|
||||
@ -1501,6 +1503,7 @@ export default {
|
||||
selectionProps.attrs.tabIndex = props.disabled ? -1 : 0
|
||||
}
|
||||
const rootCls = {
|
||||
...getClass(this),
|
||||
[prefixCls]: true,
|
||||
[`${prefixCls}-open`]: openStatus,
|
||||
[`${prefixCls}-focused`]: openStatus || !!this._focused,
|
||||
@ -1542,6 +1545,7 @@ export default {
|
||||
>
|
||||
<div
|
||||
ref='rootRef'
|
||||
style={getStyle(this)}
|
||||
class={classnames(rootCls)}
|
||||
// tabindex='-1'
|
||||
// onBlur={this.onOuterBlur}
|
||||
|
@ -10,6 +10,7 @@ const upLoadPropTypes = {
|
||||
// style: PropTypes.object,
|
||||
prefixCls: PropTypes.string,
|
||||
action: PropTypes.string,
|
||||
name: PropTypes.string,
|
||||
// className: PropTypes.string,
|
||||
multiple: PropTypes.bool,
|
||||
disabled: PropTypes.bool,
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vue-antd-ui",
|
||||
"version": "0.4.1",
|
||||
"version": "0.5.1",
|
||||
"title": "Ant Design Vue",
|
||||
"description": "An enterprise-class UI design language and Vue-based implementation",
|
||||
"keywords": [
|
||||
@ -63,6 +63,7 @@
|
||||
"babel-plugin-istanbul": "^4.1.1",
|
||||
"babel-plugin-syntax-dynamic-import": "^6.18.0",
|
||||
"babel-plugin-syntax-jsx": "^6.18.0",
|
||||
"babel-plugin-transform-class-properties": "^6.24.1",
|
||||
"babel-plugin-transform-decorators": "^6.24.1",
|
||||
"babel-plugin-transform-decorators-legacy": "^1.3.4",
|
||||
"babel-plugin-transform-es3-member-expression-literals": "^6.22.0",
|
||||
@ -128,6 +129,7 @@
|
||||
"pre-commit": "^1.2.2",
|
||||
"puppeteer": "^1.3.0",
|
||||
"querystring": "^0.2.0",
|
||||
"raw-loader": "^1.0.0-beta.0",
|
||||
"reqwest": "^2.0.5",
|
||||
"rimraf": "^2.6.2",
|
||||
"rucksack-css": "^1.0.2",
|
||||
@ -154,8 +156,9 @@
|
||||
"dependencies": {
|
||||
"add-dom-event-listener": "^1.0.2",
|
||||
"array-tree-filter": "^2.1.0",
|
||||
"babel-runtime": "6.x",
|
||||
"async-validator": "^1.8.2",
|
||||
"babel-helper-vue-jsx-merge-props": "^2.0.3",
|
||||
"babel-runtime": "6.x",
|
||||
"classnames": "^2.2.5",
|
||||
"component-classes": "^1.2.6",
|
||||
"css-animation": "^1.4.1",
|
||||
|
@ -19,7 +19,7 @@ import {
|
||||
DatePicker,
|
||||
Divider,
|
||||
Dropdown,
|
||||
// Form,
|
||||
Form,
|
||||
Icon,
|
||||
Input,
|
||||
InputNumber,
|
||||
@ -85,7 +85,8 @@ Vue.component(DatePicker.WeekPicker.name, DatePicker.WeekPicker)
|
||||
Vue.component(Divider.name, Divider)
|
||||
Vue.component(Dropdown.name, Dropdown)
|
||||
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(Input.name, Input)
|
||||
Vue.component(Input.Group.name, Input.Group)
|
||||
|
@ -51,10 +51,10 @@ export default {
|
||||
},
|
||||
data () {
|
||||
const { name = '' } = this.$route.params
|
||||
const { html, script, style, us, cn } = this.jsfiddle
|
||||
let sourceCode = `<template>${html}</template>\n`
|
||||
sourceCode = script ? sourceCode + '\<script>' + script + '<\/script>' : sourceCode
|
||||
sourceCode = style ? sourceCode + '\<style>' + style + '<\/style>' : sourceCode
|
||||
const { html, script, style, us, cn, sourceCode } = this.jsfiddle
|
||||
// let sourceCode = `<template>${html}</template>\n`
|
||||
// sourceCode = script ? sourceCode + '\<script>' + script + '<\/script>' : sourceCode
|
||||
// sourceCode = style ? sourceCode + '\<style>' + style + '<\/style>' : sourceCode
|
||||
const usTitle = (us.split('#### ')[1] || '').split('\n')[0] || ''
|
||||
const cnTitle = (cn.split('#### ')[1] || '').split('\n')[0] || ''
|
||||
if (process.env.NODE_ENV !== 'production' && usTitle === '') {
|
||||
|
81
site/components/demoContainer.vue
Normal file
81
site/components/demoContainer.vue
Normal file
@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<div>
|
||||
<demo-box :jsfiddle="jsfiddle">
|
||||
<template slot="component"><slot /></template>
|
||||
<template slot="description">
|
||||
<div v-html="cnHtml"></div>
|
||||
</template>
|
||||
<template slot="us-description">
|
||||
<div v-html="usHtml"></div>
|
||||
</template>
|
||||
<template slot="code">
|
||||
<div v-html="codeHtml"></div>
|
||||
</template>
|
||||
</demo-box>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import marked from 'marked'
|
||||
const hljs = require('highlight.js')
|
||||
const replaceDelimiters = function (str) {
|
||||
return str.replace(/({{|}})/g, '<span>$1</span>')
|
||||
}
|
||||
const renderHighlight = function (str, lang) {
|
||||
if (!(lang && hljs.getLanguage(lang))) {
|
||||
return ''
|
||||
}
|
||||
|
||||
try {
|
||||
return replaceDelimiters(hljs.highlight(lang, str, true).value)
|
||||
} catch (err) {}
|
||||
}
|
||||
const renderer = new marked.Renderer()
|
||||
renderer.heading = function (text, level) {
|
||||
return '<h' +
|
||||
level +
|
||||
' id="' +
|
||||
text.replace(/[^\w]+/g, '-') +
|
||||
'">' +
|
||||
text +
|
||||
'</h' +
|
||||
level +
|
||||
'>\n'
|
||||
}
|
||||
marked.setOptions({
|
||||
renderer,
|
||||
gfm: true,
|
||||
tables: true,
|
||||
breaks: true,
|
||||
pedantic: true,
|
||||
sanitize: true,
|
||||
smartLists: true,
|
||||
smartypants: true,
|
||||
html: true,
|
||||
highlight: renderHighlight,
|
||||
})
|
||||
const cnReg = /<cn>([\S\s\t]*?)<\/cn>/
|
||||
const usReg = /<us>([\S\s\t]*?)<\/us>/
|
||||
export default {
|
||||
name: 'demoContainer',
|
||||
props: ['code'],
|
||||
data () {
|
||||
const cn = this.code.match(cnReg) || []
|
||||
const us = this.code.match(usReg) || []
|
||||
const cnHtml = marked(cn[1].trim())
|
||||
const usHtml = marked(us[1].trim())
|
||||
const sourceCode = this.code.replace(cn[0], '').replace(us[0], '').trim()
|
||||
const codeHtml = marked('````jsx\n' + sourceCode + '````')
|
||||
return {
|
||||
codeHtml,
|
||||
cnHtml,
|
||||
usHtml,
|
||||
jsfiddle: {
|
||||
sourceCode,
|
||||
cn: cn[1].trim(),
|
||||
us: us[1].trim(),
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
@ -4,18 +4,18 @@
|
||||
<script>
|
||||
import marked from 'marked'
|
||||
import { isZhCN } from '../util'
|
||||
var renderer = new marked.Renderer();
|
||||
renderer.heading = function(text, level) {
|
||||
return '<h'
|
||||
+ level
|
||||
+ ' id="'
|
||||
+ text.replace(/[^\w]+/g, '-')
|
||||
+ '">'
|
||||
+ text
|
||||
+ '</h'
|
||||
+ level
|
||||
+ '>\n';
|
||||
};
|
||||
const renderer = new marked.Renderer()
|
||||
renderer.heading = function (text, level) {
|
||||
return '<h' +
|
||||
level +
|
||||
' id="' +
|
||||
text.replace(/[^\w]+/g, '-') +
|
||||
'">' +
|
||||
text +
|
||||
'</h' +
|
||||
level +
|
||||
'>\n'
|
||||
}
|
||||
marked.setOptions({
|
||||
renderer,
|
||||
gfm: true,
|
||||
|
@ -44,3 +44,4 @@ export { default as transfer } from 'antd/transfer/demo/index.vue'
|
||||
export { default as upload } from 'antd/upload/demo/index.vue'
|
||||
export { default as tree } from 'antd/tree/demo/index.vue'
|
||||
export { default as layout } from 'antd/layout/demo/index.vue'
|
||||
export { default as form } from 'antd/form/demo/index.vue'
|
||||
|
@ -1,3 +1,4 @@
|
||||
import 'babel-polyfill'
|
||||
import '../components/style.js'
|
||||
import './index.less'
|
||||
import 'highlight.js/styles/solarized-light.css'
|
||||
@ -9,12 +10,14 @@ import Md from './components/md'
|
||||
import Api from './components/api'
|
||||
import './components'
|
||||
import demoBox from './components/demoBox'
|
||||
import demoContainer from './components/demoContainer'
|
||||
|
||||
Vue.use(VueClipboard)
|
||||
Vue.use(VueRouter)
|
||||
Vue.component(Md.name, Md)
|
||||
Vue.component(Api.name, Api)
|
||||
Vue.component('demo-box', demoBox)
|
||||
Vue.component('demo-container', demoContainer)
|
||||
|
||||
const router = new VueRouter({
|
||||
mode: 'history',
|
||||
|
@ -4,7 +4,7 @@ import Iframe from './components/iframe.vue'
|
||||
const AsyncTestComp = () => {
|
||||
const d = window.location.hash.replace('#', '')
|
||||
return {
|
||||
component: import(`../components/layout/demo/${d}`),
|
||||
component: import(`../components/upload/demo/${d}`),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -65,6 +65,7 @@ md.core.ruler.push('update_template', function replace ({ tokens }) {
|
||||
let style = ''
|
||||
let scopedStyle = ''
|
||||
let code = ''
|
||||
let sourceCode = ''
|
||||
tokens.forEach(token => {
|
||||
if (token.type === 'html_block') {
|
||||
if (token.content.match(cnReg)) {
|
||||
@ -77,6 +78,7 @@ md.core.ruler.push('update_template', function replace ({ tokens }) {
|
||||
}
|
||||
}
|
||||
if (token.type === 'fence' && token.info === 'html' && token.markup === '```') {
|
||||
sourceCode = token.content
|
||||
code = '````html\n' + token.content + '````'
|
||||
template = fetch(token.content, 'template')
|
||||
script = fetch(token.content, 'script')
|
||||
@ -93,6 +95,7 @@ md.core.ruler.push('update_template', function replace ({ tokens }) {
|
||||
style,
|
||||
us,
|
||||
cn,
|
||||
sourceCode,
|
||||
}
|
||||
jsfiddle = md.utils.escapeHtml(JSON.stringify(jsfiddle))
|
||||
const codeHtml = code ? md.render(code) : ''
|
||||
|
Loading…
Reference in New Issue
Block a user