【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 过期、参数不匹配)。

相关推荐
辻戋6 小时前
从零实现React Scheduler调度器
前端·react.js·前端框架
徐同保6 小时前
使用yarn@4.6.0装包,项目是react+vite搭建的,项目无法启动,报错:
前端·react.js·前端框架
Qrun7 小时前
Windows11安装nvm管理node多版本
前端·vscode·react.js·ajax·npm·html5
中国lanwp7 小时前
全局 npm config 与多环境配置
前端·npm·node.js
JELEE.8 小时前
Django登录注册完整代码(图片、邮箱验证、加密)
前端·javascript·后端·python·django·bootstrap·jquery
TeleostNaCl10 小时前
解决 Chrome 无法访问网页但无痕模式下可以访问该网页 的问题
前端·网络·chrome·windows·经验分享
前端大卫12 小时前
为什么 React 中的 key 不能用索引?
前端
你的人类朋友12 小时前
【Node】手动归还主线程控制权:解决 Node.js 阻塞的一个思路
前端·后端·node.js
小李小李不讲道理14 小时前
「Ant Design 组件库探索」五:Tabs组件
前端·react.js·ant design
毕设十刻14 小时前
基于Vue的学分预警系统98k51(程序 + 源码 + 数据库 + 调试部署 + 开发环境配置),配套论文文档字数达万字以上,文末可获取,系统界面展示置于文末
前端·数据库·vue.js