diff --git a/apps/admin/src/pages/Dashboard/DashboardTodoCards.integration.test.ts b/apps/admin/src/pages/Dashboard/DashboardTodoCards.integration.test.ts new file mode 100644 index 0000000..678b0f6 --- /dev/null +++ b/apps/admin/src/pages/Dashboard/DashboardTodoCards.integration.test.ts @@ -0,0 +1,16 @@ +import { describe, expect, it } from 'vitest'; +import { shouldNavigateTodoCard } from './DashboardTodoCards'; + +describe('DashboardTodoCards 点击导航守卫', () => { + it('目标路径与当前路径不同且冷却已过时允许导航', () => { + expect(shouldNavigateTodoCard('/dashboard', '/attendance', 1000, 1600)).toBe(true); + }); + + it('目标路径与当前路径相同时不允许导航', () => { + expect(shouldNavigateTodoCard('/attendance', '/attendance', 1000, 1600)).toBe(false); + }); + + it('冷却期内忽略重复点击,防止产生多条历史记录', () => { + expect(shouldNavigateTodoCard('/dashboard', '/attendance', 1000, 1300)).toBe(false); + }); +}); diff --git a/apps/admin/src/pages/Dashboard/DashboardTodoCards.tsx b/apps/admin/src/pages/Dashboard/DashboardTodoCards.tsx index 9b71270..db49ce0 100644 --- a/apps/admin/src/pages/Dashboard/DashboardTodoCards.tsx +++ b/apps/admin/src/pages/Dashboard/DashboardTodoCards.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useRef } from 'react'; import { Card, Col, Row } from 'antd'; import { ArrowRightOutlined, @@ -6,9 +6,22 @@ import { DollarOutlined, ExclamationCircleOutlined, } from '@ant-design/icons'; -import { useNavigate } from 'react-router'; +import { useLocation, useNavigate } from 'react-router'; import { MARGIN_BOTTOM_16_STYLE, TODO_CARD_DANGER, TODO_CARD_DRAFT, TODO_CARD_OK, TODO_CARD_WARN } from './Dashboard.types'; +/** 冷却期内忽略重复点击,避免快速/幽灵点击压入多条历史记录。 */ +const TODO_CLICK_LOCK_MS = 400; + +export function shouldNavigateTodoCard( + currentPath: string, + targetPath: string, + lastNavAt: number, + now: number, +): boolean { + if (currentPath === targetPath) return false; + return now - lastNavAt >= TODO_CLICK_LOCK_MS; +} + export const DashboardTodoCards: React.FC<{ absentCount: number; draftCount: number; @@ -16,6 +29,14 @@ export const DashboardTodoCards: React.FC<{ pendingDeposits: number; }> = ({ absentCount, draftCount, draftTotal, pendingDeposits }) => { const navigate = useNavigate(); + const location = useLocation(); + const lastNavAtRef = useRef(0); + const go = (target: string) => { + const now = Date.now(); + if (!shouldNavigateTodoCard(location.pathname, target, lastNavAtRef.current, now)) return; + lastNavAtRef.current = now; + navigate(target); + }; return ( @@ -23,7 +44,7 @@ export const DashboardTodoCards: React.FC<{ 0 ? TODO_CARD_WARN : TODO_CARD_OK} styles={{ body: { padding: 16 } }} - onClick={() => navigate('/attendance')} + onClick={() => go('/attendance')} >
0 ? TODO_CARD_DRAFT : TODO_CARD_OK} styles={{ body: { padding: 16 } }} - onClick={() => navigate('/bills')} + onClick={() => go('/bills')} >
0 ? TODO_CARD_DANGER : TODO_CARD_OK} styles={{ body: { padding: 16 } }} - onClick={() => navigate('/deposits')} + onClick={() => go('/deposits')} >