后端使用 AI 开发前端速成:第七期:路由、权限与页面骨架
第七期路由、权限与页面骨架本期目标掌握管理后台的骨架搭建——侧边栏、路由、登录、权限核心理念页面骨架是管理后台的基础设施和用户列表页一样重要目录第一章管理后台骨架结构第二章Vue Router / React Router第三章登录页开发第四章路由守卫与权限控制第五章布局组件——侧边栏 顶部栏第六章Axios 封装实战第七章课后作业第一章管理后台骨架结构1.1 典型骨架布局┌─────────────────────────────────────┐ │ Logo 顶部导航栏面包屑、用户 │ ← Header ├──────────┬──────────────────────────┤ │ │ │ │ 侧边栏 │ 内容区域路由页面 │ ← Layout │ 菜单 │ │ │ │ │ │ │ │ ├──────────┴──────────────────────────┤ │ Footer │ └─────────────────────────────────────┘1.2 骨架必备元素元素用途技术实现侧边栏菜单页面导航根据路由配置生成顶部栏面包屑、用户信息、退出独立组件内容区路由页面渲染router-view/Outlet路由系统URL 到页面的映射Vue Router / React Router路由守卫登录拦截、权限校验beforeEach / loaderAxios 封装统一请求处理拦截器第二章Vue Router / React Router2.1 Vue Router 4// router/index.tsimport{createRouter,createWebHistory}fromvue-routerconstroutes[{path:/login,component:()import(../views/Login.vue),meta:{public:true}// 公开页面不需要登录},{path:/,component:()import(../layouts/MainLayout.vue),redirect:/dashboard,children:[{path:dashboard,component:()import(../views/Dashboard.vue),meta:{title:首页,icon:HomeFilled}},{path:users,component:()import(../views/UserList.vue),meta:{title:用户管理,icon:UserFilled}},{path:users/:id/edit,component:()import(../views/UserEdit.vue),meta:{title:编辑用户,hidden:true}// 不在菜单显示}]},{path:/:pathMatch(.*)*,redirect:/404}]constroutercreateRouter({history:createWebHistory(),routes})exportdefaultrouter2.2 React Router 6// router/index.tsx import { createBrowserRouter, RouterProvider } from react-router-dom const router createBrowserRouter([ { path: /login, element: Login /, meta: { public: true } }, { path: /, element: MainLayout /, children: [ { path: , element: Navigate to/dashboard / }, { path: dashboard, element: Dashboard /, meta: { title: 首页, icon: HomeOutlined } }, { path: users, element: UserList /, meta: { title: 用户管理, icon: UserOutlined } } ] }, { path: *, element: Navigate to/404 / } ]) export default router第三章登录页开发3.1 Vue 登录页template div classlogin-page el-card classlogin-card h2管理后台/h2 el-form refformRef :modelform :rulesrules keyup.enterhandleLogin el-form-item propusername el-input v-modelform.username placeholder用户名 prefix-iconUser / /el-form-item el-form-item proppassword el-input v-modelform.password typepassword placeholder密码 prefix-iconLock show-password / /el-form-item el-form-item el-button typeprimary :loadingloading stylewidth: 100% clickhandleLogin 登录 /el-button /el-form-item /el-form /el-card /div /template script setup langts import { ref, reactive } from vue import { useRouter } from vue-router import { ElMessage } from element-plus import { useUserStore } from /stores/user import request from /utils/request const router useRouter() const userStore useUserStore() const loading ref(false) const formRef ref() const form reactive({ username: , password: }) const rules { username: [{ required: true, message: 请输入用户名 }], password: [{ required: true, message: 请输入密码 }] } const handleLogin async () { await formRef.value?.validate() loading.value true try { const { data } await request.post(/api/login, form) userStore.setToken(data.token) userStore.setUserInfo(data.userInfo) ElMessage.success(登录成功) router.push(/) } catch (error) { ElMessage.error(登录失败) } finally { loading.value false } } /script style scoped .login-page { height: 100vh; display: flex; justify-content: center; align-items: center; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); } .login-card { width: 360px; } /style第四章路由守卫与权限控制4.1 Vue 路由守卫// router/index.tsimport{useUserStore}from/stores/user// 路由守卫类似后端拦截器router.beforeEach((to,from,next){constuserStoreuseUserStore()consttokenuserStore.token// 不需要登录的页面直接放行if(to.meta?.public){next()return}// 未登录跳转登录页if(!token){next(/login)return}// 已登录但不能访问该页面权限校验constrequiredRolesto.meta?.rolesasstring[]if(requiredRoles!requiredRoles.includes(userStore.userInfo?.role)){next(/403)return}next()})4.2 React 路由守卫// 使用 Outlet 条件渲染 import { Navigate, Outlet } from react-router-dom import { useUserStore } from /stores/userStore function AuthGuard() { const { isLoggedIn } useUserStore() return isLoggedIn ? Outlet / : Navigate to/login replace / } function PermissionGuard({ roles }: { roles: string[] }) { const { userInfo } useUserStore() const hasPermission roles.includes(userInfo?.role) return hasPermission ? Outlet / : Navigate to/403 replace / } // 路由配置 { path: /, element: AuthGuard /, // 先检查登录 children: [ { path: admin, element: PermissionGuard roles{[admin]} /, // 再检查权限 children: [ { path: , element: AdminPage / } ] } ] }第五章布局组件——侧边栏 顶部栏5.1 Vue 布局组件template el-container classlayout !-- 侧边栏 -- el-aside width200px classsidebar div classlogo管理后台/div el-menu :default-activeactiveMenu router background-color#001529 text-color#fff active-text-color#409EFF el-menu-item index/dashboard el-iconHomeFilled //el-icon span首页/span /el-menu-item el-menu-item index/users el-iconUserFilled //el-icon span用户管理/span /el-menu-item /el-menu /el-aside el-container !-- 顶部栏 -- el-header classheader breadcrumb / div classuser-info span{{ userStore.userName }}/span el-button typetext clickhandleLogout退出/el-button /div /el-header !-- 内容区 -- el-main classmain router-view / /el-main /el-container /el-container /template script setup import { computed } from vue import { useRoute, useRouter } from vue-router import { useUserStore } from /stores/user const route useRoute() const router useRouter() const userStore useUserStore() const activeMenu computed(() route.path) const handleLogout () { userStore.logout() router.push(/login) } /script5.2 动态菜单// 根据路由配置生成菜单constmenuRoutesrouter.getRoutes().filter(rr.meta?.title!r.meta?.hidden).map(r({path:r.path,title:r.meta.title,icon:r.meta.icon}))第六章Axios 封装实战6.1 请求封装// utils/request.tsimportaxiosfromaxiosimport{ElMessage}fromelement-plusimport{useUserStore}from/stores/userimportrouterfrom/routerconstrequestaxios.create({baseURL:import.meta.env.VITE_API_BASE_URL||/api,timeout:10000})// 请求拦截器request.interceptors.request.use((config){consttokenuseUserStore().tokenif(token){config.headers.AuthorizationBearer${token}}returnconfig},(error)Promise.reject(error))// 响应拦截器request.interceptors.response.use((response){const{code,data,message}response.dataif(code200){returndata}ElMessage.error(message||请求失败)returnPromise.reject(newError(message))},(error){if(error.response?.status401){useUserStore().logout()router.push(/login)ElMessage.error(登录已过期)}else{ElMessage.error(error.response?.data?.message||网络错误)}returnPromise.reject(error)})exportdefaultrequest6.2 React 版本// utils/request.tsimportaxiosfromaxiosimport{message}fromantdimport{useUserStore}from/stores/userStoreconstrequestaxios.create({baseURL:import.meta.env.VITE_API_BASE_URL,timeout:10000})request.interceptors.request.use((config){consttokenuseUserStore.getState().tokenif(token){config.headers.AuthorizationBearer${token}}returnconfig})request.interceptors.response.use((response){const{code,data,message:msg}response.dataif(code200)returndata message.error(msg||请求失败)returnPromise.reject(newError(msg))},(error){if(error.response?.status401){useUserStore.getState().logout()window.location.href/loginmessage.error(登录已过期)}else{message.error(error.response?.data?.message||网络错误)}returnPromise.reject(error)})exportdefaultrequest第七章实战7.1 必做实战实战 1搭建完整的管理后台骨架创建一个包含以下功能的骨架项目登录页用户名 密码路由系统首页、用户管理、404路由守卫未登录拦截布局组件侧边栏 顶部栏Axios 封装自动加 Token 401 处理实战 2添加权限控制在骨架基础上增加角色字段admin / user某些菜单只有 admin 能看到某些操作按钮按角色显示/隐藏7.2 FAQQ路由守卫和后端拦截器有什么区别路由守卫是前端的客户端校验可以被绕过直接访问 URL。后端拦截器才是真正的安全防线。QToken 过期了怎么处理在 Axios 响应拦截器中捕获 401清除本地 Token跳转到登录页。不需要刷新 Token 的复杂逻辑简单的管理后台。下一期预告对接真实后端接口 —— 打通前后端联调全流程