APIs
参数 说明 类型 默认值 必传 width 宽度,在 placement
为 right
或 left
时使用 string | number 378 false height 高度,在 placement
为 top
或 bottom
时使用 string | number 378 false title 标题 string | slot undefined false closable 是否显示左上角的关闭按钮 boolean true false placement 抽屉的方向 ‘top’ | ‘right’ | ‘bottom’ | ‘left’ ‘right’ false headerStyle 设置 Drawer
头部的样式 CSSProperties {} false bodyStyle 设置 Drawer
内容部分的样式 CSSProperties {} false extra 抽屉右上角的操作区域 string | slot undefined false footer 抽屉的页脚 string | slot undefined false footerStyle 抽屉页脚的样式 CSSProperties {} false destroyOnClose 关闭时是否销毁 Drawer
里的子元素 boolean false false zIndex 设置 Drawer
的 z-index
number 1000 false open v-model 抽屉是否可见 boolean false false
Events
事件名称 说明 参数 close 点击遮罩层或左上角叉或取消按钮的回调 (e: Event) => void
创建抽屉组件Drawer.vue
< script setup lang= "ts" >
import { computed, useSlots, type CSSProperties } from 'vue'
interface Props { width? : string | number height? : string | number title? : string closable? : boolean placement? : 'top' | 'right' | 'bottom' | 'left' headerStyle? : CSSProperties bodyStyle? : CSSProperties extra? : string footer? : string footerStyle? : CSSProperties destroyOnClose? : boolean zIndex? : number open? : boolean
}
const props = withDefaults ( defineProps < Props> ( ) , { width: 378 , height: 378 , title: undefined , closable: true , placement: 'right' , headerStyle : ( ) => ( { } ) , bodyStyle : ( ) => ( { } ) , extra: undefined , footer: 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}`" > < divv- 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" : style= "headerStyle" v- show= "showHeader" > < div class = "m-header-title" > < svgv- 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" > < pathd= "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> < div class = "m-drawer-body" : style= "bodyStyle" > < slot> < / slot> < / div> < div class = "m-drawer-footer" : 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" : style= "headerStyle" v- show= "showHeader" > < div class = "m-header-title" > < svgfocusable= "false" @ click = "onClose" class = "u-close" data- icon= "close" width= "1em" height= "1em" fill= "currentColor" aria- hidden= "true" viewBox= "64 64 896 896" > < pathd= "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> < div class = "m-drawer-body" : style= "bodyStyle" > < slot> < / slot> < / div> < div class = "m-drawer-footer" : 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 { flex: 1 ; min- width: 0 ; min- height: 0 ; padding: 24px; overflow: auto; } . 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> < Drawerv- 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> < Drawerv- 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> < Drawerv- 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>