form.vue
<template> <form class="el-form" :class="[ labelPosition ? 'el-form--label-' + labelPosition : '', { 'el-form--inline': inline } ]"> <slot></slot> </form> </template> <script> import objectAssign from 'element-ui/src/utils/merge'; export default { name: 'ElForm', componentName: 'ElForm', // 注入自身,子孙组件接收不管层级多深 provide() { return { elForm: this }; }, props: { // model 表单数据对象 model: Object, // rules 表单验证规则 rules: Object, // 表单域标签的位置,如果值为 left 或者 right 时,则需要设置 label-width labelPosition: String, // 表单域标签的宽度,例如 '50px'。作为 Form 直接子元素的 form-item 会继承该值。支持 auto。 labelWidth: String, // 表单域标签的后缀 labelSuffix: { type: String, default: '' }, inline: Boolean, // 是否以行内形式展示校验信息 inlineMessage: Boolean, // 是否在输入框中显示校验结果反馈图标 statusIcon: Boolean, // 是否显示校验错误信息 showMessage: { type: Boolean, default: true }, // 用于控制该表单内组件的尺寸 size: String, // 是否禁用该表单内的所有组件。若设置为 true,则表单内组件上的 disabled 属性不再生效 disabled: Boolean, // 是否在 rules 属性改变后立即触发一次验证 validateOnRuleChange: { type: Boolean, default: true }, // 是否显示必填字段的标签旁边的红色星号 hideRequiredAsterisk: { type: Boolean, default: false } }, watch: { rules() { // remove then add event listeners on form-item after form rules change this.fields.forEach(field => { field.removeValidateEvents(); field.addValidateEvents(); }); if (this.validateOnRuleChange) { this.validate(() => {}); } } }, computed: { autoLabelWidth() { if (!this.potentialLabelWidthArr.length) return 0; const max = Math.max(...this.potentialLabelWidthArr); return max ? `${max}px` : ''; } }, data() { return { fields: [], potentialLabelWidthArr: [] // use this array to calculate auto width }; }, created() { // 接收子组件添加rule验证事件 this.$on('el.form.addField', (field) => { if (field) { this.fields.push(field); } }); /* istanbul ignore next */ // 移除严重事件监听 this.$on('el.form.removeField', (field) => { if (field.prop) { this.fields.splice(this.fields.indexOf(field), 1); } }); }, methods: { // 重置验证 resetFields() { if (!this.model) { console.warn('[Element Warn][Form]model is required for resetFields to work.'); return; } this.fields.forEach(field => { // 每项都重置 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) { console.warn('[Element Warn][Form]model is required for validate to work!'); 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 = objectAssign({}, invalidFields, field); if (typeof callback === 'function' && ++count === this.fields.length) { callback(valid, invalidFields); } }); }); if (promise) { return promise; } }, validateField(props, cb) { props = [].concat(props); const fields = this.fields.filter(field => props.indexOf(field.prop) !== -1); if (!fields.length) { console.warn('[Element Warn]please pass correct props!'); return; } fields.forEach(field => { field.validate('', cb); }); }, // 获取label的下标 getLabelWidthIndex(width) { const index = this.potentialLabelWidthArr.indexOf(width); // it's impossible if (index === -1) { throw new Error('[ElementForm]unpected width ', width); } return index; }, // 重置labelwidth registerLabelWidth(val, oldVal) { if (val && oldVal) { const index = this.getLabelWidthIndex(oldVal); this.potentialLabelWidthArr.splice(index, 1, val); } else if (val) { this.potentialLabelWidthArr.push(val); } }, // 移除此宽度 deregisterLabelWidth(val) { const index = this.getLabelWidthIndex(val); this.potentialLabelWidthArr.splice(index, 1); } } }; </script>
label-warp.vue
<script> export default { props: { isAutoWidth: Boolean, updateAll: Boolean }, // 接收el-form和el-form-item inject: ['elForm', 'elFormItem'], render() { const slots = this.$slots.default; if (!slots) return null; if (this.isAutoWidth) { const autoLabelWidth = this.elForm.autoLabelWidth; const style = {}; if (autoLabelWidth && autoLabelWidth !== 'auto') { const marginLeft = parseInt(autoLabelWidth, 10) - this.computedWidth; if (marginLeft) { style.marginLeft = marginLeft + 'px'; } } return (<div class="el-form-item__label-wrap" style={style}> { slots } </div>); } else { return slots[0]; } }, methods: { // 获取第一个元素的宽度 getLabelWidth() { if (this.$el && this.$el.firstElementChild) { const computedWidth = window.getComputedStyle(this.$el.firstElementChild).width; return Math.ceil(parseFloat(computedWidth)); } else { return 0; } }, updateLabelWidth(action = 'update') { if (this.$slots.default && this.isAutoWidth && this.$el.firstElementChild) { if (action === 'update') { // 第一个元素宽度座位此组件的宽度 this.computedWidth = this.getLabelWidth(); } else if (action === 'remove') { // 调用父组件方法 this.elForm.deregisterLabelWidth(this.computedWidth); } } } }, watch: { // 检测computedWidth变化 computedWidth(val, oldVal) { if (this.updateAll) { // 更新父组件label this.elForm.registerLabelWidth(val, oldVal); // 更新当前item的label this.elFormItem.updateComputedLabelWidth(val); } } }, data() { return { computedWidth: 0 }; }, mounted() { this.updateLabelWidth('update'); }, updated() { this.updateLabelWidth('update'); }, beforeDestroy() { this.updateLabelWidth('remove'); } }; </script>
form-item.vue
<template> <div class="el-form-item" :class="[{ 'el-form-item--feedback': elForm && elForm.statusIcon, 'is-error': validateState === 'error', 'is-validating': validateState === 'validating', 'is-success': validateState === 'success', 'is-required': isRequired || required, 'is-no-asterisk': elForm && elForm.hideRequiredAsterisk }, sizeClass ? 'el-form-item--' + sizeClass : '' ]"> <label-wrap :is-auto-width="labelStyle && labelStyle.width === 'auto'" :update-all="form.labelWidth === 'auto'"> <label :for="labelFor" class="el-form-item__label" :style="labelStyle" v-if="label || $slots.label"> <slot name="label">{{label + form.labelSuffix}}</slot> </label> </label-wrap> <div class="el-form-item__content" :style="contentStyle"> <slot></slot> <transition name="el-zoom-in-top"> <slot v-if="validateState === 'error' && showMessage && form.showMessage" name="error" :error="validateMessage"> <div class="el-form-item__error" :class="{ 'el-form-item__error--inline': typeof inlineMessage === 'boolean' ? inlineMessage : (elForm && elForm.inlineMessage || false) }" > {{validateMessage}} </div> </slot> </transition> </div> </div> </template> <script> import AsyncValidator from 'async-validator'; import emitter from 'element-ui/src/mixins/emitter'; import objectAssign from 'element-ui/src/utils/merge'; import { noop, getPropByPath } from 'element-ui/src/utils/util'; import LabelWrap from './label-wrap'; export default { name: 'ElFormItem', componentName: 'ElFormItem', mixins: [emitter], provide() { return { elFormItem: this }; }, inject: ['elForm'], props: { label: String, labelWidth: String, prop: String, required: { type: Boolean, default: undefined }, rules: [Object, Array], error: String, validateStatus: String, for: String, inlineMessage: { type: [String, Boolean], default: '' }, showMessage: { type: Boolean, default: true }, size: String }, components: { // use this component to calculate auto width LabelWrap }, watch: { error: { immediate: true, handler(value) { this.validateMessage = value; this.validateState = value ? 'error' : ''; } }, // 验证状态 validateStatus(value) { this.validateState = value; } }, computed: { labelFor() { return this.for || this.prop; }, // labelStyle的样式 labelStyle() { const ret = {}; if (this.form.labelPosition === 'top') return ret; const labelWidth = this.labelWidth || this.form.labelWidth; if (labelWidth) { ret.width = labelWidth; } return ret; }, // 内容的样式 contentStyle() { const ret = {}; const label = this.label; if (this.form.labelPosition === 'top' || this.form.inline) return ret; if (!label && !this.labelWidth && this.isNested) return ret; const labelWidth = this.labelWidth || this.form.labelWidth; if (labelWidth === 'auto') { if (this.labelWidth === 'auto') { ret.marginLeft = this.computedLabelWidth; } else if (this.form.labelWidth === 'auto') { ret.marginLeft = this.elForm.autoLabelWidth; } } else { ret.marginLeft = labelWidth; } return ret; }, form() { let parent = this.$parent; let parentName = parent.$options.componentName; while (parentName !== 'ElForm') { if (parentName === 'ElFormItem') { this.isNested = true; } parent = parent.$parent; parentName = parent.$options.componentName; } return parent; }, // 获取当前项的值 fieldValue() { const model = this.form.model; if (!model || !this.prop) { return; } let path = this.prop; if (path.indexOf(':') !== -1) { path = path.replace(/:/, '.'); } return getPropByPath(model, path, true).v; }, // 是否必填 isRequired() { let rules = this.getRules(); let isRequired = false; if (rules && rules.length) { rules.every(rule => { if (rule.required) { isRequired = true; return false; } return true; }); } return isRequired; }, _formSize() { return this.elForm.size; }, elFormItemSize() { return this.size || this._formSize; }, sizeClass() { return this.elFormItemSize || (this.$ELEMENT || {}).size; } }, data() { return { validateState: '', validateMessage: '', validateDisabled: false, validator: {}, isNested: false, computedLabelWidth: '' }; }, methods: { // 验证 validate(trigger, callback = noop) { this.validateDisabled = false; // 获取触发方式是否匹配 const rules = this.getFilteredRule(trigger); if ((!rules || rules.length === 0) && this.required === undefined) { callback(); return true; } this.validateState = 'validating'; const descriptor = {}; if (rules && rules.length > 0) { rules.forEach(rule => { delete rule.trigger; }); } descriptor[this.prop] = rules; const validator = new AsyncValidator(descriptor); const model = {}; model[this.prop] = this.fieldValue; validator.validate(model, { firstFields: true }, (errors, invalidFields) => { this.validateState = !errors ? 'success' : 'error'; this.validateMessage = errors ? errors[0].message : ''; callback(this.validateMessage, invalidFields); this.elForm && this.elForm.$emit('validate', this.prop, !errors, this.validateMessage || null); }); }, // 清除验证 clearValidate() { this.validateState = ''; this.validateMessage = ''; this.validateDisabled = false; }, // 重置 resetField() { this.validateState = ''; this.validateMessage = ''; let model = this.form.model; let value = this.fieldValue; let path = this.prop; if (path.indexOf(':') !== -1) { path = path.replace(/:/, '.'); } let prop = getPropByPath(model, path, true); this.validateDisabled = true; if (Array.isArray(value)) { prop.o[prop.k] = [].concat(this.initialValue); } else { prop.o[prop.k] = this.initialValue; } // 通知ElTimeSelect,执行fieldReset事件,传值initialValue this.broadcast('ElTimeSelect', 'fieldReset', this.initialValue); }, // 获取rules getRules() { let formRules = this.form.rules; // 当前组件rules,默认为空[] const selfRules = this.rules; const requiredRule = this.required !== undefined ? { required: !!this.required } : []; // 获取当前项对象 const prop = getPropByPath(formRules, this.prop || ''); formRules = formRules ? (prop.o[this.prop || ''] || prop.v) : []; return [].concat(selfRules || formRules || []).concat(requiredRule); }, // getFilteredRule(trigger) { const rules = this.getRules(); return rules.filter(rule => { if (!rule.trigger || trigger === '') return true; if (Array.isArray(rule.trigger)) { return rule.trigger.indexOf(trigger) > -1; } else { return rule.trigger === trigger; } }).map(rule => objectAssign({}, rule)); }, // 触发失焦 onFieldBlur() { this.validate('blur'); }, onFieldChange() { if (this.validateDisabled) { this.validateDisabled = false; return; } // 触发change事件 this.validate('change'); }, updateComputedLabelWidth(width) { this.computedLabelWidth = width ? `${width}px` : ''; }, // 添加验证事件 addValidateEvents() { const rules = this.getRules(); if (rules.length || this.required !== undefined) { // 监听form表单blur事件,触发onFieldBlur this.$on('el.form.blur', this.onFieldBlur); // 监听form表单chang事件 this.$on('el.form.change', this.onFieldChange); } }, // 移除验证事件 removeValidateEvents() { this.$off(); } }, mounted() { if (this.prop) { // 通知ElForm组件el.form.addField方法,传递this this.dispatch('ElForm', 'el.form.addField', [this]); // 初始化的值 let initialValue = this.fieldValue; if (Array.isArray(initialValue)) { initialValue = [].concat(initialValue); } /** 语法: Object.defineProperty(obj, prop, descriptor) 参数说明: obj:必需。目标对象 prop:必需。需定义或修改的属性的名字 descriptor:必需。目标属性所拥有的特性 返回值: 传入函数的对象。即第一个参数obj */ // 设置初始值 Object.defineProperty(this, 'initialValue', { value: initialValue }); // 设置监听事件 this.addValidateEvents(); } }, // 销毁 beforeDestroy() { this.dispatch('ElForm', 'el.form.removeField', [this]); } }; </script>