You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
RuoYi-Vue/docs/architecture/frontend-architecture.md

20 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

RuoYi-Vue 前端架构分析

项目版本: 3.8.0 | 技术栈: Vue 2.6.12 + Element UI 2.15.6 + Vuex 3.6 + Vue Router 3.4.9
构建工具: Vue CLI 4.4.6 | 描述: 摸金分析系统
分析日期: 2026-06-28


1. 组件结构分析

1.1 组件树图ASCII

src/
├── components/                          # 全局通用组件22 个组件组)
│   ├── Breadcrumb/                      # ── 面包屑导航
│   ├── Crontab/                         # ── Cron 表达式生成器9 个子组件)
│   │   ├── day.vue / hour.vue / min.vue #    时间维度选择
│   │   ├── month.vue / week.vue / year.vue
│   │   ├── second.vue / result.vue / index.vue
│   ├── DictData/                        # ── 字典数据管理器JS 模块)
│   ├── DictTag/                         # ── 字典标签渲染
│   ├── Editor/                          # ── 富文本编辑器Quill
│   ├── FileUpload/                      # ── 文件上传
│   ├── Hamburger/                       # ── 汉堡菜单按钮
│   ├── HeaderSearch/                    # ── 头部搜索Fuse.js
│   ├── IconSelect/                      # ── SVG 图标选择器
│   ├── ImagePreview/                    # ── 图片预览
│   ├── ImageUpload/                     # ── 图片上传
│   ├── Kdialog/                         # ── K 线对话框(业务组件)
│   ├── NegativeDialog/                  # ── 涨跌停对话框(业务组件)
│   ├── Pagination/                      # ── 分页器
│   ├── PanThumb/                        # ── 全景缩略图
│   ├── ParentView/                      # ── 父级视图占位
│   ├── RightPanel/                      # ── 右侧面板
│   ├── RightToolbar/                    # ── 右侧工具栏
│   ├── RuoYi/                           # ── 外部链接Doc / Git
│   ├── Screenfull/                      # ── 全屏切换
│   ├── SizeSelect/                      # ── 组件尺寸选择
│   ├── SvgIcon/                         # ── SVG 图标封装
│   ├── ThemePicker/                     # ── 主题选择器
│   ├── TopNav/                          # ── 顶部导航
│   ├── TrendStocksDialog/               # ── 板块趋势对话框(业务)
│   └── iFrame/                          # ── 内嵌 iframe
│
├── layout/                              # 布局框架
│   ├── index.vue                        # ── 主布局容器
│   ├── components/
│   │   ├── AppMain.vue                  # ── 主内容区(含 keep-alive
│   │   ├── Navbar.vue                   # ── 顶部导航栏
│   │   ├── Sidebar/                     # ── 侧边栏
│   │   │   ├── index.vue / SidebarItem.vue / Item.vue / Link.vue / Logo.vue
│   │   ├── TagsView/                    # ── 标签页视图
│   │   │   ├── index.vue / ScrollPane.vue
│   │   ├── Settings/                    # ── 系统设置面板
│   │   └── InnerLink/                   # ── 内部链接
│   └── mixin/ResizeHandler.js           # ── 响应式 Mixin
│
├── views/                               # 页面视图(按业务模块)
│   ├── dashboard/                       # ── 仪表盘5 个图表组件)
│   ├── system/                          # ── 系统管理8 个子模块)
│   ├── booksystem/                      # ── 账本系统6 个子模块)
│   ├── stocksystem/                     # ── 股票系统8 个子模块)
│   ├── monitor/                         # ── 系统监控7 个子模块)
│   ├── tool/                            # ── 系统工具(代码生成等)
│   ├── error/                           # ── 错误页面
│   ├── login.vue / register.vue         # ── 认证页面
│   └── index.vue                        # ── 首页

1.2 分类统计

分类 组件/模块数量 说明
通用 UI 组件 10 Breadcrumb, Pagination, SvgIcon, Hamburger, Screenfull, SizeSelect, ThemePicker, PanThumb, ParentView, iFrame
业务组件 4 Kdialog, NegativeDialog, TrendStocksDialog, RightToolbar
功能组件 7 Crontab(9), DictTag, DictData, Editor, FileUpload, ImageUpload, ImagePreview, IconSelect, HeaderSearch
布局组件 8 Layout, AppMain, Navbar, Sidebar(5), TagsView(2), Settings, InnerLink
视图模块 6 大模块 dashboard, system, booksystem, stocksystem, monitor, tool
视图页面总数 ~40 个 包含子页面和嵌套路由页面

1.3 组件复用程度分析

高复用组件(被全局注册或在多个视图中引用):

  • Pagination — 几乎所有列表页面
  • DictTag — 所有涉及字典展示的页面
  • RightToolbar — 所有 CRUD 列表页
  • Editor / FileUpload / ImageUpload — 表单页面
  • Breadcrumb — 布局层全局使用
  • SvgIcon — 全局图标渲染基础组件

中复用组件(特定业务场景):

  • Crontab — 仅定时任务模块
  • Kdialog / TrendStocksDialog / NegativeDialog — 股票业务模块
  • HeaderSearch — 仅 Navbar 使用

低复用组件(单一职责或一次性使用):

  • PanThumb — 仅用户个人资料页
  • RightPanel — 仅布局设置入口
  • RuoYi/Doc / RuoYi/Git — 外部链接

1.4 组件职责分析

组件 职责单一性 评价
Crontab 一般 9 个子组件拆分合理,但耦合度较高
Kdialog / NegativeDialog 业务逻辑与 UI 混合,体积过大
Dashboard 图表组件 每个图表独立文件,职责清晰
Layout 子组件 Sidebar、Navbar、TagsView 分离清晰

2. 路由与权限控制

2.1 路由结构图

路由系统
├── 静态路由 (constantRoutes) — 始终可访问
│   ├── /redirect/:path        — 重定向页
│   ├── /login                 — 登录页
│   ├── /register              — 注册页
│   ├── /404                   — 404 错误页
│   ├── /401                   — 401 错误页
│   ├── / (首页)               — Dashboard
│   └── /user/profile          — 个人中心
│
├── 动态路由 (dynamicRoutes) — 按权限加载
│   ├── /system/user-auth/role/:userId    — 分配角色 [system:user:edit]
│   ├── /system/role-auth/user/:roleId    — 分配用户 [system:role:edit]
│   ├── /system/dict-data/index/:dictId   — 字典数据 [system:dict:list]
│   ├── /monitor/job-log                  — 调度日志 [monitor:job:list]
│   └── /tool/gen-edit                    — 修改生成配置 [tool:gen:edit]
│
└── 服务端路由 — 登录后从后端获取
    ├── filterAsyncRouter()  ── 转换为组件对象
    ├── filterDynamicRoutes() ── 权限过滤
    └── router.addRoutes()   ── 动态注册

2.2 权限加载流程

用户登录
   │
   ├─ 1. 输入用户名/密码/验证码
   ├─ 2. 调用 POST /login → 获取 token → 存入 Cookie (Admin-Token)
   ├─ 3. 路由守卫 beforeEach 拦截
   │     │
   │     ├─ 有 token ? ──NO──→ 跳转 /login
   │     │      │
   │     │     YES
   │     │      │
   │     │     ├─ roles.length === 0 ? ──NO──→ next()
   │     │     │      │
   │     │     │     YES
   │     │     │      │
   │     │     │     ├─ GetInfo() → 获取用户信息roles + permissions
   │     │     │     │
   │     │     │     └─ GenerateRoutes() → 请求 /getRouters
   │     │     │              │
   │     │     │              ├─ filterAsyncRouter() → 字符串转组件
   │     │     │              ├─ filterDynamicRoutes() → 权限过滤
   │     │     │              └─ router.addRoutes() → 注册路由
   │     │     │
   │     │     └─ next({ ...to, replace: true }) → 重新导航
   │     │
   │     └─ 无 token → 白名单 ? ──YES──→ next()
   │                        │
   │                       NO
   │                        │
   │                   跳转 /login?redirect=xxx
   │
   └─ 4. 页面渲染 → 动态菜单展示

2.3 权限控制机制

项目采用 三层权限控制

层级 实现方式 代码位置
路由级 路由守卫 + 服务端返回菜单 src/permission.js + src/store/modules/permission.js
页面级 v-hasPermi / v-hasRole 指令 src/directive/permission/
按钮级 $auth.hasPermi() / $auth.hasRole() src/plugins/auth.js

权限标识格式模块:资源:操作(如 system:user:add
超级权限*:*:*(拥有所有权限)
超级角色admin(拥有所有角色权限)

2.4 路由懒加载

// 开发环境:使用 require 实现
if (process.env.NODE_ENV === 'development') {
  return (resolve) => require([`@/views/${view}`], resolve)
}
// 生产环境:使用 import 实现
else {
  return () => import(`@/views/${view}`)
}

3. 状态管理分析

3.1 Vuex 模块图

Vuex Store
├── getters.js                          # 统一访问入口
│     ├── sidebar, size, device         → state.app
│     ├── visitedViews, cachedViews     → state.tagsView
│     ├── token, avatar, name, roles    → state.user
│     └── permission_routes, topbarRouters, sidebarRouters → state.permission
│
├── modules/app.js                      # 应用级状态
│     ├── sidebar { opened, withoutAnimation }
│     ├── device ('desktop' | 'mobile')
│     └── size ('medium' | 'small' | 'mini')
│     └── 持久化Cookies (sidebarStatus, size)
│
├── modules/user.js                     # 用户状态
│     ├── token, name, avatar
│     ├── roles: string[]
│     ├── permissions: string[]
│     └── Actions: Login, GetInfo, LogOut, FedLogOut
│     └── 持久化Cookies (Admin-Token)
│
├── modules/permission.js               # 权限/路由状态
│     ├── routes, addRoutes             — 完整路由表 / 动态路由
│     ├── defaultRoutes, topbarRouters, sidebarRouters
│     └── Actions: GenerateRoutes
│
├── modules/tagsView.js                 # 标签页状态
│     ├── visitedViews: []              — 已访问的标签页
│     ├── cachedViews: []               — 被 keep-alive 缓存的页面
│     └── Actions: addView, delView, delOthersViews, delAllViews,
│                  delRightTags, delLeftTags
│
└── modules/settings.js                 # 系统设置状态
      ├── theme, sideTheme, showSettings
      ├── topNav, tagsView, fixedHeader, sidebarLogo, dynamicTitle
      └── 持久化localStorage ('layout-setting')

3.2 数据流向

                    ┌──────────────┐
                    │   Vue 组件   │
                    └──────┬───────┘
                           │ dispatch
                           ▼
                    ┌──────────────┐
                    │   Actions    │  ──→ 异步操作API 调用)
                    └──────┬───────┘
                           │ commit
                           ▼
                    ┌──────────────┐
                    │  Mutations   │  ──→ 同步修改 state
                    └──────┬───────┘
                           │
                           ▼
                    ┌──────────────┐
                    │    State     │  ──→ 响应式数据
                    └──────┬───────┘
                           │
                    ┌──────┴───────┐
                    │   Getters    │  ──→ 计算属性
                    └──────┬───────┘
                           │
                    ┌──────┴───────┐
                    │   Vue 组件   │  ←── 重新渲染
                    └──────────────┘

3.3 状态持久化

状态 存储方式 键名 说明
用户 Token Cookie Admin-Token 会话认证
侧边栏状态 Cookie sidebarStatus 展开/折叠
组件尺寸 Cookie size Element UI 默认尺寸
布局设置 localStorage layout-setting JSON 对象(主题、顶栏、标签页等)

注意Vuex 本身无持久化插件,数据分散在 Cookie 和 localStorage 中手动读写。


4. API 调用层

4.1 Axios 封装架构

src/api/                              # API 模块目录
├── booksystem/                       # ── 账本系统 API6 个文件)
├── stocksystem/                      # ── 股票系统 API9 个文件)
├── system/                           # ── 系统管理 API7 个文件 + dict 子目录)
├── monitor/                          # ── 系统监控 API7 个文件)
├── tool/                             # ── 系统工具 API
├── login.js                          # ── 登录/获取信息/退出
├── menu.js                           # ── 获取动态路由
└── index.js                          # ── 通用 API

src/utils/request.js                  # Axios 实例封装
├── baseURL: process.env.VUE_APP_BASE_API
├── timeout: 60000ms (60秒)
├── Content-Type: application/json;charset=utf-8
└── download() 方法                   # ── 文件下载封装

4.2 拦截器实现

请求拦截器Request Interceptor

请求发出
   │
   ├─ 1. 检查是否需要 tokenconfig.headers.isToken === false 可跳过)
   ├─ 2. 自动添加 Authorization: Bearer {token}
   ├─ 3. GET 请求 params 序列化tansParams 处理)
   │     └─ 将 ?a=1&b=2 拼接到 URL清空 config.params
   └─ 4. 发送请求

响应拦截器Response Interceptor

收到响应
   │
   ├─ 解析业务状态码 res.data.code
   │
   ├─ code === 401 → MessageBox 弹窗 → 确认后 LogOut → 跳首页
   ├─ code === 500 → Message.error(msg) → Promise.reject
   ├─ code !== 200 → Notification.error({ title: msg }) → Promise.reject
   ├─ code === 200 → 返回 res.data
   │
   └─ HTTP 错误捕获error
       ├─ Network Error → "后端接口连接异常"
       ├─ timeout → "系统接口请求超时"
       ├─ status code 4xx/5xx → "系统接口 XXX 异常"
       └─ Message.error(message, duration: 5s)

4.3 错误处理机制

错误类型 处理方式 用户体验
401 未授权 弹窗确认 → 退出登录 强提醒,需用户操作
500 服务端错误 Message 提示 轻量提示,自动消失
其他业务错误 Notification 通知 右上角通知
网络异常 Message 提示5 秒) 长时提示
请求超时 Message 提示5 秒) 长时提示
下载失败 解析 blob 中的 JSON 错误信息 自动处理登录态失效

4.4 错误码映射

通过 src/utils/errorCode.js 实现业务码到提示文本的映射,支持:

  • 预定义错误码映射
  • 默认兜底文本
  • 服务端返回的自定义 msg

5. 存在的问题

5.1 问题清单

# 问题 影响 严重程度
1 Vue 2 + Vue CLI 技术栈老化Vue 2 已于 2023 年底停止官方维护Vue Router 3、Vuex 3、Vue CLI 4 均为旧版本,存在安全风险且无法使用 Composition API 等现代特性 长期维护成本高,无法使用新生态,第三方库兼容性逐步下降 🔴
2 权限逻辑重复实现:权限校验在 4 个位置分别实现(plugins/auth.jsutils/permission.jsdirective/permission/hasPermi.jsdirective/permission/hasRole.js),逻辑高度重复但存在细微差异,如超级权限标识散落在各处 维护困难,修改权限规则需同时修改多处,易遗漏导致权限漏洞 🟡
3 状态持久化分散且不统一Token 存 Cookie、sidebar 状态存 Cookie、布局设置存 localStorage手动读写无统一管理无 vuex-persistedstate 等插件,刷新后部分状态可能不一致 状态管理混乱,调试困难,存在不同步风险 🟡
4 业务组件与通用组件混放KdialogTrendStocksDialogNegativeDialog 等强业务耦合组件放在 src/components/ 通用组件目录,且通过全局注册加载,增加了首屏包体积 首屏加载变慢,通用组件库被业务代码污染,不利于复用 🟡
5 视图页面高度模板化system/booksystem/stocksystem/ 下的 CRUD 页面结构高度相似(搜索表单 + 表格 + 分页 + 弹窗),但未抽取为可配置的高阶组件或 Schema 驱动表单 代码冗余度高,新增 CRUD 页面工作量大,修改通用逻辑需改多个文件 🟡
6 生产/开发环境使用不同的懒加载方式:开发环境用 require(),生产环境用 import(),两份逻辑增加了维护复杂度,且开发环境无法享受代码分割的优化效果 开发构建产物大,热更新慢,行为不一致可能导致线上问题 🟢
7 响应拦截器中 401 处理使用同步 location.href 跳转location.href = '/index' 是硬跳转,绕过了 Vue Router 的导航守卫和状态清理流程 可能导致 Vuex 状态未完全清理、标签页数据残留 🟢
8 缺少统一的请求重试机制:网络波动或后端短暂不可用时,所有请求直接失败,无自动重试策略 用户体验下降,偶发性网络问题导致操作失败 🟢

5.2 优化建议

优先级 建议 预期收益
P0 规划升级至 Vue 3 + Vite + Pinia 获得性能提升、Composition API、更好的类型支持
P1 抽取统一的权限校验核心模块,消除重复代码 降低维护成本,消除权限逻辑不一致风险
P1 引入 vuex-persistedstate 或自定义统一持久化层 状态管理规范化,减少手动读写 Cookie/LocalStorage
P2 将业务组件移至对应业务模块目录,或采用按需异步加载 减少首屏包体积,保持通用组件库纯净
P2 抽取 CRUD 页面为 Schema 驱动的高阶组件 减少 60%+ 的 CRUD 页面代码量
P3 统一开发/生产的组件加载方式 简化代码,提升开发体验
P3 401 处理改用 router.replace() 代替 location.href 状态清理更完整,避免残留数据
P3 在 request.js 中添加可配置的重试机制 提升网络不稳定场景下的用户体验

附录:技术栈概览

类别 技术 版本
核心框架 Vue 2.6.12
UI 框架 Element UI 2.15.6
路由 Vue Router 3.4.9
状态管理 Vuex 3.6.0
HTTP 客户端 Axios 0.24.0
构建工具 Vue CLI 4.4.6
CSS 预处理器 Sass 1.32.13
图表库 ECharts 4.9.0
富文本 Quill 1.3.7
代码高亮 highlight.js 9.18.5
拖拽排序 SortableJS / vuedraggable 1.10.2 / 2.24.3
SVG 图标 svg-sprite-loader 5.1.1
加密 jsencrypt 3.2.1
进度条 NProgress 0.2.0
Cookie js-cookie 3.0.1