【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,一经查实,立即删除!

相关文章

LeetCode题解:34.在排序数组中查找元素的第一个和最后一个位置【Python题解超详细,二分查找法、index法】,知识拓展:index方法详解

题目描述 给你一个按照非递减顺序排列的整数数组 nums&#xff0c;和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。如果数组中不存在目标值 target&#xff0c;返回 [-1, -1]。你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。 示例 1&…

Cereal,一个轻量级的 C++ 序列化库!

嗨&#xff0c;大家好&#xff01;我是橙子。今天咱们来探索超棒的 Cereal 库&#xff0c;它能轻松搞定 C数据的序列化与反序列化&#xff0c;就像神奇的魔法&#xff0c;把数据变成能存储和传输的格式&#xff0c;然后又能变回来&#xff0c;超有趣哦&#xff01;快来开启学习…

理解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;贴入以下代码…

LabVIEW实现串口调试助手

目录 1、串口通信原理 2、硬件环境部署 3、串口通信函数 4、程序架构 5、前面板设计 6、程序框图设计 本专栏以LabVIEW为开发平台,讲解物联网通信组网原理与开发方法,覆盖RS232、TCP、MQTT、蓝牙、Wi-Fi、NB-IoT等协议。 结合实际案例,展示如何利用LabVIEW和常用模块实现物联…

云计算的发展历史与未来展望

云计算的起源与发展 云计算的概念最早可以追溯到20世纪60年代&#xff0c;当时的计算机科学家约翰麦卡锡&#xff08;John McCarthy&#xff09;提出了“按需提供计算能力”的构想。尽管这一理念在当时的技术条件下无法实现&#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;登录成功后显示欢迎窗体…

Java基础全解:构建扎实编程技能

文章目录 1. Hello World 程序深入解析&#xff1a; 2. 数据类型深入解析&#xff1a; 3. 条件判断深入解析&#xff1a; 4. 循环结构深入解析&#xff1a; 5. 数组深入解析&#xff1a; 6. 方法定义与调用深入解析&#xff1a; 1. Hello World 程序 深入解析&#xff1a; 类…

开发规范:Restful风格

REST&#xff08;REpresentational State Transfer&#xff09;&#xff0c;表述性状态转换&#xff0c;它是一种软件架构的风格。这么说可能太过于专业&#xff0c;有些难以理解&#xff0c;不妨看个案例&#xff1a; 传统风格url 假如说我们需要使用传统风格url发起请求&am…

StarRocks-同步hive数据

官方文档对csv&#xff0c;orc ,json &#xff0c;parquet都支持的比较好。写文章的时候SR我们生产用的是3.1.x版本 1、同步hive表textfile格式的数据 hive表结构 CREATE EXTERNAL TABLE ads_d_app_rank_inc(filename string COMMENT 解析的文件名称,rank string COMMENT ap…

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

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

详解Qt pdf 之QPdfSelection 选择文本类

文章目录 QPdfSelection 类详解前言 详细说明公共函数说明1. 构造函数2. text3. boundingRect4. isEmpty5. startPage6. endPage 使用场景示例代码代码说明总结 QPdfSelection 类详解 前言 QPdfSelection 是 Qt PDF 模块中的一个类&#xff0c;用于表示在 PDF 文档中被选中的…

记录部署dvwa靶场踩的几个坑

DVWA reCAPTCHA key: Missing 解决方法&#xff1a;网上随便copy一个&#xff0c;粘贴到config.inc.php配置文件里&#xff0c;具体我也是参考这篇文章的&#xff1a;DVWA下载、安装You dont have permission to access this resource.Server unable to read htaccess file, de…