组件系统是Vue中非常重要的一个特性,通过组件的封装,我们可以实现代码的复用以及功能的解耦。组件可以扩展HTML元素,封装可重用的代码。在较高层面上,组件是自定义的元素,Vue.js的编译器为它添加特殊功能。在有些情况下,组件也可以是原生HTML元素的形式,以is特性扩展。
组件的注册
组件的注册有两种方式,第一种方式是全局注册,当我们注册全局组件后,我们可以在任何Vue根实例的模板中使用它,全局注册组件的语法如下:
Vue.component('my-component-name', {
// ... 选项 ...
})
全局注册组件也存在一个问题,假如我们项目中的组件都进行了全局注册,webpack打包工具会将这些注册了的组件都进行打包,即使在项目中不再使用的组件也仍旧会被打包,这就会造成代码的冗余,所以我们还有一种注册组件的方式,就是局部注册,它的语法如下:
先使用普通JavaScript对象的方式来定义组件:
var ComponentA = { /* ... */ }
var ComponentB = { /* ... */ }
var ComponentC = { /* ... */ }
然后在 components 选项中定义你想要使用的组件:
new Vue({
el: '#app',
components: {
'component-a': ComponentA,
'component-b': ComponentB
}
})
components 对象中的每个属性的属性名就是自定义元素的名字,其属性值就是这个组件的选项对象。
上面简单回顾了Vue组件的注册,但通常我们在开发的过程中会使用到如Babel 和 webpack 的模块系统,通过mport/require语法导入组件,我们的项目中会有一个components目录用来存放封装的所有组件,每个组件都有各自的文件。举个简单的例子,在componments文件夹下,我们保存了两个封装好的组件ComponentA和ComponentB,然后我们在ComponentC中引入ComponentA和ComponentB:
import ComponentA from './components/ComponentA'
import ComponentB from './components/ComponentB'
export default {
components: {
ComponentA,
ComponentB
},
// ...
}
这样就完成了组件在模块系统中的局部注册,开发中也是最常用的组件注册方式,接下来我就演示一下如何在使用vue-cli脚手架工具创建的项目中从零开始封装一些通用的组件,以封装一个Modal组件为例。
封装一个Modal组件
先使用vue-cli官方的脚手架工具创建一个vue项目:
vue create modal
然后在src目录下创建一个components文件夹,为了方便管理,封装组件的时候每个组件也拥有各自的文件夹,里面存放组件的源码以及组件注册的代码,创建一个modal文件夹,在modal文件夹下面创建一个index.vue文件,里面写组件相关的模板、逻辑和样式。封装一个组件的核心是父子组件之间的通讯。
首先在main.vue中写入我们的组件骨架以及样式:
<template>
<div class="vue-modal__wrapper" v-if="visible">
<div class="vue-modal">
<div class="vue-modal__header">
<span class="vue-modal__title">我是Title</span>
<span class="vue-modal__headerbtn">x</span>
</div>
<div class="vue-modal__body">
我是Body
</div>
<div class="vue-modal__footer">
<button class="vue-modal__button">取消</button>
<button class="vue-modal__button primary">确定</button>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'VueModal',
props: {
visible: {
type: Boolean,
default: false
}
},
data() {
return {
};
},
methods: {
}
}
</script>
<style>
.vue-modal__wrapper {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
overflow: auto;
margin: 0;
z-index: 9999;
background: rgba(0, 0, 0, 0.5);
}
.vue-modal {
position: relative;
margin: 0 auto 50px;
border-radius: 2px;
-webkit-box-shadow: 0 1px 3px rgba(0,0,0,.3);
box-shadow: 0 1px 3px rgba(0,0,0,.3);
-webkit-box-sizing: border-box;
box-sizing: border-box;
width: 50%;
margin-top: 15vh;
}
.vue-modal {
background: #fff;
box-sizing: border-box;
}
.vue-modal__header {
padding: 20px 20px 10px;
}
.vue-modal__title {
line-height: 24px;
font-size: 18px;
color: #303133;
}
.vue-modal__headerbtn {
position: absolute;
top: 20px;
right: 20px;
padding: 0;
background: 0 0;
border: none;
outline: 0;
cursor: pointer;
font-size: 16px;
}
.vue-modal__body {
padding: 30px 20px;
color: #606266;
font-size: 14px;
}
.vue-modal__footer {
padding: 10px 20px 20px;
text-align: right;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
.vue-modal__button {
display: inline-block;
line-height: 1;
white-space: nowrap;
cursor: pointer;
background: #fff;
border: 1px solid #dcdfe6;
color: #606266;
-webkit-appearance: none;
text-align: center;
-webkit-box-sizing: border-box;
box-sizing: border-box;
outline: 0;
margin: 0;
-webkit-transition: .1s;
transition: .1s;
font-weight: 500;
padding: 12px 20px;
font-size: 14px;
border-radius: 4px;
}
.vue-modal__button.primary {
background: #3a8ee6;
color: #fff;
}
.vue-modal__button+.vue-modal__button {
margin-left: 10px;
}
</style>
然后我们修改一下App.vue:
<template>
<div id="app">
<span class="button" @click="openModal">Open Modal</span>
<Modal :visible="modalVisible">
</Modal>
</div>
</template>
<script>
import Modal from './components/modal/index';
export default {
name: 'app',
data() {
return {
modalVisible: false
}
},
components: {
Modal
},
methods: {
openModal() {
this.modalVisible = true;
}
}
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
.button {
cursor: pointer;
}
</style>
点击Open Modal按钮,展示Modal的雏形如下:
接下来进一步完善Modal组件,我们可以打开Modal了,但怎么让它关闭呢,很简单,监听Modal右上角关闭图标和取消按钮的点击事件,点击事件被触发时,使用$emit通知父组件关闭Modal,完善后的组件代码如下:
<template>
<div class="vue-modal__wrapper" v-if="visible">
<div class="vue-modal">
<div class="vue-modal__header">
<span class="vue-modal__title">我是Title</span>
<span class="vue-modal__headerbtn" @click="handleClose">x</span>
</div>
<div class="vue-modal__body">
我是Body
</div>
<div class="vue-modal__footer">
<button class="vue-modal__button" @click="handleClose">取消</button>
<button class="vue-modal__button primary">确定</button>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'VueModal',
props: {
visible: {
type: Boolean,
default: false
}
},
data() {
return {
};
},
methods: {
handleClose() {
this.$emit('onCancel');
}
}
}
</script>
<style>
.vue-modal__wrapper {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
overflow: auto;
margin: 0;
z-index: 9999;
background: rgba(0, 0, 0, 0.5);
}
.vue-modal {
position: relative;
margin: 0 auto 50px;
border-radius: 2px;
-webkit-box-shadow: 0 1px 3px rgba(0,0,0,.3);
box-shadow: 0 1px 3px rgba(0,0,0,.3);
-webkit-box-sizing: border-box;
box-sizing: border-box;
width: 50%;
margin-top: 15vh;
}
.vue-modal {
background: #fff;
box-sizing: border-box;
}
.vue-modal__header {
padding: 20px 20px 10px;
}
.vue-modal__title {
line-height: 24px;
font-size: 18px;
color: #303133;
}
.vue-modal__headerbtn {
position: absolute;
top: 20px;
right: 20px;
padding: 0;
background: 0 0;
border: none;
outline: 0;
cursor: pointer;
font-size: 16px;
}
.vue-modal__body {
padding: 30px 20px;
color: #606266;
font-size: 14px;
}
.vue-modal__footer {
padding: 10px 20px 20px;
text-align: right;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
.vue-modal__button {
display: inline-block;
line-height: 1;
white-space: nowrap;
cursor: pointer;
background: #fff;
border: 1px solid #dcdfe6;
color: #606266;
-webkit-appearance: none;
text-align: center;
-webkit-box-sizing: border-box;
box-sizing: border-box;
outline: 0;
margin: 0;
-webkit-transition: .1s;
transition: .1s;
font-weight: 500;
padding: 12px 20px;
font-size: 14px;
border-radius: 4px;
}
.vue-modal__button.primary {
background: #3a8ee6;
color: #fff;
}
.vue-modal__button+.vue-modal__button {
margin-left: 10px;
}
</style>
App.vue的代码如下:
<template>
<div id="app">
<span class="button" @click="openModal">Open Modal</span>
<Modal
:visible="modalVisible"
@onCancel="handleCancel">
</Modal>
</div>
</template>
<script>
import Modal from './components/modal/index';
export default {
name: 'app',
data() {
return {
modalVisible: false
}
},
components: {
Modal
},
methods: {
openModal() {
this.modalVisible = true;
},
handleCancel() {
this.modalVisible = false;
}
}
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
.button {
cursor: pointer;
}
</style>
现在我们可以打开Modal,也可以关闭Modal了,下一步我们要做的是监听确定按钮的点击事件,点击后也是通过$emit通知父组件做相应的处理,Modal最常用的创建是里面有一个表单,我们点击确定按钮的时候提交表单到后台,进一步修改main.vue代码如下:
<template>
<div class="vue-modal__wrapper" v-if="visible">
<div class="vue-modal">
<div class="vue-modal__header">
<span class="vue-modal__title">我是Title</span>
<span class="vue-modal__headerbtn" @click="handleClose">x</span>
</div>
<div class="vue-modal__body">
我是Body
</div>
<div class="vue-modal__footer">
<button class="vue-modal__button" @click="handleClose">取消</button>
<button class="vue-modal__button primary" @click="handleOk">确定</button>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'VueModal',
props: {
visible: {
type: Boolean,
default: false
}
},
data() {
return {
};
},
methods: {
handleClose() {
this.$emit('onCancel');
},
handleOk() {
this.$emit('onOk');
}
}
}
</script>
<style>
.vue-modal__wrapper {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
overflow: auto;
margin: 0;
z-index: 9999;
background: rgba(0, 0, 0, 0.5);
}
.vue-modal {
position: relative;
margin: 0 auto 50px;
border-radius: 2px;
-webkit-box-shadow: 0 1px 3px rgba(0,0,0,.3);
box-shadow: 0 1px 3px rgba(0,0,0,.3);
-webkit-box-sizing: border-box;
box-sizing: border-box;
width: 50%;
margin-top: 15vh;
}
.vue-modal {
background: #fff;
box-sizing: border-box;
}
.vue-modal__header {
padding: 20px 20px 10px;
}
.vue-modal__title {
line-height: 24px;
font-size: 18px;
color: #303133;
}
.vue-modal__headerbtn {
position: absolute;
top: 20px;
right: 20px;
padding: 0;
background: 0 0;
border: none;
outline: 0;
cursor: pointer;
font-size: 16px;
}
.vue-modal__body {
padding: 30px 20px;
color: #606266;
font-size: 14px;
}
.vue-modal__footer {
padding: 10px 20px 20px;
text-align: right;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
.vue-modal__button {
display: inline-block;
line-height: 1;
white-space: nowrap;
cursor: pointer;
background: #fff;
border: 1px solid #dcdfe6;
color: #606266;
-webkit-appearance: none;
text-align: center;
-webkit-box-sizing: border-box;
box-sizing: border-box;
outline: 0;
margin: 0;
-webkit-transition: .1s;
transition: .1s;
font-weight: 500;
padding: 12px 20px;
font-size: 14px;
border-radius: 4px;
}
.vue-modal__button.primary {
background: #3a8ee6;
color: #fff;
}
.vue-modal__button+.vue-modal__button {
margin-left: 10px;
}
</style>
App.vue的代码如下:
<template>
<div id="app">
<span class="button" @click="openModal">Open Modal</span>
<Modal
:visible="modalVisible"
@onCancel="handleCancel"
@onOk="handleOk">
</Modal>
</div>
</template>
<script>
import Modal from './components/modal/index';
export default {
name: 'app',
data() {
return {
modalVisible: false
}
},
components: {
Modal
},
methods: {
openModal() {
this.modalVisible = true;
},
handleCancel() {
this.modalVisible = false;
},
handleOk() {
this.modalVisible = false;
}
}
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
.button {
cursor: pointer;
}
</style>
现在,作为一个Modal最基本的功能已经做好了,我需要进一步拓展Modal的功能。
Modal的标题有时我们可能需要让它呈现一些自定义的样式或者文字,所以我们可以借助slot插槽实现,这一步完善后的man.vue代码如下:
<template>
<div class="vue-modal__wrapper" v-if="visible">
<div class="vue-modal">
<div class="vue-modal__header">
<div class="vue-modal__title" v-if="$slots.title">
<slot name="title"></slot>
</div>
<div class="vue-modal__title" v-else>{{ title }}</div>
<span class="vue-modal__headerbtn" @click="handleClose">x</span>
</div>
<div class="vue-modal__body">
我是Body
</div>
<div class="vue-modal__footer">
<button class="vue-modal__button" @click="handleClose">取消</button>
<button class="vue-modal__button primary" @click="handleOk">确定</button>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'VueModal',
props: {
visible: {
type: Boolean,
default: false
},
title: {
type: String,
default: ''
},
},
data() {
return {
};
},
methods: {
handleClose() {
this.$emit('onCancel');
},
handleOk() {
this.$emit('onOk');
}
}
}
</script>
<style>
.vue-modal__wrapper {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
overflow: auto;
margin: 0;
z-index: 9999;
background: rgba(0, 0, 0, 0.5);
}
.vue-modal {
position: relative;
margin: 0 auto 50px;
border-radius: 2px;
-webkit-box-shadow: 0 1px 3px rgba(0,0,0,.3);
box-shadow: 0 1px 3px rgba(0,0,0,.3);
-webkit-box-sizing: border-box;
box-sizing: border-box;
width: 50%;
margin-top: 15vh;
}
.vue-modal {
background: #fff;
box-sizing: border-box;
}
.vue-modal__header {
padding: 20px 20px 10px;
}
.vue-modal__title {
line-height: 24px;
font-size: 18px;
color: #303133;
}
.vue-modal__headerbtn {
position: absolute;
top: 20px;
right: 20px;
padding: 0;
background: 0 0;
border: none;
outline: 0;
cursor: pointer;
font-size: 16px;
}
.vue-modal__body {
padding: 30px 20px;
color: #606266;
font-size: 14px;
}
.vue-modal__footer {
padding: 10px 20px 20px;
text-align: right;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
.vue-modal__button {
display: inline-block;
line-height: 1;
white-space: nowrap;
cursor: pointer;
background: #fff;
border: 1px solid #dcdfe6;
color: #606266;
-webkit-appearance: none;
text-align: center;
-webkit-box-sizing: border-box;
box-sizing: border-box;
outline: 0;
margin: 0;
-webkit-transition: .1s;
transition: .1s;
font-weight: 500;
padding: 12px 20px;
font-size: 14px;
border-radius: 4px;
}
.vue-modal__button.primary {
background: #3a8ee6;
color: #fff;
}
.vue-modal__button+.vue-modal__button {
margin-left: 10px;
}
</style>
App.vue的代码如下:
<template>
<div id="app">
<span class="button" @click="openModal">Open Modal</span>
<Modal
:visible="modalVisible"
@onCancel="handleCancel"
@onOk="handleOk">
<span slot="title">这里是自定义的标题</span>
</Modal>
</div>
</template>
<script>
import Modal from './components/modal/index';
export default {
name: 'app',
data() {
return {
modalVisible: false
}
},
components: {
Modal
},
methods: {
openModal() {
this.modalVisible = true;
},
handleCancel() {
this.modalVisible = false;
},
handleOk() {
this.modalVisible = false;
}
}
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
.button {
cursor: pointer;
}
</style>
同样地,body也是用户在使用Modal组件的时候才能决定的,所以同样借助slot插槽,进一步完善main.vue:
<template>
<div class="vue-modal__wrapper" v-if="visible">
<div class="vue-modal">
<div class="vue-modal__header">
<div class="vue-modal__title" v-if="$slots.title">
<slot name="title"></slot>
</div>
<div class="vue-modal__title" v-else>{{ title }}</div>
<span class="vue-modal__headerbtn" @click="handleClose">x</span>
</div>
<div class="vue-modal__body">
<slot name="body"></slot>
</div>
<div class="vue-modal__footer">
<button class="vue-modal__button" @click="handleClose">取消</button>
<button class="vue-modal__button primary" @click="handleOk">确定</button>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'VueModal',
props: {
visible: {
type: Boolean,
default: false
},
title: {
type: String,
default: ''
},
},
data() {
return {
};
},
methods: {
handleClose() {
this.$emit('onCancel');
},
handleOk() {
this.$emit('onOk');
}
}
}
</script>
<style>
.vue-modal__wrapper {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
overflow: auto;
margin: 0;
z-index: 9999;
background: rgba(0, 0, 0, 0.5);
}
.vue-modal {
position: relative;
margin: 0 auto 50px;
border-radius: 2px;
-webkit-box-shadow: 0 1px 3px rgba(0,0,0,.3);
box-shadow: 0 1px 3px rgba(0,0,0,.3);
-webkit-box-sizing: border-box;
box-sizing: border-box;
width: 50%;
margin-top: 15vh;
}
.vue-modal {
background: #fff;
box-sizing: border-box;
}
.vue-modal__header {
padding: 20px 20px 10px;
}
.vue-modal__title {
line-height: 24px;
font-size: 18px;
color: #303133;
}
.vue-modal__headerbtn {
position: absolute;
top: 20px;
right: 20px;
padding: 0;
background: 0 0;
border: none;
outline: 0;
cursor: pointer;
font-size: 16px;
}
.vue-modal__body {
padding: 30px 20px;
color: #606266;
font-size: 14px;
}
.vue-modal__footer {
padding: 10px 20px 20px;
text-align: right;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
.vue-modal__button {
display: inline-block;
line-height: 1;
white-space: nowrap;
cursor: pointer;
background: #fff;
border: 1px solid #dcdfe6;
color: #606266;
-webkit-appearance: none;
text-align: center;
-webkit-box-sizing: border-box;
box-sizing: border-box;
outline: 0;
margin: 0;
-webkit-transition: .1s;
transition: .1s;
font-weight: 500;
padding: 12px 20px;
font-size: 14px;
border-radius: 4px;
}
.vue-modal__button.primary {
background: #3a8ee6;
color: #fff;
}
.vue-modal__button+.vue-modal__button {
margin-left: 10px;
}
</style>
App.vue同步更新如下:
<template>
<div id="app">
<span class="button" @click="openModal">Open Modal</span>
<Modal
:visible="modalVisible"
@onCancel="handleCancel"
@onOk="handleOk">
<span slot="title">这里是自定义的标题</span>
<div slot="body">
这里是自定义的内容
</div>
</Modal>
</div>
</template>
<script>
import Modal from './components/modal/index';
export default {
name: 'app',
data() {
return {
modalVisible: false
}
},
components: {
Modal
},
methods: {
openModal() {
this.modalVisible = true;
},
handleCancel() {
this.modalVisible = false;
},
handleOk() {
this.modalVisible = false;
}
}
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
.button {
cursor: pointer;
}
</style>
接下来我们对Modal底部的按钮进行完善,有时候的需求可能需要自定义按钮的文本,取消按钮和确定按钮的文本我们分别通过cancelText和okText从父组件接收,修改main.vue,如下:
<template>
<div class="vue-modal__wrapper" v-if="visible">
<div class="vue-modal">
<div class="vue-modal__header">
<div class="vue-modal__title" v-if="$slots.title">
<slot name="title"></slot>
</div>
<div class="vue-modal__title" v-else>{{ title }}</div>
<span class="vue-modal__headerbtn" @click="handleClose">x</span>
</div>
<div class="vue-modal__body">
<slot name="body"></slot>
</div>
<div class="vue-modal__footer">
<button class="vue-modal__button" @click="handleClose">{{ cancelText ? cancelText : '取消'}}</button>
<button class="vue-modal__button primary" @click="handleOk">{{ okText ? okText : '确定'}}</button>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'VueModal',
props: {
visible: {
type: Boolean,
default: false
},
title: {
type: String,
default: ''
},
okText: {
type: String,
default: ''
},
cancelText: {
type: String,
default: ''
},
},
data() {
return {
};
},
methods: {
handleClose() {
this.$emit('onCancel');
},
handleOk() {
this.$emit('onOk');
}
}
}
</script>
<style>
.vue-modal__wrapper {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
overflow: auto;
margin: 0;
z-index: 9999;
background: rgba(0, 0, 0, 0.5);
}
.vue-modal {
position: relative;
margin: 0 auto 50px;
border-radius: 2px;
-webkit-box-shadow: 0 1px 3px rgba(0,0,0,.3);
box-shadow: 0 1px 3px rgba(0,0,0,.3);
-webkit-box-sizing: border-box;
box-sizing: border-box;
width: 50%;
margin-top: 15vh;
}
.vue-modal {
background: #fff;
box-sizing: border-box;
}
.vue-modal__header {
padding: 20px 20px 10px;
}
.vue-modal__title {
line-height: 24px;
font-size: 18px;
color: #303133;
}
.vue-modal__headerbtn {
position: absolute;
top: 20px;
right: 20px;
padding: 0;
background: 0 0;
border: none;
outline: 0;
cursor: pointer;
font-size: 16px;
}
.vue-modal__body {
padding: 30px 20px;
color: #606266;
font-size: 14px;
}
.vue-modal__footer {
padding: 10px 20px 20px;
text-align: right;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
.vue-modal__button {
display: inline-block;
line-height: 1;
white-space: nowrap;
cursor: pointer;
background: #fff;
border: 1px solid #dcdfe6;
color: #606266;
-webkit-appearance: none;
text-align: center;
-webkit-box-sizing: border-box;
box-sizing: border-box;
outline: 0;
margin: 0;
-webkit-transition: .1s;
transition: .1s;
font-weight: 500;
padding: 12px 20px;
font-size: 14px;
border-radius: 4px;
}
.vue-modal__button.primary {
background: #3a8ee6;
color: #fff;
}
.vue-modal__button+.vue-modal__button {
margin-left: 10px;
}
</style>
修改App.vue:
<template>
<div id="app">
<span class="button" @click="openModal">Open Modal</span>
<Modal
:visible="modalVisible"
@onCancel="handleCancel"
@onOk="handleOk"
cancelText="cancel"
okText="ok">
<span slot="title">这里是自定义的标题</span>
<div slot="body">
这里是自定义的内容
</div>
</Modal>
</div>
</template>
<script>
import Modal from './components/modal/index';
export default {
name: 'app',
data() {
return {
modalVisible: false
}
},
components: {
Modal
},
methods: {
openModal() {
this.modalVisible = true;
},
handleCancel() {
this.modalVisible = false;
},
handleOk() {
this.modalVisible = false;
}
}
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
.button {
cursor: pointer;
}
</style>
到了这里,Modal已经是一个比较完善的组件了,但还有进一步优化的空间,有时候我们的需求还可能是对底部的html和样式进行自定义,同样引入slot插槽,当使用Modal的时候,如果用户没有对Modal的底部进行自定义,则默认显示取消和关闭按钮,如果有进行自定义则显示自定义的内容,完善后的main.vue代码如下:
<template>
<div class="vue-modal__wrapper" v-if="visible">
<div class="vue-modal">
<div class="vue-modal__header">
<div class="vue-modal__title" v-if="$slots.title">
<slot name="title"></slot>
</div>
<div class="vue-modal__title" v-else>{{ title }}</div>
<span class="vue-modal__headerbtn" @click="handleClose">x</span>
</div>
<div class="vue-modal__body">
<slot name="body"></slot>
</div>
<div class="vue-modal__footer" v-if="$slots.footer">
<slot name="footer"></slot>
</div>
<div class="vue-modal__footer" v-else>
<button class="vue-modal__button" @click="handleCancel">{{ cancelText ? cancelText : '取消'}}</button>
<button class="vue-modal__button primary" @click="handleOk">{{ okText ? okText : '确定'}}</button>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'VueModal',
props: {
visible: {
type: Boolean,
default: false
},
title: {
type: String,
default: ''
},
okText: {
type: String,
default: ''
},
cancelText: {
type: String,
default: ''
},
},
data() {
return {
};
},
methods: {
handleClose() {
this.$emit('onCancel');
},
handleOk() {
this.$emit('onOk');
}
}
}
</script>
<style>
.vue-modal__wrapper {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
overflow: auto;
margin: 0;
z-index: 9999;
background: rgba(0, 0, 0, 0.5);
}
.vue-modal {
position: relative;
margin: 0 auto 50px;
border-radius: 2px;
-webkit-box-shadow: 0 1px 3px rgba(0,0,0,.3);
box-shadow: 0 1px 3px rgba(0,0,0,.3);
-webkit-box-sizing: border-box;
box-sizing: border-box;
width: 50%;
margin-top: 15vh;
}
.vue-modal {
background: #fff;
box-sizing: border-box;
}
.vue-modal__header {
padding: 20px 20px 10px;
}
.vue-modal__title {
line-height: 24px;
font-size: 18px;
color: #303133;
}
.vue-modal__headerbtn {
position: absolute;
top: 20px;
right: 20px;
padding: 0;
background: 0 0;
border: none;
outline: 0;
cursor: pointer;
font-size: 16px;
}
.vue-modal__body {
padding: 30px 20px;
color: #606266;
font-size: 14px;
}
.vue-modal__footer {
padding: 10px 20px 20px;
text-align: right;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
.vue-modal__button {
display: inline-block;
line-height: 1;
white-space: nowrap;
cursor: pointer;
background: #fff;
border: 1px solid #dcdfe6;
color: #606266;
-webkit-appearance: none;
text-align: center;
-webkit-box-sizing: border-box;
box-sizing: border-box;
outline: 0;
margin: 0;
-webkit-transition: .1s;
transition: .1s;
font-weight: 500;
padding: 12px 20px;
font-size: 14px;
border-radius: 4px;
}
.vue-modal__button.primary {
background: #3a8ee6;
color: #fff;
}
.vue-modal__button+.vue-modal__button {
margin-left: 10px;
}
</style>
App.vue的代码:
<template>
<div id="app">
<span class="button" @click="openModal">Open Modal</span>
<Modal
:visible="modalVisible"
@onCancel="handleCancel"
@onOk="handleOk"
cancelText="cancel"
okText="ok">
<span slot="title">这里是自定义的标题</span>
<div slot="body">
这里是自定义的内容
</div>
<div slot="footer">
<button class="slot-button" @click="closeModal">自定义关闭按钮</button>
</div>
</Modal>
</div>
</template>
<script>
import Modal from './components/modal/index';
export default {
name: 'app',
data() {
return {
modalVisible: false
}
},
components: {
Modal
},
methods: {
openModal() {
this.modalVisible = true;
},
handleCancel() {
this.modalVisible = false;
},
closeModal() {
this.handleCancel();
},
handleOk() {
this.modalVisible = false;
}
}
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
.button {
cursor: pointer;
}
.slot-button {
display: inline-block;
line-height: 1;
white-space: nowrap;
cursor: pointer;
background: #fff;
border: 1px solid #dcdfe6;
color: #606266;
-webkit-appearance: none;
text-align: center;
-webkit-box-sizing: border-box;
box-sizing: border-box;
outline: 0;
margin: 0;
-webkit-transition: .1s;
transition: .1s;
font-weight: 500;
padding: 12px 20px;
font-size: 14px;
border-radius: 4px;
background: #3a8ee6;
color: #fff;
}
</style>
现在,我们的Modal算是比较完美了,但是加点转场动画会不会更好一点呢,是的,Modal显示或者关闭的时候给它加些动画,修改main.vue:
<template>
<transition name="vue-modal-fade">
<div class="vue-modal__wrapper" v-if="visible">
<div class="vue-modal">
<div class="vue-modal__header">
<div class="vue-modal__title" v-if="$slots.title">
<slot name="title"></slot>
</div>
<div class="vue-modal__title" v-else>{{ title }}</div>
<span class="vue-modal__headerbtn" @click="handleClose">x</span>
</div>
<div class="vue-modal__body">
<slot name="body"></slot>
</div>
<div class="vue-modal__footer" v-if="$slots.footer">
<slot name="footer"></slot>
</div>
<div class="vue-modal__footer" v-else>
<button class="vue-modal__button" @click="handleCancel">{{ cancelText ? cancelText : '取消'}}</button>
<button class="vue-modal__button primary" @click="handleOk">{{ okText ? okText : '确定'}}</button>
</div>
</div>
</div>
</transition>
</template>
<script>
export default {
name: 'VueModal',
props: {
visible: {
type: Boolean,
default: false
},
title: {
type: String,
default: ''
},
okText: {
type: String,
default: ''
},
cancelText: {
type: String,
default: ''
},
},
data() {
return {
};
},
methods: {
handleClose() {
this.$emit('onCancel');
},
handleOk() {
this.$emit('onOk');
}
}
}
</script>
<style>
.vue-modal__wrapper {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
overflow: auto;
margin: 0;
z-index: 9999;
background: rgba(0, 0, 0, 0.5);
}
.vue-modal {
position: relative;
margin: 0 auto 50px;
border-radius: 2px;
-webkit-box-shadow: 0 1px 3px rgba(0,0,0,.3);
box-shadow: 0 1px 3px rgba(0,0,0,.3);
-webkit-box-sizing: border-box;
box-sizing: border-box;
width: 50%;
margin-top: 15vh;
}
.vue-modal {
background: #fff;
box-sizing: border-box;
}
.vue-modal__header {
padding: 20px 20px 10px;
}
.vue-modal__title {
line-height: 24px;
font-size: 18px;
color: #303133;
}
.vue-modal__headerbtn {
position: absolute;
top: 20px;
right: 20px;
padding: 0;
background: 0 0;
border: none;
outline: 0;
cursor: pointer;
font-size: 16px;
}
.vue-modal__body {
padding: 30px 20px;
color: #606266;
font-size: 14px;
}
.vue-modal__footer {
padding: 10px 20px 20px;
text-align: right;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
.vue-modal__button {
display: inline-block;
line-height: 1;
white-space: nowrap;
cursor: pointer;
background: #fff;
border: 1px solid #dcdfe6;
color: #606266;
-webkit-appearance: none;
text-align: center;
-webkit-box-sizing: border-box;
box-sizing: border-box;
outline: 0;
margin: 0;
-webkit-transition: .1s;
transition: .1s;
font-weight: 500;
padding: 12px 20px;
font-size: 14px;
border-radius: 4px;
}
.vue-modal__button.primary {
background: #3a8ee6;
color: #fff;
}
.vue-modal__button+.vue-modal__button {
margin-left: 10px;
}
.vue-modal-fade-enter-active {
animation: vue-modal-fade-in .3s;
}
.vue-modal-fade-leave-active {
animation: vue-modal-fade-out .3s;
}
@keyframes vue-modal-fade-in {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@keyframes vue-modal-fade-out {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
</style>
仔细思考一下,还有几个简单的细节可以进行优化,一个是点击蒙层的时候我们可能希望能关闭modal, 还有就是我们可能不需要Modal右上角的关闭按钮,很简单,分别使用showClose和maskClosable两个props从父组件接收值,showClose为false的时候不显示icon,默认为true,maskClosable为true时表示点击蒙层可以关闭Modal,默认为false,还有一点也是比较常见的需求,有时候我们希望Modal的宽度由我们自己定义,修改一下main.vue:
<template>
<transition name="vue-modal-fade">
<div class="vue-modal__wrapper" v-if="visible">
<div class="vue-modal" :style="{width}">
<div class="vue-modal__header">
<div class="vue-modal__title" v-if="$slots.title">
<slot name="title"></slot>
</div>
<div class="vue-modal__title" v-else>{{ title }}</div>
<span class="vue-modal__headerbtn" @click="handleClose">x</span>
</div>
<div class="vue-modal__body">
<slot name="body"></slot>
</div>
<div class="vue-modal__footer" v-if="$slots.footer">
<slot name="footer"></slot>
</div>
<div class="vue-modal__footer" v-else>
<button class="vue-modal__button" @click="handleCancel">{{ cancelText ? cancelText : '取消'}}</button>
<button class="vue-modal__button primary" @click="handleOk">{{ okText ? okText : '确定'}}</button>
</div>
</div>
</div>
</transition>
</template>
<script>
export default {
name: 'VueModal',
props: {
visible: {
type: Boolean,
default: false
},
title: {
type: String,
default: ''
},
okText: {
type: String,
default: ''
},
cancelText: {
type: String,
default: ''
},
showClose: {
type: Boolean,
default: true
},
maskClosable: {
type: Boolean,
default: false,
},
width: {
type: String
}
},
data() {
return {
};
},
methods: {
handleClose() {
this.$emit('onCancel');
},
handleOk() {
this.$emit('onOk');
}
}
}
</script>
<style>
.vue-modal__wrapper {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
overflow: auto;
margin: 0;
z-index: 9999;
background: rgba(0, 0, 0, 0.5);
}
.vue-modal {
position: relative;
margin: 0 auto 50px;
border-radius: 2px;
-webkit-box-shadow: 0 1px 3px rgba(0,0,0,.3);
box-shadow: 0 1px 3px rgba(0,0,0,.3);
-webkit-box-sizing: border-box;
box-sizing: border-box;
width: 50%;
margin-top: 15vh;
}
.vue-modal {
background: #fff;
box-sizing: border-box;
}
.vue-modal__header {
padding: 20px 20px 10px;
}
.vue-modal__title {
line-height: 24px;
font-size: 18px;
color: #303133;
}
.vue-modal__headerbtn {
position: absolute;
top: 20px;
right: 20px;
padding: 0;
background: 0 0;
border: none;
outline: 0;
cursor: pointer;
font-size: 16px;
}
.vue-modal__body {
padding: 30px 20px;
color: #606266;
font-size: 14px;
}
.vue-modal__footer {
padding: 10px 20px 20px;
text-align: right;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
.vue-modal__button {
display: inline-block;
line-height: 1;
white-space: nowrap;
cursor: pointer;
background: #fff;
border: 1px solid #dcdfe6;
color: #606266;
-webkit-appearance: none;
text-align: center;
-webkit-box-sizing: border-box;
box-sizing: border-box;
outline: 0;
margin: 0;
-webkit-transition: .1s;
transition: .1s;
font-weight: 500;
padding: 12px 20px;
font-size: 14px;
border-radius: 4px;
}
.vue-modal__button.primary {
background: #3a8ee6;
color: #fff;
}
.vue-modal__button+.vue-modal__button {
margin-left: 10px;
}
.vue-modal-fade-enter-active {
animation: vue-modal-fade-in .3s;
}
.vue-modal-fade-leave-active {
animation: vue-modal-fade-out .3s;
}
@keyframes vue-modal-fade-in {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@keyframes vue-modal-fade-out {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
</style>
现在我们的Modal组件就封装好了,也可以说是一个比较完美的Modal组件了。
以上也是封装Vue组件的时候比较通用的思路。