Dialog 组件
需求
-
点击后弹出
-
有遮罩层overlay
-
有close按钮
-
有标题
-
有内容
-
有yes/no按钮
API设计
- Dialog组件怎么用
<Dialog
:visible ="true"
title="标题"
@yes="fn1" @no="fn2"
></Dialog>
创建Dialog组件
创建Dialog.vue,在DialogDemo.vue里面引入Dialog.vue,在Dialog.vue里面创建两个div,一个是dialog-overlay,一个是dialog-wrapper
让Dialog支持visible属性
不要用show表示是否可见(是个错误的命令,show是动词,不能表示可见)
首先,在Dialog.vue 里面接受一个props,然后DialogDemo.vue里面声明一个变量,让:visible="x"
props: {
visible: {
type: Boolean,
default: false,
},
这时visible没有任何的作用,看不见效果,使用:
在template标签里面再写个template , <template><template>其他内容</template></template>
<template>
<template v-if="visible">
<div class="circle-dialog-overlay"></div>
<div class="circle-dialog-wrapper">
<div class="circle-dialog">
<header>
标题
<span class="gulu-dialog-close"></span>
</header>
<main>
<p>第一行字</p><p>第二行字</p>
</main>
<footer>
<Button level="main">OK</Button>
<Button>Cancel</Button>
</footer>
</div>
</div>
</template>
</template>
<script lang="ts">
import Button from "./Button.vue";
export default {
components: {
Button,
},
props: {
visible: {
type: Boolean,
default: false,
},
}
}
</script>
让Dialog可以点击关闭
注意不能修改 props
<Dialog :visible="x" @update:visible="x = $event"></Dialog>
//等价于
<Dialogv v-model:visible="x"></Dialog>
点击ok按钮关闭,点击cancel按钮关闭,点击右上方X关闭(closeOnClickOverlay)
让Dialog支持title和content
为了让title和content都可以支持自定义内容
使用插槽 slot
使用具名插槽
顾名思义,就是具有名字的插槽。就是将插槽后面加上name="..."
在Dialog.vue使用<slot name="title"/>和<slot name="content"/>
然后在DialogDemo.vue里面使用
<template v-slot:content>
<strong>hi</strong>
<div>hi2</div>
</template>
<template v-slot:title>
<strong>加粗的标题</strong>
</template>
使用Teleport
把Dialog移到body下
防止Dialog被遮挡
新组件:Teleport
将两个div都放到Teleport里面,加上属性<Teleport to="body">...</Teleport>。可以理解为:Teleport相当于一个传送门,请把我传送到body的下面
为什么把Dialog移到body下面?
因为CSS存在层叠上下文的,导致Dialog被遮住,使用用Teleport。
一句话打开Dialog
动态挂载组件
创建showDialog.ts
showDialog.vue
import Dialog from "./Dialog.vue";
import { createApp, h } from "vue";
export const openDialog = (options) => {
const { title, content, ok, cancel } = options;
const div = document.createElement("div");
document.body.appendChild(div);
const close = () => {
app.unmount();
div.remove();
};
const app = createApp({
render() {
return h(
Dialog,
{
visible: true,
"onUpdate:visible": (newVisible) => {
if (newVisible === false) {
close();
}
},
ok,
cancel,
},
{
title,
content,
}
);
},
});
app.mount(div);
};
DialogDemo.vue
<template>
<div>Dialog 示例</div>
<h1>示例1</h1>
<Button @click="toggle">toggle</Button>
<Dialog
v-model:visible="x"
:closeOnClickOverlay="false"
:ok="f1"
:cancel="f2"
>
<template v-slot:content>
<strong>hi</strong>
<div>hi2</div>
</template>
<template v-slot:title>
<strong>加粗的标题</strong>
</template>
</Dialog>
<h1>示例2</h1>
<Button @click="showDialog">show</Button>
</template>
<script lang="ts">
import { ref, h } from "vue";
import Button from "../lib/Button.vue";
import Dialog from "../lib/Dialog.vue";
import { openDialog } from "../lib/openDialog";
export default {
components: { Dialog, Button },
setup() {
const x = ref(false); //x 的参考值为false
const toggle = () => {
x.value = !x.value; //x 的值等于 x 的值取相反值
};
const f1 = () => {
return false;
};
const f2 = () => {};
const showDialog = () => {
openDialog({
title: h("strong", {}, "标题"),
content: "你好",
ok() {
console.log("ok");
},
cancel() {
console.log("cancel");
},
});
};
return {
x,
toggle,
f1,
f2,
showDialog,
};
},
};
</script>
Dialog.vue
<template>
<template v-if="visible">
<Teleport to="body">
<div class="circle-dialog-overlay" @click="onClickOverlay"></div>
<div class="circle-dialog-wrapper">
<div class="circle-dialog">
<header>
<slot name="title" />
<span @click="close" class="circle-dialog-close"></span>
</header>
<main>
<slot name="content" />
</main>
<footer>
<Button level="main" @click="ok">OK</Button>
<Button @click="cancel">Cancel</Button>
</footer>
</div>
</div>
</Teleport>
</template>
</template>
<script lang="ts">
import Button from "./Button.vue";
export default {
components: {
Button,
},
props: {
visible: {
type: Boolean,
default: false,
},
closeOnClickOverlay: {
type: Boolean,
default: true,
}, //是否要做到遮盖层关闭,默认是true
ok: {
type: Function,
},
cancel: {
type: Function,
},
},
setup(props, context) {
const close = () => {
context.emit("update:visible", false);
};
const onClickOverlay = () => {
if (props.closeOnClickOverlay) {
close();
}
//如果开启这个功能就调用这个close,否则什么都不做
};
const ok = () => {
if (props.ok && props.ok !== false) {
close();
}
//如果ok 存在,且props.ok执行之后的返回值不等于false,就close
};
const cancel = () => {
props.cancel && props.cancel();
close();
};
return {
close,
onClickOverlay,
ok,
cancel,
};
},
};
</script>
<style lang="scss">
$radius: 4px;
$border-color: #d9d9d9;
.circle-dialog {
background: white;
border-radius: $radius;
box-shadow: 0 0 3px fade-out(black, 0.5);
min-width: 15em;
max-width: 90%;
&-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: fade-out(black, 0.5);
z-index: 10;
}
&-wrapper {
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
z-index: 11;
}
> header {
padding: 12px 6px;
border-bottom: 1px solid $border-color;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 20px;
}
> main {
padding: 12px 16px;
}
> footer {
border-top: 1px solid $border-color;
padding: 12px 16px;
text-align: right;
}
&-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);
}
}
}
</style>