前端将Markdown文本转换为富文本显示/编辑,并保存为word文件
rolling_kitten 2024-07-16 16:03:02 阅读 57
参考:https://www.wangeditor.com/
https://blog.csdn.net/weixin_43797577/article/details/138854324
插件:
markdown-it
@traptitech/markdown-it-katex
markdown-it-link-attributes
highlight.js
@wangeditor/editor
@wangeditor/editor-for-vue
html-docx-js-typescript
markdown展示组件:
<code><!-- 展示 后端传来的Markdown格式文字 的组件 -->
<script setup lang="ts">code>
import MarkdownIt from 'markdown-it'
import mdKatex from '@traptitech/markdown-it-katex'
import mila from 'markdown-it-link-attributes'
import hljs from 'highlight.js'
import 'highlight.js/styles/default.css';
import { ref, computed } from 'vue';
const props = defineProps<{
markdown: string // 父组件传入要展示/编辑的markdown格式文本
fontSize?: string
}>()
// 设定文字大小
const fontSize = computed<string>(() => {
if (props.fontSize) {
return props.fontSize
} else {
return '16px'
}
})
// 对外暴露innerText,以供复制
const showAreaRef = ref()
const innerText = computed<string>(() => {
return showAreaRef.value.innerText
})
defineExpose({
innerText
})
const mdi = new MarkdownIt({
linkify: true,
highlight: (code: any, lang: any) => {
if (lang && hljs.getLanguage(lang)) {
return hljs.highlight(code, { language: lang }).value;
} else {
return hljs.highlightAuto(code).value;
}
}
})
mdi.use(mila, { attrs: { target: '_blank', rel: 'noopener' } })
mdi.use(mdKatex, { blockClass: 'katexmath-block rounded-md p-[10px]', errorColor: ' #cc0000' })
const text = computed<string>(() => {
const value = props.markdown ?? ''
// mdi实例将markdown文本渲染成HTML格式文本
return mdi.render(value)
})
</script>
<template>
<!-- 展示状态 -->
<div class="show-area" v-html="text" ref="showAreaRef"></div>code>
</template>
<style scoped lang="scss">code>
.show-area {
width: 100%;
word-wrap: break-word;
font-size: v-bind(fontSize);
}
</style>
markdown文本放入富文本编辑器、可导出为word组件
<!-- 编辑 后端传来的Markdown格式文字 的组件 -->
<script setup lang="ts">code>
import MarkdownIt from 'markdown-it'
import mdKatex from '@traptitech/markdown-it-katex'
import mila from 'markdown-it-link-attributes'
import hljs from 'highlight.js'
import 'highlight.js/styles/default.css';
import { ref, computed, onBeforeUnmount, shallowRef, watch, nextTick } from 'vue';
// WangEditor 相关
import '@wangeditor/editor/dist/css/style.css'
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
//将html转为word
import { asBlob } from 'html-docx-js-typescript'
import { useWriteStore } from '@/stores'
import { storeToRefs } from 'pinia'
// 是否要导出文档,监听它,只要值改变就导出
const { isExportDoc } = storeToRefs(useWriteStore())
const props = defineProps<{
markdown: string // 父组件传入要展示/编辑的markdown格式文本
title?: string
}>()
// markdown-it 相关
const mdi = new MarkdownIt({
linkify: true,
highlight: (code: any, lang: any) => {
if (lang && hljs.getLanguage(lang)) {
return hljs.highlight(code, { language: lang }).value;
} else {
return hljs.highlightAuto(code).value;
}
}
})
mdi.use(mila, { attrs: { target: '_blank', rel: 'noopener' } })
mdi.use(mdKatex, { blockClass: 'katexmath-block rounded-md p-[10px]', errorColor: ' #cc0000' })
const text = computed<string>(() => {
const value = props.markdown ?? ''
// mdi实例将markdown文本渲染成HTML格式文本
return mdi.render(value)
})
// 以下是编辑状态相关代码
// 编辑器实例,必须用 shallowRef
const editorRef = shallowRef()
// 编辑器页面高度
const editorHeight = computed(() => {
return (window.innerHeight - 40) + 'px'
})
// 编辑器编辑部分最小高度
const editorInitHeight = computed(() => {
return (window.innerHeight - 70) + 'px'
})
const editArea = ref<HTMLDivElement>()
// 内容 HTML
const valueHtml = ref<string>(text.value)
watch(text, (newValue) => {
// 如果newValue为空字符串,说明传输已经结束,writeStore临时存储的文本已被重置,因此编辑器不再接收
if (newValue) {
valueHtml.value = newValue
}
else {
// 传输结束,开启新的一行
valueHtml.value += '<p>\n</p>'
ElMessage.success({
offset: 55,
message: 'AI撰写完成'
})
}
nextTick(() => {
editorRef.value.focus(true) // 在内容末尾focus,nextTick确保在内容加载完成后,才让光标focus到末尾
editArea.value!.scrollTop = editArea.value!.scrollHeight
})
})
// mode
const mode = ref<string>('default')
const toolbarConfig = { }
const editorConfig = {
placeholder: '请输入内容...',
scroll: false
}
// 组件销毁时,也及时销毁编辑器
onBeforeUnmount(() => {
const editor = editorRef.value
if (editor == null) return
editor.destroy()
})
const handleCreated = (editor: any) => {
editorRef.value = editor // 记录 editor 实例,重要!
}
// 下载为word文档函数
async function exportDoc() {
const editor = editorRef.value
// 将富文本内容拼接为一个完整的html
const html = editor.getHtml()
const value = `<!DOCTYPE html>
<html lang="zh-CN">code>
<head>
<meta charset="UTF-8">code>
<title>文档</title>
</head>
<body>
${ html}
</body>
</html>`
// landscape就是横着的,portrait是竖着的,默认是竖屏portrait。
const data = await asBlob(value, { orientation: 'portrait' }) as Blob
const a = document.createElement('a')
a.href = window.URL.createObjectURL(data)
a.setAttribute('download', `${ props.title ? props.title : '知识平台智能生成文档'}.docx`)
a.click()
// 下载后将标签移除
a.remove()
}
// 监听,如果值变动,就调用下载函数,导出为word
watch(isExportDoc, () => {
exportDoc()
})
</script>
<template>
<!-- 编辑状态 -->
<Toolbar :defaultConfig="toolbarConfig" :mode="mode" :editor="editorRef"code>
style="width: 100%;height: 40px; border-bottom: 1px solid #ccc;position: fixed;z-index: 99;" />code>
<div class="edit-area" style="border: 1px solid #ccc" ref="editArea">code>
<Editor style="height: auto;margin: 15px 200px 15px 200px;" v-model="valueHtml" :defaultConfig="editorConfig"code>
:mode="mode" @onCreated="handleCreated" />code>
</div>
</template>
<style scoped lang="scss">code>
.edit-area {
margin-top: 40px;
width: 100%;
height: v-bind(editorHeight);
overflow-y: auto;
:deep(.w-e-text-container) {
min-height: v-bind(editorInitHeight);
}
}
</style>
下一篇: 【无标题】vue webrtc 播放rtsp视频流
本文标签
声明
本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。