/** * 表单项基础类, 所有输入组件都继承Base * @module $ui/components/my-form/src/Base */ import { FormItem } from "element-ui"; import { setStyle } from "element-ui/lib/utils/dom"; import { addResizeListener, removeResizeListener } from "element-ui/lib/utils/resize-event"; const _get = require("lodash/get"); const _set = require("lodash/set"); const _isEqual = require("lodash/isEqual"); const _cloneDeep = require("lodash/cloneDeep"); /** * 深拷贝 * @param {*} value 要深拷贝的值 * @return {*} 返回拷贝后的值 */ export function cloneDeep(value) { return _cloneDeep(value); } /** * 判断两个对象是否相等 * @param {*} object 对象1 * @param {*} other 对象2 * @return {boolean} */ export function isEqual(object, other) { return _isEqual(object, other); } /** * 插槽 * @member slots * @property {string} before 输入组件前面的内容,仅当父组件是MyForm有效 * @property {string} after 输入组件后面的内容,仅当父组件是MyForm有效 * @property {string} label 定义字段的label内容,仅当父组件是MyForm有效 * @property {string} error 作用域插槽,定义验证错误提示内容,仅当父组件是MyForm有效 */ export default { inject: { myForm: { default: null } }, components: { FormItem }, /** * 属性参数 * @member props * @property {string} [name] 表单域 model 字段名称, 等价于 el-form-item 的 prop 参数 * @property {string} [width] 宽度,css属性,支持像素,百分比和表达式,也可以在MyForm中统一设置itemWidth * @property {object} [props] 输入组件参数对象,即 element 组件的参数 * @property {Array} [options] 选项数据,数据优先顺序,options > loader > form.dictMap > form.loader * @property {Object} [keyMap] 选项数据对象属性名称映射, 默认:{id, parentId, label, value} * @property {boolean} [collapsible] 可收起 * @property {boolean} [stopEnterEvent] 阻止回车事件冒泡 * @property {string} [depend] 依赖字段名称 * @property {*} [dependValue] 依赖字段的值,即依赖字段的值等于该值才会显示 * @property {string} [cascade] 级联的上级字段名称,需要与loader配合加载数据 * @property {Function} [loader] 加载数据函数,必须返回Promise * @property {string} [dict] 字典名称,只是标识,需要与loader配合 或 表单的dictMap加载数据 * @property {boolean} [disabled] 禁用 * @property {boolean} [readonly] 只读 * @property {string} [placeholder] 占位文本 * */ props: { // 表单域 model 字段名称 name: String, // 宽度,支持像素,百分比和表达式 width: String, // 输入组件参数对象 props: Object, // 选项数据,数据优先顺序,options > loader > form.dictMap > form.loader options: Array, // 选项数据对象属性名称映射 keyMap: { type: Object, default() { return { id: "id", label: "label", value: "value", disabled: "disabled", parentId: "parentId" }; } }, // 可折叠 collapsible: Boolean, // 阻止回车事件冒泡 stopEnterEvent: Boolean, // 依赖字段名称 depend: String, // 依赖字段的值,即依赖字段的值等于该值才会显示 dependValue: [String, Number, Boolean, Object, Array, Function], // 级联的上级字段名称,需要与loader配合加载数据 cascade: String, // 加载数据函数,必须返回Promise loader: Function, // 字典名称,只是标识,需要与loader配合 或 表单的dictMap加载数据 dict: String, // 禁用 disabled: Boolean, // 只读 readonly: Boolean, // 占位文本 placeholder: String, // 尺寸 size: String }, data() { return { // 级联的值缓存 cascadeValue: null, // 当前选项数据 currentOptions: [], // 正在调用loader loading: false }; }, computed: { // 如果有name参数,并且是MyForm的子组件,即与MyForm的currentModel作双向绑定 // 否则与组件自身的value作双向绑定 fieldValue: { get() { if (this.name && this.myForm) { const { currentModel } = this.myForm; return _get(currentModel, this.name, this.getDefaultValue()); } else { return this.value || this.getDefaultValue(); } }, set(val) { if (this.name && this.myForm) { const { currentModel } = this.myForm; const model = cloneDeep(currentModel); _set(model, this.name, val); if (!isEqual(currentModel, model)) { this.myForm.currentModel[this.name] = model[this.name]; this.myForm.currentModel = model; } } else { this.$emit("input", val); } } }, // 字段域的宽度 itemWidth() { // 优先取自身设置的宽度,没有就取父组件设置的公共设置宽度 return ( this.width || (this.myForm && this.myForm.itemWidth ? this.myForm.itemWidth : null) ); }, // 字段域样式 itemStyle() { return { width: this.itemWidth }; }, // 输入框组件参数 innerProps() { return { disabled: this.disabled, readonly: this.readonly, placeholder: this.placeholder, size: this.size, ...this.props }; } }, watch: { itemWidth: { immediate: true, handler() { this.$nextTick(() => { this.setContentWidth(); }); } }, "myForm.currentCollapsed"(val) { const { resetCollapsed, model } = this.myForm; // 收起时重置表单项值 if (val && resetCollapsed && model && this.collapsible) { this.$nextTick(() => { // this.fieldValue = this.myForm.model[this.name] this.fieldValue = _get(this.myForm.model, this.name, this.getDefaultValue()); }); } // 开启了折叠功能 if (this.collapsible) { // 折叠时先要清除事件句柄,因为原先的dom即将发生改变 if (val) { removeResizeListener(this.$el, this.setContentWidth); } else { // 如果没有加载过选项数据,触发加载函数 if (!this.currentOptions || this.currentOptions.length === 0) { this.loadOptions(this.myForm.currentModel, this); } // 展开时,待DOM生成后,重新注册事件句柄 this.$nextTick(() => { addResizeListener(this.$el, this.setContentWidth); this.setContentWidth(); }); } } }, // options 为了提高性能,不设置deep options: { immediate: true, handler(val) { this.currentOptions = cloneDeep(val) || []; // options改变后,会触发表单验证,这里需要清楚验证错误信息 this.$nextTick(() => { this.clearValidate(); }); } } }, methods: { // 获取表单项的默认值,不同组件有不同的默认值,可在具体的组件重写这个函数 getDefaultValue() { return ""; }, // 重置字段 resetField() { this.$refs.elItem && this.$refs.elItem.resetField(); }, // 清除验证错误信息 clearValidate() { this.$refs.elItem && this.$refs.elItem.clearValidate(); }, isCollapsed() { if (!this.myForm) return false; const { collapsible, currentCollapsed } = this.myForm; // 是否已收起 return collapsible && currentCollapsed && this.collapsible; }, isMatchDepend() { // 没有设置依赖,即忽略,当已匹配处理 if (!this.depend || !this.myForm) return true; const model = this.myForm.currentModel; // 依赖不支持 按路径查找 const value = model[this.depend]; let isMatch = true; // 如果 dependValue 是函数,执行回调函数返回布尔值 if (typeof this.dependValue === "function") { isMatch = this.dependValue(value, model, this); } else { // 以上都不符合,即检验 dependValue 与 currentModel中的依赖属性是否一致 isMatch = isEqual(this.dependValue, value); } // 清除依赖不符合字段的值 if (!isMatch && this.name && model[this.name]) { this.fieldValue = this.getDefaultValue(); delete model[this.name]; } return isMatch; }, // 传递给输入组件的插槽 createSlots(slots = []) { return slots.map(name => { return {this.$slots[name]}; }); }, // 渲染输入组件 renderComponent(vnode) { // 如果组件不是MyForm的子组件,不需要包裹Item组件 if (!this.myForm) { return vnode; } // el-form-item 作用域插槽 const scopedSlots = this.$scopedSlots.error ? { error: props => (