一、解决问题
1、图片上传、视频上传
2、图片复制、图片拖拽和缩放
3、字体、字号
4、居中不生效
二、解决方式
1.组件内容 index.vue
<!--富文本编辑器-->
<template>
<div class="RichTextEditor-Wrap" v-loading="loading">
<quill-editor :content="content"
:options="editorOption"
class="ql-editor"
ref="myQuillEditor"
@change="onEditorChange($event)">
</quill-editor>
<!-- 图片上传组件辅助 :file-list="fileList"-->
<el-upload
v-show="false"
:show-file-list="false"
:name="uploadImgConfig.name"
:multiple="false"
:action="uploadImgConfig.uploadUrl"
:before-upload="onBeforeUpload"
:on-success="onSuccess"
:on-error="onError">
<button ref="myinput">上传文件</button>
</el-upload>
<!--视频上传组件辅助-->
<el-upload
v-show="false"
:show-file-list="false"
:action="uploadVideoConfig.uploadUrl"
accept="video/*"
:name="uploadVideoConfig.name"
:before-upload="onBeforeUploadVideo"
:on-success="onSuccessVideo"
:on-error="onErrorVideo"
:multiple="false">
<button ref="myvideo">上传文件</button>
</el-upload>
</div>
</template>
<script>
//main.js
import VueQuillEditor from 'vue-quill-editor'
Vue.use(VueQuillEditor);
//样式
import 'quill/dist/quill.core.css'
import 'quill/dist/quill.snow.css'
import 'quill/dist/quill.bubble.css'
import {quillEditor, Quill} from 'vue-quill-editor'
// 设置title
import {addQuillTitle} from './quill-title.js'
import ImageResize from 'quill-image-resize-module'; // 图片缩小放大插件
import {ImageDrop} from 'quill-image-drop-module'; // 拖动加载图片组件。
import {ImageExtend} from 'quill-image-paste-module'
Quill.register('modules/imageDrop', ImageDrop);
Quill.register('modules/imageResize', ImageResize);
Quill.register('modules/ImageExtend', ImageExtend)
//------------------------------------------------------工具栏配置项
// 自定义字体大小
let Size = Quill.import('attributors/style/size')
Size.whitelist = ['10px', '12px', '16px', '18px', '20px', '30px', '32px']
Quill.register(Size, true)
// 自定义字体类型
var fonts = ['SimSun', 'SimHei', 'Microsoft-YaHei', 'KaiTi', 'FangSong', 'Arial', 'sans-serif']
var Font = Quill.import('formats/font')
Font.whitelist = fonts
Quill.register(Font, true)
import Parchment from 'parchment';
//目的是修改富文本内容居中不了的问题
let config = {
scope: Parchment.Scope.BLOCK,
whitelist: ['right', 'center', 'justify']
};
let AlignStyle = new Parchment.Attributor.Style('align', 'text-align', config);
export { AlignStyle };
var Align = Quill.import('attributors/style/align');
Align.whitelist = ['right', 'center', 'justify'];
Quill.register(Align, true);
// 工具栏
const toolbarOptions = [
['bold', 'italic', 'underline', 'strike'], // toggled buttons
['blockquote', 'code-block'],
[{'header': 1}, {'header': 2}],
[{'list': 'ordered'}, {'list': 'bullet'}],
[{'script': 'sub'}, {'script': 'super'}], // superscript/subscript
[{'indent': '-1'}, {'indent': '+1'}], // outdent/indent
[{'direction': 'rtl'}],
[{ 'size': ['10px', '12px', false, '16px', '18px', '20px', '30px', '32px'] }], // 字体大小
// [{'size': fontSizeStyle.whitelist }], // [{'size': ['small', false, 'large', 'huge']}],//
[{'header': [1, 2, 3, 4, 5, 6, false]}],
// [{'font':fonts}],//[{'font': []}],
[{ 'font': [false, 'SimSun', 'SimHei', 'Microsoft-YaHei', 'KaiTi', 'FangSong', 'Arial', 'sans-serif'] }], // 字体种类
[{'color': []}, {'background': []}], // dropdown with defaults from theme
[{'align': []}],
[{'clean': '清除'}], // remove formatting button
// ['link', 'image', 'video']
['image', 'video']
]
export default {
name: 'RichTextEditor',
model: {
prop: 'content',
event: 'change'
},
components: {
quillEditor
},
props: {
content: { // 返回的html片段
type: String,
default: ''
},
uploadImgConfig: { // 图片上传配置 - 若不配置则使用quillEditor默认方式,即base64方式
type: Object,
default(){
return {
uploadUrl: 'http://192.168.1.177:1000/platform/exchangeForumInfoController/uploadFileFwb', // 图片上传地址
maxSize: 100, // 图片上传大小限制,默认不超过2M
name: 'file' // 图片上传字段
}
}
},
uploadVideoConfig: { // 视频上传配置
type: Object,
default(){
return {
uploadUrl: 'http://192.168.1.177:1000/platform/exchangeForumInfoController/uploadFileFwb', // 上传地址
maxSize: 100, // 图片上传大小限制,默认不超过2M
name: 'file' // 图片上传字段
}
}
}
},
data() {
let _self = this;
return {
loading: false, // 加载loading
editorOption: {
placeholder: '',
theme: 'snow', // or 'bubble'
modules: {
toolbar: {
container: toolbarOptions, // 工具栏
handlers: {
'video': function (value) {
_self.videoDialog.show = true;
},
'image': function () { // 劫持原来的图片点击按钮事件
document.querySelector('.quill-image-input').click()
}
}
},
imageDrop: true,
imageResize: {
displayStyles: {
backgroundColor: 'black',
border: 'none',
color: 'white'
},
modules: [ 'Resize', 'DisplaySize', 'Toolbar' ]
},
clipboard: {
// 粘贴过滤
matchers: [[Node.ELEMENT_NODE, this.HandleCustomMatcher]]
},
ImageExtend: {
loading: true,
name: 'file',
change: (xhr, FormData) => {
FormData.append('token', '123443')
},
action: 'http://192.168.1.177:1000/platform/exchangeForumInfoController/uploadFileFwb',
response: (res) => {
return res.redata;
},
}
}
},
// 图片上传变量
// fileList: [],
// 视频上传变量
// videoDialog: {
// show: false,
// videoLink: '',
// activeName: 'first'
// }
}
},
mounted () {
// 初始给编辑器设置title
addQuillTitle()
let toolbar = this.$refs['myQuillEditor'].quill.getModule('toolbar');
// 是否开启图片上传到服务器功能
if (this.uploadImgConfig.uploadUrl) {
toolbar.addHandler('image', this.addImageHandler);
}
// 是否开启视频上传到服务器功能
if (this.uploadVideoConfig.uploadUrl) {
toolbar.addHandler('video', this.addvideoHandler);
}
},
methods: {
HandleCustomMatcher (node, Delta) {
// 文字,从别处复制而来,革除自带款式,转为纯文本
if(node.src && node.src.indexOf('data:image/png') > -1){
Delta.ops = [];
return Delta;
}
let ops = []
let img=[]
Delta.ops.forEach(op => {
if (op.insert && typeof op.insert === 'string') {
ops.push({
insert: op.insert
})
}else if(op.insert && typeof op.insert.image === 'string'){
// console.log(op.insert);
}
})
Delta.ops = ops
return Delta
},
// 文本编辑
onEditorChange ({quill, html, text}) {
// console.log('editor change!', quill, html, text)
// console.log(html.replace(/<[^>]*>|/g, ''), 33333333)
this.$emit('update:content', html)
this.$emit('change', html)
},
hideLoading(){
this.loading = false
},
// --------- 图片上传相关 start ---------
addImageHandler(value){
if (value) {
// 触发input框选择图片文件
this.$refs['myinput'].click();
} else {
this.quill.format('image', false)
}
},
// 把已经上传的图片显示回富文本编辑框中
uploadSuccess (imgurl) {
let quill = this.$refs['myQuillEditor'].quill
let range = quill.getSelection()
let index = 0;
if (range == null) {
index = 0;
} else {
index = range.index; // 获取光标所在位置
}
// 插入
quill.insertEmbed(index, 'image', imgurl) // imgurl是服务器返回的图片链接地址
// 调整光标到最后
quill.setSelection(index + 1)
},
// el-文件上传组件
onBeforeUpload (file) {
this.loading = true
let acceptArr = ['image/jpg', 'image/jpeg', 'image/gif', 'image/png']
const isIMAGE = acceptArr.includes(file.type)
const isLt1M = file.size / 1024 / 1024 < this.uploadImgConfig.maxSize
if (!isIMAGE) {
this.hideLoading()
this.$message.error('只能插入图片格式!')
}
if (!isLt1M) {
this.hideLoading()
this.$message.error(`上传文件大小不能超过 ${this.uploadImgConfig.maxSize}MB!`)
}
return isLt1M && isIMAGE
},
// 文件上传成功时的钩子
onSuccess (response, file, fileList) { // ---- 注意这部分需要改为对应的返回格式
this.hideLoading()
if (response.recode =='200') {
this.uploadSuccess(response.redata)
} else {
this.$message.error('上传失败')
}
},
// 文件上传失败时的钩子
onError (file, fileList) {
this.hideLoading()
this.$message.error('上传失败')
},
// --------- 图片上传相关 end ---------
// --------- 视频上传相关 start ---------
addvideoHandler(value){
if (value) {
// 触发input框选择视频文件
this.$refs['myvideo'].click();
} else {
this.quill.format('video', false)
}
},
addVideoLink(videoLink) {
let quill = this.$refs['myQuillEditor'].quill
let range = quill.getSelection()
let index = 0;
if (range == null) {
index = 0;
} else {
index = range.index;
}
// 插入
quill.insertEmbed(index, 'video', videoLink)
// 调整光标到最后
quill.setSelection(index + 1)
},
// el-文件上传组件
onBeforeUploadVideo (file) {
this.loading = true
let acceptArr = ['video/mp4']
const isVideo = acceptArr.includes(file.type)
const isLt1M = file.size / 1024 / 1024 < this.uploadVideoConfig.maxSize
if (!isVideo) {
this.hideLoading()
this.$message.error('只能上传mp4格式文件!')
}
if (!isLt1M) {
this.hideLoading()
this.$message.error(`上传文件大小不能超过 ${this.uploadVideoConfig.maxSize}MB!`)
}
return isLt1M && isVideo
},
// 文件上传成功时的钩子
onSuccessVideo (response, file, fileList) { // ---- 注意这部分需要改为对应的返回格式
this.hideLoading()
if (response.recode =='200') {
console.log(response.redata);
this.addVideoLink(response.redata)
} else {
this.$message.error('上传失败')
}
},
// 文件上传失败时的钩子
onErrorVideo (file, fileList) {
this.hideLoading()
this.$message.error('上传失败')
},
// --------- 视频上传相关 end ---------
}
}
</script>
<style>
.RichTextEditor-Wrap .ql-container {
height: 300px;
}
.RichTextEditor-Wrap .ql-editor {
padding: 0;
white-space:normal !important;
}
.RichTextEditor-Wrap .ql-tooltip {
left: 5px !important;
}
.editor {
line-height: normal !important;
height: 300px;
}
.SizeTiShi{
font-size: 12px;
color: #999999;
text-align: right;
/* margin-right: 20px; */
/* margin-top: 60px; */
}
.ql-snow .ql-tooltip[data-mode=link]::before {
content: "请输入链接地址:";
}
.ql-snow .ql-tooltip.ql-editing a.ql-action::after {
border-right: 0px;
content: '保存';
padding-right: 0px;
}
.ql-snow .ql-tooltip[data-mode=video]::before {
content: "请输入视频地址:";
}
.ql-snow .ql-picker.ql-size .ql-picker-label::before,
.ql-snow .ql-picker.ql-size .ql-picker-item::before {
content: '14px' !important;
font-size: 14px;
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='10px']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='10px']::before {
content: '10px' !important;
font-size: 10px;
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='12px']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='12px']::before {
content: '12px' !important;
font-size: 12px;
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='16px']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='16px']::before {
content: '16px' !important;
font-size: 16px;
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='18px']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='18px']::before {
content: '18px' !important;
font-size: 18px;
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='20px']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='20px']::before {
content: '20px' !important;
font-size: 20px;
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='30px']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='30px']::before {
content: '30px' !important;
font-size: 30px;
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='32px']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='32px']::before {
content: '32px' !important;
font-size: 32px;
}
.ql-snow .ql-picker.ql-header .ql-picker-label::before,
.ql-snow .ql-picker.ql-header .ql-picker-item::before {
content: '文本' !important;
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="1"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]::before {
content: '标题1' !important;
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="2"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]::before {
content: '标题2' !important;
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="3"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]::before {
content: '标题3' !important;
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="4"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]::before {
content: '标题4' !important;
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="5"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]::before {
content: '标题5' !important;
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="6"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]::before {
content: '标题6' !important;
}
.ql-snow .ql-picker.ql-font .ql-picker-label::before,
.ql-snow .ql-picker.ql-font .ql-picker-item::before {
content: '标准字体' !important;
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=serif]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=serif]::before {
content: '衬线字体' !important;
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=monospace]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=monospace]::before {
content: '等宽字体' !important;
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=SimSun]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=SimSun]::before {
content: "宋体" !important;
font-family: "SimSun";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=SimHei]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=SimHei]::before {
content: "黑体" !important;
font-family: "SimHei";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=Microsoft-YaHei]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=Microsoft-YaHei]::before {
content: "微软雅黑" !important;
font-family: "Microsoft YaHei";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=KaiTi]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=KaiTi]::before {
content: "楷体" !important;
font-family: "KaiTi";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=FangSong]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=FangSong]::before {
content: "仿宋" !important;
font-family: "FangSong";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=Arial]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=Arial]::before {
content: "Arial" !important;
font-family: "Arial";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=Times-New-Roman]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=Times-New-Roman]::before {
content: "Times New Roman" !important;
font-family: "Times New Roman";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=sans-serif]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=sans-serif]::before {
content: "sans-serif" !important;
font-family: "sans-serif";
}
.ql-font-SimSun {
font-family: "SimSun";
}
.ql-font-SimHei {
font-family: "SimHei";
}
.ql-font-Microsoft-YaHei {
font-family: "Microsoft YaHei";
}
.ql-font-KaiTi {
font-family: "KaiTi";
}
.ql-font-FangSong {
font-family: "FangSong";
}
.ql-font-Arial {
font-family: "Arial";
}
.ql-font-Times-New-Roman {
font-family: "Times New Roman";
}
.ql-font-sans-serif {
font-family: "sans-serif";
}
</style>
2.富文本框菜单title-----quill-title.js
const titleConfig = {
'ql-bold': '加粗',
'ql-font': '字体',
'ql-code': '插入代码',
'ql-italic': '斜体',
'ql-link': '添加链接',
'ql-color': '字体颜色',
'ql-background': '背景颜色',
'ql-size': '字体大小',
'ql-strike': '删除线',
'ql-script': '上标/下标',
'ql-underline': '下划线',
'ql-blockquote': '引用',
'ql-header': '标题',
'ql-indent': '缩进',
'ql-list': '列表',
'ql-align': '文本对齐',
'ql-direction': '文本方向',
'ql-code-block': '代码块',
'ql-formula': '公式',
'ql-image': '图片',
'ql-video': '视频',
'ql-clean': '清除字体样式'
}
export function addQuillTitle () {
const oToolBar = document.querySelector('.ql-toolbar')
const aButton = oToolBar.querySelectorAll('button')
const aSelect = oToolBar.querySelectorAll('select')
aButton.forEach(function (item) {
if (item.className === 'ql-script') {
item.value === 'sub' ? item.title = '下标' : item.title = '上标'
} else if (item.className === 'ql-indent') {
item.value === '+1' ? item.title = '向右缩进' : item.title = '向左缩进'
} else {
item.title = titleConfig[item.className]
}
})
// 字体颜色和字体背景特殊处理,两个在相同的盒子
aSelect.forEach(function (item) {
if (item.className.indexOf('ql-background') > -1) {
item.previousSibling.title = titleConfig['ql-background']
} else if (item.className.indexOf('ql-color') > -1) {
item.previousSibling.title = titleConfig['ql-color']
} else {
item.parentNode.title = titleConfig[item.className]
}
})
}
3.使用组件
<el-form-item label="内容" v-if="dialogVisible">
<quill-editor @change="editerChange"></quill-editor>
</el-form-item>
4.参考
vue-quill-editor 富文本编辑器封装,可上传图片,视频(附源码) - 简书https://www.jianshu.com/p/5fba48c07debvue-quill-editor的详细配置与使用 - 简书最近工作中需求使用一款富文本编辑器,经过再三比较选择了vue-quill-editor,之所以选择vue-quill-editor,是看上了它的轻量以及外观简洁的优势。打开官...https://www.jianshu.com/p/efcedcabb192vue-quill-editor 样式不生效的解决方法_西坦的博客-CSDN博客在main.js引入import 'quill/dist/quill.snow.css'在需要展示的页面中用如下元素包裹即可<div class="ql-container ql-snow"> <div class="ql-editor" v-html="list.content"></div>...
https://blog.csdn.net/qq_41144623/article/details/104876294vue-quill-editor编辑器文本不居中问题处理_吃范范的博客-CSDN博客一、问题出现的原因 由于 使用这个样式 class="ql-align-center",但是在app中显示时没有这个样式,所以就不居中,我们可以修改一下,改成 style="text-align: center;"这样就能完美显示了二、修改<script>import Quill from 'quill';import Parchment from 'parchment';//目的是修改富文本内容居中不了的问题let config = { scope: P...
https://blog.csdn.net/fanhuiixa/article/details/118540311
5.追加
解决问题:编辑给富文本赋值样式丢失(原因:富文本保护机制会自动过滤样式)
解决方式:
<quill-editor ref='myQuill' @change="editerChange"></quill-editor>
//调用组件的方法
this.$nextTick(e=>{
this.$refs.myQuill.setContent(content);
})
//组件中给元素赋值
setContent(content){
this.$refs.myQuillEditor.$el.lastChild.childNodes[0].innerHTML=content;
},