| <template> | |
| <Teleport to="body"> | |
| <Transition :name="`drawer-slide-${placement}`" | |
| @afterLeave="contentVisible = false" | |
| @before-enter="contentVisible = true" | |
| > | |
| <div :class="['drawer', placement]" v-show="visible" :style="{ width: props.width + 'px' }"> | |
| <div class="header"> | |
| <slot name="title"></slot> | |
| <span class="close-btn" @click="emit('update:visible', false)"><IconClose /></span> | |
| </div> | |
| <div class="content" v-if="contentVisible" :style="contentStyle"> | |
| <slot></slot> | |
| </div> | |
| </div> | |
| </Transition> | |
| </Teleport> | |
| </template> | |
| <script lang="ts" setup> | |
| import { computed, ref, type CSSProperties } from 'vue' | |
| const props = withDefaults(defineProps<{ | |
| visible: boolean | |
| width?: number | |
| contentStyle?: CSSProperties | |
| placement?: 'left' | 'right' | |
| }>(), { | |
| width: 320, | |
| placement: 'right', | |
| }) | |
| const emit = defineEmits<{ | |
| (event: 'update:visible', payload: boolean): void | |
| }>() | |
| const contentVisible = ref(false) | |
| const contentStyle = computed(() => { | |
| return { | |
| width: props.width + 'px', | |
| ...(props.contentStyle || {}) | |
| } | |
| }) | |
| </script> | |
| <style lang="scss" scoped> | |
| .drawer { | |
| height: 100%; | |
| position: fixed; | |
| top: 0; | |
| bottom: 0; | |
| z-index: 5000; | |
| background: #fff; | |
| display: flex; | |
| flex-direction: column; | |
| &.left { | |
| left: 0; | |
| box-shadow: 3px 0 6px -4px rgba(0, 0, 0, 0.12), 9px 0 28px 8px rgba(0, 0, 0, 0.05); | |
| } | |
| &.right { | |
| right: 0; | |
| box-shadow: -3px 0 6px -4px rgba(0, 0, 0, 0.12), -9px 0 28px 8px rgba(0, 0, 0, 0.05); | |
| } | |
| } | |
| .header { | |
| height: 50px; | |
| padding: 0 15px; | |
| position: relative; | |
| display: flex; | |
| align-items: center; | |
| .close-btn { | |
| width: 20px; | |
| height: 20px; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| position: absolute; | |
| top: 15px; | |
| right: 15px; | |
| cursor: pointer; | |
| } | |
| } | |
| .content { | |
| padding: 0 15px; | |
| overflow: auto; | |
| flex: 1; | |
| } | |
| .drawer-slide-right-enter-active { | |
| animation: drawer-slide-right-enter .25s both ease; | |
| } | |
| .drawer-slide-right-leave-active { | |
| animation: drawer-slide-right-leave .25s both ease; | |
| } | |
| .drawer-slide-left-enter-active { | |
| animation: drawer-slide-left-enter .25s both ease; | |
| } | |
| .drawer-slide-left-leave-active { | |
| animation: drawer-slide-left-leave .25s both ease; | |
| } | |
| @keyframes drawer-slide-right-enter { | |
| from { | |
| transform: translateX(100%); | |
| } | |
| } | |
| @keyframes drawer-slide-right-leave { | |
| to { | |
| transform: translateX(100%); | |
| } | |
| } | |
| @keyframes drawer-slide-left-enter { | |
| from { | |
| transform: translateX(-100%); | |
| } | |
| } | |
| @keyframes drawer-slide-left-leave { | |
| to { | |
| transform: translateX(-100%); | |
| } | |
| } | |
| </style> |