| name | create-route |
| description | 指导创建符合规范的路由配置,包括路由模块、导航守卫、权限控制。当需要创建新的路由或配置路由守卫时使用本技能。 |
创建路由配置
使用场景
当你需要:
- 创建新的路由配置
- 设置导航守卫和权限控制
- 配置动态路由
- 创建路由模块
请使用本技能,并同时遵守 .agents/rules/06-路由规范.md。
创建路由步骤
1. 创建路由模块
在 src/router/modules/ 目录下创建新的路由模块:
import type { AppRouteRecordRaw } from '../types'
const productRoutes: AppRouteRecordRaw[] = [
{
path: '/product',
name: 'product',
component: () => import('@/layouts/default.vue'),
redirect: '/product/list',
meta: {
title: '产品管理',
requiresAuth: true,
icon: 'product',
order: 3,
permissions: ['product:view']
},
children: [
{
path: 'list',
name: 'productList',
component: () => import('@/views/product/list.vue'),
meta: {
title: '产品列表',
keepAlive: true,
permissions: ['product:view']
}
},
{
path: 'detail/:id',
name: 'productDetail',
component: () => import('@/views/product/detail.vue'),
meta: {
title: '产品详情',
hideInMenu: true,
permissions: ['product:view']
}
},
{
path: 'create',
name: 'productCreate',
component: () => import('@/views/product/create.vue'),
meta: {
title: '创建产品',
permissions: ['product:create']
}
},
{
path: 'edit/:id',
name: 'productEdit',
component: () => import('@/views/product/edit.vue'),
meta: {
title: '编辑产品',
hideInMenu: true,
permissions: ['product:update']
}
}
]
}
]
export default productRoutes
2. 在主路由配置中注册
import type { AppRouteRecordRaw } from './types'
import homeRoutes from './modules/home'
import userRoutes from './modules/user'
import productRoutes from './modules/product'
import orderRoutes from './modules/order'
export const routes: AppRouteRecordRaw[] = [
...homeRoutes,
...userRoutes,
...productRoutes,
...orderRoutes,
{
path: '/:pathMatch(.*)*',
name: 'notFound',
component: () => import('@/views/error/404.vue'),
meta: {
title: '页面不存在',
hideInMenu: true
}
},
{
path: '/403',
name: 'forbidden',
component: () => import('@/views/error/403.vue'),
meta: {
title: '没有权限',
hideInMenu: true
}
}
]
3. 创建路由类型定义
import type { RouteRecordRaw } from 'vue-router'
export interface RouteMeta {
title?: string
requiresAuth?: boolean
permissions?: string[]
roles?: string[]
keepAlive?: boolean
hideInMenu?: boolean
icon?: string
order?: number
activeMenu?: string
breadcrumb?: boolean
affix?: boolean
}
export type AppRouteRecordRaw = RouteRecordRaw & {
meta?: RouteMeta
}
export interface BreadcrumbItem {
title: string
path?: string
name?: string
noRedirect?: boolean
}
4. 配置导航守卫
import type { Router, RouteLocationNormalized } from 'vue-router'
import { useUserStore } from '@/store/user'
import { ElMessage } from '@tiansu/element-plus'
import { getToken } from '@/utils/auth'
export function setupGuards(router: Router) {
const whiteList = ['login', 'register', '404', '403']
const hasPermission = (route: RouteLocationNormalized): boolean => {
const { permissions, roles } = route.meta || {}
const userStore = useUserStore()
if (permissions && permissions.length > 0) {
const hasRequiredPermission = permissions.some(permission => userStore.permissions.includes(permission))
if (!hasRequiredPermission) return false
}
if (roles && roles.length > 0) {
const hasRequiredRole = roles.some(role => userStore.roles.includes(role))
if (!hasRequiredRole) return false
}
return true
}
router.beforeEach(async (to, from, next) => {
if (to.meta?.title) {
document.title = `${to.meta.title} - 管理系统`
}
const hasToken = getToken()
const userStore = useUserStore()
if (hasToken) {
if (to.name === 'login') {
next({ name: 'dashboard' })
return
}
if (!userStore.userInfo) {
try {
await userStore.getUserInfo()
} catch (error) {
await userStore.logout()
next({
name: 'login',
query: { redirect: to.fullPath }
})
return
}
}
if (!hasPermission(to)) {
ElMessage.error('您没有访问权限')
next({ name: '403' })
return
}
next()
} else {
if (whiteList.includes(to.name as string)) {
next()
} else {
ElMessage.warning('请先登录')
next({
name: 'login',
query: { redirect: to.fullPath }
})
}
}
})
router.afterEach((to, from) => {
console.log(`路由跳转: ${from.path} -> ${to.path}`)
})
router.onError(error => {
console.error('路由错误:', error)
ElMessage.error('页面加载失败')
})
}
5. 创建路由实例
import { createRouter, createWebHistory } from 'vue-router'
import type { Router } from 'vue-router'
import { routes } from './routes'
import { setupGuards } from './guards'
export const router: Router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes,
scrollBehavior(to, from, savedPosition) {
if (savedPosition) {
return savedPosition
}
if (to.hash) {
return {
el: to.hash,
behavior: 'smooth'
}
}
return { top: 0, behavior: 'smooth' }
}
})
setupGuards(router)
export default router
动态路由
从后端获取路由配置
import type { AppRouteRecordRaw } from './types'
import { useUserStore } from '@/stores/user'
export async function generateDynamicRoutes(): Promise<AppRouteRecordRaw[]> {
const userStore = useUserStore()
try {
const { getUserRoutes } = await import('@/apis/user')
const routeData = await getUserRoutes()
return transformRoutes(routeData)
} catch (error) {
console.error('获取动态路由失败:', error)
return []
}
}
function transformRoutes(data: any[]): AppRouteRecordRaw[] {
return data.map(item => ({
path: item.path,
name: item.name,
component: loadComponent(item.component),
meta: {
title: item.title,
icon: item.icon,
permissions: item.permissions,
order: item.order,
keepAlive: item.keepAlive,
hideInMenu: item.hideInMenu
},
children: item.children ? transformRoutes(item.children) : []
}))
}
function loadComponent(component: string) {
const componentMap: Record<string, () => Promise<any>> = {
dashboard: () => import('@/views/dashboard/index.vue'),
user: () => import('@/views/user/index.vue'),
product: () => import('@/views/product/index.vue'),
order: () => import('@/views/order/index.vue')
}
return componentMap[component] || (() => import('@/views/error/404.vue'))
}
添加动态路由
import { generateDynamicRoutes } from '@/router/dynamic'
const addDynamicRoutes = async () => {
const dynamicRoutes = await generateDynamicRoutes()
dynamicRoutes.forEach(route => {
router.addRoute(route)
})
}
路由工具函数
面包屑生成
import type { RouteLocationMatched } from 'vue-router'
import type { BreadcrumbItem } from './types'
export function generateBreadcrumbs(matched: RouteLocationMatched[]): BreadcrumbItem[] {
return matched
.filter(item => item.meta?.title)
.map(item => ({
title: item.meta!.title!,
path: item.path,
name: item.name as string,
noRedirect: item.redirect === undefined
}))
}
菜单生成
export function generateMenu(routes: AppRouteRecordRaw[]) {
return routes
.filter(route => !route.meta?.hideInMenu)
.sort((a, b) => (a.meta?.order || 0) - (b.meta?.order || 0))
.map(route => ({
title: route.meta?.title || '',
icon: route.meta?.icon || '',
path: route.path,
name: route.name,
children: route.children ? generateMenu(route.children) : []
}))
}
快速检查清单