refactor(admin): 用 usehooks-ts useEventListener/useTimeout 与 dayjs 替代手写监听、撤销计时与相对时间

This commit is contained in:
2026-08-08 17:21:33 +08:00
parent dab28f85e8
commit 74d5b90faa
3 changed files with 16 additions and 27 deletions

View File

@@ -1,6 +1,7 @@
import { useEffect, useState } from 'react';
import { Button, Tooltip } from 'antd';
import { VerticalAlignTopOutlined } from '@ant-design/icons';
import { useEventCallback, useEventListener } from 'usehooks-ts';
/**
* 全局「回到顶部」浮动按钮:长列表滚动超过 400px 后出现。
@@ -8,13 +9,13 @@ import { VerticalAlignTopOutlined } from '@ant-design/icons';
*/
export const BackTop: React.FC<{ threshold?: number }> = ({ threshold = 400 }) => {
const [visible, setVisible] = useState(false);
const updateVisible = useEventCallback(() => setVisible(window.scrollY > threshold));
useEffect(() => {
const onScroll = () => setVisible(window.scrollY > threshold);
onScroll();
window.addEventListener('scroll', onScroll, { passive: true });
return () => window.removeEventListener('scroll', onScroll);
}, [threshold]);
updateVisible();
}, [threshold, updateVisible]);
useEventListener('scroll', updateVisible, undefined, { passive: true });
if (!visible) return null;

View File

@@ -3,6 +3,7 @@ import { DatePicker, Input, InputNumber, Select, Spin, Tooltip } from 'antd';
import dayjs, { type Dayjs } from 'dayjs';
import equal from 'fast-deep-equal';
import { usePermission } from '../../hooks/usePermission';
import { useTimeout } from 'usehooks-ts';
import { useEditableCellStore } from '../../store/editableCell/editableCellStore';
import { message } from '../../ui/app-message';
import './style.css';
@@ -102,16 +103,10 @@ const EditableCell = <Value,>({
);
// 保存成功后短暂显示「撤销」入口:记录保存前的序列化旧值
const [undoMeta, setUndoMeta] = useState<{ serializedPrevious: unknown } | null>(null);
const undoTimerRef = useRef<number | undefined>(undefined);
// 撤销入口 6 秒后自动消失useTimeout 在 undoMeta 置空/组件卸载时自动清理
useTimeout(() => setUndoMeta(null), undoMeta ? 6_000 : null);
const enabled = !disabled && (!permission || hasPermission(permission));
useEffect(
() => () => {
window.clearTimeout(undoTimerRef.current);
},
[],
);
const original = useMemo(
() =>
serializeEditableValue(
@@ -146,10 +141,8 @@ const EditableCell = <Value,>({
await onSave(parseValue ? parseValue(serialized) : (serialized as Value));
useEditableCellStore.getState().clearIfActive(idRef.current);
setEditing(false);
// 提供 6 秒内的撤销入口(把旧值再保存一次)
// 提供 6 秒内的撤销入口(把旧值再保存一次)useTimeout 负责到时自动清除
setUndoMeta({ serializedPrevious: previousValue });
window.clearTimeout(undoTimerRef.current);
undoTimerRef.current = window.setTimeout(() => setUndoMeta(null), 6_000);
return true;
} catch (error) {
message.error(getErrorMessage(error, '保存失败'));
@@ -210,7 +203,6 @@ const EditableCell = <Value,>({
}
useEditableCellStore.getState().setActiveCell({ id: idRef.current, save });
// 重新进入编辑时清掉上一次的撤销入口
window.clearTimeout(undoTimerRef.current);
setUndoMeta(null);
setDraft(normalizeEditableValue(formatValue ? formatValue(value) : value, editor));
setEditing(true);
@@ -218,7 +210,6 @@ const EditableCell = <Value,>({
const handleUndo = async () => {
if (!undoMeta) return;
window.clearTimeout(undoTimerRef.current);
setUndoMeta(null);
try {
await onSave(

View File

@@ -1,4 +1,5 @@
import React, { useCallback, useEffect, useState } from 'react';
import dayjs from 'dayjs';
import { validateResponse } from '../../utils/validate';
import { notificationsSchema } from '../../api/schemas';
import { List, Typography, Menu, Layout, Button, Spin, Space, Grid, Select } from 'antd';
@@ -43,15 +44,11 @@ const typeMap: Record<string, { label: string; icon: React.ReactNode }> = {
};
function timeAgo(dateStr: string): string {
const diff = Date.now() - new Date(dateStr).getTime();
const mins = Math.floor(diff / 60000);
if (mins < 1) return '刚刚';
if (mins < 60) return `${mins}分钟前`;
const hours = Math.floor(mins / 60);
if (hours < 24) return `${hours}小时前`;
const days = Math.floor(hours / 24);
if (days < 7) return `${days}天前`;
return new Date(dateStr).toLocaleDateString('zh-CN');
const diff = Date.now() - dayjs(dateStr).valueOf();
if (diff < 60_000) return '刚刚';
// 7 天内用相对时间dayjs relativeTime 已全局配置),更早显示具体日期
if (diff < 7 * 86_400_000) return dayjs(dateStr).fromNow();
return dayjs(dateStr).format('YYYY/M/D');
}
const FILTER_ITEMS: Array<{ key: string; icon: React.ReactNode; label: string }> = [