用js的方式实现vue弹框

在平常,我们使用vue组件的时候,都要先在.vue文件中引入我们要使用的组件。虽然这样能满足大部分日常开发的需求,但这种方法在某些场景下,就有些难以应对。

  1. 组件的模板是通过调用接口从服务端获取的,需要动态渲染组件;
  2. 实现类似原生 window.alert() 的提示框组件,它的位置是在 下,而非
    ,并且不会通过常规的组件自定义标签的形式使用,而是像 JS 调用函数一样使用。

extend

Vue.extend 的作用,就是基于 Vue 构造器,创建一个“子类”,它的参数跟 new Vue 的基本一样,但 data 要跟组件一样,是个函数,再配合 $mount ,就可以让组件渲染,并且挂载到任意指定的节点上,比如 body。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import Vue from 'vue';

<!-- 创建了一个构造器,这个过程就可以解决异步获取 template 模板的问题 -->
const AlertComponent = Vue.extend({
template: '<div>{{ message }}</div>',
data () {
return {
message: 'Hello, Aresn'
};
},
});
<!-- 调用 $mount 方法对组件进行了手动渲染, -->
const component = new AlertComponent().$mount();
<!-- 挂载节点 -->
document.body.appendChild(component.$el);
<!-- 快捷的挂载方式 -->
new AlertComponent().$mount('#app');

这样就可以满足我们用js方法调用的方式来控制组件,但是在平常的开发中,我们用的vue的runtime编译环境,不支持template模板,而且用字符串来描述组件的模板,也有些不太友好。那么,我们可以试试用new Vue()的方式。

new Vue

new Vue()也可以直接创建 Vue 实例,并且用一个 Render 函数来渲染一个 .vue 文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import Vue from 'vue';
import Notification from './notification.vue';

const props = {}; // 这里可以传入一些组件的 props 选项

const Instance = new Vue({
render (h) {
return h(Notification, {
props: props
});
}
});

const component = Instance.$mount();
document.body.appendChild(component.$el);

这样既可以使用 .vue 来写复杂的组件,还可以根据需要传入适当的 props。渲染后,如果想操作 Render 的 Notification 实例,也是很简单的:

1
const notification = Instance.$children[0];

实例

接下来就给大家,看下我在项目中用new Vue()的方式来实现的弹框组件。首先我们要新建一个vue文件来,完成弹框的基本布局和样式。

modal-dialog
此文件实现了弹框的基本布局,蒙层和关闭事件,弹框内容可以通过插槽来控制,支持的props有width,height,bgSrc(弹框的背景图片),visible,hasClose(是否有关闭‘X’)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
<template>
<transition name="fade">
<div
style="position:fixed;top: 0;left: 0;width:100vw;height:100vh;z-index:100;"
v-if="isShow"
>
<div class="mask" @click.stop="closeDialog"></div>
<div
class="dialog"
:class="classObject"
:style="{
width,
height,
backgroundImage: `url(${bgSrc})`,
backgroundRepeat: 'no-repeat',
...wrapStyle
}"
>
<slot></slot>
<div
v-if="hasClose"
class="close"
@click.stop="closeDialog"
:style="closeStyle"
></div>
</div>
</div>
</transition>
</template>
<script>
export default {
name: "modalDialog",
props: {
visible: {
type: Boolean,
default: false
},
width: {
type: String,
default: "6.52rem"
},
bgSrc: {
type: String,
required: true
},
height: {
type: String,
default: "6.83rem"
},
hasClose: {
type: Boolean,
default: true
},
wrapClass: {
type: String,
default: ""
},
wrapStyle: {
type: Object,
default: function() {
return {};
}
},
closeStyle: {
type: Object,
default: function() {
return {};
}
}
},
watch: {
visible(val) {
<!-- 同步visible的变化 -->
this.isShow = val;
}
},
data() {
return {
<!-- vue单向数据流,因此我们重新定义一个属性isShow -->
isShow: this.visible,
classObject: {
[this.wrapClass]: true
}
};
},
created() {},
methods: {
closeDialog() {
this.isShow = false;
<!-- 通知父组件 visible的变化 父组件使用.sync修饰符即可 -->
this.$emit("update:visible", false);
}
}
};
</script>
<style lang="less" scoped>
@imgPath: "../assets/img/dialog";
.translateXCenter {
left: 50%;
transform: translateX(-50%) !important;
-webkit-transform: translateX(-50%) !important;
}
.translateCenter {
top: 50%;
left: 50%;
transform: translate(-50%, -50%) !important;
-webkit-transform: translate(-50%, -50%) !important;
}
.translateY(@y) {
left: 50%;
top: 50%;
transform: translate(-50%, @y);
-webkit-transform: translate(-50%, @y);
}
.background(@imgName) {
background: url("@{imgPath}/@{imgName}") no-repeat;
background-size: 100% auto;
}

.mask {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(10, 13, 24, 0.8);
z-index: 100;
}

.dialog {
// background-repeat: no-repeat
background-size: 100% auto;
position: absolute;
z-index: 100;
// .translateXCenter;
.translateCenter;
}

.close {
width: 0.56rem;
height: 0.56rem;
position: absolute;
top: -0.4rem;
right: 0rem;
.background("btn_close.png");
}
.fade-enter-active,
.fade-leave-active {
transition: all 0.2s ease-in;
}
.fade-enter,
.fade-leave-to {
opacity: 0;
}
.fade-enter-to,
.fade-leave {
opacity: 1;
}
</style>

弹框的基本功能实现后,我们就用它来建一个常用的通知组件 alert-box。文件中的图片大家自行替换呀。样式方面也请自行调整呀。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
<template>
<modal-dialog
<!-- 这里一定要加sync修饰符 -->
:visible.sync="isShow"
width="6.7rem"
height="5.51rem"
:bgSrc="require('@img/dialog/tuan_bg.png')"
>
<div class="content">
<ul>
<template v-if="textArr.length > 0">
<li v-for="(text, index) in textArr" :key="index">{{ text }}</li>
</template>
</ul>
<div class="btn-wrap">
<div class="konw" @click="hideBox"></div>
</div>
</div>
</modal-dialog>
</template>
<script>
import ModalDialog from "@/components/modalDialog.vue";

export default {
name: "AlertBox",
props: {
<!-- 显示隐藏 -->
visible: {
type: Boolean,
default: false
},
<!-- 消息的内容 -->
textArr: {
type: Array,
default() {
return [];
}
}
},
components: {
ModalDialog
},
data: function() {
return {
<!-- props单向数据流,组件内部用isShow记录组件状态,最后同步给父级 -->
isShow: this.visible,
};
},
methods: {
hideBox() {
this.isShow = false;
}
},
watch: {
visible(newval) {
this.isShow = newval;
},
isShow(newval) {
<!-- 通知父组件,同步组件显示隐藏状态 -->
this.$emit("update:visible", newval);
}
}
};
</script>
<style lang="less" scoped>
@imgPath: "../assets/img/dialog";
.background(@imgName) {
background: url("@{imgPath}/@{imgName}") no-repeat;
background-size: 100% auto;
}
.translateCenter {
top: 50%;
left: 50%;
transform: translate(-50%, -50%) !important;
-webkit-transform: translate(-50%, -50%) !important;
}
.content {
width: 100%;
height: 100%;
position: relative;
ul {
position: absolute;
// background-color: skyblue;
width: 5.24rem;
height: 2.83rem;
color: #914e4c;
font-size: 0.32rem;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
top: 0.9rem;
left: 0.76rem;
// font-weight: 600;
li {
text-align: justify;
}
}
.btn-wrap {
position: absolute;
top: 4.2rem;
width: 100%;
.konw {
width: 2.66rem;
height: 1.05rem;
margin: 0 auto;
display: block;
.background("btn_know.png");
}
}
}
</style>

最后,是重头戏,如何将这个alertBox的实例挂载在vue的原型上呢?下面我们用new Vue来实现
alert.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import Vue from "vue";
import AlertBox from "./alertBox.vue";

let contain = document.body,
messageInstance = null,
component = null;

AlertBox.newInstance = properties => {
const props = properties || {};
// props : visible ; textType 传入props visible,我们还可以传入很多的props 详情查看官方文档
const Instance = new Vue({
render(h) {
return h(AlertBox, {
props,
nativeOn: {
"update:visible": function(val) {
// 监听alerBox isShow变化的事件,如果组件隐藏,我们移除挂载节点,并销毁实例
if (!val) {
contain.removeChild(component.$el);
Instance.$destroy();
}
}
}
});
}
});
// 组件渲染
component = Instance.$mount();
// 组件挂载
contain.appendChild(component.$el);
};

// 保证单一实例
function getMessageInstance(props) {
messageInstance = messageInstance || AlertBox.newInstance(props);
}

export default {
// 暴露info方法,后面可以增加新的方法
info(props) {
return getMessageInstance(props);
}
};

最后我们在项目的main.js文件引入alert.js

1
2
import MyAlert from "./components/alert";
Vue.prototype.$myAlert = MyAlert;

就可以在vue文件中,用 this.$myAlert.info() 实现通知弹框了。

1
2
3
4
this.$myAlert.info({
visible: true,
textArr: ["团队解散失败,请重试"]
});

-------------本文结束感谢您的阅读-------------