第一部分
1、初始化项目
npm init vite@latest
npm run dev :运行项目
q+Enter:退出运行
2、安装路由依赖
npm install vue-router@4 # Vue3 对应 vue-router 4.x 版本
第二部分:
创建页面组件
日统计(daily.vue)
<template><div class="page home-page"><h2>首页</h2><p>这是网站首页内容</p></div> </template><style scoped> .page {padding: 20px; } .home-page {background-color: #f0f9ff; } </style>
周统计(weekly.vue)
<template><div class="page home-page"><h2>首页</h2><p>这是网站首页内容</p></div> </template><style scoped> .page {padding: 20px; } .home-page {background-color: #f0fff4; } </style>
在 src/views/home/dashboard
目录下创建三个页面组件:
概览
<template><div class="page home-page"><h2>首页</h2><p>这是网站首页内容</p></div> </template><style scoped> .page {padding: 20px; } .home-page {background-color: #f0f9ff; } </style>
趋势分析
<template><div class="page home-page"><h2>首页</h2><p>这是网站首页内容</p></div> </template><style scoped> .page {padding: 20px; } .home-page {background-color: #f0fff4; } </style>
来源分析
<template><div class="page home-page"><h2>首页</h2><p>这是网站首页内容</p></div> </template><style scoped> .page {padding: 20px; } .home-page {background-color: #f0fff4; } </style>
在 src/views/user/permissions
目录下创建两个页面组件:
角色配置
<template><div class="page about-page"><h2>角色管理</h2><p>这是角色管理页面内容</p></div> </template><style scoped> .page {padding: 20px; } .about-page {background-color: #f0f9ff; } </style>
权限分配
<template><div class="page about-page"><h2>权限设置</h2><p>这是权限设置页面内容</p></div> </template><style scoped> .page {padding: 20px; } .about-page {background-color: #f0fff4; } </style>
在 src/views/user
目录下创建一个页面组件:
<template><div class="page about-page"><h2>用户列表</h2><p>这是用户列表页面内容</p></div> </template><style scoped> .page {padding: 20px; } .about-page {background-color: #fff0f0; } </style>
创建导航组件
<template><div class="layout-container"><!-- 顶部导航(一级菜单) --><header class="top-nav"><div class="logo">Admin Panel</div><nav class="main-menu"><router-linkv-for="menu in mainMenus":key="menu.path":to="menu.path"class="main-menu-item":class="{ active: isMainMenuActive(menu) }"><span class="icon">{{ menu.icon }}</span><span class="text">{{ menu.name }}</span></router-link></nav></header><div class="content-wrapper"><!-- 侧边栏(二级/三级菜单) --><aside class="sidebar"><div class="sidebar-header"><h3>{{ currentMainMenu?.name }} 菜单</h3></div><nav class="sub-menu"><!-- 递归渲染二级/三级菜单 --><SubMenuRecursive :menu-list="currentSubMenus" /></nav></aside><!-- 主内容区 --><main class="main-content"><router-view /></main></div></div> </template><script setup lang="ts"> import { ref, watch } from "vue"; import { useRoute } from "vue-router"; import type { MainMenu, SubMenu } from "../router/menu"; import { mainMenus } from "../router/menu"; import SubMenuRecursive from "../components/SubMenuRecursive.vue";const route = useRoute(); // 先获取第一个菜单,不存在则用默认值 const firstMenu = mainMenus[0] || { name: "", path: "", subMenus: [] } as MainMenu;// 用 firstMenu 初始化,避免 undefined const currentMainMenu = ref<MainMenu | null>(firstMenu); const currentSubMenus = ref<SubMenu[]>(firstMenu.subMenus);// 检查一级菜单是否激活 const isMainMenuActive = (menu: MainMenu) => {return route.path.startsWith(menu.path); };// 路由变化时更新当前菜单 watch(() => route.path,(newPath) => {const matchedMainMenu = mainMenus.find((menu) => newPath.startsWith(menu.path));if (matchedMainMenu) {currentMainMenu.value = matchedMainMenu;currentSubMenus.value = matchedMainMenu.subMenus;}},{ immediate: true } ); </script><style scoped> .layout-container {display: flex;flex-direction: column;min-height: 100vh;color: #333; }/* 顶部导航 */ .top-nav {display: flex;align-items: center;height: 60px;background-color: #2c3e50;color: white;padding: 0 20px;box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); }.logo {font-size: 1.2rem;font-weight: bold;margin-right: 30px; }.main-menu {display: flex;gap: 2px; }.main-menu-item {display: flex;align-items: center;gap: 8px;padding: 0 15px;height: 60px;color: #ecf0f1;text-decoration: none;transition: background-color 0.2s; }.main-menu-item:hover, .main-menu-item.active {background-color: #34495e; }/* 内容区 */ .content-wrapper {display: flex;flex: 1; }/* 侧边栏 */ .sidebar {width: 220px;background-color: #34495e;color: white;padding: 20px 0; }.sidebar-header {padding: 0 20px 15px;border-bottom: 1px solid #2c3e50;margin-bottom: 15px; }.sidebar-header h3 {font-size: 0.9rem;color: #bdc3c7;margin: 0; }/* 主内容区 */ .main-content {flex: 1;padding: 20px;background-color: #f5f7fa;overflow-y: auto; } </style>
配置路由
menu.ts
// 1. 三级菜单接口(直接导出) export interface ThirdMenu {name: string;path: string;icon?: string; }// 2. 二级菜单接口(直接导出) export interface SubMenu {name: string;path?: string;icon?: string;children?: ThirdMenu[]; }// 3. 一级菜单接口(直接导出) export interface MainMenu {name: string;path: string;icon?: string;subMenus: SubMenu[]; }// 4. 菜单数据(直接使用接口类型标注) export const mainMenus: MainMenu[] = [{name: "首页",path: "/home",icon: "📊",subMenus: [{ name: "数据中心", icon: "📈",children: [{ name: "概览", path: "/home/dashboard/overview" },{ name: "趋势分析", path: "/home/dashboard/trend" },{ name: "来源分布", path: "/home/dashboard/source" }]},{ name: "访问统计", icon: "📉",children: [{ name: "日统计", path: "/home/analytics/daily" },{ name: "周统计", path: "/home/analytics/weekly" }]}]},{name: "用户管理",path: "/user",icon: "👥",subMenus: [{ name: "用户列表", path: "/user/list",icon: "👤"},{ name: "权限管理", icon: "🔑",children: [{ name: "角色配置", path: "/user/permissions/roles" },{ name: "权限分配", path: "/user/permissions/assign" }]}]} ];
index.ts
import { createRouter, createWebHistory, type RouteRecordRaw } from "vue-router"; import Layout from "../layouts/MainLayout.vue"; import type { MainMenu, SubMenu, ThirdMenu } from "./menu"; import {mainMenus} from "./menu";// 导入所有视图组件(非动态导入方式) import HomeOverview from "../views/home/dashboard/overview.vue"; import HomeTrend from "../views/home/dashboard/trend.vue"; import HomeSource from "../views/home/dashboard/source.vue"; import AnalyticsDaily from "../views/home/analytics/daily.vue"; import AnalyticsWeekly from "../views/home/analytics/weekly.vue"; import UserList from "../views/user/list.vue"; import RolesConfig from "../views/user/permissions/roles.vue"; import PermissionsAssign from "../views/user/permissions/assign.vue";// 组件映射表(路径 -> 组件) const componentMap: Record<string, any> = {"/home/dashboard/overview": HomeOverview,"/home/dashboard/trend": HomeTrend,"/home/dashboard/source": HomeSource,"/home/analytics/daily": AnalyticsDaily,"/home/analytics/weekly": AnalyticsWeekly,"/user/list": UserList,"/user/permissions/roles": RolesConfig,"/user/permissions/assign": PermissionsAssign, };// 递归生成路由(支持三级菜单) const generateRoutes = (): RouteRecordRaw[] => {const routes: RouteRecordRaw[] = [{path: "/",component: Layout,children: [],},];// 处理一级菜单mainMenus.forEach((mainMenu: MainMenu) => {// 一级菜单默认重定向到第一个三级菜单const firstSub = mainMenu.subMenus[0]!;const firstThird = firstSub.children?.[0] || firstSub;routes[0]!.children!.push({path: mainMenu.path,redirect: firstThird.path!,});// 处理二级和三级菜单mainMenu.subMenus.forEach((subMenu: SubMenu) => {// 若二级菜单有三级菜单,生成二级路由组if (subMenu.children && subMenu.children.length > 0) {subMenu.children.forEach((thirdMenu: ThirdMenu) => {routes[0]!.children!.push({path: thirdMenu.path!,component: componentMap[thirdMenu.path!],});});} else if (subMenu.path) {// 若二级菜单无三级菜单,直接生成路由routes[0]!.children!.push({path: subMenu.path,component: componentMap[subMenu.path],});}});});return routes; };const router = createRouter({history: createWebHistory(import.meta.env.BASE_URL),routes: generateRoutes(), });export default router;
修改根组件(App.vue)
<template><router-view /> </template><script setup lang="ts"></script><style> * {margin: 0;padding: 0;box-sizing: border-box;font-family: 'Segoe UI', sans-serif; }body {background-color: #f5f7fa; } </style>
配置入口文件(main.ts)