实现效果
解决的问题
- vue.use的使用/ vue.extend+mount 和 vue.component的区别
- 如何使用prototype实现全局通过函数调用
- 弹窗效果的实现,以及对应弹窗的排版和回调函数的执行
- 如何实现弹窗的拖拽效果 (v-directive 拖拽指令)
问题相关知识
-
vue.use
- vue.install源码分析
从源码可知,vue.use是一个注册插件或执行install方法,并将vue作为第一个参数传入插件的方法,一般为内部有自身逻辑的功能模块/* 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.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;
}
}
}
}
)