import { useEffect, useMemo, useState } from 'react'; import { Box, Button, Container, Stack, ToggleButton, ToggleButtonGroup, Typography, } from '@mui/material'; import CompareArrowsIcon from '@mui/icons-material/CompareArrows'; import { useTranslation } from 'react-i18next'; import PageHeader from '@/components/PageHeader'; import { jsonDiffPageStyles } from '@/config/pageTheme'; import JsonDiffInput from './JsonDiffInput'; import DiffResult from './DiffResult'; import DiffNavigator from './DiffNavigator'; import { diffJson } from './diffEngine'; import type { DiffResult as DiffResultType, ViewMode } from './types'; interface ParseState { value: unknown; error: string | null; } const tryParse = (raw: string, invalidMsg: string): ParseState => { const trimmed = raw.trim(); if (!trimmed) return { value: undefined, error: null }; try { return { value: JSON.parse(trimmed), error: null }; } catch { return { value: undefined, error: invalidMsg }; } }; export default function Index() { const { t } = useTranslation(['jsonDiff']); const [leftInput, setLeftInput] = useState(''); const [rightInput, setRightInput] = useState(''); const [leftError, setLeftError] = useState(null); const [rightError, setRightError] = useState(null); const [diffResult, setDiffResult] = useState(null); const [viewMode, setViewMode] = useState('sideBySide'); const [currentDiffIndex, setCurrentDiffIndex] = useState(0); // 防抖校验输入 useEffect(() => { const handle = setTimeout(() => { const invalid = t('jsonDiff:invalidJson'); setLeftError(tryParse(leftInput, invalid).error); setRightError(tryParse(rightInput, invalid).error); }, 300); return () => clearTimeout(handle); }, [leftInput, rightInput, t]); const canCompare = useMemo(() => { return leftInput.trim() !== '' && rightInput.trim() !== '' && !leftError && !rightError; }, [leftInput, rightInput, leftError, rightError]); const handleCompare = () => { const invalid = t('jsonDiff:invalidJson'); const left = tryParse(leftInput, invalid); const right = tryParse(rightInput, invalid); setLeftError(left.error); setRightError(right.error); if (left.error || right.error) { setDiffResult(null); return; } const result = diffJson(left.value, right.value); setDiffResult(result); setCurrentDiffIndex(0); }; const handleClear = () => { setLeftInput(''); setRightInput(''); setLeftError(null); setRightError(null); setDiffResult(null); setCurrentDiffIndex(0); }; const total = diffResult?.diffPaths.length ?? 0; const handlePrev = () => { if (total === 0) return; setCurrentDiffIndex((idx) => (idx - 1 + total) % total); }; const handleNext = () => { if (total === 0) return; setCurrentDiffIndex((idx) => (idx + 1) % total); }; const activePath = diffResult && total > 0 ? diffResult.diffPaths[currentDiffIndex] : undefined; return ( } iconColor={jsonDiffPageStyles.primaryColor} /> {/* 工具栏 */} v && setViewMode(v)} sx={{ borderRadius: 3 }} > {t('jsonDiff:sideBySideMode')} {t('jsonDiff:unifiedMode')} {/* 输入区 */} {/* 差异展示 */} {diffResult ? ( <> ) : ( theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.03)' : 'grey.50', border: '1px dashed', borderColor: (theme) => theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.15)' : 'grey.300', textAlign: 'center', }} > {t('jsonDiff:emptyHint')} )} ); }