后端零改动,给若依换一套现代化前端

一、背景

RuoYi(若依) 大概是国内最流行的开源后台管理框架之一:RBAC 权限体系完善、代码简洁、文档与社区成熟,RuoYi-Vue 几乎成了「后台管理系统」的默认起手式。

但官方前端多年来停留在上一代的技术审美里:观感略显陈旧、主题定制能力弱(换个主色要改一堆 Less 变量)、布局单一、没有国际化。很多团队的真实状态是------后端用着若依,前端另起炉灶重写一遍,权限、字典、代码生成这些能力全部要重新对接。

于是就有了 ruoyi-vue-nys:一套适配若依后端 API 的现代化中后台前端。

后端零改动,前端整套替换。 /getRouters、登录鉴权、字典、代码生成等接口全部原样对接;原有的业务页面、指令与全局方法直接可用,50 余个页面零改动迁移。

二、效果

亮色主题 + 主题配置抽屉(主题模式 / 布局模式 / 主题色 / 页面功能一屏可调):

暗色模式 + 英文界面(i18n 一键切换):

用户管理页(亮 / 暗),组织机构树、搜索表单、分页、行内操作一应俱全:

ECharts 监控图表也能自动跟随主题:

登录页(暗色):

三、技术栈

分类 选型
框架 Vue 3.5 + Vite(rolldown-vite)+ TypeScript 5.9
UI Element Plus 2.14 + UnoCSS
状态 / 路由 Pinia + vue-router
国际化 vue-i18n(中 / 英)
图表 ECharts 6
工程 pnpm workspace + ESLint 9 + vue-tsc

项目通过 pnpm workspace 内置了 5 个 @sa/* 包:color(色板生成)、hooks、materials、utils、uno-preset,主题、布局、路由约定等基础架构参考了同样开源的 soybean-admin 体系。

四、核心「零改动接管」

把若依的页面直接拷过来,代码是这样的:

html 复制代码
<el-table :data="userList" v-loading="loading">
  <el-table-column label="部门" prop="deptName" />
</el-table>
js 复制代码
export default {
  methods: {
    getList() {
      listUser(this.queryParams).then(res => {
        this.userList = res.rows;
        this.total = res.total;
      });
    },
    handleExport() {
      this.download('system/user/export', { ...this.queryParams }, `user_${new Date().getTime()}.xlsx`);
    }
  }
};

res.rows、this.download、this.$modal、v-hasPermi......这些全是若依的「方言」。要零改动迁移,就必须把这些方言一模一样地在新技术栈上重新实现出来。

4.1 全局能力注入(250+ 调用点)

若依页面大量依赖全局方法。新框架把它们全部挂回去(src/plugins/ruoyi.ts):

ts 复制代码
export function setupRuoYiPlugins(app: App) {
  // RuoYi 全局方法
  app.config.globalProperties.useDict = useDict;
  app.config.globalProperties.download = download;
  app.config.globalProperties.parseTime = parseTime;
  app.config.globalProperties.resetForm = resetForm;
  app.config.globalProperties.handleTree = handleTree;
  app.config.globalProperties.addDateRange = addDateRange;
  // ...

  // RuoYi 插件对象
  app.config.globalProperties.$modal = modal;
  app.config.globalProperties.$tab = ruoyiTab;
  app.config.globalProperties.$cache = cache;

  // 新框架按需引入 Element Plus,没有全量注册,这里补齐若依用到的部分
  app.config.globalProperties.$alert = ElMessageBox.alert;
  app.config.globalProperties.$confirm = ElMessageBox.confirm;
  app.config.globalProperties.$prompt = ElMessageBox.prompt;
  app.config.globalProperties.$msgbox = ElMessageBox;
  app.config.globalProperties.$notify = ElNotification;

  // 权限指令:v-hasPermi / v-hasRole / v-copyText
  directive(app);
}

其中 $modal、$tab、$cache 这类「若依专属插件对象」也有讲究:比如 $tab.closePage() 要同时兼容新框架的多标签页与 KeepAlive 语义,$modal.msgSuccess() 内部则转译到 Element Plus 的 ElMessage。

4.2 图标按需注册

若依页面用字符串引用图标(icon="Search"),这依赖图标被全局注册。最简单的做法是:

js 复制代码
import * as ElementPlusIconsVue from '@element-plus/icons-vue';
// 294 个图标 ≈ 300 KB,全部进首屏

全量注册的代价是把 294 个图标约 300 KB 全部打进首屏 chunk,而项目实际只用到几十个。这里的做法是写一个生成脚本扫描源码,得出真实使用集合,只注册命中的图标(实测 55 个):

js 复制代码
// scripts/gen-ep-icons.mjs → src/plugins/ep-icons.ts
const hit =
  src.includes(`icon="${name}"`) ||
  src.includes(`<${name} `) ||
  src.includes(`:icon="'${name}'"`) /* ... */;
if (hit) used.push(name); // 只保留实际用到的图标

还有一个隐藏坑:若依「表单构建」页用字符串 tag('el-input'、'el-select')动态渲染组件,如果为此把整套 Element Plus 全局注册一遍,这些组件会跟着入口文件一起进首屏。最终方案是让表单构建的画布自行映射这些动态组件,入口只保留按需引入。

五、请求层:保持若依的调用契约

后端返回的是若依特有的「信封」结构:

json 复制代码
{ "code": 200, "msg": "操作成功", "rows": [], "total": 100 }

请求层(src/utils/request.js)在移植时保留了这个契约,所以业务代码里的 res.rows / res.total 完全不用改:

js 复制代码
// 响应拦截:保持 { code, msg, data | rows, total } 原样返回
service.interceptors.response.use(res => {
  const code = res.data.code || 200;
  if (code === 401) { /* 提示并重置登录态 */ }
  else if (code === 500) { ElMessage({ message: msg, type: 'error' }); }
  else if (code !== 200) { ElNotification.error({ title: msg }); }
  return res.data;
});

在保持契约的同时,顺手修掉了两个历史遗留问题:

  1. Token 从 Cookie 改为 localStorage,与 SPA 的登录态管理(Pinia store + 路由守卫)统一;
  2. 防重复提交的数据缓存做了脱敏 。若依会把上次请求体写入 sessionStorage 用于比对,直接存的话密码等敏感字段会以明文落盘;这里在写入前把 password / passwd / pwd 类字段替换为掩码,只用于「判断数据是否相同」。

六、动态路由:后端菜单直接驱动

若依的 /getRouters 返回 RouterVo 树:component 字段是字符串(Layout / ParentView / InnerLink / 视图路径),meta.noCache 控制缓存,一级菜单还会包一层 meta 为空的 Layout 壳。转换层(src/router/dynamic/ruoyi.ts)负责把它翻译成前端路由:

ts 复制代码
function transformComponent(component?: string | null) {
  if (!component) return undefined;
  if (component === 'Layout') return 'layout.base';
  if (component === 'ParentView') return 'view.ParentView';
  if (component === 'InnerLink') return 'view.InnerLink';
  return `view.${component}`;
}

const meta = {
  title: route.meta?.title ?? '',
  hideInMenu: Boolean(route.hidden),
  // RuoYi 的 noCache 与前端 keepAlive 语义相反,注意取反
  keepAlive: !route.meta?.noCache
};

几个细节值得一提:

  • 拍平 isMenuFrame 壳 :后端对一级菜单会包一层 meta: null 的 Layout,转换时识别并拍平,避免菜单树多出一级;
  • 外链 :meta.link 或 http(s):// 开头的路由注册为安全路径,由路由守卫 window.open 打开;
  • 目录重定向 :父级目录自动 redirect 到第一个可见子路由,直接访问不会白屏;
  • 图标:后端菜单的 icon 字符串自动映射到项目图标体系(含 iconify 图标集)。

菜单管理里改一下菜单,刷新页面即时生效,无需重新构建。

七、图标离线化

@iconify/vue 默认在运行期 向 https://api.iconify.design 拉取图标数据。这在内网 / 无外网环境会直接导致图标缺失------实测单次请求还要等约 7 秒。

解决方案是在构建前执行内联脚本,把项目实际用到的 27 个图标数据生成到 src/plugins/iconify.ts,随包发布、离线可用:

bash 复制代码
pnpm dev          # 内部先执行 node scripts/gen-iconify-offline.mjs
pnpm build:prod   # 同上,生成后再构建

同时保留在线兜底:配置 VITE_ICONIFY_URL 指向自建 iconify 服务后,未内联的图标会自动回退到该地址拉取。新增图标只需在脚本清单里加一行、重新执行生成脚本即可。

八、主题与布局

  • 暗黑模式:跟随系统 / 手动切换,图表、表格、弹窗全量适配;
  • 主题定制 :基于 @sa/color 的色板生成(主色 → 50~950 色阶 → CSS 变量),支持色弱模式、深色侧栏;
  • 多布局:垂直、垂直混合、水平、顶部混合,侧栏宽度 / 头部高度 / 页签风格均可调;
  • 国际化:中英双语,菜单标题、路由标题同样走 i18n;
  • 多标签页 + KeepAlive :与若依原有语义对齐(noCache → 不缓存),刷新、关闭、右键菜单完整。

主题配置在开发模式下不落缓存,改默认值立即生效;生产构建以 BUILD_TIME 作为覆盖标记,新版本发布后自动应用新的默认主题,不会被老用户的本地缓存「锁死」。

九、快速开始

bash 复制代码
# 1. 前端
git clone https://github.com/niyongsheng/ruoyi-vue-nys.git
cd ruoyi-vue-nys
pnpm install
pnpm dev          # 默认 9527 端口,/dev-api 代理到 http://localhost:8080
bash 复制代码
pnpm typecheck    # 类型检查
pnpm lint         # 代码检查
pnpm build:prod   # 生产构建(/prod-api)→ dist/
pnpm build:stage  # 预发布构建(/stage-api)→ dist/

后端直接使用任意 RuoYi-Vue 3.9.x 版本(推荐 RuoYi-Vue-fast 单模块版),前端代理指过去即可,无需改动后端任何代码。

十、小结

ruoyi-vue-nys 不是又一个「重新造轮子」的后台模板,而是一次接管式迁移:承认若依后端生态的价值,把前端从上一代技术栈整体搬到 Vue 3.5 + Vite + TS + UnoCSS 上,同时用兼容层保证业务页面零改动。

  • 新项目:直接拿它当若依的前端起手式,业务代码可以照抄 RuoYi-Vue3 的页面;
  • 存量项目:把 src/api、src/views 拷过来,改掉少量 import 即可完成前端升级。

如果这个项目对你有帮助,欢迎 Star、提 Issue,或者直接提 PR。

GitHub:github.com/niyongsheng...

相关推荐
EasyBr指纹浏览器1 小时前
抖音创作者数据导出:怎样留下可比较的日报?
数据分析·开源·抖音·数据导出
Sweet锦2 小时前
不调 Python,不装向量库:我用纯 Java 写了一套以图搜图引擎
java·人工智能·开源·图搜索
网络毒刘3 小时前
开源 AI 编程助手横向对比(Cursor / Continue / Aider):能力边界与 AtomGit 落地建议
人工智能·开源
盘古开天16663 小时前
windows鱼塘可交互鱼群动态桌面资源
人工智能·chatgpt·开源
用户9177533718753 小时前
从零封装一个地图组件库:OpenLayers + Vue 的工程化实践
vue.js
liangshanbo12154 小时前
面试题:Webpack 的 publicPath 有什么作用?
前端·webpack·node.js
枫叶丹44 小时前
从一次推理请求出发:模型、显存、网络与服务系统如何共同决定性能
网络·人工智能·chatgpt·开源·agent·codex
雪芽蓝域zzs4 小时前
第7节:多选模式、自定义选项渲染、受控弹窗显隐
vue.js·elementui
deepseek235 小时前
Kolibri 拆解:计算像 3.5B、显存要 78GB 的德国主权模型,把合规做进六个架构决策
开源·大模型·moe·aiagent·ai架构