147 lines
3.9 KiB
TypeScript
147 lines
3.9 KiB
TypeScript
import { useState, useCallback, useMemo } from 'react';
|
|
|
|
/**
|
|
* 批量选择Hook
|
|
* 用于管理列表项的批量选择状态
|
|
*/
|
|
export interface UseBatchSelectionOptions {
|
|
/** 是否启用批量选择模式 */
|
|
enabled?: boolean;
|
|
/** 最大选择数量限制 */
|
|
maxSelection?: number;
|
|
}
|
|
|
|
export interface UseBatchSelectionReturn {
|
|
/** 当前选中的项目ID列表 */
|
|
selectedIds: string[];
|
|
/** 是否处于批量选择模式 */
|
|
isSelectionMode: boolean;
|
|
/** 是否全选状态 */
|
|
isAllSelected: boolean;
|
|
/** 是否部分选中状态 */
|
|
isIndeterminate: boolean;
|
|
/** 选中项目数量 */
|
|
selectedCount: number;
|
|
/** 切换批量选择模式 */
|
|
toggleSelectionMode: () => void;
|
|
/** 选择/取消选择单个项目 */
|
|
toggleItem: (id: string) => void;
|
|
/** 全选/取消全选 */
|
|
toggleAll: (allIds: string[]) => void;
|
|
/** 清空选择 */
|
|
clearSelection: () => void;
|
|
/** 检查项目是否被选中 */
|
|
isSelected: (id: string) => boolean;
|
|
/** 设置选中的项目ID列表 */
|
|
setSelectedIds: (ids: string[]) => void;
|
|
}
|
|
|
|
export function useBatchSelection(
|
|
options: UseBatchSelectionOptions = {}
|
|
): UseBatchSelectionReturn {
|
|
const { enabled = true, maxSelection } = options;
|
|
|
|
const [selectedIds, setSelectedIds] = useState<string[]>([]);
|
|
const [isSelectionMode, setIsSelectionMode] = useState(false);
|
|
|
|
// 切换批量选择模式
|
|
const toggleSelectionMode = useCallback(() => {
|
|
if (!enabled) return;
|
|
|
|
setIsSelectionMode(prev => {
|
|
const newMode = !prev;
|
|
// 退出选择模式时清空选择
|
|
if (!newMode) {
|
|
setSelectedIds([]);
|
|
}
|
|
return newMode;
|
|
});
|
|
}, [enabled]);
|
|
|
|
// 选择/取消选择单个项目
|
|
const toggleItem = useCallback((id: string) => {
|
|
if (!enabled || !isSelectionMode) return;
|
|
|
|
setSelectedIds(prev => {
|
|
const isCurrentlySelected = prev.includes(id);
|
|
|
|
if (isCurrentlySelected) {
|
|
// 取消选择
|
|
return prev.filter(selectedId => selectedId !== id);
|
|
} else {
|
|
// 选择项目
|
|
if (maxSelection && prev.length >= maxSelection) {
|
|
console.warn(`最多只能选择 ${maxSelection} 个项目`);
|
|
return prev;
|
|
}
|
|
return [...prev, id];
|
|
}
|
|
});
|
|
}, [enabled, isSelectionMode, maxSelection]);
|
|
|
|
// 全选/取消全选
|
|
const toggleAll = useCallback((allIds: string[]) => {
|
|
if (!enabled || !isSelectionMode) return;
|
|
|
|
setSelectedIds(prev => {
|
|
const isAllSelected = allIds.length > 0 && allIds.every(id => prev.includes(id));
|
|
|
|
if (isAllSelected) {
|
|
// 取消全选
|
|
return [];
|
|
} else {
|
|
// 全选(考虑最大选择数量限制)
|
|
const idsToSelect = maxSelection ? allIds.slice(0, maxSelection) : allIds;
|
|
return idsToSelect;
|
|
}
|
|
});
|
|
}, [enabled, isSelectionMode, maxSelection]);
|
|
|
|
// 清空选择
|
|
const clearSelection = useCallback(() => {
|
|
setSelectedIds([]);
|
|
}, []);
|
|
|
|
// 检查项目是否被选中
|
|
const isSelected = useCallback((id: string) => {
|
|
return selectedIds.includes(id);
|
|
}, [selectedIds]);
|
|
|
|
// 计算全选状态
|
|
const { isAllSelected, isIndeterminate } = useMemo(() => {
|
|
return {
|
|
isAllSelected: false, // 这里需要外部传入allIds才能计算
|
|
isIndeterminate: selectedIds.length > 0
|
|
};
|
|
}, [selectedIds]);
|
|
|
|
return {
|
|
selectedIds,
|
|
isSelectionMode,
|
|
isAllSelected,
|
|
isIndeterminate,
|
|
selectedCount: selectedIds.length,
|
|
toggleSelectionMode,
|
|
toggleItem,
|
|
toggleAll,
|
|
clearSelection,
|
|
isSelected,
|
|
setSelectedIds
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 计算全选状态的辅助函数
|
|
*/
|
|
export function calculateSelectionState(selectedIds: string[], allIds: string[]) {
|
|
const selectedCount = selectedIds.length;
|
|
const totalCount = allIds.length;
|
|
|
|
return {
|
|
isAllSelected: totalCount > 0 && selectedCount === totalCount,
|
|
isIndeterminate: selectedCount > 0 && selectedCount < totalCount,
|
|
selectedCount,
|
|
totalCount
|
|
};
|
|
}
|