'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 ``;
}).join('');
};
// 解析单个幻灯片
const parseSingleSlide = (data) => {
return `
${data.title || '演示文稿'}
${data.subtitle ? `${data.subtitle}
` : ''}
${data.description ? `${marked(data.description)}
` : ''}
${data.image ? `
` : ''}
`;
};
// 解析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(`
`);
}
if (item.item_video_src) {
slideContent.push(``);
}
if (item.item_button_text && item.item_button_link) {
slideContent.push(``);
}
return ``;
}).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 (
);
}
return (
);
}