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.value
    if (editor == null) return
    editor.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:8080
            String imageUrl = myEnv +"/commons/download?picName=" + randomUUIDString + fileExtension;
            data.put("url", imageUrl); // 使用拼接的URL路径
            data.put("alt", "Image description"); // 可以根据需要从文件或其他地方获取
            data.put("href", imageUrl); // 使用同一个URL作为href
            data.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,就这样,完毕!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

一款免费、开源、可批量识别的离线OCR软件,适用于 Windows7 x64及以上平台

免费&#xff1a;本项目所有代码开源&#xff0c;完全免费。方便&#xff1a;解压即用&#xff0c;离线运行&#xff0c;无需网络。高效&#xff1a;自带高效率的离线OCR引擎&#xff0c;内置多种语言识别库。灵活&#xff1a;支持命令行、HTTP接口等外部调用方式。功能&#x…

Android开发——控件

目录 TextView 注意&#xff1a; ​编辑带阴影的textview&#xff1a;&#xff08;一般用于给字体添加属性&#xff09; ​编辑 跑马灯效果的textview​编辑 Button (前几个常用&#xff09; Botton事件处理 EditText (文本框&#xff09; 如何获取文本框里面的内容…

统计学习方法概述

一、引言 随着AI的曙光逐渐普照IT界&#xff0c;众多曾经高深莫测的人工智能术语与理念&#xff0c;如监督学习、算法模型、回归分析等&#xff0c;已悄然融入广大信息技术人员的知识体系之中。老猿是个很传统的IT人&#xff0c;虽未深究这些新兴知识的精髓&#xff0c;却也在…

从零开始编写一个cmake构建脚本

简介 本文档介绍cmake构建脚本编写&#xff0c;包含的一些主要元素和命名规范。 cmake构建脚本编写步骤 cmake构建工具版本要明确 # 命令名字要小写&#xff0c;这条语句要求构建工具至少需要版本为3.12或以上 cmake_minimum_required (VERSION 3.12)工程名及库的版本号明确…

阿里面试总结 一

写了这些还是不够完整&#xff0c;阿里 字节 卷进去加班&#xff01;奥利给 ThreadLocal 线程变量存放在当前线程变量中&#xff0c;线程上下文中&#xff0c;set将变量添加到threadLocals变量中 Thread类中定义了两个ThreadLocalMap类型变量threadLocals、inheritableThrea…

深度学习的模型有几类,能干嘛用?

1、基础模型 &#xff08;1&#xff09;卷积神经网络 **卷积&#xff1a;**卷积的本质是通过矩阵运算9的方式将输入数据进行空间上的滤波&#xff0c;有效地提取数据中的局 部特征&#xff0c;从而实现特征数据更高程度的抽象表示。 **池化&#xff1a;**可以理解成“压缩”…

火绒安全软件:程序员的网络守护天使

目录 前言 系统防护 网络防护 隐私保护 高级设置 软件安全 响应速度 持续更新 总结 前言 在这个充满机遇与挑战的数字时代&#xff0c;程序员们如同探险家&#xff0c;不断探索着代码的新大陆。然而&#xff0c;网络世界也充斥着各种未知的风险和威胁。火绒安全软件&a…

浏览器工作原理与实践--渲染流水线:CSS如何影响首次加载时的白屏时间

在上一篇文章中我们详细介绍了DOM的生成过程&#xff0c;并结合具体例子分析了JavaScript是如何阻塞DOM生成的。那本文我们就继续深入聊聊渲染流水线中的CSS。因为CSS是页面中非常重要的资源&#xff0c;它决定了页面最终显示出来的效果&#xff0c;并影响着用户对整个网站的第…

初识 QT

初始QT 什么是QTQT发展史QT支持的平台QT的优点QT的应用场景搭建QT开发环境QT的开发工具概述QT下载安装 使用QT创建项目QT 实现Hello World程序使用按钮控件来实现使用标签控件来实现 项目文件解析widget.hmain.cppwidget.cppwidget.ui.pro文件 对象树QT 窗口坐标体系 什么是QT …

STM32H7的MPU学习和应用示例

STM32H7的MPU学习记录 什么是MPU&#xff1f;MPU的三种内存类型内存映射MPU保护区域以及优先级 MPU的寄存器XN位AP位TEX、C、B、S位SRD 位SIZE 位CTRL 寄存器的各个位 示例总结 什么是MPU&#xff1f; MPU&#xff08;Memory Protection Unit&#xff0c;内存保护单元&#xf…

LeetCode最长有效括号问题解

给定一个仅包含字符的字符串(’ 和 ‘)’&#xff0c;返回最长有效的长度(出色地-形成) 括号子弦。 示例1&#xff1a; 输入&#xff1a;s “(()” 输出&#xff1a;2 说明&#xff1a;最长的有效括号子字符串是 “()” 。 示例2&#xff1a; 输入&#xff1a;s “)()())…

5分钟手把手教你 Guitar Pro v8.1.1 Build 17 中文完整激活版(附教程) 64位

吉他爱好者必备神器&#xff1a;Guitar Pro v8.1.1 Build 17深度解析 随着数字音乐制作和学习的日益普及&#xff0c;越来越多的吉他爱好者开始寻找能够帮助他们提升技能、创作音乐的专业工具。在众多吉他制作软件中&#xff0c;Guitar Pro因其强大的功能和易用的界面备受推崇…

Linux内核中KASLR功能是什么?有什么作用?怎么破除?以及如何实操?(地址空间、layout random、kallsyms)

1. 背景 KASLR是一个什么技术点其实不重要&#xff0c;但重要的是有了KASLR这个功能后&#xff0c;造成内核中某个符号&#xff08;函数 or 变量&#xff09;在System.map中的地址和实际不一样了&#xff08;实际&#xff1a; cat /proc/kallsyms&#xff09;&#xff0c;进一…

JVM性能调优——GC日志分析

文章目录 1、概述2、生成GC日志3、Parallel垃圾收集器日志解析3.1、Minor GC3.2、FULL GC 4、G1垃圾收集器日志解析4.1、Minor GC4.2、并发收集4.3、混合收集4.4、Full GC 5、CMS垃圾收集器日志解析5.1、Minor GC5.2、Major GC5.3、浮动垃圾 6、日志解析工具6.1、GCeasy6.2、GC…

Java代码基础算法练习-自定义函数之求字符串长度-2024.04.13

任务描述&#xff1a; 写一函数&#xff0c;求一个字符串的长度&#xff08;字符串长度不超过255&#xff09;&#xff0c;然后在主函数中调用该函数 实现求长度操作。 任务要求&#xff1a; 代码示例&#xff1a; package April_2024;import java.util.Scanner;public class …

Spark AQE(Adaptive Query Execution)机制

&#x1f490;&#x1f490;扫码关注公众号&#xff0c;回复 spark 关键字下载geekbang 原价 90 元 零基础入门 Spark 学习资料&#x1f490;&#x1f490; AQE 的全称是 Adaptive Query Execution&#xff0c;翻译过来是“自适应查询执行”。它包含了 3 个动态优化特性&#…

Android适配平板屏幕尺寸

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

Python代码识别minist手写数字【附pdf】

一、概述 对于人类而言&#xff0c;要识别图片中的数字是一件很容易的事情&#xff0c;但是&#xff0c;如何让机器学会理解图片上的数字&#xff0c;这似乎并不容易。那么&#xff0c;能否找出一个函数&#xff08;模型&#xff09;&#xff0c;通过输入相关的信息&#xff0…

FourCastNet 论文解析

气象基础模型/气象大模型论文速递 论文链接基于arXiv Feb. 22, 2022版本阅读 几乎是第一篇气象大模型的工作&#xff0c;同时也是为数不多的对precipitation进行预测的模型。 文章目录 PerformanceStructureFourier transformToken mixing TrainingPrecipitation Model Ensembl…

科研学习|可视化——Origin绘制相关性系数矩阵

一、Origin软件版本 Origin2021版本 二、插件下载地址 CorrelationPlot.opx资源-CSDN文库 三、插件安装步骤 从上述链接下载插件将插件解压缩&#xff08;最好是解压缩到orgin的安装目录&#xff09;用origin打开插件&#xff08;或者打开origin&#xff0c;将插件拖拽到origin…