feat: 本次提交更新内容如下

定版本转移2025年7月17日
This commit is contained in:
2025-07-17 10:22:38 +08:00
parent 0f860d01e4
commit 92a3d407a7
645 changed files with 30755 additions and 118800 deletions

View 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;
}

View 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
};
}

View 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 };
};

View 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 };
};