Skip to content

列表

版本说明

时间修改人备注
2025-04-24YG补充小程序scroll-view用法

列表需要可以下拉刷新上拉加载

示例项目:
svn://39.107.13.238:12580/base_digi
/_uniapp/src/pages/demo/list/list.vue

一、基础规范

列表样式

  • 列表项高度应保持一致,避免高度不一致导致的视觉跳动
  • 列表项之间应有明确的分割线或间距,提高可读性
  • 列表项内容应对齐,保持视觉上的整齐
  • 列表项点击态应明确,提供用户反馈
  • 列表为空时应显示空状态提示,而非空白页面

列表性能

  • 列表项应避免过度嵌套,减少渲染层级
  • 列表图片应懒加载,减少初始加载时间
  • 列表数据应分页加载,避免一次性请求大量数据

二、交互规范

下拉刷新


  • 需要有下拉即可刷新、释放即可刷新、加载中、加载成功的提示
  • 只有在滚动到最顶部才能下拉刷新
  • 需要有过渡动画
  • 刷新时应显示加载指示器,并禁用其他操作
  • 刷新完成后应有成功提示,并自动消失
  • 刷新失败时应有错误提示,并提供重试选项

上拉加载


  • 需要有上拉即可加载、释放即可加载、加载中、加载成功的提示
  • 只有在滚动到最底部才能上拉加载
  • 需要有过渡动画
  • 加载完成后应无缝衔接新内容,避免视觉跳动
  • 所有数据加载完毕时,应显示"没有更多了"提示
  • 加载失败时应有错误提示,并提供重试选项

三、实现方式

1. h5实现

vue
import List from "@/components/list/index.vue";

<List ref="list" :openLoad="openLoad" @refresh="loadTop" @load="loadBottom">
/*列表内容*/
</List>

说明

参数openLoad控制是否开启上拉加载,当列表没有全部加载,openLoad 置为 true,滑动到底部时上拉触发上拉加载;当列表全部加载,openLoad 置为 false,上拉时会显示没有更多了; 事件refresh,下拉刷新触发; 事件load,上拉加载触发。

注: 请求完数据需要调用this.$refs.list.endUpate()结束加载(刷新)动画。

Props

参数说明类型默认值
openLoad是否开启上拉加载booleanfalse
maxRemoving可拉动的最大的距离,单位:rpxnumber200
allLoadHint全部加载完,上拉时显示的文案string没有更多了
refreshTime刷新时动画时间,为 0 永久存在,可通过触发 endUpate()关闭number0
loadTime加载时动画时间,为 0 永久存在,可通过触发 endUpate()关闭number0

Events

事件名说明回调参数
refresh下拉刷新触发-
load上拉加载触发-

方法

方法名说明参数返回值
endUpate结束加载(刷新)动画--

  • 注:列表的宽高设置的 100%,由父元素大小决定。加载和刷新也是在父元素里。

2. UniApp原生实现

vue
<template>
  <view class="list-container">
    <scroll-view 
      class="scroll-view"
      scroll-y
      refresher-enabled
      :refresher-triggered="isRefreshing"
      @refresherrefresh="onRefresh"
      @scrolltolower="onLoadMore"
    >
      <!-- 列表内容 -->
      <view class="list-item" v-for="(item, index) in list" :key="index">
        <text>{{ item.title }}</text>
      </view>
      
      <!-- 加载更多状态 -->
      <view class="loading-more" v-if="list.length > 0">
        <text v-if="isLoadingMore">加载中...</text>
        <text v-else-if="hasMore">上拉加载更多</text>
        <text v-else>已加载全部</text>
      </view>
      
      <!-- 空状态 -->
      <n-empty-data v-if="!isLoading && list.length === 0" />
    </scroll-view>
  </view>
</template>

<script setup>
import { ref, onMounted } from 'vue';

// 列表数据
const list = ref([]);
// 状态控制
const isLoading = ref(true);
const isRefreshing = ref(false);
const isLoadingMore = ref(false);
const hasMore = ref(true);
const page = ref(1);
const pageSize = ref(10);

// 初始加载
onMounted(() => {
  loadData();
});

// 加载数据
const loadData = async () => {
  try {
    const res = await fetchData(page.value, pageSize.value);
    list.value = res.data;
    hasMore.value = res.hasMore;
  } catch (error) {
    uni.showToast({
      title: '加载失败',
      icon: 'none'
    });
  } finally {
    isLoading.value = false;
    isRefreshing.value = false;
  }
};

// 下拉刷新
const onRefresh = async () => {
  isRefreshing.value = true;
  page.value = 1;
  await loadData();
  uni.showToast({
    title: '刷新成功',
    icon: 'success',
    duration: 1000
  });
};

// 上拉加载更多
const onLoadMore = async () => {
  if (isLoadingMore.value || !hasMore.value) return;
  
  isLoadingMore.value = true;
  page.value += 1;
  
  try {
    const res = await fetchData(page.value, pageSize.value);
    list.value = [...list.value, ...res.data];
    hasMore.value = res.hasMore;
  } catch (error) {
    page.value -= 1;
    uni.showToast({
      title: '加载失败',
      icon: 'none'
    });
  } finally {
    isLoadingMore.value = false;
  }
};

// 模拟数据请求
const fetchData = (page, pageSize) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      const total = 35;
      const start = (page - 1) * pageSize;
      const end = Math.min(start + pageSize, total);
      const data = [];
      
      for (let i = start; i < end; i++) {
        data.push({
          id: i + 1,
          title: `列表项 ${i + 1}`
        });
      }
      
      resolve({
        data,
        hasMore: end < total
      });
    }, 1000);
  });
};
</script>

<style lang="scss" scoped>
.list-container {
  position: relative;
  height: 100vh;
  
  .scroll-view {
    height: 100%;
  }
  
  .list-item {
    padding: 30rpx;
    border-bottom: 1rpx solid #eee;
  }
  
  .loading-more {
    padding: 20rpx 0;
    text-align: center;
    color: #999;
    font-size: 24rpx;
  }
  
  .empty-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 100rpx 0;
    
    image {
      width: 200rpx;
      height: 200rpx;
      margin-bottom: 20rpx;
    }
    
    text {
      color: #999;
      font-size: 28rpx;
    }
  }
}

@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}
</style>

注意事项:

  • 要注意不能一开始就展示缺省页,要先展示加载的loading,如果没有数据在展示缺省页。