两个组件解决移动端列表

移动端列表页几乎都是「下拉刷新 + 上拉加载更多」的组合。Vant 用两个组件搞定:

  • van-pull-refresh:下拉刷新容器
  • van-list:上拉加载更多(无限滚动)

最简示例

<template>
  <van-pull-refresh v-model="refreshing" @refresh="onRefresh">
    <van-list v-model:loading="loading" :finished="finished" @load="onLoad">
      <van-cell
        v-for="item in list"
        :key="item.id"
        :title="item.title"
        :label="item.summary"
      />
    </van-list>
  </van-pull-refresh>
</template>

<script setup>
import { ref } from "vue";

const list = ref([]);
const loading = ref(false);
const finished = ref(false);
const refreshing = ref(false);
let page = 0;

const onLoad = () => {
  setTimeout(() => {
    page++;
    const items = Array.from({ length: 10 }, (_, i) => ({
      id: page * 10 + i,
      title: `第 ${page} 页 - 文章 ${i + 1}`,
      summary: "这是文章摘要...",
    }));
    list.value.push(...items);
    loading.value = false;
    if (page >= 5) finished.value = true;
  }, 1000);
};

const onRefresh = () => {
  setTimeout(() => {
    page = 0;
    list.value = [];
    finished.value = false;
    refreshing.value = false;
    onLoad();
  }, 1000);
};
</script>

关键点解释

  • van-list v-model:loading:双向绑定 loading 状态,加载完数据要手动设为 false
  • finished:为 true 时触发"没有更多"状态,不再触发 load 事件
  • @load:滚动到底部时触发,需要自己控制分页逻辑
  • van-pull-refresh:包裹 van-list,v-model 绑定刷新状态

实战:对接真实接口

<template>
  <van-pull-refresh v-model="refreshing" @refresh="onRefresh">
    <van-list
      v-model:loading="loading"
      :finished="finished"
      finished-text="没有更多了"
      @load="onLoad"
    >
      <van-cell
        v-for="item in list"
        :key="item.id"
        :title="item.title"
        :label="item.description"
        is-link
        @click="goDetail(item.id)"
      />
    </van-list>
  </van-pull-refresh>
</template>

<script setup>
import { ref } from "vue";
import { showToast } from "vant";
import { useRouter } from "vue-router";
import { fetchArticleList } from "@/api/article";

const router = useRouter();
const list = ref([]);
const loading = ref(false);
const finished = ref(false);
const refreshing = ref(false);
const page = ref(1);
const pageSize = 10;
const total = ref(0);

const onLoad = async () => {
  try {
    const { data } = await fetchArticleList({
      page: page.value,
      pageSize,
    });
    list.value.push(...data.list);
    total.value = data.total;
    page.value++;
    if (list.value.length >= total.value) finished.value = true;
  } catch (e) {
    showToast("加载失败");
  } finally {
    loading.value = false;
  }
};

const onRefresh = async () => {
  try {
    const { data } = await fetchArticleList({ page: 1, pageSize });
    list.value = data.list;
    total.value = data.total;
    page.value = 2;
    finished.value = false;
  } catch (e) {
    showToast("刷新失败");
  } finally {
    refreshing.value = false;
  }
};

const goDetail = (id) => router.push(`/article/${id}`);
</script>

常见问题

List 不触发 load 事件?

通常是因为容器没有固定高度。给 van-list 或其父容器设置一个有限高度(或放在页面容器里),让它可以滚动。

finished 什么时候设为 true?

当加载到的数据量 >= 后端返回的 total 时,或者返回空数组时。

下拉刷新后为什么不自动加载?

需要手动在 onRefresh 里调用 onLoad(),或者把 loading 设为 true 触发 van-list 再次加载。

List 属性速查

属性类型说明
v-model:loadingboolean是否处于加载中
finishedboolean是否加载完成
finished-textstring加载完成提示文字
errorboolean加载是否出错
error-textstring错误提示文字
offsetnumber距离底部多远触发加载