新增: 导出 JSON + 导入 JSON + 打印/PDF 导出(P4)

- 工具栏新增三个按钮: 导出 JSON / 导入 JSON / 打印 PDF
- 导出 JSON: Blob 下载 store.exportJSON()
- 导入 JSON: 文件选择 + FileReader → store.importJSON()
- 打印 PDF: window.print() + @media print CSS,逐页 page-break
- 打印时隐藏编辑器/HUD/工具栏,只保留幻灯片内容
- 零新依赖(PDF 走浏览器原生打印)
This commit is contained in:
2026-07-12 15:02:27 +08:00
parent 9ec6173b10
commit 056dc58318
3 changed files with 67 additions and 0 deletions

View File

@@ -71,6 +71,49 @@ function onSave() {
}
function openAi() { switchTab('ai') }
/* ---------- 导出/导入 ---------- */
function onExportJson() {
const json = store.exportJSON()
const blob = new Blob([json], { type: 'application/json' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = 'u-ppt-export.json'
a.click()
URL.revokeObjectURL(url)
toast('已导出 JSON')
}
function onImportJson() {
const input = document.createElement('input')
input.type = 'file'
input.accept = '.json,application/json'
input.onchange = () => {
const file = input.files?.[0]
if (!file) return
const reader = new FileReader()
reader.onload = () => {
try {
store.importJSON(reader.result as string)
toast('已导入')
} catch (e: any) {
toast('导入失败:' + (e?.message || String(e)))
}
}
reader.readAsText(file)
}
input.click()
}
function onExportPdf() {
// 用浏览器打印 + @media print CSS 实现 PDF 导出
// 演示模式的 slide-layer 已有 print 样式
document.body.classList.add('printing')
window.print()
setTimeout(() => document.body.classList.remove('printing'), 500)
toast('在打印对话框中选择「另存为 PDF」')
}
/* ---------- 全局快捷键(编辑模式) ---------- */
function onKey(e: KeyboardEvent) {
if (document.body.dataset.mode === 'present') return
@@ -119,6 +162,9 @@ onUnmounted(() => {
@open-ai="openAi"
@save="onSave"
@open-templates="templateVisible = true"
@export-json="onExportJson"
@import-json="onImportJson"
@export-pdf="onExportPdf"
/>
<div class="editor-body">

View File

@@ -13,6 +13,9 @@ const emit = defineEmits<{
(e: 'open-ai'): void
(e: 'save'): void
(e: 'open-templates'): void
(e: 'export-json'): void
(e: 'import-json'): void
(e: 'export-pdf'): void
}>()
const themeKeys = Object.keys(themes)
@@ -70,6 +73,9 @@ defineProps<{ disabledActions?: string[] }>()
<span class="sep"></span>
<button class="btn" data-action="library" title="我的演示文库" @click="action('library')">📁 文库</button>
<button class="btn" data-action="save" title="保存到文库" :disabled="disabledActions?.includes('save')" @click="action('save')">💾 保存</button>
<button class="btn" data-action="export-json" title="导出 JSON" @click="emit('export-json')">📤 导出</button>
<button class="btn" data-action="import-json" title="导入 JSON" @click="emit('import-json')">📥 导入</button>
<button class="btn" data-action="export-pdf" title="打印/导出 PDF" @click="emit('export-pdf')">🖨 PDF</button>
<button class="btn" data-action="open-ai" title="打开 AI 助手" @click="action('open-ai')">🤖 AI</button>
<button class="btn" data-action="reset" title="重置为内置示例" :disabled="disabledActions?.includes('reset')" @click="action('reset')"> 重置</button>
<button class="btn primary" data-action="present" title="开始演示 (F5)" :disabled="disabledActions?.includes('present')" @click="action('present')"> 演示</button>

View File

@@ -121,3 +121,18 @@
.present-stage .slide-inner.animated .el[data-type="list"] .li { opacity: 1 !important; animation: none !important; }
.slide-inner.enter-next, .slide-inner.enter-prev { animation: none !important; }
}
/* ===== 打印 / 导出 PDF ===== */
@media print {
/* 隐藏编辑器、工具栏、HUD、toast */
.app-editor, .toolbar, .canvas-status, .present-hud, .toast, .panel-tabs, .side-panel, .thumb-bar { display: none !important; }
body.printing .app-present { display: block !important; }
body.printing .present-stage { position: static; }
body.printing .present-hud { display: none !important; }
body.printing .slide-layer { transform: none !important; }
body.printing .slide-inner { page-break-after: always; }
/* 编辑模式打印:隐藏画布手柄等编辑装饰 */
.handle, .canvas-placeholder { display: none !important; }
/* 如果不在演示模式,打印当前页 */
body.printing:not(:has(.app-present:not(.hidden))) .canvas-stage { display: block; }
}