From 9933a1711a900b43c273f9014afaed329d6fcdaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BB=9D=E5=B0=98?= <237809796@qq.com> Date: Sun, 12 Jul 2026 13:28:04 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84:=20=E6=B8=85=E7=90=86?= =?UTF-8?q?=E6=8A=80=E6=9C=AF=E5=80=BA=20+=20=E6=95=B0=E6=8D=AE=E5=81=A5?= =?UTF-8?q?=E5=A3=AE=E6=80=A7=E5=A2=9E=E5=BC=BA(P0+P1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 删除 inline.ts(方案A遗留死代码,已被 richtext.ts 替代) - 删除 ElementStyle.rich 字段(segments 方案后无用) - 删除 PropsPanel 未使用的 CHART_TYPE_MAP - 删除 editor.css 旧 class 兼容样式(.el-card-title 等) - 生成整套/重置时自动备份当前 deck 到 .bak(防误覆盖) - 新增 recoverBackup() 方法(可恢复上一次被覆盖的 deck) - 旧版本数据不再直接丢弃,改为 migrateDeck() 补字段升级 - chart 元素自动补 chartType:'bar' 默认值 --- src/components/editor/PropsPanel.vue | 2 +- src/core/inline.ts | 54 ---------------------------- src/core/store.ts | 51 +++++++++++++++++++++++--- src/core/types.ts | 2 -- src/styles/editor.css | 7 +--- 5 files changed, 49 insertions(+), 67 deletions(-) delete mode 100644 src/core/inline.ts diff --git a/src/components/editor/PropsPanel.vue b/src/components/editor/PropsPanel.vue index 4e2cc24..3835f6f 100644 --- a/src/components/editor/PropsPanel.vue +++ b/src/components/editor/PropsPanel.vue @@ -20,8 +20,8 @@ const CHART_TYPES: Array<{ k: ChartType; label: string; icon: string }> = [ { k: 'radar', label: '雷达图', icon: '🕸' }, { k: 'progress', label: '进度图', icon: '⭕' } ] -const CHART_TYPE_MAP: Record = Object.fromEntries(CHART_TYPES.map(t => [t.k, t])) +const emit = defineEmits const selected = computed(() => store.getSelected()) const slide = computed(() => store.currentSlide.value) diff --git a/src/core/inline.ts b/src/core/inline.ts deleted file mode 100644 index 6c2b1f9..0000000 --- a/src/core/inline.ts +++ /dev/null @@ -1,54 +0,0 @@ -/* ===================================================================== - * inline.ts — Markdown 行内格式渲染 - * 把 **加粗** / *斜体* / ~~删除线~~ / ==高亮== / `代码` / ~下标~ / ^上标^ 转 HTML - * 先转义 HTML,再按顺序替换(避免互相破坏) - * ===================================================================== */ - -function escapeHtml(s: string): string { - return s.replace(/&/g, '&').replace(//g, '>') -} - -/** - * 把 Markdown 行内语法转 HTML。 - * 输入应为一行文本(不含换行)。 - * 安全:先转义 HTML,再插入受控标签。 - */ -export function renderInline(text: string): string { - if (!text) return '' - let s = escapeHtml(text) - - // 加粗 **text**(必须先于斜体,避免 ** 与 * 冲突) - s = s.replace(/\*\*([^*]+)\*\*/g, '$1') - // 斜体 *text* 或 _text_ - s = s.replace(/(?$1') - s = s.replace(/(?$1') - // 删除线 ~~text~~ - s = s.replace(/~~([^~]+)~~/g, '$1') - // 高亮 ==text== - s = s.replace(/==([^=]+)==/g, '$1') - // 行内代码 `code` - s = s.replace(/`([^`]+)`/g, '$1') - // 下标 ~text~(与删除线区分:单个 ~) - s = s.replace(/(?$1') - // 上标 ^text^ - s = s.replace(/\^([^\^\s][^\^]*?)\^/g, '$1') - - return s -} - -/** - * 渲染多行文本:逐行 renderInline,用
连接。 - * 用于 title / text / quote 元素的 content。 - */ -export function renderInlineMulti(text: string): string { - if (!text) return '' - return text.split('\n').map(renderInline).join('
') -} - -/** - * 是否包含 Markdown 行内语法(用于决定是否走 rich 渲染路径) - */ -export function hasInlineSyntax(text: string): boolean { - if (!text) return false - return /\*\*[^*]+\*\*|(? { touchKey = null }, 900) } +/* ---------- 数据版本迁移(旧版数据补字段升级,不丢弃) ---------- */ +function migrateDeck(d: any): Deck { + // v2→v3: 给元素补默认 chartType / 新元素类型无需处理 + if (!d.v || d.v < 3) { + // 确保每个元素有 style 对象 + (d.slides || []).forEach((s: any) => { + (s.elements || []).forEach((e: any) => { + if (!e.style) e.style = {}; + if (e.type === 'chart' && !e.style.chartType) e.style.chartType = 'bar'; + }); + }); + } + d.v = DECK_VERSION; + return d as Deck; +} + /* ---------- 初始化 ---------- */ function init() { let loaded: Deck | null = null try { loaded = JSON.parse(localStorage.getItem(LS_KEY) || 'null') } catch (e) { /* ignore */ } const curV = SAMPLE_DECK.v - const valid = loaded && loaded.slides && loaded.slides.length && loaded.v === curV - if (!valid && loaded && loaded.slides) { - try { localStorage.setItem(LS_KEY + '.bak', JSON.stringify(loaded)) } catch (e) {} // 旧版备份 + let deck: Deck + if (loaded && loaded.slides && loaded.slides.length) { + if (loaded.v !== curV) { + // 版本不匹配 → 尝试迁移 + try { localStorage.setItem(LS_KEY + '.bak', JSON.stringify(loaded)) } catch (e) {} + deck = migrateDeck(loaded) + } else { + deck = loaded + } + } else { + deck = clone(SAMPLE_DECK) } Object.assign(state, { - deck: valid ? loaded! : clone(SAMPLE_DECK), + deck, currentIndex: 0, selectedId: null, activeLibId: null @@ -377,6 +401,10 @@ function redo(): boolean { /* ---------- 整体替换(AI 生成 / 导入 / 重置用) ---------- */ function replaceDeck(newDeck: Deck | Record, opts?: { keepHistory?: boolean; newChat?: boolean }) { if (!opts?.keepHistory) pushHistory() + // 自动备份:newChat=true 时说明是生成整套/重置等“覆盖性操作”,把当前 deck 存入 .bak + if (opts?.newChat && state.deck?.slides?.length) { + try { localStorage.setItem(LS_KEY + '.bak', JSON.stringify(state.deck)) } catch (e) {} + } suppressHistory = true let d = newDeck as Deck if (!d || typeof d !== 'object') d = {} as Deck @@ -663,6 +691,19 @@ function clearChat(chatId: string) { try { localStorage.removeItem(CHAT_PREFIX + chatId) } catch (e) {} } +/** 恢复上一次被覆盖的 deck(生成整套/重置前的自动备份) */ +function recoverBackup(): boolean { + try { + const bak = localStorage.getItem(LS_KEY + '.bak') + if (!bak) return false + const d = JSON.parse(bak) + if (!d || !d.slides || !d.slides.length) return false + replaceDeck(d) + localStorage.removeItem(LS_KEY + '.bak') + return true + } catch (e) { return false } +} + /* ---------- 导出 ---------- */ export const store = { // 状态(响应式) @@ -699,4 +740,6 @@ export const store = { getTemplates, getUserTemplates, saveCurrentAsTemplate, addSlideFromTemplate, deleteTemplate, // 会话管理 getChatId, getChat, setChat, clearChat, newChatId, + // 备份恢复 + recoverBackup, } diff --git a/src/core/types.ts b/src/core/types.ts index a330305..50809c3 100644 --- a/src/core/types.ts +++ b/src/core/types.ts @@ -85,8 +85,6 @@ export interface ElementStyle { // card icon?: string accent?: ColorKey - /** 文字元素是否启用 Markdown 行内格式(加粗/斜体/高亮等) */ - rich?: boolean } /** 幻灯片元素 */ diff --git a/src/styles/editor.css b/src/styles/editor.css index 474d38e..9ac1543 100644 --- a/src/styles/editor.css +++ b/src/styles/editor.css @@ -275,11 +275,6 @@ white-space: pre-wrap; flex: 1; } -/* 兼容旧 class 名 */ -.el-card-title { font-weight: 700; font-size: 1.5em; line-height: 1.15; display: flex; align-items: center; gap: .45em; } -.el-card-text { font-size: 1em; line-height: 1.5; opacity: .88; white-space: pre-wrap; } -.el-card-body { padding: 1.1em 1.3em 1.2em; flex: 1; display: flex; flex-direction: column; gap: .5em; justify-content: flex-start; } - /* 渐变形状由 renderElement 内联 backgroundImage;此处仅兜底圆角 */ .el-shape.gradient { border-radius: var(--shape-radius, 0); } @@ -314,7 +309,7 @@ .canvas-placeholder .ph-sub { font-size: 14px; opacity: .8; } /* 长内容溢出兆底 */ -.el-text, .el-card-title, .el-card-text { overflow: hidden; } +.el-text { overflow: hidden; } .el-stat .num { overflow-wrap: anywhere; } .el-chart .bar-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }