vue封裝公用彈出框方法,實現點擊出現操作彈出框


如上圖所示,這次要實現一個點擊出現操作彈框的效果;並將這個功能封裝成一個函數,便於在項目的多個地方使用。

具體思路是:

    封裝一個組件,組件保護一個插槽,我們可以根據不同的場景,利用插槽隨意在這個彈框里插入任何元素,這個彈框顯示時根據我鼠標的點擊位置,定位彈窗的位置,並在組件里面監聽鼠標抬起事件,觸發事件時將彈窗隱藏;

接着在函數中利用createElement和appendChild方法將彈出框創建並插入到頁面中;


具體實現:

首先,我們先寫一個demo組件

在點擊出現彈出框的元素上把事件對象數據傳遞一下,以便獲取點擊時鼠標的數據,以此確定彈出框的位置

// 文件路徑參考: src > views > demo> index.vue

<template>
    <div class="demo-wrapper">
        <div class="demo-div">
            <span>更多功能</span>
            <i class="xk-icon xk-ellipsis" @click.stop='showMenu($event)'></i> // 為了獲取鼠標位置,這里把事件對象數據傳遞一下
        </div>
    </div>
</template>

<script lang="ts">
    import { Vue, Component, Prop, Watch} from "vue-property-decorator";
    @Component({

    })
    export default class articleView extends Vue {
        showMenu($event:any){
          // 點擊時出現彈出框
        }
    };
</script>

接着是彈出框里面的組件

組件隨便命名為ActionList,組件里面把把列表數據及點擊事件都基於父組件傳遞的值而定,由於只是小demo,所以我們傳遞的menu數據數組只是簡單的字符串數組

// 文件路徑參考: src > components > ActionList > index.vue

<template>
    <ul class="menu-wrapper">
        <li
            class="menu-item"
            v-for="item in menu"
            :key="item"
            @click="handleClick(item)"
        >
            {{ item }}
        </li>
    </ul>
</template>

<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
@Component
export default class ActionList extends Vue {
    @Prop() menu: string[];
    handleClick(str: string) {
        this.$emit('click', str);
    }
}
</script>

接着,開始着手寫彈框組件

  • 彈框組件的顯示隱藏用v-show控制,為什么不用v-if ?因為這里我監聽了mouseup事件來讓彈框隱藏,如果在插槽里的元素綁定事件,比如點擊事件,用v-if 的話,點擊插槽里的元素時,彈框先消失,插槽里的點擊事件就不會生效了。
  • handleOpen事件里我們根據鼠標點擊位置定位彈框位置。
// 文件路徑參考: src > components > PublicModel > index.vue
<template>
    <div class="dropdown-menu" :style="style" v-show='showModel'>
        <slot></slot>
    </div>
</template>

<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
interface IStyle {
    left?: string;
    right?: string;
    top?: string;
    bottom?: string;
}
@Component
export default class PublicModel extends Vue {
    showModel:boolean = false;
    style:IStyle = {};

    // 組件顯示時
    handleOpen($event:any){
        const { clientWidth, clientHeight, scrollWidth, scrollHeight } = document.body || document.documentElement;
        const { pageX, pageY, clientX, clientY } = $event;
        let style:IStyle = {} 
        if(clientX > (clientWidth * 2)/3 ){
            style.right = scrollWidth - pageX + 10 + 'px';
        }else{
            style.left = pageX+10+'px'
        }
        if(clientY > (clientHeight * 2) / 3 ){
            style.bottom = scrollHeight - pageY + 10 + 'px';
        }else{
            style.top = pageY + 10 + "px"
        }
        this.style = style;
        this.showModel = true;
        document.addEventListener('mouseup',this.closeModel)
    }

    // 隱藏關閉此組件
    closeModel(){
        this.showModel = false;
        document.removeEventListener('mouseup', this.closeModel);
    }

    // 組件銷毀生命周期
    destroyed(){
        document.removeEventListener('mouseup', this.closeModel);
    }
}
</script>

接着,重點,公用封裝函數

我們要在demo組件點擊時觸發這個函數,即在demo組件里的showMenu事件觸發函數,這個函數要利用createElement和appendChild方法將彈出框創建並插入到頁面中。
因為是點擊時創建並插入元素,所以為了性能優化,避免有惡意瘋狂點擊,不斷創建和插入元素,我們利用throttle-debounce插件做一個節流。
先直接看代碼,其他注釋寫在了代碼里,函數名隨意取:ModelFun

// 文件路徑參考: src > components > PublicModel > index.ts
import Vue from 'vue';
import PublicModel from './index.vue';  // 導入上面所寫的彈框組件
const throttleDebounce = require('throttle-debounce'); // throttle-debounce插件
const debounce = throttleDebounce.debounce;
const PublicModelConstructor = Vue.extend(PublicModel);
let instance:any;
const initInstance = () => {
    instance = new PublicModelConstructor({
        el: document.createElement('div'),
    });
    document.body.appendChild(instance.$el);
}
const insertInstanceSlot = (slotVNode:any, $event:any) => { // 這里兩個參數一個是彈框里插槽的組件,還有就是點擊的事件對象(方便定位彈框位置)
    if(!instance){
        initInstance()
    }
    instance.$slots.default = [slotVNode]; // 將傳遞過來的插槽組件插入彈框組件中
    instance.handleOpen($event) // 觸發彈框組件(見上一段代碼)的彈框獲取定位信息並顯示的事件
}
const ModelFun = debounce(200, false, insertInstanceSlot)  
// 使用throttle-debounce里的debounce保證在一系列調用時間中回調函數只執行一次,這里是200毫秒                                                           
// 第二個參數為false時,在點擊時會在200毫秒后再執行callback(即insertInstanceSlot),但為true時,會立即先執行一次;

export default ModelFun

最后,回過頭完善一下demo組件

利用vue的 $createElement 將ActionList組件插入彈框中,並將數據和事件傳遞給ActionList組件,這里我們傳遞的事件是簡單的彈出我們點擊的數據

// 文件路徑參考: src > views > demo> index.vue
<template>
    <div class="demo-wrapper">
        <div class="demo-div">
            <span>更多功能</span>
            <i class="xk-icon xk-ellipsis" @click.stop='showMenu($event)'></i>
        </div>
    </div>
</template>

<script lang="ts">
    import { Vue, Component, Prop, Watch} from "vue-property-decorator";
    import ActionList from "@/components/ActionList/index.vue";
    import modelFun from "@/components/PublicModel/index";
    @Component({

    })
    export default class articleView extends Vue {
        menuList: string[] = ['菜單1','菜單2','菜單3'];
        menuClick(name:string){ // 彈框里插槽的點擊事件
            this.$message({message:name,type:'success'})
        }
        showMenu($event:any){
            modelFun(
                this.$createElement(
                    ActionList,
                    {
                        props: { menu:this.menuList },
                        on:{
                            click: this.menuClick,
                        }
                    }
                ),
                $event
            )
        }
    };
</script>

至此,效果如下


最后,我們利用element ui 的 tree 組件結合我們封裝的彈框看一下效果

代碼:

<template>
    <div class="demo-wrapper">
        <el-tree
                :data="data"
          node-key="id"
                :default-expand-all="true"
          :expand-on-click-node="false"
          show-checkbox
            >
                <div class="custom-tree-node tree-item" iv slot-scope="{ node }">
                    <span>{{ node.label }}</span>
                    <span
                        class="action"
                        @click.stop="showMenu($event)"
                    >
                        <i class="el-icon-more"></i>
                    </span>
                </div>
            </el-tree>
    </div>
</template>

<script lang="ts">
    import { Vue, Component, Prop, Watch} from "vue-property-decorator";
    import ActionList from "@/components/ActionList/index.vue";
    import modelFun from "@/components/PublicModel/index";
    @Component({

    })
    export default class articleView extends Vue {
        menuList: string[] = ['菜單1','菜單2','菜單3'];
        data:any[] = [{
        id: 1,
        label: '一級 1',
        children: [{
          id: 4,
          label: '二級 1-1',
          children: [{
            id: 9,
            label: '三級 1-1-1'
          }, {
            id: 10,
            label: '三級 1-1-2'
          }]
        }]
      }, {
        id: 2,
        label: '一級 2',
        children: [{
          id: 5,
          label: '二級 2-1'
        }, {
          id: 6,
          label: '二級 2-2'
        }]
      }, {
        id: 3,
        label: '一級 3',
        children: [{
          id: 7,
          label: '二級 3-1'
        }, {
          id: 8,
          label: '二級 3-2'
        }]
      }];
        menuClick(name:string){
            console.log(name)
            this.$message({message:name,type:'success'})
        }
        showMenu($event:any){
            modelFun(
                this.$createElement(
                    ActionList,
                    {
                        props: { menu:this.menuList },
                        on:{
                            click: this.menuClick,
                        }
                    }
                ),
                $event
            )
        }
    };
</script>

效果:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM