外观
组件与 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-ui | Schema 驱动的表单引擎 | 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]。两者共享同一套生命周期钩子与状态管理。
Modal 用法
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拆分外壳与内容,提升可读性。