若依框架实现管理端与用户端不同页面布局:前后端分离下的多布局方案

发布时间:2026/10/2 12:49:42
若依框架实现管理端与用户端不同页面布局:前后端分离下的多布局方案 很多同学拿到若依框架之后第一件事就是折腾页面布局。若依框架的默认管理端布局侧边栏、顶栏、面包屑、标签页一应俱全拿来直接做后台确实顺手但真正要把管理端和用户端放在同一套系统里时问题就来了——用户端要的是清爽的首页、产品展示、登录注册、个人中心和后台那套密密麻麻的菜单完全是两个世界。这篇文章就围绕“若依框架实现管理端与用户端的不同页面布局”这个主题把我实际踩坑、拆解、落地的过程完整写出来给你一套可以直接参考的方案。先交代一下背景。我接触的大多数若依项目都是前后端分离版本RuoYi-Vue前端 Vue 2 Element UI后端 Spring Boot。默认情况下无论你访问哪个页面都会被套进同一个 Layout 布局里面菜单、导航、标签页全都存在。这个结构对管理端很舒适但对用户端而言体验是灾难级的。所以这篇文章的核心就是教你如何在同一个若依工程里让管理端继续用原来的后台布局用户端走一条完全不同的布局路线互不干扰。1. 先想清楚为什么管理端和用户端布局必须拆开很多新手拿到需求后会有一个天真的想法用户端页面就放在views目录下写几个组件不就行了吗布局嘛顶多加个隐藏菜单的开关。结果做着做着就发现全局路由守卫、动态菜单、权限控制、token 存储全都在为管理端服务用户端页面被裹挟在中间越改越乱。1.1 若依默认布局到底做了什么要理解为什么必须拆先得看若依前端默认布局的组成。RuoYi-Vue 的路由结构里几乎所有页面都被挂在一个叫Layout的组件下面。这个Layout组件内部包含了四大部分Sidebar左侧菜单栏根据后端返回的动态菜单渲染支持折叠、高亮、面包屑联动。Navbar顶栏包含折叠按钮、面包屑、用户头像下拉菜单。TagsView标签页导航打开过的页面会生成标签方便来回切换。AppMain内容区域核心的router-view所有页面内容渲染在这里。你打开任意一个管理端页面背后都是这套结构在支撑。而动态菜单是通过登录后调用GetInfo接口拿到用户角色和权限再根据路由表匹配生成的。这整套机制的默认假设是访问者是后台管理员需要使用菜单导航来完成各类管理操作。1.2 用户端直接套后台布局会踩哪些坑如果直接把用户端页面放进Layout下面你会遇到几个很现实的问题。第一用户端页面会继承管理端的侧边菜单。访客或普通用户打开用户首页侧边栏却挂着“系统管理”“用户管理”“日志监控”这些菜单既暴露后台结构又毫无意义。第二TagsView 和面包屑会显得很怪异。用户端往往是扁平化的页面跳转用户从一个产品页跳到详情页压根不需要标签页堆积面包屑对 C 端用户也没有价值。第三权限守卫逻辑对上不对下。若依的登录态、token、动态路由加载都是为管理端账号设计的。用户端如果也走这一套那用户端用户就必须要有一个管理端账号这显然不符合常理。第四前端工程越来越臃肿。用户端页面也在views里管理端动态路由也在views里两者混在一起后续维护时容易误改权限也容易出漏洞。1.3 两种前端形态单工程双布局与双工程独立部署既然默认布局不适合用户端那就有两条路可以走。一条路是单工程双布局也就是在同一个 Vue 工程里新增一套用户端 Layout管理端继续用原来的 Layout两者通过路由的顶级节点区分开。优点是代码复用度高公共组件、请求封装、工具函数都能共享开发和维护成本相对低。缺点是前端工程会变大构建时间变长而且两端的依赖版本如果冲突需要额外处理。另一条路是双工程独立部署也就是管理端就是一个若依原生精简工程用户端单独写一个全新的前端项目比如 Vue 3 Vite通过后端接口统一和若依后端通信。优点是两端完全解耦技术栈都可以不同团队可以并行开发。缺点是部署成本高需要两套 Node 构建、两套 Nginx 配置联调时跨域、token 传递都得重新设计。从我实际接过的项目来看中小型项目用单工程双布局是最划算的。团队里如果只有两三个前端维护一个工程总比维护两个工程省心。这篇文章后面讲的核心实现也是单工程双布局的方案。2. 方案选型我最终选定的实现路径方案确定了接下来就是动手落地。但动手之前必须把若依前端路由和布局的关系搞清楚否则你会在动态路由和静态路由之间来回踩坑。2.1 路由与布局的对应关系很多人搞反了Vue Router 里的component决定了页面套在哪个布局里。假如你给一个路由配置了component: Layout那么访问这个路由时页面内容会渲染在Layout的router-view里面。反之如果不配置 Layout而是直接指定一个独立组件那这个页面就不受 Layout 控制。若依的动态菜单渲染逻辑是这样的登录成功后后端返回菜单列表前端通过loadView函数把菜单对应的component字符串动态转换为组件然后统一挂到 Layout 下面。也就是说管理端所有动态路由的顶级组件都是 Layout。用户端要做成不同布局最简单的做法就是新建一条不经过 Layout 的顶级路由页面的顶级组件是用户端的自定义 Layout 组件。这样用户端页面和管理端页面就在路由层面彻底分了家。2.2 核心思路给用户端单独建一套Layout我最终选定的方案是在src目录下新建layout/user目录里面写一个UserLayout.vue再配一个用户端 Header 和 Footer。用户端的页面路由全部挂在这个 UserLayout 下面。src ├── layout │ ├── index.vue # 管理端默认布局 │ └── user │ └── UserLayout.vue # 用户端独立布局 ├── views │ ├── system # 管理端页面 │ ├── user # 用户端页面 │ │ ├── home.vue │ │ ├── list.vue │ │ └── profile.vue这样做的核心好处是路由组织清晰用户端页面在文件结构上和管理端页面完全隔离后面加权限、加接口都方便。而且用户端 Layout 自己管理自己的 Header 和 Footer不会受到管理端菜单逻辑影响。2.3 为什么不直接改默认Layout有人会说直接在默认 Layout 上加一个v-if判断当前路由前缀是/user就隐藏菜单和标签页不就行了这个思路听起来省事但实际操作下来问题很多。首先默认 Layout 里包含了大量管理端特有的逻辑比如sidebar菜单从store/modules/app和permission读取navbar里绑定用户头像和退出登录逻辑。在这些逻辑之上叠加用户端判断代码会变得非常脆弱。其次管理端菜单数据是通过GetInfo接口动态塞进去的用户端如果也需要登录那打开用户端页面时也会触发这套动态菜单加载容易导致用户端页面误渲染管理端菜单。第三布局组件肩膀上的责任越多后面做样式调整、加响应式适配就越容易误伤。所以我的建议是多花半小时建一个独立的 UserLayout比在默认 Layout 里打补丁要干净得多。这不是代码量的问题而是架构边界的问题。2.4 配套改动清单整个方案拆解下来需要动的地方包括模块改动项说明前端用户端 Layout 组件新建 UserLayout包含 Header/Footer/内容区前端路由配置新增/user顶级路由挂 UserLayout前端登录页与鉴权守卫区分管理端和用户端登录、token 存储前端请求封装用户端接口使用独立的 baseURL 和 token 读取逻辑后端用户端接口新增/app前缀接口独立 Controller后端安全配置/app/**的鉴权规则调整部署Nginx如果用户端是单页面应用确保 history 路由回退这七项改动看似多但实际都不复杂关键在于理解每一项改动的目的。3. 具体实现从建文件到跑通页面的完整过程下面进入正题我把每一步的操作过程写出来附带代码和说明。这里的代码是基于我实际项目里的一个精简版本你拿到自己项目里可以按需调整。3.1 第一步新建用户端布局组件在src/layout/user目录下创建UserLayout.vue内容大致如下template div classuser-layout header classuser-header div classlogo我的产品/div nav classnav router-link to/user/home首页/router-link router-link to/user/list产品列表/router-link router-link to/user/about关于我们/router-link /nav div classuser-info template v-ifuserToken router-link to/user/profile个人中心/router-link a clicklogout退出/a /template template v-else router-link to/user/login登录/router-link router-link to/user/register注册/router-link /template /div /header main classuser-main router-view / /main footer classuser-footer pCopyright © 2024 某某产品团队/p /footer /div /template script import { getToken, removeToken } from /utils/auth export default { name: UserLayout, computed: { userToken() { return getToken() } }, methods: { logout() { removeToken() this.$router.push(/user/login) } } } /script style scoped .user-layout { min-height: 100vh; display: flex; flex-direction: column; } .user-header { height: 60px; display: flex; align-items: center; justify-content: space-between; padding: 0 24px; background: #fff; box-shadow: 0 2px 8px rgba(0, 0, 0, .08); } .user-main { flex: 1; width: 1200px; max-width: 100%; margin: 0 auto; padding: 24px 16px; box-sizing: border-box; } .user-footer { text-align: center; padding: 24px 0; color: #888; } /style这个组件里最关键的是router-view /这一行它负责渲染当前路由对应的页面内容。Header 里的菜单项直接用router-link写死不依赖管理端的动态菜单这样用户端就彻底摆脱了后端菜单数据的控制。注意我没有在组件里引入任何 element 组件而是用了纯 CSS 搭建的简单布局。如果你希望用户端用 Element UI 的样式可以自己调整但要注意按需引入避免把整个 Element UI 全量打包进去。3.2 第二步改造前端路由配置路由配置是这次改造的核心。打开src/router/index.js在constantRoutes里新增用户端顶级路由。import Layout from /layout/index.vue import UserLayout from /layout/user/UserLayout.vue const constantRoutes [ { path: /, component: Layout, redirect: /index, children: [ { path: index, component: () import(/views/index.vue), name: Index, meta: { title: 首页, icon: dashboard, affix: true } } ] }, { path: /user, component: UserLayout, redirect: /user/home, children: [ { path: home, component: () import(/views/user/home.vue), name: UserHome, meta: { title: 用户首页 } }, { path: list, component: () import(/views/user/list.vue), name: UserList, meta: { title: 产品列表 } }, { path: profile, component: () import(/views/user/profile.vue), name: UserProfile, meta: { title: 个人中心 } }, { path: login, component: () import(/views/user/login.vue), name: UserLogin, meta: { title: 用户登录 } } ] } ]这里有几个细节需要注意。第一UserLayout.vue必须使用/layout/user/UserLayout.vue的路径不要写成相对路径否则后面动态路由处理时容易混乱。第二用户端的子路由路径不要带/写成home、list这样才能正确匹配为/user/home、/user/list。第三如果用户端有条件需要公开访问不需要登录可以在meta里标记一个自定义字段比如public: true后面在路由守卫里判断用。这一步不是必须的但提前留好扩展点后面加功能会省事很多。如果只是新增静态路由管理端和用户端的页面都能访问了但是距离真正能用还差一步登录态和权限守卫。3.3 第三步调整登录态与权限守卫若依默认的权限守卫逻辑在src/permission.js里核心判断是有 token 就放行没有 token 就跳转登录页。这套逻辑管理端能用但用户端需要做区分。我的做法是给用户端单独做一个登录页并且用户端登录后不使用管理端的 token 键名而是单独存一份。修改src/utils/auth.js增加一个独立的用户 token 读写函数const UserTokenKey User-Token export function getUserToken() { return Cookies.get(UserTokenKey) } export function setUserToken(token) { return Cookies.set(UserTokenKey, token) } export function removeUserToken() { return Cookies.remove(UserTokenKey) }然后改造src/permission.js在跳转路由前加一段判断const userWhiteList [/user/login, /user/register] router.beforeEach(async (to, from, next) { const isUserPath to.path.startsWith(/user) if (isUserPath) { // 用户端路径走用户端鉴权逻辑 const userToken getUserToken() if (userToken) { next() } else { if (userWhiteList.includes(to.path)) { next() } else { next(/user/login?redirect${to.path}) } } return } // 管理端保持原有逻辑 const token getToken() if (token) { next() } else { ... } })这样一来访问/user开头的页面时会走用户端自己的 token 判断访问管理端页面时走原来的判断两边互不干扰。注意这里的return一定不能漏否则当用户端分支处理完控制流还会继续往下走触发管理端的鉴权逻辑导致跳转异常。用户端登录页的逻辑也单独写不再复用管理端的views/login.vue。登录接口指向后端新增的用户端登录接口登录成功后把返回的 token 用setUserToken存起来然后跳转到/user/home。这里有个实际项目中常见的坑管理端 token 和用户端 token 如果都叫token同时存在时会互相覆盖。所以我用不同的键名Admin-Token和User-Token区分。这个看起来只是命名差异但能省去后面大量的排查时间。3.4 第四步后端接口按端拆分前端改完了后端也不能闲着。后端如果只有一套基于sys_user的登录逻辑用户端仍然无法独立运作。我这里假设用户端需要独立的用户体系所以新增一套/app开头的接口。后端新增AppAuthController.java专门处理用户端的登录和注册RestController RequestMapping(/app/auth) public class AppAuthController { Autowired private AppUserService appUserService; PostMapping(/login) public AjaxResult login(RequestBody LoginBody loginBody) { // 校验用户端账号密码 AppUser user appUserService.login(loginBody.getUsername(), loginBody.getPassword()); if (user null) { return AjaxResult.error(用户名或密码错误); } // 生成用户端 token String token createAppToken(user.getId()); return AjaxResult.success().put(token, token); } }如果不想引入独立用户体系也可以复用sys_user表那连登录接口都不用新增直接用若依的/login接口即可。但需要注意的是若依登录接口会返回管理端的权限信息用户端拿到这些信息没意义反而可能在后续请求管理端接口时权限过放所以从安全角度我还是建议用户端接口独立。安全配置也需要调整。在SecurityConfig里放行用户端公开接口同时保证已登录用户端用户可以访问需要鉴权的接口。Override protected void configure(HttpSecurity httpSecurity) throws Exception { httpSecurity .authorizeRequests() .antMatchers(/login, /register).permitAll() .antMatchers(/app/auth/**).permitAll() .antMatchers(/app/**).authenticated() .anyRequest().authenticated() ... }这样处理后/app/auth/login、/app/auth/register无需登录就能访问/app/user/**等业务接口需要用户端 token 才能访问。管理端原有的/system/**、/monitor/**等接口走原来的鉴权逻辑。用户端业务 Controller 统一加/app前缀例如RestController RequestMapping(/app/user) public class AppUserController { ... }这样做的好处是前端请求路径变得非常清晰所有用户端接口都以/app开头代理和后端过滤器都可以针对这个前缀做统一处理。3.5 第五步联调与效果验证前后端改完之后需要验证几个关键路径未登录访问/user/home应该跳转到/user/login。用户端登录成功后停留在用户端页面不出现管理端菜单。用户端登录后访问管理端/index应该提示未授权或跳转管理端登录。管理端登录后访问用户端页面两端 token 不冲突。管理端菜单和用户端 Header 的选中状态互不影响。开发环境记得在vue.config.js里配置代理如果你后端接口是/app前缀代理要确保转发到 Spring Boot。devServer: { proxy: { /app: { target: http://localhost:8080, changeOrigin: true }, /dev-api: { target: http://localhost:8080, changeOrigin: true } } }验证通过后用户端布局基本就跑通了。接下来我会把这些年实际踩过的问题整理成速查表方便你遇到问题时快速定位。4. 常见问题与排查技巧实录这部分是我在实际项目中反复遇到并解决过的问题每个问题都对应具体的表现和排查思路建议收藏。4.1 进入用户端后管理菜单冒出来或者直接404表现用户端页面能打开但左侧出现了管理端菜单或者用户端路由地址正确但刷新后 404。第一个问题是动态路由注入导致的。若依前端在登录后会调用GetInfo把后端返回的菜单通过store.dispatch(GenerateRoutes)动态挂载到路由表。用户端登录如果也走了这个流程动态菜单自然会出现在用户端页面。解决方法是用户端登录后不要触发GetInfo或者触发但不调用GenerateRoutes只获取用户基本信息。第二个问题是路由模式导致的。若依前端默认使用createWebHashHistory也就是 hash 模式URL 里带#这种模式下刷新不会 404。如果你改成了createWebHistory也就是 history 模式刷新/user/home时开发服务器会去找这个路径对应的静态资源找不到就 404。开发环境需要在 devServer 里配置historyApiFallback: true生产环境需要 Nginx 配置try_files。4.2 两边登录态互相顶掉表现管理端登录后打开用户端发现用户端也是已登录状态或者用户端登录后管理端被踢下线。这个问题的根因是 token 存储键名冲突。如果两端的 token 都存成同一个 key你在这边登录一次那边的 token 就被覆盖了。我在前面特意强调用两个不同的 key并不是小题大做这个坑我至少见到五个项目踩过。排查时可以打开浏览器控制台在 Application 的 Cookies 或 LocalStorage 里看当前存了哪些 key确认Admin-Token和User-Token是否各司其职。如果用的是 LocalStorage注意键名区分同样适用。4.3 部署后刷新404history路由的坑部署到测试环境后经常遇到用户访问/user/home时刷新Nginx 返回 404。这个问题和管理端页面一样根源在于前端使用 history 模式后Nginx 没有做路径回退。解决方法是在 Nginx 配置里加上location / { try_files $uri $uri/ /index.html; }如果你把用户端独立部署在子路径比如/app那 location 匹配要更精确location /app/ { alias /opt/ruoyi/dist/; try_files $uri $uri/ /app/index.html; }部署这块容易想当然我建议上线前先在测试环境模拟一次刷新别等客户反馈了再去查。4.4 IDEA导入若依Vue项目报模块错误这个和页面布局关系不大但很多人在用 IDA 打开若依工程时会遇到error adding module to project: null导致前端项目无法正常识别。通常发生在把 RuoYi-Vue 的前端目录用 IDEA 打开时因为 IDEA 默认把它当成 Java 项目处理node_modules 索引又很重就会出现这类弹窗。解决办法有两种。一是前端目录用 VS Code 打开别用 IDEA这是最省心的方式。二是如果你一定要用 IDEA可以打开File - Project Structure - Modules把前端相关目录标记为 Excluded或者直接删除.idea里的modules.xml后重新导入。还有一种常见原因是你从 Git 拉取若依分支后本地模块文件和远程不一致关闭 IDEA 删掉.idea和*.iml文件重新用 Maven 导入后端模块即可。4.5 分支混乱的Git操作提醒若依官方仓库分支很多有前后端分离版、单体版、微服务版、APP 版等。如果你在 IDEA 里从 Git 拉取了某个分支后面又想换成主分支最容易出现的情况是本地代码和远程分支不一致推不上去。我建议拉代码前先确认当前分支。如果已经改了一堆代码可以先git stash暂存再切分支避免代码直接冲突。推送时看清楚当前分支和远程分支避免把次要分支推到主分支。这虽然不是布局问题但我在折腾若依时确实被分支问题耽误过时间。5. 最后提醒几句这套方案我在多个若依项目里用过整体稳定。但有几个点还是想额外强调一下。第一用户端 Layout 的样式尽量独立别引用管理端全局样式里的sidebar、navbar相关类名否则后期升级若依版本时容易莫名串样式。第二用户端路由建议走静态路由不要尝试做成动态菜单因为用户端页面通常不多静态路由更直观排查问题也方便。第三如果项目里需要用户端也能访问管理端某些接口后端接口的权限校验一定不能放松用户端 token 只能访问自己域下的接口。最后再分享一个我自己的习惯在src/views/user目录下增加一个README.md把这个目录的业务规范写清楚比如用户端页面只能通过/user前缀路由访问、用户端接口统一走/app前缀等等。项目成员多了之后这份文档能省去很多口头沟通成本。若依框架的扩展性不差只要你把路由边界和权限边界画清楚管理端和用户端两套布局就能长期和平共存。