[封装自己的ui库] 实现message弹窗内容和loading内容(指令+服务)

181 阅读1分钟

1 message 实现

效果

message.gif

实现思路

因为message弹出盒子内容是通过this.$message()的方式实现的,所以我们需要将实现方法绑定到vue实例原型上才行(全局访问)

在用户调用服务时根据ui组件创建一个虚拟Dom, 在虚拟Dom上添加所需的props,然后转为真实Dom插入到目标元素上即可

重点在于获取,实例化一个虚拟dom,转换真实dom

我们先创建好服务文件

// 弹窗方法
export default function ({
    type, message, showClose, close
}) {}

然后将写好的弹窗页面导入

import AlertComm from './index.vue'

import Vue from 'vue'

// 创建弹窗组件继承AlertComm原型
let Alert = Vue.extend(AlertComm);

// 弹窗方法
export default function ({
    // 配置参数
    type, message, showClose, close
}) {}

因为是将弹窗组件挂载到body下的,所以需要获取body

    const body = document.body;

实例一个弹窗组件

import AlertComm from './index.vue'

import Vue from 'vue'

let Alert = Vue.extend(AlertComm);

// 弹窗方法
export default function ({
    type, message, showClose, close
}) {

    const body = document.body;

    // 实例化一个message(此处是一个虚拟Dom,之后我们要想其中更改需要props)
    const alertVnode = new Alert({
        //绑定到一个创建的div(避免绑定到body上而破坏body结构)
        el: document.createElement('div')
    })
}

向body节点对象添加真实Dom和虚拟Dom方便后面调用

import AlertComm from './index.vue'

import Vue from 'vue'

let Alert = Vue.extend(AlertComm);

// 弹窗方法
export default function ({
    type, message, showClose, close
}) {

    const body = document.body;

    // 实例化一个message
    const alertVnode = new Alert({
        el: document.createElement('div')
    })

    // 弹窗组件实例(虚拟)
    body.intsance = alertVnode

    // 添加实例props(相当于给ui组件添加props)
    body.instance.$props.type = type
    body.instance.$props.isShow = true
    body.instance.$props.title = message
    body.instance.$props.myClose = showClose
    body.instance.$props.myWithClose = close

    // 绑定一个真实DOM
    body.alert = alertVnode.$el
    // 真实Dom绑定到body
    body.appendChild(body.alert)
}

在页面中引用

 this.$tyMessage({
                type,
                message: msg,
                showClose,
                close: () => {
                    this.text = '手动关闭了'
                }
            });

弹窗组件

<template>
    <transition>
        <div ref="ani" class='message' v-if="show">
            <Alert :type='myType' showIcon :closable='isclose' :title='myTitle' @close="withclose" />
        </div>
    </transition>
</template>

<script>
import Alert from '../../alert/src/main.vue'
export default {
    name: 'message',
    props: {
        isShow: {
            type: Boolean,
            default: false
        },
        type: {
            type: String,
            default: 'info'
        },
        title: {
            type: String,
            default: '文案'
        },
        myClose: {
            type: Boolean,
            default: false
        },
        myWithClose: {
            type: Function,
        }
    },


    mounted() {
        this.show = true

        setTimeout(() => {
            this.show = false
        }, 5000);
    },

    data() {
        return {
            myType: 'info',
            myTitle: '文案',
            show: false,
            isclose: false
        }
    },

    methods: {
        withclose() {
            this.$props.myWithClose()
        }
    },

    watch: {
        isShow: {
            handler(newV) {
                if (typeof newV == 'boolean') {
                    this.show = newV;
                }
            },
            immediate: true,
        },
        type: {
            handler(newV) {
                this.myType = newV;
            },
            immediate: true,
        },
        title: {
            handler(newV) {
                this.myTitle = newV;
            },
            immediate: true,
        },
        myClose: {
            handler(newV) {
                if (typeof newV == 'boolean') {
                    this.isclose = newV;
                }
            },
            immediate: true,
        }
    },

    components: {
        Alert
    }
}
</script>

<style lang='less' scoped>
@import '../../css/message.less';
</style>

loading实现

效果

loading.gif

服务方式实现

实现

和message差不多

需要注意调用时返回的是一个对象,需要借此对象完成关闭loading的功能

代码

/* 
  全屏loading方法
*/

import Vue from 'vue'
import mark from '../../mark/src/main.vue'

// 创建一个组件继承mark
let Mark = Vue.extend(mark)

let fatherDom = ''

export default {

    start({
        target = '',
        body = false,
        text = '',
        spinner = '',
        background = ''
    }) {

        // 实例化mark
        let markVNode = new Mark({
            el: document.createElement('div')
        })

        // 创建挂载的父节点
        fatherDom = target.$el

        // 判断是否挂载body
        if (body && typeof body == 'boolean') {
            fatherDom = document.body
        }

        // 获取绑定元素的样式用于获取定位 -- 判断是否该为绑定元素添加定位
        let style = document.defaultView.getComputedStyle(fatherDom)

        // 待dom渲染完成后再获取
        Vue.nextTick(() => {
            if (style.position == 'static') {
                fatherDom.style.position = 'relative';
            }
        })

        // 父级上挂载虚拟dom
        fatherDom.instance = markVNode;
        // 父级上挂载真实dom
        fatherDom.loading = markVNode.$el;

        // 再loading组件实例上添加属性
        fatherDom.instance.$props.show = true;
        fatherDom.instance.$props.text = text;
        fatherDom.instance.$props.icon = spinner;
        fatherDom.instance.$props.bjColor = background;

        // 父级上添加loading
        fatherDom.appendChild(fatherDom.loading);

        return this
    },

    close() {
        fatherDom.instance.$props.show = false;
    }
}

指令方式实现

实现

使用自定义指令,需要使用两个生命周期钩子, 还有新提到的获取样式的方式---同专栏有文章提到。 其他并无难点

代码

/* 
    loading加载
*/
import Vue from 'vue';
import Mark from '../../mark/src/main.vue'

let markVdom = Vue.extend(Mark);

export default {
    name: 'tyLoading',
    directive: {
        // 获取目标元素 ,在目标元素下插入一个遮罩元素来控制展示与显示
        bind: (el, bind) => {

            // 实例化一个mark组件挂载到创建的div上
            let markVnode = new markVdom({
                el: document.createElement('div')
            });

            // 获取绑定元素的样式用于获取定位 -- 判断是否该为绑定元素添加定位
            let style = document.defaultView.getComputedStyle(el)

            // 待dom渲染完成后再获取
            Vue.nextTick(() => {
                if (style.position == 'static') {
                    el.style.position = 'relative';
                }
            })

            // el.instance就是mask实例就是我们的loading组件
            el.instance = markVnode;

            // 将真实Dom给到el的mark
            el.mark = markVnode.$el;

            if (bind.modifiers.fullscreen) {
                //挂载到body下
                document.body.appendChild(el.mark)
            } else {
                // 将loading的真实dom添加到绑定指令元素下
                el.appendChild(el.mark);
            }
            // 控制loaing显示隐藏 (有value则去判断)
            bind.value && toggleLoading(el, bind);
        },

        update(el, bind) {
            if (bind.oldValue !== bind.value) {
                toggleLoading(el, bind);
            }
        }
    }
}


const toggleLoading = (el, binding) => {
    // 获取到文本信息
    let text = el.getAttribute('loading-text');
    // 获取图标信息
    const icon = el.getAttribute('loading-spinner');
    // 获取背景色彩信息
    const bjColor = el.getAttribute('loading-background')

    if (binding.value) {
        // 再loading组件实例上添加属性
        el.instance.$props.show = true;
        el.instance.$props.text = text;
        el.instance.$props.icon = icon;
        el.instance.$props.bjColor = bjColor;
    } else {
        el.instance.$props.show = false;
    }
}