官网:

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. python 的time、datetime模块

    python 时间模块 import datetime ​ res = datetime.datetime.now() print(res) # 2022-08-07 16:47:07.120459 ...

  2. 「浙江理工大学ACM入队200题系列」问题 F: 零基础学C/C++39——求方程的解

    本题是浙江理工大学ACM入队200题第四套中的F题 我们先来看一下这题的题面. 由于是比较靠前的题目,这里插一句.各位新ACMer朋友们,请一定要养成仔细耐心看题的习惯,尤其是要利用好输入和输出样例. ...

  3. Window使用PowerShell改文件时间戳

    We cross infinity with every step; we meet eternity in every second. 我们每一步都跨过无穷,每一秒都遇见永恒. Window使用Po ...

  4. redisson分布式锁原理剖析

    redisson分布式锁原理剖析 ​ 相信使用过redis的,或者正在做分布式开发的童鞋都知道redisson组件,它的功能很多,但我们使用最频繁的应该还是它的分布式锁功能,少量的代码,却实现了加锁. ...

  5. Day29 派生, 封装 , 多态, 反射

    Day29 派生, 封装 , 多态, 反射 内容概要 派生方法的实践 面向对象之封装 面向对象之多态 面向对象之反射 反射的实践案例 内容详细 1.派生方法的实践 #需求展示 import json ...

  6. 一文带你快速入门 Go 语言微服务开发 - Dubbo Go 入门实践总结

    更多详细示例可直接访问 Dubbo 官网 或搜索关注官方微信公众号:Apache Dubbo 1. 安装Go语言环境 建议使用最新版 go 1.17 go version >= go 1.15 ...

  7. Pod控制器详解

    Pod控制器详解 7.1 Pod控制器介绍 Pod是kubernetes的最小管理单元,在kubernetes中,按照pod的创建方式可以将其分为两类: 自主式pod:kubernetes直接创建出来 ...

  8. 零基础学习python的第一天整理——python的安装以及pycharm安装

    ​ 一.python的安装 首先我们来谈一谈python的安装,python的官网地址:Welcome to Python.org​编辑 进入官网后点击Downloads,然后选择自己对应的系统,比如 ...

  9. K8s架构|全面整理K8s的架构介绍

    K8S架构与核心技术介绍 1. 架构图 1.1 整体结构图 1.2 组件间的协议 CNI: CNI是Container Network Interface的是一个标准的,通用的接口 ;用于连接容器管理 ...

  10. SQLMap入门——获取当前网站数据库的名称

    列出当前网站使用的数据库 python sqlmap.py -u http://localhost/sqli-labs-master/Less-1/?id=1 --current-db