【Ruoyi 解密 - 08. 前端探秘1】------ 从“交互原理”到“开发逻辑”,后端也能看懂的前端实战指南

Ruoyi-Vue3 核心知识点串讲:从"交互原理"到"开发逻辑",后端也能看懂的前端实战指南

对于非前端工程师而言,学习 Ruoyi-Vue3 并非要成为专业前端开发者,而是要掌握"前后端交互逻辑"------搞懂数据如何从后端接口流转到前端页面,明白前端如何处理状态、触发事件、渲染视图,才能在联调时快速定位问题(比如"为什么接口返回数据了页面没变化"),也能在自研开源项目时独立搭建前端骨架。

接下来我们将以 "数据流转"为核心线索,从"基础语法→状态管理→接口交互→权限控制"四个维度,串讲 Ruoyi-Vue3 中必须掌握的核心知识点,每个点都结合 Ruoyi 实际代码场景,避免纯理论堆砌。

导航:

对于基础知识的了解大家可以看一下:菜鸟教程VUE3

也可以看一下赵珊珊老师的文档:VUE3技术入门

这里放一张思维导图,可以先纵览,读完文章后再回来品味,你会有不一样的收获~

【Ruoyi-Vue3 核心知识点串讲的思维导图】

思维导图使用说明:

层级逻辑:从 "基础语法→全局数据→接口交互→业务功能→配置工具" 逐步递进,符合学习路径;

重点标注:加粗 / 高亮部分(如 ref/reactive、Pinia Store、v-hasPermi)为高频考点与联调关键;

场景绑定:每个知识点均对应 Ruoyi 实际功能(如用户管理、权限控制),避免纯理论;

使用建议:

学习时:按 "总纲→分模块→子知识点" 拆解,结合代码实操;

联调时:按 "问题场景→对应模块" 定位(如 "接口没传 Token"→ 三、3.2 请求拦截器);

复习时:以 "数据流转" 为线索(前端数据→全局数据→接口请求→页面渲染)串联各模块。

一、先搞懂 Vue3 最核心的"组合式 API":Ruoyi 代码里最常见的语法

Vue3 放弃了 Vue2 的 Options API(data/methods/computed 拆分),改用 Composition API(组合式 API),而这也是 Ruoyi-Vue3 所有页面/组件的基础语法。后端工程师可以类比:Options API 像"按功能分类写代码"(比如把所有变量放一起、所有方法放一起),Composition API 像"按业务逻辑写代码"(比如把"用户列表查询"相关的变量、方法、请求写在一块),更贴近后端"业务模块封装"的思路。

在 Ruoyi 中,你会看到所有页面都基于 setup() 语法(或 <script setup> 语法糖)编写,核心 API 只有 3 个,先记住它们的作用:

1. ref:处理"简单类型"的响应式数据(最常用)

作用 :让普通变量(数字、字符串、布尔值)变成"响应式"------即变量变化时,页面自动更新。
Ruoyi 场景 :表单输入值、表格加载状态、弹窗显示/隐藏等。
代码示例(Ruoyi 中的用户列表页面):

vue 复制代码
<script setup>
// 1. 引入 ref
import { ref } from 'vue';

// 2. 定义响应式变量:表格加载状态(默认true)、弹窗显示状态(默认false)
const loading = ref(true); 
const dialogVisible = ref(false);

// 3. 修改变量:通过 .value 操作(页面会自动更新)
const getList = () => {
  loading.value = true; // 开始加载,表格显示loading
  request({ // 调用接口(后面会讲)
    url: '/system/user/list',
    method: 'get'
  }).then(res => {
    loading.value = false; // 加载完成,隐藏loading
  });
};

// 4. 控制弹窗:点击按钮显示弹窗
const handleAdd = () => {
  dialogVisible.value = true;
};
</script>

<template>
  <!-- 页面中直接用变量名,不用 .value -->
  <el-table v-loading="loading" :data="tableData">...</el-table>
  <el-dialog v-model="dialogVisible" title="新增用户">...</el-dialog>
  <el-button @click="handleAdd">新增</el-button>
</template>

后端视角理解 :可以把 ref 类比成后端的"Bean 包装类"------普通变量无法被 Vue 监听,用 ref 包一层后,Vue 就能感知到变化,进而触发页面更新,就像后端用 Bean 封装数据才能在接口中传输一样。

2. reactive:处理"复杂类型"的响应式数据

作用 :让对象/数组变成响应式,比 ref 更适合管理"多个关联变量"(比如表单数据、表格列配置)。
Ruoyi 场景 :用户新增/编辑表单、表格查询参数。
代码示例(Ruoyi 中的用户表单):

vue 复制代码
<script setup>
import { reactive } from 'vue';

// 定义表单数据(对象类型)
const form = reactive({
  username: '', // 用户名
  deptId: '',   // 部门ID
  status: '0'   // 状态(默认启用)
});

// 定义查询参数(对象类型)
const queryParams = reactive({
  pageNum: 1,    // 页码
  pageSize: 10,  // 每页条数
  username: ''   // 用户名查询条件
});

// 修改数据:直接操作属性,不用 .value
const handleQuery = () => {
  queryParams.pageNum = 1; // 重置页码为1
  getList(); // 调用查询接口
};
</script>

<template>
  <!-- 表单绑定:用 v-model 直接关联 reactive 对象的属性 -->
  <el-form :model="form" label-width="120px">
    <el-form-item label="用户名" prop="username">
      <el-input v-model="form.username" placeholder="请输入用户名" />
    </el-form-item>
  </el-form>
</template>

关键区别 :ref 管理简单类型,修改时需加 .value;reactive 管理复杂类型,直接修改属性即可。在 Ruoyi 中,这两个 API 几乎覆盖了所有"页面数据管理"场景。

3. onMounted:页面"初始化时"执行的逻辑

作用 :Vue3 的"生命周期钩子",表示"页面渲染完成后立即执行",对应 Vue2 的 mounted
Ruoyi 场景 :页面加载时自动查询列表数据、初始化下拉框选项。
代码示例(Ruoyi 中的角色列表页面):

vue 复制代码
<script setup>
import { ref, onMounted } from 'vue';
import { listRole } from '@/api/system/role'; // 接口函数(后面讲)

const tableData = ref([]); // 表格数据

// 页面初始化时调用查询接口
onMounted(() => {
  getRoleList();
});

// 查询角色列表
const getRoleList = () => {
  listRole(queryParams).then(res => {
    tableData.value = res.rows; // 把接口返回的列表数据赋值给表格
  });
};
</script>

后端视角理解 :可以类比成后端 Controller 的"初始化方法"(如 Spring 的 @PostConstruct),在页面"准备就绪"后自动执行初始化逻辑,避免手动触发。

二、状态管理:Ruoyi 如何用 Pinia 管理"全局数据"

在前后端交互中,有些数据需要"跨页面共享"(比如用户登录信息、全局加载状态、侧边栏菜单),Vue2 用 Vuex 管理,而 Ruoyi-Vue3 改用 Pinia(Vue 官方推荐,比 Vuex 更简单)。

你不用深入理解 Pinia 的原理,只需搞懂 Ruoyi 中"如何存全局数据"和"如何取全局数据"------这是联调时定位"全局状态异常"的关键。

1. 核心概念:Store(仓库)= 全局数据容器

Pinia 把全局数据放在"Store"中,每个 Store 对应一个业务模块(比如用户信息存 userStore,菜单存 menuStore)。Ruoyi 已封装好常用 Store,位于 src/store/modules/ 目录下。

2. 实战场景1:获取当前登录用户信息

比如你需要在页面中显示"当前登录用户名",就需要从 Pinia 的 userStore 中获取,步骤如下:

vue 复制代码
<script setup>
// 1. 引入 Pinia 的 userStore(Ruoyi 已封装好)
import { useUserStore } from '@/store/modules/user';

// 2. 创建 Store 实例
const userStore = useUserStore();

// 3. 获取全局数据:当前用户信息
console.log(userStore.user.name); // 输出当前登录用户名
console.log(userStore.token);     // 输出用户登录的 JWT 令牌
</script>

<template>
  <!-- 页面中直接使用 -->
  <div class="user-info">欢迎您,{{ userStore.user.name }}</div>
</template>

3. 实战场景2:修改全局数据(如更新用户信息)

如果后端接口返回"用户信息更新成功",需要同步更新全局的用户信息,避免页面显示旧数据:

vue 复制代码
<script setup>
import { useUserStore } from '@/store/modules/user';
const userStore = useUserStore();

// 调用后端"更新用户信息"接口
const updateUser = () => {
  updateUserInfo(form).then(res => {
    // 接口成功后,更新 Pinia 中的用户信息
    userStore.setUser(res.data); 
    ElMessage.success('更新成功');
  });
};
</script>

其中 setUser 是 Ruoyi 在 userStore 中封装的"修改方法",位于 src/store/modules/user.js

javascript 复制代码
// Ruoyi 中的 userStore 定义
export const useUserStore = defineStore('user', {
  state: () => ({
    user: {}, // 存储用户信息
    token: '' // 存储令牌
  }),
  actions: {
    // 修改用户信息的方法
    setUser(user) {
      this.user = user;
    },
    // 修改令牌的方法
    setToken(token) {
      this.token = token;
    }
  }
});

关键结论:Ruoyi 中所有全局数据的"存/取"都通过 Pinia 的 Store 完成,若页面显示的全局数据异常,先检查 Store 中的数据是否正确(比如 token 是否过期、user 是否为空)。

三、接口交互:Ruoyi 如何"调用后端接口"(核心中的核心)

前后端联调的核心就是"接口调用",Ruoyi-Vue3 已封装好完整的接口请求流程,你需要搞懂 3 个问题:接口地址存在哪?如何调用接口?如何处理响应数据?

1. 第一步:接口地址封装(API 模块)

Ruoyi 把所有后端接口地址按业务模块分类,放在 src/api/ 目录下(比如系统管理相关接口在 src/api/system/),每个文件对应一个后端 Controller。

示例src/api/system/user.js(对应后端 SysUserController):

javascript 复制代码
// 引入 Ruoyi 封装的请求工具(已处理拦截器、异常提示)
import request from '@/utils/request';

// 1. 查询用户列表(对应后端 /system/user/list 接口)
export function listUser(query) {
  return request({
    url: '/system/user/list', // 接口路径(会拼接基础地址)
    method: 'get',            // 请求方法
    params: query             // GET 请求参数(自动拼在 URL 后)
  });
}

// 2. 新增用户(对应后端 /system/user 接口)
export function addUser(data) {
  return request({
    url: '/system/user',
    method: 'post',
    data: data // POST 请求参数(放在请求体中)
  });
}

// 3. 删除用户(对应后端 /system/user/{userId} 接口)
export function delUser(userId) {
  return request({
    url: '/system/user/' + userId,
    method: 'delete'
  });
}

后端视角理解:这相当于前端的"Feign 客户端",把后端接口封装成前端函数,调用时不用重复写 URL 和请求方法,统一管理更易维护。

2. 第二步:调用接口(页面中使用)

在页面中调用上述封装好的接口,只需"导入函数→传参→处理响应",流程和后端调用 Service 类似:

vue 复制代码
<script setup>
// 1. 导入接口函数(从对应的 API 文件中)
import { listUser, addUser } from '@/api/system/user';
import { ref, reactive } from 'vue';

// 定义查询参数(和后端接口的请求参数对应)
const queryParams = reactive({
  pageNum: 1,
  pageSize: 10,
  username: ''
});

// 定义表格数据(存储接口返回的列表)
const tableData = ref([]);

// 2. 调用"查询用户列表"接口
const getList = () => {
  // 传参:queryParams(和接口函数的 query 参数对应)
  listUser(queryParams).then(res => {
    // 3. 处理响应:把后端返回的 rows 赋值给表格
    tableData.value = res.rows; 
    // 把总条数赋值给分页组件(Ruoyi 分页需要)
    total.value = res.total;
  }).catch(err => {
    // 异常处理(如接口报错提示)
    ElMessage.error('查询失败:' + err.message);
  });
};

// 3. 调用"新增用户"接口
const handleAdd = () => {
  addUser(form).then(res => {
    ElMessage.success('新增成功');
    dialogVisible.value = false; // 关闭弹窗
    getList(); // 重新查询列表,显示新数据
  });
};
</script>

3. 第三步:理解"请求拦截器"(为什么不用手动传 Token)

后端接口需要"登录认证"(比如 JWT Token),但你会发现 Ruoyi 中调用接口时并没有手动传 Token------这是因为 Ruoyi 在 src/utils/request.js 中封装了"请求拦截器",自动把 Pinia 中存储的 Token 加到请求头里。

核心代码如下(不用手写,理解逻辑即可):

javascript 复制代码
// Ruoyi 封装的请求工具
const service = axios.create({
  baseURL: import.meta.env.VITE_APP_BASE_API, // 基础地址(从环境变量取)
  timeout: 5000
});

// 请求拦截器:发送请求前执行
service.interceptors.request.use(
  config => {
    // 1. 从 Pinia 的 userStore 中获取 Token
    const userStore = useUserStore();
    // 2. 如果有 Token,自动加到请求头的 Authorization 中
    if (userStore.token) {
      config.headers['Authorization'] = 'Bearer ' + userStore.token;
    }
    return config;
  },
  error => {
    return Promise.reject(error);
  }
);

联调关键点:如果后端报"未登录"或"Token 无效",先检查两点:

  1. Pinia 的 userStore.token 是否有值(浏览器 F12 → Application → Pinia → user 查看);
  2. 请求头中是否有 Authorization: Bearer xxx(浏览器 F12 → Network → 选中接口 → Headers 查看)。

四、权限控制:Ruoyi 如何实现"按钮级权限"(前后端配合)

Ruoyi 作为后台管理系统,核心需求之一是"权限控制"(比如普通用户看不到"删除用户"按钮,管理员才能看到)。这需要前后端配合实现,前端负责"根据权限显示/隐藏按钮",后端负责"接口权限校验"。

你需要搞懂 Ruoyi 中"前端权限判断"的逻辑,避免联调时出现"有权限却看不到按钮"或"没权限却能点击"的问题。

1. 核心原理:基于"权限标识"的控制

后端会给每个权限(菜单/按钮)分配一个"权限标识"(比如"user:add"表示新增用户权限,"user:delete"表示删除用户权限),登录时后端会把当前用户的所有权限标识返回给前端,存储在 Pinia 的 userStore.permissions 中。

前端通过判断"当前用户的权限标识中是否包含目标标识",来控制按钮的显示/隐藏。

2. Ruoyi 封装的权限指令:v-hasPermi

Ruoyi 封装了一个自定义指令 v-hasPermi,直接在按钮上使用即可实现权限控制,无需手动写判断逻辑。

代码示例(用户列表页面的"删除"按钮):

vue 复制代码
<template>
  <!-- v-hasPermi 指令:只有包含 "user:delete" 权限的用户才能看到该按钮 -->
  <el-button 
    type="danger" 
    icon="el-icon-delete" 
    @click="handleDelete"
    v-hasPermi="['user:delete']"
  >
    删除
  </el-button>
</template>

3. 指令背后的逻辑(不用手写,理解即可)

v-hasPermi 指令的核心逻辑是"检查当前用户的权限列表中是否包含目标标识",定义在 src/directive/permission/hasPermi.js

javascript 复制代码
export const hasPermi = {
  mounted(el, binding) {
    // 1. 获取当前用户的所有权限标识(从 Pinia 中取)
    const userStore = useUserStore();
    const permissions = userStore.permissions;
    // 2. 获取按钮需要的权限标识(binding.value 就是指令传的数组,如 ['user:delete'])
    const requiredPermis = binding.value;
    // 3. 判断:如果用户没有该权限,就隐藏按钮(移除 DOM 元素)
    const hasPermission = requiredPermis.some(permi => permissions.includes(permi));
    if (!hasPermission) {
      el.parentNode?.removeChild(el); // 移除按钮
    }
  }
};

4. 前后端配合关键点

  • 后端需做的事 :登录时查询当前用户的权限标识列表(如 user:adduser:delete),并通过接口返回给前端;接口请求时,校验当前用户是否拥有该接口对应的权限标识(避免前端隐藏按钮后,用户通过抓包绕过权限调用接口)。
  • 前端需做的事 :通过 v-hasPermi 指令控制按钮显示/隐藏,同时在路由跳转前校验权限(避免用户手动输入 URL 访问无权限页面)。

5. 路由权限控制(补充场景)

除了按钮级权限,Ruoyi 还实现了"页面级权限"------用户无权限访问的页面,即使手动输入 URL 也会被拦截跳转到 403 页面。核心逻辑在 src/router/index.js 的路由守卫中:

javascript 复制代码
// 路由前置守卫:跳转前校验权限
router.beforeEach((to, from, next) => {
  const userStore = useUserStore();
  // 1. 如果是登录页面,直接放行
  if (to.path === '/login') {
    next();
    return;
  }
  // 2. 如果未登录,跳转到登录页
  if (!userStore.token) {
    next({ path: '/login' });
    return;
  }
  // 3. 校验页面权限:判断当前用户是否有权限访问目标页面
  const hasPermission = to.meta.perms 
    ? userStore.permissions.some(permi => to.meta.perms.includes(permi)) 
    : true;
  if (hasPermission) {
    next(); // 有权限,放行
  } else {
    next({ path: '/403' }); // 无权限,跳转到403页面
  }
});

后端视角理解 :这相当于前端的"权限拦截器",和后端 Spring Security 的 Interceptor 逻辑一致------都是在"资源访问前"做权限校验,避免无权限访问。

五、表单处理:Ruoyi 如何实现"校验+提交"(前后端联调高频场景)

后台管理系统中,"表单操作"(新增/编辑用户、配置参数)是前后端联调的高频场景。Ruoyi-Vue3 基于 Element-Plus 封装了表单校验、数据提交、重置等逻辑,你需要搞懂"前端如何做表单校验"和"如何处理后端返回的校验错误",避免联调时出现"前端校验过了但后端报错"或"后端返回错误前端没提示"的问题。

1. 核心流程:表单校验 → 接口提交 → 结果处理

以"新增用户"表单为例,完整流程如下:

(1)表单绑定与校验规则定义

通过 el-formel-form-item 组件实现表单布局,同时定义校验规则(如"用户名不能为空""手机号格式正确"):

vue 复制代码
<template>
  <el-form 
    ref="formRef"  <!-- 表单引用,用于调用校验方法 -->
    :model="form"  <!-- 绑定表单数据(reactive 定义的对象) -->
    :rules="rules"  <!-- 绑定校验规则 -->
    label-width="120px"
  >
    <!-- 用户名输入框 -->
    <el-form-item label="用户名" prop="username">
      <el-input v-model="form.username" placeholder="请输入用户名" />
    </el-form-item>
    <!-- 手机号输入框 -->
    <el-form-item label="手机号" prop="phonenumber">
      <el-input v-model="form.phonenumber" placeholder="请输入手机号" />
    </el-form-item>
    <!-- 提交/重置按钮 -->
    <el-form-item>
      <el-button type="primary" @click="handleSubmit">提交</el-button>
      <el-button @click="handleReset">重置</el-button>
    </el-form-item>
  </el-form>
</template>

<script setup>
import { reactive, ref } from 'vue';
import { addUser } from '@/api/system/user';

// 1. 表单数据(reactive 定义,和后端请求参数对应)
const form = reactive({
  username: '',
  phonenumber: '',
  status: '0' // 默认启用
});

// 2. 表单引用(用于调用 validate 校验方法)
const formRef = ref(null);

// 3. 校验规则(和 form 的属性一一对应)
const rules = reactive({
  username: [
    { required: true, message: '用户名不能为空', trigger: 'blur' }, // 失去焦点时校验
    { min: 2, max: 20, message: '用户名长度在 2-20 之间', trigger: 'blur' }
  ],
  phonenumber: [
    { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }
  ]
});
</script>
(2)表单提交:先校验再调用接口

点击"提交"按钮时,先通过 formRef.value.validate() 触发前端校验,校验通过后再调用后端接口:

javascript 复制代码
// 表单提交
const handleSubmit = () => {
  // 1. 触发前端校验
  formRef.value.validate((isValid) => {
    if (isValid) { // 校验通过
      // 2. 调用后端"新增用户"接口
      addUser(form).then(res => {
        ElMessage.success('新增成功');
        // 3. 提交成功后的操作:关闭弹窗、刷新列表
        dialogVisible.value = false;
        getList();
      }).catch(err => {
        // 4. 处理后端返回的错误(如"用户名已存在")
        ElMessage.error('新增失败:' + err.message);
      });
    }
  });
};

// 表单重置
const handleReset = () => {
  formRef.value.resetFields(); // 重置表单数据和校验状态
};

2. 前后端校验配合要点

  • 前端校验:负责"格式校验"(如必填、长度、正则),目的是"减少无效接口请求",提升用户体验;
  • 后端校验:负责"业务校验"(如"用户名是否已存在""部门是否存在"),目的是"保障数据安全",避免前端绕过校验提交非法数据;
  • 联调注意 :如果后端返回业务错误(如 {"code":500,"msg":"用户名已存在"}),前端需在 catch 中捕获并提示用户------Ruoyi 封装的 request 工具已自动将后端错误抛到 catch 中,无需额外处理。

六、列表与分页:Ruoyi 如何实现"查询+分页+筛选"(后台系统通用逻辑)

后台系统中,"数据列表"(如用户列表、角色列表)是最常见的页面类型,核心需求包括"条件查询""分页展示""排序""批量操作"。Ruoyi-Vue3 基于 Element-Plus 的 el-tableel-pagination 组件封装了通用逻辑,你需要搞懂"分页参数如何传递"和"查询条件如何同步",这是联调列表接口的核心。

1. 核心组件与数据流转

(1)组件布局(列表+分页)
vue 复制代码
<template>
  <!-- 1. 查询表单 -->
  <el-form :model="queryParams" inline @submit.prevent="handleQuery">
    <el-form-item label="用户名" prop="username">
      <el-input v-model="queryParams.username" placeholder="请输入用户名" clearable />
    </el-form-item>
    <el-form-item label="状态">
      <el-select v-model="queryParams.status" placeholder="请选择" clearable>
        <el-option label="启用" value="0" />
        <el-option label="禁用" value="1" />
      </el-select>
    </el-form-item>
    <el-form-item>
      <el-button type="primary" icon="el-icon-search" @click="handleQuery">查询</el-button>
      <el-button icon="el-icon-refresh" @click="handleReset">重置</el-button>
    </el-form-item>
  </el-form>

  <!-- 2. 数据列表 -->
  <el-table :data="tableData" border stripe v-loading="loading">
    <el-table-column label="用户名" prop="username" align="center" />
    <el-table-column label="状态" prop="status" align="center">
      <!-- 状态格式化:0→启用,1→禁用 -->
      <template #default="scope">
        <el-tag type="success" v-if="scope.row.status === '0'">启用</el-tag>
        <el-tag type="danger" v-else>禁用</el-tag>
      </template>
    </el-table-column>
    <el-table-column label="操作" align="center">
      <template #default="scope">
        <el-button type="text" @click="handleEdit(scope.row)">编辑</el-button>
        <el-button type="text" @click="handleDelete(scope.row)" v-hasPermi="['user:delete']">删除</el-button>
      </template>
    </el-table-column>
  </el-table>

  <!-- 3. 分页组件 -->
  <el-pagination
    v-if="total > 0"
    :current-page="queryParams.pageNum"
    :page-size="queryParams.pageSize"
    :total="total"
    layout="total, sizes, prev, pager, next, jumper"
    @size-change="handleSizeChange"
    @current-change="handleCurrentChange"
  />
</template>
(2)数据与方法定义
javascript 复制代码
<script setup>
import { ref, reactive, onMounted } from 'vue';
import { listUser } from '@/api/system/user';

// 1. 列表数据
const tableData = ref([]);
// 2. 加载状态(控制表格 loading 动画)
const loading = ref(false);
// 3. 总条数(分页组件需要)
const total = ref(0);
// 4. 查询参数(包含分页参数和筛选条件)
const queryParams = reactive({
  pageNum: 1,    // 当前页码(后端通常从1开始)
  pageSize: 10,  // 每页条数
  username: '',  // 筛选条件:用户名
  status: ''     // 筛选条件:状态
});

// 页面初始化时查询列表
onMounted(() => {
  getList();
});

// 核心:查询列表数据
const getList = () => {
  loading.value = true; // 开启 loading
  // 调用接口:传递 queryParams(分页+筛选条件)
  listUser(queryParams).then(res => {
    tableData.value = res.rows; // 列表数据(后端返回的数组)
    total.value = res.total;    // 总条数(后端返回的总数)
  }).finally(() => {
    loading.value = false; // 关闭 loading(无论成功失败)
  });
};

// 条件查询:点击"查询"按钮触发
const handleQuery = () => {
  queryParams.pageNum = 1; // 重置页码为1(避免筛选后页码超出范围)
  getList();
};

// 重置查询条件:点击"重置"按钮触发
const handleReset = () => {
  queryParams.username = '';
  queryParams.status = '';
  queryParams.pageNum = 1;
  getList();
};

// 分页:每页条数变化(如从10条改成20条)
const handleSizeChange = (pageSize) => {
  queryParams.pageSize = pageSize;
  queryParams.pageNum = 1; // 重置页码为1
  getList();
};

// 分页:当前页码变化(如从第1页跳到第2页)
const handleCurrentChange = (pageNum) => {
  queryParams.pageNum = pageNum;
  getList();
};
</script>

2. 前后端分页配合要点

  • 参数约定 :前端传递 pageNum(当前页)和 pageSize(每页条数),后端需返回 rows(当前页数据数组)和 total(总条数),格式如下:

    json 复制代码
    {
      "code": 200,
      "msg": "success",
      "data": {
        "rows": [{"id":1,"username":"admin",...}, ...], // 列表数据
        "total": 100 // 总条数(用于分页组件计算总页数)
      }
    }
  • 联调注意 :如果分页不生效(如点击第2页还是显示第1页数据),先检查两点:

    1. 前端是否正确传递 pageNumpageSize(浏览器 F12 → Network → 接口参数);
    2. 后端是否正确接收参数并返回对应页的数据(可通过 Postman 测试后端接口)。

七、总结:Ruoyi-Vue3 前端开发逻辑的"核心脉络"

对于非前端工程师而言,无需死记硬背语法细节,只需抓住"数据流转"这一核心脉络,就能理解 Ruoyi-Vue3 的前端逻辑:

  1. 数据从哪来:要么是前端自己定义的响应式数据(ref/reactive),要么是后端接口返回的数据(通过 API 模块调用),要么是全局共享数据(Pinia Store);
  2. 数据怎么用 :通过 {``{ 变量名 }} 渲染到页面,通过 v-model 绑定表单输入,通过 :data 绑定列表数据;
  3. 数据怎么变:用户操作(点击按钮、输入内容)触发方法,方法中修改响应式数据或调用接口,数据变化后 Vue 自动更新页面;
  4. 权限怎么控 :前端通过 Pinia 存储权限标识,通过 v-hasPermi 指令控制按钮显示,通过路由守卫控制页面访问;

掌握这一脉络后,无论遇到"联调接口""定位前端问题"还是"自研小项目",都能快速理清逻辑------比如"页面没显示数据",就按"接口是否返回数据→数据是否赋值给响应式变量→变量是否正确绑定到页面"的顺序排查,效率会大幅提升。

下一节,我们将结合 Ruoyi 的实际功能模块(如用户管理、角色管理),拆解"前后端联调的完整流程",带你实战解决联调中常见的问题(如跨域、Token 过期、参数不匹配)。

相关推荐
百罹鸟3 分钟前
nestjs 从零开始 一步一步实践
前端·node.js·nestjs
袁煦丞10 分钟前
Wiki.js团队知识大脑/个人笔记管家:cpolar内网穿透实验室第496个成功挑战
前端·程序员·远程工作
维他AD钙30 分钟前
2025 年前端性能优化实战:从加载到渲染的全链路优化指南
前端
大米饭消灭者1 小时前
markdown-it是怎么将markdown转为html的
前端·面试
1024小神1 小时前
在vue/react项目中单独引入一个js文件,在js文件中使用DOMContentLoaded函数querySelectorAll为空数组解决办法
前端
moyu841 小时前
前端请求封装实战解析:基于Axios的封装技巧
前端
前端小木屋1 小时前
Express框架介绍与基础入门
前端·node.js
宁雨桥1 小时前
Vite 打包目录结构自定义配置指南
前端·javascript·typescript·npm
前端老鹰1 小时前
CSS text-decoration-thickness:精细控制文本装饰线粗细的新属性
前端·css
易协同低代码1 小时前
门户修改静态文字
前端·javascript