完整教程:vue3手机端列表加载组件

手机端列表加载组件

功能描述

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

实现方案

基础用法

<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 示例
const options = {
pageNum: 1,
pageSize: 20, // 自定义每页显示20条数据
finishedText: "没有更多数据了", // 自定义加载完毕提示
loadingText: "请稍等,加载中...", // 自定义加载提示
};

4. 搜索功能

如果需要在列表中实现搜索功能,你可以在请求数据时传递搜索参数,并在 getList 方法中处理。

4.1 实现步骤
  1. 添加搜索输入框: 在你的组件中添加一个搜索框,通过输入获取搜索关键词。
  2. 更新请求参数: 将输入的搜索关键词添加到请求参数中。
4.2 示例
<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 的插槽机制,使得使用者可以方便地自定义每个列表项的显示方式。这种设计不仅提高了组件的可扩展性,还允许开发者根据具体需求自定义列表项的样式和内容,从而提供更好的使用体验。

组件代码

<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>
posted @ 2026-01-15 17:12  clnchanpin  阅读(2)  评论(0)    收藏  举报