186 lines
4.7 KiB
TypeScript
186 lines
4.7 KiB
TypeScript
|
|
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||
|
|
// src/hooks/usePagination.ts
|
||
|
|
import { computed, ref, watch, Ref, triggerRef } from 'vue';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 分页配置项
|
||
|
|
*/
|
||
|
|
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);
|
||
|
|
|
||
|
|
watch(
|
||
|
|
() => options.current,
|
||
|
|
(val) => {
|
||
|
|
if (val !== undefined) currentRef.value = val;
|
||
|
|
},
|
||
|
|
);
|
||
|
|
watch(
|
||
|
|
() => options.pageSize,
|
||
|
|
(val) => {
|
||
|
|
if (val !== undefined) pageSizeRef.value = val;
|
||
|
|
},
|
||
|
|
);
|
||
|
|
watch(
|
||
|
|
() => options.total,
|
||
|
|
(val) => {
|
||
|
|
if (val !== undefined) totalRef.value = val;
|
||
|
|
},
|
||
|
|
);
|
||
|
|
|
||
|
|
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,
|
||
|
|
};
|
||
|
|
}
|