import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react'; import dayjs from '@/utils/dayjs'; import { TextField, Select, MenuItem, Stack, Typography, Box, IconButton, Tooltip, Container, Fade, Divider, alpha, } from '@mui/material'; import GlobalSnackbar, { useSnackbar } from '@/components/GlobalSnackbar'; import AccessTimeIcon from '@mui/icons-material/AccessTime'; import Button from '@/components/Button'; import CopyButton from '@/components/CopyButton'; import { DATE_FORMAT, ZONES, timestampPageStyles } from '@/config/pageTheme'; import type { UnitType, ZoneType } from '@/config/pageTheme'; // ================= 子组件:实时时钟 (优化交互) ================= interface LiveClockProps { unit: UnitType; onUseNow: (val: number) => void; onUnitChange: (u: UnitType) => void; showMessage?: (message: string, options?: { severity: 'success' | 'error' }) => void; } const LiveClock = React.memo(({ unit, onUseNow, onUnitChange, showMessage }: LiveClockProps) => { const [now, setNow] = useState(() => Date.now()); const onUseNowRef = useRef(onUseNow); const showMessageRef = useRef(showMessage); useEffect(() => { onUseNowRef.current = onUseNow; showMessageRef.current = showMessage; }, [onUseNow, showMessage]); useEffect(() => { const t = setInterval(() => setNow(Date.now()), 1000); return () => clearInterval(t); }, []); const displayVal = useMemo( () => String(Math.floor(now / (unit === 'ms' ? 1 : 1000))), [now, unit], ); const handleUseNow = useCallback(() => { onUseNowRef.current(now); }, [now]); return ( 当前时间戳 {displayVal} {/* 胶囊式单位切换器 */} {(['ms', 's'] as const).map((u) => ( onUnitChange(u)} sx={{ px: 1.2, py: 0.35, borderRadius: 2, cursor: 'pointer', fontSize: '0.65rem', fontWeight: 900, transition: 'all 0.2s', bgcolor: unit === u ? '#fff' : 'transparent', color: unit === u ? 'primary.main' : alpha('#2196f3', 0.4), boxShadow: unit === u ? '0 2px 6px rgba(33, 150, 243, 0.2)' : 'none', }} > {u.toUpperCase()} ))} ); }); LiveClock.displayName = 'LiveClock'; // ================= 子组件:多维度结果展示 ================= interface ResultViewProps { result: string; mode: 'ts2dt' | 'dt2ts'; unit: UnitType; zone: string; showMessage?: (message: string, options?: { severity: 'success' | 'error' }) => void; } const ResultView = React.memo(({ result, mode, unit, zone, showMessage }: ResultViewProps) => { const extraInfo = useMemo(() => { if (!result) return null; const d = mode === 'ts2dt' ? dayjs(result, DATE_FORMAT).tz(zone) : unit === 'ms' ? dayjs(Number(result)) : dayjs.unix(Number(result)); return { relative: d.fromNow(), iso: d.toISOString(), utc: d.utc().format(DATE_FORMAT) + ' UTC', }; }, [result, mode, zone, unit]); if (!result) return null; return ( 转换结果 {result} {[ { label: '相对时间', value: extraInfo?.relative }, { label: 'ISO 8601', value: extraInfo?.iso }, { label: 'UTC 时间', value: extraInfo?.utc }, ].map((item) => ( {item.label} {item.value} {item.value && ( )} ))} ); }); ResultView.displayName = 'ResultView'; // ================= 主页面组件 ================= export default function TimestampPage() { const [mode, setMode] = useState<'ts2dt' | 'dt2ts'>('ts2dt'); const [tsInput, setTsInput] = useState(() => String(Date.now())); const [dtInput, setDtInput] = useState(() => dayjs().format(DATE_FORMAT)); const [unit, setUnit] = useState('ms'); const [zone, setZone] = useState('Asia/Shanghai'); const [result, setResult] = useState(''); const [error, setError] = useState(''); const { snackbarProps, showMessage } = useSnackbar({ autoHideDuration: 1500 }); const convert = useCallback(() => { if (mode === 'ts2dt') { const rawInput = tsInput.trim(); if (!rawInput) return; const num = Number(rawInput); if (isNaN(num)) { setError('无效数字'); return; } const d = unit === 'ms' ? dayjs(num) : dayjs.unix(num); if (!d.isValid()) { setError('无效时间戳'); return; } setError(''); setResult(d.tz(zone).format(DATE_FORMAT)); } else { const rawInput = dtInput.trim(); if (!rawInput) return; const d = dayjs.tz(rawInput, DATE_FORMAT, zone); if (!d.isValid()) { setError('格式错误'); return; } setError(''); const ms = d.valueOf(); setResult(unit === 'ms' ? String(ms) : String(Math.floor(ms / 1000))); } }, [mode, tsInput, dtInput, unit, zone]); useEffect(() => { const timer = setTimeout(convert, 400); return () => clearTimeout(timer); }, [convert]); const handleUseNow = useCallback( (now: number) => { if (mode === 'ts2dt') { setTsInput(String(unit === 'ms' ? now : Math.floor(now / 1000))); } else { setDtInput(dayjs(now).tz(zone).format(DATE_FORMAT)); } }, [mode, unit, zone], ); return ( {/* Header with Icon */} 时间戳转换 Unix 毫秒数转换与格式化 {/* Live Clock Card */} {/* Mode Switcher */} {(['ts2dt', 'dt2ts'] as const).map((m) => ( { setMode(m); setError(''); setResult(''); }} sx={{ flex: 1, py: 1, textAlign: 'center', position: 'relative', zIndex: 1, cursor: 'pointer', fontWeight: 800, fontSize: '0.75rem', color: mode === m ? 'primary.main' : 'text.secondary', transition: 'color 0.3s', }} > {m === 'ts2dt' ? '时间戳 → 日期' : '日期 → 时间戳'} ))} {/* Input Area */} { const val = e.target.value; if (mode === 'ts2dt') { setTsInput(val); } else { setDtInput(val); } setError(''); }} error={!!error} helperText={error} fullWidth sx={timestampPageStyles.INPUT_STYLE} /> {/* 优化后的单位选择按钮组 */} {(['ms', 's'] as const).map((u) => ( setUnit(u)} sx={{ flex: 1, py: 0.8, textAlign: 'center', borderRadius: 3, cursor: 'pointer', fontSize: '0.75rem', fontWeight: 800, transition: 'all 0.2s', bgcolor: unit === u ? '#fff' : 'transparent', color: unit === u ? 'primary.main' : 'text.disabled', boxShadow: unit === u ? '0 2px 8px rgba(0,0,0,0.05)' : 'none', }} > {u === 'ms' ? '毫秒 (ms)' : '秒 (s)'} ))} {/* Main Action */} {/* Result View */} ); }