14 KiB
14 KiB
Platform 路由集成指南
快速开始
TL;DR
- shared 提供:路由生成工具、菜单处理、设备检测
- platform 定义:布局组件映射、视图加载器
- platform 生成:调用
generateRoutes()生成自己的路由
// 1. 导入工具
import { generateRoutes, type RouteGeneratorConfig } from 'shared/utils/route'
import type { SysMenu } from 'shared/types'
// 2. 配置生成器
const config: RouteGeneratorConfig = {
layoutMap: { /* 布局映射 */ },
viewLoader: (path) => { /* 组件加载 */ }
}
// 3. 生成路由
const routes = generateRoutes(menus, config)
// 4. 添加到路由
routes.forEach(route => router.addRoute(route))
架构说明
Platform 使用 shared 提供的路由生成工具来动态生成路由。架构如下:
┌─────────────────────────────────────────────────────────────┐
│ shared │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ utils/route/route-generator.ts │ │
│ │ - generateRoutes() 路由生成 │ │
│ │ - buildMenuTree() 菜单树构建 │ │
│ │ - filterMenusByPermissions() 权限过滤 │ │
│ │ - findMenuByPath() 路径查找 │ │
│ │ - getMenuPath() 面包屑路径 │ │
│ │ - getFirstAccessibleMenuUrl() 首页跳转 │ │
│ └───────────────────────────────────────────────────────┘ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ utils/device.ts │ │
│ │ - getDeviceType() 设备类型检测 │ │
│ │ - isMobile() 移动端判断 │ │
│ │ - useDevice() 响应式 Hook │ │
│ └───────────────────────────────────────────────────────┘ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ types/sys/menu.ts & types/enums.ts │ │
│ │ - SysMenu 菜单接口 │ │
│ │ - MenuType 菜单类型枚举 │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ Module Federation (远程模块) │
│ - shared/utils/route │
│ - shared/utils/device │
│ - shared/types │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ platform │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ src/router/index.ts │ │
│ │ 1. 定义布局组件映射 (platformLayoutMap) │ │
│ │ 2. 定义视图组件加载器 (viewLoader) │ │
│ │ 3. 调用 generateRoutes() 生成动态路由 │ │
│ │ 4. 添加到 Vue Router │ │
│ └───────────────────────────────────────────────────────┘ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ src/layouts/ │ │
│ │ - SidebarLayout.vue 侧边栏布局 │ │
│ │ - NavigationLayout.vue 导航布局(如需) │ │
│ │ - BasicLayout.vue 基础布局(如需) │ │
│ └───────────────────────────────────────────────────────┘ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ src/views/ │ │
│ │ - 各种页面组件 │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
使用步骤
1. 在 router/index.ts 中配置路由生成器
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
import { generateRoutes, type RouteGeneratorConfig } from 'shared/utils/route'
import type { SysMenu } from 'shared/types'
import { SidebarLayout } from '../layouts'
// 1. 定义布局组件映射
const platformLayoutMap: Record<string, () => Promise<any>> = {
'SidebarLayout': () => Promise.resolve({ default: SidebarLayout }),
'NavigationLayout': () => Promise.resolve({ default: SidebarLayout }), // 可复用或自定义
'BasicLayout': () => Promise.resolve({ default: SidebarLayout }),
}
// 2. 定义视图组件加载器
const VIEW_MODULES = import.meta.glob('../views/**/*.vue')
function viewLoader(componentPath: string): (() => Promise<any>) | null {
// 将后台路径转换为实际路径
let path = componentPath
if (!path.startsWith('../')) {
if (!path.startsWith('/')) {
path = '/' + path
}
path = '../views' + path
}
if (!path.endsWith('.vue')) {
path += '.vue'
}
const loader = VIEW_MODULES[path]
return loader ? (loader as () => Promise<any>) : null
}
// 3. 创建路由生成器配置
const routeConfig: RouteGeneratorConfig = {
layoutMap: platformLayoutMap,
viewLoader,
staticRoutes: routes, // 可选:静态路由
notFoundComponent: () => import('../views/public/404.vue') // 可选
}
// 4. 动态添加路由的函数
export function addDynamicRoutes(menus: SysMenu[]) {
const dynamicRoutes = generateRoutes(menus, routeConfig)
dynamicRoutes.forEach(route => {
router.addRoute(route)
})
console.log('✅ 动态路由已添加', dynamicRoutes.length, '个')
}
2. 在应用初始化时调用
// main.ts 或登录成功后
import { addDynamicRoutes } from './router'
import { menuAPI } from 'shared/api'
// 获取用户菜单
const response = await menuAPI.getUserMenus()
if (response.success && response.data) {
// 添加动态路由
addDynamicRoutes(response.data)
// 跳转到首页或指定页面
router.push('/home')
}
菜单数据格式
interface SysMenu {
menuID: string // 菜单ID,作为路由 name
parentID?: string // 父菜单ID,'0' 表示根菜单
name: string // 菜单名称
url?: string // 路由路径,如 '/user/profile'
type: MenuType // 菜单类型
icon?: string // 图标
component?: string // 组件路径,如 'user/profile/ProfileView'
layout?: string // 布局名称,如 'SidebarLayout'
orderNum?: number // 排序号
permission?: string // 权限标识
hidden?: boolean // 是否隐藏
children?: SysMenu[] // 子菜单
}
enum MenuType {
NAVIGATION = 'navigation', // 导航菜单(顶部导航)
SIDEBAR = 'sidebar', // 侧边栏菜单
MENU = 'menu', // 普通菜单
PAGE = 'page', // 页面(独立路由)
BUTTON = 'button' // 按钮(不生成路由)
}
示例菜单数据
const menus: SysMenu[] = [
{
menuID: 'user-center',
parentID: '0',
name: '用户中心',
url: '/user',
type: MenuType.NAVIGATION,
icon: 'User',
layout: 'SidebarLayout',
orderNum: 1,
children: [
{
menuID: 'user-profile',
parentID: 'user-center',
name: '个人信息',
url: '/user/profile',
type: MenuType.MENU,
component: 'user/profile/ProfileView',
orderNum: 1
},
{
menuID: 'user-settings',
parentID: 'user-center',
name: '账号设置',
url: '/user/settings',
type: MenuType.MENU,
component: 'user/settings/SettingsView',
orderNum: 2
}
]
}
]
布局组件要求
布局组件必须包含 <router-view /> 用于渲染子路由:
<template>
<div class="sidebar-layout">
<aside class="sidebar">
<!-- 侧边栏内容 -->
</aside>
<main class="content">
<router-view /> <!-- 重要!用于渲染页面组件 -->
</main>
</div>
</template>
响应式布局(可选)
如果需要移动端适配,可以使用 shared 的设备检测工具:
import { getDeviceType, DeviceType } from 'shared/utils/device'
const deviceType = getDeviceType()
if (deviceType === DeviceType.MOBILE) {
// 移动端逻辑
}
工具方法说明
所有工具方法从 shared/utils/route 导入:
import {
generateRoutes,
buildMenuTree,
filterMenusByPermissions,
findMenuByPath,
getMenuPath,
getFirstAccessibleMenuUrl,
type RouteGeneratorConfig
} from 'shared/utils/route'
import type { SysMenu, MenuType } from 'shared/types'
generateRoutes(menus, config)
根据菜单生成路由配置数组
参数:
menus: SysMenu[]- 菜单列表config: RouteGeneratorConfig- 路由生成器配置
返回: RouteRecordRaw[]
buildMenuTree(menus, staticRoutes?)
将扁平菜单列表转换为树形结构
参数:
menus: SysMenu[]- 菜单列表staticRoutes?: RouteRecordRaw[]- 静态路由(可选)
返回: SysMenu[]
filterMenusByPermissions(menus, permissions)
根据权限过滤菜单
参数:
menus: SysMenu[]- 菜单列表permissions: string[]- 权限列表
返回: SysMenu[]
findMenuByPath(menus, path)
根据路径查找菜单项
参数:
menus: SysMenu[]- 菜单列表path: string- 路由路径
返回: SysMenu | null
getMenuPath(menus, targetMenuId)
获取菜单路径数组(用于面包屑导航)
参数:
menus: SysMenu[]- 菜单列表targetMenuId: string- 目标菜单ID
返回: SysMenu[]
getFirstAccessibleMenuUrl(menus)
获取第一个可访问的菜单URL(用于登录后跳转)
参数:
menus: SysMenu[]- 菜单列表
返回: string | null
注意事项
- shared 服务必须先启动:因为使用 Module Federation,platform 依赖 shared 的远程模块
- 布局组件必须包含 router-view:否则子路由无法渲染
- 组件路径映射:确保
viewLoader能正确加载组件 - 静态路由优先:如果菜单标记为
__STATIC_ROUTE__,不会重复生成路由 - 路由守卫:记得在
router.beforeEach中添加权限检查
调试技巧
- 查看生成的路由:
console.log('所有路由:', router.getRoutes())
console.log('路由数量:', router.getRoutes().length)
- 查看菜单树结构:
import { buildMenuTree } from 'shared/utils/route'
const tree = buildMenuTree(menus)
console.log('菜单树:', JSON.stringify(tree, null, 2))
- 查看当前路由信息:
console.log('当前路由:', router.currentRoute.value)
console.log('路由路径:', router.currentRoute.value.path)
console.log('路由参数:', router.currentRoute.value.params)
console.log('路由元数据:', router.currentRoute.value.meta)
- 测试菜单查找:
import { findMenuByPath, getMenuPath } from 'shared/utils/route'
// 根据路径查找菜单
const menu = findMenuByPath(menus, '/user/profile')
console.log('找到的菜单:', menu)
// 获取面包屑路径
const breadcrumb = getMenuPath(menus, 'user-profile')
console.log('面包屑:', breadcrumb.map(m => m.name).join(' > '))
- 检查视图组件加载:
// 在 viewLoader 中添加日志
function viewLoader(componentPath: string) {
console.log('尝试加载组件:', componentPath)
const path = /* 转换逻辑 */
const loader = VIEW_MODULES[path]
console.log('找到的加载器:', loader ? '✅' : '❌')
return loader ? (loader as () => Promise<any>) : null
}