import { useEffect, ReactNode, useRef } from 'react' import { useTutorialStore, TutorialConfig } from '@stores/tutorialStore' import { StepNav } from './StepNav' import { ThemeToggle } from './ThemeToggle' import { Celebration } from './Celebration' import { ArrowLeft, ArrowRight, BookOpenText, MoreVertical, PanelLeftOpen } from 'lucide-react' interface TutorialOption { id: string title: string } interface TutorialProps { config: TutorialConfig tutorialId: string tutorialOptions: TutorialOption[] multiTutorialMode: boolean onSelectTutorial: (tutorialId: string) => void onBackToHub: () => void children: ReactNode } function isEditableTarget(target: EventTarget | null) { if (!(target instanceof HTMLElement)) { return false } if (target.isContentEditable || target.closest('[contenteditable="true"]')) { return true } return ['INPUT', 'TEXTAREA', 'SELECT'].includes(target.tagName) } export function Tutorial({ config, tutorialId, tutorialOptions, multiTutorialMode, onSelectTutorial, onBackToHub, children, }: TutorialProps) { const { setConfig, currentStepIndex, nextStep, prevStep, config: storedConfig, sidebarExpanded, toggleSidebar, getProgress, completedSteps, resetProgress, } = useTutorialStore() const hasTriggeredCelebration = useRef(false) const contentRef = useRef(null) // 当步骤切换时,重置滚动位置到顶部 useEffect(() => { if (contentRef.current) { contentRef.current.scrollTop = 0 } }, [currentStepIndex]) useEffect(() => { if (!storedConfig) { setConfig(config) } }, [config, storedConfig, setConfig]) // 监听进度变化,当完成时触发庆祝动画 useEffect(() => { const progress = getProgress() if (progress === 100 && !hasTriggeredCelebration.current && completedSteps.length > 0) { hasTriggeredCelebration.current = true // 延迟触发,让用户看到完成状态 setTimeout(() => { window.dispatchEvent(new CustomEvent('tutorial-complete')) }, 500) } }, [getProgress, completedSteps.length]) // 监听重置事件 useEffect(() => { const handleReset = () => { hasTriggeredCelebration.current = false resetProgress() } window.addEventListener('tutorial-reset', handleReset) return () => window.removeEventListener('tutorial-reset', handleReset) }, [resetProgress]) useEffect(() => { const handleKeydown = (event: KeyboardEvent) => { if (event.defaultPrevented || event.metaKey || event.ctrlKey || event.altKey) { return } if (isEditableTarget(event.target)) { return } if (event.key === 'ArrowRight' || event.key === 'PageDown') { event.preventDefault() nextStep() return } if (event.key === 'ArrowLeft' || event.key === 'PageUp') { event.preventDefault() prevStep() } } window.addEventListener('keydown', handleKeydown) return () => window.removeEventListener('keydown', handleKeydown) }, [nextStep, prevStep]) const currentStep = config.steps[currentStepIndex] const canGoNext = currentStepIndex < config.steps.length - 1 const canGoPrev = currentStepIndex > 0 return (
{/* 庆祝动画 */} {/* 左侧导航 */} {/* 主内容区 */}
{/* 顶部栏 */}
{/* 左侧:侧边栏切换 + 返回按钮 + 页面标题 */}
{/* 仅在侧边栏收起时显示顶部展开按钮,保证界面同一时刻只有一个折叠/展开按钮 */} {!sidebarExpanded && ( )}

{currentStepIndex + 1}. {currentStep?.title || '加载中...'}

{/* 右侧:主题切换 + 菜单 */}
{multiTutorialMode && ( <> )}
{/* 内容区 */}
{/* 步骤内容 */}
{children}
{/* 底部导航 - 在内容区域内 */}
) }