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 { IconArrowLeft, IconArrowRight } from "@/icons/icons";
 | 
			
		||||
import { VSpace } from "../space";
 | 
			
		||||
import { ref } from "vue";
 | 
			
		||||
 | 
			
		||||
const meta: Meta<typeof VModal> = {
 | 
			
		||||
  title: "Modal",
 | 
			
		||||
| 
						 | 
				
			
			@ -12,16 +13,20 @@ const meta: Meta<typeof VModal> = {
 | 
			
		|||
  render: (args) => ({
 | 
			
		||||
    components: { VModal, VButton, VSpace, IconArrowLeft, IconArrowRight },
 | 
			
		||||
    setup() {
 | 
			
		||||
      return { args };
 | 
			
		||||
      const modal = ref();
 | 
			
		||||
      return { args, modal };
 | 
			
		||||
    },
 | 
			
		||||
    template: `
 | 
			
		||||
      <VButton type="secondary" @click="args.visible = true">打开</VButton>
 | 
			
		||||
      <VModal
 | 
			
		||||
        v-model:visible="args.visible"
 | 
			
		||||
        ref="modal"
 | 
			
		||||
        v-if="args.visible"
 | 
			
		||||
        :fullscreen="args.fullscreen"
 | 
			
		||||
        :title="args.title"
 | 
			
		||||
        :width="args.width"
 | 
			
		||||
        :mount-to-body="true"
 | 
			
		||||
        :layerClosable="true"
 | 
			
		||||
        @close="args.visible = false"
 | 
			
		||||
      >
 | 
			
		||||
        <template #actions>
 | 
			
		||||
          <span>
 | 
			
		||||
| 
						 | 
				
			
			@ -43,7 +48,7 @@ const meta: Meta<typeof VModal> = {
 | 
			
		|||
            <VButton loading type="primary" @click="args.visible = false"
 | 
			
		||||
              >确定
 | 
			
		||||
            </VButton>
 | 
			
		||||
            <VButton @click="args.visible = false">取消</VButton>
 | 
			
		||||
            <VButton @click="modal.close()">取消</VButton>
 | 
			
		||||
          </VSpace>
 | 
			
		||||
        </template>
 | 
			
		||||
      </VModal>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
<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 type { UseOverlayScrollbarsParams } from "overlayscrollbars-vue";
 | 
			
		||||
import { useOverlayScrollbars } from "overlayscrollbars-vue";
 | 
			
		||||
| 
						 | 
				
			
			@ -17,7 +17,7 @@ const props = withDefaults(
 | 
			
		|||
    layerClosable?: boolean;
 | 
			
		||||
  }>(),
 | 
			
		||||
  {
 | 
			
		||||
    visible: false,
 | 
			
		||||
    visible: undefined,
 | 
			
		||||
    title: undefined,
 | 
			
		||||
    width: 500,
 | 
			
		||||
    height: undefined,
 | 
			
		||||
| 
						 | 
				
			
			@ -34,9 +34,23 @@ const emit = defineEmits<{
 | 
			
		|||
  (event: "close"): void;
 | 
			
		||||
}>();
 | 
			
		||||
 | 
			
		||||
const internalVisible = ref(false);
 | 
			
		||||
const rootVisible = ref(false);
 | 
			
		||||
const modelWrapper = ref<HTMLElement>();
 | 
			
		||||
 | 
			
		||||
watch(
 | 
			
		||||
  () => props.visible,
 | 
			
		||||
  () => {
 | 
			
		||||
    internalVisible.value = props.visible;
 | 
			
		||||
  }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  if (props.visible === undefined) {
 | 
			
		||||
    internalVisible.value = true;
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const wrapperClasses = computed(() => {
 | 
			
		||||
  return {
 | 
			
		||||
    "modal-wrapper-fullscreen": props.fullscreen,
 | 
			
		||||
| 
						 | 
				
			
			@ -52,10 +66,17 @@ const contentStyles = computed(() => {
 | 
			
		|||
});
 | 
			
		||||
 | 
			
		||||
function handleClose() {
 | 
			
		||||
  emit("update:visible", false);
 | 
			
		||||
  emit("close");
 | 
			
		||||
  internalVisible.value = false;
 | 
			
		||||
  setTimeout(() => {
 | 
			
		||||
    emit("update:visible", false);
 | 
			
		||||
    emit("close");
 | 
			
		||||
  }, 200);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
defineExpose({
 | 
			
		||||
  close: handleClose,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const focus = ref(false);
 | 
			
		||||
 | 
			
		||||
function handleClickLayer() {
 | 
			
		||||
| 
						 | 
				
			
			@ -69,17 +90,6 @@ function handleClickLayer() {
 | 
			
		|||
  }, 300);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
watch(
 | 
			
		||||
  () => props.visible,
 | 
			
		||||
  () => {
 | 
			
		||||
    if (props.visible) {
 | 
			
		||||
      nextTick(() => {
 | 
			
		||||
        modelWrapper.value?.focus();
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
// body scroll
 | 
			
		||||
const modalBody = ref(null);
 | 
			
		||||
const reactiveParams = reactive<UseOverlayScrollbarsParams>({
 | 
			
		||||
| 
						 | 
				
			
			@ -93,10 +103,13 @@ const reactiveParams = reactive<UseOverlayScrollbarsParams>({
 | 
			
		|||
});
 | 
			
		||||
const [initialize, instance] = useOverlayScrollbars(reactiveParams);
 | 
			
		||||
watch(
 | 
			
		||||
  () => props.visible,
 | 
			
		||||
  () => internalVisible.value,
 | 
			
		||||
  (value) => {
 | 
			
		||||
    if (value) {
 | 
			
		||||
      if (modalBody.value) initialize({ target: modalBody.value });
 | 
			
		||||
      nextTick(() => {
 | 
			
		||||
        modelWrapper.value?.focus();
 | 
			
		||||
      });
 | 
			
		||||
    } else {
 | 
			
		||||
      instance()?.destroy();
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -126,7 +139,7 @@ watch(
 | 
			
		|||
        @after-leave="rootVisible = false"
 | 
			
		||||
      >
 | 
			
		||||
        <div
 | 
			
		||||
          v-show="visible"
 | 
			
		||||
          v-show="internalVisible"
 | 
			
		||||
          class="modal-layer"
 | 
			
		||||
          @click.stop="handleClickLayer()"
 | 
			
		||||
        />
 | 
			
		||||
| 
						 | 
				
			
			@ -140,7 +153,7 @@ watch(
 | 
			
		|||
        leave-to-class="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
 | 
			
		||||
      >
 | 
			
		||||
        <div
 | 
			
		||||
          v-show="visible"
 | 
			
		||||
          v-show="internalVisible"
 | 
			
		||||
          :style="contentStyles"
 | 
			
		||||
          class="modal-content transform transition-all duration-300"
 | 
			
		||||
          :class="{ 'modal-focus': focus }"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue