mirror of https://github.com/halo-dev/halo
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
parent
36ebc24aeb
commit
44cb311fac
|
@ -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>
|
||||||
|
|
|
@ -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 }"
|
||||||
|
|
Loading…
Reference in New Issue