前端将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>



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。