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