UNPKG

wangbd-myui

Version:

MY前端基础框架核心组件库

439 lines (406 loc) 14 kB
/** * 表单项基础类, 所有输入组件都继承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' import {cloneDeep, isEqual} from '$ui/utils/util' const _get = require('lodash/get') const _set = require('lodash/set') /** * 插槽 * @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 <template slot={name}>{this.$slots[name]}</template> }) }, // 渲染输入组件 renderComponent(vnode) { // 如果组件不是MyForm的子组件,不需要包裹Item组件 if (!this.myForm) { return vnode } // el-form-item 作用域插槽 const scopedSlots = this.$scopedSlots.error ? { error: props => ( <div class="el-form-item__error my-from__custom-error"> {this.$scopedSlots.error(props)} </div> ) } : null; // 是否已收起 const collapsed = this.isCollapsed() // 是否符合依赖项 const isMatched = this.isMatchDepend() return ( <transition name={this.myForm.collapseEffect}> { (!collapsed && isMatched) ? <FormItem ref="elItem" class="my-form-item" {...{props: this.$attrs, scopedSlots: scopedSlots, style: this.itemStyle}} // 停止回车键事件冒泡 vOn:keyup_native_enter={this.stopEvent} // el-form-item 的prop用name代替 prop={this.name}> { // label 插槽 this.$slots.label ? <template slot="label">{this.$slots.label}</template> : null } {this.$slots.before} {vnode} {this.$slots.after} </FormItem> // Vue组件必须要有一个根DOM,创建一个隐藏占位元素 : <div style={{display: 'none'}}>{this.name}</div> } </transition> ) }, // 继承输入组件暴露的方法 extendMethods(ref, names = []) { if (!ref) return names.forEach(name => { // 子组件的方法加到实例 this[name] = (...args) => { ref[name].apply(ref, args) } }) }, // 设置el-form-item内部的内容区宽度 setContentWidth() { // 字段域没有设置宽度,默认自适应,不需要处理 if (!this.itemWidth || !this.$el) return const content = this.$el.querySelector('.el-form-item__content') const label = this.$el.querySelector('.el-form-item__label') if (content) { const rect = label ? label.getBoundingClientRect() : {width: 0} const itemWidth = this.$el.getBoundingClientRect().width const contentWidth = itemWidth - rect.width setStyle(content, {width: `${contentWidth}px`}) } }, // 阻止回车事件冒泡 stopEvent(e) { if (this.stopEnterEvent) { e.stopPropagation() } }, // 加载选项数据 loadOptions(model) { // 已收起的,不需要处理 if (this.isCollapsed()) return // 如果不符合依赖,不处理 if (!this.isMatchDepend()) return // 数据优先顺序,options > loader > form.dictMap > form.loader if (this.options) { this.currentOptions = cloneDeep(this.options) return } if (this.loader) { this.loading = true this.loader(model, this).then(res => { this.currentOptions = cloneDeep(res) }).finally(() => { this.loading = false }) return } // 无form容器,终止 if (!this.myForm) return if (this.dict) { const {dictMap} = this.myForm const options = (dictMap || {})[this.dict] // 建立与表单的字典数据引用 if (options) { this.currentOptions = options return } } if (this.myForm.loader) { this.loading = true this.myForm.loader(model, this).then(res => { this.currentOptions = cloneDeep(res) }).finally(() => { this.loading = false }) } }, // 响应currentModel改变处理级联加载数据 handleWatch(model) { // 级联上级的值 const val = model[this.cascade] // 与上次的值不一致即重新获取数据 if (!isEqual(this.cascadeValue, val)) { this.fieldValue = this.getDefaultValue() this.cascadeValue = val this.loadOptions(model) } }, // 绑定级联 bindCascade() { if (this.cascade && this.myForm) { const model = this.myForm.currentModel this.cascadeValue = model[this.cascade] this.unwatch = this.$watch('myForm.currentModel', this.handleWatch, {deep: true}) } }, // 销毁级联事件句柄 unbindCascade() { this.unwatch && this.unwatch() } }, mounted() { addResizeListener(this.$el, this.setContentWidth) }, created() { let model = null if (this.myForm) { this.myForm.addItem(this) model = this.myForm.currentModel } this.loadOptions(model, this) this.bindCascade() }, beforeDestroy() { removeResizeListener(this.$el, this.setContentWidth) this.unbindCascade() if (this.myForm) { this.myForm.removeItem(this) } } }