2020-07-06 14:31:07 +00:00
|
|
|
|
import { inject, provide } from 'vue';
|
2020-03-15 05:12:47 +00:00
|
|
|
|
import PropTypes from '../_util/vue-types';
|
|
|
|
|
import classNames from 'classnames';
|
|
|
|
|
import { ColProps } from '../grid/Col';
|
|
|
|
|
import isRegExp from 'lodash/isRegExp';
|
|
|
|
|
import warning from '../_util/warning';
|
|
|
|
|
import FormItem from './FormItem';
|
2020-07-06 14:31:07 +00:00
|
|
|
|
import { initDefaultProps, getListeners, getSlot } from '../_util/props-util';
|
2020-03-15 05:12:47 +00:00
|
|
|
|
import { ConfigConsumerProps } from '../config-provider';
|
2020-07-10 10:26:35 +00:00
|
|
|
|
import { getParams } from './utils';
|
2020-03-15 05:12:47 +00:00
|
|
|
|
|
|
|
|
|
export const FormProps = {
|
|
|
|
|
layout: PropTypes.oneOf(['horizontal', 'inline', 'vertical']),
|
|
|
|
|
labelCol: PropTypes.shape(ColProps).loose,
|
|
|
|
|
wrapperCol: PropTypes.shape(ColProps).loose,
|
|
|
|
|
colon: PropTypes.bool,
|
|
|
|
|
labelAlign: PropTypes.oneOf(['left', 'right']),
|
|
|
|
|
prefixCls: PropTypes.string,
|
|
|
|
|
hideRequiredMark: PropTypes.bool,
|
|
|
|
|
model: PropTypes.object,
|
|
|
|
|
rules: PropTypes.object,
|
2020-04-22 03:52:24 +00:00
|
|
|
|
validateMessages: PropTypes.any,
|
2020-03-15 05:12:47 +00:00
|
|
|
|
validateOnRuleChange: 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,
|
2020-07-10 10:26:35 +00:00
|
|
|
|
// 提交失败自动滚动到第一个错误字段
|
|
|
|
|
scrollToFirstError: PropTypes.bool,
|
2020-03-15 05:12:47 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const Form = {
|
2020-03-16 04:06:55 +00:00
|
|
|
|
name: 'AFormModel',
|
2020-07-06 14:31:07 +00:00
|
|
|
|
inheritAttrs: false,
|
2020-03-15 05:12:47 +00:00
|
|
|
|
props: initDefaultProps(FormProps, {
|
|
|
|
|
layout: 'horizontal',
|
|
|
|
|
hideRequiredMark: false,
|
|
|
|
|
colon: true,
|
|
|
|
|
}),
|
|
|
|
|
Item: FormItem,
|
|
|
|
|
created() {
|
|
|
|
|
this.fields = [];
|
2020-07-06 14:31:07 +00:00
|
|
|
|
this.form = undefined;
|
|
|
|
|
provide('FormContext', this);
|
2020-03-15 05:12:47 +00:00
|
|
|
|
},
|
2020-07-06 14:31:07 +00:00
|
|
|
|
setup() {
|
2020-03-15 05:12:47 +00:00
|
|
|
|
return {
|
2020-07-06 14:31:07 +00:00
|
|
|
|
configProvider: inject('configProvider', ConfigConsumerProps),
|
2020-03-15 05:12:47 +00:00
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
watch: {
|
|
|
|
|
rules() {
|
|
|
|
|
if (this.validateOnRuleChange) {
|
|
|
|
|
this.validate(() => {});
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
computed: {
|
|
|
|
|
vertical() {
|
|
|
|
|
return this.layout === 'vertical';
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
methods: {
|
|
|
|
|
addField(field) {
|
|
|
|
|
if (field) {
|
|
|
|
|
this.fields.push(field);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
removeField(field) {
|
|
|
|
|
if (field.prop) {
|
|
|
|
|
this.fields.splice(this.fields.indexOf(field), 1);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
onSubmit(e) {
|
|
|
|
|
if (!getListeners(this).submit) {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
} else {
|
|
|
|
|
this.$emit('submit', e);
|
|
|
|
|
}
|
|
|
|
|
},
|
2020-07-10 10:26:35 +00:00
|
|
|
|
resetFields(props = this.fields) {
|
2020-03-15 05:12:47 +00:00
|
|
|
|
if (!this.model) {
|
2020-03-16 04:06:55 +00:00
|
|
|
|
warning(false, 'FormModel', 'model is required for resetFields to work.');
|
2020-03-15 05:12:47 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
2020-07-10 10:26:35 +00:00
|
|
|
|
const fields = props.length
|
|
|
|
|
? typeof props === 'string'
|
|
|
|
|
? this.fields.filter(field => props === field.prop)
|
|
|
|
|
: this.fields.filter(field => props.indexOf(field.prop) > -1)
|
|
|
|
|
: this.fields;
|
|
|
|
|
fields.forEach(field => {
|
2020-03-15 05:12:47 +00:00
|
|
|
|
field.resetField();
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
clearValidate(props = []) {
|
|
|
|
|
const fields = props.length
|
|
|
|
|
? typeof props === 'string'
|
|
|
|
|
? this.fields.filter(field => props === field.prop)
|
|
|
|
|
: this.fields.filter(field => props.indexOf(field.prop) > -1)
|
|
|
|
|
: this.fields;
|
|
|
|
|
fields.forEach(field => {
|
|
|
|
|
field.clearValidate();
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
validate(callback) {
|
|
|
|
|
if (!this.model) {
|
2020-03-16 04:06:55 +00:00
|
|
|
|
warning(false, 'FormModel', 'model is required for resetFields to work.');
|
2020-03-15 05:12:47 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
let promise;
|
|
|
|
|
// if no callback, return promise
|
|
|
|
|
if (typeof callback !== 'function' && window.Promise) {
|
|
|
|
|
promise = new window.Promise((resolve, reject) => {
|
|
|
|
|
callback = function(valid) {
|
|
|
|
|
valid ? resolve(valid) : reject(valid);
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
let valid = true;
|
|
|
|
|
let count = 0;
|
|
|
|
|
// 如果需要验证的fields为空,调用验证时立刻返回callback
|
|
|
|
|
if (this.fields.length === 0 && callback) {
|
|
|
|
|
callback(true);
|
|
|
|
|
}
|
|
|
|
|
let invalidFields = {};
|
|
|
|
|
this.fields.forEach(field => {
|
|
|
|
|
field.validate('', (message, field) => {
|
|
|
|
|
if (message) {
|
|
|
|
|
valid = false;
|
|
|
|
|
}
|
|
|
|
|
invalidFields = Object.assign({}, invalidFields, field);
|
|
|
|
|
if (typeof callback === 'function' && ++count === this.fields.length) {
|
|
|
|
|
callback(valid, invalidFields);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
if (promise) {
|
|
|
|
|
return promise;
|
|
|
|
|
}
|
|
|
|
|
},
|
2020-07-10 10:26:35 +00:00
|
|
|
|
scrollToField() {},
|
|
|
|
|
getFieldsValue(allFields) {
|
|
|
|
|
return allFields.map(({ prop, fieldValue }) => {
|
|
|
|
|
return { [prop]: fieldValue };
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
validateFields() {
|
|
|
|
|
this.validateField(...arguments);
|
|
|
|
|
},
|
|
|
|
|
validateField(ns, opt, cb) {
|
|
|
|
|
const pending = new Promise((resolve, reject) => {
|
|
|
|
|
const params = getParams(ns, opt, cb);
|
|
|
|
|
const { names, options } = params;
|
|
|
|
|
let { callback } = params;
|
|
|
|
|
if (!callback || typeof callback === 'function') {
|
|
|
|
|
const oldCb = callback;
|
|
|
|
|
callback = (errors, values) => {
|
|
|
|
|
if (oldCb) {
|
|
|
|
|
oldCb(errors, values);
|
|
|
|
|
} else if (errors) {
|
|
|
|
|
reject({ errors, values });
|
|
|
|
|
} else {
|
|
|
|
|
resolve(values);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
const allFields = names
|
|
|
|
|
? this.fields.filter(field => names.indexOf(field.prop) !== -1)
|
|
|
|
|
: this.fields;
|
|
|
|
|
const fields = allFields.filter(field => {
|
|
|
|
|
const rules = field.getFilteredRule('');
|
|
|
|
|
return rules && rules.length;
|
|
|
|
|
});
|
|
|
|
|
if (!fields.length) {
|
|
|
|
|
callback(null, this.getFieldsValue(allFields));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!('firstFields' in options)) {
|
|
|
|
|
options.firstFields = allFields.filter(field => {
|
|
|
|
|
return !!field.validateFirst;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
let invalidFields = {};
|
|
|
|
|
let valid = true;
|
|
|
|
|
let count = 0;
|
|
|
|
|
fields.forEach(field => {
|
|
|
|
|
field.validate('', (message, field) => {
|
|
|
|
|
if (message) {
|
|
|
|
|
valid = false;
|
|
|
|
|
}
|
|
|
|
|
// TODO:
|
|
|
|
|
invalidFields = Object.assign({}, invalidFields, field);
|
|
|
|
|
if (typeof callback === 'function' && ++count === fields.length) {
|
|
|
|
|
callback(valid, invalidFields);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
pending.catch(e => {
|
|
|
|
|
if (console.error && process.env.NODE_ENV !== 'production') {
|
|
|
|
|
console.error(e);
|
|
|
|
|
}
|
|
|
|
|
return e;
|
2020-03-15 05:12:47 +00:00
|
|
|
|
});
|
2020-07-10 10:26:35 +00:00
|
|
|
|
return pending;
|
|
|
|
|
// names = [].concat(names);
|
|
|
|
|
// const fields = this.fields.filter(field => names.indexOf(field.prop) !== -1);
|
|
|
|
|
// if (!fields.length) {
|
|
|
|
|
// warning(false, 'FormModel', 'please pass correct props!');
|
|
|
|
|
// return;
|
|
|
|
|
// }
|
|
|
|
|
// fields.forEach(field => {
|
|
|
|
|
// field.validate('', cb);
|
|
|
|
|
// });
|
2020-03-15 05:12:47 +00:00
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
render() {
|
2020-07-06 14:31:07 +00:00
|
|
|
|
const { prefixCls: customizePrefixCls, hideRequiredMark, layout, onSubmit } = this;
|
2020-03-15 05:12:47 +00:00
|
|
|
|
const getPrefixCls = this.configProvider.getPrefixCls;
|
|
|
|
|
const prefixCls = getPrefixCls('form', customizePrefixCls);
|
2020-07-06 14:31:07 +00:00
|
|
|
|
const { class: className, onSubmit: originSubmit, ...restProps } = this.$attrs;
|
2020-03-15 05:12:47 +00:00
|
|
|
|
|
2020-07-06 14:31:07 +00:00
|
|
|
|
const formClassName = classNames(prefixCls, className, {
|
2020-03-15 05:12:47 +00:00
|
|
|
|
[`${prefixCls}-horizontal`]: layout === 'horizontal',
|
|
|
|
|
[`${prefixCls}-vertical`]: layout === 'vertical',
|
|
|
|
|
[`${prefixCls}-inline`]: layout === 'inline',
|
|
|
|
|
[`${prefixCls}-hide-required-mark`]: hideRequiredMark,
|
|
|
|
|
});
|
|
|
|
|
return (
|
2020-07-06 14:31:07 +00:00
|
|
|
|
<form onSubmit={onSubmit} class={formClassName} {...restProps}>
|
|
|
|
|
{getSlot(this)}
|
2020-03-15 05:12:47 +00:00
|
|
|
|
</form>
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export default Form;
|