'use client'; import { useEffect, useRef, useState, useCallback, useMemo } from 'react'; import Reveal from 'reveal.js'; import 'reveal.js/dist/reveal.css'; import 'reveal.js/dist/theme/black.css'; import { marked } from 'marked'; import './Presentation.css'; // Reveal.js配置 const REVEAL_CONFIG = { hash: true, transition: 'slide', transitionSpeed: 'default', backgroundTransition: 'fade', controls: true, progress: true, center: true, touch: true, loop: false, rtl: false, navigationMode: 'default', shuffle: false, fragments: true, fragmentInURL: false, embedded: false, help: true, showNotes: true, autoPlayMedia: null, preloadIframes: null, autoSlide: 0, autoSlideStoppable: true, autoSlideMethod: Reveal.navigateNext, defaultTiming: null, mouseWheel: false, hideInactiveCursor: true, hideCursorTime: 5000, previewLinks: false, postMessage: true, postMessageEvents: false, focusBodyOnPageVisibilityChange: true, viewDistance: 3, mobileViewDistance: 2, display: 'block', hideAnchorsOnUrl: false, plugins: [] }; // 配置marked解析器(只配置一次) marked.setOptions({ breaks: true, gfm: true, headerIds: false, mangle: false, }); export default function Presentation({ data }) { const deckRef = useRef(null); const revealRef = useRef(null); const [mounted, setMounted] = useState(false); const [error, setError] = useState(null); // 客户端挂载检测 useEffect(() => { setMounted(true); }, []); // 解析Markdown内容为幻灯片 const parseMarkdownToSlides = useCallback((data) => { if (!data) { return `

演示文稿

暂无内容

`; } try { // 如果有 content 字段,直接使用 markdown 内容 if (data.content) { return parseContentSlides(data); } // 如果没有 content 字段,使用 items 数据构建幻灯片 const items = data.items || []; if (items.length === 0) { return parseSingleSlide(data); } return parseItemsSlides(items); } catch (err) { console.error('解析幻灯片内容失败:', err); return `

解析错误

内容解析失败,请检查数据格式

`; } }, []); // 解析content字段的幻灯片 const parseContentSlides = (data) => { const slideSeparators = /^---$/gm; const sections = data.content.split(slideSeparators); return sections.map((section, index) => { const trimmedSection = section.trim(); if (!trimmedSection) return ''; // 检查是否有背景设置 const backgroundMatch = trimmedSection.match(//); let backgroundAttr = ''; let contentWithoutBackground = trimmedSection; if (backgroundMatch) { backgroundAttr = ` data-background="${backgroundMatch[1]}"`; contentWithoutBackground = trimmedSection.replace(//g, ''); } const htmlContent = marked(contentWithoutBackground); // 如果是第一个幻灯片且没有标题,添加标题 if (index === 0 && !htmlContent.includes('

') && data.title) { return `

${data.title}

${htmlContent}`; } return `${htmlContent}`; }).join(''); }; // 解析单个幻灯片 const parseSingleSlide = (data) => { return `

${data.title || '演示文稿'}

${data.subtitle ? `

${data.subtitle}

` : ''} ${data.description ? `
${marked(data.description)}
` : ''} ${data.image ? `${data.title}` : ''}
`; }; // 解析items数组的幻灯片 const parseItemsSlides = (items) => { return items.map((item, index) => { const slideContent = []; if (item.item_title) { slideContent.push(`

${item.item_title}

`); } if (item.item_subtitle) { slideContent.push(`

${item.item_subtitle}

`); } if (item.item_description) { slideContent.push(`
${marked(item.item_description)}
`); } if (item.item_image) { slideContent.push(`${item.item_title || 'Slide'}`); } if (item.item_video_src) { slideContent.push(``); } if (item.item_button_text && item.item_button_link) { slideContent.push(``); } return `
${slideContent.join('')}
`; }).join(''); }; // 初始化Reveal.js const initializeReveal = useCallback(() => { if (!deckRef.current || !data) return; try { // 清理之前的实例 if (revealRef.current) { revealRef.current.destroy(); } // 创建新实例 revealRef.current = new Reveal(deckRef.current, REVEAL_CONFIG); revealRef.current.initialize(); // 更新slides内容 const slides = parseMarkdownToSlides(data); const slidesContainer = deckRef.current.querySelector('.slides'); if (slidesContainer) { slidesContainer.innerHTML = slides; } setError(null); } catch (err) { console.error('Reveal.js初始化失败:', err); setError('PPT初始化失败,请刷新页面重试'); } }, [data, parseMarkdownToSlides]); // 数据变化时重新初始化 useEffect(() => { if (mounted && data) { initializeReveal(); } return () => { if (revealRef.current) { revealRef.current.destroy(); } }; }, [mounted, data, initializeReveal]); // 错误状态 if (error) { return (

PPT加载失败

{error}

); } // 加载状态 if (!mounted) { return (
正在加载PPT...
); } return (

正在加载内容...

); }