Vue3抽屉(Drawer)

73 篇文章 4 订阅
70 篇文章 3 订阅

效果如下图:在线预览

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

APIs

Drawer

参数说明类型默认值必传
width宽度,在 placementrightleft 时使用,单位 pxstring | number378false
height高度,在 placementtopbottom 时使用,单位 pxstring | number378false
title标题string | slotundefinedfalse
closable是否显示左上角的关闭按钮booleantruefalse
placement抽屉的方向‘top’ | ‘right’ | ‘bottom’ | ‘left’‘right’false
headerClass设置 Drawer 头部的类名stringundefinedfalse
headerStyle设置 Drawer 头部的样式CSSProperties{}false
scrollbarPropsScrollbar 组件属性配置,参考 Scrollbar Props,用于设置内容滚动条的样式object{}false
bodyClass设置 Drawer 内容部分的类名stringundefinedfalse
bodyStyle设置 Drawer 内容部分的样式CSSProperties{}false
extra抽屉右上角的操作区域string | slotundefinedfalse
footer抽屉的页脚string | slotundefinedfalse
footerClass设置 Drawer 页脚的类名stringundefinedfalse
footerStyle设置 Drawer 页脚的样式CSSProperties{}false
destroyOnClose关闭时是否销毁 Drawer 里的子元素booleanfalsefalse
zIndex设置 Drawerz-indexnumber1000false
open v-model抽屉是否可见booleanfalsefalse

Events

名称说明类型
close点击遮罩层或左上角叉或取消按钮的回调(e: Event) => void

创建抽屉组件Drawer.vue

<script setup lang="ts">
import { computed, useSlots } from 'vue'
import type { CSSProperties } from 'vue'
import Scrollbar from '../scrollbar'
interface Props {
  width?: string | number // 宽度,在 placement 为 right 或 left 时使用,单位 px
  height?: string | number // 高度,在 placement 为 top 或 bottom 时使用,单位 px
  title?: string // 标题 string | slot
  closable?: boolean // 是否显示左上角的关闭按钮
  placement?: 'top' | 'right' | 'bottom' | 'left' // 抽屉的方向
  headerClass?: string // 设置 Drawer 头部的类名
  headerStyle?: CSSProperties // 设置 Drawer 头部的样式
  scrollbarProps?: object // Scrollbar 组件属性配置,用于设置内容滚动条的样式
  bodyClass?: string // 设置 Drawer 内容部分的类名
  bodyStyle?: CSSProperties // 设置 Drawer 内容部分的样式
  extra?: string // 抽屉右上角的操作区域 string | slot
  footer?: string // 抽屉的页脚 string | slot
  footerClass?: string // 设置 Drawer 页脚的类名
  footerStyle?: CSSProperties // 设置 Drawer 页脚的样式
  destroyOnClose?: boolean // 关闭时是否销毁 Drawer 里的子元素
  zIndex?: number // 设置 Drawer 的 z-index
  open?: boolean // (v-model) 抽屉是否可见
}
const props = withDefaults(defineProps<Props>(), {
  width: 378,
  height: 378,
  title: undefined,
  closable: true,
  placement: 'right',
  headerClass: undefined,
  headerStyle: () => ({}),
  scrollbarProps: () => ({}),
  bodyClass: undefined,
  bodyStyle: () => ({}),
  extra: undefined,
  footer: undefined,
  footerClass: undefined,
  footerStyle: () => ({}),
  destroyOnClose: false,
  zIndex: 1000,
  open: false
})
const drawerWidth = computed(() => {
  if (typeof props.width === 'number') {
    return props.width + 'px'
  }
  return props.width
})
const drawerHeight = computed(() => {
  if (typeof props.height === 'number') {
    return props.height + 'px'
  }
  return props.height
})
const slots = useSlots()
const showHeader = computed(() => {
  const titleSlots = slots.title?.()
  const extraSlots = slots.extra?.()
  let n = 0
  if (titleSlots && titleSlots.length) {
    n++
  }
  if (extraSlots && extraSlots.length) {
    n++
  }
  return Boolean(n) || props.title || props.extra || props.closable
})
const showFooter = computed(() => {
  const footerSlots = slots.footer?.()
  return (footerSlots && footerSlots.length) || props.footer
})
const emits = defineEmits(['update:open', 'close'])
function onBlur(e: Event) {
  emits('update:open', false)
  emits('close', e)
}
function onClose(e: Event) {
  emits('update:open', false)
  emits('close', e)
}
</script>
<template>
  <div class="m-drawer" tabindex="-1">
    <Transition name="fade">
      <div v-show="open" class="m-drawer-mask" @click.self="onBlur"></div>
    </Transition>
    <Transition :name="`motion-${placement}`">
      <div
        v-show="open"
        class="m-drawer-wrapper"
        :class="`drawer-${placement}`"
        :style="`z-index: ${zIndex}; ${['top', 'bottom'].includes(placement) ? 'height:' + drawerHeight : 'width:' + drawerWidth};`"
      >
        <div class="m-drawer-content">
          <div class="m-drawer-body-wrapper" v-if="!destroyOnClose">
            <div class="m-drawer-header" :class="headerClass" :style="headerStyle" v-show="showHeader">
              <div class="m-header-title">
                <svg
                  v-if="closable"
                  focusable="false"
                  @click="onClose"
                  class="u-close"
                  data-icon="close"
                  width="1em"
                  height="1em"
                  fill="currentColor"
                  aria-hidden="true"
                  viewBox="64 64 896 896"
                >
                  <path
                    d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0 00203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"
                  ></path>
                </svg>
                <p class="u-title">
                  <slot name="title">{{ title }}</slot>
                </p>
              </div>
              <div class="m-drawer-extra">
                <slot name="extra">{{ extra }}</slot>
              </div>
            </div>
            <Scrollbar :content-style="{ height: '100%' }" v-bind="scrollbarProps">
              <div class="m-drawer-body" :class="bodyClass" :style="bodyStyle">
                <slot></slot>
              </div>
            </Scrollbar>
            <div class="m-drawer-footer" :class="footerClass" :style="footerStyle" v-show="showFooter">
              <slot name="footer">{{ footer }}</slot>
            </div>
          </div>
          <div class="m-drawer-body-wrapper" v-if="destroyOnClose && open">
            <div class="m-drawer-header" :class="headerClass" :style="headerStyle" v-show="showHeader">
              <div class="m-header-title">
                <svg
                  focusable="false"
                  @click="onClose"
                  class="u-close"
                  data-icon="close"
                  width="1em"
                  height="1em"
                  fill="currentColor"
                  aria-hidden="true"
                  viewBox="64 64 896 896"
                >
                  <path
                    d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0 00203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"
                  ></path>
                </svg>
                <p class="u-title">
                  <slot name="title">{{ title }}</slot>
                </p>
              </div>
              <div class="m-drawer-extra">
                <slot name="extra">{{ extra }}</slot>
              </div>
            </div>
            <Scrollbar :content-style="{ height: '100%' }" v-bind="scrollbarProps">
              <div class="m-drawer-body" :class="bodyClass" :style="bodyStyle">
                <slot></slot>
              </div>
            </Scrollbar>
            <div class="m-drawer-footer" :class="footerClass" :style="footerStyle" v-show="showFooter">
              <slot name="footer">{{ footer }}</slot>
            </div>
          </div>
        </div>
      </div>
    </Transition>
  </div>
</template>
<style lang="less" scoped>
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.3s;
}
.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}
.motion-top-enter-active,
.motion-top-leave-active {
  transition: all 0.3s;
}
.motion-top-enter-from,
.motion-top-leave-to {
  transform: translateY(-100%);
}
.motion-right-enter-active,
.motion-right-leave-active {
  transition: all 0.3s;
}
.motion-right-enter-from,
.motion-right-leave-to {
  transform: translateX(100%);
}
.motion-bottom-enter-active,
.motion-bottom-leave-active {
  transition: all 0.3s;
}
.motion-bottom-enter-from,
.motion-bottom-leave-to {
  transform: translateY(100%);
}
.motion-left-enter-active,
.motion-left-leave-active {
  transition: all 0.3s;
}
.motion-left-enter-from,
.motion-left-leave-to {
  transform: translateX(-100%);
}
.m-drawer {
  position: fixed;
  inset: 0;
  z-index: 1000;
  pointer-events: none;
  .m-drawer-mask {
    position: absolute;
    inset: 0;
    z-index: 1000;
    background: rgba(0, 0, 0, 0.45);
    pointer-events: auto;
  }
  .m-drawer-wrapper {
    position: absolute;
    transition: all 0.3s;
    .m-drawer-content {
      width: 100%;
      height: 100%;
      overflow: auto;
      background: #ffffff;
      pointer-events: auto;
      .m-drawer-body-wrapper {
        display: flex;
        flex-direction: column;
        width: 100%;
        height: 100%;
        .m-drawer-header {
          display: flex;
          flex: 0;
          align-items: center;
          padding: 16px 24px;
          font-size: 16px;
          line-height: 1.5;
          border-bottom: 1px solid rgba(5, 5, 5, 0.06);
          .m-header-title {
            display: flex;
            flex: 1;
            align-items: center;
            min-width: 0;
            min-height: 0;
            .u-close {
              display: inline-block;
              margin-inline-end: 12px;
              width: 16px;
              height: 16px;
              fill: rgba(0, 0, 0, 0.45);
              cursor: pointer;
              transition: fill 0.2s;
              &:hover {
                fill: rgba(0, 0, 0, 0.88);
              }
            }
            .u-title {
              flex: 1;
              margin: 0;
              color: rgba(0, 0, 0, 0.88);
              font-weight: 600;
              font-size: 16px;
              line-height: 1.5;
            }
          }
          .m-drawer-extra {
            flex: none;
            color: rgba(0, 0, 0, 0.88);
          }
        }
        .m-drawer-body {
          height: 100%;
          padding: 24px;
          word-break: break-all;
        }
        .m-drawer-footer {
          flex-shrink: 0;
          padding: 8px 16px;
          border-top: 1px solid rgba(5, 5, 5, 0.06);
          color: rgba(0, 0, 0, 0.88);
        }
      }
    }
  }
  .drawer-top {
    top: 0;
    inset-inline: 0;
    box-shadow:
      0 6px 16px 0 rgba(0, 0, 0, 0.08),
      0 3px 6px -4px rgba(0, 0, 0, 0.12),
      0 9px 28px 8px rgba(0, 0, 0, 0.05);
  }
  .drawer-right {
    top: 0;
    right: 0;
    bottom: 0;
    box-shadow:
      -6px 0 16px 0 rgba(0, 0, 0, 0.08),
      -3px 0 6px -4px rgba(0, 0, 0, 0.12),
      -9px 0 28px 8px rgba(0, 0, 0, 0.05);
  }
  .drawer-bottom {
    bottom: 0;
    inset-inline: 0;
    box-shadow:
      0 -6px 16px 0 rgba(0, 0, 0, 0.08),
      0 -3px 6px -4px rgba(0, 0, 0, 0.12),
      0 -9px 28px 8px rgba(0, 0, 0, 0.05);
  }
  .drawer-left {
    top: 0;
    bottom: 0;
    left: 0;
    box-shadow:
      6px 0 16px 0 rgba(0, 0, 0, 0.08),
      3px 0 6px -4px rgba(0, 0, 0, 0.12),
      9px 0 28px 8px rgba(0, 0, 0, 0.05);
  }
}
</style>

在要使用的页面引入

<script setup lang="ts">
import Drawer from './Drawer.vue'
import { ref } from 'vue'

const open1 = ref<boolean>(false)
const open2 = ref<boolean>(false)
const open3 = ref<boolean>(false)
const open4 = ref<boolean>(false)
const open5 = ref<boolean>(false)
const options = ref([
  {
    label: 'top',
    value: 'top'
  },
  {
    label: 'right',
    value: 'right'
  },
  {
    label: 'bottom',
    value: 'bottom'
  },
  {
    label: 'left',
    value: 'left'
  }
])
const placement = ref('right')
const extraPlacement = ref('right')
const footerPlacement = ref('right')
function onClose() {
  // 点击遮罩层或左上角叉或取消按钮的回调
  open3.value = false
  open4.value = false
  console.log('close')
}
</script>
<template>
  <div>
    <h1>{{ $route.name }} {{ $route.meta.title }}</h1>
    <h2 class="mt30 mb10">基本使用</h2>
    <Button type="primary" @click="open1 = true">Open</Button>
    <Drawer v-model:open="open1" title="Basic Drawer" @close="onClose">
      <p>Some contents...</p>
      <p>Some contents...</p>
      <p>Some contents...</p>
    </Drawer>
    <h2 class="mt30 mb10">自定义位置</h2>
    <Radio v-model:value="placement" :options="options" style="margin-right: 8px" />
    <Button type="primary" @click="open2 = true">Open</Button>
    <Drawer
      v-model:open="open2"
      title="Basic Drawer"
      :closable="false"
      extra="extra"
      footer="footer"
      :placement="placement"
    >
      <p>Some contents...</p>
      <p>Some contents...</p>
      <p>Some contents...</p>
    </Drawer>
    <h2 class="mt30 mb10">额外操作</h2>
    <Radio v-model:value="extraPlacement" :options="options" style="margin-right: 8px" />
    <Button type="primary" @click="open3 = true">Open</Button>
    <Drawer v-model:open="open3" title="Basic Drawer" :placement="extraPlacement">
      <template #extra>
        <Button style="margin-right: 8px" @click="onClose">Cancel</Button>
        <Button type="primary" @click="onClose">Submit</Button>
      </template>
      <p>Some contents...</p>
      <p>Some contents...</p>
      <p>Some contents...</p>
    </Drawer>
    <h2 class="mt30 mb10">抽屉页脚</h2>
    <Radio v-model:value="footerPlacement" :options="options" style="margin-right: 8px" />
    <Button type="primary" @click="open4 = true">Open</Button>
    <Drawer
      v-model:open="open4"
      title="Basic Drawer"
      :placement="footerPlacement"
      :footer-style="{ textAlign: 'right' }"
    >
      <p>Some contents...</p>
      <p>Some contents...</p>
      <p>Some contents...</p>
      <template #footer>
        <Button style="margin-right: 8px" @click="onClose">Cancel</Button>
        <Button type="primary" @click="onClose">Submit</Button>
      </template>
    </Drawer>
    <h2 class="mt30 mb10">自定义 header & body 样式</h2>
    <Button type="primary" @click="open5 = true">Open</Button>
    <Drawer
      v-model:open="open5"
      :closable="false"
      title="Basic Drawer"
      :header-style="{ textAlign: 'center' }"
      :body-style="{ textAlign: 'center' }"
    >
      <p>Some contents...</p>
      <p>Some contents...</p>
      <p>Some contents...</p>
    </Drawer>
  </div>
</template>
  • 6
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Vue抽屉组件是一个常见的UI组件,用于实现侧边栏的展开和收起功能。在Vue中,可以通过使用slot来实现子组件的抽屉效果。 首先,在父组件中引入抽屉组件,并设置一个布尔类型的data属性来控制抽屉的展开和收起状态: ```vue <template> <div> <button @click="showDrawer = true">打开抽屉</button> <drawer v-model="showDrawer"> <!-- 抽屉内容 --> <div slot="content"> <!-- 这里是抽屉的内容 --> </div> </drawer> </div> </template> <script> import Drawer from '抽屉组件的路径' export default { components: { Drawer }, data() { return { showDrawer: false } } } </script> ``` 然后,在抽屉组件中定义好抽屉的样式和交互逻辑: ```vue <template> <transition name="drawer"> <div class="drawer" v-show="value"> <!-- 抽屉内容插槽 --> <slot name="content"></slot> </div> </transition> </template> <script> export default { props: { value: { type: Boolean, default: false } } } </script> <style scoped> .drawer { position: fixed; top: 0; left: 0; width: 300px; height: 100vh; background-color: #fff; transition: transform 0.3s ease-in-out; transform: translateX(-100%); } .drawer-enter-active, .drawer-leave-active { transition: transform 0.3s ease-in-out; } .drawer-enter, .drawer-leave-to { transform: translateX(0); } </style> ``` 通过以上代码,就可以实现一个简单的抽屉组件。当点击打开按钮时,抽屉会从左侧滑入屏幕,并展示出内容;再次点击关闭按钮时,抽屉会滑出屏幕。你可以根据实际需要,自定义抽屉的样式和交互效果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值