Skip to content

组件与 UI-Kit ​

概述 ​

前端基于 Vben Admin 框架(Vue 3 + Vite + TypeScript)构建,组件能力被拆分为「原子组件库」「表单/弹窗等组合式封装」「业务组件」三层。理解这套分层,是进行页面开发的基础。

UI-Kit 分层概览 ​

frontend/packages/@core/ui-kit 是核心 UI 能力的承载包,按职责拆成多个子包;业务相关的通用组件则放在 frontend/packages/effects/common-ui。

包职责关键导出
@core/ui-kit/shadcn-ui原子级组件,基于 reka-ui 封装(按钮、图标、下拉、表格操作、描述列表等)VbenButton、VbenIcon、VbenDropdownMenu、VbenTooltip、VbenTableAction 等
@core/ui-kit/form-uiSchema 驱动的表单引擎useVbenForm、VbenForm、setupVbenForm
@core/ui-kit/popup-ui弹窗与抽屉封装useVbenModal、useVbenDrawer
@core/ui-kit/layout-ui后台整体布局骨架VbenAdminLayout
@core/ui-kit/menu-ui侧边/顶部菜单Menu、MenuBadge
@core/ui-kit/tabs-ui多页签视图TabsView
effects/common-ui业务通用组件(页面容器、验证码、图片裁剪等)Page、DocAlert、IconPicker、Captcha 等

说明:shadcn-ui 是 Vben 自带的轻量组件,element-plus 则通过 adapter/component 适配后,作为表单字段组件使用(见下文「组件适配与扩展」)。

shadcn-ui 原子组件 ​

shadcn-ui 包含 components(Vben 定制封装)与 ui(更贴近 shadcn 原始风格)两部分。日常开发最常用的是:

  • VbenButton:统一按钮样式与 loading 态。
  • VbenIcon / VbenIconButton:图标渲染,配合 @vben/icons 使用。
  • VbenDropdownMenu、VbenHoverCard、VbenTooltip、VbenPopover:浮层类组件。
  • VbenTableAction:表格「操作列」的按钮组,自动处理多个操作的分隔与折叠。
  • VbenDescriptions / VbenDescriptionsItem:详情描述列表。
  • VbenSegmented:分段选择器,常用于切换视图。
  • VbenScrollbar:自定义滚动条;VbenBackTop:回到顶部。
  • VbenCountToAnimator、VbenSpineText:轻量动效组件。

这些组件直接按需引入即可:

ts
import { VbenButton, VbenIcon, VbenTableAction } from '@vben/common-ui';

表单引擎 form-ui ​

表单是整个后台使用频率最高的组件,采用 Schema 声明式 写法:用一份 schema 数组描述字段、组件、校验规则与依赖关系,而非手写大量 <el-form-item>。

基本用法 ​

表单逻辑通常拆成 data.ts(定义 schema)与 form.vue(渲染 + 提交)。在页面中通过 useVbenForm 拿到组件与实例:

ts
import { useVbenForm } from '#/adapter/form';

const [Form, formApi] = useVbenForm({
  // 所有表单项通用配置
  commonConfig: {
    componentProps: { class: 'w-full' },
    labelWidth: 80,
  },
  layout: 'horizontal',
  schema: useFormSchema(),
  showDefaultActions: false, // 关闭默认的提交/重置按钮,交给 Modal 的确认按钮
});

useVbenForm 返回二元组 [Form, formApi]:Form 是渲染用的组件,formApi 是命令式操作表单的实例。

Schema 写法 ​

ts
export function useFormSchema(): VbenFormSchema[] {
  return [
    {
      component: 'Input',
      fieldName: 'name',
      label: '岗位名称',
      componentProps: { placeholder: '请输入岗位名称' },
      rules: 'required',
    },
    {
      component: 'RadioGroup',
      fieldName: 'status',
      label: '岗位状态',
      componentProps: { options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number') },
    },
  ];
}
  • component:字段使用的组件名(对应 adapter/component 中注册的组件,如 Input、Select、RadioGroup、ApiSelect)。
  • fieldName:字段名,也是提交数据的 key。
  • componentProps:透传给底层组件的参数。
  • rules:支持快捷字符串(required、selectRequired、mobile 等,在 adapter/form 中定义)或 Zod 规则(z.string().min(1))。
  • dependencies:字段联动,例如根据某字段值动态显示/隐藏/禁用当前字段、改写 componentProps。

常用 API ​

通过 formApi 在逻辑中操作表单:

方法说明
getValues()获取经过 valueFormat / 时间字段映射处理后的表单值
setValues(values)回填表单数据(编辑场景常用)
validate()触发表单校验,返回 { valid, errors }
resetForm()重置表单
setFieldValue(name, value)设置单个字段值
updateSchema(schema)动态更新部分字段的配置
removeSchemaByFields(fields)移除指定字段
setState(state)设置表单级状态(如 disabled、loading)
ts
const { valid } = await formApi.validate();
if (!valid) return;
const data = await formApi.getValues();

弹窗与抽屉 popup-ui ​

popup-ui 提供 useVbenModal 与 useVbenDrawer,用法与表单一致:返回 [组件, api]。两者共享同一套生命周期钩子与状态管理。

ts
const [Modal, modalApi] = useVbenModal({
  async onConfirm() {
    const { valid } = await formApi.validate();
    if (!valid) return;
    modalApi.lock();          // 锁定弹窗(禁用关闭、按钮 loading)
    try {
      await save(await formApi.getValues());
      await modalApi.close(); // 关闭
    } finally {
      modalApi.unlock();
    }
  },
  async onOpenChange(isOpen) {
    if (!isOpen) return;
    const data = modalApi.getData(); // 接收外部传入的数据
    await formApi.setValues(data ?? {});
  },
});
html
<template>
  <Modal class="w-[600px]" :title="getTitle">
    <Form />
  </Modal>
</template>

生命周期与状态 ​

useVbenModal / useVbenDrawer 通过选项回调驱动:

  • onOpenChange(isOpen):打开/关闭时触发,用于加载或清理数据。
  • onConfirm:点击确认按钮;onCancel:点击取消。
  • onOpened / onClosed:动画结束后的回调。
  • lock() / unlock():提交期间锁定弹窗,防止重复操作。

api 实例方法:open()、close()、setState(partialState)、getData()、setData(payload)。常用状态包括 title、width(通过 class 设置,如 w-[600px])、destroyOnClose、draggable、fullscreen、confirmLoading 等。

嵌入外部组件(connectedComponent) ​

当弹窗内容较复杂、希望把 Modal 与内容组件拆分维护时,可使用 connectedComponent:

ts
const [UserSelectModal, modalApi] = useVbenModal({
  connectedComponent: defineAsyncComponent(() => import('./select-modal.vue')),
});

内容组件内部再次调用 useVbenModal(不带 connectedComponent),即可通过 inject 与外层 api 共享状态,实现「弹窗外壳」与「弹窗内容」解耦。

布局与导航组件 ​

  • layout-ui 提供 VbenAdminLayout,是管理后台整体的外壳(含侧边栏、顶栏、内容区、页脚)。
  • menu-ui 提供 Menu 组件,菜单数据由后端下发,经「路由与菜单」章节描述的 generateAccessible 流程生成。
  • tabs-ui 提供 TabsView,渲染多页签,配合路由缓存使用。

这些组件已在框架初始化时装配好,页面开发通常无需直接接触,了解其存在即可。

业务组件 common-ui ​

effects/common-ui 沉淀了与具体业务耦合度更高的通用组件,避免在每个页面重复造轮子。常用包括:

  • Page、ContentWrap:页面与内容容器,统一页面内边距与标题区。
  • DocAlert:页面顶部文档提示条,引导查看对应后端文档。
  • IconPicker:图标选择器(表单内 IconPicker 组件即来自此处)。
  • Captcha:滑块/点选验证码,用于登录与敏感操作。
  • Cropper:图片裁剪。
  • EllipsisText:文本省略(超出折叠)。
  • JsonViewer:JSON 预览。
  • ApiComponent 及 ApiSelect / ApiTreeSelect / ApiCascader:支持远程拉取选项的组件(见下文)。

业务组件按需在页面中导入,例如:

ts
import { DocAlert, IconPicker } from '@vben/common-ui';

组件适配与扩展 ​

为了让 Schema 表单能直接使用 element-plus 的组件,项目在 apps/web-ele/src/adapter 中做了适配层。

表单组件映射 ​

adapter/component 定义了 ComponentType(表单可用组件清单)并注册到全局共享状态,例如 Input、Select、RadioGroup、DatePicker、RangePicker、TreeSelect、Upload、FileUpload、ImageUpload、RichTextarea,以及远程加载的 ApiSelect / ApiTreeSelect / ApiCascader。adapter/form 在此基础上封装了 setupVbenForm(注册快捷校验规则 required / mobile 等,并注入 i18n 文案)和项目专属的 useVbenForm。

因此页面里看到的 component: 'Input'、component: 'ApiSelect',都映射到 adapter/component 中注册的底层组件。

远程数据组件 ​

ApiSelect 等组件适合「选项来自后端接口」的场景,避免手动写 onMounted 拉数据:

ts
{
  component: 'ApiSelect',
  fieldName: 'userId',
  label: '用户',
  componentProps: {
    api: getUserSimpleList,        // 返回选项数组的接口
    labelField: 'name',
    valueField: 'id',
    immediate: true,              // 打开即加载
  },
}

典型组合范式 ​

一个标准的「列表 + 新增/编辑弹窗」页面,通常由三件套组成:

  • 列表页负责表格与「新增/编辑」按钮,点击时 modalApi.setData(row).open() 打开弹窗并传入数据。
  • 弹窗组件内用 onOpenChange 读取数据并 formApi.setValues 回填;onConfirm 中校验后提交并关闭。
  • 详细链路(表格、CRUD 完整范式)见「页面开发范式」章节。

最佳实践 ​

  • 表单 schema 统一抽离到 data.ts 的 useFormSchema() / useGridFormSchema(),保持 form.vue 只关注渲染与提交。
  • 弹窗内提交优先使用 modalApi.lock() / unlock() 包裹请求,避免重复提交与提前关闭。
  • 选项来自后端的下拉,优先使用 ApiSelect / ApiTreeSelect,而非手写请求。
  • 表单字段的显示/隐藏、禁用等联动用 dependencies 声明式表达,比命令式 updateSchema 更易维护。
  • 复杂弹窗用 connectedComponent 拆分外壳与内容,提升可读性。