193 lines
5.2 KiB
TypeScript
193 lines
5.2 KiB
TypeScript
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||
// src/hooks/usePagination.ts
|
||
import { computed, ref, Ref, triggerRef } from 'vue';
|
||
import { useEffect } from './useEffect';
|
||
|
||
/**
|
||
* 分页配置项
|
||
*/
|
||
export interface UsePaginationOptions {
|
||
/** 当前页码 (支持 v-model 双向绑定) */
|
||
current?: number;
|
||
/** 每页条数 (支持 v-model 双向绑定) */
|
||
pageSize?: number;
|
||
/** 数据总条数 (支持 v-model 双向绑定) */
|
||
total?: number;
|
||
/** 默认页码 (非受控模式下使用) */
|
||
defaultCurrent?: number;
|
||
/** 默认每页条数 (非受控模式下使用) */
|
||
defaultPageSize?: number;
|
||
/** 每页条数切换选项 (用于生成下拉菜单) */
|
||
pageSizeOptions?: number[];
|
||
/** 是否显示快速跳转 (可选功能) */
|
||
showQuickJumper?: boolean;
|
||
}
|
||
|
||
/**
|
||
* 分页操作 API
|
||
*/
|
||
export interface UsePaginationActions {
|
||
/** 设置当前页 */
|
||
setCurrent: (current: number) => void;
|
||
/** 设置每页条数 (会自动重置页码为 1) */
|
||
setPageSize: (size: number) => void;
|
||
/** 设置总条数 */
|
||
setTotal: (total: number) => void;
|
||
/** 下一页 */
|
||
next: () => void;
|
||
/** 上一页 */
|
||
prev: () => void;
|
||
/** 跳转到指定页 */
|
||
jump: (page: number) => void;
|
||
/** 重置到初始状态 */
|
||
reset: () => void;
|
||
/** 强制刷新 (用于某些极端响应式场景) */
|
||
refresh: () => void;
|
||
}
|
||
|
||
/**
|
||
* 分页计算属性
|
||
*/
|
||
export interface UsePaginationComputed {
|
||
/** 响应式请求参数对象 (给 API 用的) */
|
||
params: Ref<{ page: number; pageSize: number }>;
|
||
/** 总页数 */
|
||
totalPages: Ref<number>;
|
||
/** 是否有上一页 */
|
||
hasPrev: Ref<boolean>;
|
||
/** 是否有下一页 */
|
||
hasNext: Ref<boolean>;
|
||
/** 当前页起始索引 (用于显示 "显示 1-10 条") */
|
||
startIndex: Ref<number>;
|
||
/** 当前页结束索引 */
|
||
endIndex: Ref<number>;
|
||
/** 是否为第一页 */
|
||
isFirstPage: Ref<boolean>;
|
||
/** 是否为最后一页 */
|
||
isLastPage: Ref<boolean>;
|
||
}
|
||
|
||
export type UsePaginationReturn = UsePaginationOptions &
|
||
UsePaginationActions &
|
||
UsePaginationComputed;
|
||
|
||
export function usePagination(options: UsePaginationOptions = {}): UsePaginationReturn {
|
||
const { defaultCurrent = 1, defaultPageSize = 10, pageSizeOptions = [10, 20, 50, 100] } = options;
|
||
|
||
const currentRef = ref(options.current ?? defaultCurrent);
|
||
const pageSizeRef = ref(options.pageSize ?? defaultPageSize);
|
||
const totalRef = ref(options.total ?? 0);
|
||
|
||
useEffect(() => {
|
||
const val = options.current;
|
||
if (val !== undefined) currentRef.value = val;
|
||
}, [() => options.current]);
|
||
|
||
useEffect(() => {
|
||
const val = options.pageSize;
|
||
if (val !== undefined) pageSizeRef.value = val;
|
||
}, [() => options.pageSize]);
|
||
|
||
useEffect(() => {
|
||
const val = options.total;
|
||
if (val !== undefined) totalRef.value = val;
|
||
}, [() => options.total]);
|
||
|
||
const totalPages = computed(() => {
|
||
const total = totalRef.value;
|
||
const size = pageSizeRef.value;
|
||
return size === 0 ? 0 : Math.ceil(total / size);
|
||
});
|
||
|
||
const hasPrev = computed(() => currentRef.value > 1);
|
||
const hasNext = computed(() => currentRef.value < totalPages.value);
|
||
const isFirstPage = computed(() => currentRef.value === 1);
|
||
const isLastPage = computed(() => currentRef.value === totalPages.value);
|
||
|
||
const startIndex = computed(() =>
|
||
totalPages.value === 0 ? 0 : (currentRef.value - 1) * pageSizeRef.value + 1,
|
||
);
|
||
|
||
const endIndex = computed(() => Math.min(currentRef.value * pageSizeRef.value, totalRef.value));
|
||
|
||
const params = computed(() => ({
|
||
page: currentRef.value,
|
||
pageSize: pageSizeRef.value,
|
||
}));
|
||
|
||
const setCurrent = (page: number) => {
|
||
const safePage = Math.max(1, Math.min(page, totalPages.value || 1));
|
||
if (currentRef.value !== safePage) {
|
||
currentRef.value = safePage;
|
||
}
|
||
};
|
||
|
||
const setPageSize = (size: number) => {
|
||
if (pageSizeRef.value !== size) {
|
||
pageSizeRef.value = size;
|
||
currentRef.value = 1;
|
||
}
|
||
};
|
||
|
||
const setTotal = (total: number) => {
|
||
totalRef.value = total;
|
||
const maxPage = Math.max(1, Math.ceil(total / pageSizeRef.value));
|
||
if (currentRef.value > maxPage) {
|
||
currentRef.value = maxPage;
|
||
}
|
||
};
|
||
|
||
const next = () => setCurrent(currentRef.value + 1);
|
||
const prev = () => setCurrent(currentRef.value - 1);
|
||
|
||
const jump = (page: number) => {
|
||
if (!Number.isInteger(page) || page < 1) return;
|
||
setCurrent(page);
|
||
};
|
||
|
||
const reset = () => {
|
||
currentRef.value = defaultCurrent;
|
||
pageSizeRef.value = defaultPageSize;
|
||
};
|
||
|
||
const refresh = () => {
|
||
triggerRef(currentRef);
|
||
};
|
||
|
||
return {
|
||
// @ts-ignore
|
||
current: currentRef,
|
||
// @ts-ignore
|
||
pageSize: pageSizeRef,
|
||
// @ts-ignore
|
||
total: totalRef,
|
||
pageSizeOptions,
|
||
params,
|
||
totalPages,
|
||
hasPrev,
|
||
hasNext,
|
||
isFirstPage,
|
||
isLastPage,
|
||
startIndex,
|
||
endIndex,
|
||
setCurrent,
|
||
setPageSize,
|
||
setTotal,
|
||
next,
|
||
prev,
|
||
jump,
|
||
reset,
|
||
refresh,
|
||
};
|
||
}
|
||
|
||
/** 将接口返回的分页 total 同步到 pagination(支持 number / 数字字符串,含 0) */
|
||
export function syncPaginationTotal(
|
||
pagination: Pick<UsePaginationReturn, 'setTotal'>,
|
||
total: unknown,
|
||
): void {
|
||
if (total == null || total === '') return;
|
||
const n = Number(total);
|
||
if (Number.isFinite(n)) pagination.setTotal(n);
|
||
}
|