最近公司app改版,有些页面需要使用h5编写,现在遇到一个问题,页面需要实现上拉加载 下拉刷新的功能,在项目中使用的是vant组件库 使用的vue3框架
下拉刷新功能使用PullRefresh组件,上拉加载可以使用List组件
通过这两个组件可以实现上拉加载 下拉刷新
为了在项目中方便使用封装成一个组件,通过插槽设置刷新的效果和加载的文本
同时考虑到列表有可能会存在空的情况,因此需要一个空白页面,而空白页面是需要一定的提示,因此还需要封装一个空页面的组件,提示文本进行传递,如果展示的图片不一样可以在传递一个type属性用来区分图片
这样我们就封装了一个上拉加载 下拉刷新 包含空白页的组件
效果
空白页组件
<script setup name="emptyPage">
defineProps({
msg: {
type: String,
default: "暂无数据",
},
});
</script>
<template>
<div class="emptyPage">
<img src="@img/clientPages/emptyPage.png" alt="" class="pageImg" />
<div class="msg">{{ msg }}</div>
</div>
</template>
<style lang="scss" scoped>
.emptyPage {
width: 100vw;
height: 80vh;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
.pageImg {
width: 200px;
height: 200px;
}
.msg {
color: #fccae0;
font-family: "PingFang SC";
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: normal;
}
}
</style>
上拉加载 下拉刷新组件
组件上绑定的数据需要使用
v-model,要不然会存在问题
<script setup name="babyDynamics">
import dropDownImg from "@img/refresh/tutu@2x.png";
import flushedImg from "@img/refresh/tutub@2x.png";
import emptyPage from "@com/emptyPage.vue";
let props = defineProps({
// 下拉刷新参数
// 是否下拉刷新 刷新完成之后,会传递false
isFlushed: {
type: Boolean,
default: false,
},
// 触发下拉刷新的距离
pullDistance: {
type: [Number, String],
default: 50,
},
// 顶部内容高度
headHeight: {
type: [Number, String],
default: 80,
},
// 其他参数
flushedOpt: {
type: Object,
default: () => {},
},
// 加载状态
isLoad: {
type: Boolean,
default: false,
},
// 是否加载完成
loadingIsComplete: {
type: Boolean,
default: false,
},
// 其他参数
listOpt: {
type: Object,
default: () => {},
},
// 是否是空页面
isEmptyPage: {
type: Boolean,
default: false,
},
emptyText: {
type: String,
default: "暂无数据",
},
});
// 是否下拉刷新
let isFlushedData = ref(false);
// 刷新完成之后会传递false,修改组件内的状态
watch(
() => props.isFlushed,
(newVal) => {
if (!newVal) {
isFlushedData.value = false;
}
},
);
// 是否加载总
let isLoadData = ref(false);
// 同步父组件的加载状态
watch(
() => props.isLoad,
(newVal) => {
isLoadData.value = newVal;
},
);
// 暴漏的方法
let emit = defineEmits(["update:isFlushed", "onRefresh", "update:isLoad", "onLoad"]);
// 触发下拉状态 将状态传递给父组件
const changeIsFlushed = () => {
emit("update:isFlushed", true);
};
// 传递给父组件下拉刷新
const onRefresh = () => {
emit("onRefresh");
};
// 加载数据
const onLoad = () => {
emit("onLoad");
};
</script>
<template>
<div class="babyDynamics">
<van-pull-refresh
v-model="isFlushedData"
@change="changeIsFlushed"
@refresh="onRefresh"
class="PullRefresh"
:pull-distance="pullDistance"
:head-height="headHeight"
v-bind="flushedOpt"
>
<template v-slot:pulling>
<slot name="pulling">
<img :src="dropDownImg" alt="" class="refreshIcon" />
<div class="refreshText">下拉刷新</div>
</slot>
</template>
<template v-slot:loosing>
<slot name="loosing">
<img :src="flushedImg" alt="" class="refreshIcon" />
<div class="refreshText">松开看看</div>
</slot>
</template>
<template v-slot:loading>
<slot name="loading">
<img :src="dropDownImg" alt="" class="refreshIcon" />
<div class="refreshText">加载中……</div>
</slot>
</template>
<template v-slot:success>
<slot name="success">
<img :src="dropDownImg" alt="" class="refreshIcon" />
<div class="refreshText">刷新成功</div>
</slot>
</template>
<emptyPage v-if="isEmptyPage" :msg="emptyText"></emptyPage>
<van-list
v-model:loading="isLoadData"
:finished="loadingIsComplete"
finished-text="没有更多了"
@load="onLoad"
v-bind="listOpt"
v-else
>
<template v-slot:loading>
<slot name="loading">
<div class="loading">拼命加载中……</div>
</slot>
</template>
<template v-slot:finished>
<slot name="finished">
<div class="finished">没有更多了</div>
</slot>
</template>
<template v-slot:error>
<slot name="error">
<div class="finished">加载失败,请重试</div>
</slot>
</template>
<slot></slot>
</van-list>
</van-pull-refresh>
</div>
</template>
<style lang="scss" scoped>
.babyDynamics {
width: 100%;
height: 100%;
.PullRefresh {
min-height: 100%;
background: #f6f8fa;
}
}
.refreshIcon {
width: 27px;
height: 38px;
}
.refreshText {
font-size: 10px;
color: #ff96be;
line-height: 14px;
}
.loading,
.finished {
color: #b38d9c;
font-family: "PingFang SC";
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: normal;
margin: 10px 0;
}
</style>
使用
在请求数据的时候需要设置加载状态为**
true**,否则不会上拉加载
<script setup name="babyDynamics">
// 上拉刷新 下拉加载 组件
import refreshLoading from "@com/refreshLoading.vue";
import dynamic from "@img/clientPages/dynamic.png";
import attendance from "@img/clientPages/attendance.png";
import photoAlbum from "@img/clientPages/photoAlbum.png";
// 下拉刷新
const isFlushed = ref(false);
// 下拉刷新
const onRefresh = () => {
setTimeout(() => {
// 下拉刷新
onLoad("refresh");
isFlushed.value = false;
}, 1000);
};
// 列表数据
const list = ref([]);
// 是否加载
const isLoad = ref(false);
// 是否完成
const loadingIsComplete = ref(false);
// 加载更多 type有值就是刷新
const onLoad = (type) => {
// 必须添加这一句
isLoad.value = true;
setTimeout(() => {
let dataList = [];
for (let i = 0; i < 10; i++) {
let item = {
type: Math.floor(i / 3), // 类型
title: "标题",
time: "时间",
subheading: "副标题",
};
dataList.push(item);
}
// 处理数据
dataList.map((item) => {
item.typeStr = item.type == 0 ? "类型1" : item.type == 1 ? "类型2" : "类型3";
item.icon = item.type == 0 ? dynamic : item.type == 1 ? attendance : photoAlbum;
});
if (type === "refresh") {
list.value = dataList;
loadingIsComplete.value = false;
} else {
list.value = [...list.value, ...dataList];
}
// 加载状态结束
isLoad.value = false;
// 数据全部加载完成
if (list.value.length >= 40) {
loadingIsComplete.value = true;
}
}, 500);
};
// 打开详情
const openDetails = (data) => {
console.log("🚀 ~ openDetails ~ data:", data);
};
onMounted(() => {
onLoad();
});
</script>
<template>
<refreshLoading
@onRefresh="onRefresh"
v-model:isFlushed="isFlushed"
v-model:isLoad="isLoad"
:loadingIsComplete="loadingIsComplete"
@onLoad="onLoad"
:isEmptyPage="list.length == 0"
emptyText="空页面提示"
>
<div v-for="(item, index) in list" :key="index" class="item" @click="openDetails(item)">
<div class="typeRow">
<div class="type">
<img :src="item.icon" alt="" class="icon" />
<div class="hint">{{ item.typeStr }}</div>
</div>
<div class="time">{{ item.time }}</div>
</div>
<div class="title">{{ item.title }}</div>
<div class="subheading">{{ item.subheading }}</div>
</div>
</refreshLoading>
</template>
<style lang="scss" scoped>
.item {
width: 345px;
min-height: 105px;
border-radius: 16px;
background: #fff;
margin: 0 auto;
margin-top: 20px;
padding: 10px 20px;
box-sizing: border-box;
.typeRow {
display: flex;
align-items: center;
justify-content: space-between;
color: #b38d9c;
font-family: "PingFang SC";
font-size: 12px;
.type {
display: flex;
align-items: center;
.icon {
width: 18px;
height: 18px;
margin-right: 10px;
}
}
}
.title {
color: #46001c;
font-family: "PingFang SC";
font-size: 14px;
margin: 20px 0 10px;
}
.subheading {
color: #b38d9c;
font-family: "PingFang SC";
font-size: 12px;
}
}
</style>