做完按鈕之后,我們應該了解了遮罩層的概念,接下來我們來做
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
樹上。
運行效果
接下來,我們將組件引入到頁面中,看一下實際運行效果
項目地址 🎁
GitHub: https://github.com/JeremyWu917/jeremy-ui
官網地址 🌍
JeremyUI: https://ui.jeremywu.top
感謝閱讀 ☕