Files
cpms_operation_platform/src/hooks/usePagination.ts
T
2026-08-06 11:39:51 +08:00

193 lines
5.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 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);
}