Vue3 详情页跨页循环(上一条,下一条)导航功能实现

一、需求背景

在详情页中实现"上一条/下一条"的跨页循环导航功能:

功能需求:

**下一条:**当前页最后一条时,自动加载下一页第一条;最后一页最后一条时,弹框确认后跳转到第一页第一条

**上一条:**当前页第一条时,可跳转到上一页最后一条;第一页第一条时,弹框确认后跳转到最后一页最后一条

二、技术方案

前端调用现有分页接口,无需后端改动

使用 sessionStorage 保存查询条件和列表状态

通过 queryPageListBw 获取指定页数据,取首/尾记录

循环导航:首尾可循环跳转,提升浏览体验

三、实现步骤

3.1 列表页:保存查询条件

在跳转详情页时,将查询条件保存到 sessionStorage:

javascript 复制代码
// collectionst/index.vue 或 collectionst1/index.vue
const handleViewDetail = (item) => {
  const listData = {
    files: dataList.value.map(file => ({
      collectRecordId: file.collectRecordId,
      collectFileId: file.collectFileId
    })),
    currentIndex: dataList.value.findIndex(file => file.collectFileId === item.collectFileId),
    pageNum: queryParams.value.pageNum,
    pageSize: queryParams.value.pageSize,
    total: total.value,
    // 关键:保存查询条件,用于跨页调用接口
    queryParams: {
      collectType: queryParams.value.collectType,  // 采集类型(1-舌头 2-面部 3-脉诊 4-手掌)
      collectPart: queryParams.value.collectPart,  // 采集部位(1面 2舌底 3舌面 4手掌 5左脉 6右脉)
      patientName: queryParams.value.patientName,
      // ... 其他筛选条件
    }
  };
  sessionStorage.setItem('collectionst_list_data', JSON.stringify(listData));
};

注意: collectType 和 collectPart 是接口必需参数,必须保存:

collectType:区分采集类型(舌头/面部/脉诊/手掌)

collectPart:区分采集部位(面/舌底/舌面/手掌/左脉/右脉)

3.2 详情页:导入接口

javascript 复制代码
import { queryPageListBw } from "@/api/system/collection";

3.3 核心函数:获取跨页数据

javascript 复制代码
// 获取下一页的第一条数据
const fetchNextPageFirstItem = async (listData) => {
  const nextPageNum = listData.pageNum + 1;
  const queryParams = listData.queryParams || {};
  
  const bodyData = {
    pageNum: nextPageNum,
    pageSize: listData.pageSize,
    collectType: queryParams.collectType || '1',
    collectPart: queryParams.collectPart || '3',
    patientName: queryParams.patientName || '',
    patientPhone: queryParams.patientPhone || '',
    collectNumber: queryParams.collectNumber || '',
    beginTime: queryParams.beginTime || '',
    endTime: queryParams.endTime || '',
    codeList: [
      { interpretationTypeCode: 'collect_part_shedi', interpretationCode: queryParams.collectPartShedi },
      { interpretationTypeCode: 'collect_part_shetai', interpretationCode: queryParams.collectPartShetai },
      { interpretationTypeCode: 'collect_part_shexing', interpretationCode: queryParams.collectPartShexing },
      { interpretationTypeCode: 'collect_part_shezhi', interpretationCode: queryParams.collectPartShezhi }
    ].filter((item) => item.interpretationCode)
  };
  
  const res = await queryPageListBw(bodyData);
  return res.data?.rows?.[0] || null;
};

// 获取最后一页的最后一条数据
const fetchLastPageLastItem = async (listData) => {
  const totalPages = Math.ceil(listData.total / listData.pageSize);
  const queryParams = listData.queryParams || {};
  
  const bodyData = {
    pageNum: totalPages,
    pageSize: listData.pageSize,
    collectType: queryParams.collectType || '1',
    collectPart: queryParams.collectPart || '3',
    // ... 其他参数同上
  };
  
  const res = await queryPageListBw(bodyData);
  const rows = res.data?.rows || [];
  return rows[rows.length - 1] || null;
};

3.4 下一条:自动跨页 + 循环确认

javascript 复制代码
const cancel = async () => {
  const listData = JSON.parse(sessionStorage.getItem('collectionst_list_data'));
  const currentIndex = listData.files.findIndex(...);
  
  // 如果是当前页最后一条
  if (currentIndex === listData.files.length - 1) {
    const totalPages = Math.ceil(listData.total / listData.pageSize);
    
    if (listData.pageNum < totalPages) {
      // 有下一页,自动获取下一页第一条
      const nextItem = await fetchNextPageFirstItem(listData);
      if (nextItem) {
        // 更新 sessionStorage 并跳转
        listData.pageNum += 1;
        listData.currentIndex = 0;
        // ... 更新文件列表
        
        await router.replace({
          query: {
            collectFileId: nextItem.collectFileId,
            collectRecordId: nextItem.collectRecordId
          }
        });
        await fetchReportData();
      }
    } else {
      // 是最后一页的最后一条,弹框确认是否查看第一条
      goNextLoopConfirmVisible.value = true;
    }
  }
};

3.5 下一条循环确认处理

javascript 复制代码
// 下一条循环确认弹窗状态
const goNextLoopConfirmVisible = ref(false);

// 处理下一条循环确认
const handleGoNextLoopConfirm = async () => {
  goNextLoopConfirmVisible.value = false;
  
  try {
    const listData = JSON.parse(sessionStorage.getItem('collectionst_list_data'));
    const queryParams = listData.queryParams || {};
    
    // 获取第一页数据
    const bodyData = {
      pageNum: 1,  // 第一页
      pageSize: listData.pageSize,
      collectType: queryParams.collectType || '1',
      collectPart: queryParams.collectPart || '3',
      // ... 其他参数
    };
    
    const res = await queryPageListBw(bodyData);
    if (res.data?.rows?.[0]) {
      const firstItem = res.data.rows[0];
      
      // 更新 sessionStorage
      listData.currentIndex = 0;
      listData.pageNum = 1;
      listData.files = res.data.rows.map(file => ({
        collectRecordId: file.collectRecordId,
        collectFileId: file.collectFileId
      }));
      sessionStorage.setItem('collectionst_list_data', JSON.stringify(listData));
      
      // 跳转到第一条
      await router.replace({
        query: {
          collectFileId: firstItem.collectFileId,
          collectRecordId: firstItem.collectRecordId
        }
      });
      await fetchReportData();
    }
  } catch (error) {
    console.error("获取第一条数据失败:", error);
    ElMessage.error("获取第一条数据失败");
  }
};

3.6 上一条:分情况处理

javascript 复制代码
const goBack = async () => {
  const listData = JSON.parse(sessionStorage.getItem('collectionst_list_data'));
  const currentIndex = listData.files.findIndex(...);
  
  if (currentIndex === 0) {
    if (listData.pageNum === 1) {
      // 第一页第一条:弹框确认是否查看末尾数据
      goBackLoopConfirmVisible.value = true;
    } else {
      // 其他页第一条:自动获取上一页最后一条
      const prevPageNum = listData.pageNum - 1;
      const res = await queryPageListBw({
        pageNum: prevPageNum,
        ...listData.queryParams
      });
      const lastItem = res.data.rows[res.data.rows.length - 1];
      // 更新并跳转...
    }
  }
};

3.7 弹窗模板

javascript 复制代码
<!-- 上一条循环确认弹窗 -->
<div v-if="goBackLoopConfirmVisible" class="custom-confirm-overlay" @click="handleGoBackLoopCancel">
  <div class="custom-confirm-dialog" @click.stop>
    <div class="custom-confirm-content">
      <img src="@/assets/images/th.png" alt="警告" />
      <div class="custom-confirm-text">
        <div class="custom-confirm-message">
          当前是第一条数据,是否查看<br>末尾数据?
        </div>
      </div>
    </div>
    <div class="custom-confirm-buttons">
      <button @click="handleGoBackLoopCancel">取消</button>
      <button @click="handleGoBackLoopConfirm">确认</button>
    </div>
  </div>
</div>

<!-- 下一条循环确认弹窗 -->
<div v-if="goNextLoopConfirmVisible" class="custom-confirm-overlay" @click="handleGoNextLoopCancel">
  <div class="custom-confirm-dialog" @click.stop>
    <div class="custom-confirm-content">
      <img src="@/assets/images/th.png" alt="警告" />
      <div class="custom-confirm-text">
        <div class="custom-confirm-message">
          当前数据已经是最后一条,是否要<br>查看最新一条记录?
        </div>
      </div>
    </div>
    <div class="custom-confirm-buttons">
      <button @click="handleGoNextLoopCancel">取消</button>
      <button @click="handleGoNextLoopConfirm">确认</button>
    </div>
  </div>
</div>

四、完整流程图

下一条流程

用户点击"下一条"

当前是当前页最后一条?

↓ 是

有下一页?

↓ 是

自动调用接口获取下一页第一条

更新 sessionStorage 并跳转

↓ 否(最后一页最后一条)

弹框:"当前数据已经是最后一条,是否要查看最新一条记录?"

↓ 用户确认

调用接口获取第一页第一条

更新 sessionStorage 并跳转到第一条

上一条流程

用户点击"上一条"

当前是第一条?

↓ 是

是第一页?

↓ 是

弹框:"当前是第一条数据,是否查看末尾数据?"

↓ 用户确认

计算总页数:Math.ceil(total / pageSize)

调用接口获取最后一页最后一条

更新 sessionStorage 并跳转

↓ 否(其他页第一条)

自动调用接口获取上一页最后一条

更新 sessionStorage 并跳转

五、关键技术点

1.查询条件持久化:通过 sessionStorage 保存筛选条件,跨页时保持一致

2.总页数计算:Math.ceil(total / pageSize)

3.数据同步:跨页后同步更新 sessionStorage 中的页码、文件列表、索引

4.循环导航:首尾可循环跳转,提升浏览体验

5.用户确认:首尾循环时弹框确认,避免误操作

六、注意事项

6.1 必须保存的字段

collectType:采集类型(1-舌头 2-面部 3-脉诊 4-手掌)

collectPart:采集部位(1面 2舌底 3舌面 4手掌 5左脉 6右脉)

这两个字段是接口的必需参数,用于确定查询的数据类型和部位。

6.2 数据关系

舌底、舌面 → collectType: '1'(舌头)

左脉、右脉 → collectType: '3'(脉诊)

面部 → collectType: '2'

手掌 → collectType: '4'

七、总结

无需后端改动,复用现有分页接口

通过保存查询条件实现筛选状态保持

自动跨页提升操作效率

循环导航提升浏览体验

代码结构清晰,易于维护

八、适用场景

详情页需要跨页导航

需要保持筛选条件

希望实现循环浏览

希望减少后端改动

总结:

一、核心思路

不需要后端新接口,直接复用列表页的分页接口 queryPageListBw。

二、实现方式

保存查询条件

从列表页进入详情页时,将查询条件(筛选参数、页码、每页大小、总记录数)保存到 sessionStorage

跨页时用这些条件调用接口

跨页调用接口

下一条到最后一页最后一条时:调用接口获取 pageNum + 1 的第一条

上一条到第一页第一条时:调用接口获取最后一页的最后一条

循环时:调用接口获取第一页的第一条

更新本地状态

获取到数据后,更新 sessionStorage 中的页码、文件列表、当前索引

然后跳转到新记录

三、为什么用 Math.ceil(total / pageSize) 计算总页数?

原因:

total:总记录数(如 100 条)

pageSize:每页大小(如 10 条)

总页数 = 总记录数 ÷ 每页大小,向上取整

举例:

100 条记录,每页 10 条 → 100 ÷ 10 = 10 页

101 条记录,每页 10 条 → 101 ÷ 10 = 10.1 → Math.ceil(10.1) = 11 页

为什么向上取整?

因为最后一条数据需要单独一页,即使只有 1 条也要算 1 页

Math.ceil() 向上取整,确保最后一条数据能被正确计算到最后一页

不需要后端新接口,复用现有接口

通过保存查询条件,跨页时调用接口获取指定页数据

用 Math.ceil(total / pageSize) 计算总页数,确保最后一条数据被正确计算

实现成本低,只需前端改动

相关推荐
呃m2 小时前
更好地使用Google Chrome
前端·chrome
摘星编程2 小时前
React Native + OpenHarmony:BottomSheet联动效果实现
javascript·react native·react.js
前端之虎陈随易2 小时前
前端通用插件开发工具unplugin v3.0.0发布
前端·typescript
Ashley_Amanda2 小时前
SAP调用Web Service全流程详解
java·前端·数据库
Dreamy smile2 小时前
css :nth-child() 完全用法指南
前端·css
Southern Wind2 小时前
从零开始封装一个优雅的图片上传组件 - 二次改装 Layui-Upload 的教程(附完整封装代码)
前端·javascript·html·layui·css3
小白菜学前端2 小时前
Vue3 + TS 解决 ESLint 与 Prettier 格式化冲突
前端·javascript·vue.js
第二只羽毛2 小时前
搜索引擎项目
大数据·前端·c++·搜索引擎·vim