手机端列表加载组件
功能描述
- 适用手机端,实现列表加载功能。
实现方案
基础用法
<template><PageList:getList="getList"style="height:100%;"><template#default="{ item }"><!-- 渲染列表项 --><divclass="list-item">{{ item.name }}</div></template><template#empty><div>没有数据可显示</div></template></PageList></template><scriptsetup>import{ref}from"vue";importPageListfrom"@/components/PageList/index.vue";// 根据实际路径引入组件constquery=ref({memberId:123,});constgetList=async({pageNum,pageSize})=>{// 在这里实现你的数据请求逻辑query(其他参数 如 memberId等)constres=awaitqueryList({pageNum,pageSize,...query.value});returnres;};</script>2. 自定义加载文字和属性
你可以通过options属性来自定义加载文字和其他参数。options是一个对象,支持以下属性:
pageNum: 当前页码,默认值为1。pageSize: 每页显示的数据条数,默认值为10。finishedText: 当数据加载完毕时显示的文本,默认值为到底了。loadingText: 加载中的文本,默认值为加载中...。
2.1 示例
constoptions={pageNum:1,pageSize:20,// 自定义每页显示20条数据finishedText:"没有更多数据了",// 自定义加载完毕提示loadingText:"请稍等,加载中...",// 自定义加载提示};4. 搜索功能
如果需要在列表中实现搜索功能,你可以在请求数据时传递搜索参数,并在getList方法中处理。
4.1 实现步骤
- 添加搜索输入框: 在你的组件中添加一个搜索框,通过输入获取搜索关键词。
- 更新请求参数: 将输入的搜索关键词添加到请求参数中。
4.2 示例
<template><input v-model="searchQuery"placeholder="搜索..."@input="handleSearch"/><PageList:options="options":getList="fetchData"><template #default="{ item }"><divclass="list-item">{{item.name}}</div></template></PageList></template><script setup>import{ref}from'vue';importPageListfrom'./PageList.vue';constsearchQuery=ref('');consthandleSearch=()=>{// 重置搜索 刷新列表数据proxy.$refs["pagelistRef"].refresh();};constfetchData=async({pageNum,pageSize})=>{// 在这里实现你的数据请求逻辑query(其他参数 如 memberId等)constres=awaitqueryList({pageNum,pageSize,...searchQuery.value})returnres;};</script>设计思路
- 借鉴 Element Plus 的自定义指令: 在组件设计中,主要沿用了 Element Plus 提供的
v-infinite-scroll自定义指令,这样可以充分利用现有的成熟解决方案,实现无限滚动加载功能,确保在处理大量数据时能够高效且流畅地加载列表内容。 - 灵活的公共参数设置: 设计中为主要公共参数设置了默认值,使得组件在使用时更加灵活和易于配置。用户可以根据具体需求自定义
pageNum、pageSize、finishedText和loadingText等属性,以适应不同场景下的使用,提升了组件的通用性和适应性。 - 使用 Vue 3 插槽实现列表内容渲染: 列表内容的渲染采用了 Vue 3 的插槽机制,使得使用者可以方便地自定义每个列表项的显示方式。这种设计不仅提高了组件的可扩展性,还允许开发者根据具体需求自定义列表项的样式和内容,从而提供更好的使用体验。
组件代码
<template><divstyle="overflow-y:auto"v-infinite-scroll="getListData":infinite-scroll-distance="50"><slotv-for="(item, index) in listData":key="index":item="item"></slot><divclass="list-tip"><divv-if="count === 0"><divv-if="$slots.empty"><slotname="empty"></slot></div><el-emptyv-elsedescription="暂无数据"/></div><divclass="by-divider"v-if="count > 0 && listData.length >= count">{{ props.options.finishedText }}</div><divclass="list-loading"v-if="loading"v-loading="loading":element-loading-text="props.options.loadingText"></div></div></div></template><scriptsetupname="PageList">import{ref,toRefs}from"vue";constprops=defineProps({//配置参数options:{type:Object,default:()=>({pageNum:1,pageSize:10,finishedText:"到底了",loadingText:"加载中...",}),},//请求列表数据接口getList:{type:Function,default:()=>()=>{},},});constlistData=ref([]);//列表数据constloading=ref(false);//加载状态constcount=ref(-1);constqueryParams=ref({pageNum:props.options.pageNum,pageSize:props.options.pageSize,});//请求参数constgetListData=async()=>{// 处于加载状态和已经加载完毕,则不再请求数据if(loading.value||(listData.value.length>=count.value&&count.value!=-1))return;loading.value=true;try{const{pageNum,pageSize}=queryParams.value;constres=awaitprops.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;}};constrefresh=()=>{count.value=-1;queryParams.value.pageNum=1;listData.value=[];getListData();};// 定义 loadMore 方法constloadMore=()=>{console.log("Pulled to the top, loading more items...");// 在这里添加加载数据的逻辑,例如发起 API 请求// 示例:items.value.push(...newItems);};// 定义内部指令constpullDown={mounted(el,binding){constcallback=binding.value;// 获取传入的回调函数if(typeofcallback!=="function"){thrownewError("v-pull-down binding value must be a function");}constonScroll=()=>{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><stylelang="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>