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

news2024/12/22 21:01:10

目录

 概述

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,就这样,完毕!

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

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

相关文章

C++ - 二叉搜索树的基本实现

目录 0. 引言 1. 二叉搜索树 1.1 定义 1.2 特点 2. 二叉搜索树的实现 2.1 基本框架 2.2 查找 2.3 插入 2.4 删除 2.4.1 右子树为空 2.4.2 左子树为空 2.4.3 左右都不为空 2.4.4 代码 0. 引言 在C语言数据结构中&#xff0c;我们已经基本了解过二叉树&#xff…

Golang 开发实战day10 - Maps

&#x1f3c6;个人专栏 &#x1f93a; leetcode &#x1f9d7; Leetcode Prime &#x1f3c7; Golang20天教程 &#x1f6b4;‍♂️ Java问题收集园地 &#x1f334; 成长感悟 欢迎大家观看&#xff0c;不执着于追求顶峰&#xff0c;只享受探索过程 Golang 教程10 - Maps 1. M…

WordPress网站上添加看板娘

续接上篇——基于LNMP部署wordpress-CSDN博客 目录 一.下载并解压 二.设置头文件 修改header.php 修改配置文件footer.php 三.将你设置的主题包上传到/usr/share/nginx/html/wp-content这个目录里 四.扩展——将看板娘修改到左侧 一.下载并解压 [rootaliyun ~]# wget htt…

2024年阿里云优惠券领取,买前必看的多渠道代金券获取方法

阿里云优惠代金券领取入口&#xff0c;阿里云服务器优惠代金券、域名代金券&#xff0c;在领券中心可以领取当前最新可用的满减代金券&#xff0c;阿里云百科aliyunbaike.com分享阿里云服务器代金券、领券中心、域名代金券领取、代金券查询及使用方法&#xff1a; 阿里云优惠券…

Leetcode刷题之删除有序数组的重复项

一、题目描述 删除有序数组的重复项 给你一个 非严格递增排列 的数组 nums &#xff0c;请你 原地 删除重复出现的元素&#xff0c;使每个元素 只出现一次 &#xff0c;返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。 考虑 nums…

360安全卫士去除广告方法

大安全时代&#xff0c;360 安全卫士为您提供全面安全服务&#xff0c;电脑端下载&#xff1a; https://urlqh.cn/orQqc 在当今数字化时代&#xff0c;网络安全已成为人们日常生活中的重要关切。在这片浩瀚的网络海洋中&#xff0c;360安全卫士犹如一座坚不可摧的灯塔&#xf…

TL431内部架构学习

在V/I转换那个篇章里面看到了TL431的内部架构,那我们这一篇一点点的解析TL431的构成,首先TL431的内部详细原理图如下图1所示,为了便于理解我对管子进行了标注,倒时候我们好分析 图1:TL431内部原理图 拿到原理图后我们先简单的拆分,Q10和Q11就是达林顿管,控制Cathode的电压的Q2…

【RHEL】redhat yum 报错: not registered to Red Hat Subscription Management.

【RHEL】redhat yum 报错: not registered to Red Hat Subscription Management. 问题描述解决方法参考博客&#xff1a; 问题描述 使用redhat7用yum install -y dos2unix命令时出现这个错误 This system is not registered to Red Hat Subscription Management. You can use …

Zotero插件ZotCard中AI-NNDL文献笔记卡

github&#xff1a;ZotCard插件AI-NNDL论文卡片模板 Issue #67 018/zotcard (github.com) ZotCard插件AI-NNDL论文卡片模板是关于人工智能神经网络与深度学习论文的笔记卡片&#xff0c;效果预览如下图&#xff1a; 经过了整理代码如下&#xff1a; <h1><span styl…

Vue2 —— 学习(六)

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

最齐全,最简单的免费SSL证书获取方法——实现HTTPS访问

一&#xff1a;阿里云 优势&#xff1a;大平台&#xff0c;在站长中知名度最高&#xff0c;提供20张免费单域名SSL证书 缺点&#xff1a;数量有限&#xff0c;并且只有单域名证书&#xff0c;通配符以及多域名没有免费版本。并且提供的单域名证书只有三个月的期限。 二&#…

每日一题 第八十九期 洛谷 [NOIP2017 提高组] 奶酪

[NOIP2017 提高组] 奶酪 题目背景 NOIP2017 提高组 D2T1 题目描述 现有一块大奶酪&#xff0c;它的高度为 h h h&#xff0c;它的长度和宽度我们可以认为是无限大的&#xff0c;奶酪中间有许多半径相同的球形空洞。我们可以在这块奶酪中建立空间坐标系&#xff0c;在坐标系…

10 Php学习:循环

在 PHP 中&#xff0c;提供了下列循环语句&#xff1a; while - 只要指定的条件成立&#xff0c;则循环执行代码块do…while - 首先执行一次代码块&#xff0c;然后在指定的条件成立时重复这个循环for - 循环执行代码块指定的次数foreach - 根据数组中每个元素来循环代码块 当…

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

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

【cocos creator】【TS】贝塞尔曲线,地图之间显示曲线,顺着曲线移动

参考&#xff1a; https://blog.csdn.net/Ctrls_/article/details/108731313 https://blog.csdn.net/qq_28299311/article/details/104009804 const { ccclass, property } cc._decorator;ccclass export default class mapPanel extends cc.Component {property(cc.Node)pla…

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

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

spring boot学习第十七篇:OAuth2概述及使用GitHub登录第三方网站

0. 导言 我们在浏览器上可以访问成百上千个网站&#xff0c;使用每个网站的服务一般都要先注册账号&#xff0c;那么我们为了更好地记忆&#xff0c;一般都会在多个网站使用相同的账号和密码进行注册。那么问题就来了&#xff0c;如果在你注册的网站中有某些个网站的系统设计不…

C语言-----结构体详解

前面已经向大家介绍过一点结构体的知识了&#xff0c;这次我们再来深度了解一下结构体。结构体是能够方便表示一个物体具有多种属性的一种结构。物体的属性可以转换为结构体中的变量。 1.结构体类型的声明 1.1 结构体的声明 struct tag {member-list;//结构体成员变量 }vari…

VLC-Qt实现简单的视频播放器

VLC-Qt是一个结合了Qt应用程序和libVLC的免费开源库。它提供了用于媒体播放的核心类&#xff0c;以及用于快速开发媒体播放器的GUI类。由于集成了整个libVLC&#xff0c;VLC-Qt具备了libVLC的所有特性&#xff0c; 例如&#xff1a;libVLC实例和播放器、单个文件和列表播放、音…

海山数据库(He3DB)原理剖析:浅析Doris跨源分析能力

Doris湖仓分析背景&#xff1a; Doris多数据源功能演进 Doris的生态近年来围绕湖仓分析做了较多工作&#xff0c;Doris一直在积极拓宽大数据生态的OLAP分析市场&#xff0c;Doris2.0之后为了满足湖仓分析场景&#xff0c;围绕multi-catalog、数据缓存、容错、pipeline资源管理…