改变ElementUI特定组件弹出浮层的默认父节点

ElementUI中的Cascader级联选择器或者TimePicker,DatePicker选择器点击后的浮层都是直接插入body标签下的,elementUI内部会自动计算一个合适的点将浮层绝对定位上去。vue

可是最近公司在使用ElementUI框架的项目中,由于一个特殊需求不能让这类组件的浮层脱离.vue的功能组件,因此浮层默认插入body下的方式确定是不知足需求的。jquery

ELementUI中的Select选择器组件提供了一个属性能够控制下拉菜单是否插入到body中:
image.png
因此对于select只须要将该属性设置为false便可,但对于Cascader,TimePicker,DatePicker这些组件是没有这个属性的,对于这个问题我思考许久,耗费九成功力,想出了如下这种方式去模拟实现。app

将Cascader组件浮层的父节点设置id为wrap的节点

  • 利用popper-class属性给浮层定义一个类名,后面要能拿到它。给Cascader组件定义一个id,后要用到;
  • 监听Cascader组件自带的visible-change事件,当浮层弹出时开启一个定时器timer去寻找浮层dom节点;
  • 找到浮层节点后将浮层插入指定节点下,这样一来body下的浮层节点就至关于被剪贴到指定节点下了;
  • 此时的Cascader组件仍是弹出的状态,可是本来检测到的浮层被剪切走了,被剪贴到的新地址又没被检测到,因此要关闭再打开,至关于重启一下。由于这里须要手动触发点击事件,而Cascader组件的点击事件是定义在elementUI内部的,用原生很差触发,因此这里我引入了jquery,用它实例上的trigger方法去触发click事件;
  • 在这个过程当中须要加一个锁flag,不让手动触发浮层的时候再进入定时器。

范例:将Cascader组件浮层的父节点设置id为wrap的节点(为突出重点,范例中一些于此无关的代码就省略了)框架

<div class="wrap" id="wrap">
    <el-cascader 
        id="broadcast_type" 
        @visible-change="visibleChange"
        popper-class="floating-layer"//给浮层设置类名 
        :props="{ expandTrigger: 'hover' }" 
        ......//省略
    ></el-cascader>
  </div>
import $ from 'jquery'
export default {
    data() {
        return {
            timer: null,
            flag: true,//锁,控制不让手动触发浮层的时候再进入定时器
            ......//省略
        }
    },
    methods: {
        visibleChange(e) {
            if (e&&this.flag) {
                this.flag = false
                this.timer = setInterval(() => {
                    let floatingLayer = document.getElementsByClassName('floating-layer')[0];
                    if (floatingLayer&&floatingLayer.style.display==="") {//拿到浮层dom节点,可是不能是隐藏状态
                        document.getElementById('wrap').appendChild(floatingLayer)//将浮层插入wrap节点下
                        $(document.getElementById('el_cascader')).trigger('click')//第一次收起浮层
                        $(document.getElementById('el_cascader')).trigger('click')//第二次打开浮层
                        clearInterval(this.timer)
                        this.timer = null
                    }
                },300)
            } else {
                this.flag = true
                clearInterval(this.timer)
                this.timer = null
            }
        },
    }
}

将TimePicker,DatePicker选择器组件浮层的父节点设置id为wrap的节点

若是是TimePicker,DatePicker选择器的话,由于没有相似于Cascader组件的visible-change事件,可是能够focus和blur事件去代替,不过思想都是相同的。
TimePicker,DatePicker选择器在浮层父节点变化以后定位的数值彷佛是基于HTML的,因此在wrap下天然会有误差,所以须要加一个改正数,或者直接将定位该为fixed定位。Cascader组件彷佛没有这个问题,也没有去深究,下面是简化代码。dom

<template>
  <div class="wrap" id="wrap">
    <el-form-item label="开始时间">
        <el-col :span="11">
            <el-date-picker 
                id="broadcast_startdate" 
                @focus="startDateFocus" 
                @blur="startdateBlur" 
                popper-class="broadcast-start-date-drop" 
                type="date" placeholder="选择日期" 
                v-model="form.startDate" 
                style="width: 100%;"
            ></el-date-picker>
        </el-col>
        <el-col class="line" :span="2">-</el-col>
        <el-col :span="11">
            <el-time-picker 
                id="broadcast_starttime"
                @focus="startTimeFocus" 
                @blur="startTimeBlur" 
                popper-class="broadcast-start-time-drop" 
                placeholder="选择时间" 
                arrow-control 
                v-model="form.startTime" 
                style="width: 100%;"
            ></el-time-picker>
        </el-col>
    </el-form-item>
  </div>
</template>

<script>
import $ from 'jquery'
export default {
    data() {
        return {
            startDateTimer: null,
            startDateFlag: true,
            startTimeTimer: null,
            startTimeFlag: true,
            ......//省略
        }
    },
    methods: {
        startDateFocus() {
            if (this.startDateFlag) {
                this.startDateFlag = false
                this.startDateTimer = setInterval(() => {
                    let startDateDrop = document.getElementsByClassName('broadcast-start-date-drop')[0];
                    if (startDateDrop && startDateDrop.style.display==="") {
                        document.getElementById('wrap').appendChild(startDateDrop)
                        $(document.getElementById('broadcast_startdate')).trigger('click')
                        $(document.getElementById('broadcast_startdate')).trigger('click')
                        startDateDrop.style.position = "fixed"//将定位改成fiexd定位
                        clearInterval(this.startDateTimer)
                        this.startDateTimer = null
                    }
                },100)
            }
        },
        startdateBlur() {
            this.startDateFlag = true
            clearInterval(this.startDateTimer)
            this.startDateTimer = null
        },
        startTimeFocus() {
            if (this.startTimeFlag) {
                this.startTimeFlag = false
                this.startTimeTimer = setInterval(() => {
                    let startTimeDrop = document.getElementsByClassName('broadcast-start-time-drop')[0];
                    if (startTimeDrop && startTimeDrop.style.display==="") {
                        document.getElementById('wrap').appendChild(startTimeDrop)
                        $(document.getElementById('broadcast_starttime')).trigger('click')
                        $(document.getElementById('broadcast_starttime')).trigger('click')
                        startTimeDrop.style.position = "fixed"
                        clearInterval(this.startTimeTimer)
                        this.startTimeTimer = null
                    }
                },100)
            }
        },
        startTimeBlur() {
            this.startTimeFlag = true
            clearInterval(this.startTimeTimer)
            this.startTimeTimer = null
        },

    }
}
</script>

当前项目我就用到这几个组件,其它组件也不须要这样特殊处理,不过我以为这个思想是能够行的通的,须要的小伙伴们能够本身搞一下。上面提到的有的内部原理都是个人猜想没有看源码具体论证,反正这种方式解决了个人问题,也不知道还有没有什么好的方法,有想法的同窗们欢迎给我留言讨论。this

相关文章
相关标签/搜索