阶段 8Vue3 根据权限显示菜单和按钮合辑Django 系统阶段开发手册 阶段8 / 18 定位在前端按权限动态渲染菜单与按钮并对接用户管理接口 建议学时6–8 小时大家好我是小马不起床。阶段导语阶段 7 完成了前端登录首页仅做固定的用户与权限展示。本阶段在此基础上把权限数据接入渲染逻辑实现有权限才显示、无权限则隐藏的前端体验控制并对接用户列表接口。本阶段只涉及前端。本阶段目标1. 登录后根据 permissions 显示菜单 2. 根据 permissions 显示按钮 3. 没有页面权限时跳转 403 页面 4. 请求用户列表接口 5. admin 可以新增、删除用户 6. zhangsan 只能查看用户不能新增、不能删除学习目标完成本阶段后应能够复用hasPermission方法对页面、按钮的可见性做权限判定封装用户接口映射user:list、user:add、user:delete三类权限构建 403 页面并在路由层通过meta.permission拦截无权限访问实现用户管理页面按权限条件渲染新增与删除按钮在首页按权限动态显示菜单项区分前端权限渲染与后端权限校验的职责边界前置知识本阶段直接依赖阶段 7阶段 7实现 Vue3 登录.md前置条目具体要求登录状态管理stores/auth.js已提供isLogin、hasPermission与 permissions 数据请求封装utils/request.js会自动携带 access_token 并处理 401路由守卫router/index.js已配置beforeEach本阶段在其上扩展权限判定权限编码约定后端下发 user:list、user:add、user:delete、role:list 等编码目录本阶段文件清单与权限方法复用新增用户接口文件新增 403 页面新增用户管理页面修改路由与配置页面权限修改首页显示菜单启动项目与测试前端权限与安全边界阶段完整流程与完成标准阶段小结常见问题与排查下一阶段衔接1. 本阶段文件清单与权限方法复用1.1 涉及文件本阶段修改或新增的文件frontend/src/api/user.js 新增 frontend/src/views/UserList.vue 新增 frontend/src/views/Forbidden.vue 新增 frontend/src/router/index.js 修改 frontend/src/views/Home.vue 修改1.2 复用权限判断方法阶段 7 的文件frontend/src/stores/auth.js中已存在该方法hasPermission:state{returnpermissionCode{returnstate.permissions.includes(permissionCode)}}其作用是判断当前用户是否拥有某个权限调用形式authStore.hasPermission(user:list)authStore.hasPermission(user:add)authStore.hasPermission(user:delete)返回结果为true 或 false因此本阶段直接复用该方法无需重复实现。要点本阶段仅改动前端不触碰后端接口。权限渲染统一依赖已存在的hasPermissiongetter返回布尔值。新增文件集中于接口封装、页面与 403 页面改动文件集中于路由与首页。2. 新增用户接口文件新建文件frontend/src/api/user.js写入importrequestfrom../utils/requestexportfunctiongetUserListApi(){returnrequest.get(/users/)}exportfunctioncreateUserApi(data){returnrequest.post(/users/,data)}exportfunctiondeleteUserApi(userId){returnrequest.delete(/users/${userId}/)}该文件对应后端接口GET /api/users/ 查看用户列表 POST /api/users/ 新增用户 DELETE /api/users/user_id/ 删除用户接口与权限的对应关系GET /api/users/ user:list POST /api/users/ user:add DELETE /api/users/user_id/ user:delete要点每个方法对应一个后端接口路径以/api为基础由 request 实例拼接。接口方法与所需权限一一对应。删除接口以userId拼入路径参数。3. 新增 403 页面新建文件frontend/src/views/Forbidden.vue写入template div classpage h2403/h2 p你没有权限访问这个页面。/p router-link to/ 返回首页 /router-link /div /template style scoped .page { padding: 32px; } h2 { color: #dc2626; } /style该页面用于用户无权限访问某个路由时的提示。要点403 页面为纯展示组件提供返回首页的链接。该页面本身不设权限门槛作为无权限访问的落地页。4. 新增用户管理页面4.1 页面代码新建文件frontend/src/views/UserList.vue写入完整代码template div classpage div classheader div h2用户管理/h2 p当前页面需要权限user:list/p /div router-link to/ 返回首页 /router-link /div div classtoolbar button v-ifauthStore.hasPermission(user:add) clickhandleAddUser 新增用户 /button span v-else classno-permission 当前账号没有 user:add 权限所以不显示新增按钮 /span /div table thead tr th用户ID/th th用户名/th th真实姓名/th th手机号/th th角色/th th状态/th th操作/th /tr /thead tbody tr v-foruser in users :keyuser.user_id td{{ user.user_id }}/td td{{ user.username }}/td td{{ user.real_name || - }}/td td{{ user.phone || - }}/td td{{ user.role_name }}/td td {{ user.status 1 ? 正常 : 禁用 }} /td td button v-ifauthStore.hasPermission(user:delete) classdanger clickhandleDeleteUser(user) 删除 /button span v-else classno-permission 无删除权限 /span /td /tr /tbody /table p v-ifusers.length 0 classempty 暂无用户数据 /p /div /template script setup import { onMounted, ref } from vue import { useAuthStore } from ../stores/auth import { getUserListApi, createUserApi, deleteUserApi } from ../api/user const authStore useAuthStore() const users ref([]) async function loadUsers() { try { const res await getUserListApi() if (res.code 200) { users.value res.data } else { alert(res.message || 获取用户列表失败) } } catch (error) { if (error.response error.response.data) { alert(error.response.data.message || 获取用户列表失败) } else { alert(获取用户列表失败) } } } async function handleAddUser() { const username prompt(请输入用户名) if (!username) { return } const password prompt(请输入密码) if (!password) { return } const realName prompt(请输入真实姓名) if (!realName) { return } try { const res await createUserApi({ username, password, real_name: realName, phone: , role_id: 2, status: 1 }) if (res.code 200) { alert(新增用户成功) await loadUsers() } else { alert(res.message || 新增用户失败) } } catch (error) { if (error.response error.response.data) { alert(error.response.data.message || 新增用户失败) } else { alert(新增用户失败) } } } async function handleDeleteUser(user) { const confirmed confirm(确定删除用户 ${user.username} 吗) if (!confirmed) { return } try { const res await deleteUserApi(user.user_id) if (res.code 200) { alert(删除用户成功) await loadUsers() } else { alert(res.message || 删除用户失败) } } catch (error) { if (error.response error.response.data) { alert(error.response.data.message || 删除用户失败) } else { alert(删除用户失败) } } } onMounted(() { loadUsers() }) /script style scoped .page { padding: 32px; } .header { display: flex; align-items: center; justify-content: space-between; } .toolbar { margin: 20px 0; } button { padding: 8px 14px; cursor: pointer; border: 1px solid #ddd; border-radius: 6px; background: white; } button:hover { background: #f3f4f6; } .danger { color: #dc2626; border-color: #dc2626; } table { width: 100%; border-collapse: collapse; background: white; } th, td { padding: 12px; border: 1px solid #e5e7eb; text-align: left; } th { background: #f9fafb; } .no-permission { color: #999; font-size: 14px; } .empty { color: #999; margin-top: 20px; } /style4.2 页面权限判断说明该页面包含三处权限判断。其一为页面权限决定用户能否进入该页面判定编码为user:list在路由中处理。其二为新增按钮权限button v-ifauthStore.hasPermission(user:add) clickhandleAddUser 新增用户 /button含义为有 user:add 权限显示新增按钮 没有 user:add 权限不显示新增按钮其三为删除按钮权限button v-ifauthStore.hasPermission(user:delete) classdanger clickhandleDeleteUser(user) 删除 /button含义为有 user:delete 权限显示删除按钮 没有 user:delete 权限不显示删除按钮要点页面挂载时通过onMounted调用loadUsers拉取列表。新增与删除按钮分别由user:add、user:delete权限控制可见性。页面准入权限user:list在路由层判定而非组件内部。无权限时以v-else分支显示占位提示保持布局稳定。5. 修改路由与配置页面权限5.1 路由代码打开文件frontend/src/router/index.js替换为完整代码import{createRouter,createWebHistory}fromvue-routerimport{useAuthStore}from../stores/authimportLoginfrom../views/Login.vueimportHomefrom../views/Home.vueimportUserListfrom../views/UserList.vueimportForbiddenfrom../views/Forbidden.vueconstroutes[{path:/login,component:Login},{path:/,component:Home,meta:{requiresAuth:true}},{path:/users,component:UserList,meta:{requiresAuth:true,permission:user:list}},{path:/403,component:Forbidden}]constroutercreateRouter({history:createWebHistory(),routes})router.beforeEach((to,from,next){constauthStoreuseAuthStore()if(to.meta.requiresAuth!authStore.isLogin){next(/login)return}if(to.path/loginauthStore.isLogin){next(/)return}if(to.meta.permission!authStore.hasPermission(to.meta.permission)){next(/403)return}next()})exportdefaultrouter5.2 路由权限说明该路由配置{path:/users,component:UserList,meta:{requiresAuth:true,permission:user:list}}含义为访问/users页面需同时满足两个条件1. 已登录 2. 拥有 user:list 权限若未登录跳转 /login若已登录但缺少权限跳转 /403要点路由守卫在阶段 7 基础上新增一条meta.permission判定分支。/users通过 meta 同时声明登录要求与权限编码。未登录优先重定向/login登录后权限不足才重定向/403。6. 修改首页显示菜单6.1 首页代码打开文件frontend/src/views/Home.vue替换为完整代码template div classpage h2首页/h2 div classcard h3当前登录用户/h3 p 用户名{{ authStore.user?.username }} /p p 真实姓名{{ authStore.user?.real_name }} /p p 当前角色{{ authStore.user?.role_name }} /p /div div classcard h3菜单/h3 div classmenu-list router-link v-ifauthStore.hasPermission(user:list) classmenu-item to/users 用户管理 /router-link span v-ifauthStore.hasPermission(role:list) classmenu-item disabled 角色管理后续阶段再做页面 /span /div p v-if!authStore.hasPermission(user:list) !authStore.hasPermission(role:list) classno-permission 当前用户没有任何菜单权限 /p /div div classcard h3当前权限/h3 ul li v-forpermission in authStore.permissions :keypermission {{ permission }} /li /ul /div button clickhandleLogout 退出登录 /button /div /template script setup import { useRouter } from vue-router import { useAuthStore } from ../stores/auth const router useRouter() const authStore useAuthStore() function handleLogout() { authStore.logout() router.push(/login) } /script style scoped .page { padding: 32px; } .card { padding: 20px; margin-bottom: 20px; border: 1px solid #eee; border-radius: 8px; background: #fff; } .menu-list { display: flex; gap: 12px; } .menu-item { display: inline-block; padding: 10px 16px; border: 1px solid #2563eb; border-radius: 6px; color: #2563eb; text-decoration: none; } .menu-item:hover { background: #eff6ff; } .disabled { border-color: #d1d5db; color: #999; cursor: not-allowed; } .no-permission { color: #999; } button { padding: 10px 18px; cursor: pointer; } /style6.2 菜单权限说明用户管理菜单router-link v-ifauthStore.hasPermission(user:list) classmenu-item to/users 用户管理 /router-link含义为当前用户拥有user:list权限时显示用户管理菜单。角色管理菜单span v-ifauthStore.hasPermission(role:list) classmenu-item disabled 角色管理后续阶段再做页面 /span含义为当前用户拥有role:list权限时显示角色管理菜单。由于角色管理页面尚未在本阶段实现该菜单先以灰色状态呈现。要点菜单项以hasPermission结果决定是否渲染。用户管理菜单为可跳转的router-link角色管理为占位的禁用样式。当两项菜单权限均缺失时展示无菜单权限提示。7. 启动项目与测试7.1 启动后端进入rbac_project/backend执行python manage.py runserver7.2 启动前端新开一个终端进入rbac_project/frontend执行npmrun dev浏览器打开http://localhost:5173/login7.3 测试 admin登录admin / 123456首页应显示用户管理 角色管理后续阶段再做页面进入用户管理页面http://localhost:5173/users应显示新增用户按钮 删除用户按钮原因在于 admin 拥有以下权限user:list user:add user:delete role:list7.4 测试 zhangsan退出登录后改用另一账号登录zhangsan / 123456首页应显示用户管理但不显示角色管理进入用户管理页面后应显示当前账号没有 user:add 权限所以不显示新增按钮每行用户后应显示无删除权限原因在于 zhangsan 仅拥有user:list而不拥有user:add user:delete role:list要点前后端需同时运行后端提供接口前端负责渲染。admin 拥有全部权限菜单与按钮全部可见。zhangsan 仅拥有user:list新增、删除按钮与角色管理菜单均不可见。渲染差异完全由后端下发的 permissions 决定。8. 前端权限与安全边界前端当前的逻辑是有权限就显示按钮 没权限就隐藏按钮这属于用户体验层面的控制真正的安全由后端保证。以 zhangsan 为例即便看不到删除按钮若手动发起请求DELETE /api/users/3/后端仍会判定zhangsan 有没有 user:delete结果不具备该权限后端返回403因此正确的职责结构为前端控制显示 后端控制安全要点前端隐藏按钮仅影响可见性不阻止请求发出。后端独立校验 JWT 与 RBAC 权限构成实际安全边界。无权限的手动请求由后端返回 403与前端渲染无关。9. 阶段完整流程与完成标准9.1 阶段完整流程本阶段完成后的流程用户登录 ↓ 后端返回 permissions ↓ Vue3 保存 permissions ↓ 首页根据 permissions 显示菜单 ↓ 进入用户管理页面 ↓ 页面根据 permissions 显示按钮 ↓ 点击按钮请求后端接口 ↓ 后端再次判断 JWT RBAC 权限 ↓ 有权限执行 ↓ 没权限返回 4039.2 阶段完成标准需逐项确认1. admin 登录后可以看到用户管理菜单 2. admin 登录后可以看到新增用户按钮 3. admin 登录后可以看到删除用户按钮 4. zhangsan 登录后可以看到用户管理菜单 5. zhangsan 登录后看不到新增用户按钮 6. zhangsan 登录后看不到删除用户按钮 7. /users 页面必须登录后才能访问 8. /users 页面需要 user:list 权限要点完整流程贯通登录、菜单渲染、按钮渲染到后端校验。完成标准围绕两个账号的可见性差异与页面准入条件。前端渲染与后端拦截共同构成本阶段的验证闭环。阶段小结环节产出关键文件接口封装用户列表、新增、删除三类接口frontend/src/api/user.js403 页面无权限访问的落地页frontend/src/views/Forbidden.vue用户管理页面按权限渲染的新增、删除按钮与用户表格frontend/src/views/UserList.vue路由权限meta.permission与守卫拦截frontend/src/router/index.js首页菜单按权限动态显示的菜单frontend/src/views/Home.vue安全边界前端控制显示、后端控制安全前后端协同常见问题与排查现象原因处理zhangsan 登录后仍显示新增或删除按钮前端读取的 permissions 与预期不符核对 localStorage 中的 permissionszhangsan 应为[user:list]确认阶段 7 保存的权限数据正确已登录访问/users被跳转至/403路由meta.permission判定的user:list权限缺失确认该用户权限列表包含user:list并检查hasPermission返回值无权限用户手动请求删除接口仍被拒绝返回 403后端独立校验 JWT 与 RBAC 权限属预期行为前端隐藏仅为体验安全由后端保障见第 8 章首页菜单未随权限变化菜单渲染依赖的 permissions 未更新确认登录动作已将最新 permissions 写入 store 与 localStorage下一阶段衔接阶段 8 打通了前端侧的权限渲染菜单、按钮、页面准入均依据 permissions 生效用户管理接口完成对接。阶段 9阶段 9前后端完整联调和问题修复.md将把前后端放在一起完整联调逐项验证 admin 与 zhangsan 的登录、菜单、按钮、接口拦截效果并对 CORS、404、401、403 等常见问题进行集中排查与修复。本阶段建立的前端渲染逻辑是阶段 9 联调验证的直接对象。