Drawer.vue 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. <template>
  2. <Teleport to="body">
  3. <Transition :name="`drawer-slide-${placement}`"
  4. @afterLeave="contentVisible = false"
  5. @before-enter="contentVisible = true"
  6. >
  7. <div :class="['drawer', placement]" v-show="visible" :style="{ width: props.width + 'px' }">
  8. <div class="header">
  9. <slot name="title"></slot>
  10. <span class="close-btn" @click="emit('update:visible', false)"><IconClose /></span>
  11. </div>
  12. <div class="content" v-if="contentVisible" :style="contentStyle">
  13. <slot></slot>
  14. </div>
  15. </div>
  16. </Transition>
  17. </Teleport>
  18. </template>
  19. <script lang="ts" setup>
  20. import { computed, ref, type CSSProperties } from 'vue'
  21. const props = withDefaults(defineProps<{
  22. visible: boolean
  23. width?: number
  24. contentStyle?: CSSProperties
  25. placement?: 'left' | 'right'
  26. }>(), {
  27. width: 320,
  28. placement: 'right',
  29. })
  30. const emit = defineEmits<{
  31. (event: 'update:visible', payload: boolean): void
  32. }>()
  33. const contentVisible = ref(false)
  34. const contentStyle = computed(() => {
  35. return {
  36. width: props.width + 'px',
  37. ...(props.contentStyle || {})
  38. }
  39. })
  40. </script>
  41. <style lang="scss" scoped>
  42. .drawer {
  43. height: 100%;
  44. position: fixed;
  45. top: 0;
  46. bottom: 0;
  47. z-index: 5000;
  48. background: #fff;
  49. display: flex;
  50. flex-direction: column;
  51. &.left {
  52. left: 0;
  53. box-shadow: 3px 0 6px -4px rgba(0, 0, 0, 0.12), 9px 0 28px 8px rgba(0, 0, 0, 0.05);
  54. }
  55. &.right {
  56. right: 0;
  57. box-shadow: -3px 0 6px -4px rgba(0, 0, 0, 0.12), -9px 0 28px 8px rgba(0, 0, 0, 0.05);
  58. }
  59. }
  60. .header {
  61. height: 50px;
  62. padding: 0 15px;
  63. position: relative;
  64. display: flex;
  65. align-items: center;
  66. .close-btn {
  67. width: 20px;
  68. height: 20px;
  69. display: flex;
  70. justify-content: center;
  71. align-items: center;
  72. position: absolute;
  73. top: 15px;
  74. right: 15px;
  75. cursor: pointer;
  76. }
  77. }
  78. .content {
  79. padding: 0 15px;
  80. overflow: auto;
  81. flex: 1;
  82. }
  83. .drawer-slide-right-enter-active {
  84. animation: drawer-slide-right-enter .25s both ease;
  85. }
  86. .drawer-slide-right-leave-active {
  87. animation: drawer-slide-right-leave .25s both ease;
  88. }
  89. .drawer-slide-left-enter-active {
  90. animation: drawer-slide-left-enter .25s both ease;
  91. }
  92. .drawer-slide-left-leave-active {
  93. animation: drawer-slide-left-leave .25s both ease;
  94. }
  95. @keyframes drawer-slide-right-enter {
  96. from {
  97. transform: translateX(100%);
  98. }
  99. }
  100. @keyframes drawer-slide-right-leave {
  101. to {
  102. transform: translateX(100%);
  103. }
  104. }
  105. @keyframes drawer-slide-left-enter {
  106. from {
  107. transform: translateX(-100%);
  108. }
  109. }
  110. @keyframes drawer-slide-left-leave {
  111. to {
  112. transform: translateX(-100%);
  113. }
  114. }
  115. </style>