Skip to content

usePaging Vue 分页管理器

适用于 Vue setup 方便管理相关分页接口,简化接口调用步骤,ts 类型提示友好,接口调用采用队列模式同一时间仅允许调用一个接口后续的调用排队处理

代码实现

ts
import { ref, Ref } from 'vue'; // or vue-demi

export interface UsePagingOption {
  pageIndex: number;
  pageSize: number;
  /** 监听数据发生变化时执行 refresh */
  watch?: any[];
  /** 立即执行 */
  immediate?: boolean;
}

export interface UsePagingHandleReturn<R> {
  /** 记录数据 */
  list?: R[];
  /** 是否有下一页 */
  hasNext?: boolean;
  /** 记录数据总数 */
  total?: number | null;
  /** 记录数据总页数 */
  totalPage?: number | null;
}

export type UsePagingHandle<R> = (option: UsePagingOption) => Promise<UsePagingHandleReturn<R>>;

const defOptions: UsePagingOption = { pageIndex: 1, pageSize: 20 };

export function usePaging<R>(handle: UsePagingHandle<R>, userOption?: Partial<UsePagingOption>) {
  const option = ref({ ...defOptions, ...userOption });
  const list = ref<R[]>([]) as Ref<R[]>;
  const loading = ref<boolean>(false);
  const hasNext = ref<boolean>(true);
  const total = ref<number>(0);
  const totalPage = ref<number>(0);
  const empty = computed(() => list.value.length === 0 && !loading.value);

  let preHandle: Promise<void> | void;

  const waiting = async () => {
    const pre = preHandle;
    let done: () => void;
    preHandle = new Promise<void>(res => (done = res));
    if (pre) await pre;
    return done!;
  };

  const load = async () => {
    loading.value = true;
    const result = await handle(option.value);
    if (result.list) list.value.push(...result.list);
    hasNext.value = Boolean(result.hasNext);
    total.value = result.total || 0;
    totalPage.value = result.totalPage || 0;
    loading.value = false;
  };

  const next = async () => {
    const done = await waiting();
    await load();
    option.value.pageIndex++;
    done();
  };

  const refresh = async (_option?: Partial<UsePagingOption>) => {
    const done = await waiting();
    option.value = { ...option.value, pageIndex: userOption?.pageIndex ?? defOptions.pageIndex, ..._option };
    list.value = [];
    await load();
    option.value.pageIndex++;
    done();
  };

  if (option.value.watch) watch([...option.value.watch], () => refresh());
  if (option.value.immediate) onMounted(next);
  return { option, list, loading, hasNext, empty, total, totalPage, next, refresh };
}

使用示例

ts
let count = 1;
const fakeFetch = async (op: UsePagingOption) => {
  console.log(count++, op.pageIndex);
  await new Promise(res => setTimeout(res, Math.random() * 1000));
  return { dataList: [], hasNext: true };
};

const paging = usePaging(async ({ pageIndex, pageSize }) => {
  const { dataList, hasNext } = (await fakeFetch({ pageIndex, pageSize })) || {};
  return { list: dataList, hasNext };
});

setTimeout(async () => {
  paging.next();
  paging.next();
  paging.refresh();
  paging.next();
  paging.next();
  paging.refresh({ pageSize: 30 });
  paging.refresh();
  paging.next();
  await paging.refresh();
  console.log('paging: ', paging);
}, 100);
ts
const store = useUserStore();
const addressActive = ref<string>('110000');

const paging = usePaging(
  async ({ pageIndex, pageSize }) => {
    const { list, totalPage } =
      (await store.fetchTravelStrategy({ pageNum: pageIndex, pageSize, regionIdPath: addressActive.value, queryValue: search.value || void 0 })) ??
      {};
    return { list: list || [], hasNext: (totalPage ?? 0) > pageIndex };
  },
  { pageSize: 10, watch: [addressActive, search], immediate: true }
);