<template> <div> <div class="editor" ref="editor" :style="styles" @change="onEditorChange($event)"></div> <!-- 图片上传组件辅助--> <el-upload v-show="false" ref="uploadImg" class="avatar-uploader" :action="imgUploadUrl" name="img" :headers="header" :data="imgData" :show-file-list="false" :on-success="uploadSuccess" :on-error="uploadError" :before-upload="beforeUpload"> </el-upload> <el-upload v-show="false" ref="uploadVideo" class="video-uploader" :action="serverUrl" :headers="header" :data="videoData" :show-file-list="false" :before-upload="beforeVideoUpload" :on-success="uploadVideoSuccess" :on-error="uploadVideoError"> </el-upload> </div> </template> <script> import Quill from "quill"; import "quill/dist/quill.core.css"; import "quill/dist/quill.snow.css"; import "quill/dist/quill.bubble.css"; import { uploadFile } from "@/api/common/common"; import { getToken } from '@/utils/auth' export default { name: "Editor", props: { /* 编辑器的内容 */ value: { type: String, default: "", }, /* 高度 */ height: { type: Number, default: null, }, /* 最小高度 */ minHeight: { type: Number, default: null, }, //参数 params: { type: Object, default: null, } }, data() { return { Quill: null, currentValue: "", content: null, serverUrl: process.env.VUE_APP_BASE_API+'/common/upload', imgUploadUrl: process.env.VUE_APP_BASE_API+'/common/uploadImg', header:{ 'Authorization': 'Bearer ' + getToken() }, videoData:{ modName: this.params.modName+'video' }, imgData:{ modName: this.params.modName+'img' }, options: { theme: "snow", bounds: document.body, debug: "warn", modules: { // 工具栏配置 toolbar: { container:[ ['sourceEditor'], ["bold", "italic", "underline", "strike"], // 加粗 斜体 下划线 删除线 ["blockquote", "code-block"], // 引用 代码块 [{ list: "ordered" }, { list: "bullet" }], // 有序、无序列表 [{ indent: "-1" }, { indent: "+1" }], // 缩进 [{ size: ["small", false, "large", "huge"] }], // 字体大小 [{ header: [1, 2, 3, 4, 5, 6, false] }], // 标题 [{ color: [] }, { background: [] }], // 字体颜色、字体背景颜色 [{ align: [] }], // 对齐方式 ["clean"], // 清除文本格式 ["link", "image", "video"], // 链接、图片、视频 ], handlers: { shadeBox: null, source: false, // 添加工具方法 sourceEditor: function () { this.source = true; //开启源码编辑 console.log("开启源码编辑") // alert('我新添加的工具方法'); const container = this.container; const firstChild = container.nextElementSibling.firstChild; // 在第一次点击源码编辑的时候,会在整个工具条上加一个div,层级比工具条高,再次点击工具条任意位置,就会退出源码编辑。可以在下面cssText里面加个背景颜色看看效果。 if (!this.shadeBox) { let shadeBox = this.shadeBox = document.createElement('div'); shadeBox.style.cssText = 'position:absolute; top:0; left:0; width:100%; height:100%; cursor:pointer'; container.style.position = 'relative'; container.appendChild(shadeBox); firstChild.innerText = firstChild.innerHTML; shadeBox.addEventListener('click', function () { this.source = false; //关闭源码编辑 console.log("关闭源码编辑") this.style.display = 'none'; firstChild.innerHTML = firstChild.innerText.trim(); }, false); } else { this.shadeBox.style.display = 'block'; firstChild.innerText = firstChild.innerHTML; } // this.$emit("isSource",this.source); //父组件触发自定义事件,是否开启源码编辑 }, 'image': function (value) { if (value) { // 触发input框选择图片文件 document.querySelector('.avatar-uploader input').click() } else { this.quill.format('image', false); } }, 'video': function (value) { if (value) { // 触发input框选择图片文件 document.querySelector('.video-uploader input').click() } else { this.quill.format('video', false); } } } } }, placeholder: "请输入内容", readOnly: false, initButton: function () { // 样式随便改 const sourceEditorButton = document.querySelector('.ql-sourceEditor'); sourceEditorButton.style.cssText = 'font-size:18px'; // 加了elementui的icon sourceEditorButton.classList.add('el-icon-edit-outline'); // 鼠标放上去显示的提示文字 sourceEditorButton.title = '源码编辑'; }, register(q){ //注册标签(因为在富文本编辑器中是没有div,table等标签的,需要自己去注册自己需要的标签) class div extends q.import('blots/block/embed') {} class table extends q.import('blots/block/embed') {} class tr extends q.import('blots/block/embed') {} class td extends q.import('blots/block/embed') {} div.blotName =div.tagName='div'; table.blotName =table.tagName='table'; tr.blotName =tr.tagName='tr'; td.blotName =td.tagName='td'; q.register(div); q.register(table); q.register(tr); q.register(td); }, }, }; }, computed: { styles() { let style = {}; if (this.minHeight) { style.minHeight = `${this.minHeight}px`; } if (this.height) { style.height = `${this.height}px`; } return style; }, }, watch: { value: { handler(val) { if (val !== this.currentValue) { this.currentValue = val === null ? "" : val; if (this.Quill) { this.Quill.pasteHTML(this.value); } } }, immediate: true, }, }, mounted() { this.init(); this.options.register(Quill); this.options.initButton(); }, beforeDestroy() { this.Quill = null; }, methods: { init() { const editor = this.$refs.editor; this.Quill = new Quill(editor, this.options); this.Quill.pasteHTML(this.currentValue); this.Quill.on("text-change", (delta, oldDelta, source) => { const html = this.$refs.editor.children[0].innerHTML; const text = this.Quill.getText(); const quill = this.Quill; this.currentValue = html; this.$emit("input", html); this.$emit("on-change", { html, text, quill }); }); this.Quill.on("text-change", (delta, oldDelta, source) => { this.$emit("on-text-change", delta, oldDelta, source); }); this.Quill.on("selection-change", (range, oldRange, source) => { this.$emit("on-selection-change", range, oldRange, source); }); this.Quill.on("editor-change", (eventName, ...args) => { this.$emit("on-editor-change", eventName, ...args); }); }, onEditorChange({editor, html, text}) {//内容改变事件 console.log("---内容改变事件---"); this.content = html // console.log(html) }, // 富文本图片上传前 beforeUpload(file) { // 显示loading动画 const index = ['image/jpg','image/jpeg','image/gif','image/png'].findIndex(y => Object.is(file.type, y)); const isLt2M = file.size / 1024 / 1024 < 2; var isJPG = true; if (index < 0) { isJPG = false; this.$message.error('上传头像图片只能是 jpg/gif/png 格式!'); } if (!isLt2M) { this.$message.error('上传头像图片大小不能超过 2MB!'); } return isJPG && isLt2M; }, uploadSuccess(res, file) { // res为图片服务器返回的数据 // 获取富文本组件实例 console.log(res); let quill = this.Quill; // 如果上传成功 if (res.code == 200 ) { // 获取光标所在位置 let length = quill.getSelection().index; // 插入图片 res.url为服务器返回的图片地址 var fileName = res.fileName.replace('//', '/') quill.insertEmbed(length, 'image',process.env.VUE_APP_BASE_API+ fileName); // 调整光标到最后 quill.setSelection(length + 1) } else { this.$message.error('图片插入失败') } }, uploadFile(){ return process.env.VUE_APP_BASE_API+'/common/uploadImg' }, // 富文本图片上传失败 uploadError() { this.$message.error('图片插入失败') }, //--------视频上传----------- beforeVideoUpload(file){ const size = file.size / 1024 / 1024 < 20; if (['video/MP4','video/mp4', 'video/ogg', 'video/webm', 'video/WebM'].indexOf(file.type) == -1) { this.$message.error('视频仅支持 MP4,WebM,Ogg 格式'); return false; } if (!size) { this.$message.error('上传视频大小不能超过 20MB'); return false; } }, uploadVideoSuccess(res, file){ // res为服务器返回的数据 // 获取富文本组件实例 console.log(res); let quill = this.Quill; // 如果上传成功 if (res.code == 200 ) { // 获取光标所在位置 let length = quill.getSelection().index; // 插入图片 res.url为服务器返回的图片地址 var fileName = res.fileName.replace('//', '/') quill.insertEmbed(length, 'video',process.env.VUE_APP_BASE_API+ fileName); // 调整光标到最后 quill.setSelection(length + 1) } else { this.$message.error('视频插入失败') } }, uploadVideoError(){ this.$message.error('视频插入失败') } }, }; </script> <style> .editor { white-space: pre-wrap!important; line-height: normal !important; } .quill-img { display: none; } .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; } .quill-video { display: none; } /* .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"; } .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="small"]::before, .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="small"]::before { content: "10px"; } .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="large"]::before, .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="large"]::before { content: "18px"; } .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="huge"]::before, .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="huge"]::before { content: "32px"; } .ql-snow .ql-picker.ql-header .ql-picker-label::before, .ql-snow .ql-picker.ql-header .ql-picker-item::before { content: "文本"; } .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"; } .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"; } .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"; } .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"; } .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"; } .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"; } .ql-snow .ql-picker.ql-font .ql-picker-label::before, .ql-snow .ql-picker.ql-font .ql-picker-item::before { content: "标准字体"; } .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: "衬线字体"; } .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: "等宽字体"; } /* 编辑器内部出现滚动条 */ .ql-container{ overflow-y:auto; height:8rem!important; } /*滚动条整体样式*/ .ql-container ::-webkit-scrollbar{ width: 10px;/*竖向滚动条的宽度*/ height: 10px;/*横向滚动条的高度*/ } .ql-container ::-webkit-scrollbar-thumb{/*滚动条里面的小方块*/ background: #666666; border-radius: 5px; } .ql-container ::-webkit-scrollbar-track{/*滚动条轨道的样式*/ background: #ccc; border-radius: 5px; } </style>