fix: 修复路由初始化竞态、存储状态静默覆盖与存储清理误删

This commit is contained in:
Cursor Agent
2026-06-26 09:12:58 +00:00
committed by 雨霖铃
parent 1948ba67a7
commit 0545910b16
7 changed files with 137 additions and 11 deletions
@@ -103,4 +103,31 @@ describe('StorageCleaner 页面', () => {
expect(screen.getByRole('button', { name: /立即清理/ })).not.toBeDisabled();
});
});
it('展示的数据与当前标签页不一致时不应执行清理', async () => {
let currentTabId = 1;
vi.mocked(getCurrentTab).mockImplementation(
async () =>
({
id: currentTabId,
url: currentTabId === 1 ? 'https://a.example.com' : 'https://b.example.com',
}) as any,
);
render(<Index />);
await waitFor(() => {
expect(screen.getByRole('button', { name: /立即清理/ })).not.toBeDisabled();
});
currentTabId = 2;
fireEvent.click(screen.getByRole('button', { name: /立即清理/ }));
fireEvent.click(screen.getByRole('button', { name: /确认清理/ }));
await waitFor(() => {
expect(clearStorage).not.toHaveBeenCalled();
expect(toast.warning).toHaveBeenCalledWith('当前标签页已切换,请等待数据刷新后再清理');
});
});
});
+2 -1
View File
@@ -15,6 +15,7 @@ export default function Index() {
sizes,
reloadAfterClean,
loading,
isRefreshingSizes,
result,
showConfirm,
setShowConfirm,
@@ -26,7 +27,7 @@ export default function Index() {
handleClean,
} = useStorageCleaner();
const isButtonDisabled = !(someSelected || allSelected) || loading;
const isButtonDisabled = !(someSelected || allSelected) || loading || isRefreshingSizes;
if (isInitializing) {
return (
@@ -75,6 +75,7 @@ export interface UseStorageCleanerReturn {
sizes: Record<string, StorageSizeInfo>;
reloadAfterClean: boolean;
loading: boolean;
isRefreshingSizes: boolean;
result: CleaningResult | null;
showConfirm: boolean;
setShowConfirm: (show: boolean) => void;
@@ -95,10 +96,12 @@ export function useStorageCleaner(): UseStorageCleanerReturn {
const [sizes, setSizes] = useState<Record<string, StorageSizeInfo>>({});
const [reloadAfterClean, setReloadAfterClean] = useState<boolean>(true);
const [loading, setLoading] = useState<boolean>(false);
const [isRefreshingSizes, setIsRefreshingSizes] = useState<boolean>(false);
const [result, setResult] = useState<CleaningResult | null>(null);
const [showConfirm, setShowConfirm] = useState<boolean>(false);
const requestIdRef = useRef<number>(0);
const boundTabRef = useRef<{ id: number; url: string } | null>(null);
const debounceTimerRef = useRef<NodeJS.Timeout | null>(null);
const storageTimerRef = useRef<NodeJS.Timeout | null>(null);
const loadingRef = useRef(loading);
@@ -117,6 +120,7 @@ export function useStorageCleaner(): UseStorageCleanerReturn {
// 核心数据拉取链条
const loadInfo = useCallback(async () => {
const currentRequestId = ++requestIdRef.current;
setIsRefreshingSizes(true);
try {
const tab = await getCurrentTab();
if (currentRequestId !== requestIdRef.current) return;
@@ -159,6 +163,7 @@ export function useStorageCleaner(): UseStorageCleanerReturn {
cacheStorage: { value: cacheCount, displayType: 'count' },
serviceWorkers: { value: swCount, displayType: 'count' },
});
boundTabRef.current = { id: tabId, url };
} catch (err) {
console.error('Failed to load storage cleaner info:', err);
if (currentRequestId === requestIdRef.current) {
@@ -167,6 +172,7 @@ export function useStorageCleaner(): UseStorageCleanerReturn {
} finally {
if (currentRequestId === requestIdRef.current) {
setIsInitializing(false);
setIsRefreshingSizes(false);
}
}
}, []);
@@ -256,6 +262,13 @@ export function useStorageCleaner(): UseStorageCleanerReturn {
return;
}
const boundTab = boundTabRef.current;
if (!boundTab || boundTab.id !== tab.id) {
toast.warning('当前标签页已切换,请等待数据刷新后再清理');
setShowConfirm(false);
return;
}
setLoading(true);
setShowConfirm(false);
try {
@@ -297,6 +310,7 @@ export function useStorageCleaner(): UseStorageCleanerReturn {
sizes,
reloadAfterClean,
loading,
isRefreshingSizes,
result,
showConfirm,
setShowConfirm,