mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-02-18 10:40:50 +08:00
feat: initialize new React application structure with TypeScript, ESLint, and Prettier configurations, while removing legacy files and adding new components and pages for enhanced functionality
This commit is contained in:
10
src/hooks/index.ts
Normal file
10
src/hooks/index.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Hooks 统一导出
|
||||
*/
|
||||
|
||||
export { useApi } from './useApi';
|
||||
export { useDebounce } from './useDebounce';
|
||||
export { useLocalStorage } from './useLocalStorage';
|
||||
export { useInterval } from './useInterval';
|
||||
export { useMediaQuery } from './useMediaQuery';
|
||||
export { usePagination } from './usePagination';
|
||||
65
src/hooks/useApi.ts
Normal file
65
src/hooks/useApi.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* 通用 API 调用 Hook
|
||||
*/
|
||||
|
||||
import { useState, useCallback } from 'react';
|
||||
import { useNotificationStore } from '@/stores';
|
||||
|
||||
interface UseApiOptions<T> {
|
||||
onSuccess?: (data: T) => void;
|
||||
onError?: (error: Error) => void;
|
||||
showSuccessNotification?: boolean;
|
||||
showErrorNotification?: boolean;
|
||||
successMessage?: string;
|
||||
}
|
||||
|
||||
export function useApi<T = any, Args extends any[] = any[]>(
|
||||
apiFunction: (...args: Args) => Promise<T>,
|
||||
options: UseApiOptions<T> = {}
|
||||
) {
|
||||
const [data, setData] = useState<T | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<Error | null>(null);
|
||||
|
||||
const { showNotification } = useNotificationStore();
|
||||
|
||||
const execute = useCallback(
|
||||
async (...args: Args) => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const result = await apiFunction(...args);
|
||||
setData(result);
|
||||
|
||||
if (options.showSuccessNotification && options.successMessage) {
|
||||
showNotification(options.successMessage, 'success');
|
||||
}
|
||||
|
||||
options.onSuccess?.(result);
|
||||
return result;
|
||||
} catch (err) {
|
||||
const errorObj = err as Error;
|
||||
setError(errorObj);
|
||||
|
||||
if (options.showErrorNotification !== false) {
|
||||
showNotification(errorObj.message, 'error');
|
||||
}
|
||||
|
||||
options.onError?.(errorObj);
|
||||
throw errorObj;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
},
|
||||
[apiFunction, options, showNotification]
|
||||
);
|
||||
|
||||
const reset = useCallback(() => {
|
||||
setData(null);
|
||||
setError(null);
|
||||
setLoading(false);
|
||||
}, []);
|
||||
|
||||
return { data, loading, error, execute, reset };
|
||||
}
|
||||
21
src/hooks/useDebounce.ts
Normal file
21
src/hooks/useDebounce.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* 防抖 Hook
|
||||
*/
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
export function useDebounce<T>(value: T, delay: number = 500): T {
|
||||
const [debouncedValue, setDebouncedValue] = useState<T>(value);
|
||||
|
||||
useEffect(() => {
|
||||
const handler = setTimeout(() => {
|
||||
setDebouncedValue(value);
|
||||
}, delay);
|
||||
|
||||
return () => {
|
||||
clearTimeout(handler);
|
||||
};
|
||||
}, [value, delay]);
|
||||
|
||||
return debouncedValue;
|
||||
}
|
||||
24
src/hooks/useInterval.ts
Normal file
24
src/hooks/useInterval.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* 定时器 Hook
|
||||
*/
|
||||
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
export function useInterval(callback: () => void, delay: number | null) {
|
||||
const savedCallback = useRef<(() => void) | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
savedCallback.current = callback;
|
||||
}, [callback]);
|
||||
|
||||
useEffect(() => {
|
||||
if (delay === null) return;
|
||||
|
||||
const tick = () => {
|
||||
savedCallback.current?.();
|
||||
};
|
||||
|
||||
const id = setInterval(tick, delay);
|
||||
return () => clearInterval(id);
|
||||
}, [delay]);
|
||||
}
|
||||
32
src/hooks/useLocalStorage.ts
Normal file
32
src/hooks/useLocalStorage.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* LocalStorage Hook
|
||||
*/
|
||||
|
||||
import { useState } from 'react';
|
||||
|
||||
export function useLocalStorage<T>(
|
||||
key: string,
|
||||
initialValue: T
|
||||
): [T, (value: T | ((val: T) => T)) => void] {
|
||||
const [storedValue, setStoredValue] = useState<T>(() => {
|
||||
try {
|
||||
const item = window.localStorage.getItem(key);
|
||||
return item ? (JSON.parse(item) as T) : initialValue;
|
||||
} catch (error) {
|
||||
console.error(`Error reading localStorage key "${key}":`, error);
|
||||
return initialValue;
|
||||
}
|
||||
});
|
||||
|
||||
const setValue = (value: T | ((val: T) => T)) => {
|
||||
try {
|
||||
const valueToStore = value instanceof Function ? value(storedValue) : value;
|
||||
setStoredValue(valueToStore);
|
||||
window.localStorage.setItem(key, JSON.stringify(valueToStore));
|
||||
} catch (error) {
|
||||
console.error(`Error setting localStorage key "${key}":`, error);
|
||||
}
|
||||
};
|
||||
|
||||
return [storedValue, setValue];
|
||||
}
|
||||
27
src/hooks/useMediaQuery.ts
Normal file
27
src/hooks/useMediaQuery.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 媒体查询 Hook
|
||||
*/
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
export function useMediaQuery(query: string): boolean {
|
||||
const [matches, setMatches] = useState(() => {
|
||||
return window.matchMedia(query).matches;
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const media = window.matchMedia(query);
|
||||
|
||||
const listener = (event: MediaQueryListEvent) => {
|
||||
setMatches(event.matches);
|
||||
};
|
||||
|
||||
// Set initial value via listener to avoid direct setState in effect
|
||||
listener({ matches: media.matches } as MediaQueryListEvent);
|
||||
|
||||
media.addEventListener('change', listener);
|
||||
return () => media.removeEventListener('change', listener);
|
||||
}, [query]);
|
||||
|
||||
return matches;
|
||||
}
|
||||
59
src/hooks/usePagination.ts
Normal file
59
src/hooks/usePagination.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* 分页 Hook
|
||||
*/
|
||||
|
||||
import { useState, useMemo } from 'react';
|
||||
import type { PaginationState } from '@/types';
|
||||
|
||||
export function usePagination<T>(
|
||||
items: T[],
|
||||
initialPageSize: number = 20
|
||||
): PaginationState & {
|
||||
currentItems: T[];
|
||||
goToPage: (page: number) => void;
|
||||
nextPage: () => void;
|
||||
prevPage: () => void;
|
||||
setPageSize: (size: number) => void;
|
||||
} {
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(initialPageSize);
|
||||
|
||||
const totalItems = items.length;
|
||||
const totalPages = Math.ceil(totalItems / pageSize) || 1;
|
||||
|
||||
const currentItems = useMemo(() => {
|
||||
const start = (currentPage - 1) * pageSize;
|
||||
const end = start + pageSize;
|
||||
return items.slice(start, end);
|
||||
}, [items, currentPage, pageSize]);
|
||||
|
||||
const goToPage = (page: number) => {
|
||||
const validPage = Math.max(1, Math.min(page, totalPages));
|
||||
setCurrentPage(validPage);
|
||||
};
|
||||
|
||||
const nextPage = () => {
|
||||
goToPage(currentPage + 1);
|
||||
};
|
||||
|
||||
const prevPage = () => {
|
||||
goToPage(currentPage - 1);
|
||||
};
|
||||
|
||||
const handleSetPageSize = (size: number) => {
|
||||
setPageSize(size);
|
||||
setCurrentPage(1); // 重置到第一页
|
||||
};
|
||||
|
||||
return {
|
||||
currentPage,
|
||||
pageSize,
|
||||
totalPages,
|
||||
totalItems,
|
||||
currentItems,
|
||||
goToPage,
|
||||
nextPage,
|
||||
prevPage,
|
||||
setPageSize: handleSetPageSize
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user