feat: 添加ESLint和Prettier配置并优化代码结构
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled

style: 格式化代码文件并修复样式问题

docs: 新增部署文档和系统要求文档

test: 更新测试截图和覆盖率报告

refactor: 重构SchedulerPanel加载状态逻辑

ci: 添加lint和format脚本到package.json

build: 更新依赖项并添加开发工具

chore: 添加验证报告和上线审查计划
This commit is contained in:
iven
2026-03-26 08:02:23 +08:00
parent bf6d81f9c6
commit d0c6319fc1
286 changed files with 239803 additions and 1118 deletions

View File

@@ -349,9 +349,38 @@ export function ClassroomPreviewer({
const handleExport = (format: 'pptx' | 'html' | 'pdf') => {
if (onExport) {
onExport(format);
} else {
toast(`导出 ${format.toUpperCase()} 功能开发中...`, 'info');
return;
}
// Default export implementation
toast(`正在导出 ${format.toUpperCase()} 格式...`, 'info');
setTimeout(() => {
try {
if (format === 'html') {
const htmlContent = generateClassroomHTML(data);
downloadFile(htmlContent, `${data.title}.html`, 'text/html');
toast('HTML 导出成功', 'success');
} else if (format === 'pptx') {
// Export as JSON for conversion
const pptxData = JSON.stringify(data, null, 2);
downloadFile(pptxData, `${data.title}.slides.json`, 'application/json');
toast('幻灯片数据已导出JSON格式', 'success');
} else if (format === 'pdf') {
const htmlContent = generatePrintableHTML(data);
const printWindow = window.open('', '_blank');
if (printWindow) {
printWindow.document.write(htmlContent);
printWindow.document.close();
printWindow.print();
toast('已打开打印预览', 'success');
}
}
} catch (err) {
const errorMsg = err instanceof Error ? err.message : '导出失败';
toast(`导出失败: ${errorMsg}`, 'error');
}
}, 300);
};
return (
@@ -530,3 +559,135 @@ export function ClassroomPreviewer({
}
export default ClassroomPreviewer;
// === Helper Functions ===
function downloadFile(content: string, filename: string, mimeType: string) {
const blob = new Blob([content], { type: mimeType });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
}
function generateClassroomHTML(data: ClassroomData): string {
const scenesHTML = data.scenes.map((scene, index) => `
<section class="slide" data-index="${index}">
<div class="slide-content ${scene.type}">
<h2>${scene.content.heading || scene.title}</h2>
${scene.content.bullets ? `
<ul>
${scene.content.bullets.map((b: string) => `<li>${b}</li>`).join('')}
</ul>
` : ''}
${scene.narration ? `<p class="narration">${scene.narration}</p>` : ''}
</div>
</section>
`).join('');
return `<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${data.title}</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #3b82f6, #8b5cf6, #6366f1);
min-height: 100vh;
color: white;
}
.presentation { max-width: 1200px; margin: 0 auto; padding: 2rem; }
header { text-align: center; padding: 2rem 0; border-bottom: 1px solid rgba(255,255,255,0.2); margin-bottom: 2rem; }
h1 { font-size: 2.5rem; margin-bottom: 0.5rem; }
.meta { opacity: 0.8; font-size: 0.9rem; }
.slide {
background: rgba(255,255,255,0.1);
border-radius: 1rem;
padding: 2rem;
margin-bottom: 1.5rem;
backdrop-filter: blur(10px);
}
.slide h2 { font-size: 1.8rem; margin-bottom: 1rem; }
.slide ul { list-style: none; padding-left: 1rem; }
.slide li { margin-bottom: 0.75rem; font-size: 1.1rem; }
.slide li::before { content: '•'; color: #60a5fa; margin-right: 0.5rem; }
.narration {
background: rgba(0,0,0,0.3);
padding: 1rem;
border-radius: 0.5rem;
margin-top: 1rem;
font-style: italic;
opacity: 0.9;
}
.title .slide-content { text-align: center; min-height: 200px; display: flex; flex-direction: column; justify-content: center; }
.quiz { background: rgba(34, 197, 94, 0.2); }
.summary { background: rgba(168, 85, 247, 0.2); }
footer { text-align: center; padding: 2rem 0; border-top: 1px solid rgba(255,255,255,0.2); margin-top: 2rem; opacity: 0.6; }
</style>
</head>
<body>
<div class="presentation">
<header>
<h1>${data.title}</h1>
<p class="meta">${data.subject} · ${data.difficulty} · ${data.duration} 分钟</p>
</header>
<main>${scenesHTML}</main>
<footer><p>由 ZCLAW 课堂生成器创建</p></footer>
</div>
</body>
</html>`;
}
function generatePrintableHTML(data: ClassroomData): string {
const scenesHTML = data.scenes.map((scene, index) => `
<div class="page" style="page-break-after: always;">
<div class="slide-print">
<h1 style="font-size: 24pt; margin-bottom: 20pt;">${scene.content.heading || scene.title}</h1>
${scene.content.bullets ? `
<ul style="font-size: 14pt; line-height: 1.8;">
${scene.content.bullets.map((b: string) => `<li>${b}</li>`).join('')}
</ul>
` : ''}
${scene.narration ? `<p style="background: #f0f0f0; padding: 10pt; margin-top: 20pt; font-style: italic;">${scene.narration}</p>` : ''}
<p style="position: absolute; bottom: 20pt; right: 20pt; color: #999; font-size: 10pt;">${index + 1} / ${data.scenes.length}</p>
</div>
</div>
`).join('');
return `<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>${data.title} - 打印版</title>
<style>
@media print {
body { margin: 0; }
.page { page-break-after: always; }
}
body { font-family: 'Microsoft YaHei', sans-serif; }
.slide-print {
width: 100%;
height: 100vh;
padding: 40pt;
position: relative;
}
</style>
</head>
<body>
<div class="document">
<header style="text-align: center; margin-bottom: 30pt;">
<h1 style="font-size: 32pt;">${data.title}</h1>
<p style="color: #666;">${data.subject} · ${data.difficulty} · ${data.duration} 分钟</p>
</header>
${scenesHTML}
</div>
</body>
</html>`;
}