Day 2|项目目录与多布局、路由与权限守卫:从结构到落地代码
1. 目标与产出明确中后台项目目录分层与职责边界。落地多布局主布局/业务布局沉淀可复用容器组件。设计路由与权限模型角色/权限点实现路由守卫与菜单联动。产出可复用的 Pinia 权限状态、权限指令、路由守卫样例代码。2. 推荐目录结构按职责分层src/ ├─ assets/ # 静态资源icons/images/styles ├─ common/ # 通用能力hooks、utils、config ├─ components/ # 无业务耦合的通用组件 ├─ layouts/ # 多布局容器MainLayout、LeaseLayout ├─ pages/ # 业务页面按域划分 ├─ router/ # 路由声明与守卫 ├─ store/ # Pinia 状态auth、app、dict 等 ├─ directives/ # 自定义指令如权限指令 ├─ api/ # 业务 API 建议与 types 配合 ├─ types/ # 全局与生成的类型scripts/generator-api.js 输出 └─ main.ts / App.vue要点单一职责布局只做“框架与占位”页面承载业务逻辑。可复用通用组件/指令不依赖具体业务api/types 保持清晰边界。3. 多布局设计MainLayout / LeaseLayoutMainLayout系统主框架顶部导航、侧边菜单、面包屑、内容区。LeaseLayout示例业务布局自定义顶部/工具栏/标签页等。示例src/layouts/MainLayout.vuescript setup langts // 可注入全局头部、侧边栏、面包屑等 /script template div classlayout-main Header / div classlayout-body Sidebar / main classlayout-contentrouter-view //main /div /div /template style scoped .layout-main { display: flex; flex-direction: column; height: 100vh; } .layout-body { display: flex; flex: 1; min-height: 0; } .layout-content { flex: 1; padding: 16px; overflow: auto; } /style页面基于路由的meta.layout选择布局容器或在路由树中作为父级嵌套。多布局渲染关系图mainlease路由记录meta.layoutmain/lease选择布局MainLayoutLeaseLayout渲染页面说明通过meta.layout指定父级布局路由子路由在对应布局的router-view/中渲染。4. 路由设计与命名规范路由命名统一moduleFeatureAction如userList、roleEdit。懒加载组件按需异步加载减少首屏体积。元信息通过meta.roles、meta.perms、meta.layout指定访问控制与布局。示例src/router/routes.tsimporttype{RouteRecordRaw}fromvue-routerexportconstroutes:RouteRecordRaw[][{path:/,name:root,redirect:/dashboard,component:()import(/layouts/MainLayout.vue),meta:{layout:main,requiresAuth:true},children:[{path:dashboard,name:dashboard,component:()import(/pages/dashboard/index.vue),meta:{title:仪表盘,icon:home,roles:[admin,ops]},},{path:user,name:user,redirect:{name:userList},meta:{title:用户管理,icon:user,roles:[admin]},children:[{path:list,name:userList,component:()import(/pages/user/list.vue),meta:{title:用户列表,perms:[user:read]},},{path:edit/:id,name:userEdit,component:()import(/pages/user/edit.vue),meta:{title:编辑用户,perms:[user:write],hidden:true},},],},],},{path:/lease,name:leaseRoot,component:()import(/layouts/LeaseLayout.vue),meta:{layout:lease,requiresAuth:true},children:[{path:assets,name:leaseAssets,component:()import(/pages/lease/assets.vue),meta:{title:资产管理,perms:[lease:asset:read]},},],},{path:/login,name:login,component:()import(/pages/login/index.vue),meta:{public:true}},{path:/:pathMatch(.*)*,name:notFound,component:()import(/pages/_sys/404.vue),meta:{public:true}},]5. 权限模型与状态建议统一权限模型roles: 角色集合admin、ops、user…。perms: 权限点如 user:read、user:write。示例src/store/auth.tsimport{defineStore}frompiniainterfaceUserInfo{id:string;name:string;roles:string[];perms:string[]}interfaceAuthState{token:string|null;user:UserInfo|null}exportconstuseAuthStoredefineStore(auth,{state:():AuthState({token:null,user:null}),persist:true,// pinia-plugin-persistedstate 建议已在项目中配置actions:{setToken(token:string){this.tokentoken},setUser(user:UserInfo){this.useruser},logout(){this.tokennull;this.usernull},hasRole(role:string){return!!this.user?.roles?.includes(role)},hasPerm(perm:string){return!!this.user?.perms?.includes(perm)},},})6. 路由守卫登录态 角色/权限校验 动态路由示例src/router/index.tsimport{createRouter,createWebHistory}fromvue-routerimporttype{RouteRecordRaw}fromvue-routerimport{useAuthStore}from/store/authimport{routes}from./routesconstroutercreateRouter({history:createWebHistory(),routes:routesasRouteRecordRaw[]})router.beforeEach(async(to){constauthuseAuthStore()// 允许匿名访问if(to.meta.public)returntrue// 登录校验if(to.meta.requiresAuth!false!auth.token){return{name:login,query:{redirect:to.fullPath}}}// 角色/权限校验可按需放宽为“有任意其一即可”constneedRoles(to.meta.rolesasstring[])||[]constneedPerms(to.meta.permsasstring[])||[]if(needRoles.length!needRoles.some(rauth.user?.roles?.includes(r))){return{name:notFound}// 或专门的 403 页面}if(needPerms.length!needPerms.every(pauth.user?.perms?.includes(p))){return{name:notFound}}returntrue})exportdefaultrouter路由与权限守卫流程图是否否是不满足满足不满足满足导航开始to.meta.public?直接放行已登录? token跳转 login 并携带 redirect校验角色 roles跳转 403/404校验权限 perms放行说明先放行公开路由再校验登录态通过meta.roles与meta.perms控制访问失败时跳转到 403/404 或登录页。7. 权限指令v-perm / v-role与菜单渲染指令让“按钮级权限”更自然v-permuser:write。菜单根据路由元信息动态生成过滤hidden与无权限项。指令示例src/directives/permission.tsimporttype{Directive}fromvueimport{useAuthStore}from/store/authexportconstvPerm:DirectiveHTMLElement,string|string[]{mounted(el,binding){constauthuseAuthStore()constneedArray.isArray(binding.value)?binding.value:[binding.value]constokneed.every(pauth.user?.perms?.includes(p))if(!ok)el.style.displaynone},}菜单生成示例importtype{RouteRecordRaw}fromvue-routerexportfunctionbuildMenuFromRoutes(routes:RouteRecordRaw[],hasPerm:(p:string)boolean){constloop(rs:RouteRecordRaw[])rs.filter(r!r.meta?.hidden!r.meta?.public).map(r({title:r.meta?.titleasstring,icon:r.meta?.iconasstring,name:r.nameasstring,children:r.children?loop(r.children):[],show:r.meta?.perms?hasPermAll(r.meta?.permsasstring[]):true,})).filter(mm.show)consthasPermAll(perms:string[])perms.every(hasPerm)returnloop(routes)}8. 登录流程与数据流建议与 Day 1 请求层协作使用 Day 1 的requestT与拦截器提供login(payload)与getCurrentUser()接口。登录成功保存token随后并发拉取当前用户信息落盘roles/perms再router.replace(redirect || /)。失败策略友好提示 限制重试 记录异常。示例简化import{useAuthStore}from/store/authimport{request}from/api/httpexportasyncfunctionloginAndBootstrap(payload:{username:string;password:string},redirect?:string){constauthuseAuthStore()consttokenawaitrequeststring({url:/auth/login,method:post,data:payload})auth.setToken(token)constmeawaitrequest{id:string;name:string;roles:string[];perms:string[]}({url:/auth/me,method:get})auth.setUser(me)// router.replace(redirect || /)}9. 测试建议单测 E2E单测权限指令传入不同perms断言元素是否隐藏。路由守卫构造to场景未登录、有/无角色、权限不全断言跳转。E2EPlaywright用测试账号含/不含权限访问菜单断言不可见菜单与按钮确实隐藏。10. 总结与明日预告我们从目录分层、布局容器、路由与权限模型到守卫与指令搭建了“结构化”的中后台基座。明日预告接入“角色管理/权限管理”实战页串联 Day 1 的请求层 Day 2 的守卫/指令提供表格/表单/分页的完整范式与vue-request最佳实践。欢迎评论「你最想要的下一期模块」并留下你的点赞/收藏
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2622799.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!