写在前面的话:这是一个综合的案例,涉及到守卫,重定向。入门初级的兄弟们可以逐个技术栈速通,不要沉迷知识点的学习过程,实操可能更加的重要主包写了两三天
建议用时(2天-5天)
📘 Vue CLI 路由专项训练_前端开发文档
🧭 项目目标
开发一个具有完整前端路由功能的单页应用,涵盖权限控制、路由跳转、动态嵌套路由、查询参数、懒加载、重定向与 404 页面处理,用于 Vue Router 的专项训练。
📦 技术要求
- Vue 2.x
- Vue Router 3.x
- Vue CLI 构建
- Element UI 用于页面 UI
- 项目目录结构清晰、组件模块划分合理
📁 项目结构建议
src/
├── main.js
├── router/
│ ├── index.js // 路由配置
│ └── guards.js // 路由守卫
├── views/
│ ├── Home.vue
│ ├── About.vue
│ ├── Products.vue
│ ├── Dashboard.vue
│ ├── Admin.vue
│ ├── RedirectInfo.vue
│ ├── NotFound.vue
│ └── user/
│ ├── UserDetail.vue
│ ├── UserProfile.vue
│ └── UserSettings.vue
└── App.vue
📍 路由功能要求
✅ 基础页面
/
首页/about
关于页面- 页面中按钮跳转需通过路由控制
🔐 路由权限控制
/dashboard
:登录后才能访问/admin
:仅管理员才能访问- 配置路由元信息
meta.requiresAuth
/meta.requiresAdmin
🔗 动态 & 嵌套路由
/user/:id
:用户详情页- 嵌套:
/user/:id/profile
、/user/:id/settings
- 子页面通过
<router-view>
展示
🔍 查询参数
/products?category=xxx&page=1
- 页面应能读取并展示查询条件
📦 懒加载路由
/lazy
:以异步组件形式加载页面- 页面应模拟加载延迟(2秒左右)
🔁 重定向
/redirect-test
→/about
/old-dashboard
→/dashboard
🚫 404 页面
- 无匹配路径 → 显示 NotFound 页面
总览:
模块 | 要求说明 |
---|---|
基础路由配置 | 所有页面路径与组件绑定正确 |
页面跳转 | 页面内跳转通过 $router 实现 |
动态路由 | 能读取用户 ID 并展示相关内容 |
嵌套路由 | 子页面通过嵌套路由显示 |
查询参数处理 | 页面可读写查询参数,刷新保持状态 |
路由守卫 | 登录/权限控制逻辑合理,跳转提示完整 |
懒加载实现 | 页面实现异步加载逻辑并显示加载提示 |
重定向与 404 | 能正常重定向,404 页面显示无误 |
路由导航栏高亮状态 | 导航菜单可识别当前路由状态 |
这里是参考答案,直接运行单个HTML文件即可
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue Router 核心功能训练 - 修复版</title>
<!-- Element Plus CSS -->
<link rel="stylesheet" href="https://unpkg.com/element-plus@2.4.4/dist/index.css">
<!-- Vue 3 -->
<script src="https://unpkg.com/vue@3.3.8/dist/vue.global.js"></script>
<!-- Vue Router 4 -->
<script src="https://unpkg.com/vue-router@4.2.5/dist/vue-router.global.js"></script>
<!-- Element Plus -->
<script src="https://unpkg.com/element-plus@2.4.4/dist/index.full.js"></script>
<!-- Element Plus Icons -->
<script src="https://unpkg.com/@element-plus/icons-vue@2.1.0/dist/index.iife.min.js"></script>
</head>
<body>
<div id="app"></div>
<script>
const { createApp, ref, reactive, computed } = Vue;
const { createRouter, createWebHashHistory } = VueRouter;
const { ElMessage, ElMessageBox } = ElementPlus;
// 权限状态管理 - 修复响应式问题
const authState = reactive({
isLoggedIn: false, // 直接使用普通值,不需要ref
isAdmin: false, // 直接使用普通值,不需要ref
username: '', // 直接使用普通值,不需要ref
async login() {
try {
// 模拟登录过程
const { value: credentials } = await ElMessageBox.prompt('请输入用户名 (admin/user)', '登录', {
confirmButtonText: '登录',
cancelButtonText: '取消',
inputPattern: /\S+/,
inputErrorMessage: '用户名不能为空'
});
// 预设账号逻辑
if (credentials === 'admin') {
this.isLoggedIn = true;
this.isAdmin = true;
this.username = 'admin';
ElMessage.success('🎉 管理员登录成功!');
} else if (credentials === 'user') {
this.isLoggedIn = true;
this.isAdmin = false;
this.username = 'user';
ElMessage.success('✅ 普通用户登录成功!');
} else {
// 其他用户名默认为普通用户
this.isLoggedIn = true;
this.isAdmin = false;
this.username = credentials;
ElMessage.success(`✅ 用户 ${credentials} 登录成功!`);
}
console.log('登录状态已更新 - 登录:', this.isLoggedIn, '管理员:', this.isAdmin, '用户名:', this.username);
} catch {
ElMessage.info('取消登录');
}
},
logout() {
console.log('开始退出登录...');
this.isLoggedIn = false;
this.isAdmin = false;
this.username = '';
ElMessage.success('退出登录成功');
console.log('退出登录完成 - 登录:', this.isLoggedIn, '管理员:', this.isAdmin, '用户名:', this.username);
},
setAdmin() {
console.log('尝试设为管理员 - 当前登录状态:', this.isLoggedIn, '用户名:', this.username);
if (!this.isLoggedIn) {
ElMessage.warning('请先登录');
return;
}
this.isAdmin = true;
ElMessage.success(`${this.username} 已设置为管理员`);
console.log('设为管理员完成 - 管理员状态:', this.isAdmin);
},
setUser() {
console.log('尝试设为普通用户 - 当前登录状态:', this.isLoggedIn, '用户名:', this.username);
if (!this.isLoggedIn) {
ElMessage.warning('请先登录');
return;
}
this.isAdmin = false;
ElMessage.success(`${this.username} 已设置为普通用户`);
console.log('设为普通用户完成 - 管理员状态:', this.isAdmin);
},
getStatusText() {
if (!this.isLoggedIn) return '未登录';
const role = this.isAdmin ? '管理员' : '普通用户';
return `${this.username} (${role})`;
}
});
// 用户设置数据(持久化)
const userSettings = reactive({
data: {},
save(userId, settings) {
this.data[userId] = { ...settings };
ElMessage.success('设置保存成功');
},
load(userId) {
return this.data[userId] || {
emailNotify: true,
smsNotify: false,
privacy: 'public'
};
}
});
// 路由组件
// 首页
const Home = {
template: `
<div>
<el-alert title="Vue Router 核心功能训练 - 修复版" type="success" show-icon />
<!-- 权限控制面板 -->
<el-card style="margin-top: 20px;">
<template #header>
<span>权限控制面板 - 路由守卫测试</span>
</template>
<!-- 添加账号说明 -->
<el-alert
title="测试账号说明"
type="info"
:closable="false"
style="margin-bottom: 15px;"
>
<p><strong>管理员账号:</strong> admin</p>
<p><strong>普通用户:</strong> user</p>
<p><strong>其他用户名:</strong> 默认为普通用户</p>
</el-alert>
<div style="margin-bottom: 15px;">
当前状态: <el-tag :type="getTagType()">{{ authState.getStatusText() }}</el-tag>
</div>
<el-space>
<el-button @click="handleLogin" type="success" :disabled="authState.isLoggedIn">
<el-icon><User /></el-icon>
登录
</el-button>
<el-button @click="handleLogout" type="info" :disabled="!authState.isLoggedIn">
<el-icon><SwitchButton /></el-icon>
退出登录
</el-button>
<el-button @click="handleSetAdmin" type="danger" :disabled="authState.isAdmin || !authState.isLoggedIn">
<el-icon><Crown /></el-icon>
设为管理员
</el-button>
<el-button @click="handleSetUser" type="warning" :disabled="!authState.isAdmin">
<el-icon><Avatar /></el-icon>
设为普通用户
</el-button>
</el-space>
</el-card>
<el-row :gutter="20" style="margin-top: 20px;">
<el-col :span="12">
<el-card header="基础路由">
<el-button @click="$router.push('/about')" block>
<el-icon><InfoFilled /></el-icon>
关于页面
</el-button>
</el-card>
</el-col>
<el-col :span="12">
<el-card header="动态路由">
<el-button @click="$router.push('/user/123')" block>
<el-icon><User /></el-icon>
用户 123
</el-button>
</el-card>
</el-col>
</el-row>
<el-row :gutter="20" style="margin-top: 20px;">
<el-col :span="12">
<el-card header="查询参数">
<el-button @click="$router.push('/products?category=books')" block>
<el-icon><Reading /></el-icon>
图书分类
</el-button>
</el-card>
</el-col>
<el-col :span="12">
<el-card header="路由守卫测试">
<el-space direction="vertical" style="width: 100%;">
<el-button @click="$router.push('/dashboard')" type="primary" block>
<el-icon><DataBoard /></el-icon>
需要登录
</el-button>
<el-button @click="$router.push('/admin')" type="danger" block>
<el-icon><Setting /></el-icon>
需要管理员
</el-button>
</el-space>
</el-card>
</el-col>
</el-row>
<el-row :gutter="20" style="margin-top: 20px;">
<el-col :span="24">
<el-card header="其他功能">
<el-space>
<el-button @click="$router.push('/lazy')" type="warning">
<el-icon><Loading /></el-icon>
懒加载
</el-button>
<el-button @click="testRedirect" type="info">
<el-icon><Right /></el-icon>
重定向测试
</el-button>
<el-button @click="$router.push('/notfound')" type="danger">
<el-icon><WarningFilled /></el-icon>
404测试
</el-button>
</el-space>
</el-card>
</el-col>
</el-row>
</div>
`,
computed: {
authState() {
return authState;
}
},
methods: {
async handleLogin() {
console.log('点击登录按钮');
await authState.login();
this.$forceUpdate(); // 强制更新组件
},
handleLogout() {
console.log('点击退出登录按钮');
authState.logout();
this.$forceUpdate(); // 强制更新组件
},
handleSetAdmin() {
console.log('点击设为管理员按钮');
authState.setAdmin();
this.$forceUpdate(); // 强制更新组件
},
handleSetUser() {
console.log('点击设为普通用户按钮');
authState.setUser();
this.$forceUpdate(); // 强制更新组件
},
getTagType() {
if (!authState.isLoggedIn) return 'info';
return authState.isAdmin ? 'danger' : 'success';
},
testRedirect() {
ElMessage.info('即将重定向到关于页面...');
setTimeout(() => {
this.$router.push('/redirect-test');
}, 1000);
}
}
};
// 关于页面
const About = {
template: `
<div>
<el-alert title="关于页面 - 基础路由演示" type="info" show-icon />
<el-card style="margin-top: 20px;">
<p>这是一个基础路由页面,演示了简单的页面跳转功能。</p>
<el-divider />
<p><strong>路由信息:</strong></p>
<ul>
<li>路径: {{ $route.path }}</li>
<li>名称: {{ $route.name || '未命名' }}</li>
<li>来源: {{ $route.meta.from || '直接访问' }}</li>
</ul>
<el-space style="margin-top: 20px;">
<el-button @click="$router.back()" icon="ArrowLeft">返回上页</el-button>
<el-button @click="$router.push('/')" type="primary" icon="House">返回首页</el-button>
</el-space>
</el-card>
</div>
`
};
// 重定向说明页面
const RedirectInfo = {
template: `
<div>
<el-alert title="重定向演示页面" type="warning" show-icon />
<el-card style="margin-top: 20px;">
<p>🎯 <strong>重定向逻辑说明:</strong></p>
<ol>
<li>当您访问 <code>/redirect-test</code> 时</li>
<li>路由会自动重定向到 <code>/about</code> 页面</li>
<li>但会在 about 页面的 meta 中标记来源为 "重定向"</li>
</ol>
<el-divider />
<p><strong>测试重定向:</strong></p>
<el-space direction="vertical" style="width: 100%;">
<el-button @click="testRedirect1" type="primary" block>
测试重定向 1: /redirect-test → /about
</el-button>
<el-button @click="testRedirect2" type="success" block>
测试重定向 2: /old-dashboard → /dashboard (需要登录)
</el-button>
<el-button @click="testRedirect3" type="warning" block>
测试重定向 3: /legacy-admin → /admin (需要管理员)
</el-button>
</el-space>
<el-space style="margin-top: 20px;">
<el-button @click="$router.back()" icon="ArrowLeft">返回上页</el-button>
<el-button @click="$router.push('/')" type="primary" icon="House">返回首页</el-button>
</el-space>
</el-card>
</div>
`,
methods: {
testRedirect1() {
ElMessage.info('即将重定向: /redirect-test → /about');
setTimeout(() => {
this.$router.push('/redirect-test');
}, 1000);
},
testRedirect2() {
ElMessage.info('即将重定向: /old-dashboard → /dashboard');
setTimeout(() => {
this.$router.push('/old-dashboard');
}, 1000);
},
testRedirect3() {
ElMessage.info('即将重定向: /legacy-admin → /admin');
setTimeout(() => {
this.$router.push('/legacy-admin');
}, 1000);
}
}
};
// 用户详情页面 - 动态路由
const UserDetail = {
template: `
<div>
<el-alert :title="'用户详情 - ID: ' + $route.params.id + ' (动态路由)'" type="success" show-icon />
<el-card style="margin-top: 20px;">
<p><strong>用户ID:</strong> {{ $route.params.id }}</p>
<p><strong>用户名:</strong> 用户{{ $route.params.id }}</p>
<el-divider content-position="left">嵌套路由</el-divider>
<el-space>
<el-button @click="$router.push('/user/' + $route.params.id + '/profile')" type="primary">
<el-icon><User /></el-icon>
个人资料
</el-button>
<el-button @click="$router.push('/user/' + $route.params.id + '/settings')" type="success">
<el-icon><Setting /></el-icon>
账户设置
</el-button>
</el-space>
<!-- 嵌套路由出口 -->
<div style="margin-top: 20px;">
<router-view></router-view>
</div>
<el-divider />
<el-space>
<el-button @click="$router.back()" icon="ArrowLeft">返回上页</el-button>
<el-button @click="$router.push('/')" type="primary" icon="House">返回首页</el-button>
</el-space>
</el-card>
</div>
`
};
// 用户资料 - 嵌套路由
const UserProfile = {
template: `
<el-card>
<template #header>
<el-icon><User /></el-icon>
个人资料 (嵌套路由)
</template>
<p><strong>真实姓名:</strong> 用户{{ $route.params.id }}</p>
<p><strong>邮箱:</strong> user{{ $route.params.id }}@example.com</p>
<p><strong>注册时间:</strong> 2024-01-01</p>
<p><strong>最后登录:</strong> {{ new Date().toLocaleString() }}</p>
</el-card>
`
};
// 用户设置 - 嵌套路由
const UserSettings = {
template: `
<el-card>
<template #header>
<el-icon><Setting /></el-icon>
账户设置 (嵌套路由)
</template>
<el-form label-width="100px">
<el-form-item label="邮件通知">
<el-switch v-model="settings.emailNotify" />
</el-form-item>
<el-form-item label="短信通知">
<el-switch v-model="settings.smsNotify" />
</el-form-item>
<el-form-item label="隐私设置">
<el-select v-model="settings.privacy">
<el-option label="公开" value="public" />
<el-option label="仅好友" value="friends" />
<el-option label="私密" value="private" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="save">保存设置</el-button>
<el-button @click="reset">重置</el-button>
</el-form-item>
</el-form>
<el-divider />
<p><strong>当前设置:</strong></p>
<pre>{{ JSON.stringify(settings, null, 2) }}</pre>
</el-card>
`,
data() {
return {
settings: {}
};
},
created() {
this.loadSettings();
},
watch: {
'$route'() {
this.loadSettings();
}
},
methods: {
loadSettings() {
this.settings = userSettings.load(this.$route.params.id);
},
save() {
userSettings.save(this.$route.params.id, this.settings);
},
reset() {
this.settings = {
emailNotify: true,
smsNotify: false,
privacy: 'public'
};
ElMessage.info('设置已重置');
}
}
};
// 商品页面 - 查询参数
const Products = {
template: `
<div>
<el-alert title="商品列表 - 查询参数演示" type="warning" show-icon />
<el-card style="margin-top: 20px;">
<el-alert :title="'当前URL: ' + $route.fullPath" type="info" :closable="false" />
<el-form :inline="true" style="margin-top: 20px;">
<el-form-item label="分类">
<el-select v-model="category" @change="updateQuery">
<el-option label="全部" value="" />
<el-option label="图书" value="books" />
<el-option label="电子产品" value="electronics" />
<el-option label="服装" value="clothing" />
</el-select>
</el-form-item>
<el-form-item label="页码">
<el-input-number v-model="page" :min="1" @change="updateQuery" />
</el-form-item>
<el-form-item>
<el-button @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<p style="margin-top: 20px;">
<strong>筛选结果:</strong>
{{ category ? '分类: ' + category : '全部商品' }}
{{ page > 1 ? ', 第' + page + '页' : '' }}
</p>
<el-space style="margin-top: 20px;">
<el-button @click="$router.back()">返回上页</el-button>
<el-button @click="$router.push('/')" type="primary">返回首页</el-button>
</el-space>
</el-card>
</div>
`,
data() {
return {
category: '',
page: 1
};
},
created() {
const query = this.$route.query;
this.category = query.category || '';
this.page = parseInt(query.page) || 1;
},
watch: {
'$route'() {
const query = this.$route.query;
this.category = query.category || '';
this.page = parseInt(query.page) || 1;
}
},
methods: {
updateQuery() {
const query = {};
if (this.category) query.category = this.category;
if (this.page > 1) query.page = this.page;
this.$router.push({ path: '/products', query: query });
},
resetQuery() {
this.$router.push('/products');
}
}
};
// 数据面板 - 需要登录
const Dashboard = {
template: `
<div>
<el-alert title="数据面板 - 路由守卫演示 (需要登录)" type="success" show-icon />
<el-card style="margin-top: 20px;">
<p>🎉 恭喜!您已通过登录验证,可以访问此页面。</p>
<p>当前用户状态: <el-tag type="success">{{ authState.getStatusText() }}</el-tag></p>
<p>访问时间: {{ new Date().toLocaleString() }}</p>
<el-space style="margin-top: 20px;">
<el-button @click="$router.back()">返回上页</el-button>
<el-button @click="$router.push('/')" type="primary">返回首页</el-button>
</el-space>
</el-card>
</div>
`,
computed: {
authState() {
return authState;
}
}
};
// 管理员页面 - 需要管理员权限
const Admin = {
template: `
<div>
<el-alert title="管理员页面 - 路由守卫演示 (需要管理员权限)" type="danger" show-icon />
<el-card style="margin-top: 20px;">
<p>🎉 恭喜!您拥有管理员权限,可以访问此页面。</p>
<p>当前用户状态: <el-tag type="danger">{{ authState.getStatusText() }}</el-tag></p>
<p>访问时间: {{ new Date().toLocaleString() }}</p>
<el-space style="margin-top: 20px;">
<el-button @click="$router.back()">返回上页</el-button>
<el-button @click="$router.push('/')" type="primary">返回首页</el-button>
</el-space>
</el-card>
</div>
`,
computed: {
authState() {
return authState;
}
}
};
// 懒加载页面
const LazyPage = {
template: `
<div>
<el-alert title="懒加载页面 - 异步组件演示" type="warning" show-icon />
<el-card style="margin-top: 20px;">
<p>🎉 这个页面通过懒加载方式加载,模拟了2秒加载时间。</p>
<p>加载完成时间: {{ new Date().toLocaleString() }}</p>
<el-space style="margin-top: 20px;">
<el-button @click="$router.back()">返回上页</el-button>
<el-button @click="$router.push('/')" type="primary">返回首页</el-button>
</el-space>
</el-card>
</div>
`
};
// 404页面
const NotFound = {
template: `
<el-result
icon="warning"
title="404"
sub-title="页面未找到"
>
<template #extra>
<el-space>
<el-button @click="$router.back()">返回上页</el-button>
<el-button type="primary" @click="$router.push('/')">返回首页</el-button>
</el-space>
</template>
</el-result>
`
};
// 主应用组件
const App = {
template: `
<el-container style="min-height: 100vh;">
<!-- 头部导航 -->
<el-header>
<el-menu :default-active="$route.path" mode="horizontal">
<el-menu-item index="/" @click="$router.push('/')">
<el-icon><House /></el-icon>
首页
</el-menu-item>
<el-menu-item index="/about" @click="$router.push('/about')">
<el-icon><InfoFilled /></el-icon>
关于
</el-menu-item>
<el-menu-item index="/products" @click="$router.push('/products')">
<el-icon><Goods /></el-icon>
商品
</el-menu-item>
<el-menu-item index="/redirect-info" @click="$router.push('/redirect-info')">
<el-icon><Right /></el-icon>
重定向
</el-menu-item>
<div style="flex: 1;"></div>
<!-- 状态显示 -->
<div style="display: flex; align-items: center; margin-right: 20px;">
<span style="margin-right: 10px;">当前状态:</span>
<el-tag :type="getStatusTagType()">
{{ authState.getStatusText() }}
</el-tag>
</div>
</el-menu>
</el-header>
<!-- 主要内容 -->
<el-main>
<!-- 路由信息 -->
<el-alert
:title="getRouteInfo()"
type="info"
:closable="false"
style="margin-bottom: 20px;"
/>
<!-- 页面内容 -->
<router-view></router-view>
</el-main>
</el-container>
`,
computed: {
authState() {
return authState;
}
},
methods: {
getStatusTagType() {
if (!authState.isLoggedIn) return 'info';
return authState.isAdmin ? 'danger' : 'success';
},
getRouteInfo() {
let info = `当前路由: ${this.$route.path}`;
if (Object.keys(this.$route.params).length > 0) {
info += ` | 参数: ${JSON.stringify(this.$route.params)}`;
}
if (Object.keys(this.$route.query).length > 0) {
info += ` | 查询: ${JSON.stringify(this.$route.query)}`;
}
if (this.$route.meta.from) {
info += ` | 来源: ${this.$route.meta.from}`;
}
return info;
}
}
};
// 路由配置
const routes = [
{ path: '/', component: Home },
{
path: '/about',
component: About,
beforeEnter: (to, from, next) => {
// 检查是否来自重定向
if (from.path === '/redirect-test') {
to.meta.from = '重定向';
}
next();
}
},
{ path: '/redirect-info', component: RedirectInfo },
{
path: '/user/:id',
component: UserDetail,
children: [
{ path: 'profile', component: UserProfile },
{ path: 'settings', component: UserSettings }
]
},
{ path: '/products', component: Products },
{ path: '/dashboard', component: Dashboard, meta: { requiresAuth: true } },
{ path: '/admin', component: Admin, meta: { requiresAuth: true, requiresAdmin: true } },
{
path: '/lazy',
component: () => {
return new Promise(resolve => {
ElMessage.info('正在加载页面,请稍候...');
setTimeout(() => resolve(LazyPage), 2000);
});
}
},
// 重定向路由 - 更明显的重定向逻辑
{
path: '/redirect-test',
redirect: to => {
ElMessage.success('重定向成功: /redirect-test → /about');
return '/about';
}
},
{
path: '/old-dashboard',
redirect: '/dashboard'
},
{
path: '/legacy-admin',
redirect: '/admin'
},
{ path: '/:pathMatch(.*)*', component: NotFound }
];
// 创建路由
const router = createRouter({
history: createWebHashHistory(),
routes
});
// 路由守卫 - 改进的守卫逻辑
router.beforeEach((to, from, next) => {
console.log('=== 路由守卫检查 ===');
console.log('从:', from.path, '到:', to.path);
console.log('登录状态:', authState.isLoggedIn);
console.log('管理员状态:', authState.isAdmin);
console.log('路由元信息:', to.meta);
// 检查是否需要登录
if (to.meta.requiresAuth && !authState.isLoggedIn) {
ElMessage.error('🔒 请先登录才能访问此页面!');
console.log('❌ 访问被拒绝: 需要登录');
next('/');
return;
}
// 检查是否需要管理员权限
if (to.meta.requiresAdmin && (!authState.isLoggedIn || !authState.isAdmin)) {
if (!authState.isLoggedIn) {
ElMessage.error('🔒 请先登录!');
console.log('❌ 访问被拒绝: 需要登录');
} else {
ElMessage.error('🔒 需要管理员权限才能访问此页面!');
console.log('❌ 访问被拒绝: 需要管理员权限');
}
next('/');
return;
}
console.log('✅ 路由守卫通过');
next();
});
// 路由后置守卫 - 记录访问日志
router.afterEach((to, from) => {
console.log(`✅ 路由跳转完成: ${from.path} → ${to.path}`);
});
// 创建应用
const app = createApp(App);
// 注册图标
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component);
}
app.use(ElementPlus);
app.use(router);
app.mount('#app');
console.log('🚀 Vue Router 训练应用启动成功!');
</script>
</body>
</html>