☰
若依App版项目结构全解析:从目录到二次开发
2026/10/18 7:15:03 网站建设 项目流程

拿到若依(RuoYi)的App版工程,很多人第一反应是:这不就是RuoYi-Vue的移动端换壳吗?目录结构应该差不多吧。真把工程克隆下来一打开,往往又有点懵——pages.json、manifest.json、uni_modules、条件编译,再加上一堆以#ifdef开头的注释,整个结构和后台管理端完全是两套思路。这篇文章就围绕RuoYiApp版的项目结构做一次完整的拆解,把每个目录的职责、页面之间的跳转逻辑、token在整个应用里的流动路径,以及二次开发时从哪里下手讲透。适合刚入手若依App版准备改业务的人,也适合从后台管理转移动端开发、想快速理解这套工程的人。

1. RuoYiApp版到底是什么:它在若依技术栈里的真实位置

1.1 若依家族的版本划分

若依不是一个单一项目,而是一整套基于Spring Boot的快速开发解决方案。常见的有这么几条线:单体版RuoYi,前后端分离版RuoYi-Vue,微服务版RuoYi-Cloud,以及移动端方向上的RuoYiApp版。这几个版本的后端逻辑大体同源,区别主要在于前端形态和部署方式。

RuoYiApp版并不是一套全新的后台系统,它的真正身份是一个移动端前端工程。后端还是复用若依那套Spring Boot接口,只是把页面载体从浏览器里的vue-element-admin换成了uni-app。换句话说,RuoYiApp版解决的是"若依系统怎么长出移动端"的问题。一套代码编译出来,可以跑成Android App、iOS App、H5网页,也能发布到各种小程序平台。

这个定位非常关键,因为它决定了你看项目结构时的基本心态:不用纠结后端的UserController、RoleService这些类在哪儿,你手里这个工程里根本没有Java代码。你要处理的全部内容,都是View层、状态层和网络请求层。

1.2 技术栈的本质:uni-app + Vue3 + Vite

RuoYiApp版依赖的移动端底座是uni-app。这个框架的核心理念是"一套代码、多端发布",写法上以Vue为中心,页面、组件、路由、状态管理都沿用Vue的思维,但额外引入了一套生命周期和内置API。

近几年的RuoYiApp版本普遍基于Vue3和Vite构建,目录风格和Vue3单页应用很像,但又有自己的调整。比如路由不再是vue-router那套,而是交给pages.json统一声明;全局生命周期也不写在router里,而是放在App.vue的onLaunch、onShow中;状态管理有的版本用Vuex,新版本则逐步转到了Pinia。

从后台管理端转过来的读者,最容易踩的第一个坑,就是把vue-router的路由思维带进来。在RuoYiApp版里,根本没有router目录,也没有路由守卫文件。页面跳转靠的是uni.navigateTo这类API,页面注册靠pages.json。这个差异是后续读代码时的一道坎,理解了之后整个项目结构就顺了。

2. 顶层那几个文件别跳过:启动入口和全局配置的线是怎么串起来的

2.1 顶层目录文件清单

打开RuoYiApp版工程,先不急着进pages,顶层这几个文件反而是整个应用的起点:

RuoYiApp ├── App.vue ├── main.js ├── manifest.json ├── pages.json ├── uni.scss └── pages/ # 业务页面

这几个文件数量不多,但每一个都在干一个不可替代的活。很多人觉得App.vue无非是模板根组件,改两下就算完事,实际线上排查问题的时候,登录失效、启动白屏、页面跳错,最后往往都能追回到这几个文件里。

2.2 App.vue:整个移动端的应用级生命周期

在uni-app里,App.vue承载的是应用级逻辑,对应后台管理端main.js里那套全局初始化的活。常见的做法是在onLaunch里做三件事:读取本地缓存的登录状态、拉取系统配置、判断要跳转到哪个页面。

这里有个天然的顺序问题:onLaunch触发时,页面还没渲染出来,所以这里适合做需要提前完成的初始化;如果业务配置要在页面里用,那就得等异步回调回来后再更新store。很多启动阶段的偶发问题,本质都是初始化过程和页面渲染并行执行导致的竞态。实际开发中,我会把登录状态检查和用户信息拉取拆开,登录状态检查同步读缓存,用户信息拉取异步执行,避免页面一进来数据为空。

2.3 main.js与manifest.json、pages.json的分工

main.js负责创建Vue实例并挂载全局依赖,以Vue3版本为例大致是:

import { createSSRApp } from 'vue' import App from './App.vue' import * as Pinia from 'pinia' export function createApp() { const app = createSSRApp(App) app.use(Pinia.createPinia()) return { app, Pinia } }

这里不需要手动注册路由表,也不需要把每个页面import进来,页面注册统一在pages.json里完成。pages.json实际扮演的是"路由表+页面配置+导航栏配置"的合并角色,每个页面要在这里声明path和style,tabBar页面同样在这里指定。第一次在某个页面里修改了导航栏标题却没生效,多半就是只改了vue文件里的页面数据,忘了同步pages.json里的对应配置。

manifest.json则负责工程级和应用市场级配置:应用名称、版本号、各平台appid、图标、启动图、SDK配置等。代码逻辑整体可以迁移,但打包发版时,manifest.json里的平台标识经常被忽略,导致本地跑得好好的,真机扫码一打开就出现白屏或API不可用,排查半天发现是某个平台模块没勾选。

3. pages目录:移动端页面是怎么按业务模块组织起来的

3.1 业务页面的典型分类方式

RuoYiApp版里页面不是平铺的,而是按业务域分了目录。常见的主模块包括:登录注册、工作台、任务、流程、我的个人中心,以本项目实际页面为准,但目录思路基本都是"一个业务场景收敛到一块"。这样组织的好处很直接:要改登录逻辑,只需要打开pages/login下的相关文件,不会牵扯到业务页面;要删掉一个模块,整个目录一起移除即可。

每个页面在uni-app里是一个vue文件,也可以是一个带子组件的目录。页面的生命周期和普通Vue组件不同,比如onLoad对应页面加载、onShow对应页面每次展示。这和后端管理页面的created、mounted不一样:后台管理里页面一般是常驻的,但移动端页面频繁进栈出栈,很多逻辑必须放在onShow里才能保证每次进入都重新执行刷新。

3.2 跳转、tabBar和导航栏的三个经验点

页面之间跳转用的是uni-app内置API。普通页面用uni.navigateTo,它会保留当前页面并把新页面压入栈;tabBar页面则必须用uni.switchTab,否则会报错。这是个很典型的初学者问题:明明页面存在,跳过去却提示"页面不存在"或直接静默失败,多数时候是把switchTab该用的场景写成了navigateTo。

导航栏有两种选择:一种是原生导航栏,配置在pages.json的style里,性能和系统一致性最好;另一种是自定义导航栏,把pages.json里对应页面的navigationStyle设成custom,然后页面内自己写头部组件。RuoYiApp版里很多页面为了统一风格会走自定义导航栏,这样Android和iOS的状态栏高度、胶囊按钮位置都要自己做适配。如果你在某个页面发现导航栏上面空出一块或者遮住状态栏,优先检查status-bar-height的处理逻辑。

关于下拉刷新和上拉加载,页面配置里开启enablePullDownRefresh后,页面里要监听onPullDownRefresh和onReachBottom。一个容易忽略的细节是:每次下拉刷新收尾必须调用uni.stopPullDownRefresh(),否则刷新圈会一直转,这种问题在真机上很显眼,模拟器里倒是不容易发现。

3.3 页面文件与组件的拆法

RuoYiApp版里页面文件通常保持轻量,复杂交互抽到components里。推荐的标准是:页面内只保留当前页面特有的数据加载和事件编排,下拉选项、卡片、弹窗这类可复用的UI做成组件。这样当业务页面多了以后,改一个公共组件就能全局生效,不用逐个页面去翻。

页面在目录里怎么放也有讲究。单个页面只有业务代码时,直接是一个xxx.vue;如果有专属子组件,则收进一个pages/xxx/目录,再在子目录里放components。从维护角度看,目录方式更好,新增页面体积大起来后不至于让pages目录变成一长串文件堆。

4. api目录与统一请求封装:多端接口调用的关键枢纽

4.1 request.js是整条通信链路的心脏

RuoYiApp版的所有后端通信几乎都收敛在api目录下。它和后台管理端有点类似,但底层不是一个封装好的axios实例,而是一层针对uni.request的统一封装,核心文件一般是utils/request.js或者api/request.js。

统一请求封装要做的事情比较集中:拼接基础地址、注入token、处理响应体、统一报错、遇到特定状态码时触发登录失效逻辑。简化后的核心逻辑大致是这样:

const BASE_URL = 'http://192.168.1.100:8080' function request(options) { // 从store里取出token,注入请求头 const token = store.state.token header['Authorization'] = 'Bearer ' + token return new Promise((resolve, reject) => { uni.request({ url: BASE_URL + options.url, method: options.method || 'GET', data: options.data || {}, header, success: (res) => { const code = res.data.code if (code === 401) { // 登录失效:清理缓存、跳转登录页 logout() return } if (code === 200) { resolve(res.data.data) } else { uni.showToast({ title: res.data.msg, icon: 'none' }) reject(res.data) } }, fail: (err) => reject(err) }) }) }

这套逻辑的核心价值,是把"每个页面都要处理的鉴权和错误提示"收敛到了一处。页面里只管调接口、拿数据,出了问题统一弹提示,不用每段业务代码都去判断返回码。

4.2 api目录的模块划分方式

api目录下通常会按后端Controller维度继续拆文件,比如登录认证、个人用户、任务、流程、公告等,每个文件里导出若干个方法,页面层直接import方法名调用。这样做的对齐价值非常明显:后端一个REST接口对应前端一个api方法,改接口时顺着方法名就能找到所有调用方。

我看到很多团队二次开发时,接口一多就直接在页面里写uni.request,看起来快,但后续后端改了url、加了鉴权,页面代码就得翻遍全工程去改。RuoYiApp版的模块化结构本身已经铺好了路,正确的增量方式是:后端新加接口 -> api目录下新增方法 -> 页面统一调用方法。

4.3 多端下的baseURL差异

多端适配在请求层就能体现出来。H5端存在跨域问题,后端需要做跨域处理,或者用开发代理转发;App端不存在浏览器同源策略,同一局域网下直接填IP地址即可;小程序端对域名有强约束,只能是HTTPS且需要在平台后台配置合法域名。

所以baseURL最好不要硬编码写死在request.js里,而是根据编译平台做条件取值。常见做法是在不同位置用条件编译区分,不同端各配一个环境地址。签过这套坑的人都知道,App在模拟器上能跑、真机就请求失败,多半就是baseURL写成了localhost,换成了电脑的局域网IP才正常。

5. store与登录态:token和用户信息在App里是怎么流动的

5.1 登录状态的完整流转过程

RuoYiApp版的store里,最核心的模块是登录状态和用户信息。打开App后,先从本地缓存把token读出来放入store,有token就维持登录状态,没有token就进登录流程。登录接口返回后,token写入store,同时用uni.setStorageSync持久化到本地,之后getUserInfo再拉个人信息,也一并缓存到本地。

这个"store内存态 + storage持久态"的双写设计,是从后台管理端转过来时最容易忽略的。后台管理系统里刷新页面,Vuex状态全部重建,所以token一定要存在cookie或localStorage里。移动端也一样,App冷启动后store是空的,能维持登录身份的只有storage里的缓存。退出登录时,双写清理要同时做,只清store不清storage会出现"界面已退出,重启又自动登录"的诡异问题。

5.2 401拦截与重新登录的闭环

登录态失效的处理很能检验项目结构是否清晰。正常链路是:某个接口返回401 -> 统一请求封装里触发登出 -> 清空store和storage -> 跳转到登录页。这个逻辑放在request.js里而不是每个页面单独处理,是RuoYiApp版结构里很值得借鉴的设计。

实际应用中要注意一个细节:多个接口同时401时,会触发多次跳转和多次清理。更稳妥的做法是设置一个标记位,只有第一次401才执行登出动作,其余请求直接reject,避免登录页被重复打开。这个问题在弱网和接口慢的场景下很容易出现,调试时会看到页面疯狂跳动。

5.3 用户信息与权限控制的取舍

移动端App对权限控制的颗粒度,和后台管理系统完全不同。后台管理端习惯于根据角色、按钮权限动态渲染页面,但移动端如果完全按这套走,页面渲染会非常卡,且移动端页面数量通常远少于后台。RuoYiApp版更务实的做法是:用户基本信息进store全局共享,细粒度的操作权限在具体业务接口由后端返回并校验,前端只在必要的入口做粗略判断。

这样设计后,store里不建议塞太多东西。凡是页面可以临时请求、用完就丢的数据,放进页面局部状态即可;只有全局都要用、且频繁读取的数据,才值得放入store。

6. components、utils、static与uni_modules:辅助层的分工边界

6.1 公共组件与页面组件的边界

components目录收集的是跨页面复用的组件。判断一个组件该不该进这里,标准很简单:有没有至少两个页面会用到。只有当前页面使用的组件,留在页面目录下更合理。RuoYiApp版里常见的公共组件有头像选择、状态标签、空数据占位、自定义弹窗等。

在uni-app里,组件引用的方式比传统Vue更灵活。配置了easycom规则后,只要组件放在固定目录下,页面里无需import和注册,直接用标签名就能渲染。这个机制在写公共组件时特别顺手,但也会带来一个排查难点:页面里出现了一个组件标签,全局搜索不到import语句,容易误判为未定义组件。遇到这种问题,先看项目根目录或pages.json里是否配置了easycom规则。

6.2 utils目录:纯函数与可复用逻辑

utils目录放的是不依赖页面、不依赖Vue实例的纯逻辑工具。RuoYiApp版里除了前文提到的request.js,还有处理token读写、时间格式化、字典翻译、字符串校验等工具文件。判断一个函数应不应该放utils,有一个很直观的标准:如果它在两个以上页面做同样的事情,就应该抽出来。

这里想强调一个原则:utils里的函数尽量保持"无状态、无副作用"。尤其是请求封装,不应该直接操作某个页面的UI。多人协作时,常见的问题是有人在utils里塞了一个弹窗提示,导致纯工具函数携带了UI耦合,测试和复用都变困难。

6.3 static与uni_modules的定位差异

static目录是静态资源专区,图片、字体、音频等放在这里。多端环境下,图片路径的写法有讲究,小程序端对本地图片的使用限制比H5更多,开发时要留意。如果一个资源必须依赖CDN地址,建议统一配置在常量文件里,避免散落进各页面写死。

uni_modules是uni-app的插件生态目录,RuoYiApp版中常见的插件形式有扩展组件、SDK封装、平台能力桥接等。这个目录的维护有版本概念,升级插件时要注意版本兼容。一个稳妥的习惯是:凡是从插件市场引入的能力,尽量再包一层业务适配,这样以后换插件时,不用把业务代码全部重写一遍。

7. 条件编译与多端差异:同一套代码是怎么同时跑App、H5、小程序的

7.1 条件编译不是魔法,是预处理

uni-app实现多端的一个核心武器是条件编译。它的用法不是运行时判断,而是编译期处理:编译到某个平台时,只保留该平台对应的代码块,其余代码直接剔除。写法上通过特殊注释实现:

<!-- #ifdef MP-WEIXIN --> <view>只在微信小程序端显示</view> <!-- #endif -->
// #ifdef H5 const baseUrl = '/prod-api' // #endif // #ifndef H5 const baseUrl = 'https://api.example.com' // #endif

理解这一点,就能解释很多奇怪现象。比如你代码里明明写了一段"App端才执行的逻辑",H5端怎么完全没反应?原因是编译到H5时,那段代码根本就没进编译产物。反过来,如果你把一段平台的API无条件写在公共文件里,编译到另一个平台时,IDE可能不报错,运行时才会因为API不存在而中断。

7.2 各平台最容易踩的差异点

先看登录。微信小程序里登录流程和App端不一样,小程序通常要拿code换openid,再拿openid去后端换自定义token;App端通用做法是用手机号验证码或账号密码。RuoYiApp版的结构中,登录逻辑一般收敛在login页面和对应的auth api文件里,条件编译主要用来处理"当前在哪个平台、需要走哪条登录链路"。

再看网络。H5端打包后部署在Nginx下,接口地址通常考虑用相对路径加代理转发,避免跨域;App端则没有跨域概念,但Android 9及以上对明文HTTP请求有默认限制,开发时需要调整网络安全配置。小程序端最麻烦,生产环境必须使用HTTPS域名,且要提前在平台后台配置合法域名,上线前一天才发现域名没加白名单的事,业界真不少见。

最后看打包。App端打包涉及图标、启动图、应用证书等配置,这些都在manifest.json里。真机调试时需要注意:同一局域网内,App可以直接访问电脑的IP端口,但手机如果连的是移动数据或另一个Wi-Fi,自然就访问不到后端服务,报错也往往是网络异常。

8. 从目录到上手:二次开发的读码顺序、踩坑记录与配置细节

8.1 推荐的代码阅读路径

如果第一次接触RuoYiApp版,建议不要按目录从上往下扫,那没什么效果。我自己摸索下来比较顺的顺序是:先跑起来,然后按下面这条线走。

第一步,打开pages.json,把整个页面清单过一遍,知道这个App有哪几个一级模块、tabBar选了哪些。第二步,打开request.js,只看baseURL、token注入、401处理三个地方,弄清楚一个网络请求从发出到落地会经过哪几道关卡。第三步,打开store相关文件和登录页面,把token"登录写入、启动恢复、退出清理"这条链路走通。第四步,以某一个具体业务页面为样本,从事件触发开始,定位它调的是哪个api方法,然后顺着api方法回到request.js,再把返回值在页面里如何渲染看一遍。

这套顺序只用半天左右,但比漫无目的地翻文件高效得多。核心目的只有一个:先建立"页面 -> api -> request -> 后端"这条主链路,目录里的其他细节以后遇到再补。

8.2 新增一个业务页面的完整动作

很多人改完工程加页面时,只创建了vue文件,运行起来却找不到页面。这里把完整动作列一遍:

  1. 在pages目录下创建对应的页面vue文件。
  2. 在pages.json的pages数组里注册页面路径,并配置导航栏标题、是否允许下拉刷新等样式。
  3. 如果这个页面要在tabBar平级切换,还要在tabBar配置里同步处理,并留意页面是否已经存在冲突。
  4. 在api目录下新增或复用接口方法,把请求参数和返回结构理清。
  5. 在页面中引入api方法,处理加载态、失败提示和返回数据渲染。
  6. 真机或模拟器上自测登录状态、下拉刷新、返回跳转几个基础动作。

第2步是最容易漏的。uni-app的页面不会自动注册,pages.json相当于路由表,漏了它,编译不报错,但运行时怎么跳都是一片空白。这个问题我见过不止一两次,每次都是在pages.json里找到解决方案。

8.3 二次开发阶段的高频问题记录

把常见问题做成一个简表,方便排查时直接对号入座:

现象根因处理方向
登录成功但首页请求401新token没写入storage检查登录流程里setStorageSync是否执行,存储key是否和request读取的key一致
页面跳转提示不存在pages.json漏注册或路径写错核对页面路径和pages数组中的path
App调用接口报URL无效baseURL写了localhost或端口错误改为电脑局域网IP,确认后端允许局域网访问
Web端跨域后端未配置CORS或代理未生效优先用代理转发,避免明文轻易暴露接口
真机可打开但H5白屏manifest或构建配置平台信息不完整检查平台配置和应用标识

这套问题清单的价值在于,大部分"诡异问题"最后都落在请求、路由、baseURL这三类基础环节上,项目结构分析阶段如果能把这三点吃透,实际调试时能少走很多弯路。

二次开发另一个值得注意的地方,是版本升级。工程目录里的pages.json、manifest.json、request.js这几个关键文件,在升级前最好先备份一份。因为新版工程往往在这几处调整了配置项和默认值,直接整体覆盖,业务页面大概率能保留,但请求封装和平台配置可能被冲掉,届时又把问题归因到自己的代码上,白白浪费时间。

我个人实际改这个项目时,最深的体会是:RuoYiApp版的目录结构并不复杂,真正决定开发效率的,是你对整个请求链路和登录态流转的理解程度。如果把这两条主线摸透,再加多少个页面、换多少个模块,本质都是不断重复同一个动作:在正确的位置,调用正确的方法。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询