diff --git a/app/(presentation)/presentation/layout.jsx b/app/(presentation)/presentation/layout.jsx index f85fd44..368a3b2 100644 --- a/app/(presentation)/presentation/layout.jsx +++ b/app/(presentation)/presentation/layout.jsx @@ -1,6 +1,5 @@ import Context from "@/context/Context"; - export default function RootLayout({ children }) { return ( diff --git a/components/presentation/Presentation.css b/components/presentation/Presentation.css new file mode 100644 index 0000000..df4d5c6 --- /dev/null +++ b/components/presentation/Presentation.css @@ -0,0 +1,121 @@ +.reveal-container { + height: 100vh; + width: 100%; + position: fixed; + top: 0; + left: 0; + z-index: 1000; + background: black; +} + +.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; +} diff --git a/components/presentation/Presentation.jsx b/components/presentation/Presentation.jsx index e320fa2..cd11765 100644 --- a/components/presentation/Presentation.jsx +++ b/components/presentation/Presentation.jsx @@ -1,153 +1,146 @@ 'use client'; -import { useEffect, useRef } from 'react'; +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: 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: [] +}; + +// 配置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(() => { - if (!data) return; + setMounted(true); + }, []); - // 配置 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) => { + // 解析Markdown内容为幻灯片 + const parseMarkdownToSlides = useCallback((data) => { if (!data) { return `

演示文稿

暂无内容

`; } - // 如果有 content 字段,直接使用 markdown 内容 - if (data.content) { - const slideSeparators = /^---$/gm; - const sections = data.content.split(slideSeparators); + 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 ''; - return sections.map((section, index) => { - const trimmedSection = section.trim(); - if (!trimmedSection) return ''; - - const htmlContent = marked(trimmedSection); - - // 如果是第一个幻灯片且没有标题,添加标题 - if (index === 0 && !htmlContent.includes('

') && data.title) { - return `

${data.title}

${htmlContent}
`; - } - - return `
${htmlContent}
`; - }).join(''); - } + const htmlContent = marked(trimmedSection); + + // 如果是第一个幻灯片且没有标题,添加标题 + if (index === 0 && !htmlContent.includes('

') && data.title) { + return `

${data.title}

${htmlContent}
`; + } + + return `
${htmlContent}
`; + }).join(''); + }; - // 如果没有 content 字段,使用 items 数据构建幻灯片 - const items = data.items || []; - if (items.length === 0) { - // 使用主数据构建单个幻灯片 - return `
-

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

- ${data.subtitle ? `

${data.subtitle}

` : ''} - ${data.description ? `
${marked(data.description)}
` : ''} - ${data.image ? `${data.title}` : ''} -
`; - } + // 解析单个幻灯片 + const parseSingleSlide = (data) => { + return `
+

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

+ ${data.subtitle ? `

${data.subtitle}

` : ''} + ${data.description ? `
${marked(data.description)}
` : ''} + ${data.image ? `${data.title}` : ''} +
`; + }; - // 使用 items 构建多个幻灯片 + // 解析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(`
@@ -168,128 +160,81 @@ export default function Presentation({ data }) { }).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 ( -
- +
- {/* 初始内容,会被 JavaScript 替换 */}

正在加载内容...