Vben Drawer 抽屉组件使用指南:从基础用法到数据共享与状态锁定
2026/9/11 9:33:29 网站建设 项目流程

Vben Drawer 抽屉组件使用指南:从基础用法到数据共享与状态锁定

【免费下载链接】vue-vben-adminA modern vue admin panel built with Vue3, Shadcn UI, Vite, TypeScript, and Monorepo. It's fast!项目地址: https://gitcode.com/GitHub_Trending/vu/vue-vben-admin

导读

本文基于 vue-vben-admin 项目讲解其内置的VbenDrawer抽屉组件(位于 packages/@core/ui-kit/popup-ui/src/drawer)。useVbenDrawer是框架封装的高阶抽屉方案,支持自动计算高度、loading、组件抽离、connectedComponent内外组件连接、数据共享以及 5.5.3 版本引入的提交锁定(lock/unlock)等能力。读完本文,你将掌握如何创建最基础的抽屉、如何将抽屉内容抽离并复用、如何在内外组件间共享数据并约束数据类型,以及如何用drawerApisetState动态控制抽屉状态。

写在前面:如果你觉得现有组件的封装不够理想,或者不完全符合你的需求,大可以直接使用原生组件,亦或亲手封装一个适合的组件。框架提供的组件并非束缚,使用与否,完全取决于你的需求与自由。

基础用法

使用useVbenDrawer创建最基础的抽屉。以文档示例 demos/vben-drawer/basic/index.vue 为例:

<script lang="ts" setup> import { useVbenDrawer, VbenButton } from '@vben/common-ui'; const [Drawer, drawerApi] = useVbenDrawer(); </script> <template> <div> <VbenButton @click="() => drawerApi.open()">Open</VbenButton> <Drawer class="w-150" title="基础示例"> drawer content </Drawer> </div> </template>

可以看到,useVbenDrawer返回一个元组:

  • Drawer:抽屉组件,直接放入模板即可;
  • drawerApi:抽屉实例方法集合,用于控制打开、关闭、更新状态等。

抽屉的宽度等布局样式通过class配置,例如示例中的w-150(宽度 150 单位),这与框架中模态框的用法保持一致。

组件抽离

Drawer 内的内容在真实业务中通常比较复杂,因此我们可以将 drawer 内的内容抽离出来,也方便复用。通过connectedComponent参数,可以将内外组件进行连接,而不用其他任何操作。

外层入口组件示例 demos/vben-drawer/extra/index.vue:

<script lang="ts" setup> import { useVbenDrawer, VbenButton } from '@vben/common-ui'; import ExtraDrawer from './drawer.vue'; const [Drawer, drawerApi] = useVbenDrawer({ // 连接抽离的组件 connectedComponent: ExtraDrawer, }); function open() { drawerApi.open(); } </script> <template> <div> <Drawer /> <VbenButton @click="open">Open</VbenButton> </div> </template>

从源码实现看(use-drawer.ts),当传入connectedComponent时,useVbenDrawer会通过provide/inject机制把内部生成的DrawerApi注入到子组件中:外层创建一个名为VbenParentDrawer的包装组件,将connectedComponent作为其渲染内容,并通过USER_DRAWER_INJECT_KEYSymbol('VBEN_DRAWER_INJECT'))提供注入数据;内层(即抽离出的子组件)再次调用useVbenDrawer时,会通过inject获取到这份注入数据,从而拿到同一个extendedApi。这就是“不用其他任何操作”即可完成内外连接的原因。

注意:如果同时设置了相同的参数,那么以内部为准(也就是没有设置connectedComponent的代码),例如同时设置了onConfirm,那么以内部的onConfirm为准。onOpenChange事件除外,内外都会触发。

提示:使用了connectedComponent参数时,可以配置destroyOnClose属性来决定当关闭弹窗时,是否要销毁connectedComponent组件(重新创建connectedComponent组件,这将会把其内部所有的变量、状态、数据等恢复到初始状态)。源码中,onClosed回调会判断mergedOptions.destroyOnClose,并通过reCreateDrawer将内部组件重建,实现状态复位。

自动计算高度

弹窗会自动计算内容高度,超过一定高度会出现滚动条,同时结合loading效果以及使用prepend-footer插槽。完整示例见 demos/vben-drawer/auto-height(包含drawer.vueindex.vue两个文件)。

抽屉默认将内容区域控制在可视范围内,内容过多时自动出现滚动条,无需手工设置高度。配合loading属性可以在数据加载期间展示加载态,避免用户误操作。prepend-footer插槽用于在取消按钮左侧插入额外内容(如“重置”按钮),关于插槽的完整说明见下文 API 章节。

使用 Api

通过drawerApi可以调用 drawer 的方法以及使用setState更新 drawer 的状态。示例见 demos/vben-drawer/dynamic。

// Drawer 为弹窗组件 // drawerApi 为弹窗的方法 const [Drawer, drawerApi] = useVbenDrawer({ // 属性 // 事件 });

setState支持两种传参方式:直接传Partial<DrawerState>,或传入一个接收prev的函数(prev: DrawerState) => Partial<DrawerState>,两种方式都会返回drawerApi本身,因此可以链式调用,例如:

drawerApi .setState({ title: '新的标题', loading: true }) .open();

数据共享

如果你使用了connectedComponent参数,那么内外组件会共享数据,比如一些表单回填等操作。可以用drawerApi来获取数据和设置数据,配合onOpenChange,可以满足大部分的需求。示例见 demos/vben-drawer/shared-data。

子组件(connected 组件)内声明数据并暴露 api 的典型写法(shared-data/drawer.vue):

<script lang="ts" setup> import { ref } from 'vue'; import { useVbenDrawer } from '@vben/common-ui'; interface SharedData { content: string; payload: string; } const data = ref<SharedData>(); const [Drawer, drawerApi] = useVbenDrawer<SharedData>({ onCancel() { drawerApi.close(); }, onConfirm() { console.info('onConfirm'); }, onOpenChange(isOpen: boolean) { if (isOpen) { data.value = drawerApi.getData(); } }, }); defineExpose({ drawerApi }); </script>

外层组件通过setData传递数据、通过getData读取数据:

const [Drawer, drawerApi] = useVbenDrawer({ connectedComponent: EditDrawer, }); // 打开前写入共享数据 drawerApi.setData({ content: '回填内容', payload: 'id-001' }).open();

典型流程是:外层在open()前调用setData写入待回填数据;子组件在onOpenChange(isOpen === true)时调用getData()取数并渲染到表单。

数据类型约束

推荐在 connected 子组件中声明一次数据类型并暴露drawerApi,外部会从connectedComponent自动推导setDatagetData的类型:

// connected 子组件 const [Drawer, drawerApi] = useVbenDrawer<EditData>(); defineExpose({ drawerApi }); // 外部组件,无需重复声明 EditData const [Drawer, drawerApi] = useVbenDrawer({ connectedComponent: EditDrawer, });

无法从组件公开实例推导时,可以显式使用useVbenDrawer<EditData>()。需要让多个文件共享同一契约时,可以在独立模块中预绑定:

export const useEditDrawer = createVbenDrawer<EditData>();

三种方式的优先级为:显式泛型、connected component 自动推导、unknown。普通 SFC 通过defineExpose支持自动推导;泛型 SFC、函数式组件或被标注为宽Component的组件应使用显式泛型或契约工厂。getData()在尚未调用setData()时返回undefined,业务允许null、部分对象等值时,需要在数据泛型中准确声明。

从源码结构看(use-drawer.ts),内部通过ResolvedDrawerData条件类型实现推导:当调用方未显式传入泛型TData(即DrawerDataNotProvided)时,会回退到从TConnectedComponent的公开实例推导InferDrawerData<TConnectedComponent>;仓库测试 drawer-types.test.ts 与 fixtures typed-drawer.vue 覆盖了这类类型推导场景。

参数优先级

VbenDrawer组件对于参数的处理优先级是slot>props>state(通过 api 更新的状态以及useVbenDrawer参数)。如果你已经传入了slot或者props,那么setState将不会生效,这种情况下你可以通过slot或者props来更新状态。

全局默认配置

如果抽屉的默认行为不符合你的预期,可以在对应应用的apps/<app>/src/bootstrap.ts中修改setDefaultDrawerProps的参数来设置默认属性,例如修改默认zIndex等。

import { setDefaultDrawerProps } from '@vben/common-ui'; setDefaultDrawerProps({ zIndex: 1200, closeOnClickModal: false, });

源码中,setDefaultDrawerProps会将传入的属性合并进模块级常量DEFAULT_DRAWER_PROPS(use-drawer.ts),随后在每个useVbenDrawer调用中,通过mergedOptions将默认配置与当前配置合并。另外,源码还默认将全局的 Esc 快捷键配置(globalEscapeShortcutKey)作为closeOnPressEscape的默认值,使抽屉行为与应用级偏好保持一致。

API

Props

所有属性都可以传入useVbenDrawer的第一个参数中。

属性名描述类型默认值
appendToMain是否挂载到内容区域(默认挂载到 body)booleanfalse
connectedComponent连接另一个 Drawer 组件Component-
destroyOnClose关闭时销毁booleanfalse
title标题string\|slot-
titleTooltip标题提示信息string\|slot-
description描述信息string\|slot-
isOpen弹窗打开状态booleanfalse
loading弹窗加载状态booleanfalse
closable显示关闭按钮booleantrue
closeIconPlacement关闭按钮位置'left'\|'right'right
modal显示遮罩booleantrue
header显示 headerbooleantrue
footer显示 footerboolean\|slottrue
confirmLoading确认按钮 loading 状态booleanfalse
closeOnClickModal点击遮罩关闭弹窗booleantrue
closeOnPressEscapeesc 关闭弹窗booleantrue
confirmText确认按钮文本string\|slot确认
cancelText取消按钮文本string\|slot取消
placement抽屉弹出位置'left'\|'right'\|'top'\|'bottom'right
showCancelButton显示取消按钮booleantrue
showConfirmButton显示确认按钮booleantrue
classmodal 的 class,宽度通过这个配置string-
contentClassmodal 内容区域的 classstring-
footerClassmodal 底部区域的 classstring-
headerClassmodal 顶部区域的 classstring-
zIndex抽屉的 ZIndex 层级number1000
overlayBlur遮罩模糊度number-
appendToMain

appendToMain可以指定将抽屉挂载到内容区域,打开抽屉时,内容区域以外的部分(标签栏、导航菜单等等)不会被遮挡。默认情况下,抽屉会挂载到 body 上。但是:挂载到内容区域时,作为页面根容器的Page组件,需要设置auto-content-height属性,以便抽屉能够正确计算高度。

Event

以下事件,只有在useVbenDrawer({ onCancel: () => {} })中传入才会生效。

事件名描述类型版本限制
onBeforeClose关闭前触发,返回false或 Promise reject 则禁止关闭()=>Promise<boolean \| undefined>\|boolean\|undefined>5.5.2 支持 Promise
onCancel点击取消按钮触发()=>void---
onClosed关闭动画播放完毕时触发()=>void>5.5.2
onConfirm点击确认按钮触发()=>void---
onOpenChange关闭或者打开弹窗时触发(isOpen:boolean)=>void---
onOpened打开动画播放完毕时触发()=>void>5.5.2

onBeforeClose是拦截关闭的关键钩子:返回false或返回一个 reject 的 Promise 时,抽屉将禁止关闭,常用于“表单未保存确认关闭”等场景;5.5.2 版本起支持异步判断。

Slots

除了上面的属性类型包含slot,还可以通过插槽来自定义弹窗的内容。

插槽名描述
default默认插槽 - 弹窗内容
prepend-footer取消按钮左侧
center-footer取消按钮和确认按钮中间(不使用 footer 插槽时有效)
append-footer确认按钮右侧
close-icon关闭按钮图标
extra额外内容(标题右侧)

drawerApi

方法描述类型版本限制
setState动态设置抽屉状态属性(((prev: DrawerState) => Partial<DrawerState>)\| Partial<DrawerState>)=>drawerApi---
open打开弹窗()=>void---
close关闭弹窗()=>void---
setData设置共享数据(data:TData)=>drawerApi---
getData获取共享数据()=>TData\|undefined---
useStore获取可响应式状态----
lock将抽屉标记为提交中,锁定当前状态(isLock:boolean)=>drawerApi>5.5.3
unlocklock 方法的反操作,解除抽屉的锁定状态,也是 lock(false) 的别名()=>drawerApi>5.5.3

useStore通过useSelector对抽屉内部的 store 做响应式选择订阅(见 use-drawer.ts),可用于在抽屉外部响应式地监听抽屉状态。

lock

lock方法用于锁定抽屉的状态,一般用于提交数据的过程中防止用户重复提交或者抽屉被意外关闭、表单数据被改变等等。当处于锁定状态时,抽屉的确认按钮会变为 loading 状态,同时禁用取消按钮和关闭按钮、禁止 ESC 或者点击遮罩等方式关闭抽屉、开启抽屉的 spinner 动画以遮挡弹窗内容。调用close方法关闭处于锁定状态的抽屉时,会自动解锁。要主动解除这种状态,可以调用unlock方法或者再次调用lock方法并传入false参数。

典型提交场景用法:

async function handleSubmit() { drawerApi.lock(); // 锁定:确认按钮 loading、禁止关闭 try { await submitForm(); drawerApi.close(); // close 会自动解锁 } finally { drawerApi.unlock(); // 兜底解锁 } }

仓库针对该功能的交互测试见 drawer-interaction.test.ts。

小结

VbenDrawer将抽屉的“展示层”与“控制层”解耦:展示层由Drawer组件负责,控制层由drawerApi统一管理,配合connectedComponentprovide/inject机制实现了内外组件零成本连接与类型安全的数据共享。无论是简单的确认抽屉、复杂的表单回填,还是需要防重复提交的提交锁定场景,都可以基于这套 API 快速落地。若默认行为不符预期,优先通过drawerApi.setState或全局的setDefaultDrawerProps进行调整,保持业务代码的简洁。

【免费下载链接】vue-vben-adminA modern vue admin panel built with Vue3, Shadcn UI, Vite, TypeScript, and Monorepo. It's fast!项目地址: https://gitcode.com/GitHub_Trending/vu/vue-vben-admin

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询