'use client'; import { useState, useEffect, useCallback, useRef, useMemo } from 'react'; import { useAppStore } from '../store/useAppStore'; import { useI18n } from '../lib/useI18n'; const ONBOARDING_KEY = 'author-onboarding-done'; export default function TourOverlay({ onOpenHelp }) { const { startTour, setStartTour, language, visualTheme } = useAppStore(); const { t } = useI18n(); const [status, setStatus] = useState('hidden'); // hidden, intro, tour const [currentStepIndex, setCurrentStepIndex] = useState(0); const [targetRect, setTargetRect] = useState(null); const [windowSize, setWindowSize] = useState({ w: 0, h: 0 }); // 根据当前语言动态生成 TOUR_STEPS const TOUR_STEPS = useMemo(() => [ { targetId: 'tour-new-chapter', title: t('tour.step1Title'), content: t('tour.step1Content'), placement: 'right' }, { targetId: 'tour-editor', title: t('tour.step2Title'), content: t('tour.step2Content'), placement: 'center' }, { targetId: 'tour-ai-btn', title: t('tour.step3Title'), content: t('tour.step3Content'), placement: 'left' }, { targetId: 'tour-settings', title: t('tour.step4Title'), content: t('tour.step4Content'), placement: 'top' }, { targetId: 'tour-help', title: t('tour.step5Title'), content: t('tour.step5Content'), placement: 'top' }, { targetId: 'tour-github', title: t('tour.step6Title'), content: t('tour.step6Content'), placement: 'top' } ], [t]); useEffect(() => { if (typeof window !== 'undefined') { const isDone = localStorage.getItem(ONBOARDING_KEY); if (!isDone) { if (language && visualTheme) { setStatus('intro'); } } setWindowSize({ w: window.innerWidth, h: window.innerHeight }); } }, [language, visualTheme]); const updateRect = useCallback((index) => { const step = TOUR_STEPS[index]; if (!step) return; let el = document.getElementById(step.targetId); if (!el && step.targetId === 'tour-editor') { el = document.querySelector('.editor-container'); } if (el) { const rect = el.getBoundingClientRect(); setTargetRect({ x: rect.x, y: rect.y, width: rect.width, height: rect.height }); el.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'center' }); } else { setTimeout(() => { const retryEl = document.getElementById(step.targetId) || document.querySelector('.editor-container'); if (retryEl) { const r = retryEl.getBoundingClientRect(); setTargetRect({ x: r.x, y: r.y, width: r.width, height: r.height }); } else { // Fallback: show tooltip centered when target element not found const cx = window.innerWidth / 2; const cy = window.innerHeight / 2; setTargetRect({ x: cx - 1, y: cy - 1, width: 2, height: 2 }); } }, 300); } }, [TOUR_STEPS]); const finishTour = () => { localStorage.setItem(ONBOARDING_KEY, 'true'); setStatus('hidden'); }; const skipToHelp = () => { setStatus('tour'); const helpStepIndex = TOUR_STEPS.length - 2; setCurrentStepIndex(helpStepIndex); updateRect(helpStepIndex); }; const beginTour = useCallback(() => { setStatus('tour'); setCurrentStepIndex(0); updateRect(0); }, [updateRect]); useEffect(() => { if (startTour) { beginTour(); setStartTour(false); } }, [startTour, beginTour, setStartTour]); // 监听窗口缩放和滚动 useEffect(() => { if (status !== 'tour') return; const handleResize = () => { setWindowSize({ w: window.innerWidth, h: window.innerHeight }); updateRect(currentStepIndex); }; window.addEventListener('resize', handleResize); window.addEventListener('scroll', handleResize, true); return () => { window.removeEventListener('resize', handleResize); window.removeEventListener('scroll', handleResize, true); }; }, [status, currentStepIndex, updateRect]); const nextStep = () => { if (currentStepIndex === TOUR_STEPS.length - 1) { finishTour(); } else { setCurrentStepIndex(i => i + 1); updateRect(currentStepIndex + 1); } }; const prevStep = () => { if (currentStepIndex > 0) { setCurrentStepIndex(i => i - 1); updateRect(currentStepIndex - 1); } }; if (status === 'hidden') return null; // ================= Intro 弹窗 ================= if (status === 'intro') { return (
{t('tour.introSubtitle')}
{t('tour.introHint')}