- 前端
- AI 技能
【免费下载链接】basic
⭐⭐⭐⭐⭐ 面向 AI 编程的管理系统框架,兼容PC、移动端。AI-oriented management system framework, compatible with PC and mobile device.
本文以 slot-positions.md 为主线,完整梳理 Fantastic-admin 管理系统框架中的 19 个插槽位置、选择准则与目录约定,并结合本仓库(monorepo 多应用结构)的真实源码(如 slots/index.ts、layouts/index.vue)剖析插槽的自动发现与渲染原理,最后给出可直接复制的插槽组件模板。读完本文,你将能够在不改动框架布局组件源码的前提下,向任意区域(顶部横幅、头部、主/子侧边栏、标签栏、工具栏、悬浮层)注入自定义 Vue 组件。
一、插槽机制概述:约定优于配置
Fantastic-admin 提供了一套基于"目录约定"的插槽(Slot)机制:只要把文件放在约定好的目录里,框架就会自动发现并渲染它,无需修改任何布局源码。整套机制的核心约定只有两条:
- 目录名必须与插槽名完全匹配(区分大小写);
- 文件名固定为
index.vue。
这两条约定并非文档空谈,而是由框架底层的自动发现逻辑决定的。以 apps/example/src/slots/index.ts 为例,其核心实现如下:
import { pascalCase } from 'scule' type Slots = 'layout-top' | 'layout-bottom' | 'header-start' | 'header-after-logo' | 'header-after-menu' | 'header-end' | 'main-sidebar-top' | 'main-sidebar-after-logo' | 'main-sidebar-after-menu' | 'main-sidebar-bottom' | 'sub-sidebar-top' | 'sub-sidebar-after-logo' | 'sub-sidebar-after-menu' | 'sub-sidebar-bottom' | 'tabbar-start' | 'tabbar-end' | 'toolbar-start' | 'toolbar-end' | 'free-position' function tryLoadComponent(name: Slots) { const componentMap = import.meta.glob('./*/index.vue', { eager: true }) const path = `./${pascalCase(name as unknown as string)}/index.vue` const component = componentMap[path as keyof typeof componentMap] if (!component) { return { default: defineComponent({ name: 'SlotsInvalidComponent', render: () => null, }), } } return component } export function useSlots(name: Slots) { const component = tryLoadComponent(name) return defineComponent((component as any).default) }这段代码揭示了三层关键信息:
- 自动发现:
import.meta.glob('./*/index.vue', { eager: true })会在构建期扫描slots目录下所有一级子目录中的index.vue,这正是"文件名必须为 index.vue、目录层级必须是一级子目录"的根本原因; - 命名转换:插槽调用时使用 kebab-case(如
'layout-top'),底层通过scule的pascalCase转换为目录名(如LayoutTop)再拼接路径./LayoutTop/index.vue,因此目录名必须使用与插槽名严格对应的大驼峰写法; - 容错降级:如果某个插槽目录不存在,
tryLoadComponent会返回一个名为SlotsInvalidComponent、渲染为null的空组件,所以未创建的插槽不会产生任何报错,页面照常渲染。
在 monorepo 中,每个应用(apps/下的core、example等)都各自拥有一份独立的src/slots/目录与slots/index.ts,插槽按应用隔离、互不干扰。
二、19 个插槽位置总览
框架将全部插槽按所在区域划分为七类,共 19 个位置:
| 区域 | 插槽名称 |
|---|---|
| 布局 | LayoutTop、LayoutBottom |
| 头部 | HeaderStart、HeaderAfterLogo、HeaderAfterMenu、HeaderEnd |
| 主侧边栏 | MainSidebarTop、MainSidebarAfterLogo、MainSidebarAfterMenu、MainSidebarBottom |
| 子侧边栏 | SubSidebarTop、SubSidebarAfterLogo、SubSidebarAfterMenu、SubSidebarBottom |
| 标签栏 | TabbarStart、TabbarEnd |
| 工具栏 | ToolbarStart、ToolbarEnd |
| 自由定位 | FreePosition |
其中HeaderAfterMenu、MainSidebarAfterMenu、SubSidebarAfterMenu三个插槽需要 v5.3.0 及以上版本支持,使用时请核对框架版本。
下面按区域逐一展开每个插槽的位置、适用场景与布局方式。
三、布局插槽(2 个位置)
布局插槽位于应用布局的最外层,横跨全宽,是最"重"的插槽。
LayoutTop
- 位置:整个应用的最顶部,位于头部(Header)之上;
- 布局方式:全宽块级容器;
- 适用场景:
- 全局公告横幅
- 系统维护通知
- Cookie 同意栏
- 试用到期提醒
关键行为:LayoutTop的内容会将整个布局向下撑开,适合需要立即引起注意的横幅。
这一点在源码中有直接印证:layouts/index.vue 中通过useElementSize实时测量插槽容器的实际高度,并写入 CSS 变量:
const layoutTopRef = useTemplateRef('layoutTopRef') const { height: layoutTopHeight } = useElementSize(layoutTopRef)随后在根节点样式里通过--g-slots-layout-top-height: ${layoutTopHeight}px暴露给整个布局,头部(Header)、侧边栏容器(top: calc(var(--g-slots-layout-top-height) + ...))以及主内容区(pt-[calc(var(--g-slots-layout-top-height)+...)])都会随之让位,从而真正实现"向下撑开"。
LayoutBottom
- 位置:整个应用的最底部,位于页脚(Copyright)之下;
- 布局方式:全宽块级容器;
- 适用场景:
- 全局版权声明
- 法律免责声明
- 持久状态栏
与LayoutTop对称,其高度同样被测量并写入--g-slots-layout-bottom-height,主内容区通过pb-[calc(var(--g-slots-layout-bottom-height)+...)]预留底部空间,避免内容被插槽遮挡。
四、头部插槽(4 个位置)
头部插槽位于应用顶部的头部导航栏(Header)中,均为水平弹性布局,渲染位置可从 layouts/components/Header/index.vue 的模板中一一对应:
<div class="header-container"> <Component :is="useSlots('header-start')" /> <!-- 最左侧,logo 之前 --> <Logo class="title" /> <Component :is="useSlots('header-after-logo')" /> <!-- logo 紧后方 --> <!-- ...主菜单(menu-container)... --> <Component :is="useSlots('header-after-menu')" /> <!-- 主菜单之后 --> <!-- ...用户头像按钮... --> <Component :is="useSlots('header-end')" /> <!-- 最右侧 --> </div>HeaderStart
- 位置:头部最左侧,logo 之前;
- 适用场景:菜单折叠按钮、面包屑导航、自定义品牌元素。
HeaderAfterLogo
- 位置:头部 logo 紧后方;
- 适用场景:
- 应用标题或副标题
- 版本徽标
- 环境标识(开发/预发/生产)
HeaderAfterMenu
- 位置:头部主菜单之后;
- 版本要求:v5.3.0+;
- 适用场景:搜索框、快捷操作、通知提示。
HeaderEnd
- 位置:头部最右侧;
- 适用场景:
- 用户头像下拉菜单
- 设置按钮
- 退出登录按钮
- 主题切换器
五、主侧边栏插槽(4 个位置)
主侧边栏插槽位于主导航侧边栏中,均为垂直弹性布局。渲染位置可从 layouts/components/MainSidebar/index.vue 中对应:
<div class="main-sidebar-container"> <Component :is="useSlots('main-sidebar-top')" /> <Logo :show-title="false" class="sidebar-logo" /> <Component :is="useSlots('main-sidebar-after-logo')" /> <!-- ...主菜单(FaScrollArea)... --> <Component :is="useSlots('main-sidebar-after-menu')" /> <!-- ...用户头像按钮... --> <Component :is="useSlots('main-sidebar-bottom')" /> </div>MainSidebarTop
- 位置:主侧边栏顶部,logo 之前;
- 适用场景:折叠/展开按钮、自定义头部内容、工作区选择器。
MainSidebarAfterLogo
- 位置:主侧边栏 logo 紧后方;
- 适用场景:
- 用户信息卡片
- 快速统计数据
- 工作区名称
MainSidebarAfterMenu
- 位置:主侧边栏导航菜单之后;
- 版本要求:v5.3.0+;
- 适用场景:附加导航项、快捷方式、固定项目。
仓库中 apps/example/src/slots/MainSidebarAfterMenu/index.vue 就是一个真实范例——在主菜单下方注入一个"升级到专业版"的 Popover 入口:
<script setup lang="ts"> function upgrade() { window.open('https://fantastic-admin.hurui.me/buy.html', '_blank') } </script> <template> <div class="flex-center"> <FaPopover align="end" side="right" class="p-0 min-w-auto"> <FaButton size="icon" variant="ghost" class="size-12"> <FaIcon name="i-noto:crown" class="text-8 filter-grayscale" /> </FaButton> <template #panel> <FaCard title="升级到专业版" description="解锁全部功能,享受极致体验" class="border-none w-60"> <FaButton size="sm" class="w-full" @click="upgrade"> 升级 </FaButton> </FaCard> </template> </FaPopover> </div> </template>MainSidebarBottom
- 位置:主侧边栏底部;
- 适用场景:
- 帮助/支持链接
- 版本信息
- 底部内容
- 折叠按钮
六、子侧边栏插槽(4 个位置)
子侧边栏插槽位于次级导航侧边栏中(使用多级导航时显示),均为垂直弹性布局。渲染位置可从 layouts/components/SubSidebar/index.vue 中对应:
<div class="sub-sidebar-container"> <Component :is="useSlots('sub-sidebar-top')" /> <!-- ...logo(部分菜单模式下)... --> <Component :is="useSlots('sub-sidebar-after-logo')" /> <!-- ...次级菜单(FaScrollArea)... --> <!-- ...折叠按钮(subMenuCollapseButton)... --> <Component :is="useSlots('sub-sidebar-after-menu')" /> <!-- ...single 模式下的账号按钮... --> <Component :is="useSlots('sub-sidebar-bottom')" /> </div>SubSidebarTop
- 位置:子侧边栏顶部;
- 适用场景:区块标题、返回按钮、面包屑。
SubSidebarAfterLogo
- 位置:子侧边栏 logo 之后;
- 适用场景:区块描述、上下文信息。
SubSidebarAfterMenu
- 位置:子侧边栏导航菜单之后;
- 版本要求:v5.3.0+;
- 适用场景:附加子导航、相关链接。
SubSidebarBottom
- 位置:子侧边栏底部;
- 适用场景:区块专属操作、底部内容。
注意:子侧边栏存在折叠态(
is-collapse),宽度会由--g-sub-sidebar-width收缩为--g-sub-sidebar-collapse-width,在其中放置文字类内容时需考虑折叠场景下的展示问题。
七、顶部栏插槽(4 个位置)
顶部栏插槽位于标签栏(Tabbar)和工具栏(Toolbar)区域,均为水平弹性布局。标签栏的渲染位置见 layouts/components/Topbar/Tabbar/index.vue:
<div class="tabbar"> <Component :is="useSlots('tabbar-start')" /> <div class="tabbar-container"><!-- 标签列表 --></div> <Component :is="useSlots('tabbar-end')" /> </div>工具栏左侧的渲染位置见 layouts/components/Topbar/Toolbar/startSide.vue:
<div class="flex items-center"> <FaButton v-if="appSettingsStore.mode === 'mobile'" ... /> <Component :is="useSlots('toolbar-start')" /> <Tools mode="left-side" /> </div>工具栏右侧同样位于endSide.vue中,与左侧对称地渲染toolbar-end(从目录结构与 startSide 的实现可以推断其模板形态)。
TabbarStart
- 位置:标签栏左侧;
- 适用场景:标签导航控件、刷新按钮、自定义标签操作。
TabbarEnd
- 位置:标签栏右侧;
- 适用场景:关闭所有标签按钮、标签管理操作。
ToolbarStart
- 位置:工具栏左侧;
- 适用场景:页面专属操作、面包屑、页面标题。
ToolbarEnd
- 位置:工具栏右侧;
- 适用场景:操作按钮、筛选器、导出/导入按钮。
八、FreePosition 自由定位插槽
FreePosition是唯一的自由定位插槽,其渲染位置在 layouts/index.vue 的末尾(<Component :is="useSlots('free-position')" />),默认情况下它不占据任何布局空间,因此需要自行控制定位。
- 位置:灵活定位,需手动设置坐标;
- 特殊要求:
- 必须在样式中使用
position: absolute; - 必须手动设置定位坐标(top/right/bottom/left)
- 必须设置合适的 z-index
- 必须在样式中使用
- 适用场景:
- 悬浮操作按钮(FAB)
- 客服聊天组件
- 自定义遮罩层
- 通知 Toast
- 帮助按钮
样式示例(来自原文档):
.free-position-slot { position: absolute; bottom: 20px; right: 20px; z-index: 1000; }重要提醒:
FreePosition插槽必须在样式中手动设置定位,否则内容将不可见(无法在正常文档流中渲染出来)。
九、插槽选择指南
何时使用布局插槽
- 需要显示在所有内容之上的全局横幅(公告、维护通知)→
LayoutTop; - 需要显示在所有内容之下的全局底栏(版权声明、法律免责)→
LayoutBottom。
LayoutTop/LayoutBottom位于整个布局的最外层,内容会横跨全宽,适合需要独占一行的场景。
何时使用头部插槽
- 全局导航元素 →
HeaderStart/HeaderEnd; - 用户账号控件;
- 全局操作按钮;
- 品牌元素 →
HeaderAfterLogo。
何时使用侧边栏插槽
- 导航增强内容 →
MainSidebarAfterMenu/SubSidebarAfterMenu; - 用户信息展示 →
MainSidebarAfterLogo; - 工作区上下文;
- 帮助与支持链接 →
MainSidebarBottom。
何时使用顶部栏插槽
- 页面专属操作 →
ToolbarStart/ToolbarEnd; - 标签管理 →
TabbarStart/TabbarEnd; - 上下文控件;
- 面包屑导航。
何时使用 FreePosition
- 不适合放入标准布局的悬浮元素;
- 需要覆盖在内容之上的元素;
- 客服聊天组件或帮助按钮;
- 需要自定义定位的组件。
十、文件结构约定与组件模板
目录约定
所有插槽必须遵循以下目录结构:
/src/slots/{插槽名称}/index.vue注意:文件名必须为index.vue。以本仓库example应用为例(见 apps/example/src/slots):
apps/example/src/slots/LayoutTop/index.vue apps/example/src/slots/MainSidebarAfterMenu/index.vue apps/example/src/slots/index.ts # 自动发现与加载逻辑在 monorepo 中,目标应用的插槽根目录为apps/<app>/src/slots/。
普通插槽模板
<script setup lang="ts"> // 在此添加插槽逻辑 </script> <template> <div> <!-- 在此添加插槽内容 --> </div> </template> <style scoped> /* 在此添加插槽样式 */ </style>FreePosition 插槽模板
<script setup lang="ts"> // 在此添加插槽逻辑 </script> <template> <div class="free-position-slot"> <!-- 在此添加插槽内容 --> <!-- 注意:此插槽需要绝对定位 --> </div> </template> <style scoped> .free-position-slot { position: absolute; /* 在此设置定位坐标,例如: */ /* bottom: 20px; */ /* right: 20px; */ /* z-index: 1000; */ } </style>十一、仓库真实范例:LayoutTop 促销横幅
本仓库 apps/example/src/slots/LayoutTop/index.vue 是一个完整可参考的实战案例——它演示了布局插槽最常见的两种能力:按时间条件控制显示与一键关闭。
<script setup lang="ts"> import isBetween from 'dayjs/plugin/isBetween' import dayjs from '@/utils/dayjs' const isShow = ref(false) onMounted(() => { dayjs.extend(isBetween) if (dayjs().isBetween('2026-10-01', '2026-10-17')) { isShow.value = true } }) function handleOpen() { window.open('https://fantastic-admin.hurui.me/buy-anniversary.html', '_blank') } </script> <template> <div v-if="isShow" class="text-sm text-gray-100 font-medium flex-center gap-3 h-12 relative from-slate-800 to-gray-900 bg-gradient-to-r"> <span class="text-lg font-bold">✨ 六周年庆,全年最低价 ✨</span> <button class="text-xs text-white font-semibold px-4 py-1 rounded-full bg-blue-600 transition-colors duration-200 hover:bg-blue-500" @click="handleOpen"> 查看详情 → </button> <button class="text-gray-400 p-1 bg-transparent flex-center transition-colors duration-200 right-3 top-1/2 absolute hover:text-gray-200 -translate-y-1/2" @click="isShow = false"> <FaIcon name="i-ep:close" /> </button> </div> </template>该范例可以提炼出三条可复用的实践要点:
- 插槽内逻辑完全自治:显示/隐藏、数据判断、事件处理都在插槽组件内部完成,框架只负责渲染位置;
v-if控制空态:当isShow为 false 时整个插槽不渲染任何内容(配合布局层容器的empty:hidden特性,不占据高度);- 布局插槽的"撑开"效果:该横幅一旦显示,框架会按测量到的高度自动下推头部与主内容区(原理见第三节),无需手动调整布局。
十二、故障排除
插槽未显示?请按顺序检查:
- 目录名是否与插槽名完全匹配(区分大小写):例如插槽
LayoutTop的目录必须是LayoutTop,写成layouttop或LayoutTop/以外的名称都无法命中import.meta.glob与pascalCase拼接的路径; - 文件名是否为
index.vue:只有该文件名会被自动发现机制加载; apps/<app>/src/slots/目录是否存在:确保在正确的应用目录下创建(monorepo 中每个应用拥有独立的 slots 目录);- FreePosition 是否设置了绝对定位:该插槽未设置
position: absolute与坐标时内容不可见; - 版本是否满足要求:
HeaderAfterMenu、MainSidebarAfterMenu、SubSidebarAfterMenu需 v5.3.0+。
另外,由于底层对未创建插槽会回退到渲染null的空组件,"没报错"不等于"插槽生效",排查时应优先核对命名约定,而不是依赖报错信息。
十三、小结
Fantastic-admin 的插槽体系用一套极简的目录约定({PascalCase插槽名}/index.vue)覆盖了布局、头部、主/子侧边栏、标签栏、工具栏与自由定位共 19 个注入点。其底层由 slots/index.ts 中的import.meta.glob自动发现机制驱动,配合布局层对插槽高度的动态测量(layouts/index.vue),实现了"插入即生效、删除即消失、高度自动让位"的零侵入扩展能力。无论是全局公告横幅、环境标识、侧边栏用户卡片,还是悬浮按钮与客服浮窗,都能在不修改框架布局源码的前提下,通过一个插槽组件快速落地。
- 前端
- AI 技能
【免费下载链接】basic
⭐⭐⭐⭐⭐ 面向 AI 编程的管理系统框架,兼容PC、移动端。AI-oriented management system framework, compatible with PC and mobile device.
相关推荐
Element UI表格插槽:Table自定义内容技巧
Element UI表格插槽:Table自定义内容技巧 你是否还在为Element UI表格组件的内容自定义而烦恼?想在表格中嵌入按钮、图片或复杂组件却不知从何
前端UI组件设计系统vant-weapp Empty 空状态组件完全指南:占位提示、内置图片类型与自定义插槽实战
vant weapp Empty 空状态组件完全指南:占位提示、内置图片类型与自定义插槽实战 导读 本文将基于 vant weapp 组件库中的 Empty(空
前端小程序UI组件移动开发vxe-table自定义插槽应用:高级表格内容定制
vxe table自定义插槽应用:高级表格内容定制 你是否还在为表格内容展示单调、无法满足复杂业务需求而困扰?是否遇到过需要在表格中嵌入按钮、图片、进度条等元素
前端UI组件
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考