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.mzph.cn/news/813603.shtml

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

相关文章

伤感视频素材哪里找?五大平台助您深情表达

大家好&#xff01;制作视频时&#xff0c;找到合适的伤感视频素材可以极大地增强作品的情感表达。那么&#xff0c;伤感视频素材哪里找呢&#xff1f;今天&#xff0c;我将为大家介绍五个能提供优质伤感视频素材的平台&#xff0c;让您的视频作品更加动人。 蛙学网&#xff0…

2023ICPC网络预选赛 ( 2 ) (2) C.Covering【2-SAT、前后缀虚拟节点区间连边】

C.Covering 题意 给定一个长度为 n n n 的正整数数组 a a a&#xff0c;现在要从中选择一些下标&#xff0c;满足&#xff1a; 对于每个下标 i i i&#xff0c; i i i 和 i − 1 i - 1 i−1 至少 有一个被选对于所有选择的下标&#xff0c;任意两个下标 i , j ( i ≠ j…

Android适配平板屏幕尺寸

一、划分手机和平板 人为判断方法: 大于6英寸的就是平板。小于6英寸的都是手机 平板尺寸&#xff1a; 6英寸、7英寸、10英寸、14英寸… Android系统支持多配置资源文件&#xff0c;我们可以追加新的资源目录到你的Android项目中。命名规范&#xff1a; 资源名字-限制符 l…

C++进阶03 模板与群体数据

听课笔记简单整理&#xff0c;供小伙伴们参考~&#x1f95d;&#x1f95d; 第1版&#xff1a;听课的记录代码~&#x1f9e9;&#x1f9e9; 编辑&#xff1a;梅头脑&#x1f338; 审核&#xff1a;文心一言 目录 &#x1f433;课程来源 &#x1f40b;模板 &#x1f40b;8.…

Vue2 —— 学习(六)

一、Vue 脚手架 &#xff08;一&#xff09;介绍 Vue 脚手架是 Vue 官方提供的标准化开发工具 &#xff08;开发平台&#xff09; 脚手架版本最新版本 是 4.x 文档可以查看 http://cli.vuejs.org/zh/ 就是vue 官网文档中 的 vue.cli command line interface &#xff08;…

Python 爬虫基础——http请求和http响应

写本篇文章&#xff0c;我认为是能把自己所理解的内容分享出来&#xff0c;说不定就有和我一样有这样思维的共同者&#xff0c;希望本篇文章能帮助大家&#xff01;✨✨ 文章目录 一、 &#x1f308;python介绍和分析二、 &#x1f308;http请求三、 &#x1f308;http响应四、…

Python项目2 数据可视化

生成数据 数据可视化 指的是通过可视化表示来探索数据&#xff0c;它与数据挖掘 数据挖掘 紧密相关&#xff0c;而数据挖掘指的是使用代码来探索数据集的规律和关联。数据集可以是用一行代码就能表 示的小型数字列表&#xff0c;也可以是数以吉字节的数据。 漂亮地呈现数据关…

【论文笔记】Planning-oriented Autonomous Driving

原文链接&#xff1a;https://arxiv.org/abs/2212.10156 1. 引言 目前的自动驾驶工业界通常为不同任务部署不同的模型&#xff0c;但优化的孤立性会导致模块之间的信息损失、误差积累和特征不对齐。 一种更好的设计是将各种任务整合为多任务学习&#xff0c;即为共享的特征提…

设计模式之责任链模式讲解

概念&#xff1a;使多个对象都有机会处理请求&#xff0c;从而避免了请求的发送者和接收者之间的耦合关系。将这些对象连成一条链&#xff0c;并沿着这条链传递该请求&#xff0c;直到有对象处理它为止。最匹配的场景应该就是逐层审批的模式。 责任链模式只有两个角色&#xff…

wife_wife-攻防世界

题目 注册发现可以注册管理员,但是好像有条件 抓包试试 没思路了 看看其他师傅的wp&#xff0c;用到 js 原型链污染攻击 Nodejs原型链污染攻击基础知识 | Savants Blog (lxscloud.top) 网站后端是Node.js搭建的 原型链污染 简单来讲&#xff0c;通过 newUser.__proto__ …

RHCE实验2-DNS服务正反向解析

实验开始 一、DNS正向解析 注&#xff1a; server端&#xff1a;192.168.32.147 node端&#xff1a;192.168.32.141 网址&#xff1a;www.openlab.com 1、server端和node端都关闭安全软件&#xff08;以server端为例&#xff09; [rootserver ~]# setenforce 0 [rootser…

Java基于微信小程序的校园跑腿小程序,附源码

博主介绍&#xff1a;✌程序员徐师兄、8年大厂程序员经历。全网粉丝15w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

vue3+element plus图片预览点击按钮直接显示图片的预览形式

1 需求 直接上需求&#xff1a; 我想要直接点击下面这个“预览”按钮&#xff0c;然后呈现出预览图片的形式 ok&#xff0c;需求知道了&#xff0c;下面让我们来看看如何实现吧 ~ 2 实现 template部分 <el-buttontype"primary"size"small"click&qu…

链表中常见的使用方法逻辑整理

文章目录 1. 链表特点2. 链表创建3. 链表遍历通用方法3.1 在链表的开头添加元素3.2 在链表的结尾添加元素3.3 删除链表的第一个元素3.4 删除链表的最后一个元素3.5 遍历链表3.6 查找链表中的元素3.7 反转链表 4. 常见面试题4.1 相交链表4.2 反转链表4.3 环形链表4.4 环形链表 I…

easyui combobox下拉框组件输入检索全模糊查询

前引&#xff1a; easyui下拉组件&#xff08;combobox&#xff09;&#xff0c;输入检索下拉内容&#xff0c;是默认的右模糊匹配&#xff0c;而且不支持选择。因业务要求需要做成全模糊查询&#xff0c;目前网上搜索有两种方案&#xff1a; 1.修改easyui源码&#xff0c;这个…

LeetCode700:二叉搜索树中的搜索

题目描述 给定二叉搜索树&#xff08;BST&#xff09;的根节点 root 和一个整数值 val。 你需要在 BST 中找到节点值等于 val 的节点。 返回以该节点为根的子树。 如果节点不存在&#xff0c;则返回 null 。 代码 递归法 class Solution { public:TreeNode* searchBST(TreeN…

Visual Studio code无法正常执行Executing task: pnpm run docs:dev

最近尝试调试一个开源的项目&#xff0c;发现cmd可以正常启动&#xff0c;但是在vs中会报错&#xff0c;报错内容如下 Executing task: pnpm run docs:dev pnpm : 无法加载文件 E:\XXXX\pnpm.ps1&#xff0c;因为在此系统上禁止运行脚本。有关详细信息&#xff0c;请参阅 http…

组合导航的结果分段跳变问题

1 现象 用上海代数律动公司的AlgoT1-3组合导航设备采集数据进行组合导航算法调试&#xff0c;AlgoT1-3机器输出的结果很好很平滑&#xff0c;AlgoT1-3是带GNSS/INS的组合导航设备&#xff0c;另外还有一款更贵一点的带视觉的组合导航AlgoT1&#xff0c;效果会更好一些&#xf…

【Tars-go】腾讯微服务框架学习使用03-- TarsUp协议

3 TarsUP协议 统一通信协议 TarsTup | TarsDocs (tarscloud.github.io) TarsDocs/base at master TarsCloud/TarsDocs (github.com) &#xff1a; 有关于tars的所有介绍 每一个rpc调用双方都约定一套数据序列化协议&#xff0c;gprc用的是protobuff&#xff0c;tarsgo是统一…

每日OJ题_01背包③_力扣494. 目标和(dp+滚动数组优化)

目录 力扣494. 目标和 问题解析 解析代码 滚动数组优化代码 力扣494. 目标和 494. 目标和 难度 中等 给你一个非负整数数组 nums 和一个整数 target 。 向数组中的每个整数前添加 或 - &#xff0c;然后串联起所有整数&#xff0c;可以构造一个 表达式 &#xff1a; …