feat:回退操作
This commit is contained in:
@@ -1,5 +1,10 @@
|
||||
import React from 'react';
|
||||
import { useThrottledRequestWithLoading } from '../hooks/useThrottledRequest';
|
||||
import {
|
||||
useThrottledRequestWithLoading,
|
||||
useThrottledRequestWithError,
|
||||
useRequestWithRetry,
|
||||
useCancellableRequest
|
||||
} from '../hooks/useThrottledRequest';
|
||||
|
||||
interface ThrottledButtonProps {
|
||||
onClick: () => Promise<any>;
|
||||
@@ -7,6 +12,14 @@ interface ThrottledButtonProps {
|
||||
delay?: number;
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
variant?: 'throttle' | 'debounce' | 'retry' | 'cancellable';
|
||||
maxRetries?: number;
|
||||
retryDelay?: number;
|
||||
showLoadingText?: boolean;
|
||||
loadingText?: string;
|
||||
errorText?: string;
|
||||
onSuccess?: (result: any) => void;
|
||||
onError?: (error: any) => void;
|
||||
}
|
||||
|
||||
export const ThrottledButton: React.FC<ThrottledButtonProps> = ({
|
||||
@@ -14,17 +27,265 @@ export const ThrottledButton: React.FC<ThrottledButtonProps> = ({
|
||||
children,
|
||||
delay = 1000,
|
||||
disabled = false,
|
||||
className = ''
|
||||
className = '',
|
||||
variant = 'throttle',
|
||||
maxRetries = 3,
|
||||
retryDelay = 1000,
|
||||
showLoadingText = true,
|
||||
loadingText = '处理中...',
|
||||
errorText,
|
||||
onSuccess,
|
||||
onError
|
||||
}) => {
|
||||
// 处理请求结果
|
||||
const handleRequest = async () => {
|
||||
try {
|
||||
const result = await onClick();
|
||||
onSuccess?.(result);
|
||||
} catch (error) {
|
||||
onError?.(error);
|
||||
}
|
||||
};
|
||||
|
||||
// 根据variant渲染不同的按钮
|
||||
const renderButton = () => {
|
||||
switch (variant) {
|
||||
case 'retry':
|
||||
return <RetryButtonContent
|
||||
onClick={handleRequest}
|
||||
maxRetries={maxRetries}
|
||||
retryDelay={retryDelay}
|
||||
loadingText={loadingText}
|
||||
showLoadingText={showLoadingText}
|
||||
disabled={disabled}
|
||||
className={className}
|
||||
>
|
||||
{children}
|
||||
</RetryButtonContent>;
|
||||
|
||||
case 'cancellable':
|
||||
return <CancellableButtonContent
|
||||
onClick={handleRequest}
|
||||
loadingText={loadingText}
|
||||
showLoadingText={showLoadingText}
|
||||
disabled={disabled}
|
||||
className={className}
|
||||
>
|
||||
{children}
|
||||
</CancellableButtonContent>;
|
||||
|
||||
case 'debounce':
|
||||
return <DebounceButtonContent
|
||||
onClick={handleRequest}
|
||||
delay={delay}
|
||||
loadingText={loadingText}
|
||||
showLoadingText={showLoadingText}
|
||||
disabled={disabled}
|
||||
className={className}
|
||||
errorText={errorText}
|
||||
>
|
||||
{children}
|
||||
</DebounceButtonContent>;
|
||||
|
||||
default:
|
||||
return <ThrottleButtonContent
|
||||
onClick={handleRequest}
|
||||
delay={delay}
|
||||
loadingText={loadingText}
|
||||
showLoadingText={showLoadingText}
|
||||
disabled={disabled}
|
||||
className={className}
|
||||
>
|
||||
{children}
|
||||
</ThrottleButtonContent>;
|
||||
}
|
||||
};
|
||||
|
||||
return renderButton();
|
||||
};
|
||||
|
||||
// 节流按钮内容组件
|
||||
const ThrottleButtonContent: React.FC<{
|
||||
onClick: () => Promise<any>;
|
||||
delay: number;
|
||||
loadingText: string;
|
||||
showLoadingText: boolean;
|
||||
disabled: boolean;
|
||||
className: string;
|
||||
children: React.ReactNode;
|
||||
}> = ({ onClick, delay, loadingText, showLoadingText, disabled, className, children }) => {
|
||||
const { throttledRequest, loading } = useThrottledRequestWithLoading(onClick, delay);
|
||||
|
||||
|
||||
const getButtonText = () => {
|
||||
return loading && showLoadingText ? loadingText : children;
|
||||
};
|
||||
|
||||
const getButtonClassName = () => {
|
||||
const baseClasses = 'px-4 py-2 rounded font-medium transition-colors duration-200 disabled:cursor-not-allowed';
|
||||
const variantClasses = loading
|
||||
? 'bg-gray-400 text-white cursor-not-allowed'
|
||||
: 'bg-blue-500 text-white hover:bg-blue-600 disabled:bg-gray-400';
|
||||
|
||||
return `${baseClasses} ${variantClasses} ${className}`;
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={throttledRequest}
|
||||
disabled={disabled || loading}
|
||||
className={`px-4 py-2 rounded bg-blue-500 text-white hover:bg-blue-600 disabled:bg-gray-400 disabled:cursor-not-allowed ${className}`}
|
||||
className={getButtonClassName()}
|
||||
>
|
||||
{loading ? '处理中...' : children}
|
||||
{getButtonText()}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
// 防抖按钮内容组件
|
||||
const DebounceButtonContent: React.FC<{
|
||||
onClick: () => Promise<any>;
|
||||
delay: number;
|
||||
loadingText: string;
|
||||
showLoadingText: boolean;
|
||||
disabled: boolean;
|
||||
className: string;
|
||||
errorText?: string;
|
||||
children: React.ReactNode;
|
||||
}> = ({ onClick, delay, loadingText, showLoadingText, disabled, className, errorText, children }) => {
|
||||
const { debouncedRequest, loading, error } = useThrottledRequestWithError(onClick, delay);
|
||||
|
||||
const getButtonText = () => {
|
||||
return loading && showLoadingText ? loadingText : children;
|
||||
};
|
||||
|
||||
const getButtonClassName = () => {
|
||||
const baseClasses = 'px-4 py-2 rounded font-medium transition-colors duration-200 disabled:cursor-not-allowed';
|
||||
let variantClasses = '';
|
||||
if (loading) {
|
||||
variantClasses = 'bg-gray-400 text-white cursor-not-allowed';
|
||||
} else if (error) {
|
||||
variantClasses = 'bg-red-500 text-white hover:bg-red-600';
|
||||
} else {
|
||||
variantClasses = 'bg-blue-500 text-white hover:bg-blue-600 disabled:bg-gray-400';
|
||||
}
|
||||
|
||||
return `${baseClasses} ${variantClasses} ${className}`;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={debouncedRequest}
|
||||
disabled={disabled || loading}
|
||||
className={getButtonClassName()}
|
||||
>
|
||||
{getButtonText()}
|
||||
</button>
|
||||
|
||||
{error && errorText && (
|
||||
<span className="text-red-500 text-sm">{errorText}</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// 重试按钮内容组件
|
||||
const RetryButtonContent: React.FC<{
|
||||
onClick: () => Promise<any>;
|
||||
maxRetries: number;
|
||||
retryDelay: number;
|
||||
loadingText: string;
|
||||
showLoadingText: boolean;
|
||||
disabled: boolean;
|
||||
className: string;
|
||||
children: React.ReactNode;
|
||||
}> = ({ onClick, maxRetries, retryDelay, loadingText, showLoadingText, disabled, className, children }) => {
|
||||
const { requestWithRetry, loading, retryCount } = useRequestWithRetry(onClick, maxRetries, retryDelay);
|
||||
|
||||
const getButtonText = () => {
|
||||
if (loading) {
|
||||
if (retryCount > 0) {
|
||||
return `${loadingText} (重试 ${retryCount}/${maxRetries})`;
|
||||
}
|
||||
return showLoadingText ? loadingText : children;
|
||||
}
|
||||
return children;
|
||||
};
|
||||
|
||||
const getButtonClassName = () => {
|
||||
const baseClasses = 'px-4 py-2 rounded font-medium transition-colors duration-200 disabled:cursor-not-allowed';
|
||||
const variantClasses = loading
|
||||
? 'bg-gray-400 text-white cursor-not-allowed'
|
||||
: 'bg-blue-500 text-white hover:bg-blue-600 disabled:bg-gray-400';
|
||||
|
||||
return `${baseClasses} ${variantClasses} ${className}`;
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={requestWithRetry}
|
||||
disabled={disabled || loading}
|
||||
className={getButtonClassName()}
|
||||
>
|
||||
{getButtonText()}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
// 可取消按钮内容组件
|
||||
const CancellableButtonContent: React.FC<{
|
||||
onClick: () => Promise<any>;
|
||||
loadingText: string;
|
||||
showLoadingText: boolean;
|
||||
disabled: boolean;
|
||||
className: string;
|
||||
children: React.ReactNode;
|
||||
}> = ({ onClick, loadingText, showLoadingText, disabled, className, children }) => {
|
||||
const { cancellableRequest, loading, cancelRequest } = useCancellableRequest(onClick);
|
||||
|
||||
const getButtonText = () => {
|
||||
return loading && showLoadingText ? loadingText : children;
|
||||
};
|
||||
|
||||
const getButtonClassName = () => {
|
||||
const baseClasses = 'px-4 py-2 rounded font-medium transition-colors duration-200 disabled:cursor-not-allowed';
|
||||
const variantClasses = loading
|
||||
? 'bg-gray-400 text-white cursor-not-allowed'
|
||||
: 'bg-blue-500 text-white hover:bg-blue-600 disabled:bg-gray-400';
|
||||
|
||||
return `${baseClasses} ${variantClasses} ${className}`;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={cancellableRequest}
|
||||
disabled={disabled || loading}
|
||||
className={getButtonClassName()}
|
||||
>
|
||||
{getButtonText()}
|
||||
</button>
|
||||
|
||||
{loading && cancelRequest && (
|
||||
<button
|
||||
onClick={cancelRequest}
|
||||
className="px-3 py-2 rounded bg-red-500 text-white hover:bg-red-600 text-sm"
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// 导出其他类型的按钮组件
|
||||
export const DebouncedButton: React.FC<Omit<ThrottledButtonProps, 'variant'> & { delay?: number }> = (props) => (
|
||||
<ThrottledButton {...props} variant="debounce" delay={props.delay || 300} />
|
||||
);
|
||||
|
||||
export const RetryButton: React.FC<Omit<ThrottledButtonProps, 'variant'> & { maxRetries?: number; retryDelay?: number }> = (props) => (
|
||||
<ThrottledButton {...props} variant="retry" maxRetries={props.maxRetries || 3} retryDelay={props.retryDelay || 1000} />
|
||||
);
|
||||
|
||||
export const CancellableButton: React.FC<Omit<ThrottledButtonProps, 'variant'>> = (props) => (
|
||||
<ThrottledButton {...props} variant="cancellable" />
|
||||
);
|
||||
Reference in New Issue
Block a user