feat: add layer-closeable prop for modal (#861)

#### What type of PR is this?

/kind feature

#### What this PR does / why we need it:

VModal 组件支持设置是否允许点击蒙层以关闭模态框,并将所有包含表单的模态框设置为不允许点击蒙层关闭。

#### Which issue(s) this PR fixes:

Fixes https://github.com/halo-dev/halo/issues/3328

#### Screenshots:

![2023-02-17 12 08 56](https://user-images.githubusercontent.com/21301288/219547318-d7c59742-8546-4bc8-9d49-fcff4053602f.gif)


#### Special notes for your reviewer:

测试方式:

1. 打开 Console 端任意一个表单模态框,测试点击表单外部区域是否会关闭弹框。

#### Does this PR introduce a user-facing change?


```release-note
优化 Console 端部分包含表单的模态框,默认不允许点击外部区域关闭模态框。
```
pull/841/head^2
Ryan Wang 2023-02-17 14:52:13 +08:00 committed by GitHub
parent fad0dcc3ba
commit c8dbd2422c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 39 additions and 3 deletions

View File

@ -90,7 +90,12 @@ const handleClose = () => {
}; };
</script> </script>
<template> <template>
<VModal :visible="visible" :width="450" @close="handleCancel()"> <VModal
:visible="visible"
:width="450"
:layer-closable="true"
@close="handleCancel()"
>
<div class="flex justify-between items-start py-2 mb-2"> <div class="flex justify-between items-start py-2 mb-2">
<div class="flex flex-row items-center gap-3"> <div class="flex flex-row items-center gap-3">
<component <component

View File

@ -12,6 +12,7 @@ const props = withDefaults(
bodyClass?: string[]; bodyClass?: string[];
mountToBody?: boolean; mountToBody?: boolean;
centered?: boolean; centered?: boolean;
layerClosable?: boolean;
}>(), }>(),
{ {
visible: false, visible: false,
@ -22,6 +23,7 @@ const props = withDefaults(
bodyClass: undefined, bodyClass: undefined,
mountToBody: false, mountToBody: false,
centered: true, centered: true,
layerClosable: false,
} }
); );
@ -52,6 +54,19 @@ function handleClose() {
emit("close"); emit("close");
} }
const focus = ref(false);
function handleClickLayer() {
if (props.layerClosable) {
handleClose();
return;
}
focus.value = true;
setTimeout(() => {
focus.value = false;
}, 300);
}
watch( watch(
() => props.visible, () => props.visible,
() => { () => {
@ -85,7 +100,11 @@ watch(
@before-enter="rootVisible = true" @before-enter="rootVisible = true"
@after-leave="rootVisible = false" @after-leave="rootVisible = false"
> >
<div v-show="visible" class="modal-layer" @click.stop="handleClose()" /> <div
v-show="visible"
class="modal-layer"
@click.stop="handleClickLayer()"
/>
</transition> </transition>
<transition <transition
enter-active-class="ease-out duration-200" enter-active-class="ease-out duration-200"
@ -98,7 +117,8 @@ watch(
<div <div
v-show="visible" v-show="visible"
:style="contentStyles" :style="contentStyles"
class="modal-content transform transition-all" class="modal-content transform transition-all duration-300"
:class="{ 'modal-focus': focus }"
> >
<div v-if="$slots.header || title" class="modal-header group"> <div v-if="$slots.header || title" class="modal-header group">
<slot name="header"> <slot name="header">
@ -163,6 +183,10 @@ watch(
width: calc(100vw - 20px); width: calc(100vw - 20px);
max-height: calc(100vh - 5rem); max-height: calc(100vh - 5rem);
&.modal-focus {
@apply scale-[1.02];
}
.modal-header { .modal-header {
@apply flex @apply flex
justify-between justify-between

View File

@ -372,6 +372,7 @@ const onVisibleChange = (visible: boolean) => {
:mount-to-body="true" :mount-to-body="true"
:width="650" :width="650"
:centered="false" :centered="false"
:layer-closable="true"
@update:visible="onVisibleChange" @update:visible="onVisibleChange"
> >
<div id="search-input" class="border-b border-gray-100 px-4 py-2.5"> <div id="search-input" class="border-b border-gray-100 px-4 py-2.5">

View File

@ -32,6 +32,7 @@ const onVisibleChange = (visible: boolean) => {
:visible="visible" :visible="visible"
fullscreen fullscreen
:title="title" :title="title"
:layer-closable="true"
@update:visible="onVisibleChange" @update:visible="onVisibleChange"
> >
<template #actions> <template #actions>

View File

@ -75,6 +75,7 @@ const onVisibleChange = (visible: boolean) => {
:visible="visible" :visible="visible"
:width="1000" :width="1000"
:mount-to-body="mountToBody" :mount-to-body="mountToBody"
:layer-closable="true"
height="calc(100vh - 20px)" height="calc(100vh - 20px)"
@update:visible="onVisibleChange" @update:visible="onVisibleChange"
> >

View File

@ -122,6 +122,7 @@ watch(
:width="750" :width="750"
title="存储策略" title="存储策略"
:body-class="['!p-0']" :body-class="['!p-0']"
:layer-closable="true"
@update:visible="onVisibleChange" @update:visible="onVisibleChange"
> >
<template #actions> <template #actions>

View File

@ -87,6 +87,7 @@ const handleConfirm = () => {
:visible="visible" :visible="visible"
:width="1240" :width="1240"
:mount-to-body="true" :mount-to-body="true"
:layer-closable="true"
title="选择附件" title="选择附件"
height="calc(100vh - 20px)" height="calc(100vh - 20px)"
@update:visible="onVisibleChange" @update:visible="onVisibleChange"

View File

@ -29,6 +29,7 @@ const onVisibleChange = (visible: boolean) => {
<VModal <VModal
:body-class="['!p-0']" :body-class="['!p-0']"
:visible="visible" :visible="visible"
:layer-closable="true"
fullscreen fullscreen
title="文章预览" title="文章预览"
@update:visible="onVisibleChange" @update:visible="onVisibleChange"

View File

@ -58,6 +58,7 @@
v-model:visible="widgetsModal" v-model:visible="widgetsModal"
height="calc(100vh - 20px)" height="calc(100vh - 20px)"
:width="1280" :width="1280"
:layer-closable="true"
title="小组件" title="小组件"
> >
<VTabbar <VTabbar