拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Fantastic-admin 路由生成器实战:从路由文件到导航菜单、权限与保活的完整配置指南

Fantastic-admin 路由生成器实战:从路由文件到导航菜单、权限与保活的完整配置指南
  • 前端
  • AI 技能

【免费下载链接】basic

⭐⭐⭐⭐⭐ 面向 AI 编程的管理系统框架,兼容PC、移动端。AI-oriented management system framework, compatible with PC and mobile device.

项目地址:https://gitcode.com/GitHub_Trending/ba/basic
点击查看免费下载

本篇技术指南以 Fantastic-admin 框架的路由生成器技能(skills/fa-route-generator/SKILL.md)为骨架,系统讲解如何在该框架中创建、修改路由配置,并通过meta元信息驱动导航菜单、权限控制、页面保活与面包屑展示。读完本文,你将掌握路由文件的标准写法、RouteMetaRaw全部可用属性、列表-详情页保活等高频实战方案,以及这些配置在框架源码中的真实生效链路。

一、路由生成器是什么:一条路由配置驱动整站导航

Fantastic-admin 是一个面向 AI 编程的管理系统框架,兼容 PC 与移动端,仓库采用 monorepo 架构,apps/目录下存放各应用(如core、example、core-ant-design-vue等)。框架的核心设计之一是导航菜单由路由配置自动生成——你不需要单独维护菜单数据,只要按照约定编写路由文件并配置meta属性,侧边栏菜单、标签页、面包屑、权限过滤与页面保活都会随之联动。

这意味着路由配置是整个应用导航体系的"单一数据源",而路由生成器(fa-route-generator)就是围绕这一机制提供的操作规范:它规定了创建新路由、修改现有路由的完整流程,并明确了meta中每个属性的含义与取值。

二、前置准备:确认目标应用与路由驱动模式

第一步:确认工作区(必须等待用户明确回复)

由于是 monorepo 架构,在执行任何文件读写之前,必须先确认要在哪个应用中操作路由。操作规范要求:

  1. 执行ls apps/列出所有可用应用;
  2. 向用户提问,明确询问在哪个应用中操作路由,并停止等待回复;
  3. 收到明确回复后才能继续。

严格规则:如果用户没有在请求中明确说明目标应用(例如"在 example 应用中"、"apps/core"),则必须提问,不得自行猜测或默认选择任何应用。

确认后,后续所有文件路径均以该应用目录为根,例如apps/<app>/src/router/。

第二步:检查路由驱动模式(routeBaseOn)

读取apps/<app>/src/settings.ts,检查app.routeBaseOn的值:

  • 'frontend'(默认):可以继续,手动创建的路由文件生效;
  • 'backend':路由由后端驱动,手动创建的路由文件会被忽略,此时应告知用户该模式不支持前端路由文件生成。

以apps/core为例,其 settings.ts 内容为空(未显式配置),因此采用默认值。从源码看,默认值定义于 packages/settings/src/default.ts:

routeMode: 'hash', routeBaseOn: 'frontend',

而 packages/settings/src/types.ts 中定义的完整取值是'frontend' | 'backend' | 'filesystem',其中历史遗留的'filesystem'会在配置解析时被统一映射为'frontend'(见 packages/settings/src/utils.ts)。

该模式在运行时如何生效?查看路由守卫 apps/core/src/router/guards.ts 可知:

switch (appSettingsStore.settings.app.routeBaseOn) { case 'frontend': appRouteStore.generateRoutesAtFront(asyncRoutes) break case 'backend': await appRouteStore.generateRoutesAtBack() break }

前端模式直接使用routes.ts中导出的asyncRoutes生成并注册动态路由;后端模式则调用 API 获取路由数据后再格式化注册(见 apps/core/src/store/modules/app/route.ts)。

三、框架约定:路由文件必须遵守的规则

  • 一级路由path必须以/开头,component必须是Layout(即@/layouts/index.vue);
  • 子路由path不要以/开头;
  • 多级路由的中间层级无需设置component;
  • 所有路由的name必须全局唯一。

其中"多级路由中间层级无需设置 component"不仅是约定,更是框架的强制处理逻辑。在 apps/core/src/store/modules/app/route.ts 中,deleteMiddleRouteComponent会递归删除所有含children的中间层级路由的component:

function deleteMiddleRouteComponent(routes: RouteRecordRaw[]) { const res: RouteRecordRaw[] = [] routes.forEach((route) => { if (route.children?.length) { delete route.component route.children = deleteMiddleRouteComponent(route.children) } else { delete route.children } res.push(route) }) return res }

这一处理在generateRoutesAtFront与systemRoutes计算属性中都会被调用,因此即使你在路由文件中给中间层级写了component,最终注册时也会被清理。

四、场景 A:创建新路由

1. 收集路由信息

向用户询问或确认:路由路径、页面标题、图标(可选)、是否多级路由、所属主导航分组。

2. 创建路由文件

在apps/<app>/src/router/modules/下创建<模块名>.ts,例如example.ts:

import type { RouteRecordRaw } from 'vue-router' function Layout() { return import('@/layouts/index.vue') } const routes: RouteRecordRaw = { path: '/example', component: Layout, name: 'example', meta: { title: '示例', icon: 'i-ep:menu', }, children: [ { path: '', name: 'exampleIndex', component: () => import('@/views/example/index.vue'), meta: { title: '示例页面', }, }, ], } export default routes

仓库中真实的路由模块可参考 apps/core/src/router/modules/multilevel.menu.example.ts,它演示了三级导航的标准写法:顶层component: Layout,中间层级(level2)只写meta与children不写component,叶子节点才声明页面组件。

3. 更新 routes.ts 并注册到主导航分组

在apps/<app>/src/router/routes.ts中添加 import,并把路由模块注册到对应主导航分组的children数组中。以apps/core的 routes.ts 为例:

import type { RouteRecordMainRaw } from '@fantastic-admin/types' import type { RouteRecordRaw } from 'vue-router' import pinia from '@/store' import MultilevelMenuExample from './modules/multilevel.menu.example' // 动态路由(异步路由、导航菜单路由) const asyncRoutes: RouteRecordMainRaw[] = [ { meta: { title: '演示', icon: 'i-ri:function-ai-line', }, children: [ MultilevelMenuExample, ], }, ] export { asyncRoutes, constantRoutes, systemRoutes, }

可以看到主导航分组的类型是RouteRecordMainRaw,它本身没有path与component,只承载meta(auth、title、icon、sort)与children(packages/types/types.ts),真正的路由路径由 children 中的路由模块提供。constantRoutes(固定路由)与systemRoutes(系统路由)与此并列导出,最终constantRoutes在 apps/core/src/router/index.ts 中交给createRouter,asyncRoutes由守卫动态注册。

五、场景 B:修改现有路由

定位路由文件(在apps/<app>/src/router/modules/下搜索),读取后按需修改meta属性。

权限控制

meta: { auth: 'user:view' } // 或数组 ['user:view', 'user:edit']

auth为字符串时表示需要该权限;为数组时表示满足其中一个即可。菜单生成时会据此过滤无权限的导航项(见下文"权限过滤")。

页面保活(从详情返回列表时保留列表状态)

// 列表页 meta: { keepAlive: ['productDetail'] } // 详情页 meta: { menu: false, activeMenu: '/product', noKeepAlive: 'productList' }
  • 列表页声明keepAlive: ['productDetail']:只有从名为productDetail的页面返回时才保留列表状态;
  • 详情页声明menu: false不出现在导航中,activeMenu: '/product'让详情页仍高亮"产品管理"菜单,noKeepAlive: 'productList'表示从列表进入详情时不保活(保证详情始终加载最新数据)。

隐藏菜单项

meta: { menu: false, activeMenu: '/parent/path' }

某些页面(如详情页、个人设置)不需要出现在导航菜单中,可用menu: false隐藏,同时用activeMenu指定进入该页面时应该高亮的菜单地址。

六、RouteMeta 属性全解析

框架在 packages/types/types.ts 中定义了RouteMetaRaw接口,并通过 packages/types/global.d.ts 将其合并进 vue-router 的RouteMeta,因此所有路由meta都能获得完整的类型提示。详细说明见 skills/fa-route-generator/references/route-meta.md。

权限相关

auth

  • 类型:string | string[]
  • 默认值:undefined
  • 说明:路由访问权限,配置为数组时,只需满足一个即可进入
  • 示例:
auth: 'news:view' // 需要具备 news:view 权限 auth: ['news:view', 'news:edit'] // 需要具备其中一个权限

导航显示

title(必需)

  • 类型:string | (() => string)
  • 默认值:undefined
  • 说明:标题会在导航、标签页、面包屑等需要的展示位置显示
  • 示例:
title: '新闻管理' title: () => '动态标题' // 支持函数式动态标题

icon

  • 类型:string
  • 默认值:undefined
  • 说明:导航菜单、标签页等展示区域使用的图标(Iconify 图标格式)
  • 示例:
icon: 'i-ep:lock' // 默认显示 i-ep:lock 图标

menu

  • 类型:boolean
  • 默认值:true
  • 说明:是否在导航中显示;当子导航里没有可展示的导航时,会直接显示父导航

activeMenu

  • 类型:string
  • 默认值:undefined
  • 说明:高亮导航,需要设置完整路由地址
  • 示例:
activeMenu: '/news/list'

expand

  • 类型:boolean
  • 默认值:undefined(实际表现为不默认展开)
  • 说明:是否默认展开
  • 示例:
expand: true // 默认展开

breadcrumb

  • 类型:boolean
  • 默认值:true
  • 说明:是否在面包屑中显示

页面行为

keepAlive

  • 类型:boolean | string | string[]
  • 默认值:undefined
  • 说明:保活,根据规则保活当前路由页面
  • 示例:
keepAlive: true // 始终保活 keepAlive: 'news' // 访问路由name为news的页面时保活 keepAlive: ['news', 'user'] // 访问路由name为news或user的页面时保活

noKeepAlive

  • 类型:string | string[]
  • 默认值:undefined
  • 说明:不保活,根据规则不保活当前路由页面
  • 示例:
noKeepAlive: 'news' // 访问路由name为news的页面时不保活 noKeepAlive: ['news', 'user'] // 访问路由name为news或user的页面时不保活

link

  • 类型:string
  • 默认值:undefined
  • 说明:外部链接,会在浏览器新窗口访问该链接
  • 示例:
link: 'https://example.com' // 在浏览器新窗口打开该外部地址

其他(源码补充)

RouteMetaRaw中还包含两个未在技能文档中展开的属性:

  • sort:导航排序,数字越大越靠前,默认值0。它由 apps/core/src/store/modules/app/route.ts 的sortAsyncRoutes递归应用到每层路由;
  • copyright:是否显示版权,不设置时使用全局配置。

技能文档开篇强调的"常用属性"即:title(必需)、icon、menu、auth、keepAlive、activeMenu、breadcrumb,其余属性按需使用即可。

七、高频场景配置示例

以下示例均整理自 skills/fa-route-generator/references/examples.md,可直接复制到apps/<app>/src/router/modules/下按需修改。

单页面路由

最简单的单页面路由配置:

import type { RouteRecordRaw } from 'vue-router' function Layout() { return import('@/layouts/index.vue') } const routes: RouteRecordRaw = { path: '/dashboard', component: Layout, name: 'dashboard', meta: { title: '仪表盘', icon: 'i-ep:data-line', }, children: [ { path: '', name: 'dashboardIndex', component: () => import('@/views/dashboard/index.vue'), meta: { title: '数据概览', }, }, ], } export default routes

注意子路由path: ''表示该页面直接渲染在父级路径/dashboard上。

列表-详情路由

最常见的列表与详情页组合:

const routes: RouteRecordRaw = { path: '/article', component: Layout, name: 'article', meta: { title: '文章管理', icon: 'i-ep:document', }, children: [ { path: '', name: 'articleList', component: () => import('@/views/article/list.vue'), meta: { title: '文章列表', keepAlive: 'articleDetail', // 从详情返回时保持列表状态 }, }, { path: 'detail/:id?', name: 'articleDetail', component: () => import('@/views/article/detail.vue'), meta: { title: '文章详情', menu: false, // 不在导航中显示 activeMenu: '/article', // 高亮文章管理菜单 keepAlive: true, noKeepAlive: 'articleList', // 从列表进入时不保活 }, }, ], }

其中path: 'detail/:id?'中?表示id参数可选,详情页无需重复注册为独立菜单。

二级菜单

const routes: RouteRecordRaw = { path: '/system', component: Layout, name: 'system', meta: { title: '系统管理', icon: 'i-ep:setting', }, children: [ { path: 'user', name: 'systemUser', component: () => import('@/views/system/user/index.vue'), meta: { title: '用户管理', icon: 'i-ep:user', }, }, { path: 'role', name: 'systemRole', component: () => import('@/views/system/role/index.vue'), meta: { title: '角色管理', icon: 'i-ep:avatar', }, }, { path: 'permission', name: 'systemPermission', component: () => import('@/views/system/permission/index.vue'), meta: { title: '权限管理', icon: 'i-ep:lock', }, }, ], }

三级菜单

const routes: RouteRecordRaw = { path: '/content', component: Layout, name: 'content', meta: { title: '内容管理', icon: 'i-ep:folder', }, children: [ { path: 'article', name: 'contentArticle', meta: { title: '文章管理', icon: 'i-ep:document', }, // 注意: 多级路由的中间层级不需要设置 component children: [ { path: 'list', name: 'contentArticleList', component: () => import('@/views/content/article/list.vue'), meta: { title: '文章列表', }, }, { path: 'category', name: 'contentArticleCategory', component: () => import('@/views/content/article/category.vue'), meta: { title: '文章分类', }, }, ], }, { path: 'media', name: 'contentMedia', meta: { title: '媒体管理', icon: 'i-ep:picture', }, children: [ { path: 'image', name: 'contentMediaImage', component: () => import('@/views/content/media/image.vue'), meta: { title: '图片管理', }, }, { path: 'video', name: 'contentMediaVideo', component: () => import('@/views/content/media/video.vue'), meta: { title: '视频管理', }, }, ], }, ], }

带权限的路由

const routes: RouteRecordRaw = { path: '/admin', component: Layout, name: 'admin', meta: { title: '管理员', icon: 'i-ep:user-filled', auth: 'admin', // 需要 admin 权限 }, children: [ { path: 'users', name: 'adminUsers', component: () => import('@/views/admin/users.vue'), meta: { title: '用户列表', auth: ['admin:view', 'admin:edit'], // 需要其中一个权限 }, }, ], }

外部链接

const routes: RouteRecordRaw = { path: '/external', component: Layout, name: 'external', meta: { title: '外部链接', icon: 'i-ep:link', }, children: [ { path: 'github', name: 'externalGithub', component: () => import('@/views/external/link.vue'), meta: { title: 'GitHub', link: 'https://example.com', // 在新窗口打开外部地址 }, }, ], }

默认展开的路由

const routes: RouteRecordRaw = { path: '/menu', component: Layout, name: 'menu', meta: { title: '菜单示例', icon: 'i-ep:menu', expand: true, // 默认展开 }, children: [ { path: 'always', name: 'menuAlways', meta: { title: '默认展开', expand: true, }, children: [ { path: 'item1', name: 'menuAlwaysItem1', component: () => import('@/views/menu/item1.vue'), meta: { title: '菜单项 1', }, }, ], }, ], }

八、路由配置调整:从改一行 meta 到完整落地

权限配置调整

场景 1:添加单个权限

// 修改前 meta: { title: '用户管理', } // 修改后 meta: { title: '用户管理', auth: 'user:view', // 需要 user:view 权限 }

场景 2:添加多个权限(或关系)

meta: { title: '用户管理', auth: ['user:view', 'user:edit'], // 满足其中一个即可 }

页面保活配置

场景 1:列表页保活——列表页需要在从详情页返回时保持状态:

{ path: '', name: 'orderList', component: () => import('@/views/order/list.vue'), meta: { title: '订单列表', keepAlive: ['orderDetail', 'orderEdit'], // 从这些页面返回时保活 }, }

场景 2:详情页保活——详情页需要保活,但从列表页进入时不保活(确保显示最新数据):

{ path: 'detail/:id', name: 'orderDetail', component: () => import('@/views/order/detail.vue'), meta: { title: '订单详情', menu: false, activeMenu: '/order', keepAlive: true, // 始终保活 noKeepAlive: 'orderList', // 从列表进入时不保活 }, }

场景 3:取消保活——某些页面不需要保活,每次进入都重新加载:

// 删除或注释掉 keepAlive 相关配置 meta: { title: '实时数据', // keepAlive: true, // 删除此行 }

导航显示调整

隐藏导航项——某些页面(如个人设置)不需要出现在导航菜单中:

meta: { title: '个人设置', menu: false, // 不在导航中显示 activeMenu: '/user', // 但高亮用户菜单 }

面包屑配置

场景 1:隐藏面包屑

meta: { title: '登录', breadcrumb: false, // 不显示面包屑 }

场景 2:自定义面包屑高亮

meta: { title: '编辑文章', activeMenu: '/article/list', // 面包屑高亮到文章列表 }

在 routes.ts 中的完整注册

import ArticleRoute from './modules/article' import ContentRoute from './modules/content' // 1. 导入路由模块 import DashboardRoute from './modules/dashboard' import SystemRoute from './modules/system' // 2. 添加到 asyncRoutes const asyncRoutes: Route.recordMainRaw[] = [ { meta: { title: '工作台', icon: 'i-ep:monitor', }, children: [ DashboardRoute, ], }, { meta: { title: '内容', icon: 'i-ep:document', }, children: [ ArticleRoute, ContentRoute, ], }, { meta: { title: '系统', icon: 'i-ep:setting', }, children: [ SystemRoute, ], }, ]

九、底层原理:路由如何变成菜单、权限与保活如何生效

1. 菜单自动生成

菜单并非独立维护,而是由路由实时转换而来。apps/core/src/store/modules/app/menu.ts 中的convertRouteToMenu把主导航分组转换成MenuRecordMainRaw,convertRouteToMenuRecursive递归展开每层路由,并只挑选菜单需要的 meta 字段:auth、title、icon、menu、expand、link。这也解释了为什么menu: false、expand: true会直接影响导航展示,而keepAlive等页面行为属性则不会出现在菜单数据结构中。

2. 权限过滤

apps/core/src/store/modules/app/menu.ts 的filterAsyncMenus会基于auth.auth(menu.meta?.auth)递归过滤菜单:父级无权限则整组移除,子级全部无权限的父级也不会展示。同时路由守卫 apps/core/src/router/guards.ts 中的setupRedirectAuthChildrenRoute会在父级路由未配置重定向时,自动重定向到第一个有访问权限且menu !== false的子路由,保证无权限用户不会落到空白页。

3. 保活机制

保活由路由守卫 apps/core/src/router/guards.ts 的setupKeepAlive驱动,规则与文档完全一致:

  • to.meta.keepAlive为boolean时,不为true则清除保活;
  • 为string时,与from.name不一致则清除保活;
  • 为array时,不包含from.name则清除保活;
  • to.meta.noKeepAlive为string且与from.name一致,或为array且包含from.name,则清除保活;
  • 从reload页面刷新进入时清除保活。

清除或添加操作最终落到 apps/core/src/store/modules/app/keepAlive.ts 的appKeepAliveStore(add/remove/clean),由布局中的<KeepAlive>组件消费。另外需要注意:保活依赖组件name,若组件未命名,开发环境下会通过warnKeepAliveComponentNameMissing给出警告提示。

4. 路由注册与守卫

apps/core/src/router/guards.ts 的setupRoutes是核心守卫:登录后先(可选)拉取用户权限,再按routeBaseOn模式生成动态路由,并通过router.addRoute注册,同时记录removeRoutes回调供登出时清理。此外框架还扩展了 vue-router 的能力(apps/core/src/router/extensions.ts),为router增加了back(关闭当前标签页并回退/跳转)与close(关闭当前标签页并跳转)等扩展语法,与标签页功能联动。路由模式(hash / history)则由settings.app.routeMode控制(apps/core/src/router/index.ts)。

十、小结

Fantastic-admin 的路由体系以"约定优于配置"为原则:写好modules/下的路由文件、遵守路径与组件约定、按需配置meta,即可自动获得导航菜单、权限控制、页面保活、面包屑与标签页等完整能力。实操时可遵循以下 Checklist:

  1. 先确认目标应用(monorepo 下不得猜测)与routeBaseOn是否为frontend;
  2. 新建路由:在apps/<app>/src/router/modules/创建模块文件 → 在routes.ts注册到主导航分组;
  3. 修改路由:直接调整meta,优先使用auth/menu/activeMenu/keepAlive/noKeepAlive/breadcrumb等高频属性;
  4. 牢记框架约定:一级路由以/开头且挂载Layout、子路由不带/、中间层级不写component、name全局唯一;
  5. 遇到保活、权限、菜单展示问题,可回查 guards.ts 与 menu.ts 的判定逻辑,理解配置背后的真实执行规则。
  • 前端
  • AI 技能

【免费下载链接】basic

⭐⭐⭐⭐⭐ 面向 AI 编程的管理系统框架,兼容PC、移动端。AI-oriented management system framework, compatible with PC and mobile device.

项目地址:https://gitcode.com/GitHub_Trending/ba/basic
点击查看免费下载

相关推荐

上一篇:成为 Apache DolphinScheduler Committer 的完整指南:从贡献者到 PMC 成员的晋升之路
下一篇:Obsidian Templater插件完整指南:打造智能笔记自动化系统

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

返回列表