富文本编辑器
一、富文本编辑器element-tiptap
Section titled “一、富文本编辑器element-tiptap”element-tiptap 报错的话
- 找到目录
**node_modules/tiptap-extensions/node-modules**,把最后的node-modules目录名字修改为node-modules–,就解决了! - 这样的话自动化部署就有问题
二、vue-quill-editor
Section titled “二、vue-quill-editor”- vue3 使用
vueup/vue-quill
图片上传还没做过,用的时候可以查看下文档
#安装vue-quill-editornpm install vue-quill-editor --save
#安装 "拖动图片" 和 "修改图片大小"、"图片上传到服务器" 的插件npm install quill-image-resize-module quill-image-drop-module quill-image-extend-module --save2、webpack配置
Section titled “2、webpack配置”不配置的话会报错 :Cannot read property ‘imports’ of undefined”
- 配置一:
这个好像还有点问题,用的时候在查下配置。
chainWebpack: config => { config.plugin('provide').use(ProvidePlugin, [{ 'window.Quill': 'quill' }]);},- 配置二:
configureWebpack: { plugins: [ new webpack.ProvidePlugin({ 'window.Quill': 'quill/dist/quill.js', 'Quill': 'quill/dist/quill.js' }) ] },3、完整代码
Section titled “3、完整代码”<template> <div class="container" ><!-- <el-button @click="uploadCallBack">uploadCallBack</el-button>--> <template> <!-- bidirectional data binding(双向数据绑定) --> <quill-editor v-model="content" v-max-window ref="myQuillEditor" :options="editorOption" :disabled="disabled" @change="onEditorChange($event)" > </quill-editor>
<!-- Or manually control the data synchronization(或手动控制数据流) --><!-- <quill-editor :content="content" :options="editorOption" @blur="onEditorBlur($event)" @focus="onEditorFocus($event)" @ready="onEditorReady($event)" @change="onEditorChange($event)"> </quill-editor>--> <uploadCpn v-if="uploadVisible" ref="uploadRef" @refreshDataList="uploadCallBack"></uploadCpn> </template> </div></template>
<script>//导入自定义video插件,这个js代码在下面import Video from "./quill-video";import 'quill/dist/quill.core.css'import 'quill/dist/quill.snow.css'import 'quill/dist/quill.bubble.css'import { quillEditor } from 'vue-quill-editor'import Quill from "quill";import ImageResize from "quill-image-resize-module"; // 修改图片大小import { ImageDrop } from 'quill-image-drop-module'; // 拖动加载图片组件。//container 所有的工具栏import {container, ImageExtend, QuillWatch} from 'quill-image-extend-module'import Cookies from "js-cookie"; //上传图片Quill.register("modules/imageResize", ImageResize); // 注册Quill.register('modules/imageDrop', ImageDrop);//图片上传到服务器的插件Quill.register('modules/ImageExtend', ImageExtend)//更换iframQuill.register(Video, true);export default { emits: ["updateTextContent"], props: { showContent: { type: String, default: "" }, removeToolOption: { type: Array, default: () => [] }, quillHeight: { type: String, default: "400px" }, quillWidth: { type: String, default: "800px" } }, components: { quillEditor }, data () { return { editorOption: { // some quill options modules: { toolbar: { container:[['bold', 'italic', 'underline', 'strike'], ['blockquote', 'code-block'], [{'header': 1}, {'header': 2}], [{'list': 'ordered'}, {'list': 'bullet'}], [{'script': 'sub'}, {'script': 'super'}], [{'indent': '-1'}, {'indent': '+1'}], [{'direction': 'rtl'}], [{'size': ['small', false, 'large', 'huge']}], [{'header': [1, 2, 3, 4, 5, 6, false]}], [{'color': []}, {'background': []}], [{'font': []}], [{'align': []}], ['clean'], ['link', 'image'/*,'video'*/]], handlers: { 'image': function () { QuillWatch.emit(this.quill.id) }, 'video': (e) => { //打开上传视频的插件 this.uploadVisible = true; this.$nextTick(() => { this.$refs.uploadRef.visible = true this.$refs.uploadRef.init() }) } } }, history: { delay: 1000, maxStack: 50, userOnly: false }, //上传插件配置 ImageExtend: { name: 'file', size: 10, // 单位为M, 1M = 1024KB action: "", headers: (xhr) => { }, response: (res) => {
// console.log(res.data.src, "success"); // 获取富文本组件实例 let quill = this.$refs.myQuillEditor.quill // 如果上传成功 if (res.code === 0) {
let index = 0 //这里需要$nextTick函数否则光标为0 this.$nextTick(e => { // 获取光标所在位置 index = quill.getSelection().index; console.log(index); // 插入图片,res为服务器返回的图片链接地址 quill.insertEmbed(index, 'image', res.data.src) // 调整光标到最后 quill.setSelection(index + 1) })
} else { // 提示信息,需引入Message this.$message.error('图片插入失败!') }
return res } }, imageDrop: true, imageResize: { displayStyles: { backgroundColor: 'black', border: 'none', color: 'white' }, modules: [ 'Resize', 'DisplaySize', 'Toolbar' ] } }, placeholder: '输入内容........' } } }, // manually control the data synchronization // 如果需要手动控制数据同步,父组件需要显式地处理changed事件 methods: { //上传视频成功回传 uploadCallBack({file, width, height, position}) { let quill = this.$refs.myQuillEditor.quill; quill.focus() // 如果上传成功 if (file.response.code === 0) { // 获取光标所在位置 let index = 0 this.$nextTick(e => { index = quill.getSelection().index; console.log(width, height, "宽高"); console.log(quill.insertEmbed(index, "video", { url: file.response.data.src, controls: "controls", width: width+"px", height: height+"px", style: position }), "视频元素"); quill.setSelection(index + 1); }) console.log(this.content) } else { this.$message.error("视频插入失败"); } }, //失焦 onEditorBlur(quill) { console.log('editor blur!', quill) console.log(this.content); }, //聚焦 onEditorFocus(quill) { console.log('editor focus!', quill) }, //渲染 onEditorReady(quill) { console.log('editor ready!', quill) }, //被修改 onEditorChange({ quill, html, text }) { console.log('editor change!', quill, html, text) this.content = html }, initOption() { this.editorOption.modules.ImageExtend.action = `${window.SITE_CONFIG['apiURL']}/sys/oss/upload?token=${Cookies.get('token')}` if (this.removeToolOption.length) { this.editorOption.modules.toolbar.container[0].splice(this.editorOption.modules.toolbar[0].find((val, idx) => val === this.removeToolOption[idx] ), 1) } } }, computed: { editor() { return this.$refs.myQuillEditor.quill }, content: { get() { return this.showContent }, set(value) { this.$emit("updateTextContent", value) } } }, created() { this.initOption() }}
</script>
<style scoped>::v-deep .ql-editor{ height: v-bind(quillHeight); width: v-bind(quillWidth);}::v-deep .ql-container { height: v-bind(quillHeight); width: v-bind(quillWidth);
}::v-deep .ql-toolbar { width: v-bind(quillWidth);
}</style>4、自定义全屏
Section titled “4、自定义全屏”自定义插件
const domList = [ { dom: `<svg t="1637824425355" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10463"><path d="M243.2 780.8v-179.2H153.6v179.2c0 49.28 40.32 89.6 89.6 89.6h179.2v-89.6H243.2zM780.8 153.6h-179.2v89.6h179.2v179.2h89.6V243.2c0-49.28-40.32-89.6-89.6-89.6zM243.2 243.2h179.2V153.6H243.2c-49.28 0-89.6 40.32-89.6 89.6v179.2h89.6V243.2z m537.6 537.6h-179.2v89.6h179.2c49.28 0 89.6-40.32 89.6-89.6v-179.2h-89.6v179.2z" p-id="10464" fill="#000000"></path></svg>`, tit: "最大化", }, { dom: `<svg t="1637824296192" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6336"><path d="M341.065143 910.189714v-146.285714c0-53.686857-43.885714-97.572571-97.572572-97.572571h-146.285714a48.786286 48.786286 0 0 0 0 97.499428h146.285714v146.285714a48.786286 48.786286 0 1 0 97.499429 0z m-292.571429-617.910857c0 26.916571 21.796571 48.786286 48.713143 48.786286h146.285714c53.686857 0 97.572571-43.885714 97.572572-97.572572v-146.285714a48.786286 48.786286 0 0 0-97.499429 0v146.285714h-146.285714a48.786286 48.786286 0 0 0-48.786286 48.786286z m910.409143 0a48.786286 48.786286 0 0 0-48.713143-48.786286h-146.285714v-146.285714a48.786286 48.786286 0 1 0-97.499429 0v146.285714c0 53.686857 43.885714 97.572571 97.499429 97.572572h146.285714a48.786286 48.786286 0 0 0 48.713143-48.786286z m0 422.765714a48.786286 48.786286 0 0 0-48.713143-48.713142h-146.285714c-53.686857 0-97.572571 43.885714-97.572571 97.572571v146.285714a48.786286 48.786286 0 1 0 97.499428 0v-146.285714h146.285714a48.786286 48.786286 0 0 0 48.786286-48.786286z" fill="#000000" p-id="6337"></path></svg>`, tit: "还原", },];
/**@ bing *2021-11-25 15:58:21 * v-maxWindow: 只针对 vue-quill-editor组件 最大化最小化 */
export function quill_scale(Vue) { Vue.directive("maxWindow", { //属性名称maxWindow,前面加v- 使用 bind(el, binding, vnode, oldVnode) { setTimeout(() => { // 获取控制条 let dialogHeaderEl = el.querySelector(".ql-toolbar"); // 获取内容区 let qlContainer = el.querySelector(".ql-container")
// 全屏按钮 let dom1 = document.createElement("span"); dom1.className = "ql-formats"; dom1.style.display = 'inline-block' dom1.innerHTML = `<button type="button" class="ql-clean"> ${domList[0].dom} </button>` dialogHeaderEl.appendChild(dom1); // 取消全屏 let dom2 = document.createElement("span"); dom2.className = "ql-formats"; dom2.style.display = 'none' dom2.innerHTML = `<button type="button" class="ql-clean"> ${domList[1].dom} </button>` dialogHeaderEl.appendChild(dom2); const ql_editor_el = el.getElementsByClassName("ql-editor")[0] const ql_toolbar_el = el.getElementsByClassName("ql-toolbar")[0] const ql_container_el = el.getElementsByClassName("ql-container")[0] const tempWidth = ql_editor_el.offsetWidth const tempHeight = ql_editor_el.offsetHeight /*margin-left: 50%; transform: translateX(-50%)*/
dom1.onclick = () => { //自定义 ql_editor_el.style.height = '90vh'; ql_container_el.style.marginLeft = '50%' ql_toolbar_el.style.marginLeft = '50%' ql_container_el.style.transform = 'translateX(-50%)' ql_toolbar_el.style.transform = 'translateX(-50%)'
el.style.width = 100 + "vw"; el.style.height = 100 + "vh"; el.style.position = "fixed"; el.style.top = 0; el.style.left = 0; el.style.zIndex = 1500; el.style.background = "white"; // 要给内容区的高度减去控制条的高度,不然会有遮挡 qlContainer.style.height = 'calc(100% - 48px)'
dom1.style.display = "none"; dom2.style.display = "inline-block"; }; dom2.onclick = () => { //自定义样式 ql_editor_el.style.height = tempHeight+"px" ql_container_el.style.marginLeft = '' ql_toolbar_el.style.marginLeft = '' ql_container_el.style.transform = '' ql_toolbar_el.style.transform = ''
el.style.width = "auto"; el.style.height = "auto"; el.style.position = "inherit"; el.style.zIndex = 0;
dom1.style.display = "inline-block"; dom2.style.display = "none"; };
}, 0); }, });}5、设置文本框的高度
Section titled “5、设置文本框的高度”修改指定的两个class就好了
//记得要穿透才可以::v-deep .ql-editor{ height: v-bind(quillHeight);}::v-deep .ql-container { height: v-bind(quillHeight);}三、Ckeditor
Section titled “三、Ckeditor”<style> /*修改文本输入框的大小*/ .intro_editor_area .ck-editor__editable[role="textbox"] { height: 200px; }
/*设置放大后的全屏*/ .ck_fullScreen .ck-editor__editable[role="textbox"] { height: 90vh; }
/*放大后添加这个class*/ .ck_fullScreen { position: fixed; top: 0; bottom: 0; right: 0; left: 0; margin: auto; z-index: 9999; background-color: #fff; max-width: 100%; }</style>
<body> <div class="col-sm-8 intro_editor_area pt-2"> <a type="button" class="btn btn-default fullScreen mb-2" data-flag>全屏 <div id="desc_editor">#(product.desc_content ??)</div> <input hidden type="text" id="desc_editor_content" name="product.desc_content" class="form-control" placeholder=""> </div></body>
<script src="#(CPATH)/static/components/ckeditor5/ckeditor.js"></script><script>
//切换全屏 $(".fullScreen").on("click", function (e) { const flag = this.dataset.flag === "false" ? false : true; const el = this.parentElement if (flag) { this.textContent = "缩小" el.classList.add("ck_fullScreen") } else { this.textContent = "全屏" el.classList.remove("ck_fullScreen") } this.dataset.flag = !flag })
//初始化编辑器 initDescCkEdtior("#desc_editor") function initDescCkEdtior(selector) { ClassicEditor.create(document.querySelector(selector), { fontSize: { options: [ 9, 11, 13, 'default', 17, 19, 21, 22, 24, 26, 28, 30, 36, 48, ] }, fontFamily: { options: [ 'default', '宋体', '黑体', '仿宋, 仿宋_GB2312', '楷体,楷体_GB2312', '幼圆', '微软雅黑', '宋体隶书,隶书', 'Arial', 'Helvetica', 'sans-serif', 'Times New Roman, Times, serif', 'Verdana',
], supportAllValues: true }, toolbar: { items: [ 'heading', '|', 'bold', 'italic', 'underline', 'link', 'code', 'bulletedList', 'numberedList', '|', 'fontFamily', 'fontSize', 'fontColor', 'fontBackgroundColor', 'outdent', 'indent', 'alignment', 'removeFormat', '|', 'blockQuote', 'imageInsert', // 'videoUpload', 'insertTable', 'codeBlock', '|', 'undo', 'redo', '|', 'sourceEditing' ] }, simpleUpload: { uploadUrl: getContextPath() + '/commons/ckeditor5/upload', }, language: 'zh-cn', image: { toolbar: [ 'imageTextAlternative', 'imageStyle:alignBlockLeft', 'imageStyle:alignBlockRight', 'imageStyle:block', 'imageStyle:inline', 'imageStyle:side', 'imageStyle:alignLeft', 'imageStyle:alignRight',
] }, table: { contentToolbar: [ 'tableColumn', 'tableRow', 'mergeTableCells', 'tableProperties', 'tableCellProperties' ] }, htmlSupport: { allow: [{ name: /.*/, attributes: true, classes: true, styles: true }] // disallow: [ /* HTML features to disallow */ ] } }) .then(editor => { //监听数据修改 editor.model.document.on( 'change:data', e => { $("#desc_editor_content").val(editor.getData()) }); }) .catch(error => { console.log(error); }); }</script>四、Vditor Markdown
Section titled “四、Vditor Markdown”<script src="#(CPATH)/static/components/vditor/dist/index.min.js"></script><link href="#(CPATH)/static/components/vditor/dist/index.css" rel="stylesheet">//初始化编辑器<script>function initCkEdtior(selector) { ClassicEditor .create(document.querySelector(selector), { fontSize: { options: [ 9, 11, 13, 'default', 17, 19, 21, 22, 24, 26, 28, 30, 36, 48, ] }, fontFamily: { options: [ 'default', '宋体', '黑体', '仿宋, 仿宋_GB2312', '楷体,楷体_GB2312', '幼圆', '微软雅黑', '宋体隶书,隶书', 'Arial', 'Helvetica', 'sans-serif', 'Times New Roman, Times, serif', 'Verdana',
], supportAllValues: true }, toolbar: { items: [ 'heading', '|', 'bold', 'italic', 'underline', 'link', 'code', 'bulletedList', 'numberedList', '|', 'fontFamily', 'fontSize', 'fontColor', 'fontBackgroundColor', 'outdent', 'indent', 'alignment', 'removeFormat', '|', 'blockQuote', 'imageInsert', // 'videoUpload', 'insertTable', 'codeBlock', '|', 'undo', 'redo', '|', 'sourceEditing' ] }, simpleUpload: { uploadUrl: getContextPath() + '/commons/ckeditor5/upload', }, language: 'zh-cn', image: { toolbar: [ 'imageTextAlternative', 'imageStyle:alignBlockLeft', 'imageStyle:alignBlockRight', 'imageStyle:block', 'imageStyle:inline', 'imageStyle:side', 'imageStyle:alignLeft', 'imageStyle:alignRight',
] }, table: { contentToolbar: [ 'tableColumn', 'tableRow', 'mergeTableCells', 'tableProperties', 'tableCellProperties' ] }, htmlSupport: { allow: [{ name: /.*/, attributes: true, classes: true, styles: true }] // disallow: [ /* HTML features to disallow */ ] } }) .then(editor => { window.currentCKEditor = editor; }) .catch(error => { console.log(error); });}</script>