###### <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是如何实现插件封装的?")