fix(admin): dashboard 待办卡片点击导航去重与防抖
Some checks failed
CI / check (pull_request) Failing after 3m19s
Some checks failed
CI / check (pull_request) Failing after 3m19s
- 同一目标路径不再重复 navigate,避免历史记录堆叠 - 400ms 冷却期内忽略重复/幽灵点击,防止来回跳转 - 新增 shouldNavigateTodoCard 守卫单元测试
This commit is contained in:
@@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import React, { useRef } from 'react';
|
||||||
import { Card, Col, Row } from 'antd';
|
import { Card, Col, Row } from 'antd';
|
||||||
import {
|
import {
|
||||||
ArrowRightOutlined,
|
ArrowRightOutlined,
|
||||||
@@ -6,9 +6,22 @@ import {
|
|||||||
DollarOutlined,
|
DollarOutlined,
|
||||||
ExclamationCircleOutlined,
|
ExclamationCircleOutlined,
|
||||||
} from '@ant-design/icons';
|
} 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';
|
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<{
|
export const DashboardTodoCards: React.FC<{
|
||||||
absentCount: number;
|
absentCount: number;
|
||||||
draftCount: number;
|
draftCount: number;
|
||||||
@@ -16,6 +29,14 @@ export const DashboardTodoCards: React.FC<{
|
|||||||
pendingDeposits: number;
|
pendingDeposits: number;
|
||||||
}> = ({ absentCount, draftCount, draftTotal, pendingDeposits }) => {
|
}> = ({ absentCount, draftCount, draftTotal, pendingDeposits }) => {
|
||||||
const navigate = useNavigate();
|
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 (
|
return (
|
||||||
<Card title="待办与异常" style={MARGIN_BOTTOM_16_STYLE}>
|
<Card title="待办与异常" style={MARGIN_BOTTOM_16_STYLE}>
|
||||||
<Row gutter={[16, 16]}>
|
<Row gutter={[16, 16]}>
|
||||||
@@ -23,7 +44,7 @@ export const DashboardTodoCards: React.FC<{
|
|||||||
<Card
|
<Card
|
||||||
style={absentCount > 0 ? TODO_CARD_WARN : TODO_CARD_OK}
|
style={absentCount > 0 ? TODO_CARD_WARN : TODO_CARD_OK}
|
||||||
styles={{ body: { padding: 16 } }}
|
styles={{ body: { padding: 16 } }}
|
||||||
onClick={() => navigate('/attendance')}
|
onClick={() => go('/attendance')}
|
||||||
>
|
>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||||
<ExclamationCircleOutlined
|
<ExclamationCircleOutlined
|
||||||
@@ -55,7 +76,7 @@ export const DashboardTodoCards: React.FC<{
|
|||||||
<Card
|
<Card
|
||||||
style={draftCount > 0 ? TODO_CARD_DRAFT : TODO_CARD_OK}
|
style={draftCount > 0 ? TODO_CARD_DRAFT : TODO_CARD_OK}
|
||||||
styles={{ body: { padding: 16 } }}
|
styles={{ body: { padding: 16 } }}
|
||||||
onClick={() => navigate('/bills')}
|
onClick={() => go('/bills')}
|
||||||
>
|
>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||||
<DollarOutlined
|
<DollarOutlined
|
||||||
@@ -87,7 +108,7 @@ export const DashboardTodoCards: React.FC<{
|
|||||||
<Card
|
<Card
|
||||||
style={pendingDeposits > 0 ? TODO_CARD_DANGER : TODO_CARD_OK}
|
style={pendingDeposits > 0 ? TODO_CARD_DANGER : TODO_CARD_OK}
|
||||||
styles={{ body: { padding: 16 } }}
|
styles={{ body: { padding: 16 } }}
|
||||||
onClick={() => navigate('/deposits')}
|
onClick={() => go('/deposits')}
|
||||||
>
|
>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||||
<BankOutlined
|
<BankOutlined
|
||||||
|
|||||||
Reference in New Issue
Block a user