uniapp 小程序 vue2 组合式Api列表页hook封装

226 阅读3分钟

项目一开始用的vue2,所以后面有点空闲时间了才使用组合式Api做的列表封装。 参考了掘金里的一些文章。

hooks/usePageList.js

import { ref, reactive, toRefs } from '@vue/composition-api';
import toast from '@/utils/toast.js';

/**
 * @description 创建并管理页面列表数据的状态和操作方法。
 * 接收一个配置对象`opts`,返回包含页面状态和操作函数的对象,以便于在Vue组件中直接使用。
 * @param {Object} opts - 配置选项对象
 * @param {Function} opts.getListApi - 获取列表数据的异步函数
 * @param {Function} [opts.onFetchListSuccess] - 获取列表数据成功后的回调,默认为空函数
 * @param {Function} [opts.onFetchListFail] - 获取列表数据失败后的回调,默认为空函数
 * @returns {Object} 返回一个包含页面状态和操作方法的对象:
 * @returns {Object} page - 响应式对象,管理页面列表的状态,具体包含:
 * @returns {boolean} page.loading - 表示数据是否正在加载中。
 * @returns {boolean} page.refreshing - 表示列表是否处于刷新状态。
 * @returns {boolean} page.isEnablePullUp - 是否启用上拉加载更多逻辑。
 * @returns {string} page.listStatus - 列表加载状态,可为:"loadmore"(需加载更多)、"loading"(加载中)、"nomore"(没有更多数据)。
 * @returns {Array} page.list - 存储列表数据的数组。
 * @returns {number} page.pageIndex - 当前分页的页码。
 * @returns {number} page.pageSize - 每页显示的数据条数。
 * @returns {number} page.total - 数据库中的总记录数,用于分页计算。
 * @returns {Object} page.resData - 从后端获取的额外数据,后端老要塞其他数据。
 * @returns {Function} debounceSearch - 防抖处理的搜索函数,用于触发列表数据刷新。
 * @returns {Function} onSearch - 立即触发列表数据刷新的函数。
 * @returns {Function} onRefresh - 刷新列表数据的函数,常用于下拉刷新操作。
 * @returns {Function} loadData - 加载更多数据的函数,通常绑定至列表上拉加载事件。
 */
export default function usePage(opts) {
  console.log('接收参数', opts);
  const {
    getListApi,
    onFetchListSuccess = (opts) => {},
    onFetchListFail = (opts) => {},
  } = opts;
  const page = reactive({
    loading: false,
    refreshing: false,
    isEnablePullUp: true,
    listStatus: 'loadmore',
    list: [],
    pageIndex: 1,
    pageSize: 15,
    //   // 总共多少页 后端不给提供
    //   totalPage: 0,
    total: 0,
    resData: {},
    searchParams: {}, // 查询参数
  });

  // const {
  //   loading,
  //   refreshing,
  //   isEnablePullUp,
  //   listStatus,
  //   list,
  //   pageIndex,
  //   pageSize,
  //   // totalPage, // 如果需要 totalPage,取消注释
  //   total,
  //   resData,
  // } = toRefs(page);

  // 加载数据
  const loadData = () => {
    return new Promise((resolve, reject) => {
      console.log('加载数据');
      page.loading = true; // 在开始加载时设置加载状态为true
      getListApi()
        .then((res) => {
          if (res.status !== 200) {
            toast.showToast(res.message);
            onFetchListFail(res);
            page.loading = false; // 加载失败后重置加载状态
            reject(res);
            return;
          }

          page.total = res.data.count;
          page.resData = res.data;
          page.list.push(...res.data.list);

          if (page.list.length === res.data.count) {
            console.log('列表加载完成,没有更多数据了');
            page.isEnablePullUp = false;
            page.listStatus = 'nomore';
          }

          page.loading = false; // 加载成功后重置加载状态
          onFetchListSuccess(res.data);
          resolve(res.data);
        })
        .catch((err) => {
          console.log('列表接口失败', err);
          page.loading = false; // 加载失败后重置加载状态
          onFetchListFail(err);
          reject(err);
        });
    });
  };

  // 下拉刷新
  const onRefresh = async () => {
    page.refreshing = true;
    page.isEnablePullUp = true;
    page.list = [];
    page.listStatus = 'loadmore';
    page.pageIndex = 1;
    await loadData();
  };
  // 上拉加载
  const onPullUp = async () => {
    if (!page.isEnablePullUp) {
      console.log('没有更多数据直接退出');
      return;
    }

    page.pageIndex += 1;

    // 加载数据
    await loadData();
  };

  const onSearch = async () => {
    await onRefresh();
  };

  // 防抖
  const debounceSearch = () => {
    uni.$u.debounce(onSearch(), 1000);
  };

  return {
    page,
    debounceSearch,
    onSearch,
    onRefresh,
    onPullUp,
    loadData,
  };
}

使用 script setup 模式

xxx.vue

<template>
  <view class="page">
    <view
      style="height: 300rpx; margin-bottom: 100rpx; background-color: red"
      v-for="(item, index) in page.list"
      :key="index"
    ></view>
  </view>
</template>

<script setup>
import {
  ref,
  reactive,
  defineExpose,
  inject,
  computed,
} from '@vue/composition-api';
import {
  onReady,
  onLoad,
  onPullDownRefresh,
  onReachBottom,
} from '@dcloudio/uni-app';
import usePage from '@/hooks/usePageList.js';
import { Api } from '../../models/api.js';
import toast from '@/utils/toast.js';

const app = getApp();
const api = new Api();

// 列表相关
const getListApi = () => {
  return api.getMerchantList({
    page: page.pageIndex,
    limit: page.pageSize,
    ...page.searchParams,
  });
};

const onFetchListSuccess = (data) => {
  console.log('列表成功回调函数', data);
};

const onFetchListFail = (err) => {
  console.log('列表失败回调函数', err);
};

// 加载数据,和下拉刷新,等一系列操作,封装在 usePage 中,
const { page, debounceSearch, onSearch, onRefresh, onPullUp } = usePage({
  getListApi,
  onFetchListSuccess,
  onFetchListFail,
});

// 修改查询参数
page.searchParams = {
  test: '123',
  test1: '123',
};

console.log('xxxxxxxxx', page.searchParams);

// // 多个列表可以使用命名空间
// const newList = reactive(
//   usePage({
//     getListApi,
//     onFetchListSuccess,
//     onFetchListFail,
//   })
// );

// console.log(newList.page, "newList", newList);

onLoad(async () => {
  toast.showLoading();
  await onSearch();
  uni.hideLoading();
  console.log('onLoad初始化', page);
});

onReady(() => {
  console.log('onReady', page);
});

// 下拉刷新
onPullDownRefresh(() => {
  onRefresh();
  console.log('下拉刷新', page);
});

// 上拉加载
onReachBottom(() => {
  onPullUp();
  console.log('上拉加载', page);
});

defineExpose({
  // 注意要在 defineExpose 函数外定义
  // title,
  // testClick,
});
</script>

<style lang="scss" scoped>
@import '@/base.scss';
</style>