当前位置: 首页 > news >正文

前端将Markdown文本转换为富文本显示/编辑,并保存为word文件

参考: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展示组件:

<!-- 展示 后端传来的Markdown格式文字 的组件 -->
<script setup lang="ts">
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>
</template><style scoped lang="scss">
.show-area {width: 100%;word-wrap: break-word;font-size: v-bind(fontSize);
}
</style>

markdown文本放入富文本编辑器、可导出为word组件

<!-- 编辑 后端传来的Markdown格式文字 的组件 -->
<script setup lang="ts">
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.valueif (editor == null) returneditor.destroy()
})const handleCreated = (editor: any) => {editorRef.value = editor // 记录 editor 实例,重要!
}
// 下载为word文档函数
async function exportDoc() {const editor = editorRef.value// 将富文本内容拼接为一个完整的htmlconst html = editor.getHtml()const value = `<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8"><title>文档</title></head><body>${html}</body></html>`//  landscape就是横着的,portrait是竖着的,默认是竖屏portrait。const data = await asBlob(value, { orientation: 'portrait' }) as Blobconst 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"style="width: 100%;height: 40px; border-bottom: 1px solid #ccc;position: fixed;z-index: 99;" /><div class="edit-area" style="border: 1px solid #ccc" ref="editArea"><Editor style="height: auto;margin: 15px 200px 15px 200px;" v-model="valueHtml" :defaultConfig="editorConfig":mode="mode" @onCreated="handleCreated" /></div>
</template><style scoped lang="scss">
.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 之 vuex
  • HTML(6)——表单
  • 重装了mysql,然后安装为服务时,net start 启动一直报错,MySQL服务无法启动的解决
  • Streamlit 与 Gradio:Python 仪表板的终极对决
  • 智能体(Agent)实战——从gpts到auto gen
  • Postman接口测试/接口自动化实战教程
  • 【STC8A8K64D4开发板】第3-1讲:温度传感器DS18B20
  • 使用python绘制三维直方图
  • 面向对象编程基本概念
  • opengauss安装postgis插件(Docker部署)
  • GPU性能相关的工具
  • 多态深度剖析
  • 算法day26
  • spring boot jwt 实现用户登录完整java
  • 如何用 JavaScript 下载文件
  • 【347天】每日项目总结系列085(2018.01.18)
  • 【刷算法】求1+2+3+...+n
  • Android开源项目规范总结
  • Docker 1.12实践:Docker Service、Stack与分布式应用捆绑包
  • ES6系统学习----从Apollo Client看解构赋值
  • gops —— Go 程序诊断分析工具
  • Java深入 - 深入理解Java集合
  • JDK 6和JDK 7中的substring()方法
  • mac修复ab及siege安装
  • MySQL主从复制读写分离及奇怪的问题
  • python_bomb----数据类型总结
  • python大佬养成计划----difflib模块
  • React的组件模式
  • Redis学习笔记 - pipline(流水线、管道)
  • Vue全家桶实现一个Web App
  • Webpack 4x 之路 ( 四 )
  • Xmanager 远程桌面 CentOS 7
  • 成为一名优秀的Developer的书单
  • 猴子数据域名防封接口降低小说被封的风险
  • 将 Measurements 和 Units 应用到物理学
  • 力扣(LeetCode)22
  • 前嗅ForeSpider中数据浏览界面介绍
  • 使用前端开发工具包WijmoJS - 创建自定义DropDownTree控件(包含源代码)
  • 原生 js 实现移动端 Touch 滑动反弹
  • RDS-Mysql 物理备份恢复到本地数据库上
  • 阿里云ACE认证学习知识点梳理
  • 专访Pony.ai 楼天城:自动驾驶已经走过了“从0到1”,“规模”是行业的分水岭| 自动驾驶这十年 ...
  • 昨天1024程序员节,我故意写了个死循环~
  • #### go map 底层结构 ####
  • #android不同版本废弃api,新api。
  • #NOIP 2014# day.1 T2 联合权值
  • ${factoryList }后面有空格不影响
  • %@ page import=%的用法
  • (3)nginx 配置(nginx.conf)
  • (55)MOS管专题--->(10)MOS管的封装
  • (bean配置类的注解开发)学习Spring的第十三天
  • (C++)八皇后问题
  • (el-Transfer)操作(不使用 ts):Element-plus 中 Select 组件动态设置 options 值需求的解决过程
  • (floyd+补集) poj 3275
  • (层次遍历)104. 二叉树的最大深度