refactor(admin): 用 usehooks-ts useEventListener/useTimeout 与 dayjs 替代手写监听、撤销计时与相对时间
This commit is contained in:
@@ -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;
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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 }> = [
|
||||
|
||||
Reference in New Issue
Block a user