做完按鈕之後,我們應該瞭解了遮罩層的概念,接下來我們來做
Dialog
元件!返回閱讀列表點選 這裡
需求分析
- 預設是不可見的,在使用者觸發某個動作後變為可見
- 自帶白板卡片,分為上中下三個區域,分別放置標題、內容、操作
- 有兩個基本操作:確定、取消
- 卡片後應放置淡黑色遮罩層,遮住原本網頁內容
- 可以自定義是否允許取消
- 右上角提供小叉叉來允許關閉
- 允許通過點選遮罩層來關閉
所以,我們能夠得出如下的參數列格
引數 | 含義 | 型別 | 可選值 | 預設值 |
---|---|---|---|---|
visible | 是否可見 | boolean | false / true | false |
title | 標題 | string | 任意字串 | 必填 |
ok | 確定回撥 | ()=>boolean | 返回 boolean 的函式 | ()=>true |
cancel | 取消回撥 | ()=>boolean | 返回 boolean 的函式 | ()=>true |
注意:可以通過設定返回值為 true
來允許事件發生,反之不允許。可以通過設定返回 false
來取消事件
骨架
我們複用之前做好的
Button
元件
一般情況下,我們不希望對話方塊彈窗在 DOM
樹上的位置,而希望是 body
的直接子元素,那麼我們可以使用 vue3
的 teleport
元件。
程式碼如下:
<template>
<template v-if="visible">
<teleport to="body">
<div class="jeremy-dialog-overlay" @click="close"></div>
<div class="jeremy-dialog">
<header class="jeremy-dialog-header">
{{ title }}
<span class="jeremy-dialog-close" @click="close"></span>
</header>
<div class="jeremy-dialog-divider" />
<main class="jeremy-dialog-main">
<slot></slot>
</main>
<div class="jeremy-dialog-divider" />
<footer class="jeremy-dialog-footer">
<jeremy-button level="plain" @click="close">取消</jeremy-button>
<jeremy-button @click="task" :loading="loading">確定</jeremy-button>
</footer>
</div>
</teleport>
</template>
</template>
這樣,在渲染時,teleport
內部的內容就會出現在 body
的子級上。
功能
現在 ts
中宣告引數:
declare const props: {
visible: boolean;
title: string;
ok: () => boolean;
cancel: () => boolean;
};
declare const context: SetupContext;
然後在 export default
中,寫入我們的引數:
export default {
install: function (Vue) {
Vue.component(this.name, this);
},
name: "JeremyDialog",
props: {
visible: {
type: Boolean,
default: false,
},
title: {
type: String,
required: true,
},
ok: {
type: Function,
default: () => {
return true;
},
},
cancel: {
type: Function,
default: () => {
return true;
},
},
},
components: {
JeremyButton,
},
};
再補全 setup
方法,此處選用 Promise
製造提交等待響應的感覺
setup(props, context) {
const loading = ref(false);
const close = () => {
if (loading.value) {
return;
}
new Promise((resolve, reject) => {
resolve(props.cancel());
}).then((result) => {
if (result !== false) {
context.emit("update:visible", false);
}
});
};
const task = () => {
new Promise((resolve, reject) => {
loading.value = true;
resolve(props.ok());
}).then((result) => {
if (result === true) {
loading.value = false;
context.emit("update:visible", false);
}
});
};
return { loading, close, task };
},
樣式表
最後再補全樣式表:
<style lang="scss">
.jeremy-dialog-overlay {
z-index: 20;
position: fixed;
left: 0;
top: 0;
background: fade-out($color: #000000, $amount: 0.7);
width: 100vw;
height: 100vh;
}
.jeremy-dialog {
z-index: 20;
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
min-width: 300px;
min-height: 200px;
border-radius: 8px;
background: white;
display: flex;
flex-direction: column;
> * {
padding: 8px;
}
> .jeremy-dialog-divider {
border: 1px solid #8c6fef;
padding: 0;
}
> .jeremy-dialog-header {
display: flex;
justify-content: space-between;
> .jeremy-dialog-close {
position: relative;
display: inline-block;
width: 16px;
height: 16px;
cursor: pointer;
&::before,
&::after {
content: "";
position: absolute;
height: 1px;
background: black;
width: 100%;
top: 50%;
left: 50%;
}
&::before {
transform: translate(-50%, -50%) rotate(-45deg);
}
&::after {
transform: translate(-50%, -50%) rotate(45deg);
}
}
}
> .jeremy-dialog-main {
flex-grow: 1;
background: white;
}
> .jeremy-dialog-footer {
display: flex;
justify-content: flex-end;
}
}
</style>
一行程式碼開啟
多數時候我們是不希望使用元件式的,而是直接用函式生成一個彈窗。那麼,我們只要使用 vue3
提供的 createApp
和 h
函式就可以做到了。
我們再建立一個 ts
檔案,即 createDialog.ts
,程式碼如下:
import { createApp, h } from 'vue'
import JeremyDialog from './Dialog.vue'
export const createDialog = options => {
const { title, content, ok, cancel } = options
const div = document.createElement('div')
document.body.appendChild(div)
const close = () => {
app.unmount(div)
div.remove()
}
const app = createApp({
render() {
return h(JeremyDialog, {
visible: true,
'onUpdate:visible': newVisible => {
if (newVisible === false) {
close();
}
},
title,
ok, cancel
}, { default() { return content } })
}
})
app.mount(div)
}
然後再需要使用的地方匯入即可:
import {createDialog} from './createDialog.ts'
注意:該函式要求傳入一個 options
物件,該物件包含 title, content, ok, cancel
等 4 個部分,content
指代元件式中的插槽,其餘含義見需求分析
然後使用 h
函式渲染新 app
中的內容,並作為引數傳入 createApp
函式用以建立新的 app
,最後掛載到 DOM
樹上。
執行效果
接下來,我們將元件引入到頁面中,看一下實際執行效果
感謝閱讀 ☕