301 lines
9.1 KiB
JavaScript
301 lines
9.1 KiB
JavaScript
'use client';
|
||
|
||
import { useEffect, useRef } from 'react';
|
||
import Reveal from 'reveal.js';
|
||
import 'reveal.js/dist/reveal.css';
|
||
import 'reveal.js/dist/theme/black.css';
|
||
import { marked } from 'marked';
|
||
|
||
export default function Presentation({ data }) {
|
||
const deckRef = useRef(null);
|
||
const revealRef = useRef(null);
|
||
|
||
useEffect(() => {
|
||
if (!data) return;
|
||
|
||
// 配置 marked 解析器
|
||
marked.setOptions({
|
||
breaks: true,
|
||
gfm: true,
|
||
headerIds: false,
|
||
mangle: false,
|
||
});
|
||
|
||
// 初始化 Reveal.js
|
||
if (revealRef.current) {
|
||
revealRef.current.destroy();
|
||
}
|
||
|
||
revealRef.current = new Reveal(deckRef.current, {
|
||
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: false,
|
||
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: []
|
||
});
|
||
|
||
revealRef.current.initialize();
|
||
|
||
// 在Reveal.js初始化后,更新slides内容
|
||
setTimeout(() => {
|
||
if (deckRef.current) {
|
||
const slides = parseMarkdownToSlides(data);
|
||
const slidesContainer = deckRef.current.querySelector('.slides');
|
||
if (slidesContainer) {
|
||
slidesContainer.innerHTML = slides;
|
||
}
|
||
}
|
||
}, 100);
|
||
|
||
// 清理函数
|
||
return () => {
|
||
if (revealRef.current) {
|
||
revealRef.current.destroy();
|
||
}
|
||
};
|
||
}, [data]);
|
||
|
||
// 将数据解析为幻灯片
|
||
const parseMarkdownToSlides = (data) => {
|
||
if (!data) {
|
||
return `<section><h1>演示文稿</h1><p>暂无内容</p></section>`;
|
||
}
|
||
|
||
// 如果有 content 字段,直接使用 markdown 内容
|
||
if (data.content) {
|
||
const slideSeparators = /^---$/gm;
|
||
const sections = data.content.split(slideSeparators);
|
||
|
||
return sections.map((section, index) => {
|
||
const trimmedSection = section.trim();
|
||
if (!trimmedSection) return '';
|
||
|
||
const htmlContent = marked(trimmedSection);
|
||
|
||
// 如果是第一个幻灯片且没有标题,添加标题
|
||
if (index === 0 && !htmlContent.includes('<h1>') && data.title) {
|
||
return `<section><h1>${data.title}</h1>${htmlContent}</section>`;
|
||
}
|
||
|
||
return `<section>${htmlContent}</section>`;
|
||
}).join('');
|
||
}
|
||
|
||
// 如果没有 content 字段,使用 items 数据构建幻灯片
|
||
const items = data.items || [];
|
||
if (items.length === 0) {
|
||
// 使用主数据构建单个幻灯片
|
||
return `<section>
|
||
<h1>${data.title || '演示文稿'}</h1>
|
||
${data.subtitle ? `<p class="lead">${data.subtitle}</p>` : ''}
|
||
${data.description ? `<div class="mt-4">${marked(data.description)}</div>` : ''}
|
||
${data.image ? `<img src="${data.image}" alt="${data.title}" style="max-width: 100%; height: auto; margin: 1rem 0;" />` : ''}
|
||
</section>`;
|
||
}
|
||
|
||
// 使用 items 构建多个幻灯片
|
||
return items.map((item, index) => {
|
||
const slideContent = [];
|
||
|
||
// 标题
|
||
if (item.item_title) {
|
||
slideContent.push(`<h1>${item.item_title}</h1>`);
|
||
}
|
||
|
||
// 副标题
|
||
if (item.item_subtitle) {
|
||
slideContent.push(`<p class="lead">${item.item_subtitle}</p>`);
|
||
}
|
||
|
||
// 描述
|
||
if (item.item_description) {
|
||
slideContent.push(`<div class="mt-4">${marked(item.item_description)}</div>`);
|
||
}
|
||
|
||
// 图片
|
||
if (item.item_image) {
|
||
slideContent.push(`<img src="${item.item_image}" alt="${item.item_title || 'Slide'}" style="max-width: 100%; height: auto; margin: 1rem 0;" />`);
|
||
}
|
||
|
||
// 视频
|
||
if (item.item_video_src) {
|
||
slideContent.push(`<video controls style="max-width: 100%; height: auto; margin: 1rem 0;">
|
||
<source src="${item.item_video_src}" type="video/mp4">
|
||
您的浏览器不支持视频播放。
|
||
</video>`);
|
||
}
|
||
|
||
// 按钮
|
||
if (item.item_button_text && item.item_button_link) {
|
||
slideContent.push(`<div class="mt-6">
|
||
<a href="${item.item_button_link}" class="btn btn-primary" target="_blank" rel="noopener noreferrer">
|
||
${item.item_button_text}
|
||
</a>
|
||
</div>`);
|
||
}
|
||
|
||
return `<section>${slideContent.join('')}</section>`;
|
||
}).join('');
|
||
};
|
||
|
||
return (
|
||
<div className="reveal-container" style={{ height: '100vh', width: '100%', position: 'fixed', top: 0, left: 0, zIndex: 1000, background: 'black' }}>
|
||
<style jsx global>{`
|
||
.reveal-container .reveal {
|
||
height: 100% !important;
|
||
width: 100% !important;
|
||
}
|
||
|
||
.reveal-container .reveal .slides {
|
||
height: 100% !important;
|
||
}
|
||
|
||
.reveal-container .reveal .slides section {
|
||
height: 100% !important;
|
||
display: flex !important;
|
||
flex-direction: column !important;
|
||
justify-content: center !important;
|
||
align-items: center !important;
|
||
text-align: center !important;
|
||
padding: 2rem !important;
|
||
color: white !important;
|
||
}
|
||
|
||
.reveal-container .reveal .slides section h1,
|
||
.reveal-container .reveal .slides section h2,
|
||
.reveal-container .reveal .slides section h3,
|
||
.reveal-container .reveal .slides section h4,
|
||
.reveal-container .reveal .slides section h5,
|
||
.reveal-container .reveal .slides section h6 {
|
||
margin-bottom: 1rem !important;
|
||
color: white !important;
|
||
}
|
||
|
||
.reveal-container .reveal .slides section p {
|
||
margin-bottom: 0.5rem !important;
|
||
line-height: 1.6 !important;
|
||
color: white !important;
|
||
}
|
||
|
||
.reveal-container .reveal .slides section ul,
|
||
.reveal-container .reveal .slides section ol {
|
||
text-align: left !important;
|
||
margin: 1rem 0 !important;
|
||
color: white !important;
|
||
}
|
||
|
||
.reveal-container .reveal .slides section li {
|
||
margin-bottom: 0.5rem !important;
|
||
color: white !important;
|
||
}
|
||
|
||
.reveal-container .reveal .slides section code {
|
||
background: #333 !important;
|
||
color: #fff !important;
|
||
padding: 0.2rem 0.4rem !important;
|
||
border-radius: 3px !important;
|
||
font-family: 'Courier New', monospace !important;
|
||
}
|
||
|
||
.reveal-container .reveal .slides section pre {
|
||
background: #333 !important;
|
||
color: #fff !important;
|
||
padding: 1rem !important;
|
||
border-radius: 5px !important;
|
||
text-align: left !important;
|
||
overflow-x: auto !important;
|
||
}
|
||
|
||
.reveal-container .reveal .slides section blockquote {
|
||
border-left: 4px solid #666 !important;
|
||
padding-left: 1rem !important;
|
||
margin: 1rem 0 !important;
|
||
text-align: left !important;
|
||
color: #ccc !important;
|
||
}
|
||
|
||
.reveal-container .reveal .slides section table {
|
||
border-collapse: collapse !important;
|
||
width: 100% !important;
|
||
margin: 1rem 0 !important;
|
||
color: white !important;
|
||
}
|
||
|
||
.reveal-container .reveal .slides section th,
|
||
.reveal-container .reveal .slides section td {
|
||
border: 1px solid #666 !important;
|
||
padding: 0.5rem !important;
|
||
text-align: left !important;
|
||
color: white !important;
|
||
}
|
||
|
||
.reveal-container .reveal .slides section th {
|
||
background: #444 !important;
|
||
}
|
||
|
||
.reveal-container .reveal .slides section img {
|
||
max-width: 100% !important;
|
||
height: auto !important;
|
||
margin: 1rem 0 !important;
|
||
}
|
||
|
||
.reveal-container .reveal .slides section .btn {
|
||
display: inline-block !important;
|
||
padding: 0.5rem 1rem !important;
|
||
background: #007bff !important;
|
||
color: white !important;
|
||
text-decoration: none !important;
|
||
border-radius: 0.25rem !important;
|
||
margin: 0.5rem !important;
|
||
}
|
||
|
||
.reveal-container .reveal .slides section .btn:hover {
|
||
background: #0056b3 !important;
|
||
}
|
||
`}</style>
|
||
<div
|
||
ref={deckRef}
|
||
className="reveal"
|
||
style={{ height: '100%', width: '100%' }}
|
||
>
|
||
<div className="slides">
|
||
{/* 初始内容,会被 JavaScript 替换 */}
|
||
<section>
|
||
<p>正在加载内容...</p>
|
||
</section>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|