唯心主义蠢货的[UI组件_2]如何写一个全局通过函数调用的弹窗

实现效果

在这里插入图片描述

解决的问题

  • vue.use的使用/ vue.extend+mount 和 vue.component的区别
  • 如何使用prototype实现全局通过函数调用
  • 弹窗效果的实现,以及对应弹窗的排版和回调函数的执行
  • 如何实现弹窗的拖拽效果 (v-directive 拖拽指令)

问题相关知识

  • vue.use

    • vue.install源码分析
    /* istanbul ignore if */
    if (plugin.installed) {
      return
    }
    // additional parameters
    var args = toArray(arguments, 1);
    args.unshift(this); //unshift -> 向数组中一个或多个元素,并返回新的数组长度
    if (typeof plugin.install === 'function') {
      plugin.install.apply(plugin, args);
    } else if (typeof plugin === 'function') {
      plugin.apply(null, args);
    }
    plugin.installed = true;
    return this
    };
    
    从源码可知,vue.use是一个注册插件或执行install方法,并将vue作为第一个参数传入插件的方法,一般为内部有自身逻辑的功能模块
  • vue.prototype.xx的使用
    在vue的原型上添加一个属性,然后即可以在全局上进行调用,一般为逻辑简单的功能方法,或者特定操作
  • vue.extend + mount
    extend为组件模板拓展,传入一个组件,可以生成一个模板类
    然后通过new 模板类的方式进行 新组件的创建,通过propsdata即可向其内部传入参数
    然后xx.mount() 则是挂载的操作,即将新生成的组件挂载到dom树上进行生成

代码实现

vue文件

弹窗组件的dom结构和css较为简单,具体结构如下
在这里插入图片描述

/*
类名用下划线命名法  小写加下划线
变量用驼峰命名法  第一个单词的首字母小写 第二个单词的首字母大写
方法用首字母大写
*/
<template>
    <transition name="fadeIn">
        <div id="message_bg" v-if="visible">
            <div id="message_body"  @mousemove="changePosition" @mouseup="endMoving" ref="msgBody">
                <div id="message_header"  @mousedown="startMoving" :class="['f_row','al_baseline','jc_'+headerAlign]">
                    <p >{{ header }}</p>
                </div>
                <div class="line"></div>
                <div id="message_content":class="['al_center','jc_'+contentAlign]">
                    <p>{{ content }}</p>
                </div>
                <div id="message_operations" :class="['jc_'+operatorAlign]">
                    <div :class="['message_operator']">
                        <beike-button  @click="handleConfirm">
                            {{ confirmText }}
                        </beike-button>
                    </div>
                    <div v-if="cancelText" :class="['message_operator']">
                        <beike-button  @click="handleCancel">
                            {{ cancelText }}
                        </beike-button>
                    </div>
                </div>
            </div>
        </div>
    </transition>
</template>

<script>
    // import BeikeButton from 'src/components/beike-button/beike-button';
    import BeikeButton from 'components/beike-button';
    export default {
        name: 'index',
        components: { BeikeButton },
        props: {
            header: {
                type: String,
                default: 'default',
            },
            headerAlign:{
              type:String,
              default:'center'
            },
            content: {
                type: String,
                default: '这是内容部分,可以添加内容',
            },
            contentAlign:{
                type:String,
                default:'center'
            },
            confirmText: {
                type: String,
                default: '确定',
            },
            cancelText: {
                type: String,
                default: '',
            },
            operatorAlign:{
                type:String,
                default:'center'
            },
            confirmCallback: {
                type:Function,
                default: function () {
                    alert('点此确认');
                },
            },
            cancelCallback: {
                type:Function,
                default: function () {
                    alert('点此取消');
                },
            },
            resolve:{
                type:Function,
                default: null
            },
            reject:{
                type:Function,
                default:null
            }
        },
        data() {
            return {
                visible: false,
                x:0,
                y:0,
                node:null,
                isMove:false
            };
        },
        methods:{
            handleConfirm:function (  ) {
                this.confirmCallback();
                if(this.resolve){
                    this.resolve("确定");
                }
                this.close();
            },
            handleCancel:function (  ) {
                this.cancelCallback();
                if(this.resolve){
                    this.resolve("取消");
                }
                this.close();
            },
            close:function (  ) {
                this.visible = false;
            },
            startMoving:function ( e ) {
                let nodeStyle = window.getComputedStyle(this.$refs.msgBody);

                //初始化相对距离
                //设置可以进行移动
                this.startXDis = e.clientX - parseInt(nodeStyle.left);
                this.startYDis = e.clientY - parseInt(nodeStyle.top);
                this.isMove = true;
            },
            changePosition:function ( e ) {
                document.onmouseup = () =>{
                    this.isMove = false;
                };
                if(this.isMove){
                    console.log(e.clientX);
                    this.x = e.clientX - this.startXDis;
                    this.y = e.clientY - this.startYDis;
                    let node = this.$refs.msgBody;
                    node.style.left = this.x + 'px';
                    node.style.top = this.y + 'px';
                }
            },
            endMoving:function (  ) {
                this.isMove = false;
            },
        }
    };
</script>

<style scoped>
    .fadeIn-enter, .fadeIn-leave-to {
        opacity: 0;
        transform: translateX(-10rem);
    }

    .fadeIn-enter-active, .fadeIn-leave-active {
        transition: all 0.3s ease-in-out;
    }

    .fadeIn-enter-to, .fadeIn-leave {

    }
</style>
<style lang="scss">
    @import "flex_layout";
    #message_bg {
        position: fixed;
        top: 0;
        left: 0;
        height: 100%;
        width: 100%;
        background-color: rgba(0, 0, 0, 0.3);
        #message_body {
            position: fixed;
            width: 25rem;
            height: 15rem;
            border-radius: 0.5rem;
            overflow: hidden;
            background-color: rgba(255, 255, 255, 1);
            padding: 1rem;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: space-around;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);

            #message_header {
                height: 20%;
                padding: 0.3rem;
                width: 100%;
                user-select: none;
                cursor: move;
                /*align-self: flex-start;*/
            }
            .line{
                height: 1px;
                width: 100%;
                background-color: rgba(0,0,0,0.1);
            }
            #message_content {
                height: 60%;
                width: 100%;
                padding: 0.3rem;
                display: flex;
            }

            #message_operations {
                height: 20%;
                width: 100%;
                display: flex;
                align-items: center;
                .message_operator{
                    margin: 0px 0.3rem;
                }
            }
        }
    }
</style>

js文件

  • 定义默认参数,再根据传入的参数生成传入组件内部的参数
  • 返回promise,将promise的处理函数resolve和reject作为参数传入组件内部,即可以实现根据组件操作而进行promise的响应
  • 插件可以有很多个prototype控制不同的功能
import templateMessageBox from './index'
import Vue from 'vue'


let MessageBox={};
let instance={};
const defaultProps = {
    header: "标题",
    headerAlign:"start",
    content: "内容",
    contentAlign:"center",
    confirmText: "确认",
    cancelText: "取消",
    operatorAlign:"end",
    confirmCallback: function(){
        alert("点此确认");
    },
    cancelCallback:function(){
        alert("点此取消");
    },
    resolve:null,
    reject:null,
};
let config = defaultProps;

// function execetor( resolve, reject ,qqq) {
//     if(instance.visible)
//         resolve("创建成功");
//     else
//         reject("创建失败");
// }
function initialize(props){
    const messageClass = Vue.extend(templateMessageBox);//拓展的类
    for(let prop in props){
        config[prop] = props[prop];
    }
    instance = new messageClass({
        propsData:config
    });
    console.log("initilize Component");
    instance.$mount();
    document.body.appendChild(instance.$el);
    instance.visible = true;
}


MessageBox.install =function ( Vue ) {

    Vue.prototype.$msgBOX = (p) =>{
        function exector( resolve,reject ) {
            initialize(p);
            instance.resolve = resolve;
            instance.reject = reject;
        }
        return new Promise(exector)
    };

    Vue.prototype.$msgBOXHide = function (  ) {
        instance.visible=false;
    };

};


export default  MessageBox;

在Main中进行调用,进行插件的注册

import messageBox from ‘path’
Vue.use(messageBox);

实现弹窗的拖拽效果

设置拖动弹窗头部才可以进行移动
1.在头部div @mousedown绑定startMoving函数,初始化相对距离,并设置为可以移动
因为头部有文字,所以选择时可能会选中文字,所以可以在文本标签上添加user-select:none属性,使得文本无法被选择
2.在外部div @mouseover绑定changePosition函数,当前位置-相对位置 即弹窗位置
3.在外部div @mouseup绑定endMoving函数,设置不可移动
4.之所以会用到parseInt 主要是因为以下原因(style.left和offsetLeft的区别):

  • style.left返回值为字符串,如”21px”,offsetLeft返回值为数值,如28;所以需要parseInt将字符串改成数字
  • style.left可获取也可设置,offsetLeft只可读;
  • style.left需事先在内联样式中定义,否则在js中获取到的值为空
            startMoving:function ( e ) {
                let nodeStyle = window.getComputedStyle(this.$refs.msgBody);

                //初始化相对距离
                //设置可以进行移动
                this.startXDis = e.clientX - parseInt(nodeStyle.left);
                this.startYDis = e.clientY - parseInt(nodeStyle.top);
                this.isMove = true;
            },
            changePosition:function ( e ) {
            document.onmouseup = () =>{
                    this.isMove = false;
                }; // 当移出弹窗后鼠标松开 则也不能移动
                if(this.isMove){
                    this.x = e.clientX - this.startXDis;
                    this.y = e.clientY - this.startYDis;
                    let node = this.$refs.msgBody;
                    node.style.left = this.x;
                    node.style.top = this.y;
                }
            },
            endMoving:function (  ) {
                this.isMove = false;
            },

拓展

因为拖拽操作在弹窗或者对话框中会经常用到,所以可以将其封装成一个函数,然后通过v-directive绑定到全局,即可以在标签中直接调用

Vue.directive()介绍

Vue.directive(“hello”,function(el,binding,vnode){
xxxx})
Vue.directive(“指令名”,function( 绑定元素,对象的指令属性,对应虚节点{}

对应声明周期

  • bind 只调用一次,指令第一次绑定到元素时候调用,用这个钩子可以定义一个绑定时执行一次的初始化动作。

  • inserted:被绑定的元素插入父节点的时候调用(父节点存在即可调用,不必存在document中)

  • update: 被绑定与元素所在模板更新时调用,而且无论绑定值是否有变化,通过比较更新前后的绑定值,忽略不必要的模板更新

  • componentUpdate :被绑定的元素所在模板完成一次更新更新周期的时候调用

  • unbind: 只调用一次,指令月元素解绑的时候调用


拖拽操作的绑定

设置style.left 需要当前元素的position为非static,即有位置属性,可以进行移动

Vue.directive("drag",{
        bind:function ( element,binding ) {
            let el = element;
            el.style.left = 600+"px";
            el.onmousedown = function (e) {
            //点击后改变状态
                let disX = e.clientX - el.offsetLeft;
                let disY = e.clientY - el.offsetTop;
                el.onmousemove = function ( e ) {
                    //拖拽时位置的移动
                    el.style.left = (e.clientX - disX)+'px';
                    el.style.top = (e.clientY - disY)+'px';
                }
                document.onmouseup = function ( e ) {
                    //鼠标抬起时的操作
                    el.onmousemove = null;
                    document.onmouseup = null;
                }
            }
        }
    }
)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值