refactor: rename autoRefresh to reloadAfterClean for clarity and consistency across components
This commit is contained in:
@@ -1,17 +1,44 @@
|
|||||||
|
/**
|
||||||
|
* 自动刷新开关组件
|
||||||
|
*
|
||||||
|
* 用于 StorageCleaner 页面,控制是否自动刷新存储数据列表。
|
||||||
|
* 以卡片形式展示,左侧为标签文本,右侧为 shadcn/ui Switch 开关。
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* <AutoRefreshToggle
|
||||||
|
* reloadAfterClean={reloadAfterClean}
|
||||||
|
* onChange={(checked) => setReloadAfterClean(checked)}
|
||||||
|
* />
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useLazyTranslation } from '@/utils/useLazyTranslation';
|
import { useLazyTranslation } from '@/utils/useLazyTranslation';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
// 引入官方的 Switch 原子组件
|
|
||||||
import { Switch } from '@/components/ui/switch';
|
import { Switch } from '@/components/ui/switch';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
|
|
||||||
interface AutoRefreshToggleProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'> {
|
export interface AutoRefreshToggleProps extends Omit<
|
||||||
autoRefresh: boolean;
|
React.HTMLAttributes<HTMLDivElement>,
|
||||||
|
'onChange'
|
||||||
|
> {
|
||||||
|
/** 开关的当前状态(受控) */
|
||||||
|
reloadAfterClean: boolean;
|
||||||
|
/** 状态变化时的回调函数 */
|
||||||
onChange: (checked: boolean) => void;
|
onChange: (checked: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自动刷新开关
|
||||||
|
*
|
||||||
|
* @param reloadAfterClean - 当前开关状态
|
||||||
|
* @param onChange - 状态变化回调,接收新的布尔值
|
||||||
|
* @param className - 额外的 CSS 类名,用于覆盖或扩展样式
|
||||||
|
* @param props - 透传给外层 div 的其他 HTML 属性
|
||||||
|
*/
|
||||||
export default function AutoRefreshToggle({
|
export default function AutoRefreshToggle({
|
||||||
autoRefresh,
|
reloadAfterClean,
|
||||||
onChange,
|
onChange,
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
@@ -21,15 +48,12 @@ export default function AutoRefreshToggle({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
// 外层包裹维持优雅的 shadcn 风格中性卡片,去除硬编码 mb-3 扩展灵活性
|
|
||||||
'w-full p-4 rounded-xl border border-border bg-card text-card-foreground shadow-sm transition-all focus-within:ring-1 focus-within:ring-ring flex justify-between items-center',
|
'w-full p-4 rounded-xl border border-border bg-card text-card-foreground shadow-sm transition-all focus-within:ring-1 focus-within:ring-ring flex justify-between items-center',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{/* 3. 使用标准的 shadcn/ui Label 组件:
|
{/* Label 与 Switch 通过 htmlFor + id 关联,支持点击文字触发开关 */}
|
||||||
绑定 htmlFor 建立安全的表单无障碍桥梁,使得用户点击文字也能触发开关联动
|
|
||||||
*/}
|
|
||||||
<Label
|
<Label
|
||||||
htmlFor="auto-refresh-switch"
|
htmlFor="auto-refresh-switch"
|
||||||
className="text-sm font-bold text-foreground cursor-pointer select-none tracking-tight"
|
className="text-sm font-bold text-foreground cursor-pointer select-none tracking-tight"
|
||||||
@@ -37,16 +61,7 @@ export default function AutoRefreshToggle({
|
|||||||
{t('storageCleaner:autoRefresh')}
|
{t('storageCleaner:autoRefresh')}
|
||||||
</Label>
|
</Label>
|
||||||
|
|
||||||
{/* 4. 超进化:彻底废除 200 个字符的原生 checkbox 拼接!
|
<Switch id="auto-refresh-switch" checked={reloadAfterClean} onCheckedChange={onChange} />
|
||||||
完美调用 shadcn 的 Switch 组件。它会自动应用全站统一的主色(Primary)、
|
|
||||||
带阻尼的滑块硬件加速动效、以及教科书级别的 WAI-ARIA 无障碍键盘焦点提示。
|
|
||||||
*/}
|
|
||||||
<Switch
|
|
||||||
id="auto-refresh-switch"
|
|
||||||
checked={autoRefresh}
|
|
||||||
onCheckedChange={onChange}
|
|
||||||
className="data-[state=checked]:bg-primary" // 如果依然需要特定的琥珀色可写 data-[state=checked]:bg-amber-500
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,29 +11,26 @@ import { useLazyTranslation } from '@/utils/useLazyTranslation';
|
|||||||
export default function Index() {
|
export default function Index() {
|
||||||
const { t } = useLazyTranslation('storageCleaner');
|
const { t } = useLazyTranslation('storageCleaner');
|
||||||
|
|
||||||
// 1. 完美对接全新重构、无需回调参数的纯净版状态 Hook
|
|
||||||
const {
|
const {
|
||||||
error,
|
error,
|
||||||
isInitializing,
|
isInitializing,
|
||||||
options,
|
options,
|
||||||
sizes,
|
sizes,
|
||||||
autoRefresh,
|
reloadAfterClean,
|
||||||
loading,
|
loading,
|
||||||
result,
|
result,
|
||||||
showConfirm,
|
showConfirm,
|
||||||
setShowConfirm,
|
setShowConfirm,
|
||||||
allSelected,
|
allSelected,
|
||||||
someSelected,
|
someSelected,
|
||||||
handleAutoRefreshChange,
|
handleReloadAfterCleanChange,
|
||||||
handleOptionChange,
|
handleOptionChange,
|
||||||
handleSelectAll,
|
handleSelectAll,
|
||||||
handleClean,
|
handleClean,
|
||||||
} = useStorageCleaner();
|
} = useStorageCleaner();
|
||||||
|
|
||||||
// 计算当前的按钮锁定状态:没有任何一项被勾选,或者正在清理中,则禁用大按钮
|
|
||||||
const isButtonDisabled = !(someSelected || allSelected) || loading;
|
const isButtonDisabled = !(someSelected || allSelected) || loading;
|
||||||
|
|
||||||
// 2. 初始化骨架屏:全面升级为符合 shadcn 规范的无损微动效 Spinner
|
|
||||||
if (isInitializing) {
|
if (isInitializing) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center justify-center py-12 min-h-[280px] w-full animate-in fade-in duration-200">
|
<div className="flex flex-col items-center justify-center py-12 min-h-[280px] w-full animate-in fade-in duration-200">
|
||||||
@@ -45,18 +42,12 @@ export default function Index() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 拦截非法域名或受限域名的错误提示页(ErrorDisplay 内部已在上一轮做好 p-4 居中)
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return <ErrorDisplay error={error} />;
|
return <ErrorDisplay error={error} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
/* 3. 终极版页面根容器:
|
|
||||||
- 彻底灌入 p-4,全局对齐线向内收缩 16px,终结贴边惨剧。
|
|
||||||
- 用 space-y-3.5 替代块级外边距(mb-3 等),让所有卡片之间的垂直间距处于绝对一致的黄金比例。
|
|
||||||
*/
|
|
||||||
<div className="p-4 w-full flex flex-col space-y-3.5 animate-in fade-in duration-300">
|
<div className="p-4 w-full flex flex-col space-y-3.5 animate-in fade-in duration-300">
|
||||||
{/* 存储网格核心中控面板 (内部已满血复活半选状态) */}
|
|
||||||
<StorageOptionsGrid
|
<StorageOptionsGrid
|
||||||
options={options}
|
options={options}
|
||||||
sizes={sizes}
|
sizes={sizes}
|
||||||
@@ -66,14 +57,11 @@ export default function Index() {
|
|||||||
onSelectAll={handleSelectAll}
|
onSelectAll={handleSelectAll}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* 自动刷新开关 */}
|
<AutoRefreshToggle
|
||||||
<AutoRefreshToggle autoRefresh={autoRefresh} onChange={handleAutoRefreshChange} />
|
reloadAfterClean={reloadAfterClean}
|
||||||
|
onChange={handleReloadAfterCleanChange}
|
||||||
|
/>
|
||||||
|
|
||||||
{/* 4. 主行动按钮超进化:
|
|
||||||
- 彻底剥离 bg-amber-500,全面回归系统标准的 variant="destructive"。
|
|
||||||
- 享受高风险操作该有的危险红警示,完美契合上一轮重构的二次确认弹窗基调。
|
|
||||||
- 高级动态加载:当处于 cleaning 状态时,文字自动流转,且左侧自动淡入等宽 Loader2 图标。
|
|
||||||
*/}
|
|
||||||
<Button
|
<Button
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
size="default"
|
size="default"
|
||||||
@@ -91,10 +79,8 @@ export default function Index() {
|
|||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{/* 动态清理结果返回反馈卡片 */}
|
|
||||||
<CleaningResult result={result} />
|
<CleaningResult result={result} />
|
||||||
|
|
||||||
{/* 二次风险防御确认弹窗 */}
|
|
||||||
<StorageCleanerConfirm
|
<StorageCleanerConfirm
|
||||||
open={showConfirm}
|
open={showConfirm}
|
||||||
onClose={() => setShowConfirm(false)}
|
onClose={() => setShowConfirm(false)}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ const DEFAULT_OPTIONS: StorageCleanerOptions = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const DEFAULT_PREFERENCES: StorageCleanerPreferences = {
|
const DEFAULT_PREFERENCES: StorageCleanerPreferences = {
|
||||||
autoRefresh: true,
|
reloadAfterClean: true,
|
||||||
selectedTypes: DEFAULT_OPTIONS,
|
selectedTypes: DEFAULT_OPTIONS,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ export interface UseStorageCleanerReturn {
|
|||||||
isInitializing: boolean;
|
isInitializing: boolean;
|
||||||
options: StorageCleanerOptions;
|
options: StorageCleanerOptions;
|
||||||
sizes: Record<string, number>;
|
sizes: Record<string, number>;
|
||||||
autoRefresh: boolean;
|
reloadAfterClean: boolean;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
result: CleaningResult | null;
|
result: CleaningResult | null;
|
||||||
showConfirm: boolean;
|
showConfirm: boolean;
|
||||||
@@ -49,7 +49,7 @@ export interface UseStorageCleanerReturn {
|
|||||||
allSelected: boolean;
|
allSelected: boolean;
|
||||||
someSelected: boolean;
|
someSelected: boolean;
|
||||||
|
|
||||||
handleAutoRefreshChange: (checked: boolean) => void;
|
handleReloadAfterCleanChange: (checked: boolean) => void;
|
||||||
handleOptionChange: (key: keyof StorageCleanerOptions) => void;
|
handleOptionChange: (key: keyof StorageCleanerOptions) => void;
|
||||||
handleSelectAll: (checked: boolean) => void;
|
handleSelectAll: (checked: boolean) => void;
|
||||||
handleClean: () => Promise<void>;
|
handleClean: () => Promise<void>;
|
||||||
@@ -62,7 +62,7 @@ export function useStorageCleaner(): UseStorageCleanerReturn {
|
|||||||
const [isInitializing, setIsInitializing] = useState<boolean>(true);
|
const [isInitializing, setIsInitializing] = useState<boolean>(true);
|
||||||
const [options, setOptions] = useState<StorageCleanerOptions>(DEFAULT_OPTIONS);
|
const [options, setOptions] = useState<StorageCleanerOptions>(DEFAULT_OPTIONS);
|
||||||
const [sizes, setSizes] = useState<Record<string, number>>({});
|
const [sizes, setSizes] = useState<Record<string, number>>({});
|
||||||
const [autoRefresh, setAutoRefresh] = useState<boolean>(true);
|
const [reloadAfterClean, setReloadAfterClean] = useState<boolean>(true);
|
||||||
const [loading, setLoading] = useState<boolean>(false);
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
const [result, setResult] = useState<CleaningResult | null>(null);
|
const [result, setResult] = useState<CleaningResult | null>(null);
|
||||||
const [showConfirm, setShowConfirm] = useState<boolean>(false);
|
const [showConfirm, setShowConfirm] = useState<boolean>(false);
|
||||||
@@ -117,7 +117,7 @@ export function useStorageCleaner(): UseStorageCleanerReturn {
|
|||||||
if (currentRequestId !== requestIdRef.current) return;
|
if (currentRequestId !== requestIdRef.current) return;
|
||||||
|
|
||||||
if (savedPrefs) {
|
if (savedPrefs) {
|
||||||
setAutoRefresh(savedPrefs.autoRefresh ?? DEFAULT_PREFERENCES.autoRefresh);
|
setReloadAfterClean(savedPrefs.reloadAfterClean ?? DEFAULT_PREFERENCES.reloadAfterClean);
|
||||||
setOptions(savedPrefs.selectedTypes ?? DEFAULT_PREFERENCES.selectedTypes);
|
setOptions(savedPrefs.selectedTypes ?? DEFAULT_PREFERENCES.selectedTypes);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,26 +170,22 @@ export function useStorageCleaner(): UseStorageCleanerReturn {
|
|||||||
};
|
};
|
||||||
}, [debouncedLoadInfo]);
|
}, [debouncedLoadInfo]);
|
||||||
|
|
||||||
// 2. 超进化:防抖写盘管道 (Chrome Storage Debounce Pipeline)
|
|
||||||
// 用户疯狂点击勾选时,React 状态保持丝滑的 0 延迟同步,只有停下点击 500ms 后,才会真正发起一次 Chrome 存盘,配额永远安全。
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// 过滤掉首次初始化时的无意义写盘
|
|
||||||
if (isInitializing) return;
|
if (isInitializing) return;
|
||||||
|
|
||||||
if (storageTimerRef.current) clearTimeout(storageTimerRef.current);
|
if (storageTimerRef.current) clearTimeout(storageTimerRef.current);
|
||||||
storageTimerRef.current = setTimeout(async () => {
|
storageTimerRef.current = setTimeout(async () => {
|
||||||
await storageUtil
|
await storageUtil
|
||||||
.set('storageCleaner/preferences', {
|
.set('storageCleaner/preferences', {
|
||||||
autoRefresh,
|
reloadAfterClean,
|
||||||
selectedTypes: options,
|
selectedTypes: options,
|
||||||
})
|
})
|
||||||
.catch(console.error);
|
.catch(console.error);
|
||||||
}, 500);
|
}, 500);
|
||||||
}, [options, autoRefresh, isInitializing]);
|
}, [options, reloadAfterClean, isInitializing]);
|
||||||
|
|
||||||
// 3. 极速状态分发:同步函数化(去掉了原有的 async 声明,只负责触发状态)
|
const handleReloadAfterCleanChange = useCallback((checked: boolean) => {
|
||||||
const handleAutoRefreshChange = useCallback((checked: boolean) => {
|
setReloadAfterClean(checked);
|
||||||
setAutoRefresh(checked);
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleOptionChange = useCallback((key: keyof StorageCleanerOptions) => {
|
const handleOptionChange = useCallback((key: keyof StorageCleanerOptions) => {
|
||||||
@@ -207,7 +203,6 @@ export function useStorageCleaner(): UseStorageCleanerReturn {
|
|||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 清理动作核心
|
|
||||||
const handleClean = useCallback(async () => {
|
const handleClean = useCallback(async () => {
|
||||||
if (loadingRef.current) return;
|
if (loadingRef.current) return;
|
||||||
|
|
||||||
@@ -222,7 +217,7 @@ export function useStorageCleaner(): UseStorageCleanerReturn {
|
|||||||
const cleaningResult = await clearStorage(tab.id, tab.url, options);
|
const cleaningResult = await clearStorage(tab.id, tab.url, options);
|
||||||
setResult(cleaningResult);
|
setResult(cleaningResult);
|
||||||
|
|
||||||
if (autoRefresh && cleaningResult.success) {
|
if (reloadAfterClean && cleaningResult.success) {
|
||||||
toast.success(t('storageCleaner:cleanSuccessReload'));
|
toast.success(t('storageCleaner:cleanSuccessReload'));
|
||||||
await sendMessage(MessageAction.RELOAD_TAB, { tabId: tab.id, delay: 1000 });
|
await sendMessage(MessageAction.RELOAD_TAB, { tabId: tab.id, delay: 1000 });
|
||||||
} else {
|
} else {
|
||||||
@@ -234,9 +229,8 @@ export function useStorageCleaner(): UseStorageCleanerReturn {
|
|||||||
setLoading(false);
|
setLoading(false);
|
||||||
setShowConfirm(false);
|
setShowConfirm(false);
|
||||||
}
|
}
|
||||||
}, [options, autoRefresh, loadInfo, t]);
|
}, [options, reloadAfterClean, loadInfo, t]);
|
||||||
|
|
||||||
// 4. 精准的流式衍生计算收拢:完全切断垃圾内存常态分配
|
|
||||||
const totalSize = useMemo(() => {
|
const totalSize = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
(sizes.cookies || 0) +
|
(sizes.cookies || 0) +
|
||||||
@@ -259,7 +253,7 @@ export function useStorageCleaner(): UseStorageCleanerReturn {
|
|||||||
isInitializing,
|
isInitializing,
|
||||||
options,
|
options,
|
||||||
sizes,
|
sizes,
|
||||||
autoRefresh,
|
reloadAfterClean,
|
||||||
loading,
|
loading,
|
||||||
result,
|
result,
|
||||||
showConfirm,
|
showConfirm,
|
||||||
@@ -267,7 +261,7 @@ export function useStorageCleaner(): UseStorageCleanerReturn {
|
|||||||
totalSize,
|
totalSize,
|
||||||
allSelected: selectionMetrics.all,
|
allSelected: selectionMetrics.all,
|
||||||
someSelected: selectionMetrics.some,
|
someSelected: selectionMetrics.some,
|
||||||
handleAutoRefreshChange,
|
handleReloadAfterCleanChange,
|
||||||
handleOptionChange,
|
handleOptionChange,
|
||||||
handleSelectAll,
|
handleSelectAll,
|
||||||
handleClean,
|
handleClean,
|
||||||
|
|||||||
Vendored
+1
-1
@@ -157,7 +157,7 @@ export interface FieldTypePreferences {
|
|||||||
*/
|
*/
|
||||||
export interface StorageCleanerPreferences {
|
export interface StorageCleanerPreferences {
|
||||||
/** 是否在清理后自动刷新页面 */
|
/** 是否在清理后自动刷新页面 */
|
||||||
autoRefresh: boolean;
|
reloadAfterClean: boolean;
|
||||||
/** 默认勾选的清理类型 */
|
/** 默认勾选的清理类型 */
|
||||||
selectedTypes: StorageCleanerOptions;
|
selectedTypes: StorageCleanerOptions;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ describe('chromeStorage', () => {
|
|||||||
|
|
||||||
it('应该成功设置复杂对象值', async () => {
|
it('应该成功设置复杂对象值', async () => {
|
||||||
const preferences = {
|
const preferences = {
|
||||||
autoRefresh: false,
|
reloadAfterClean: false,
|
||||||
selectedTypes: {
|
selectedTypes: {
|
||||||
localStorage: true,
|
localStorage: true,
|
||||||
sessionStorage: true,
|
sessionStorage: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user