手机端列表加载组件
功能描述
- 适用手机端,实现列表加载功能。
实现方案
基础用法
html
<template>
<PageList :getList="getList" style="height:100%;">
<template #default="{ item }">
<!-- 渲染列表项 -->
<div class="list-item">{{ item.name }}</div>
</template>
<template #empty>
<div>没有数据可显示</div>
</template>
</PageList>
</template>
<script setup>
import { ref } from "vue";
import PageList from "@/components/PageList/index.vue"; // 根据实际路径引入组件
const query = ref({
memberId: 123,
});
const getList = async ({ pageNum, pageSize }) => {
// 在这里实现你的数据请求逻辑query(其他参数 如 memberId等)
const res = await queryList({ pageNum, pageSize, ...query.value });
return res;
};
</script>
2. 自定义加载文字和属性
你可以通过 options 属性来自定义加载文字和其他参数。options 是一个对象,支持以下属性:
pageNum: 当前页码,默认值为1。pageSize: 每页显示的数据条数,默认值为10。finishedText: 当数据加载完毕时显示的文本,默认值为到底了。loadingText: 加载中的文本,默认值为加载中...。
2.1 示例
javascript
const options = {
pageNum: 1,
pageSize: 20, // 自定义每页显示20条数据
finishedText: "没有更多数据了", // 自定义加载完毕提示
loadingText: "请稍等,加载中...", // 自定义加载提示
};
4. 搜索功能
如果需要在列表中实现搜索功能,你可以在请求数据时传递搜索参数,并在 getList 方法中处理。
4.1 实现步骤
- 添加搜索输入框: 在你的组件中添加一个搜索框,通过输入获取搜索关键词。
- 更新请求参数: 将输入的搜索关键词添加到请求参数中。
4.2 示例
javascript
<template>
<input v-model="searchQuery" placeholder="搜索..." @input="handleSearch" />
<PageList
:options="options"
:getList="fetchData"
>
<template #default="{ item }">
<div class="list-item">{{ item.name }}</div>
</template>
</PageList>
</template>
<script setup>
import { ref } from 'vue';
import PageList from './PageList.vue';
const searchQuery = ref('');
const handleSearch = () => {
// 重置搜索 刷新列表数据
proxy.$refs["pagelistRef"].refresh();
};
const fetchData = async ({ pageNum, pageSize }) => {
// 在这里实现你的数据请求逻辑query(其他参数 如 memberId等)
const res = await queryList({ pageNum, pageSize ,...searchQuery.value})
return res;
};
</script>
设计思路
- 借鉴 Element Plus 的自定义指令 : 在组件设计中,主要沿用了 Element Plus 提供的
v-infinite-scroll自定义指令,这样可以充分利用现有的成熟解决方案,实现无限滚动加载功能,确保在处理大量数据时能够高效且流畅地加载列表内容。 - 灵活的公共参数设置 : 设计中为主要公共参数设置了默认值,使得组件在使用时更加灵活和易于配置。用户可以根据具体需求自定义
pageNum、pageSize、finishedText和loadingText等属性,以适应不同场景下的使用,提升了组件的通用性和适应性。 - 使用 Vue 3 插槽实现列表内容渲染: 列表内容的渲染采用了 Vue 3 的插槽机制,使得使用者可以方便地自定义每个列表项的显示方式。这种设计不仅提高了组件的可扩展性,还允许开发者根据具体需求自定义列表项的样式和内容,从而提供更好的使用体验。
组件代码
html
<template>
<div style="overflow-y: auto" v-infinite-scroll="getListData" :infinite-scroll-distance="50">
<slot v-for="(item, index) in listData" :key="index" :item="item"></slot>
<div class="list-tip">
<div v-if="count === 0">
<div v-if="$slots.empty">
<slot name="empty"></slot>
</div>
<el-empty v-else description="暂无数据" />
</div>
<div class="by-divider" v-if="count > 0 && listData.length >= count">{{ props.options.finishedText }}</div>
<div class="list-loading" v-if="loading" v-loading="loading" :element-loading-text="props.options.loadingText"></div>
</div>
</div>
</template>
<script setup name="PageList">
import { ref, toRefs } from "vue";
const props = defineProps({
//配置参数
options: {
type: Object,
default: () => ({
pageNum: 1,
pageSize: 10,
finishedText: "到底了",
loadingText: "加载中...",
}),
},
//请求列表数据接口
getList: {
type: Function,
default: () => () => {},
},
});
const listData = ref([]); //列表数据
const loading = ref(false); //加载状态
const count = ref(-1);
const queryParams = ref({
pageNum: props.options.pageNum,
pageSize: props.options.pageSize,
}); //请求参数
const getListData = async () => {
// 处于加载状态和已经加载完毕,则不再请求数据
if (loading.value || (listData.value.length >= count.value && count.value != -1)) return;
loading.value = true;
try {
const { pageNum, pageSize } = queryParams.value;
const res = await props.getList({ pageNum, pageSize });
if (res.code == "0") {
listData.value = listData.value.concat(res.data);
count.value = res.count;
queryParams.value.pageNum++;
}
loading.value = false;
} catch (error) {
loading.value = false;
}
};
const refresh = () => {
count.value = -1;
queryParams.value.pageNum = 1;
listData.value = [];
getListData();
};
// 定义 loadMore 方法
const loadMore = () => {
console.log("Pulled to the top, loading more items...");
// 在这里添加加载数据的逻辑,例如发起 API 请求
// 示例:items.value.push(...newItems);
};
// 定义内部指令
const pullDown = {
mounted(el, binding) {
const callback = binding.value; // 获取传入的回调函数
if (typeof callback !== "function") {
throw new Error("v-pull-down binding value must be a function");
}
const onScroll = () => {
const { scrollTop } = el;
console.log(scrollTop);
// 判断是否滚动到顶部
if (scrollTop === 0) {
callback(); // 调用回调函数
}
};
el.addEventListener("scroll", onScroll);
// 清理工作
el._onScroll = onScroll; // 保存引用以便在 unmounted 中使用
},
unmounted(el) {
el.removeEventListener("scroll", el._onScroll); // 移除事件监听
},
};
defineExpose({
pullDown,
listData,
loading,
refresh,
});
</script>
<style lang="scss" scoped>
.list-loading {
--el-loading-spinner-size: 30px;
--el-color-primary: #969799;
height: var(--el-loading-spinner-size);
margin: 15px 0px;
background: transparent;
:deep(.el-loading-spinner) {
display: flex;
justify-content: center;
align-items: center;
}
:deep(.el-loading-text) {
margin-left: 10px;
}
:deep(.el-loading-mask) {
background: transparent;
}
}
.by-divider {
margin: 16px 0px;
color: #969799;
font-size: 14px;
line-height: 24px;
border-color: #ebedf0;
border-style: solid;
border-width: 0;
align-items: center;
display: flex;
&:before,
&:after {
content: "";
box-sizing: border-box;
border-color: inherit;
border-style: inherit;
border-width: 1px 0 0;
flex: 1;
height: 1px;
display: block;
}
&:before {
margin-right: 16px;
}
&:after {
margin-left: 16px;
}
}
</style>