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

416 lines
20 KiB

# 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 路由懒加载
```javascript
// 开发环境:使用 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.js`、`utils/permission.js`、`directive/permission/hasPermi.js`、`directive/permission/hasRole.js`),逻辑高度重复但存在细微差异,如超级权限标识散落在各处 | 维护困难,修改权限规则需同时修改多处,易遗漏导致权限漏洞 | 🟡 中 |
| 3 | **状态持久化分散且不统一**Token 存 Cookie、sidebar 状态存 Cookie、布局设置存 localStorage手动读写无统一管理无 vuex-persistedstate 等插件,刷新后部分状态可能不一致 | 状态管理混乱,调试困难,存在不同步风险 | 🟡 中 |
| 4 | **业务组件与通用组件混放**`Kdialog`、`TrendStocksDialog`、`NegativeDialog` 等强业务耦合组件放在 `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 |