- 个人觉得wangeditor富文本编辑器非常好用,官方文档也写的很详细易懂,虽然wangeditor富文本编辑器支持图片添加超链接点击跳转,但那也仅限于你在wangeditor富文本编辑器编辑器里面支持,如果你用wangeditor富文本编辑器进行了各种编辑操纵并保存了值,别的地方需要用
v-html
去解析(例如移动端),此时你就会发现我的图片怎么点击没反应?,因为wangeditor富文本编辑器会把你添加的超链接定义为img
标签的一个自定义属性并将其转义。此时你就得想办法给img
标签外部套一个a
标签进行跳转方便v-html
去解析,以下就演示如何使用wangeditor富文本编辑器,并支持v-html
解析返回数据。
1 . Vue中使用wangeditor富文本编辑器
1.1 下载wangeditor富文本编辑器NPM包
npm i wangeditor
1.2 引入并配置富文本编辑器(以下是以组件形式书写)
this.editor.config.uploadImgServer
配置服务器端地址,此处请自行填写(上传图片接口地址)
- 【注意】
uploadImgShowBase64(base64 格式)
和 uploadImgServer(上传图片到服务器)
两者不能同时使用!!!
- 【注意】使用弹窗组件时,在关闭弹窗事件中切勿直接清掉富文本
v-model
绑定的值,这样写的话会导致点击编辑第一次查看富文本是正确的,取消再进来值就没了,刷新下界面,值又有了,自己可以在富文本子组件mounted
区域打印value
这个值。
// 子组件
<template>
<div class="editor">
<div ref="toolbar" class="toolbar"></div>
<div ref="editor" class="text"></div>
</div>
</template>
<script>
import E from 'wangeditor'
import request from '@/utils/request'
export default {
name: 'editoritem',
data() {
return {
editor: null,
info_: null,
baseURL: request.defaults.baseURL, // 请求根路径
imgUrl: '',
}
},
model: {
prop: 'value',
event: 'change',
},
props: {
value: {
type: String,
default: '',
},
},
watch: {
isClear(val) {
// 触发清除文本域内容
if (val) {
this.editor.txt.clear()
this.info_ = null
}
},
},
mounted() {
this.seteditor()
this.editor.txt.html(this.value)
},
methods: {
// 富文本图片存在回显失败问题,建议自己在合适的区域由父组件代替触发
updateText(){
const dom = document.querySelector('.w-e-text')
dom.innerHTML=this.value
},
// 富文本图片存在清除失败问题,建议自己在合适的区域由父组件代替触发
clearText(){
const dom = document.querySelector('.w-e-text')
dom.innerHTML='"<p><br></p>"'
},
seteditor() {
// http://192.168.2.125:8080/admin/storage/create
this.editor = new E(this.$refs.toolbar, this.$refs.editor)
this.editor.config.uploadImgShowBase64 = false // base 64 存储图片
// this.editor.config.showLinkImg = false // 是否支持超链接图片 此时特别注意:showLinkImg属性与uploadImgServer属性不能同时存在,官方文档有说明
this.editor.config.uploadImgServer ='//file.xxxx.com/upload' // 配置服务器端地址 此处请自行填写
this.editor.config.uploadImgParamsWithUrl = true
this.editor.config.uploadFileName = 'file' // 后端接受上传文件的参数名
this.editor.config.debug = true // 开启debug模式
this.editor.config.withCredentials = true
// this.editor.config.uploadImgHeaders = {
// token: this.$store.state.user.token, // 设置请求头
// }
this.editor.config.uploadImgMaxSize = 3 * 1024 * 1024 // 将图片大小限制为 2M
this.editor.config.uploadImgMaxLength = 3 // 限制一次最多上传 3 张图片
this.editor.config.uploadImgTimeout = 3000 // 设置超时时间
this.editor.config.uploadImgHooks = {
before: function (xhr, editor, file) {
let formData = new FormData()
formData.append('file', file[0])
request({
method: 'post',
data: formData,
url: '/cmpp-manage/marketFileUpload/upload',
})
.then((res) => {
console.log(res);
if (res.code) {
this.imgUrl = res.data
}
})
.catch((err) => {
console.log('err: ', err)
})
},
customInsert: function (insertImgFn, result) {
setTimeout(() => {
console.log('图片链接: ', this.imgUrl)
insertImgFn(this.imgUrl)
}, 1000)
//customInsert是异步函数,一开始没有获取到this.imgUrl
// result 即服务端返回的接口
// insertImgFn 可把图片插入到编辑器,传入图片 src ,执行函数即可
// insertImgFn(process.env.VUE_APP_PICTURE + result.data)
// insertImgFn(this.imgUrl)
},
}
// 配置菜单
this.editor.config.menus = [
'head', // 标题
'bold', // 粗体
'fontSize', // 字号
'fontName', // 字体
'italic', // 斜体
'underline', // 下划线
'strikeThrough', // 删除线
'foreColor', // 文字颜色
'backColor', // 背景颜色
'link', // 插入链接
'list', // 列表
'justify', // 对齐方式
'quote', // 引用
'emoticon', // 表情
'image', // 插入图片
'table', // 表格
'video', // 插入视频
'code', // 插入代码
'undo', // 撤销
'redo', // 重复
'fullscreen', // 全屏
]
this.editor.config.onchange = (text) => {
this.info_ = text.trim() // 绑定当前逐渐地值
this.$emit('change', this.info_) // 将内容同步到父组件中
}
// 创建富文本编辑器
this.editor.create()
},
},
}
</script>
<style lang="css">
.editor {
width: 100%;
margin: 0 auto;
position: relative;
z-index: 0;
}
.toolbar {
border: 1px solid #ccc;
flex-wrap: wrap;
}
.text {
border: 1px solid #ccc;
min-height: 500px;
}
.w-e-text p {
margin: 0;
}
</style>
- 子组件有提到
updateText()
与clearText()
两个事件函数,需父组件在合适的时机自行触发,使用this.$refs.editorTxt.updateText()
与this.$refs.editorTxt.clearText()
即可在父组件事件中触发,具体原因是因为如果插入了图片关闭表单弹窗并不能重置掉字段(即使使用了element表单的清除),另外就是由于编辑时获取的数据是异步方式,会导致图片加载不出来,刷新下界面才能加载出来。
// 父组件
<template>
<div class="">
<button @click="testBlock()">点击</button>
<el-dialog
title="富文本弹窗"
:visible.sync="dialogVisible"
width="80%"
@close="close"
>
<el-form ref="ruleForm" :model="ruleForm" size="medium" label-width="150px">
<el-form-item label="富文本" prop="description">
<editor-bar
v-model="ruleForm.description"
:rows="2"
ref="editor"
></editor-bar>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取 消</el-button>
<el-button type="primary" @click="dialogVisible = false">确 定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import EditorBar from './views/Home.vue'
export default {
name: '',
props: {},
components: {
EditorBar
},
data () {
return {
dialogVisible:false,
ruleForm:{
description:""
}
}
},
computed: {},
watch: {},
created () {
},
methods: {
testBlock(){
// 假装这里有个根据id获取详情的接口
// 如果成功{
this.dialogVisible = true
this.$refs.editor.updateText()
//}
},
close(){
this.$refs.editor.clearText()
}
},
mounted () {},
}
</script>
<style scoped lang="less">
</style>
-
以上就可以直接使用wangeditor富文本编辑器啦
2 . wangeditor富文本编辑器生成的数据支持v-htm解析后点击图片跳转
- 此处虽然支持富文本内添加图片与跳转链接,但其实添加完他的数据标签格式是这样的:
description:"<p><img src="https://img2.baidu.com/it/u=3995308129,3997014782&fm=253&fmt=auto&app=138&f=JPEG?w=480&h=300" alt="1" data-href="http%3A%2F%2Flocalhost%3A8080%2F%23%2Flogin" style="max-width:100%;" contenteditable="false"/></p>"
- 会看到富文本把图片的跳转链接定义为了一个
data-href
的自定义属性,虽然富文本内还是支持点击跳转的,但想要v-html
支持解析就必须得给img
标签套一个a
标签
- 虽然使用Vue不建议操纵DOM元素,但官方未提供合适的API所以,此处会进行大量的DOM操纵。
- 以下解决办法为操纵DOM元素实现,此方法适用于很多场景(具体如何合理使用,看自己发挥咯),建议了解其原理写法,以下暂时先写核心写法,最后会贴上完整代码。
...
// wangeditor富文本编辑器插入网络图片的回调事件
this.editor.config.linkImgCallback = function (src, alt, href) {
// 由于获取的所有img标签会形成为 NodeList [img] 存储 此处使用Array.prototype.slice.call方法将其转换为数组
dom = Array.prototype.slice.call( document.querySelector(".w-e-text").querySelectorAll("img"));
// 判断dom元素是否存在,防止报错
if (dom) {
try {
// 对dom数组进行循环
dom.map((x, index) => {
// dom[index]此时是一个数组,根据索引找到对应图片属性对象,里面有很多关于这img标签的属性,outerHTML为其中img标签
if ((x = dom[index].outerHTML)) {
// 此处直接改变对图片属性里的img标签进行改变
// 由于wangeditor富文本编辑器会对跳转链接进行编码,此处用decodeURIComponent进行解码
// dom[index].dataset.href 为编码后的跳转链接
dom[index].outerHTML = `<a href=${decodeURIComponent(dom[index].dataset.href)}>${dom[index].outerHTML}</a>`;
}
});
} catch (e) {
console.log(e);
return e;
}
}
};
...
- 这是修改后的插入网络图片标签(由于是获取所有的图片标签,所以会为所有是图片的进行操纵,不会影响其他标签属性):
description:"<p><a href="http://localhost:8080/#/login"><img src="https://img2.baidu.com/it/u=3995308129,3997014782&fm=253&fmt=auto&app=138&f=JPEG?w=480&h=300" alt="1" data-href="http%3A%2F%2Flocalhost%3A8080%2F%23%2Flogin" style="max-width:100%;" contenteditable="false"/></a></p>"
- 本来几行代码就能解决的问题,虽然使用Vue不建议操纵DOM元素,但根据实际场景去书写合适的代码我觉得才是正解。
3 . 完整子组件代码(父组件不变)
// 子组件
<template>
<div class="editor">
<div ref="toolbar" class="toolbar"></div>
<div ref="editor" class="text"></div>
</div>
</template>
<script>
import E from 'wangeditor'
import request from '@/utils/request'
export default {
name: 'editoritem',
data() {
return {
editor: null,
info_: null,
baseURL: request.defaults.baseURL, // 请求根路径
imgUrl: '',
}
},
model: {
prop: 'value',
event: 'change',
},
props: {
value: {
type: String,
default: '',
},
},
watch: {
isClear(val) {
// 触发清除文本域内容
if (val) {
this.editor.txt.clear()
this.info_ = null
}
},
},
mounted() {
this.seteditor()
this.editor.txt.html(this.value)
},
methods: {
// 富文本图片存在回显失败问题,建议自己在合适的区域由父组件代替触发
updateText(){
const dom = document.querySelector('.w-e-text')
dom.innerHTML=this.value
},
// 富文本图片存在清除失败问题,建议自己在合适的区域由父组件代替触发
clearText(){
const dom = document.querySelector('.w-e-text')
dom.innerHTML='"<p><br></p>"'
},
seteditor() {
// http://192.168.2.125:8080/admin/storage/create
this.editor = new E(this.$refs.toolbar, this.$refs.editor)
// this.editor.config.uploadImgShowBase64 = false // base 64 存储图片
// this.editor.config.showLinkImg = false // 是否支持超链接图片 此时特别注意:showLinkImg属性与uploadImgServer属性不能同时存在,官方文档有说明
this.editor.config.uploadImgServer ='//file.xxxx.com/upload' // 配置服务器端地址 此处请自行填写
this.editor.config.uploadImgParamsWithUrl = true
this.editor.config.uploadFileName = 'file' // 后端接受上传文件的参数名
this.editor.config.debug = true // 开启debug模式
this.editor.config.withCredentials = true
// this.editor.config.uploadImgHeaders = {
// token: this.$store.state.user.token, // 设置请求头
// }
this.editor.config.uploadImgMaxSize = 3 * 1024 * 1024 // 将图片大小限制为 2M
this.editor.config.uploadImgMaxLength = 3 // 限制一次最多上传 3 张图片
this.editor.config.uploadImgTimeout = 3000 // 设置超时时间
this.editor.config.uploadImgHooks = {
before: function (xhr, editor, file) {
let formData = new FormData()
formData.append('file', file[0])
request({
method: 'post',
data: formData,
url: '/cmpp-manage/marketFileUpload/upload',
})
.then((res) => {
console.log(res);
if (res.code) {
this.imgUrl = res.data
}
})
.catch((err) => {
console.log('err: ', err)
})
},
customInsert: function (insertImgFn, result) {
setTimeout(() => {
console.log('图片链接: ', this.imgUrl)
insertImgFn(this.imgUrl)
}, 1000)
//customInsert是异步函数,一开始没有获取到this.imgUrl
// result 即服务端返回的接口
// insertImgFn 可把图片插入到编辑器,传入图片 src ,执行函数即可
// insertImgFn(process.env.VUE_APP_PICTURE + result.data)
// insertImgFn(this.imgUrl)
},
}
// 配置菜单
this.editor.config.menus = [
'head', // 标题
'bold', // 粗体
'fontSize', // 字号
'fontName', // 字体
'italic', // 斜体
'underline', // 下划线
'strikeThrough', // 删除线
'foreColor', // 文字颜色
'backColor', // 背景颜色
'link', // 插入链接
'list', // 列表
'justify', // 对齐方式
'quote', // 引用
'emoticon', // 表情
'image', // 插入图片
'table', // 表格
'video', // 插入视频
'code', // 插入代码
'undo', // 撤销
'redo', // 重复
'fullscreen', // 全屏
]
// wangeditor富文本编辑器插入网络图片的回调事件
this.editor.config.linkImgCallback = function (src, alt, href) {
// 由于获取的所有img标签会形成为 NodeList [img] 存储 此处使用Array.prototype.slice.call方法将其转换为数组
dom = Array.prototype.slice.call( document.querySelector(".w-e-text").querySelectorAll("img"));
// 判断dom元素是否存在,防止报错
if (dom) {
try {
// 对dom数组进行循环
dom.map((x, index) => {
// dom[index]此时是一个数组,根据索引找到对应图片属性对象,里面有很多关于这img标签的属性,outerHTML为其中img标签
if ((x = dom[index].outerHTML)) {
// 此处直接改变对图片属性里的img标签进行改变
// 由于wangeditor富文本编辑器会对跳转链接进行编码,此处用decodeURIComponent进行解码
// dom[index].dataset.href 为编码后的跳转链接
dom[index].outerHTML = `<a href=${decodeURIComponent(dom[index].dataset.href)}>${dom[index].outerHTML}</a>`;
}
});
} catch (e) {
console.log(e);
return e;
}
}
};
this.editor.config.onchange = (text) => {
this.info_ = text.trim() // 绑定当前逐渐地值
this.$emit('change', this.info_) // 将内容同步到父组件中
}
// 创建富文本编辑器
this.editor.create()
},
},
}
</script>
<style lang="css">
.editor {
width: 100%;
margin: 0 auto;
position: relative;
z-index: 0;
}
.toolbar {
border: 1px solid #ccc;
flex-wrap: wrap;
}
.text {
border: 1px solid #ccc;
min-height: 500px;
}
.w-e-text p {
margin: 0;
}
</style>
- 如果cv过去跑不动请留言哈,另外报某某数据未定义的话,麻烦看看自己是不是数据定义错了。有更好的想法或者建议也请留言哈,以前为个人见解写法
- 最后再提供下禁止输入纯空格的数据校验:
this.ruleForm.description==='<p><br></p>'||this.ruleForm.description.slice(3, this.ruleForm.description.length-4).replace(/ /gi, '').trim().length===0