From 2ddd12082e94b1406872ee4db17337951acd96f0 Mon Sep 17 00:00:00 2001 From: Muse Date: Sat, 14 Nov 2026 11:55:31 +0800 Subject: [PATCH] =?UTF-8?q?fix(export):=20=E7=9B=B8=E5=AF=B9=E5=AF=BC?= =?UTF-8?q?=E5=87=BA=E4=B8=89=E9=81=93=E4=BF=9D=E9=99=A9=EF=BC=88E3=20?= =?UTF-8?q?=E4=B8=89=E8=BD=AE=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 未保存文档禁用相对模式并说明原因;保存对话框默认打开到文档所在 文件夹;导出后按 statFile 核查正文图片在文档旁是否存在,缺失弹窗报数, 不再静默裂图。 --- src/App.tsx | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 2e8abb6..f85500d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -824,16 +824,48 @@ export default function App() { }) }, [docOverrides, ui.docTheme, tauriMode, resolveExportImage]) + /** 相对导出后核查:正文引用的本地图片是否真实存在于文档旁,缺失数返回给提示。 */ + const countMissingImages = useCallback(async (docPath: string, content: string): Promise => { + const srcs = new Set() + for (const m of content.matchAll(/!\[[^\]]*\]\(([^)\s]+)/g)) srcs.add(m[1]) + let missing = 0 + for (const src of srcs) { + if (/^(https?:|data:)/i.test(src)) continue + let decoded = src + try { decoded = decodeURIComponent(src) } catch { /* 保持原样 */ } + try { + if (!(await statFile(joinPath(dirOf(docPath), decoded))).exists) missing++ + } catch { missing++ } + } + return missing + }, []) + const exportHtmlFile = useCallback(async (imageMode: ImageMode) => { if (!tab) return setExportOpen(false) try { if (tauriMode) { - // 先定落点再生成:相对模式要按 HTML 文件所在目录重写图片路径(验收 E3) - const target = await pickSaveHtmlFile(`${tab.title}.html`) + // 相对模式的前提是文档已落盘:没有磁盘路径就无从定位图片(验收 E3 三轮) + if (imageMode === 'relative' && !tab.path) { + setError('当前文档尚未保存到磁盘,相对路径模式无法定位图片;请先保存文档,或改用「图片内嵌」模式导出') + return + } + // 先定落点再生成:相对模式要按 HTML 文件所在目录重写图片路径(验收 E3); + // 保存对话框默认打开到文档所在文件夹,同目录导出时相对路径天然成立 + const defaultTarget = imageMode === 'relative' && tab.path + ? joinPath(dirOf(tab.path), `${tab.title}.html`) + : `${tab.title}.html` + const target = await pickSaveHtmlFile(defaultTarget) if (!target) return const html = await buildExportHtml(tab, imageMode, { exportDir: dirOf(target) }) await writeTextFile(target, html, false) + if (imageMode === 'relative' && tab.path) { + const missing = await countMissingImages(tab.path, tab.content) + if (missing > 0) { + flash(`已导出 HTML,但有 ${missing} 张图片在文档旁未找到,相对链接会失效(先确认编辑器里图片是否正常显示)`) + return + } + } flash(`已导出 HTML:${baseName(target)}`) } else { const html = await buildExportHtml(tab, imageMode) @@ -841,7 +873,7 @@ export default function App() { flash('已导出 HTML(浏览器下载)') } } catch (e) { setError(String(e)) } - }, [tab, buildExportHtml, tauriMode, flash]) + }, [tab, buildExportHtml, tauriMode, flash, countMissingImages]) const copyHtml = useCallback(async () => { if (!tab) return