跳转到内容

usePaging (JS)

paging.js

js
/**
 * usePaging 分页钩子函数
 * 依赖于 lodash-es 的 cloneDeep 方法
 */
import { cloneDeep } from 'lodash-es'
import { reactive, isRef, isProxy, toRaw } from 'vue'

export const usePaging = (options) => {
  const {
    page_no = 1,
    page_size = 15,
    fetchFu,
    params = {},
    append = false,
    initialLoading = false,
    initialFetch = false,
    filterValues = [void 0],
    filterFn = (p) => p,
  } = options

  // 分页数据
  const pager = reactive({
    page_no,
    page_size,
    loading: initialLoading,
    count: 0,
    list: [],
    extra: {},
    more: true,
    finished: false,
  })

  // 记录初始参数
  const initParams = Object.freeze(cloneDeep(getRaw(params)))
  // 记录初始 pager
  const initPager = Object.freeze(cloneDeep(pager))

  let setLastOutdate = () => {}

  // 请求分页接口
  const getList = async () => {
    setLastOutdate()
    let outdated = false
    setLastOutdate = () => {
      outdated = true
    }

    pager.loading = true

    return fetchFu({
      page_no: pager.page_no,
      page_size: pager.page_size,
      ...filterFn(filterParams(params, filterValues)),
    })
      .then((res) => {
        if (outdated) return res

        // 更新分页数据
        pager.count = res?.count
        pager.extra = res?.extra
        pager.more = pager.page_no * pager.page_size < pager.count
        pager.finished = !pager.more

        // 处理数据追加逻辑
        if (append) {
          pager.list.push(...res?.list)
        } else {
          pager.list = res?.list
        }
        return res
      })
      .finally(() => {
        pager.loading = false
      })
  }

  /**
   * 重置页码
   */
  const resetPage = () => {
    pager.page_no = initPager.page_no
    getList()
  }

  /**
   * 重置参数
   */
  const resetParams = () => {
    if (isRef(params)) {
      params.value = cloneDeep(initParams)
    } else {
      // 清空 params
      for (const key in params) {
        delete params[key]
      }
      Object.assign(params, cloneDeep(initParams))
    }
  }

  /**
   * 重置 pager
   */
  const resetPager = () => {
    Object.assign(pager, cloneDeep(initPager))
  }

  /**
   * 刷新(重置分页器并重新请求)
   */
  const refresh = () => {
    resetPager()
    getList()
  }

  if (initialFetch) {
    getList()
  }

  return {
    pager,
    getList,
    resetPage,
    resetParams,
    resetPager,
    refresh,
  }
}

/**
 * 获取原始数据(辅助函数)
 */
const getRaw = (value) => {
  if (isRef(value)) {
    if (isProxy(value.value)) {
      return toRaw(value.value)
    }
    return value.value
  } else if (isProxy(value)) {
    return toRaw(value)
  } else {
    return value
  }
}

/**
 * 过滤参数(辅助函数)
 */
const filterParams = (params, values = []) => {
  const _params = { ...params }
  for (const key in _params) {
    if (values.includes(_params[key])) {
      delete _params[key]
    }
  }
  return _params
}

paging.d.ts

ts
/**
 * 分页基础参数类型
 */
export type PagingParams = {
  /** 当前页码 */
  page_no?: number
  /** 每页数量 */
  page_size?: number
}

export type Params = Record<string, any>

/**
 * 分页器数据结构
 */
export interface Pager<
  Item = any,
  Extra extends Record<string, any> = {},
> {
  /** 当前页码 */
  page_no: number
  /** 每页数量 */
  page_size: number
  /** 是否加载中 */
  loading: boolean
  /** 数据总数 */
  count: number
  /** 数据列表 */
  list: Item[]
  /** 额外数据 */
  extra: Extra
  /** 是否还有更多 */
  more: boolean
  /** 是否已经完成(对 more 的取反) */
  finished: boolean
}

/**
 * 分页钩子配置项
 */
export interface PagingOptions {
  /** 初始页数(默认1) */
  page_no?: number
  /** 初始每页数量(默认15) */
  page_size?: number
  /** 请求方法 */
  fetchFu: (params: Params) => Promise<any>
  /** 请求参数 */
  params?: Params
  /** 是否为追加模式(默认false) */
  append?: boolean
  /** 是否初始加载状态(默认false) */
  initialLoading?: boolean
  /** 是否立即执行初始请求(默认false) */
  initialFetch?: boolean
  /** 需要过滤的参数值(默认[undefined]) */
  filterValues?: any[]
  /** 自定义参数过滤函数 */
  filterFn?: (params: Params) => Params
}

/**
 * Vue分页钩子函数
 */
export declare const usePaging: <
  Item = any,
  Extra extends Record<string, any> = {},
>(
  options: PagingOptions,
) => {
  /** 分页器实例 */
  pager: Pager<Item, Extra>
  /** 执行分页请求 */
  getList: () => Promise<any>
  /** 重置页码 */
  resetPage: () => void
  /** 重置参数 */
  resetParams: () => void
  /** 重置分页器 */
  resetPager: () => void
  /** 刷新(重置并请求) */
  refresh: () => void
}