You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
4.0 KiB
4.0 KiB
设计:个人主页文章详情页导出 Markdown
日期:2026-04-23
状态:已定稿(待你最终确认后进入实现计划)
1. 背景与目标
当前个人主页文章详情页支持阅读与评论,但缺少“导出原始 Markdown”能力。
本期目标是在公开文章详情页新增导出能力,满足“访客可直接下载 .md 文件”的需求。
2. 本期确认范围
- 导出格式:仅
Markdown (.md)。 - 入口位置:个人主页文章详情页(
/@[publicSlug]/posts/[postSlug])。 - 权限:所有访客可导出(无需登录)。
- 导出内容:仅正文
bodyMarkdown。 - 图片处理:导出时将站内相对图片链接转换为绝对 URL。
3. 非目标
- 不导出 PDF / HTML。
- 不附带 Front Matter 元数据。
- 不打包图片文件到本地,仅处理 Markdown 内图片链接文本。
- 不新增后端导出 API(本期前端本地导出)。
4. 技术方案
采用前端本地导出(Blob + download):
- 在详情页数据加载完成后显示
导出 .md按钮。 - 点击按钮时读取当前已加载的
data.bodyMarkdown。 - 仅对 Markdown 图片语法
中的链接做归一化:- 若为站内相对路径(如
/static/media/...),替换为window.location.origin + path。 - 其他链接类型保持不变。
- 若为站内相对路径(如
- 生成
Blob(['...'], { type: 'text/markdown;charset=utf-8' })。 - 通过临时
<a download>触发下载,文件名按规则生成。
该方案不新增接口,改动范围小,符合“仅导出正文、访客可用、快速交付”的目标。
5. URL 归一化规则
5.1 处理对象
- 仅处理 Markdown 图片语法:
。 - 普通链接语法
[](...)不处理。
5.2 保持不变
http://...、https://...//...(协议相对)data:image...
5.3 转换条件
- 以
/开头的站内相对路径(重点覆盖/static/media/...)。
5.4 基地址来源
- 使用
window.location.origin作为导出时站点基地址。
5.5 示例
- 输入:
 - 当前访问域名:
https://example.com - 输出:

6. 交互与错误处理
- 按钮位置:详情页顶部操作区,与“返回主页 / 编辑”同级。
- 显示与可用性:
pending状态禁用按钮。error && !data时不显示按钮。data就绪后访客可点击导出。
- 成功反馈:导出完成后给轻量 Toast(如“已导出 Markdown”)。
- 失败反馈:捕获异常并提示“导出失败,请稍后重试”,不影响页面其他功能。
- 空正文:允许导出空
.md文件。
7. 文件命名规则
- 优先:
${postSlug}.md - 回退:
post-${id}.md
确保命名稳定、可预测,便于用户归档。
8. 验收标准
8.1 功能验收
- 公开文章详情页可见
导出 .md按钮,未登录访客可使用。 - 点击后下载文件名符合规则。
- 导出正文与
bodyMarkdown一致(仅图片链接归一化除外)。
8.2 图片链接验收
转换为当前域名下绝对 URL。- 已是绝对地址、协议相对地址、data URI 的图片链接保持不变。
- 普通链接
[](...)不被误改。
8.3 状态与异常
- 加载中按钮禁用;加载失败场景不显示导出按钮。
- 成功/失败提示可见且文案清晰。
- 异常不导致页面崩溃。
8.4 回归检查
- 原有“返回主页”“编辑(作者可见)”“评论”功能不受影响。
- 移动端与桌面端按钮布局保持可用。
9. 实现建议(供下一步计划使用)
- 修改页面:
app/pages/@[publicSlug]/posts/[postSlug].vue - 可抽离工具函数(可选):
app/utils/markdown-export.tsnormalizeMarkdownImageUrls(markdown: string, origin: string): stringdownloadMarkdownFile(filename: string, content: string): void
后续进入 implementation plan 时,可根据当前代码风格决定“内联实现”或“工具函数抽离”。