请求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>