首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >免费的Markdown转PDF工具开源教程

免费的Markdown转PDF工具开源教程

原创
作者头像
天码行空
发布2026-01-21 10:34:22
发布2026-01-21 10:34:22
2120
举报
文章被收录于专栏:前端框架前端框架

我是老郑,今天做一个markdown文档站时,或者我们在使用各种平台的发布系统时都是markdown编辑器了。markdown语言现在是一项必备技能了,学习成本也很低,结构简单,没有HTML的过多标签和标签嵌套,几乎支持HTML所有功能,对于没有过多交互和排版要求,纯文字和图片内容的保存和展示是个很好的方式。

Markdown转PDF工具

回归正题,有时使用平台的markdown editor编辑器可以实时渲染成HTML,但有时想保存文字下来复制粘贴不方便,为了方便大家使用基本上AI也能实现回答内容导出成PDF了,于是我就也手搓一个Markdown to PDF工具,测试预览地址:https://www.markdownlang.com/markdown-to-pdf/

Markdown to PDF 是一款免费实用的在线转换工具,专注于将 Markdown 内容高效转为 PDF 格式,无论是本地 .md 文件还是 GitHub 上的 Markdown 文档,都能通过简单三步完成转换。

只需输入或导入内容,从多样专业样式中挑选心仪设计,借助实时预览功能确认效果后,即可直接下载生成的 PDF 文件,让技术笔记、文档分享或资料归档变得便捷又美观,无需复杂配置就能获得排版规整的专业文档。

##Markdown转PDF实现代码

代码语言:html
复制
<div class="main-content">
      <div class="input-panel">
        <div class="panel-header">
          <span class="panel-title">Markdown</span>
        </div>
        <div class="editor-container">
          <div class="textarea-with-linenumbers">
            <div class="line-numbers" ref="markdownLineNumbers"></div>
            <textarea
              v-model="markdownInput"
              @input="handleInput"
              @scroll="syncScroll"
              ref="markdownTextarea"
              :placeholder="t('md.inputPlaceholder')"
              class="markdown-editor"
              spellcheck="false"
            ></textarea>
          </div>
        </div>
        <div v-if="!isRealTime" class="panel-actions">
          <button @click="convertToHtml" class="convert-btn">{{ t('md.convert') }}</button>
        </div>
      </div>

      <div class="output-panel">
        <div class="panel-header">
          <span class="panel-title">PDF Preview</span>
          <div class="panel-actions">
            <button @click="copyHtml" style="margin-right: 10px;" class="action-btn" :disabled="!htmlOutput">{{ t('common.copy') }}</button>
            <button @click="exportPdf" class="action-btn" :disabled="!htmlOutput || isExporting">
          {{ isExporting ? 'Exporting...' : 'Export PDF' }}
        </button>
          </div>
        </div>
        <div class="output-container">
          <div class="html-preview" ref="previewRef" v-html="htmlOutput"></div>
        </div>
      </div>
    </div>

纯javascript实现转换

代码语言:javascript
复制
<script setup>
import { ref, computed, watch, nextTick, onMounted } from 'vue'
import { marked } from 'marked'
import { useI18n } from '../i18n'

const { t } = useI18n()

const markdownInput = ref('')
const isRealTime = ref(true)
const gfmSupport = ref(true)
const toast = ref({ show: false, message: '', type: 'success' })
const isExporting = ref(false)

const markdownTextarea = ref(null)
const markdownLineNumbers = ref(null)
const previewRef = ref(null)

marked.setOptions({
  gfm: gfmSupport.value,
  breaks: true,
  sanitize: false
})

const htmlOutput = computed(() => {
  if (!markdownInput.value.trim()) return ''
  try {
    return marked.parse(markdownInput.value)
  } catch (error) {
    console.error('Markdown parsing error:', error)
    return `<div class="error-message">${t('msg.md.convertError', { error: error.message })}</div>`
  }
})

watch(gfmSupport, (newValue) => {
  marked.setOptions({ gfm: newValue, breaks: true, sanitize: false })
})

watch(markdownInput, () => {
  if (isRealTime.value) updateLineNumbers()
})

const setConversionMode = (mode) => { isRealTime.value = mode === 'realtime' }
const toggleGfm = () => { gfmSupport.value = !gfmSupport.value }
const handleInput = () => { if (isRealTime.value) updateLineNumbers() }
const convertToHtml = () => { updateLineNumbers() }

const updateLineNumbers = () => {
  nextTick(() => {
    if (!markdownTextarea.value || !markdownLineNumbers.value) return
    const lines = markdownTextarea.value.value.split('\n').length
    const lineNumbers = Array.from({ length: lines }, (_, i) => i + 1).join('\n')
    markdownLineNumbers.value.textContent = lineNumbers
  })
}

const syncScroll = () => {
  if (markdownTextarea.value && markdownLineNumbers.value) {
    markdownLineNumbers.value.scrollTop = markdownTextarea.value.scrollTop
  }
}

const insertExample = () => {
  const exampleMarkdown = t('md.example.content')
  markdownInput.value = exampleMarkdown
  updateLineNumbers()
}

const clearAll = () => {
  markdownInput.value = ''
  updateLineNumbers()
}

const copyHtml = async () => {
  try {
    await navigator.clipboard.writeText(htmlOutput.value)
    showToast(t('msg.md.copySuccess'), 'success')
  } catch (error) {
    console.error('复制失败:', error)
    showToast(t('msg.md.copyFail'), 'error')
  }
}

const exportPdf = async () => {
  if (!previewRef.value || isExporting.value) return
  isExporting.value = true
  try {
    const html2pdf = (await import('html2pdf.js')).default
    const opt = {
      margin:       10,
      filename:     'markdown.pdf',
      image:        { type: 'jpeg', quality: 0.98 },
      html2canvas:  { scale: 2, useCORS: true },
      jsPDF:        { unit: 'mm', format: 'a4', orientation: 'portrait' }
    }
    await html2pdf().set(opt).from(previewRef.value).save()
    showToast(t('common.download') + ' PDF OK', 'success')
  } catch (e) {
    console.error(e)
    showToast('PDF export failed', 'error')
  } finally {
    isExporting.value = false
  }
}

const showToast = (message, type = 'success') => {
  toast.value = { show: true, message, type }
  setTimeout(() => { toast.value.show = false }, 3000)
}

onMounted(() => { updateLineNumbers() })
</script>

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Markdown转PDF工具
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档