vue3手机端列表加载组件

手机端列表加载组件

功能描述

  • 适用手机端,实现列表加载功能。

实现方案

基础用法

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 实现步骤
  1. 添加搜索输入框: 在你的组件中添加一个搜索框,通过输入获取搜索关键词。
  2. 更新请求参数: 将输入的搜索关键词添加到请求参数中。
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>

设计思路

  1. 借鉴 Element Plus 的自定义指令 : 在组件设计中,主要沿用了 Element Plus 提供的 v-infinite-scroll 自定义指令,这样可以充分利用现有的成熟解决方案,实现无限滚动加载功能,确保在处理大量数据时能够高效且流畅地加载列表内容。
  2. 灵活的公共参数设置 : 设计中为主要公共参数设置了默认值,使得组件在使用时更加灵活和易于配置。用户可以根据具体需求自定义 pageNumpageSizefinishedTextloadingText 等属性,以适应不同场景下的使用,提升了组件的通用性和适应性。
  3. 使用 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>
相关推荐
崔庆才丨静觅5 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60616 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了6 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅6 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅7 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅7 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment7 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅7 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊7 小时前
jwt介绍
前端
爱敲代码的小鱼7 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax