Vue3整合wangEditor(富文本编辑器框架) 以及提供存储渲染方案

目录

 概述

Vue3整合wagnEditor

图片的上传

图片的删除

文章存储

文章渲染


 概述

实现功能:管理端使用富文本编辑器编写文章内容,将编辑好的文章存入数据库或服务器中,前端应用读取存储的文章内容作展示。

本文章能提供

Vue3整合wangEditor过程。

整合后实现图片上传并提供服务端代码参考。

提供监测编辑器删除图片事件捕获,同步删除服务器上图片。

Vue3整合wagnEditor

Vue3起步文档:用于 Vue | wangEditor

进入地址,可以点击此处看demo蛮有用:

1.安装

 npm install @wangeditor/editor --save

npm install @wangeditor/editor-for-vue@next --save

2.添加html结构

<template><div style="border: 1px solid #ccc;"><Toolbar:editor="editorRef":defaultConfig="toolbarConfig":mode="mode"style="border-bottom: 1px solid #ccc"/><Editor:defaultConfig="editorConfig":mode="mode"v-model="valueHtml"style="height: 500px; overflow-y: hidden;"@onCreated="handleCreated"@onDestroyed="handleDestroyed"/></div>
</template>

3.编写js部分

<script setup>
import '@wangeditor/editor/dist/css/style.css' // 引入 css
import { onBeforeUnmount, ref, shallowRef, onMounted } from 'vue'
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'// 编辑器实例,必须用 shallowRef
const editorRef = shallowRef();let mode = "defualt";// 内容 HTML
const valueHtml = ref('');// 编辑器配置
const toolbarConfig = {};
const editorConfig = { placeholder: '请输入内容...',MENU_CONF: {uploadImage: {fieldName: 'file',server: `${import.meta.env.VITE_BASE_URL}/commons/upload`         // 注意 ${import.meta.env.VITE_BASE_URL} 写你自己的后端服务地址}}
};// 组件销毁时,也及时销毁编辑器
onBeforeUnmount(() => {const editor = editorRef.valueif (editor == null) returneditor.destroy()
})const handleCreated = (editor) => {editorRef.value = editor // 记录 editor 实例,重要!
}
</script>

ok,弄到这里,基本样式就出来了,基本的文字编辑内容是可以用了的。

ps:如果你的需求无需加图片,其实功能已经实现,直接将html内容存储即可,可以直接跳到目录"存储文章"看看细节。 

这时候图片的上传和粘贴图片并不奏效,需要往配置编写点代码:

let's go 让我们往下

图片的上传

图片的上传也很简单,两步走:

前端代码配置后端的图片上传接口路径。

后端把图片上传接口的返回值设置固定格式。

①前端添加配置(在上方的js代码中你会发现下面代码包含在其中)

const editorConfig = { placeholder: '请输入内容...',MENU_CONF: {uploadImage: {fieldName: 'file',server: `${import.meta.env.VITE_BASE_URL}/commons/upload`}}
};

 其中的${import.meta.env.VITE_BASE_URL}/commons/upload 请修改为你后端的图片上传接口地址,如果你担心接口代码兼容性问题,不用担心,下面我会提供我后端的图片上传接口给你作参考适配。

 ②后端固定返回值格式

这是必要且固定的格式设置,官网描述:

这是因为当我们将图片上传之后,以固定的数据结构返回,那么前端就能拦截并获取到url等信息用以构造<img>标签然后放到页面中展示。

我的文件上传接口(springboot中代码)

主要看到try{}块中代码:

/*** 文件上传接口* @param file 前端传入的文件对象* @return 返回存在服务器的文件名称*/@PostMapping("/upload")public ResponseEntity<Map<String, Object>> fileUpload(@RequestParam("file") MultipartFile file) {Map<String, Object> response = new HashMap<>();// 获取上传的图片文件后缀名String originalFilename = file.getOriginalFilename();String fileExtension = originalFilename.substring(originalFilename.lastIndexOf('.'));// 检查文件是否为空if (file.isEmpty()) {response.put("errno", 1);response.put("message", "File is empty!");return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);}// 检查文件名是否合法,避免目录遍历攻击String fileName = StringUtils.cleanPath(originalFilename);if (fileName.contains("..")) {response.put("errno", 1);response.put("message", "Illegal name!");return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);}try {// 给上传的图片随机生成一个名称,将之返回,// 用户就可以根据此名称下载图片,防止图片名称冲突。UUID uuid = UUID.randomUUID();String randomUUIDString = uuid.toString();// 将文件保存到指定目录文件File targetFile = new File(this.picturePath + randomUUIDString + fileExtension);// 将传入的图片转存到指定目录文件file.transferTo(targetFile);// 构建成功响应Map<String, Object> data = new HashMap<>();// myEnv 服务端的前缀例如本地测试时 http://localhost:8080String imageUrl = myEnv +"/commons/download?picName=" + randomUUIDString + fileExtension;data.put("url", imageUrl); // 使用拼接的URL路径data.put("alt", "Image description"); // 可以根据需要从文件或其他地方获取data.put("href", imageUrl); // 使用同一个URL作为hrefdata.put("pictureName", randomUUIDString + fileExtension);response.put("errno", 0);response.put("data", data);return ResponseEntity.ok(response);} catch (IOException e) {e.printStackTrace();response.put("errno", 1);response.put("message", "Server error!");return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);}}

实现上传的方式千千万,只要返回指定的格式即可。上方代码供参考。

当你做完这两步你会发现,编辑器就可以实现图片上传和图片粘贴功能了。

图片的删除

当然,这里说明的并不是用手指点击一下"backspace"的操作把图片删除的新手电脑人教程。

而是当我们将图片删除之后,我们其实是需要获取删除图片对象,将服务器中对应的图片删除的,因为这个编辑器框架上传图片的方式是粘贴图片后直接上传到服务器,没有先存在缓存。

这里说明一下,我在开发的时候没有注意到(眼瞎)官方已经提供了解决方案,甚至还吐槽了一下这个功能都没有,结果就在刚刚我再次访问时看到了...:

非常贴心,但是现在才看到的我已经裂开,因为我已经自己用vue的watch和diff差异对比库实现了这个功能。当然,如果看到这的小伙伴,建议直接使用官网提供的方法做就行,更成熟。我没有考虑到图片撤回的操作。

我的实现方法(大伙基本可以跳过,直接去使用官方提供的方法即可)

// 监听valueHtml的变化,做差异化对比,查看是否是删除图片操作,是的话获取src中的文件名
watch(valueHtml, (newValue, oldValue) => {if(newValue.length > oldValue.length){ // 如果是插入不做任何操作return 0;}// 删除操作,调用自定义对比函数,获取差异内容const diff = getHtmlDifference(oldValue, newValue);if (!diff.includes('img')){ // 差异内容包含img,即可确定删除了图片return 0;}// 使用正则表达式匹配src属性中的文件名const regex = /src="http?:\/\/[^\/]+\/commons\/download\?picName=([^"]+)/;const match = diff.match(regex);if (match && match[1]) {const fileName = match[1]; // 提取的文件名// 删除服务器图片,并给出提示deletePictureFromServer(fileName);}
});// 调用diff库与html作差异化对比
function getHtmlDifference(previousHtml, currentHtml){let changes = diffWords(previousHtml, currentHtml);if(changes.length <=1){return "";}return changes[1].value
}// 删除服务器冗余图片
async function deletePictureFromServer(fileName){let res = await axios.delete(`/commons/deleteFile/${fileName}`);if(res.data == "删除图片成功!"){ElNotification({title: '服务器提示',message: '图片删除成功',type: 'success',})}
}

 其中,用到的文本差异化对比库为diff,github地址:GitHub - kpdecker/jsdiff: A javascript text differencing implementation.

 服务端删除功能接口代码:

/*** 删除文件图片* @param imageName 图片名称* @return ·*/@DeleteMapping("/deleteFile/{imageName}")public String deleteImage(@PathVariable("imageName") String imageName) {if (StringUtils.isEmpty(imageName)) {return "请传入图片名称";}String imagePath = this.picturePath + imageName;try {File file = new File(imagePath);if (!file.exists()) {return "图片没有找到!";}if (!file.delete()) {return "删除图片失败!";}return "删除图片成功!";} catch (Exception e) {return "发生错误!";}}

ok,就这样。

文章存储

当我们做完如上操作之后,基本的文本编辑,图片处理就没问题了,可以开始考虑存储问题了。

可以注意到一开始给的代码中有一个变量:valueHtml

 当然,应该都能看出它就是存储我们的html结构内容的。

我们编写好内容之后仅需要将这个变量中存储的内容持久化存储起来就好。

一般有两种存储方案:

将html结构内容转换为.html文件或md文件,然后存储到服务器中。

直接将html结构内容存到数据库中(数据类型选longtext比较合适)。

因为我的文章内容不是非常非常长那种,所以就直接使用第二种方案了。

代码就不贴了,将valueHtml变量作为参数传入你的接口存入数据库就行(废话了)

但是有一点需要注意,没错,就是我踩的坑了 :(

如果,你遇到如下错误,请报警...开玩笑

### Error updating database. Cause: java.sql.SQLException: Incorrect string value: '\xF0\x9F\x99\x81</...' for column 'content' at row 1
### The error may exist in com/mh/dao/TabArticleDao.java (best guess)
### The error may involve com.mh.dao.TabArticleDao.insert-Inline
### The error occurred while setting parameter

这个错误通常是当你在编辑器中使用了emoji表情,他是四个字节的 UTF-8 编码的字符,在MYSQL默认使用的字符集中,并不支持四字节的 UTF-8 字符,因此你需要修改数据库,数据表,数据字段的字符集为 utf8mb4

 ps: 这就体现出创建数据库时显示的指定数据库字符集的重要性了。

# 修改数据库字符集
ALTER DATABASE your_database_name CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;# 修改表的字符集
ALTER TABLE your_table_name CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;# 修改指定列的字符集
ALTER TABLE your_table_name CHANGE column_name column_name TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

如果你的目标表是被参考表/主表,也就是存在外键约束,那么你需要先将外键删除掉后才能通过上方语句修改字符集。

# 查看指定数据库的指定表的所有键名SELECT CONSTRAINT_NAME FROM information_schema.KEY_COLUMN_USAGE WHERE TABLE_NAME = 'tab_article_tag' AND TABLE_SCHEMA = 'your_database_name';# 找到外键名之后,删除外键ALTER TABLE 表名 DROP FOREIGN KEY 外键名;

 当然,改完记得恢复你的外键。

ALTER TABLE 表名 ADD CONSTRAINT 外键名 FOREIGN KEY (字段名) REFERENCES 被参考表名(被参考字段);

文章渲染

通过接口获取存储在数据库或服务器的html结构内容应当不是重点,当我们获取到html结构内容的之后,我们只要将他们丢到html结构中即可,非常之简单,这里提及主要是想给大家复习一下vue中的 v-html 的用法。

<template>中直接声明:

<template><div id='container'><div v-html="rawHtml"></div></div>
</template>

<javascript setup>中编写:

<script setup>
import { ref, onMounted } from 'vue';
import axios from "../config/axios.js"// 文章内容 
const rawHtml = ref('');onMounted(()=>{// 获取文章内容渲染getArticleContent('文字频闭一下')
})// 获取html结构赋予rawHtml 展示即可
async function getArticleContent(articleId){let res = await axios.get(`/tabArticles/${articleId}`);rawHtml.value = res.data.content
}
</script>

OK,就这样,完毕!

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

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

相关文章

友思特应用 | 高精度呈现:PCB多类型缺陷检测系统

导读 PCB等电子产品的精密生产制造过程中&#xff0c;往往需要将缺陷问题100%高精度暴露。友思特 PCB 多类型缺陷检测系统&#xff0c;借由Neuro-T深度学习模型自动排查全部微小缺陷&#xff0c;为工业 PCB 生产制造提供了先进可靠的质量保障。 在现代制造业中&#xff0c;尤其…

工业项目中你连PLM系统都没见过?

什么是 PLM 软件&#xff1f; PLM 软件是用于管理全球供应链中产品或服务全生命周期环节的解决方案。它包括从物料、零部件、产品、文档、规定、工程变更单到质量工作流的数据管理。 PLM 的发展历史 从最初的产品设计管理到如今的数字化转型和智能化生产&#xff0c;PLM 在不断…

【uniapp】省市区下拉列表组件

1. 效果图 2. 组件完整代码 <template><view class="custom-area-picker"><view

【MATLAB】GA_ELM神经网络时序预测算法

有意向获取代码&#xff0c;请转文末观看代码获取方式~ 1 基本定义 GA_ELM&#xff08;Genetic Algorithm and Extreme Learning Machine&#xff09;是一种结合了遗传算法和极限学习机的神经网络时序预测算法。它的核心思想是通过使用遗传算法来优化极限学习机的权重和偏差&…

scipy.signal.cwt 与 pywt.cwt 使用记录

scipy.signal.cwt 该代码中widths以及freq计算公式来源于scipy.signal.morlet2函数官方案例 from scipy.signal import morlet, morlet2 from scipy import signal import matplotlib.pyplot as pltsignal_length 2000 fs 1000# 生成信号数据 time np.arange(0, signal_leng…

Go语言中如何正确使用 errgroup

不管是哪种编程语言,重新发明轮子都不是一个好主意。代码库重新实现如何启动多个goroutine并汇总错误也很常见。但是Go生态系统中的一个包旨在支持这种常见的用例。让我们来看看这个包并了解为什么它应该成为Go开发人员工具集的一部分。 golang.org/x是一个为标准库提供扩展的…

微信订阅号环境搭建及开发者工具下载

目录 一、注册订阅号 1.1 选择注册 2.2 选择订阅号注册 1.3 登录进入主页面 ​编辑 1.4 可以进行自定义菜单 1.5 我们重点关注公众平台测试账号 ​编辑 1.6 自定义一个域名 1.7 用自己的微信扫描这个二维码 ​编辑 1.8 点击修改&#xff0c;并自定义个域名 二、开发…

第1章 计算机网络体系结构

王道学习 【考纲内容】 &#xff08;一&#xff09;计算机网络概述 计算机网络的概念、组成与功能&#xff1b;计算机网络的分类&#xff1b; 计算机网络的性能指标 &#xff08;二&#xff09;计算机网络体系结构与参考模型 计算机网络分层结…

创建线程的四种方式

在实际业务中最好使用线程池的方式&#xff0c;因为其他三种我们一直new 新的线程&#xff0c;在高并发情况下容易发生内存泄漏

用于显著提高检索速度和降低成本的二进制和标量嵌入量化

我们引入了嵌入量化的概念&#xff0c;并展示了它们对检索速度、内存使用、磁盘空间和成本的影响。我们将讨论理论上和实践中如何对嵌入进行量化&#xff0c;然后介绍一个 演示&#xff0c;展示了 4100 万维基百科文本的真实检索场景。 演示地址https://hf.co/spaces/sentence-…

【神经网络与深度学习】循环神经网络基础

tokenization tokenization&#xff1a;分词 每一个词语都是token 分词方法&#xff1a;转为单个词、转为多个词语 N-gram表示法 准备词语特征的方法 &#xff08;把连续的N个词作为特征&#xff09; 如 ”我爱你“——>[我&#xff0c;爱&#xff0c;你] 2-gram——[[我…

联储降息预期落空打了谁的脸

美国 3 月消费者价格指数&#xff08;CPI&#xff09;于本周发布&#xff0c;最新数据全线高于预期。具体而言&#xff0c;美国劳工部周三公布的数据显示&#xff0c;美国 3 月消费者物价指数&#xff08;CPI&#xff09;同比上涨 3.5%&#xff0c;为 2023 年 9 月以来最高水平…