123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154 |
- <template>
- <Teleport to="body">
- <Transition name="modal-fade">
- <div class="modal" ref="modalRef" v-show="visible" tabindex="-1" @keyup.esc="onEsc()">
- <div class="mask" @click="onClickMask()"></div>
- <Transition name="modal-zoom"
- @afterLeave="contentVisible = false"
- @before-enter="contentVisible = true"
- >
- <div class="modal-content" v-show="visible" :style="contentStyle">
- <span class="close-btn" v-if="closeButton" @click="close()"><IconClose /></span>
- <slot v-if="contentVisible"></slot>
- </div>
- </Transition>
- </div>
- </Transition>
- </Teleport>
- </template>
- <script lang="ts" setup>
- import { computed, nextTick, ref, watch, type CSSProperties } from 'vue'
- import { icons } from '@/plugins/icon'
- const { IconClose } = icons
- const props = withDefaults(defineProps<{
- visible: boolean
- width?: number
- closeButton?: boolean
- closeOnClickMask?: boolean
- closeOnEsc?: boolean
- contentStyle?: CSSProperties
- }>(), {
- width: 480,
- closeButton: false,
- closeOnClickMask: true,
- closeOnEsc: true,
- })
- const modalRef = ref<HTMLDivElement>()
- const emit = defineEmits<{
- (event: 'update:visible', payload: boolean): void
- (event: 'closed'): void
- }>()
- const contentVisible = ref(false)
- const contentStyle = computed(() => {
- return {
- width: props.width + 'px',
- ...(props.contentStyle || {})
- }
- })
- watch(() => props.visible, () => {
- if (props.visible) {
- nextTick(() => modalRef.value!.focus())
- }
- })
- const close = () => {
- emit('update:visible', false)
- emit('closed')
- }
- const onEsc = () => {
- if (props.visible && props.closeOnEsc) close()
- }
- const onClickMask = () => {
- if (props.closeOnClickMask) close()
- }
- </script>
- <style lang="scss" scoped>
- .modal, .mask {
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- z-index: 1999;
- }
- .modal {
- position: fixed;
- display: flex;
- justify-content: center;
- align-items: center;
- outline: 0;
- border: 0;
- }
- .mask {
- position: absolute;
- background: rgba(0, 0, 0, .25);
- }
- .modal-content {
- z-index: 5001;
- padding: 20px;
- background: #fff;
- border-radius: $borderRadius;
- overflow: hidden;
- box-shadow: 0 1px 3px rgba(0, 0, 0, .2);
- position: relative;
- }
- .close-btn {
- width: 20px;
- height: 20px;
- display: flex;
- justify-content: center;
- align-items: center;
- position: absolute;
- top: 16px;
- right: 16px;
- cursor: pointer;
- }
- .modal-fade-enter-active {
- animation: modal-fade-enter .25s both ease-in;
- }
- .modal-fade-leave-active {
- animation: modal-fade-leave .25s both ease-out;
- }
- .modal-zoom-enter-active {
- animation: modal-zoom-enter .25s both cubic-bezier(.4, 0, 0, 1.5);
- }
- .modal-zoom-leave-active {
- animation: modal-zoom-leave .25s both;
- }
- @keyframes modal-fade-enter {
- from {
- opacity: 0;
- }
- }
- @keyframes modal-fade-leave {
- to {
- opacity: 0;
- }
- }
- @keyframes modal-zoom-enter {
- from {
- transform: scale3d(.3, .3, .3);
- }
- }
- @keyframes modal-zoom-leave {
- to {
- transform: scale3d(.3, .3, .3);
- }
- }
- </style>
|