diff --git a/src/App.vue b/src/App.vue index 0693118..93a9181 100644 --- a/src/App.vue +++ b/src/App.vue @@ -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" />
diff --git a/src/components/editor/Toolbar.vue b/src/components/editor/Toolbar.vue index def6820..cdc337d 100644 --- a/src/components/editor/Toolbar.vue +++ b/src/components/editor/Toolbar.vue @@ -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[] }>() + + + diff --git a/src/styles/present.css b/src/styles/present.css index 05546b0..662e488 100644 --- a/src/styles/present.css +++ b/src/styles/present.css @@ -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; } +}