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; }
+}