mock数据,AI对话,全部应用
This commit is contained in:
382
urbanLifelineWeb/packages/platform/ROUTE_GUIDE.md
Normal file
382
urbanLifelineWeb/packages/platform/ROUTE_GUIDE.md
Normal file
@@ -0,0 +1,382 @@
|
||||
# Platform 路由集成指南
|
||||
|
||||
## 快速开始
|
||||
|
||||
### TL;DR
|
||||
|
||||
1. **shared 提供**:路由生成工具、菜单处理、设备检测
|
||||
2. **platform 定义**:布局组件映射、视图加载器
|
||||
3. **platform 生成**:调用 `generateRoutes()` 生成自己的路由
|
||||
|
||||
```typescript
|
||||
// 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 中配置路由生成器
|
||||
|
||||
```typescript
|
||||
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. 在应用初始化时调用
|
||||
|
||||
```typescript
|
||||
// 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')
|
||||
}
|
||||
```
|
||||
|
||||
## 菜单数据格式
|
||||
|
||||
```typescript
|
||||
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' // 按钮(不生成路由)
|
||||
}
|
||||
```
|
||||
|
||||
## 示例菜单数据
|
||||
|
||||
```typescript
|
||||
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 />` 用于渲染子路由:
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div class="sidebar-layout">
|
||||
<aside class="sidebar">
|
||||
<!-- 侧边栏内容 -->
|
||||
</aside>
|
||||
<main class="content">
|
||||
<router-view /> <!-- 重要!用于渲染页面组件 -->
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
## 响应式布局(可选)
|
||||
|
||||
如果需要移动端适配,可以使用 shared 的设备检测工具:
|
||||
|
||||
```typescript
|
||||
import { getDeviceType, DeviceType } from 'shared/utils/device'
|
||||
|
||||
const deviceType = getDeviceType()
|
||||
if (deviceType === DeviceType.MOBILE) {
|
||||
// 移动端逻辑
|
||||
}
|
||||
```
|
||||
|
||||
## 工具方法说明
|
||||
|
||||
所有工具方法从 `shared/utils/route` 导入:
|
||||
|
||||
```typescript
|
||||
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`
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **shared 服务必须先启动**:因为使用 Module Federation,platform 依赖 shared 的远程模块
|
||||
2. **布局组件必须包含 router-view**:否则子路由无法渲染
|
||||
3. **组件路径映射**:确保 `viewLoader` 能正确加载组件
|
||||
4. **静态路由优先**:如果菜单标记为 `__STATIC_ROUTE__`,不会重复生成路由
|
||||
5. **路由守卫**:记得在 `router.beforeEach` 中添加权限检查
|
||||
|
||||
## 调试技巧
|
||||
|
||||
1. **查看生成的路由**:
|
||||
```typescript
|
||||
console.log('所有路由:', router.getRoutes())
|
||||
console.log('路由数量:', router.getRoutes().length)
|
||||
```
|
||||
|
||||
2. **查看菜单树结构**:
|
||||
```typescript
|
||||
import { buildMenuTree } from 'shared/utils/route'
|
||||
|
||||
const tree = buildMenuTree(menus)
|
||||
console.log('菜单树:', JSON.stringify(tree, null, 2))
|
||||
```
|
||||
|
||||
3. **查看当前路由信息**:
|
||||
```typescript
|
||||
console.log('当前路由:', router.currentRoute.value)
|
||||
console.log('路由路径:', router.currentRoute.value.path)
|
||||
console.log('路由参数:', router.currentRoute.value.params)
|
||||
console.log('路由元数据:', router.currentRoute.value.meta)
|
||||
```
|
||||
|
||||
4. **测试菜单查找**:
|
||||
```typescript
|
||||
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(' > '))
|
||||
```
|
||||
|
||||
5. **检查视图组件加载**:
|
||||
```typescript
|
||||
// 在 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
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user