vant实现上拉加载 下拉刷新

767 阅读2分钟

最近公司app改版,有些页面需要使用h5编写,现在遇到一个问题,页面需要实现上拉加载 下拉刷新的功能,在项目中使用的是vant组件库 使用的vue3框架

下拉刷新功能使用PullRefresh组件,上拉加载可以使用List组件

通过这两个组件可以实现上拉加载 下拉刷新

为了在项目中方便使用封装成一个组件,通过插槽设置刷新的效果和加载的文本

同时考虑到列表有可能会存在空的情况,因此需要一个空白页面,而空白页面是需要一定的提示,因此还需要封装一个空页面的组件,提示文本进行传递,如果展示的图片不一样可以在传递一个type属性用来区分图片

这样我们就封装了一个上拉加载 下拉刷新 包含空白页的组件

效果

recording

空白页组件

<script setup name="emptyPage">
defineProps({
  msg: {
    type: String,
    default: "暂无数据",
  },
});
</script><template>
  <div class="emptyPage">
    <img src="@img/clientPages/emptyPage.png" alt="" class="pageImg" />
    <div class="msg">{{ msg }}</div>
  </div>
</template>
<style lang="scss" scoped>
.emptyPage {
  width: 100vw;
  height: 80vh;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  .pageImg {
    width: 200px;
    height: 200px;
  }
  .msg {
    color: #fccae0;
    font-family: "PingFang SC";
    font-size: 12px;
    font-style: normal;
    font-weight: 400;
    line-height: normal;
  }
}
</style>

上拉加载 下拉刷新组件

组件上绑定的数据需要使用v-model,要不然会存在问题

<script setup name="babyDynamics">
import dropDownImg from "@img/refresh/tutu@2x.png";
import flushedImg from "@img/refresh/tutub@2x.png";
import emptyPage from "@com/emptyPage.vue";
​
let props = defineProps({
  //   下拉刷新参数
  // 是否下拉刷新  刷新完成之后,会传递false
  isFlushed: {
    type: Boolean,
    default: false,
  },
  //   触发下拉刷新的距离
  pullDistance: {
    type: [Number, String],
    default: 50,
  },
  // 顶部内容高度
  headHeight: {
    type: [Number, String],
    default: 80,
  },
  //   其他参数
  flushedOpt: {
    type: Object,
    default: () => {},
  },
​
  //   加载状态
  isLoad: {
    type: Boolean,
    default: false,
  },
  //   是否加载完成
  loadingIsComplete: {
    type: Boolean,
    default: false,
  },
  //   其他参数
  listOpt: {
    type: Object,
    default: () => {},
  },
  //   是否是空页面
  isEmptyPage: {
    type: Boolean,
    default: false,
  },
  emptyText: {
    type: String,
    default: "暂无数据",
  },
});
// 是否下拉刷新
let isFlushedData = ref(false);
// 刷新完成之后会传递false,修改组件内的状态
watch(
  () => props.isFlushed,
  (newVal) => {
    if (!newVal) {
      isFlushedData.value = false;
    }
  },
);
// 是否加载总
let isLoadData = ref(false);
// 同步父组件的加载状态
watch(
  () => props.isLoad,
  (newVal) => {
    isLoadData.value = newVal;
  },
);
​
// 暴漏的方法
let emit = defineEmits(["update:isFlushed", "onRefresh", "update:isLoad", "onLoad"]);
// 触发下拉状态 将状态传递给父组件
const changeIsFlushed = () => {
  emit("update:isFlushed", true);
};
// 传递给父组件下拉刷新
const onRefresh = () => {
  emit("onRefresh");
};
​
// 加载数据
const onLoad = () => {
  emit("onLoad");
};
</script><template>
  <div class="babyDynamics">
    <van-pull-refresh
      v-model="isFlushedData"
      @change="changeIsFlushed"
      @refresh="onRefresh"
      class="PullRefresh"
      :pull-distance="pullDistance"
      :head-height="headHeight"
      v-bind="flushedOpt"
    >
      <template v-slot:pulling>
        <slot name="pulling">
          <img :src="dropDownImg" alt="" class="refreshIcon" />
          <div class="refreshText">下拉刷新</div>
        </slot>
      </template>
      <template v-slot:loosing>
        <slot name="loosing">
          <img :src="flushedImg" alt="" class="refreshIcon" />
          <div class="refreshText">松开看看</div>
        </slot>
      </template>
      <template v-slot:loading>
        <slot name="loading">
          <img :src="dropDownImg" alt="" class="refreshIcon" />
          <div class="refreshText">加载中……</div>
        </slot>
      </template>
      <template v-slot:success>
        <slot name="success">
          <img :src="dropDownImg" alt="" class="refreshIcon" />
          <div class="refreshText">刷新成功</div>
        </slot>
      </template>
      <emptyPage v-if="isEmptyPage" :msg="emptyText"></emptyPage>
​
      <van-list
        v-model:loading="isLoadData"
        :finished="loadingIsComplete"
        finished-text="没有更多了"
        @load="onLoad"
        v-bind="listOpt"
        v-else
      >
        <template v-slot:loading>
          <slot name="loading">
            <div class="loading">拼命加载中……</div>
          </slot>
        </template>
        <template v-slot:finished>
          <slot name="finished">
            <div class="finished">没有更多了</div>
          </slot>
        </template>
        <template v-slot:error>
          <slot name="error">
            <div class="finished">加载失败,请重试</div>
          </slot>
        </template>
        <slot></slot>
      </van-list>
    </van-pull-refresh>
  </div>
</template>
<style lang="scss" scoped>
.babyDynamics {
  width: 100%;
  height: 100%;
  .PullRefresh {
    min-height: 100%;
    background: #f6f8fa;
  }
}
.refreshIcon {
  width: 27px;
  height: 38px;
}
.refreshText {
  font-size: 10px;
  color: #ff96be;
  line-height: 14px;
}
.loading,
.finished {
  color: #b38d9c;
  font-family: "PingFang SC";
  font-size: 12px;
  font-style: normal;
  font-weight: 400;
  line-height: normal;
  margin: 10px 0;
}
</style>

使用

在请求数据的时候需要设置加载状态为**true**,否则不会上拉加载

<script setup name="babyDynamics">
    // 上拉刷新  下拉加载 组件
import refreshLoading from "@com/refreshLoading.vue";
import dynamic from "@img/clientPages/dynamic.png";
import attendance from "@img/clientPages/attendance.png";
import photoAlbum from "@img/clientPages/photoAlbum.png";
​
// 下拉刷新
const isFlushed = ref(false);
​
// 下拉刷新
const onRefresh = () => {
  setTimeout(() => {
    // 下拉刷新
    onLoad("refresh");
    isFlushed.value = false;
  }, 1000);
};
// 列表数据
const list = ref([]);
// 是否加载
const isLoad = ref(false);
// 是否完成
const loadingIsComplete = ref(false);
​
// 加载更多  type有值就是刷新
const onLoad = (type) => {
// 必须添加这一句
  isLoad.value = true;
    
  setTimeout(() => {
    let dataList = [];
    for (let i = 0; i < 10; i++) {
      let item = {
        type: Math.floor(i / 3), // 类型
        title: "标题",
        time: "时间",
        subheading: "副标题",
      };
      dataList.push(item);
    }
    // 处理数据
    dataList.map((item) => {
      item.typeStr = item.type == 0 ? "类型1" : item.type == 1 ? "类型2" : "类型3";
      item.icon = item.type == 0 ? dynamic : item.type == 1 ? attendance : photoAlbum;
    });
    if (type === "refresh") {
      list.value = dataList;
      loadingIsComplete.value = false;
    } else {
      list.value = [...list.value, ...dataList];
    }
    // 加载状态结束
    isLoad.value = false;
    // 数据全部加载完成
    if (list.value.length >= 40) {
      loadingIsComplete.value = true;
    }
  }, 500);
};
​
// 打开详情
const openDetails = (data) => {
  console.log("🚀 ~ openDetails ~ data:", data);
};
​
onMounted(() => {
  
  onLoad();
});
</script><template>
  <refreshLoading
    @onRefresh="onRefresh"
    v-model:isFlushed="isFlushed"
    v-model:isLoad="isLoad"
    :loadingIsComplete="loadingIsComplete"
    @onLoad="onLoad"
    :isEmptyPage="list.length == 0"
    emptyText="空页面提示"
  >
    <div v-for="(item, index) in list" :key="index" class="item" @click="openDetails(item)">
      <div class="typeRow">
        <div class="type">
          <img :src="item.icon" alt="" class="icon" />
          <div class="hint">{{ item.typeStr }}</div>
        </div>
        <div class="time">{{ item.time }}</div>
      </div>
      <div class="title">{{ item.title }}</div>
      <div class="subheading">{{ item.subheading }}</div>
    </div>
  </refreshLoading>
</template>
<style lang="scss" scoped>
.item {
  width: 345px;
  min-height: 105px;
  border-radius: 16px;
  background: #fff;
  margin: 0 auto;
  margin-top: 20px;
  padding: 10px 20px;
  box-sizing: border-box;
  .typeRow {
    display: flex;
    align-items: center;
    justify-content: space-between;
    color: #b38d9c;
    font-family: "PingFang SC";
    font-size: 12px;
    .type {
      display: flex;
      align-items: center;
      .icon {
        width: 18px;
        height: 18px;
        margin-right: 10px;
      }
    }
  }
  .title {
    color: #46001c;
    font-family: "PingFang SC";
    font-size: 14px;
    margin: 20px 0 10px;
  }
  .subheading {
    color: #b38d9c;
    font-family: "PingFang SC";
    font-size: 12px;
  }
}
</style>