演示测试成功,控制台仍然有错误
This commit is contained in:
parent
ed6e484d94
commit
a86ddfcbe3
@ -1,6 +1,5 @@
|
|||||||
import Context from "@/context/Context";
|
import Context from "@/context/Context";
|
||||||
|
|
||||||
|
|
||||||
export default function RootLayout({ children }) {
|
export default function RootLayout({ children }) {
|
||||||
return (
|
return (
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
|||||||
121
components/presentation/Presentation.css
Normal file
121
components/presentation/Presentation.css
Normal file
@ -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;
|
||||||
|
}
|
||||||
@ -1,32 +1,14 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useRef, useState, useCallback, useMemo } from 'react';
|
||||||
import Reveal from 'reveal.js';
|
import Reveal from 'reveal.js';
|
||||||
import 'reveal.js/dist/reveal.css';
|
import 'reveal.js/dist/reveal.css';
|
||||||
import 'reveal.js/dist/theme/black.css';
|
import 'reveal.js/dist/theme/black.css';
|
||||||
import { marked } from 'marked';
|
import { marked } from 'marked';
|
||||||
|
import './Presentation.css';
|
||||||
|
|
||||||
export default function Presentation({ data }) {
|
// Reveal.js配置
|
||||||
const deckRef = useRef(null);
|
const REVEAL_CONFIG = {
|
||||||
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,
|
hash: true,
|
||||||
transition: 'slide',
|
transition: 'slide',
|
||||||
transitionSpeed: 'default',
|
transitionSpeed: 'default',
|
||||||
@ -62,37 +44,54 @@ export default function Presentation({ data }) {
|
|||||||
display: 'block',
|
display: 'block',
|
||||||
hideAnchorsOnUrl: false,
|
hideAnchorsOnUrl: false,
|
||||||
plugins: []
|
plugins: []
|
||||||
|
};
|
||||||
|
|
||||||
|
// 配置marked解析器(只配置一次)
|
||||||
|
marked.setOptions({
|
||||||
|
breaks: true,
|
||||||
|
gfm: true,
|
||||||
|
headerIds: false,
|
||||||
|
mangle: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
revealRef.current.initialize();
|
export default function Presentation({ data }) {
|
||||||
|
const deckRef = useRef(null);
|
||||||
|
const revealRef = useRef(null);
|
||||||
|
const [mounted, setMounted] = useState(false);
|
||||||
|
const [error, setError] = useState(null);
|
||||||
|
|
||||||
// 在Reveal.js初始化后,更新slides内容
|
// 客户端挂载检测
|
||||||
setTimeout(() => {
|
useEffect(() => {
|
||||||
if (deckRef.current) {
|
setMounted(true);
|
||||||
const slides = parseMarkdownToSlides(data);
|
}, []);
|
||||||
const slidesContainer = deckRef.current.querySelector('.slides');
|
|
||||||
if (slidesContainer) {
|
|
||||||
slidesContainer.innerHTML = slides;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, 100);
|
|
||||||
|
|
||||||
// 清理函数
|
// 解析Markdown内容为幻灯片
|
||||||
return () => {
|
const parseMarkdownToSlides = useCallback((data) => {
|
||||||
if (revealRef.current) {
|
|
||||||
revealRef.current.destroy();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
// 将数据解析为幻灯片
|
|
||||||
const parseMarkdownToSlides = (data) => {
|
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return `<section><h1>演示文稿</h1><p>暂无内容</p></section>`;
|
return `<section><h1>演示文稿</h1><p>暂无内容</p></section>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
// 如果有 content 字段,直接使用 markdown 内容
|
// 如果有 content 字段,直接使用 markdown 内容
|
||||||
if (data.content) {
|
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 `<section><h1>解析错误</h1><p>内容解析失败,请检查数据格式</p></section>`;
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// 解析content字段的幻灯片
|
||||||
|
const parseContentSlides = (data) => {
|
||||||
const slideSeparators = /^---$/gm;
|
const slideSeparators = /^---$/gm;
|
||||||
const sections = data.content.split(slideSeparators);
|
const sections = data.content.split(slideSeparators);
|
||||||
|
|
||||||
@ -109,45 +108,39 @@ export default function Presentation({ data }) {
|
|||||||
|
|
||||||
return `<section>${htmlContent}</section>`;
|
return `<section>${htmlContent}</section>`;
|
||||||
}).join('');
|
}).join('');
|
||||||
}
|
};
|
||||||
|
|
||||||
// 如果没有 content 字段,使用 items 数据构建幻灯片
|
// 解析单个幻灯片
|
||||||
const items = data.items || [];
|
const parseSingleSlide = (data) => {
|
||||||
if (items.length === 0) {
|
|
||||||
// 使用主数据构建单个幻灯片
|
|
||||||
return `<section>
|
return `<section>
|
||||||
<h1>${data.title || '演示文稿'}</h1>
|
<h1>${data.title || '演示文稿'}</h1>
|
||||||
${data.subtitle ? `<p class="lead">${data.subtitle}</p>` : ''}
|
${data.subtitle ? `<p class="lead">${data.subtitle}</p>` : ''}
|
||||||
${data.description ? `<div class="mt-4">${marked(data.description)}</div>` : ''}
|
${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;" />` : ''}
|
${data.image ? `<img src="${data.image}" alt="${data.title}" style="max-width: 100%; height: auto; margin: 1rem 0;" />` : ''}
|
||||||
</section>`;
|
</section>`;
|
||||||
}
|
};
|
||||||
|
|
||||||
// 使用 items 构建多个幻灯片
|
// 解析items数组的幻灯片
|
||||||
|
const parseItemsSlides = (items) => {
|
||||||
return items.map((item, index) => {
|
return items.map((item, index) => {
|
||||||
const slideContent = [];
|
const slideContent = [];
|
||||||
|
|
||||||
// 标题
|
|
||||||
if (item.item_title) {
|
if (item.item_title) {
|
||||||
slideContent.push(`<h1>${item.item_title}</h1>`);
|
slideContent.push(`<h1>${item.item_title}</h1>`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 副标题
|
|
||||||
if (item.item_subtitle) {
|
if (item.item_subtitle) {
|
||||||
slideContent.push(`<p class="lead">${item.item_subtitle}</p>`);
|
slideContent.push(`<p class="lead">${item.item_subtitle}</p>`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 描述
|
|
||||||
if (item.item_description) {
|
if (item.item_description) {
|
||||||
slideContent.push(`<div class="mt-4">${marked(item.item_description)}</div>`);
|
slideContent.push(`<div class="mt-4">${marked(item.item_description)}</div>`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 图片
|
|
||||||
if (item.item_image) {
|
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;" />`);
|
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) {
|
if (item.item_video_src) {
|
||||||
slideContent.push(`<video controls style="max-width: 100%; height: auto; margin: 1rem 0;">
|
slideContent.push(`<video controls style="max-width: 100%; height: auto; margin: 1rem 0;">
|
||||||
<source src="${item.item_video_src}" type="video/mp4">
|
<source src="${item.item_video_src}" type="video/mp4">
|
||||||
@ -155,7 +148,6 @@ export default function Presentation({ data }) {
|
|||||||
</video>`);
|
</video>`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 按钮
|
|
||||||
if (item.item_button_text && item.item_button_link) {
|
if (item.item_button_text && item.item_button_link) {
|
||||||
slideContent.push(`<div class="mt-6">
|
slideContent.push(`<div class="mt-6">
|
||||||
<a href="${item.item_button_link}" class="btn btn-primary" target="_blank" rel="noopener noreferrer">
|
<a href="${item.item_button_link}" class="btn btn-primary" target="_blank" rel="noopener noreferrer">
|
||||||
@ -168,128 +160,81 @@ export default function Presentation({ data }) {
|
|||||||
}).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 (
|
return (
|
||||||
<div className="reveal-container" style={{ height: '100vh', width: '100%', position: 'fixed', top: 0, left: 0, zIndex: 1000, background: 'black' }}>
|
<div className="min-h-screen bg-black flex items-center justify-center">
|
||||||
<style jsx global>{`
|
<div className="text-white text-xl text-center">
|
||||||
.reveal-container .reveal {
|
<p className="mb-4">PPT加载失败</p>
|
||||||
height: 100% !important;
|
<p className="text-sm text-gray-400">{error}</p>
|
||||||
width: 100% !important;
|
<button
|
||||||
|
onClick={() => window.location.reload()}
|
||||||
|
className="mt-4 px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
|
||||||
|
>
|
||||||
|
刷新页面
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
.reveal-container .reveal .slides {
|
// 加载状态
|
||||||
height: 100% !important;
|
if (!mounted) {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-black flex items-center justify-center">
|
||||||
|
<div className="text-white text-xl">正在加载PPT...</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
.reveal-container .reveal .slides section {
|
return (
|
||||||
height: 100% !important;
|
<div className="reveal-container">
|
||||||
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
|
<div
|
||||||
ref={deckRef}
|
ref={deckRef}
|
||||||
className="reveal"
|
className="reveal"
|
||||||
style={{ height: '100%', width: '100%' }}
|
|
||||||
>
|
>
|
||||||
<div className="slides">
|
<div className="slides">
|
||||||
{/* 初始内容,会被 JavaScript 替换 */}
|
|
||||||
<section>
|
<section>
|
||||||
<p>正在加载内容...</p>
|
<p>正在加载内容...</p>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user