feat: 本次提交更新内容如下
定版本转移2025年7月17日
This commit is contained in:
17
Cunkebao/src/hooks/use-debounce.ts
Normal file
17
Cunkebao/src/hooks/use-debounce.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
export function useDebounce<T>(value: T, delay: number): T {
|
||||
const [debouncedValue, setDebouncedValue] = useState<T>(value);
|
||||
|
||||
useEffect(() => {
|
||||
const handler = setTimeout(() => {
|
||||
setDebouncedValue(value);
|
||||
}, delay);
|
||||
|
||||
return () => {
|
||||
clearTimeout(handler);
|
||||
};
|
||||
}, [value, delay]);
|
||||
|
||||
return debouncedValue;
|
||||
}
|
||||
80
Cunkebao/src/hooks/useAuthGuard.ts
Normal file
80
Cunkebao/src/hooks/useAuthGuard.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useNavigate, useLocation } from 'react-router-dom';
|
||||
import { useAuth } from '@/contexts/AuthContext';
|
||||
|
||||
// 不需要登录的公共页面路径
|
||||
const PUBLIC_PATHS = [
|
||||
'/login',
|
||||
'/register',
|
||||
'/forgot-password',
|
||||
'/reset-password',
|
||||
'/404',
|
||||
'/500'
|
||||
];
|
||||
|
||||
/**
|
||||
* 认证守卫Hook
|
||||
* 用于在组件中检查用户是否已登录
|
||||
* @param requireAuth 是否需要认证,默认为true
|
||||
* @param redirectTo 未认证时重定向的路径,默认为'/login'
|
||||
*/
|
||||
export function useAuthGuard(requireAuth: boolean = true, redirectTo: string = '/login') {
|
||||
const { isAuthenticated, isLoading } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
|
||||
// 检查当前路径是否是公共页面
|
||||
const isPublicPath = PUBLIC_PATHS.some(path =>
|
||||
location.pathname.startsWith(path)
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
// 如果正在加载,不进行任何跳转
|
||||
if (isLoading) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果需要认证但未登录且不是公共页面
|
||||
if (requireAuth && !isAuthenticated && !isPublicPath) {
|
||||
// 保存当前URL,登录后可以重定向回来
|
||||
const returnUrl = encodeURIComponent(window.location.href);
|
||||
navigate(`${redirectTo}?returnUrl=${returnUrl}`, { replace: true });
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果已登录但在登录页面,重定向到首页
|
||||
if (isAuthenticated && location.pathname === '/login') {
|
||||
navigate('/', { replace: true });
|
||||
return;
|
||||
}
|
||||
}, [isAuthenticated, isLoading, location.pathname, navigate, requireAuth, redirectTo, isPublicPath]);
|
||||
|
||||
return {
|
||||
isAuthenticated,
|
||||
isLoading,
|
||||
isPublicPath,
|
||||
// 是否应该显示内容
|
||||
shouldRender: !isLoading && (isAuthenticated || isPublicPath || !requireAuth)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 简单的认证检查Hook
|
||||
* 只返回认证状态,不进行自动重定向
|
||||
*/
|
||||
export function useAuthCheck() {
|
||||
const { isAuthenticated, isLoading } = useAuth();
|
||||
const location = useLocation();
|
||||
|
||||
const isPublicPath = PUBLIC_PATHS.some(path =>
|
||||
location.pathname.startsWith(path)
|
||||
);
|
||||
|
||||
return {
|
||||
isAuthenticated,
|
||||
isLoading,
|
||||
isPublicPath,
|
||||
// 是否需要认证
|
||||
requiresAuth: !isPublicPath
|
||||
};
|
||||
}
|
||||
182
Cunkebao/src/hooks/useBackNavigation.ts
Normal file
182
Cunkebao/src/hooks/useBackNavigation.ts
Normal file
@@ -0,0 +1,182 @@
|
||||
import { useCallback, useRef, useEffect } from 'react';
|
||||
import { useNavigate, useLocation } from 'react-router-dom';
|
||||
|
||||
interface BackNavigationOptions {
|
||||
/** 默认返回路径,当没有历史记录时使用 */
|
||||
defaultPath?: string;
|
||||
/** 是否在组件卸载时保存当前路径到历史记录 */
|
||||
saveOnUnmount?: boolean;
|
||||
/** 最大历史记录数量 */
|
||||
maxHistoryLength?: number;
|
||||
/** 自定义返回逻辑 */
|
||||
customBackLogic?: (history: string[], currentPath: string) => string | null;
|
||||
}
|
||||
|
||||
interface BackNavigationReturn {
|
||||
/** 返回上一页 */
|
||||
goBack: () => void;
|
||||
/** 返回到指定路径 */
|
||||
goTo: (path: string) => void;
|
||||
/** 返回到首页 */
|
||||
goHome: () => void;
|
||||
/** 检查是否可以返回 */
|
||||
canGoBack: () => boolean;
|
||||
/** 获取历史记录 */
|
||||
getHistory: () => string[];
|
||||
/** 清除历史记录 */
|
||||
clearHistory: () => void;
|
||||
/** 当前路径 */
|
||||
currentPath: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 高级返回导航Hook
|
||||
* 提供更智能的返回逻辑和历史记录管理
|
||||
*/
|
||||
export const useBackNavigation = (options: BackNavigationOptions = {}): BackNavigationReturn => {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const historyRef = useRef<string[]>([]);
|
||||
const {
|
||||
defaultPath = '/',
|
||||
saveOnUnmount = true,
|
||||
maxHistoryLength = 10,
|
||||
customBackLogic
|
||||
} = options;
|
||||
|
||||
// 保存路径到历史记录
|
||||
const saveToHistory = useCallback((path: string) => {
|
||||
const history = historyRef.current;
|
||||
|
||||
// 如果路径已经存在,移除它
|
||||
const filteredHistory = history.filter(p => p !== path);
|
||||
|
||||
// 添加到开头
|
||||
filteredHistory.unshift(path);
|
||||
|
||||
// 限制历史记录长度
|
||||
if (filteredHistory.length > maxHistoryLength) {
|
||||
filteredHistory.splice(maxHistoryLength);
|
||||
}
|
||||
|
||||
historyRef.current = filteredHistory;
|
||||
}, [maxHistoryLength]);
|
||||
|
||||
// 获取历史记录
|
||||
const getHistory = useCallback(() => {
|
||||
return [...historyRef.current];
|
||||
}, []);
|
||||
|
||||
// 清除历史记录
|
||||
const clearHistory = useCallback(() => {
|
||||
historyRef.current = [];
|
||||
}, []);
|
||||
|
||||
// 检查是否可以返回
|
||||
const canGoBack = useCallback(() => {
|
||||
return historyRef.current.length > 1 || window.history.length > 1;
|
||||
}, []);
|
||||
|
||||
// 返回上一页
|
||||
const goBack = useCallback(() => {
|
||||
const history = getHistory();
|
||||
|
||||
// 如果有自定义返回逻辑,使用它
|
||||
if (customBackLogic) {
|
||||
const targetPath = customBackLogic(history, location.pathname);
|
||||
if (targetPath) {
|
||||
navigate(targetPath);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果有历史记录,返回到上一个路径
|
||||
if (history.length > 1) {
|
||||
const previousPath = history[1]; // 当前路径在索引0,上一个在索引1
|
||||
navigate(previousPath);
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果浏览器历史记录有上一页,使用浏览器返回
|
||||
if (window.history.length > 1) {
|
||||
navigate(-1);
|
||||
return;
|
||||
}
|
||||
|
||||
// 最后回退到默认路径
|
||||
navigate(defaultPath);
|
||||
}, [navigate, location.pathname, getHistory, customBackLogic, defaultPath]);
|
||||
|
||||
// 返回到指定路径
|
||||
const goTo = useCallback((path: string) => {
|
||||
navigate(path);
|
||||
}, [navigate]);
|
||||
|
||||
// 返回到首页
|
||||
const goHome = useCallback(() => {
|
||||
navigate('/');
|
||||
}, [navigate]);
|
||||
|
||||
// 组件挂载时保存当前路径
|
||||
useEffect(() => {
|
||||
saveToHistory(location.pathname);
|
||||
}, [location.pathname, saveToHistory]);
|
||||
|
||||
// 组件卸载时保存路径(可选)
|
||||
useEffect(() => {
|
||||
if (!saveOnUnmount) return;
|
||||
|
||||
return () => {
|
||||
saveToHistory(location.pathname);
|
||||
};
|
||||
}, [location.pathname, saveToHistory, saveOnUnmount]);
|
||||
|
||||
return {
|
||||
goBack,
|
||||
goTo,
|
||||
goHome,
|
||||
canGoBack,
|
||||
getHistory,
|
||||
clearHistory,
|
||||
currentPath: location.pathname
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 简化的返回Hook,只提供基本的返回功能
|
||||
*/
|
||||
export const useSimpleBack = (defaultPath: string = '/') => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const goBack = useCallback(() => {
|
||||
if (window.history.length > 1) {
|
||||
navigate(-1);
|
||||
} else {
|
||||
navigate(defaultPath);
|
||||
}
|
||||
}, [navigate, defaultPath]);
|
||||
|
||||
return { goBack };
|
||||
};
|
||||
|
||||
/**
|
||||
* 带确认的返回Hook
|
||||
*/
|
||||
export const useConfirmBack = (
|
||||
message: string = '确定要离开当前页面吗?',
|
||||
defaultPath: string = '/'
|
||||
) => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const goBack = useCallback(() => {
|
||||
if (window.confirm(message)) {
|
||||
if (window.history.length > 1) {
|
||||
navigate(-1);
|
||||
} else {
|
||||
navigate(defaultPath);
|
||||
}
|
||||
}
|
||||
}, [navigate, message, defaultPath]);
|
||||
|
||||
return { goBack };
|
||||
};
|
||||
265
Cunkebao/src/hooks/useThrottledRequest.ts
Normal file
265
Cunkebao/src/hooks/useThrottledRequest.ts
Normal file
@@ -0,0 +1,265 @@
|
||||
import { useCallback, useRef, useState, useEffect } from 'react';
|
||||
import { requestDeduplicator, requestCancelManager } from '../api/utils';
|
||||
|
||||
// 节流请求Hook
|
||||
export const useThrottledRequest = <T extends (...args: any[]) => any>(
|
||||
requestFn: T,
|
||||
delay: number = 1000
|
||||
) => {
|
||||
const lastCallRef = useRef(0);
|
||||
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
const throttledRequest = useCallback(
|
||||
((...args: any[]) => {
|
||||
const now = Date.now();
|
||||
|
||||
if (now - lastCallRef.current < delay) {
|
||||
// 如果在节流时间内,取消之前的定时器并设置新的
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current);
|
||||
}
|
||||
|
||||
timeoutRef.current = setTimeout(() => {
|
||||
lastCallRef.current = now;
|
||||
requestFn(...args);
|
||||
}, delay - (now - lastCallRef.current));
|
||||
} else {
|
||||
// 如果超过节流时间,直接执行
|
||||
lastCallRef.current = now;
|
||||
requestFn(...args);
|
||||
}
|
||||
}) as T,
|
||||
[requestFn, delay]
|
||||
);
|
||||
|
||||
return throttledRequest;
|
||||
};
|
||||
|
||||
// 防抖请求Hook
|
||||
export const useDebouncedRequest = <T extends (...args: any[]) => any>(
|
||||
requestFn: T,
|
||||
delay: number = 300
|
||||
) => {
|
||||
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
const debouncedRequest = useCallback(
|
||||
((...args: any[]) => {
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current);
|
||||
}
|
||||
|
||||
timeoutRef.current = setTimeout(() => {
|
||||
requestFn(...args);
|
||||
}, delay);
|
||||
}) as T,
|
||||
[requestFn, delay]
|
||||
);
|
||||
|
||||
return debouncedRequest;
|
||||
};
|
||||
|
||||
// 带加载状态的请求Hook
|
||||
export const useRequestWithLoading = <T extends (...args: any[]) => Promise<any>>(
|
||||
requestFn: T
|
||||
) => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const requestWithLoading = useCallback(
|
||||
(async (...args: any[]) => {
|
||||
if (loading) {
|
||||
console.log('请求正在进行中,跳过重复请求');
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
const result = await requestFn(...args);
|
||||
return result;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}) as T,
|
||||
[requestFn, loading]
|
||||
);
|
||||
|
||||
return { requestWithLoading, loading };
|
||||
};
|
||||
|
||||
// 组合Hook:节流 + 加载状态
|
||||
export const useThrottledRequestWithLoading = <T extends (...args: any[]) => Promise<any>>(
|
||||
requestFn: T,
|
||||
delay: number = 1000
|
||||
) => {
|
||||
const { requestWithLoading, loading } = useRequestWithLoading(requestFn);
|
||||
const throttledRequest = useThrottledRequest(requestWithLoading, delay);
|
||||
|
||||
return { throttledRequest, loading };
|
||||
};
|
||||
|
||||
// 带错误处理的请求Hook
|
||||
export const useRequestWithError = <T extends (...args: any[]) => Promise<any>>(
|
||||
requestFn: T
|
||||
) => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const requestWithError = useCallback(
|
||||
(async (...args: any[]) => {
|
||||
setError(null);
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
const result = await requestFn(...args);
|
||||
return result;
|
||||
} catch (err) {
|
||||
const errorMessage = err instanceof Error ? err.message : '请求失败';
|
||||
setError(errorMessage);
|
||||
throw err;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}) as T,
|
||||
[requestFn]
|
||||
);
|
||||
|
||||
return { requestWithError, loading, error, clearError: () => setError(null) };
|
||||
};
|
||||
|
||||
// 带重试的请求Hook
|
||||
export const useRequestWithRetry = <T extends (...args: any[]) => Promise<any>>(
|
||||
requestFn: T,
|
||||
maxRetries: number = 3,
|
||||
retryDelay: number = 1000
|
||||
) => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [retryCount, setRetryCount] = useState(0);
|
||||
|
||||
const requestWithRetry = useCallback(
|
||||
(async (...args: any[]) => {
|
||||
setLoading(true);
|
||||
setRetryCount(0);
|
||||
|
||||
let lastError: any;
|
||||
|
||||
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
||||
try {
|
||||
setRetryCount(attempt);
|
||||
const result = await requestFn(...args);
|
||||
return result;
|
||||
} catch (error) {
|
||||
lastError = error;
|
||||
|
||||
if (attempt === maxRetries) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
// 等待后重试
|
||||
await new Promise(resolve => setTimeout(resolve, retryDelay));
|
||||
}
|
||||
}
|
||||
|
||||
throw lastError;
|
||||
}) as T,
|
||||
[requestFn, maxRetries, retryDelay]
|
||||
);
|
||||
|
||||
return { requestWithRetry, loading, retryCount };
|
||||
};
|
||||
|
||||
// 可取消的请求Hook
|
||||
export const useCancellableRequest = <T extends (...args: any[]) => Promise<any>>(
|
||||
requestFn: T
|
||||
) => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const abortControllerRef = useRef<AbortController | null>(null);
|
||||
|
||||
const cancellableRequest = useCallback(
|
||||
(async (...args: any[]) => {
|
||||
// 取消之前的请求
|
||||
if (abortControllerRef.current) {
|
||||
abortControllerRef.current.abort();
|
||||
}
|
||||
|
||||
// 创建新的AbortController
|
||||
abortControllerRef.current = new AbortController();
|
||||
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
const result = await requestFn(...args);
|
||||
return result;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
abortControllerRef.current = null;
|
||||
}
|
||||
}) as T,
|
||||
[requestFn]
|
||||
);
|
||||
|
||||
const cancelRequest = useCallback(() => {
|
||||
if (abortControllerRef.current) {
|
||||
abortControllerRef.current.abort();
|
||||
setLoading(false);
|
||||
abortControllerRef.current = null;
|
||||
}
|
||||
}, []);
|
||||
|
||||
// 组件卸载时取消请求
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (abortControllerRef.current) {
|
||||
abortControllerRef.current.abort();
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return { cancellableRequest, loading, cancelRequest };
|
||||
};
|
||||
|
||||
// 组合Hook:节流 + 加载状态 + 错误处理
|
||||
export const useThrottledRequestWithError = <T extends (...args: any[]) => Promise<any>>(
|
||||
requestFn: T,
|
||||
delay: number = 1000
|
||||
) => {
|
||||
const { requestWithError, loading, error, clearError } = useRequestWithError(requestFn);
|
||||
const throttledRequest = useThrottledRequest(requestWithError, delay);
|
||||
|
||||
return { throttledRequest, loading, error, clearError };
|
||||
};
|
||||
|
||||
// 组合Hook:防抖 + 加载状态 + 错误处理
|
||||
export const useDebouncedRequestWithError = <T extends (...args: any[]) => Promise<any>>(
|
||||
requestFn: T,
|
||||
delay: number = 300
|
||||
) => {
|
||||
const { requestWithError, loading, error, clearError } = useRequestWithError(requestFn);
|
||||
const debouncedRequest = useDebouncedRequest(requestWithError, delay);
|
||||
|
||||
return { debouncedRequest, loading, error, clearError };
|
||||
};
|
||||
|
||||
// 请求状态监控Hook
|
||||
export const useRequestMonitor = () => {
|
||||
const [pendingCount, setPendingCount] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
const updatePendingCount = () => {
|
||||
setPendingCount(requestDeduplicator.getPendingCount());
|
||||
};
|
||||
|
||||
// 初始更新
|
||||
updatePendingCount();
|
||||
|
||||
// 定期检查待处理请求数量
|
||||
const interval = setInterval(updatePendingCount, 100);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
const cancelAllRequests = useCallback(() => {
|
||||
requestCancelManager.cancelAllRequests();
|
||||
requestDeduplicator.clear();
|
||||
}, []);
|
||||
|
||||
return { pendingCount, cancelAllRequests };
|
||||
};
|
||||
Reference in New Issue
Block a user