【element-tiptap】导出word

前言:前面的文章 【element-tiptap】导入word并解析成HTML 已经介绍过如何在 element-tiptap 中导入 word。这篇文章来探究一下怎么将编辑器的内容导出成word

(一)创建菜单项

1、图标

首先上 fontawesome 这个网站上找一个合适的图标,把它的 svg 复制下来
然后创建 src/icons/export.svg,增加 widthheightfill 属性

<svg xmlns="http://www.w3.org/2000/svg" width="28" height="32" viewBox="0 0 576 512"><path fill="currentColor" d="M0 64C0 28.7 28.7 0 64 0L224 0l0 128c0 17.7 14.3 32 32 32l128 0 0 128-168 0c-13.3 0-24 10.7-24 24s10.7 24 24 24l168 0 0 112c0 35.3-28.7 64-64 64L64 512c-35.3 0-64-28.7-64-64L0 64zM384 336l0-48 110.1 0-39-39c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l80 80c9.4 9.4 9.4 24.6 0 33.9l-80 80c-9.4 9.4-24.6 9.4-33.9 0s-9.4-24.6 0-33.9l39-39L384 336zm0-208l-128 0L256 0 384 128z"/>
</svg>
2、创建扩展

src/extensions/export.ts

import { Extension } from '@tiptap/core';
import { Editor } from '@tiptap/vue-3';
import ExportDropdown from '@/components/menu-commands/export.dropdown.vue';export interface ExportOptions {
}declare module '@tiptap/core' {interface Commands<ReturnType> {export: {exportToWord: () => ReturnType;};}
}const Export = Extension.create<ExportOptions>({name: 'export',addCommands() {return {exportToWord:() =>({ editor }) => {// 这里可以添加导出 Word 的具体实现return true;},exportToPdf:() =>({ editor }) => {// 这里可以添加导出 PDF 的具体实现return true;},};},addOptions() {return {button({ editor, t }: { editor: Editor; t: (...args: any[]) => string }) {return {component: CommandButton,componentProps: {command: () => {editor.commands.exportToWord();},isActive: editor.isActive('export'),icon: 'export',tooltip: t('editor.extensions.Export.tooltip'),},};},};},
});export default Export; 
2、在根组件中引入当前的扩展

老生常谈的步骤,这里就不讲了啦
以及定义菜单的提示语
src/i18n/locales/zh/index.ts

Export: {tooltip: '导出文档',
},

在这里插入图片描述

(二)实现导出 word

使用开源库 prosemirror-docx。

1、安装依赖
npm install prosemirror-docx --save

后来,我发现源码里还是有很多不完善的地方,官网还有处于 open 状态的提交记录😂
在这里插入图片描述
其中的 #35 还是很关键的,是修复的图片尺寸检测失败的 bug。所以还是将源码中的 src 文件夹都放在自己的项目里面
在这里插入图片描述
另外还需要安装一些别的依赖,直接使用 npm install xxx 安装即可

"docx": "^9.0.3",
"file-saver": "^2.0.5",
"image-dimensions": "^2.3.0",
2、图片 buffer

根据 export-tiptap-content-to-microsoft-word 上给出的示例代码,

import {writeDocx,DocxSerializer,defaultNodes,defaultMarks
} from "prosemirror-docx";
import { saveAs } from "file-saver";const nodeSerializer = {...defaultNodes,hardBreak: defaultNodes.hard_break,codeBlock: defaultNodes.code_block,orderedList: defaultNodes.ordered_list,listItem: defaultNodes.list_item,bulletList: defaultNodes.bullet_list,horizontalRule: defaultNodes.horizontal_rule,image(state, node) {// No imagestate.renderInline(node);state.closeBlock(node);}
};const docxSerializer = new DocxSerializer(nodeSerializer, defaultMarks);const write = useCallback(async () => {const opts = {getImageBuffer(src) {return Buffer.from("Real buffer here");}};const wordDocument = docxSerializer.serialize(editor.state.doc, opts);await writeDocx(wordDocument, (buffer) => {saveAs(new Blob([buffer]), "example.docx");});}, [editor?.state.doc]);

使用这个库需要将图片转成 buffer
使用 Buffer.from() 方法,需要传递一个 base64 字符串。当前的图片,是保存在服务器返回的地址上,需要转换成 base64。起先我想在 getImageBuffer 方法中异步加载远程的图片资源,然后转成 base64,后来发现这个方法只能是同步的,所以就需要先遍历文档所有节点,将所有的图片都转成 base64。

addCommands() {return {exportToWord:() =>async ({ editor }) => {// 给编辑器中所有的图片的src转换为base64const images: Node[] = [];editor.state.doc.descendants((node) => {if (node.type.name === 'image') {images.push(node);}});for (const image of images) {const src = image.attrs.src;if (!src.startsWith('data:')) {try {const response = await fetch(src);const blob = await response.blob();const reader = new FileReader();const base64 = await new Promise<string>((resolve) => {reader.onload = () => resolve(reader.result as string);reader.readAsDataURL(blob);});const tr = editor.state.tr;const pos = editor.state.doc.resolve(0);editor.state.doc.nodesBetween(0, editor.state.doc.content.size, (node, pos) => {if (node.type.name === 'image' && node.attrs.src === src) {tr.setNodeMarkup(pos, undefined, {...node.attrs,src: base64});}});editor.view.dispatch(tr);} catch (error) {console.error('Failed to convert image to base64:', error);}}}const opts = {getImageBuffer(src) {// base64转 Bufferreturn Buffer.from(src.split(',')[1], 'base64');}};const wordDocument = docxSerializer.serialize(editor.state.doc, opts);await writeDocx(wordDocument, (buffer) => {saveAs(new Blob([buffer]), "example.docx");});return true;},};
},
3、源码bug修复

使用这个代码出现的一些问题:
1、图片尺寸检测不出来,需要根据 这个提交 去修改代码
2、图片会变得很大,是因为图片的宽高没有传进去,使用的是默认的宽高
3、图片默认居中,对齐方式显示成 left 即可
src/utils/prosemirror-docx/schema.ts

// Presentational
image(state, node) {const { src, width, height } = node.attrs;state.image(src, width, height);state.closeBlock(node);
},

src/utils/prosemirror-docx/serializer.ts

image(src: string,width: number,height: number,widthPercent = 70,align: AlignOptions = 'left',imageRunOpts?: IImageOptions,
) {const buffer = this.options.getImageBuffer(src);const dimensions = imageDimensionsFromData(buffer);if (!dimensions) return;const aspect = dimensions.height / dimensions.width;if(!width) {width = this.maxImageWidth * (widthPercent / 100);}this.current.push(new ImageRun({data: buffer,transformation: {width,height: height || width * aspect,},...imageRunOpts,}),);return this.addParagraphOptions({alignment: align,});
}

以上就可以实现导出成 word 的功能啦!
看下效果:
文档如下:
在这里插入图片描述
导出word如下:
在这里插入图片描述

(三)todoList、文本颜色、背景颜色处理

经过实际验证,文档中的todoList,docx 库是解析不了的,控制台会报错
在这里插入图片描述
需要将它处理成 [] xxx 的形式。
另外,背景颜色和文字颜色没有带到 word 中
src/utils/prosemirror-docx/serializer.ts

export class DocxSerializer {nodes: NodeSerializer;marks: MarkSerializer;constructor(nodes: NodeSerializer, marks: MarkSerializer) {this.nodes = {taskList: (state, node) => {node.forEach((child, _, i) => {state.render(child, node, i);});},taskItem: (state, node) => {state.current = [];const checkbox = node.attrs.checked ? '[x] ' : '[ ] ';state.text(checkbox);state.renderContent(node);state.closeBlock(node);},...nodes,this.marks = {...marks,textStyle: (state, node, mark) => ({color: mark.attrs.color,}),highlight: (state, node, mark) => ({shading: { fill: mark.attrs.color },}),};}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/61559.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

理解Java集合的基本用法—Collection:List、Set 和 Queue,Map

本博文部分参考 博客 &#xff0c;强烈推荐这篇博客&#xff0c;写得超级全面&#xff01;&#xff01;&#xff01; 图片来源 Java 集合框架 主要包括两种类型的容器&#xff0c;一种是集合&#xff08;Collection&#xff09;&#xff0c;存储一个元素集合&#xff08;单列…

使用 PDF API 合并 PDF 文件

内容来源&#xff1a; 如何在 Mac 上合并 PDF 文件 1. 注册与认证 您可以注册一个免费的 ComPDFKit API 帐户&#xff0c;该帐户允许您在 30 天内免费无限制地处理 1,000 多个文档。 ComPDFKit API 使用 JSON Web Tokens 方法进行安全身份验证。从控制面板获取您的公钥和密钥&…

架构师:Dubbo 服务请求失败处理的实践指南

1、简述 在分布式服务中,服务调用失败是不可避免的,可能由于网络抖动、服务不可用等原因导致。Dubbo 作为一款高性能的 RPC 框架,提供了多种机制来处理服务请求失败问题。本文将介绍如何在 Dubbo 中优雅地处理服务请求失败,并结合具体实践步骤进行讲解。 2、常见处理方式 …

加载不同本地gltf模型,模型内容不更新的解决方案

相关链接 http://mars3d.cn/editor-vue.html?keyex_6_2_2&idlayer-graphic/draw/draw-model 问题内容 加载本地gltf模型的时候&#xff0c;不clear图层&#xff0c;再打开其他本地gltf&#xff0c;gltf的内容就不更新 重现步骤 进入官网示例&#xff0c;贴入以下代码…

【51单片机】程序实验910.直流电机-步进电机

主要参考学习资料&#xff1a;B站【普中官方】51单片机手把手教学视频 前置知识&#xff1a;C语言 单片机套装&#xff1a;普中STC51单片机开发板A4标准版套餐7 码字不易&#xff0c;求点赞收藏加关注(•ω•̥) 有问题欢迎评论区讨论~ 目录 程序实验9&10.直流电机-步进电机…

Linux——自定义简单shell

shell 自定义shell目标普通命令和内建命令&#xff08;补充&#xff09; shell实现实现原理实现代码 自定义shell 目标 能处理普通命令能处理内建命令要能帮助我们理解内建命令/本地变量/环境变量这些概念理解shell的运行 普通命令和内建命令&#xff08;补充&#xff09; …

如何把Qt exe文件发送给其他人使用

如何把Qt exe文件发送给其他人使用 1、先把 Debug改成Release2、重新构建项目3、运行项目4、找到release文件夹5、新建文件夹&#xff0c;存放exe文件6、打开qt控制台串口7、下载各种文件8、压缩&#xff0c;发送压缩包给别人 1、先把 Debug改成Release 2、重新构建项目 3、运行…

Kafka的消费消息是如何传递的?

大家好&#xff0c;我是锋哥。今天分享关于【Kafka的消费消息是如何传递的&#xff1f;】面试题。希望对大家有帮助&#xff1b; Kafka的消费消息是如何传递的&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 在Kafka中&#xff0c;消息的消费是通过消费…

el-drawer如何实现增加resize拖曳改变宽度大小,通过小图标进行拖拽

请先看效果图 我主要是通过这个按钮来进行拖拽的&#xff0c;记住自行添加按钮图片 第一步 新建一个myDrawerDrag.js文件 import Vue from vueVue.directive(drawerDrag, {bind(el, binding, vnode, oldVnode) {const minWidth 400const dragDom el.querySelector(.el-drawe…

C#窗体简单登录

创建一个Windows登录程序&#xff0c;创建两个窗体&#xff0c;一个用来登录&#xff0c;一个为欢迎窗体&#xff0c;要求输入用户名和密码&#xff08;以个人的姓名和学号分别作为用户名和密码&#xff09;&#xff0c;点击【登录】按钮登录&#xff0c;登录成功后显示欢迎窗体…

【大数据学习 | Spark-SQL】定义UDF和DUAF,UDTF函数

1. UDF函数&#xff08;用户自定义函数&#xff09; 一般指的是用户自己定义的单行函数。一进一出&#xff0c;函数接受的是一行中的一个或者多个字段值&#xff0c;返回一个值。比如MySQL中的&#xff0c;日期相关的dateDiff函数&#xff0c;字符串相关的substring函数。 先…

linux——进程间通信及管道的应用场景

linux进程的控制-CSDN博客 liunx——进程间通信&#xff08;管道通信&#xff09;-CSDN博客 文章目录 文章目录 前言 二、管道的应用 1.创建子进程 1、描述&#xff1a; 2.创建进程及管理 3、子进程接受任务 4、控制子进程 总结 前言 上篇博客我们学习了进程间通信&…

FPGA实现串口升级及MultiBoot(十)串口升级SPI FLASH实现

本文目录索引 工程架构example9工程设计Vivado设计Vitis设计example9工程验证1、读取FLASH ID2、擦除整个FLASH3、Blank-Check4、烧写Golden区位流5、读取FLASH内容6、烧写MultiBoot区位流(升级位流)7、MultiBoot区位流(升级位流)启动example10工程设计Vivado设计Vitis设计exam…

AIGC引领金融大模型革命:未来已来

文章目录 金融大模型的应用场景1. **金融风险管理**2. **量化交易**3. **个性化投资建议**4. **金融欺诈检测和预防**5. **智能客户服务** 金融大模型开发面临的挑战应对策略《金融大模型开发基础与实践》亮点内容简介作者简介获取方式 在AIGC&#xff08;Artificial Intellige…

设计模式学习[10]---迪米特法则+外观模式

文章目录 前言1. 迪米特法则2. 外观模式2.1 原理阐述2.2 举例说明 总结 前言 之前有写到过 依赖倒置原则&#xff0c;这篇博客中涉及到的迪米特法则和外观模式更像是这个依赖倒置原则的一个拓展。 设计模式的原则嘛&#xff0c;总归还是高内聚低耦合&#xff0c;下面就来阐述…

BWO-CNN-BiGRU-Attention白鲸优化算法优化卷积神经网络结合双向门控循环单元时间序列预测,含优化前后对比

BWO-CNN-BiGRU-Attention白鲸优化算法优化卷积神经网络结合双向门控循环单元时间序列预测&#xff0c;含优化前后对比 目录 BWO-CNN-BiGRU-Attention白鲸优化算法优化卷积神经网络结合双向门控循环单元时间序列预测&#xff0c;含优化前后对比预测效果基本介绍模型描述程序设计…

【Linux】死锁、读写锁、自旋锁

文章目录 1. 死锁1.1 概念1.2 死锁形成的四个必要条件1.3 避免死锁 2. 读者写者问题与读写锁2.1 读者写者问题2.2 读写锁的使用2.3 读写策略 3. 自旋锁3.1 概念3.2 原理3.3 自旋锁的使用3.4 优点与缺点 1. 死锁 1.1 概念 死锁是指在⼀组进程中的各个进程均占有不会释放的资源…

Vue3之弹窗

文章目录 第一步、引入JS第二步、弹框 在前端开发语言Vue3&#xff0c;在管理端如何进行弹窗&#xff1f;下面根据API实现效果。 Element API文档&#xff1a; Element-plus文档 搭建环境可参考博客【 初探Vue3环境搭建与nvm使用】 第一步、引入JS <script lang"ts&…

2、Three.js初步认识场景Scene、相机Camera、渲染器Renderer三要素

三要素之间关系&#xff1a; 有了虚拟场景Scene&#xff0c;相机录像Camera&#xff0c;在相机小屏幕上看到的Renderer Scene当前空间 Mesh人在场景 Camera相机录像 Renderer显示器上 首先先描述下Scene&#xff1a; 这个场景为三要素之一&#xff0c;一切需要展示的东西都需…

cin/cout的性能优化和缓冲区同步问题

目录 背景导入 问题 1.1ios::sync_with_stdio(false) 1.2为什么要解除C/C IO流同步? 1.3使用场景 2.1cin和cout的绑定关系 2.2为什么要解除绑定关系? 2.3注意事项 背景导入 大家可以先看一下这段背景知识;后面我会谈谈自己的理解; 1.在C中&#xff0c;标准输⼊输出流…