改變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

相關文章
相關標籤/搜索