官网:

wangEditor  https://www.wangeditor.com/v5/

为啥用这个富文本编辑器(我觉得官网写自己优势已经非常好了没有啥可补充的了)

文档特别的全和友好

安装

yarn add @wangeditor/editor
# 或者 npm install @wangeditor/editor --save vue3
yarn add @wangeditor/editor-for-vue@next
# 或者 npm install @wangeditor/editor-for-vue@next --save vue2
yarn add @wangeditor/editor-for-vue
# 或者 npm install @wangeditor/editor-for-vue --save 安装 React 组件(可选)
yarn add @wangeditor/editor-for-react
# 或者 npm install @wangeditor/editor-for-react --save cdn
<!-- 引入 css -->
<link href="https://unpkg.com/@wangeditor/editor@latest/dist/css/style.css" rel="stylesheet">
<!-- 引入 js -->
<script src="https://unpkg.com/@wangeditor/editor@latest/dist/index.js"></script>
<script>
var E = window.wangEditor; // 全局变量
</script>

效果样式:

特别说明我的修改文章和发布文章用的是同一个路由地址和组件就只改改接口和一些文字

定义一个子组件专门放编辑器的

<template>
<div style="margin-top:10px;box-shadow: rgb(0 0 0 / 10%) 0px 2px 12px 0px;">
// toobar 这个是就是头部那些编辑器 editor 是下面那个编辑器实例 defaultConfig是配置编辑器的
<Toolbar style="border-bottom: 1px solid #efefef" :editor="editorRef" :defaultConfig="toolbarConfig"
mode="default" />
// v-model 获取到的值就是文本编辑器内的值 defaultConfig 是配置编辑器的全局配置
<Editor style="height: calc(100vh - 280px); overflow-y: hidden;" v-model="valueHtml" :defaultConfig="editorConfig"
mode="default" @onCreated="handleCreated" />
</div>
</template>
<script setup lang="ts">
import '@wangeditor/editor/dist/css/style.css'
import { SlateElement } from '@wangeditor/editor'
import { Editor, Toolbar } from '@wangeditor/editor-for-vue' 
import { onBeforeUnmount, ref, shallowRef, inject, watch, nextTick } from 'vue'
// 这个是我自己定义的两个服务器接口函数 一个上传图片的 一个删除图片的
import { postArticleImage, deleteArticleImages } from '@/utils/articleHttp'
import { ElMessage } from 'element-plus';
import { useRoute } from 'vue-router'
const route = useRoute()
// 定义插入成功后的图片类型
type ImageElement = SlateElement & {
src: string
alt: string
url: string
href: string
}
// 编辑器实例,必须用 shallowRef
const editorRef = shallowRef()
// 全局状态 这个是自己定义的组件内传递数据的 类似于vuex
const store: any = inject('store')
// 内容 HTML
const valueHtml = ref<string>('')
// 插入的图片
const InsertionImages: ImageElement[] = []
// 背景图片
const bg = ref<string>('') // 传入的文本 默认值没有
const props = withDefaults(defineProps<{
updateHtml: string
}>(), { updateHtml: '' }) // 因为我要靠queryid 去发请求请求要修改的数据 所以有id 才会执行下面逻辑
route.query.id && watch(() => props.updateHtml, () => { // 监视传入的值的改变
valueHtml.value = props.updateHtml // 给编辑器赋值初始化
nextTick(() => { // 要在数据回来dom 上树之后才能获取编辑器上的图片
InsertionImages.push(...editorRef.value.getElemsByType('image')) //把编辑器上初始的图片放入一个数组里面方便提交的时候对比
})
}, {
immediate: true // 上来就监视
})
// 上传图片
type InsertFnType = (url: string, alt: string, href: string) => void // 定义类型
const updatedImage = async (file: File, insertFn: InsertFnType) => {
// 因为我要上传到服务器的图片是multipart /form-data格式必须要放入到formdata 里面
const fd = new FormData()
fd.append('file', file) // 服务器定义的文件名字
postArticleImage(fd).then(res => { // 这个是一个上传服务器方法 这个根据自己的实际情况定义
if (res.data.ok) { // 这个是我自己写发服务器所以就拿ok 来判断是否成功了
insertFn(store.state.BaseUrl + res.data.imageUrl, '服务错误', '') // 这个是调用的回调函数来插入图片到编辑当中 三个参数 地址 alt href
}
else ElMessage.error(res.data.info) // 否者返回错误
})
} // 下面是插入图片的回调 官网给的的钩子函数
const onInsertedImage = (imageNode: ImageElement | null) => {
if (imageNode == null) return
// 放入图片到数组 方便发送的时候对比
InsertionImages.push(imageNode)
}
const toolbarConfig = {
// 这个是工具栏上的小工具的全局配置
excludeKeys: [ // 把上传视频的剔除掉 因为我的服务器实在也是垃圾接收视频需要占用大量的资源
'group-video']
}
const editorConfig = { // 这个是编辑器的全局配置
placeholder: '请输入内容...', // 情况的时候显示的提示
MENU_CONF: {
// 配置上传图片
uploadImage: {
// 小于该值就插入 base64 格式(而不上传),默认为 0
base64LimitSize: 10 * 1024, // 10kb
customUpload: updatedImage // 自定义上传文件的方法 在上面
},
insertImage: {
onInsertedImage: onInsertedImage // 插入图片后成功的回调
}
}
}
// 组件销毁时,也及时销毁编辑器
onBeforeUnmount(() => {
const editor = editorRef.value
if (editor == null) return
editor.destroy() // 销毁
}) const handleCreated = (editor: any) => {
editorRef.value = editor // 记录 editor 实例,重要!
}
// 删除服务器 图片的方法
const deletImage = (): void => {
// 最后的图片
const images = (editorRef.value.getElemsByType('image') as ImageElement[]).map(item => item.src)
const deletImages = InsertionImages.filter(item => !images.includes(item.src)) //筛选出来要删除的图片 就是上传时候的图片和插入过的图片对比多出来的就是
// 调用删除的函数
deleteArticleImages(deletImages.map(item => item.src))
images[0] && (bg.value = images[0].split(':3999/')[1]) // 这个是我选中如果文本上传图片的话就用第一张当文字背景
} // 清空编辑器里的东西
const clearHtml = () => {
InsertionImages.splice(0) //清空插入过的图片数组
} // 把外面用的东西暴露了出去方便外面使用
defineExpose({ deletImage, valueHtml, bg, clearHtml, InsertionImages })
</script> <style lang="scss">
.w-e-bar-item {
padding: 1px;
}
</style>

定义父组件来引用富文本编辑器 (因为我的element 组件是cdn引入所以不用按需引入就能使用看你们个人情况)

<template>
<el-card class="box-card">
<h2 class="titleHeader">{{ route.query.id ? '修改文章' : '发布文章' }} // 判断是否是修改文章的
<div class="headerRight">
// 这个是文章的类型
<el-select v-model="lableNameData" class="m-2" placeholder="默认草稿">
<el-option v-for="item in store.state.lableData" :key="item._id" :label="item.labelName"
:value="item.labelName" />
</el-select>
<SendBtn @click="handleSend">send</SendBtn> // 这个是自己自定义的一个小按钮
</div>
</h2>
<el-input v-model="articleTitle" placeholder="请输入文章标题" clearable />
<TinymceEditor ref="Editor" :updateHtml="updateHtml"></TinymceEditor> // 引入文本编辑器组件传入值
</el-card>
</template> <script lang="ts" setup>
import { ref, onBeforeUnmount, inject, defineAsyncComponent } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import dayjs from 'dayjs' // 这个是专门处理事件的比momentjs要小一些
import { ElMessage } from 'element-plus';
import { postArticle, getArticleSingle, updateArticle } from '@/utils/articleHttp' // 自己定义的上传文章获取文章内容 更新文章的方法
const TinymceEditor = defineAsyncComponent(() => import('@/components/article/TinymceEditor.vue')) // 异步导入文本组件 为了打包的时候分包可以在加载到这个的时候在加载资源
const SendBtn = defineAsyncComponent(() => import('@/components/button/SendBtn.vue')) // 这个单词的为了和上面的统一哈哈哈 // 传入的要更新的html
const updateHtml = ref('')
// 全局状态 这个是自己写的类使用vuex
const store: any = inject('store')
// 分类标签
store.GetLabelData()// 获取分类标签 有就return了 为了减轻服务器压力少发请求
// 编辑框
const Editor = ref()
// 文章标题
const articleTitle = ref<string>('')
articleTitle.value = dayjs(new Date()).format('YYYY-MM-DD HH:mm') // 标题上默认给个时间作为标题单纯为了好看
// 引入路由
const route = useRoute()
const router = useRouter()
const labelNameData = ref<string>('') // 文章标签的数据
// 是否是更新的文章
const updateArticleFlag: Boolean = !!route.query.id
// 原始的标签
let oldlabelName: string;
// 更新的就有id 先存起来不能用路由跳转的时候就没了
let updateArticleId = route.query.id as string | undefined
// 如果有query id就发请求
const getUpdateData = () => {
getArticleSingle(updateArticleId).then(res => { // 发请求自己定义的获取 要修改文章的内容
articleTitle.value = res.data.info[0].title // 初始化修改文章标题
lableNameData.value = res.data.info[0].articleType //初始化修改文章的类型
oldlabelName = res.data.info[0].articleType // 传给oldLableName
updateHtml.value = res.data.info[0].info // 初始化修改文章的内容
})
}
// 判断是否是传入的是更新的文章 就执行后面函数
updateArticleFlag && getUpdateData() // 提交
const handleSend = async () => {
if (!articleTitle.value.trim()) return ElMessage.error('标题不能为空')
const labelName = labelNameData.value || '草稿'; // 如果不选类型就当草稿发送
(Editor.value as { deletImage: () => void }).deletImage();// 触发删除图片函数就是定义在子组件的
let res;
if (updateArticleFlag) { // 判断是否是修改的文章
// 修改的逻辑
res = await updateArticle(updateArticleId, articleTitle.value, Editor.value.valueHtml, labelName, Editor.value.bg) // 上传修改的内容
// 删除旧的labelName的文章数量 为啥删除因为方便下面还要添加 如果改了这个值就不需要判断直接能加1 了
store.subtractLabelArticle(oldlabelName) // 因为为了少发请求 lablename值改变了就会改变全局的label数据 因为这个数据在多个地方都用到了
} else {
// 提交的逻辑
res = await postArticle(articleTitle.value, Editor.value.valueHtml, Editor.value.bg, labelName)
}
// 对应label的文章数量加1
store.addLableArticle(labelName)
res.data.ok ? ElMessage.success(res.data.info) : ElMessage.error(res.data.info) // 服务器返回数据 提示一下
// 清空那个数组以防多次触发保存 因为我要在页面刷新的时候和切换组件的时候去执行提交函数 主要为了防止上传完图片而切换走了服务器的图片就会堆积删除不掉了
Editor.value && Editor.value.clearHtml()
router.replace('/home/article/article-list') // 跳转到文章列表页
}
// 当组件销毁的时候的钩子函数
onBeforeUnmount(() => {
window.onbeforeunload = null // 把事件销毁
Editor.value.InsertionImages.length && handleSend() // 当没按提交按钮的时候切换组件导致 上传图片没有显示照成图片在服务器上堆积
})
// 当页面刷新的时候钩子函数
window.onbeforeunload = () => {
Editor.value.InsertionImages.length && handleSend() // 刷新页面的时候也是一样的
} </script> <style lang="scss" scoped>
.titleHeader {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
} .m-2 {
width: 100px;
} .headerRight {
display: flex;
}
</style>

真的是写代码十分钟打注释半个小时 ^_^

wangeditor富文本编辑和vue3的更多相关文章

  1. 深入理解javascript中的富文本编辑

    前面的话 一说起富文本,人们第一印象就是像使用word一样,在网页上操作文档.实际上差不多就是这样.富文本编辑,又称为WYSIWYG (What You See Is What You Get所见即所 ...

  2. laravel富文本编辑和图片上传

    ---恢复内容开始--- 首先先找到一个适合的编辑器是胜利的一步,选择wangEditor这个编辑器 地址:http://www.wangeditor.com/ 然后选择下载,我是通过网上学习的,所以 ...

  3. 「newbee-mall新蜂商城开源啦」 页面优化,最新版 wangEditor 富文本编辑器整合案例

    大家比较关心的新蜂商城 Vue3 版本目前已经开发了大部分内容,相信很快就能够开源出来让大家尝鲜了,先让大家看看当前的开发进度: 开源仓库地址为 https://github.com/newbee-l ...

  4. kendo ui 富文本编辑控件 Editor 实现本地上传图片,并显示

    富文本编辑的组件有很多,大名鼎鼎的KENDO UI中自然也有,但是默认功能中,只能包含网络图片, 而如果要实现本地上传图片,KENDO UI也提供了相应的功能,但必须实现KENDO规定的多个接口, 而 ...

  5. 更加简洁易用——wangEditor富文本编辑器新版本发布

    1. 前言 wangEditor富文本编辑器(www.wangEditor.com)从去年11月份发布,至今已经有将近10各月了.它就像一个襁褓中的小婴儿,在我的努力以及众多使用者的支持下不断摸索.成 ...

  6. UEditor富文本编辑框学习

    1.首先需要引入CSS.JS <!--富文本编辑框--> <link href="${pageContext.request.contextPath}/css/plugin ...

  7. ueditor富文本编辑在 asp.net MVC下使用步骤

    mvc项目中用到了这个富文本编辑就试着把遇到的问题个使用步骤在这里记录一下,希望大家少走弯路. 1.首先我们先下载net版本的uediot r.

  8. 富文本编辑,xss攻击

    富文本编辑 KindEditor 在线HTML编辑器 http://kindeditor.net/doc.php 下载成功,解压放到项目中去 查看官方文档进行操作 xss攻击 XSS攻击全称跨站脚本攻 ...

  9. 14.5 富文本编辑【JavaScript高级程序设计第三版】

    富文本编辑,又称为WYSIWYG(What You See Is What You Get,所见即所得).在网页中编辑富文本内容,是人们对Web 应用程序最大的期待之一.虽然也没有规范,但在IE 最早 ...

  10. SNF快速开发平台MVC-EasyUI3.9之-ueditor富文本编辑在 asp.net MVC下使用步骤

    mvc项目中用到了这个富文本编辑就试着把遇到的问题个使用步骤在这里记录一下,希望大家少走弯路. 1.首先我们先下载net版本的uediot 2.然后把整个文档拷贝到我们的项目中,记得是整个 把下载的文 ...

随机推荐

  1. jvm双亲委派机制详解

    双亲委派机制 ​ 记录一下JVM的双亲委派机制学习记录. 类加载器种类 ​ 当我们运行某一个java类的main方法时,首先需要由java虚拟机的类加载器将我们要执行的main方法所在的class文件 ...

  2. SpringBoot 03: 常用web组件 - - - 拦截器 + Servlet + 过滤器

    常用web组件 拦截器 Servlet 过滤器 使用思想 创建自定义类 实现或者继承框架里的接口或类 将自定义类注册到框架中 使用自定义类 拦截器 说明 拦截器是SpringMVC中的一种对象,能拦截 ...

  3. [Android开发学iOS系列] TableView展现一个list

    TableView 基础 本文讲讲TableView的基本使用. 顺便介绍一下delegation. TableView用来做什么 TableView用来展示一个很长的list. 和Android中的 ...

  4. (C++) 笔记 C++11 std::mutex std::condition_variable 的使用

    #include <atomic> #include <chrono> #include <condition_variable> #include <ios ...

  5. CopyOnWriteArrayList 是如何保证线程安全的?

    本文已收录到 AndroidFamily,技术和职场问题,请关注公众号 [彭旭锐] 提问. 前言 大家好,我是小彭. 在上一篇文章里,我们聊到了ArrayList 的线程安全问题,其中提到了 Copy ...

  6. 8 STL-stack

    ​ 重新系统学习c++语言,并将学习过程中的知识在这里抄录.总结.沉淀.同时希望对刷到的朋友有所帮助,一起加油哦!  生命就像一朵花,要拼尽全力绽放!死磕自个儿,身心愉悦! 写在前面,本篇章主要介绍S ...

  7. 关于在linux上vm virtualbox读取不到U盘问题的解决

    1.设置usb2.0模式 如果你没安装拓展插件的话,调成usb2.0就会出现无效的配置这个提示,并且启动虚拟机会报 Implementation of the USB 2.0 controller n ...

  8. ajax 跨域请求jsonp

    最近一段时间为这个事情走了不少弯路,现将成功经验分享,避免后来人再绕远路,不过也是第一次使用中间有什么问题大家可以留言探讨. ajax的跨域请求jsonp主要运用于不同系统的交互,一个系统想通过该种方 ...

  9. 【Java Web】项目通用返回模块ServerResponse:枚举code状态码、泛型返回值、序列化注解限制数据

    一.枚举类编写ResponseCode package com.boulderaitech.common; /** * 编写枚举类的步骤 * (1)编写所需的变量 * (2)编写枚举类构造方法 * ( ...

  10. 【大数据面试】Hbase:数据、模型结构、操作、读写数据流程、集成、优化

    一.概述 1.概念 分布式.可扩展.海量数据存储的NoSQL数据库 2.模型结构 (1)逻辑结构 store相当于某张表中的某个列族 (2)存储结构 (3)模型介绍 Name Space:相当于数据库 ...