uniapp页面列表列表请求hook记录

请求hook

js 复制代码
import {
	ref,
	watch,
	computed
} from "vue"
import {
	onLoad,
	onReachBottom,
	onPullDownRefresh
} from "@dcloudio/uni-app"
import {
	throttle,
	debounce
} from "@/assets/utils/common.js"

export const useListData = (requestParams = {}) => {
	const request = requestParams.request || ref(null) // 请求数据的方法
	const params = requestParams.params || ref({}) // 请求传递的参数
	const size = requestParams.size || 10 // 一次加载多少条数据
	const immediate = requestParams.immediate !== undefined ? requestParams.immediate : true // 页面加载时立即加载数据
	const watchParams = requestParams.watchParams || true // 监听参数变化
	const watchRequest = requestParams.watchRequest === undefined ? true : requestParams.watchRequest // 监听请求参数的变化
	const includeKeys = requestParams.includeKeys || [] // 需要监听那些参数的变化

	const loading = ref(true)
	const finished = ref(false)
	const error = ref(false)
	const noData = ref(false)
	const pageIndex = ref(1)
	const pageSize = ref(size)
	const list = ref([])

	const resetData = (refresh = true) => {
		loading.value = true
		finished.value = false
		error.value = false
		noData.value = false
		list.value = []
		pageIndex.value = 1
		refresh && getListFn()
	}
	const getData = () => {
		// if (!request.value) return getApp().globalData.toast('请传入请求数据的方法')
		// if (!request?.value || loginInfoStore.loading.value) return
		if (!request?.value) return
		pageIndex.value === 1 && resetData(false)
		loading.value = true
		const data = {
			...params.value,
			pageIndex: pageIndex.value,
			pageSize: pageSize.value,
		}
		request.value(data)
			.then(res => {
				// 兼容不同接口返回来的数据结构不同-集优臻选
				if (res?.data?.records) {
					const data = res?.data?.records || []
					if (pageIndex.value === 1) {
						list.value = [...data]
					} else {
						list.value = [...list.value, ...data]
					}
					if (list.value?.length === res?.data?.total) {
						finished.value = true
					}
					if (!list.value?.length) {
						noData.value = true
					}
					return
				}
				// 兼容不同接口返回来的数据结构不同-速团
				if (res?.data?.list) {
					const data = res?.data?.list.data || []
					list.value = [...list.value, ...data]
					if (Number(res?.data?.list.pageCount) === pageIndex.value || (pageSize.value > data?.length)) {
						finished.value = true
					}
					if (!list.value?.length) {
						noData.value = true
					}
					return
				}
				if (res?.data?.data) {
					const data = res?.data?.data || []
					list.value = [...list.value, ...data]
					if (Number(res?.data?.pageCount) === pageIndex.value) {
						finished.value = true
					}
					if (!list.value?.length) {
						noData.value = true
					}
					return
				}
				if (Array.isArray(res.data)) {
					list.value = res.data
					finished.value = true
					if (!list.value?.length) {
						noData.value = true
					}
					return
				}
				console.log('没有找到对应结构')
				finished.value = true
				noData.value = true
			})
			.catch((err) => {
				error.value = true
			})
			.finally(() => {
				loading.value = false
			})
	}

	const getListFn = debounce(getData, 200)

	const onError = () => {
		getListFn()
	}
	const filterWatchKeys = () => {
		//  保证includeKeys中的key存在与params中
		const paramsKeys = Object.keys(params.value)
		const includesKeys = paramsKeys.filter(key => includeKeys.includes(key))
		return includesKeys.map(key => params.value[key])
	}
	onLoad(() => {
		immediate && getListFn()
	})
	onReachBottom(() => {
		if (loading.value || finished.value) return
		// 防止前面内容过长,页面还没有初始化加载的时候就触发了page++,造成初次加载加载的是第二页的数据
		if (pageIndex.value === 1 && !list.value?.length) return
		pageIndex.value = pageIndex.value + 1
		getListFn()
	})
	const onScrollToLower = () => {
		if (loading.value || finished.value) return
		// 防止前面内容过长,页面还没有初始化加载的时候就触发了page++,造成初次加载加载的是第二页的数据
		if (pageIndex.value === 1 && !list.value?.length) return
		pageIndex.value = pageIndex.value + 1
		getListFn()
	}

	onPullDownRefresh(() => {
		resetData()
	})
	// 监听参数变化
	watch(
		() => filterWatchKeys(),
		() => {
			watchParams && resetData()
		}
	)
	// 监听请求数据的方法的变化
	watch(
		() => request.value,
		() => {
			watchRequest && resetData()
		}
	)
	return {
		loading,
		finished,
		error,
		noData,
		list,
		pageIndex,
		pageSize,
		getListFn,
		resetData,
		onError,
		onScrollToLower
	}
}

export default useListData

使用

js 复制代码
import useListData from "@/hooks/useListData.js";
import { getList } from "@/api/product.js";

const request = ref(getList);
const params = ref({
  // lon: location.value.longitude,
  // lat: location.value.latitude,
  isGold: 0,
  isPresale: 1
});
const {
  loading,
  finished,
  error,
  noData,
  list,
  pageIndex,
  pageSize,
  getListFn,
  resetData,
  onError,
} = useListData({
  immediate: true,
  request,
  params,
  // includeKeys: ['cityId', 'tagKey', 'categoryKey', 'lat']
});

对应列表状态组件

html 复制代码
<template>
	<view class="comp-my-list-status">
		<view v-if="loading" class="text-tip" :class="textClass" >{{ loadingText }}</view>
		<view v-else-if="error" class="text-tip" :class="textClass" @click="onError">{{ errorText }}</view>
		<view v-else-if="finished && !noData" class="text-tip" :class="textClass">{{ finishedText }}</view>
		<view v-else-if="finished && noData" class="no-data-box" >
			<view class="no-data-img-box">
				<image class="no-data-img" :class="noDataImgClass" src="../static/imgs/img-no-data@2x.png" mode="widthFix"></image>
			</view>
			
			<view class="no-data-text" :class="noDataTextClass">{{ noDataText }}</view>
		</view>
		<view v-else class="" ></view>
	</view>
</template>

<script setup>
	import {
		defineProps,
		defineEmits
	} from "vue"
	
	const emits = defineEmits(['error'])
	const props = defineProps({
		loading: {
			type: Boolean,
			default: false
		},
		finished: {
			type: Boolean,
			default: false
		},
		error: {
			type: Boolean,
			default: false
		},
		noData: {
			type: Boolean,
			default: false
		},
		loadingText: {
			type: String,
			default: '数据加载中...'
		},
		finishedText: {
			type: String,
			default: '已加载全部数据'
		},
		errorText: {
			type: String,
			default: '数据加载错误,点击重新加载'
		},
		noDataText: {
			type: String,
			default: '暂时还没有数据哦~'
		},
		noDataTextClass: {
			type: String,
			default: ''
		},
		noDataImgClass: {
			type: String,
			default: ''
		},
		textClass: {
			type: String,
			default: ''
		}
	})
	
	const onError = () => {
		emits('error')
	}
</script>

<style lang="scss">
.comp-my-list-status{
	.text-tip{
		text-align: center;
		font-size: 24rpx;
		padding: 16rpx;
		color: #ccc;
	}
	.no-data-box{
		display: flex;
		flex-direction: column;
		align-items: center;
		justify-content: center;
		
		.no-data-img-box{
			width: 50%;
			aspect-ratio: 1;
			.no-data-img{
				// width: 228rpx;
				// height: 228rpx;
				width: 100%;
				height: 100%;
			}
		}
		
		.no-data-text{
			text-align: center;
			font-weight: 400;
			// font-size: 20rpx;
			font-size: 24rpx;
			color: #000000;
			line-height: 28rpx;
		}
	}
}
</style>
相关推荐
QQ1__8115175155 小时前
Spring boot名城小区物业管理系统信息管理系统源码-SpringBoot后端+Vue前端+MySQL【可直接运行】
前端·vue.js·spring boot
钛态5 小时前
前端微前端架构:大项目的救命稻草还是自找麻烦?
前端·vue·react·web
一粒黑子5 小时前
【实战解析】阿里开源 PageAgent:纯前端 GUI Agent,一行JS让网页支持自然语言操控
前端·javascript·开源
独角鲸网络安全实验室5 小时前
2026微信小程序抓包全解析:从实操落地到合规风控,解锁前端调试新范式
前端·微信小程序·小程序·抓包·系统代理绕过·https证书严格校验·进程隔离
紫微AI5 小时前
前端文本测量成了卡死一切创新的最后瓶颈,pretext实现突破了
前端·人工智能·typescript
GISer_Jing5 小时前
AI前端(From豆包)
前端·aigc·ai编程
IT枫斗者5 小时前
前端部署后如何判断“页面是不是最新”?一套可落地的版本检测方案(适配 Vite/Vue/React/任意 SPA)
前端·javascript·vue.js·react.js·架构·bug
测试修炼手册5 小时前
[测试技术] 深入理解 JSON Web Token (JWT)
前端·json
AI老李5 小时前
2026 年 Web 前端开发的 8 个趋势!
前端
里欧跑得慢5 小时前
15. Web可访问性最佳实践:让每个用户都能平等访问
前端·css·flutter·web