前言:实现界面触底数据加载。后端接口得支持翻页传参(本案例使用django)
1、后端接口
1.1 封装翻页公共方法standardPagination.py
# -*- coding: utf-8 -*-
# @Time : 2024/10/15 13:15
# @Author : super
# @File : standardPagination.py
# @Software: PyCharm
# @Describe:
from rest_framework.pagination import PageNumberPagination
# 定义分页类,可以自定义每页显示的记录数
class StandardResultsSetPagination(PageNumberPagination):
page_size = 10 # 每页显示的记录数,可以根据需要调整
page_size_query_param = 'page_size' # 允许客户端通过此参数指定每页显示的记录数
max_page_size = 100 # 最大每页显示的记录数,防止客户端请求过多数据
1.2 views代码
支持按标题进行查询
class HonorExperienceSet(ModelViewSet):
queryset = HonorExperience.objects.all().order_by('-date_earned','-create_time')
serializer_class = HonorExperienceSerializer
pagination_class = StandardResultsSetPagination # 设置分页类
def get_queryset(self):
queryset = super().get_queryset()
title = self.request.query_params.get('title', None)
if title is not None:
queryset = queryset.filter(title__icontains=title)
return queryset
2、uniapp方法
2.1 页面支持数据查询
页面可进行查询,然后导入onReachBottom
<template>
<view class="pageBg" >
<view class="header">
<!-- 搜索框 -->
<input type="text" v-model="searchQuery" placeholder="搜索荣誉标题" class="search-input" />
<!-- 确定按钮 -->
<button @click="searchHonor" class="search-button">
<image src="/static/images/honorWall/ss.png" class="search-icon" />
</button>
<!-- 新增按钮 -->
<navigator url="/pages/addHonor/addHonor">
<button class="add-button">
<image src="/static/images/honorWall/add.png" class="add-icon" />
</button>
</navigator>
</view>
<!-- 列表展示 -->
<view v-if="honorList.length > 0" class="honor-list">
<view v-for="(item,index) in honorList" :key="item.id" class="honor-item">
<text>{{ index + 1 }}</text>
<text>{{ item.type_display }}</text>
<text>{{ item.title }}</text>
<text>{{ formatDate(item.date_earned) }}</text>
</view>
</view>
<!-- 无数据时的占位提示 -->
<view v-else class="no-data">
<text>暂无荣誉数据</text>
</view>
</view>
</template>
<script setup>
import { api } from '../../config/settings';
import { ref, onMounted, watch } from 'vue';
import {onReachBottom} from '@dcloudio/uni-app';
import {apiFetchHonorData} from '@/api/apis.js';
import { useRouter } from 'vue-router';
// 定义存储荣誉数据的变量
const honorList = ref([]);
// 查询条件
const searchQuery = ref('');
// 定义请求参数,title 参数如果没有提供,默认为 null
const params = ref({
title: '', // 不传 title 参数时,接口将不过滤 title
page: 1, // 页码
page_size: 20 // 每页显示的记录数
});
// 格式化日期的方法
const formatDate = (dateStr) => {
const date = new Date(dateStr);
return `${date.getFullYear()}-${('0' + (date.getMonth() + 1)).slice(-2)}-${('0' + date.getDate()).slice(-2)}`;
};
// 默认查询
const fetchHonorData = async () => {
// console.log(params);
try {
const res = await apiFetchHonorData(params.value); // 初始加载时不带查询条件
honorList.value = res.data.results || []; // 确保即使响应中没有 data 字段,honorList 也是一个数组
} catch (error) {
console.error('组件挂载时获取荣誉数据失败:', error);
// 可以选择在这里处理错误,比如显示一个错误消息
}
}
//挂载时执行默认查询
onMounted(fetchHonorData);
// 搜索查询
const searchHonor = async () => {
// 更新 params 的 title 值
params.value.title = searchQuery.value;
// console.log(params.value);
try {
const res = await apiFetchHonorData(params.value);
honorList.value = res.data.results || []; // 更新荣誉列表
} catch (error) {
console.error('搜索荣誉数据时失败:', error);
// 可以选择在这里处理错误,比如显示一个错误消息或重置查询框
}
};
onReachBottom(()=>{
console.log("sss")
})
</script>
<style lang="scss" scoped>
.header {
display: flex;
justify-content: space-between;
align-items: center;
// margin-bottom: 5px;
}
.search-input {
flex-grow: 1;
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
}
.search-button {
display: flex;
align-items: center;
color: white;
border: none;
border-radius: 5px;
padding: 10px;
cursor: pointer;
}
.search-icon {
width: 24px;
height: 24px;
}
.add-button {
display: flex;
align-items: center;
color: white;
border: none;
border-radius: 5px;
padding: 10px;
cursor: pointer;
}
.add-icon {
width: 24px;
height: 24px;
}
.honor-list {
padding: 5px;
}
// .honor-item {
// display: flex;
// justify-content: space-between;
// border-bottom: 1px solid #ddd;
// padding: 10px 0;
// }
.honor-item {
display: flex;
align-items: center; // 确保所有子元素垂直居中
border-bottom: 1px solid #ddd;
padding: 10px 0;
}
.honor-item > text:nth-child(1) { // 序号列
width: 40px; // 固定宽度
text-align: center;
}
.honor-item > text:nth-child(2) { // 类型显示列
width: 60px; // 根据内容调整或固定宽度
text-align: left; // 或根据需求调整
}
.honor-item > text:nth-child(3) { // 标题列
flex-grow: 1; // 填充剩余空间
min-width: 0; // 允许收缩到内容的最小宽度
white-space: nowrap; // 防止文本换行
overflow: hidden; // 隐藏溢出内容
text-overflow: ellipsis; // 使用省略号表示溢出文本
margin: 0 10px; // 可选:增加左右间距以改善可读性
}
.honor-item > text:nth-child(4) { // 日期列
width: 120px; // 固定宽度
text-align: right; // 根据需求调整
}
.no-data {
text-align: center;
padding: 20px;
color: #999;
}
</style>
2.2 添加实现触底翻页查询
onReachBottom(()=>{
params.value.page++;
fetchHonorData();
console.log("sss")
})
2.3 实现数据触底拼接
honorList.value = [...honorList.value, ...res.data.results] || []; // 更新荣誉列表
2.4 解决数据加载完毕还执行查询问题
//定义获取不到数据的处理
const noDate = ref(false)
#此位置加入如下代码
honorList.value = [...honorList.value, ...res.data.results] || []; // 确保即使响应中没有 data 字段,honorList 也是一个数组
if(params.value.page_size > res.data.results.length) noDate.value = true;
# 加判断
onReachBottom(()=>{
if(noDate.value
) return;
params.value.page++;
fetchHonorData();
console.log("sss")
})
2.5 完整代码
<template>
<view class="pageBg" >
<view class="header">
<!-- 搜索框 -->
<input type="text" v-model="searchQuery" placeholder="搜索荣誉标题" class="search-input" />
<!-- 确定按钮 -->
<button @click="searchHonor" class="search-button">
<image src="/static/images/honorWall/ss.png" class="search-icon" />
</button>
<!-- 新增按钮 -->
<navigator url="/pages/addHonor/addHonor">
<button class="add-button">
<image src="/static/images/honorWall/add.png" class="add-icon" />
</button>
</navigator>
</view>
<!-- 列表展示 -->
<view v-if="honorList.length > 0" class="honor-list">
<view v-for="(item,index) in honorList" :key="item.id" class="honor-item">
<text>{{ index + 1 }}</text>
<text>{{ item.type_display }}</text>
<text>{{ item.title }}</text>
<text>{{ formatDate(item.date_earned) }}</text>
</view>
</view>
<!-- 无数据时的占位提示 -->
<view v-else class="no-data">
<text>暂无荣誉数据</text>
</view>
</view>
</template>
<script setup>
import { api } from '../../config/settings';
import { ref, onMounted, watch } from 'vue';
import {onReachBottom} from '@dcloudio/uni-app';
import {apiFetchHonorData} from '@/api/apis.js';
import { useRouter } from 'vue-router';
//定义获取不到数据的处理
const noDate = ref(false)
// 定义存储荣誉数据的变量
const honorList = ref([]);
// 查询条件
const searchQuery = ref('');
// 定义请求参数,title 参数如果没有提供,默认为 null
const params = ref({
title: '', // 不传 title 参数时,接口将不过滤 title
page: 1, // 页码
page_size: 20 // 每页显示的记录数
});
// 格式化日期的方法
const formatDate = (dateStr) => {
const date = new Date(dateStr);
return `${date.getFullYear()}-${('0' + (date.getMonth() + 1)).slice(-2)}-${('0' + date.getDate()).slice(-2)}`;
};
// 默认查询
const fetchHonorData = async () => {
// console.log(params);
try {
const res = await apiFetchHonorData(params.value); // 初始加载时不带查询条件
honorList.value = [...honorList.value, ...res.data.results] || []; // 确保即使响应中没有 data 字段,honorList 也是一个数组
if(params.value.page_size > res.data.results.length) noDate.value = true;
} catch (error) {
console.error('组件挂载时获取荣誉数据失败:', error);
// 可以选择在这里处理错误,比如显示一个错误消息
}
}
//挂载时执行默认查询
onMounted(fetchHonorData);
// 搜索查询
const searchHonor = async () => {
// 更新 params 的 title 值
params.value.title = searchQuery.value;
// console.log(params.value);
try {
const res = await apiFetchHonorData(params.value);
honorList.value = [...honorList.value, ...res.data.results] || []; // 更新荣誉列表
if(params.value.page_size > res.data.results.length) noDate.value = true;
} catch (error) {
console.error('搜索荣誉数据时失败:', error);
// 可以选择在这里处理错误,比如显示一个错误消息或重置查询框
}
};
onReachBottom(()=>{
if(noDate.value
) return;
params.value.page++;
fetchHonorData();
console.log("sss")
})
</script>
<style lang="scss" scoped>
.header {
display: flex;
justify-content: space-between;
align-items: center;
// margin-bottom: 5px;
}
.search-input {
flex-grow: 1;
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
}
.search-button {
display: flex;
align-items: center;
color: white;
border: none;
border-radius: 5px;
padding: 10px;
cursor: pointer;
}
.search-icon {
width: 24px;
height: 24px;
}
.add-button {
display: flex;
align-items: center;
color: white;
border: none;
border-radius: 5px;
padding: 10px;
cursor: pointer;
}
.add-icon {
width: 24px;
height: 24px;
}
.honor-list {
padding: 5px;
}
// .honor-item {
// display: flex;
// justify-content: space-between;
// border-bottom: 1px solid #ddd;
// padding: 10px 0;
// }
.honor-item {
display: flex;
align-items: center; // 确保所有子元素垂直居中
border-bottom: 1px solid #ddd;
padding: 10px 0;
}
.honor-item > text:nth-child(1) { // 序号列
width: 40px; // 固定宽度
text-align: center;
}
.honor-item > text:nth-child(2) { // 类型显示列
width: 60px; // 根据内容调整或固定宽度
text-align: left; // 或根据需求调整
}
.honor-item > text:nth-child(3) { // 标题列
flex-grow: 1; // 填充剩余空间
min-width: 0; // 允许收缩到内容的最小宽度
white-space: nowrap; // 防止文本换行
overflow: hidden; // 隐藏溢出内容
text-overflow: ellipsis; // 使用省略号表示溢出文本
margin: 0 10px; // 可选:增加左右间距以改善可读性
}
.honor-item > text:nth-child(4) { // 日期列
width: 120px; // 固定宽度
text-align: right; // 根据需求调整
}
.no-data {
text-align: center;
padding: 20px;
color: #999;
}
</style>