###### <p style="text-align: right"> 建立日期:2021-01-13 / 更新日期:2021-01-13</p>
# Vue.js 3 封裝 Modal / Loading Plugin 方法
###### tags: `Vue.js` `Vue Plugins`
## 資料夾架構
* 在 **plugins** 資料夾內新增 **modal** 資料夾,裡面放置 **index.js**
* **Modal.vue** 可以放在 **modal** 資料夾內,或是放在 **components** 資料夾,`import` 時路徑寫對即可
## index.js
:::spoiler 點擊顯示完整內容
```javascript=
import { createVNode, render } from 'vue';
import modalComponent from './Modal.vue';
const pluginModal = {
install(app) {
// 1. 創建容器
const modalContainer = document.createElement('div');
// 2. 將 Modal.vue 轉換成虛擬 dom
const modalVNode = createVNode(modalComponent);
// 3. 透過 render 將其渲染到 modalContainer
render(modalVNode, modalContainer);
// 4. 將 modalContainer 新增至 document.body
document.body.appendChild(modalContainer);
// 5. 創建實例
const modalInstance = modalVNode.component;
// 6. 取得 Modal.vue 內的 methods,應該不是正規的寫法,但目前我只會這樣
const modalCtx = modalInstance.ctx;
// 7. 定義 Global 可用的 methods
const modalMethods = {
open({ title, msg, btnText }) {
modalCtx.open({ title, msg, btnText });
},
hide() {
modalCtx.hide();
},
};
// 8. 註冊到 Global
/* eslint-disable no-param-reassign */
app.config.globalProperties.$modal = modalMethods;
/* eslint-disable no-param-reassign */
},
};
export default pluginModal;
```
:::
## Modal.vue
:::spoiler 點擊顯示完整內容
```javascript=
<template lang="pug">
Teleport(to="body")
.semi-transparent(:class="{'hide': !display}")
.modal
.modal__title {{ title }}
.modal__body(:class="{ imgTick: !msg }") {{ msg }}
.modal__footer
button(@click="hide()") {{ btnText }}
</template>
<script>
export default {
name: 'Modal',
data() {
return {
display: false,
title: '',
msg: '',
btnText: '',
};
},
methods: {
open({ title, msg, btnText }) {
this.title = title;
this.msg = msg;
this.btnText = btnText;
this.display = true;
},
hide() {
this.display = false;
this.title = '';
this.msg = '';
this.btnText = '';
},
},
};
</script>
<style lang="sass" scoped>
$primary-color: #ffffff
@mixin flex($jc: flex-start, $ai: flex-start)
display: flex
justify-content: $jc
align-items: $ai
@mixin zebra-three-slashes
$bg: rgba(255, 255, 255, 0.3)
$bgW: 8
$line-color: #575757
$lineW: 1.5
width: 45px
height: 9px
background-image: repeating-linear-gradient(45deg, $bg, $bg #{$bgW}px, $lineColor #{$bgW}px, $bg #{$bgW + $lineW}px, $bg #{$bgW + $lineW + 1}px)
.semi-transparent
width: 100vw
height: 100vh
position: fixed
top: 0
left: 0
background: rgba(#000000, 0.5)
@include flex(center, center)
.modal
width: 423px
padding: 32px 42px
background: $primary-color
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.5)
*
font-weight: 500
.modal__title
font-size: 24px
line-height: 36px
letter-spacing: 2.5px
margin-bottom: 31px
position: relative
&::before
content: ''
position: absolute
bottom: -15px
left: 0
@include zebra-three-slashes
.modal__body
margin-bottom: 30px
.imgTick
background-image: top center url('~@/assets/images/modal/yourImg.svg') no-repeat
.modal__footer
@include flex(flex-end)
button
padding: 8px 24px
background: #484848
color: $primary-color
</style>
```
* `Teleport(to="body")`: 有加此行,`.semi-transparent` 的父元素是 `body`。沒加的話,`.semi-transparent` 的父元素是 `div`
:::
## main.js
:::spoiler 點擊顯示完整內容
```javascript=
import { createApp } from 'vue';
import App from './App.vue';
import pluginModal from './plugins/modal';
createApp(App).use(pluginModal).mount('#app');
```
:::
## 參考資料
* [Vue3丨TS丨7 个思路封装一个灵活的 Modal 对话框](https://segmentfault.com/a/1190000038928664 "Vue3丨TS丨7 个思路封装一个灵活的 Modal 对话框")
* [vue3.0是如何实现插件封装的?](https://juejin.cn/post/6905409280761921543 "vue3.0是如何实现插件封装的?")