refactor: visible condition of modal component (#5078)

#### What type of PR is this?

/area console
/kind improvement
/milestone 2.12.x

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

重构 UI 的 Modal 组件,支持通过 v-if 控制是否显示(渲染)。

example:

```vue
<script lang="ts" setup>
import { ref } from "vue"
const visible = ref(false)
const modal = ref()

function open() {
  visible.value = true
}

function close() {
  modal.value.close()
}
</script>

<template>
  <button @click="open">Open</button>
    <VModal v-if="visible" ref="modal" title="test">
      <button @click="close">Close</button>
    </VModal>
</template>
```

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

Fixes #5077 

#### Special notes for your reviewer:

测试方式:

1. cd console && pnpm --filter "./packages/components" storybook
2. 测试 Modal 组件在文档中是否工作正常。
3. 启动 Console 或者 UC。
4. 观察以前页面上的弹框是否工作正常。

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

```release-note
重构 UI 的 Modal 组件,支持通过 v-if 控制是否显示(渲染)。
```
pull/5128/head
Ryan Wang 2023-12-29 15:15:39 +08:00 committed by GitHub
parent 36ebc24aeb
commit 44cb311fac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 39 additions and 21 deletions

View File

@ -4,6 +4,7 @@ import { VModal } from ".";
import { VButton } from "../button"; import { VButton } from "../button";
import { IconArrowLeft, IconArrowRight } from "@/icons/icons"; import { IconArrowLeft, IconArrowRight } from "@/icons/icons";
import { VSpace } from "../space"; import { VSpace } from "../space";
import { ref } from "vue";
const meta: Meta<typeof VModal> = { const meta: Meta<typeof VModal> = {
title: "Modal", title: "Modal",
@ -12,16 +13,20 @@ const meta: Meta<typeof VModal> = {
render: (args) => ({ render: (args) => ({
components: { VModal, VButton, VSpace, IconArrowLeft, IconArrowRight }, components: { VModal, VButton, VSpace, IconArrowLeft, IconArrowRight },
setup() { setup() {
return { args }; const modal = ref();
return { args, modal };
}, },
template: ` template: `
<VButton type="secondary" @click="args.visible = true"></VButton> <VButton type="secondary" @click="args.visible = true"></VButton>
<VModal <VModal
v-model:visible="args.visible" ref="modal"
v-if="args.visible"
:fullscreen="args.fullscreen" :fullscreen="args.fullscreen"
:title="args.title" :title="args.title"
:width="args.width" :width="args.width"
:mount-to-body="true" :mount-to-body="true"
:layerClosable="true"
@close="args.visible = false"
> >
<template #actions> <template #actions>
<span> <span>
@ -43,7 +48,7 @@ const meta: Meta<typeof VModal> = {
<VButton loading type="primary" @click="args.visible = false" <VButton loading type="primary" @click="args.visible = false"
> >
</VButton> </VButton>
<VButton @click="args.visible = false"></VButton> <VButton @click="modal.close()"></VButton>
</VSpace> </VSpace>
</template> </template>
</VModal> </VModal>

View File

@ -1,5 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed, nextTick, reactive, ref, watch } from "vue"; import { computed, nextTick, reactive, ref, watch, onMounted } from "vue";
import { IconClose } from "../../icons/icons"; import { IconClose } from "../../icons/icons";
import type { UseOverlayScrollbarsParams } from "overlayscrollbars-vue"; import type { UseOverlayScrollbarsParams } from "overlayscrollbars-vue";
import { useOverlayScrollbars } from "overlayscrollbars-vue"; import { useOverlayScrollbars } from "overlayscrollbars-vue";
@ -17,7 +17,7 @@ const props = withDefaults(
layerClosable?: boolean; layerClosable?: boolean;
}>(), }>(),
{ {
visible: false, visible: undefined,
title: undefined, title: undefined,
width: 500, width: 500,
height: undefined, height: undefined,
@ -34,9 +34,23 @@ const emit = defineEmits<{
(event: "close"): void; (event: "close"): void;
}>(); }>();
const internalVisible = ref(false);
const rootVisible = ref(false); const rootVisible = ref(false);
const modelWrapper = ref<HTMLElement>(); const modelWrapper = ref<HTMLElement>();
watch(
() => props.visible,
() => {
internalVisible.value = props.visible;
}
);
onMounted(() => {
if (props.visible === undefined) {
internalVisible.value = true;
}
});
const wrapperClasses = computed(() => { const wrapperClasses = computed(() => {
return { return {
"modal-wrapper-fullscreen": props.fullscreen, "modal-wrapper-fullscreen": props.fullscreen,
@ -52,10 +66,17 @@ const contentStyles = computed(() => {
}); });
function handleClose() { function handleClose() {
emit("update:visible", false); internalVisible.value = false;
emit("close"); setTimeout(() => {
emit("update:visible", false);
emit("close");
}, 200);
} }
defineExpose({
close: handleClose,
});
const focus = ref(false); const focus = ref(false);
function handleClickLayer() { function handleClickLayer() {
@ -69,17 +90,6 @@ function handleClickLayer() {
}, 300); }, 300);
} }
watch(
() => props.visible,
() => {
if (props.visible) {
nextTick(() => {
modelWrapper.value?.focus();
});
}
}
);
// body scroll // body scroll
const modalBody = ref(null); const modalBody = ref(null);
const reactiveParams = reactive<UseOverlayScrollbarsParams>({ const reactiveParams = reactive<UseOverlayScrollbarsParams>({
@ -93,10 +103,13 @@ const reactiveParams = reactive<UseOverlayScrollbarsParams>({
}); });
const [initialize, instance] = useOverlayScrollbars(reactiveParams); const [initialize, instance] = useOverlayScrollbars(reactiveParams);
watch( watch(
() => props.visible, () => internalVisible.value,
(value) => { (value) => {
if (value) { if (value) {
if (modalBody.value) initialize({ target: modalBody.value }); if (modalBody.value) initialize({ target: modalBody.value });
nextTick(() => {
modelWrapper.value?.focus();
});
} else { } else {
instance()?.destroy(); instance()?.destroy();
} }
@ -126,7 +139,7 @@ watch(
@after-leave="rootVisible = false" @after-leave="rootVisible = false"
> >
<div <div
v-show="visible" v-show="internalVisible"
class="modal-layer" class="modal-layer"
@click.stop="handleClickLayer()" @click.stop="handleClickLayer()"
/> />
@ -140,7 +153,7 @@ watch(
leave-to-class="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" leave-to-class="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
> >
<div <div
v-show="visible" v-show="internalVisible"
:style="contentStyles" :style="contentStyles"
class="modal-content transform transition-all duration-300" class="modal-content transform transition-all duration-300"
:class="{ 'modal-focus': focus }" :class="{ 'modal-focus': focus }"