Tailwind CSSTailwind CSS
Home
  • Tailwind CSS 书籍目录
  • Vue 3 开发实战指南
  • React 和 Next.js 学习
  • TypeScript
  • React开发框架书籍大纲
  • Shadcn学习大纲
  • Swift 编程语言:从入门到进阶
  • SwiftUI 学习指南
  • 函数式编程大纲
  • Swift 异步编程语言
  • Swift 协议化编程
  • SwiftUI MVVM 开发模式
  • SwiftUI 图表开发书籍
  • SwiftData
  • ArkTS编程语言:从入门到精通
  • 仓颉编程语言:从入门到精通
  • 鸿蒙手机客户端开发实战
  • WPF书籍
  • C#开发书籍
learn
  • Java编程语言
  • Kotlin 编程入门与实战
  • /python/outline.html
  • AI Agent
  • MCP (Model Context Protocol) 应用指南
  • 深度学习
  • 深度学习
  • 强化学习: 理论与实践
  • 扩散模型书籍
  • Agentic AI for Everyone
langchain
Home
  • Tailwind CSS 书籍目录
  • Vue 3 开发实战指南
  • React 和 Next.js 学习
  • TypeScript
  • React开发框架书籍大纲
  • Shadcn学习大纲
  • Swift 编程语言:从入门到进阶
  • SwiftUI 学习指南
  • 函数式编程大纲
  • Swift 异步编程语言
  • Swift 协议化编程
  • SwiftUI MVVM 开发模式
  • SwiftUI 图表开发书籍
  • SwiftData
  • ArkTS编程语言:从入门到精通
  • 仓颉编程语言:从入门到精通
  • 鸿蒙手机客户端开发实战
  • WPF书籍
  • C#开发书籍
learn
  • Java编程语言
  • Kotlin 编程入门与实战
  • /python/outline.html
  • AI Agent
  • MCP (Model Context Protocol) 应用指南
  • 深度学习
  • 深度学习
  • 强化学习: 理论与实践
  • 扩散模型书籍
  • Agentic AI for Everyone
langchain
  • 实战案例:实现一个带权限控制的多页应用

实战案例:实现一个带权限控制的多页应用

Vue Router 4 的动态路由、嵌套路由和守卫功能为构建带权限控制的多页应用提供了强大支持。本节通过一个实战案例,设计并实现一个简单的多页应用,包含用户认证、权限校验和页面导航,展示如何在 Vue 3 项目中整合这些特性,打造一个实用的权限管理系统。

需求分析

我们需要实现以下功能:

  1. 页面结构:
    • 公开页面:首页、登录页。
    • 受限页面:仪表盘(包含嵌套子页面:概览、设置)。
  2. 权限控制:
    • 未登录用户只能访问公开页面。
    • 已登录用户可访问仪表盘及其子页面。
    • 管理员用户可访问额外管理页面。
  3. 状态管理:
    • 使用 Pinia 管理用户认证状态。
  4. 导航体验:
    • 动态导航菜单。
    • 路由懒加载优化性能。

项目结构

src/
├── stores/
│   └── auth.js         # 认证状态管理
├── router/
│   └── index.js       # 路由配置
├── views/
│   ├── Home.vue      # 首页
│   ├── Login.vue     # 登录页
│   ├── Dashboard.vue # 仪表盘
│   ├── Overview.vue  # 概览子页面
│   ├── Settings.vue  # 设置子页面
│   └── Admin.vue     # 管理页面
├── components/
│   └── Nav.vue       # 导航组件
├── App.vue
└── main.js

实现步骤

1. 状态管理

使用 Pinia 管理用户认证:

// src/stores/auth.js
import { defineStore } from 'pinia';
import { ref } from 'vue';

export const useAuthStore = defineStore('auth', () => {
  const user = ref(null); // { name: string, role: 'user' | 'admin' }

  const login = (credentials) => {
    // 模拟登录
    const { username, password } = credentials;
    if (username && password === '123') {
      user.value = { name: username, role: username === 'admin' ? 'admin' : 'user' };
      localStorage.setItem('user', JSON.stringify(user.value));
    }
  };

  const logout = () => {
    user.value = null;
    localStorage.removeItem('user');
  };

  // 初始化用户状态
  const storedUser = localStorage.getItem('user');
  if (storedUser) user.value = JSON.parse(storedUser);

  return { user, login, logout };
});

2. 路由配置

定义路由并添加权限守卫:

// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router';
import { useAuthStore } from '@/stores/auth';

const routes = [
  { path: '/', name: 'Home', component: () => import('@/views/Home.vue') },
  { path: '/login', name: 'Login', component: () => import('@/views/Login.vue') },
  {
    path: '/dashboard',
    name: 'Dashboard',
    component: () => import('@/views/Dashboard.vue'),
    meta: { requiresAuth: true },
    children: [
      { path: '', name: 'Overview', component: () => import('@/views/Overview.vue') },
      { path: 'settings', name: 'Settings', component: () => import('@/views/Settings.vue') }
    ]
  },
  { 
    path: '/admin', 
    name: 'Admin', 
    component: () => import('@/views/Admin.vue'),
    meta: { requiresAuth: true, requiresAdmin: true }
  },
  { path: '/:pathMatch(.*)*', name: 'NotFound', component: () => import('@/views/NotFound.vue') }
];

const router = createRouter({
  history: createWebHistory(),
  routes
});

router.beforeEach((to, from, next) => {
  const authStore = useAuthStore();
  const isAuthenticated = !!authStore.user;
  const isAdmin = authStore.user?.role === 'admin';

  if (to.meta.requiresAuth && !isAuthenticated) {
    next('/login');
  } else if (to.meta.requiresAdmin && !isAdmin) {
    next('/dashboard');
  } else {
    next();
  }
});

export default router;

3. 导航组件

动态显示导航项:

<!-- src/components/Nav.vue -->
<template>
  <nav>
    <router-link to="/">首页</router-link>
    <router-link v-if="!isAuthenticated" to="/login">登录</router-link>
    <template v-else>
      <router-link to="/dashboard">仪表盘</router-link>
      <router-link to="/dashboard/settings">设置</router-link>
      <router-link v-if="isAdmin" to="/admin">管理</router-link>
      <button @click="logout">退出</button>
    </template>
  </nav>
</template>

<script>
import { useAuthStore } from '@/stores/auth';
import { useRouter } from 'vue-router';
import { computed } from 'vue';

export default {
  setup() {
    const authStore = useAuthStore();
    const router = useRouter();

    const isAuthenticated = computed(() => !!authStore.user);
    const isAdmin = computed(() => authStore.user?.role === 'admin');

    const logout = () => {
      authStore.logout();
      router.push('/login');
    };

    return { isAuthenticated, isAdmin, logout };
  }
};
</script>

<style scoped>
nav { margin: 10px; }
nav a, nav button { margin-right: 10px; }
</style>

4. 页面组件

App.vue

<template>
  <div>
    <Nav />
    <Suspense>
      <template #default>
        <router-view />
      </template>
      <template #fallback>
        <p>加载中...</p>
      </template>
    </Suspense>
  </div>
</template>

<script>
import Nav from './components/Nav.vue';

export default {
  components: { Nav }
};
</script>

Home.vue

<template>
  <h1>欢迎来到首页</h1>
</template>

Login.vue

<template>
  <div>
    <h1>登录</h1>
    <input v-model="username" placeholder="用户名" />
    <input v-model="password" type="password" placeholder="密码" />
    <button @click="login">登录</button>
  </div>
</template>

<script>
import { useAuthStore } from '@/stores/auth';
import { useRouter } from 'vue-router';
import { ref } from 'vue';

export default {
  setup() {
    const authStore = useAuthStore();
    const router = useRouter();
    const username = ref('');
    const password = ref('');

    const login = () => {
      authStore.login({ username: username.value, password: password.value });
      if (authStore.user) router.push('/dashboard');
    };

    return { username, password, login };
  }
};
</script>

Dashboard.vue

<template>
  <div>
    <h1>仪表盘</h1>
    <router-view />
  </div>
</template>

Overview.vue

<template>
  <p>概览页面 - 欢迎,{{ user.name }}</p>
</template>

<script>
import { useAuthStore } from '@/stores/auth';

export default {
  setup() {
    const authStore = useAuthStore();
    return { user: authStore.user };
  }
};
</script>

Settings.vue

<template>
  <p>设置页面</p>
</template>

Admin.vue

<template>
  <h1>管理面板 - 仅管理员可见</h1>
</template>

NotFound.vue

<template>
  <h1>404 - 页面未找到</h1>
</template>

功能亮点

  1. 权限控制:
    • 全局守卫结合 meta 字段实现分级权限。
  2. 嵌套路由:
    • 仪表盘内子页面通过嵌套路由管理。
  3. 懒加载:
    • 所有页面按需加载,提升性能。
  4. 动态导航:
    • 根据用户状态显示菜单项。
  5. 状态管理:
    • Pinia 统一管理认证状态。

测试与验证

  1. 未登录:
    • 访问 /dashboard 跳转到 /login。
    • 导航只显示“首页”和“登录”。
  2. 普通用户登录:
    • 输入 alice 和 123,跳转到 /dashboard。
    • 可访问 /dashboard/settings,但访问 /admin 跳转到 /dashboard。
  3. 管理员登录:
    • 输入 admin 和 123,可访问所有页面。

扩展与优化

1. 添加加载状态

<!-- src/App.vue -->
<template>
  <div>
    <Nav />
    <Suspense>
      <template #default>
        <router-view />
      </template>
      <template #fallback>
        <div class="loading">加载中...</div>
      </template>
    </Suspense>
  </div>
</template>

<style scoped>
.loading { font-size: 18px; text-align: center; padding: 20px; }
</style>

2. 异步数据预加载

在守卫中加载用户数据:

router.beforeEach(async (to, from, next) => {
  const authStore = useAuthStore();
  if (to.meta.requiresAuth && !authStore.user) {
    await authStore.fetchUser(); // 假设异步方法
    if (!authStore.user) next('/login');
    else next();
  } else {
    next();
  }
});

3. 类型支持

为 Store 添加 TypeScript:

// src/stores/auth.ts
import { defineStore } from 'pinia';
import { ref } from 'vue';

interface User {
  name: string;
  role: 'user' | 'admin';
}

export const useAuthStore = defineStore('auth', () => {
  const user = ref<User | null>(null);

  const login = (credentials: { username: string; password: string }) => {
    // ...
  };

  return { user, login, logout };
});

注意事项

  1. 路由重复:
    • 使用 router.push().catch(() => {}) 处理重复导航错误。
  2. 持久化:
    • 确保 localStorage 与实际认证同步。
  3. 性能:
    • 懒加载减少初始加载,守卫逻辑保持简洁。

总结

通过本案例,你实现了一个带权限控制的多页应用,结合 Vue Router 4 的嵌套路由、懒加载和守卫功能,以及 Pinia 的状态管理,完成了从认证到导航的完整流程。这一实践为你提供了构建复杂应用的模板。本章结束,下一章将探索 Vue 3 的新特性,继续提升你的开发能力!

Last Updated:: 2/24/25, 12:59 PM