前言

在做管理后台项目时,需要用到富文本编辑器,于是在网上找了一些富文本编辑器,对比发现vue-quill-editor能够较好的支持vue,且界面漂亮,官方文档也比较详细.但是在使用过程中,富文本编辑器一般都用于表单form中提交数据的,所以希望能够配合表单组件,在表单项的前面能够添加必选的星号(*)标记,以及进行非空或其他规则校验,另外提交表单时希望也能够提交富文本编辑器里的内容.那么直接使用vue-quill-editor肯定是不能满足的,所以对其再次封装,当成组件使用.

一、vue-quill-editor的安装

1.引入库

代码如下:

npm install vue-quill-editor -S
//如果需要图片缩放和拖拽功能,则需要安装下面三个依赖
npm install quill-image-resize-module -S  //缩放
npm install quill-image-drop-module -S //拖动
npm install quill -S

2.vue-quill-editor直接使用

如果你对vue-quill-editor还很陌生,不了解相应的api,你可以参考vue-quill-editor官方中文文档,这里我就不再赘述了.
如果你想直接使用原版vue-quill-editor,不想使用自定义封装的组件,你可以参考基于Vue项目的富文本vue-quill-editor的使用,这篇文章对vue-quill-editor的使用介绍也非常详细


3.vue-quill-editor封装

新建MyQuillEditor.vue文件

3.1 template代码

代码如下:

<template>
    <div>
        <div  style="margin-bottom: 50px;">
            <quill-editor
                    :content="content"
                    ref="myQuillEditor"
                    :options="editorOption"
                    @focus="onEditorFocus($event)"
                    @blur="onEditorBlur($event)"
                    @change="onEditorChange($event)"
                    class="editor"
            ></quill-editor>

            <!--自定义图片上传,写一个form表单,然后form表单中隐藏一个input标签,type为file属性.当我们的自定义图片处理函数被点击时,进行触发打开文件选择框,选择文件后触发uploadImg方法,
            最后调用图片上传的接口,返回图片在服务器的地址之后,回显于当前富文本中 -->
            <form action method="post" enctype="multipart/form-data" id="uploadFormMulti">
                <input
                        style="display: none"
                        :id="uniqueId"
                        type="file"
                        name="file"
                        multiple
                        accept="image/jpg, image/jpeg, image/png, image/gif"
                        @change="uploadImg('uploadFormMulti')"
                />
            </form>
        </div>
    </div>
</template>

3.2 script代码

代码如下:

<script>
    //引入quill样式
    import "quill/dist/quill.core.css";
    import "quill/dist/quill.snow.css";
    import "quill/dist/quill.bubble.css";
    //引入quillEditor编辑器模块,引入Quill模块
    import { quillEditor,Quill } from "vue-quill-editor";
    //引入图片缩放插件,需要依赖Quill模块
    import ImageResize from 'quill-image-resize-module'
    //引入图片拖拽插件,需要依赖Quill模块
    import {ImageDrop} from "quill-image-drop-module"
    //注册插件
    Quill.register('modules/imageResize',ImageResize)
    Quill.register('modules/imageDrop',ImageDrop)
    //按钮多为图标显示,用户使用起来不能清楚明白功能按钮的作用,给工具栏按钮添加鼠标停留提示的交互
    import { addQuillTitle } from './quill-title.js'
    //自定义上传图片api
    import {UploadOSS} from "@/services/upload";

    // 工具栏配置
    const toolbarOptions = [
        ["bold", "italic", "underline", "strike"], // 加粗 斜体 下划线 删除线 -----['bold', 'italic', 'underline', 'strike']
        ["blockquote", "code-block"], // 引用  代码块-----['blockquote', 'code-block']
        [{ header: 1 }, { header: 2 }], // 1、2 级标题-----[{ header: 1 }, { header: 2 }]
        [{ list: "ordered" }, { list: "bullet" }], // 有序、无序列表-----[{ list: 'ordered' }, { list: 'bullet' }]
        [{ script: "sub" }, { script: "super" }], // 上标/下标-----[{ script: 'sub' }, { script: 'super' }]
        [{ indent: "-1" }, { indent: "+1" }], // 缩进-----[{ indent: '-1' }, { indent: '+1' }]
        [{ direction: "rtl" }], // 文本方向-----[{'direction': 'rtl'}]
        [{ size: ["small", false, "large", "huge"] }], // 字体大小-----[{ size: ['small', false, 'large', 'huge'] }]
        [{ header: [1, 2, 3, 4, 5, 6, false] }], // 标题-----[{ header: [1, 2, 3, 4, 5, 6, false] }]
        [{ color: [] }, { background: [] }], // 字体颜色、字体背景颜色-----[{ color: [] }, { background: [] }]
        [{ font: [] }], // 字体种类-----[{ font: [] }]
        [{ align: [] }], // 对齐方式-----[{ align: [] }]
        ["clean"], // 清除文本格式-----['clean']
        ["image", "video",'link'] // 链接、图片、视频-----['link', 'image', 'video']
    ];

    export default {
        components: {
            quillEditor
        },
        props:{
            //接收父组件的参数
            value: null,
        },
        mixins: [],
        computed: {
            //当前富文本实例
            editor() {
                return this.$refs.myQuillEditor.quill;
            }
        },

        watch: {
            value(val = '') {
                //监听父组件参数变化,回显到富文本框
                this.content = val;
            },
        },

        data() {
            return {
                content: this.value || '',
                uniqueId: "uniqueId",
                editorOption: {          //  富文本编辑器配置
                    modules: {  //模块配置
                        toolbar: toolbarOptions, //设置工具栏
                        imageResize: {            //图片缩放配置
                            displayStyles: {
                                backgroundColor: "black",
                                border: "none",
                                color: "white"
                            },
                            modules: ["Resize", "DisplaySize", "Toolbar"]
                        },
                        imageDrop: false,       //图片拖拽配置,false为开启拖拽,true关闭拖拽1
                    },
                    theme: "snow",  //主题配置
                    placeholder: "请输入正文"
                }
            }
        },
        methods: {
            // 准备富文本编辑器
            onEditorReady(quill) {

            },
            // 富文本编辑器 失去焦点事件
            onEditorBlur(quill) {

            },
            // 富文本编辑器 获得焦点事件
            onEditorFocus(quill) {

            },
            // 富文本编辑器 内容改变事件
            onEditorChange({ quill, html, text }) {
                //将内容提交给父组件的change事件
                this.$emit('change',html)
            },

            //自定义上传图片方法
            uploadImg: async function() {
                var _this = this;
                //构造formData对象
                var formData = new FormData();
                formData.append("file", document.getElementById(_this.uniqueId).files[0]);

                try {
                    //调用上传文件接口
                    UploadOSS(formData).then(res => {
                        //返回上传文件的地址
                        let url = res.data;
                        if (url != null && url.length > 0) {
                            let Range = _this.editor.getSelection();
                            url = url.indexOf("http") != -1 ? url : "http:" + url;
                            //上传文件成功之后在富文本中回显(显示)
                            _this.editor.insertEmbed(
                                Range != null ? Range.index : 0,
                                "image",
                                url
                            );
                        } else {
                            _this.$message.warning("图片上传失败");
                        }
                        //成功之后,将文件的文本框的value置空
                        document.getElementById(_this.uniqueId).value = "";
                    });
                } catch ({ message: msg }) {
                    document.getElementById(_this.uniqueId).value = "";
                    _this.$message.warning(msg);
                }
            }

        },
        created() {
        },
        mounted() {
            var _this = this;

            //构造一个工具栏图片按钮被点击时的处理函数
            var imgHandler = async function(image) {
                if (image) {
                    var fileInput = document.getElementById(_this.uniqueId); //获取隐藏的file文本ID对应document对象
                    fileInput.click(); //触发document对象的点击事件
                }
            };
            //将处理函数绑定到工具栏的image图标上
            _this.editor.getModule("toolbar").addHandler("image", imgHandler);
            this.$nextTick(() => {
                //为工具栏的所有图标按钮添加鼠标悬停时的文字提示
                addQuillTitle();
            });
        }
    }


</script>

3.3 css代码

代码如下:

<style>
    /*.editor 自定义的样式,可以自己控制富文本窗体大小*/
    .editor {
        line-height: normal !important;
        height: 300px;
    }
    /*以下样式是为了给工具栏汉化处理*/
    .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";
    }
    .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: "等宽字体";
    }
</style>

3.4 关于MyQuillEditor.vue文件中script代码块引入的quill-title.js

代码如下:

const titleConfig = {
    'ql-bold': '加粗',
    'ql-color': '字体颜色',
    'ql-font': '字体',
    'ql-code': '插入代码',
    'ql-italic': '斜体',
    'ql-link': '选中文字,添加链接',
    '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'),
            aButton = oToolBar.querySelectorAll('button'),
            aSelect = oToolBar.querySelectorAll('select'),
            aSpan= oToolBar.querySelectorAll('span')
        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 if(item.className === 'ql-list'){
                item.value==='ordered' ? item.title='有序列表' : item.title='无序列表'
            }else if(item.className === 'ql-header'){
                item.value === '1' ? item.title = '标题H1': item.title = '标题H2';
            }else{
                item.title = titleConfig[item.classList[0]];
            }
        })
        aSelect.forEach(function (item) {
            item.parentNode.title = titleConfig[item.classList[0]]
        })

        aSpan.forEach((item)=>{
            if(item.classList[0]==='ql-color'){
                item.title = titleConfig[item.classList[0]];
            }else if(item.classList[0]==='ql-background'){
                item.title = titleConfig[item.classList[0]];
            }
        })



}

3.5 关于MyQuillEditor.vue文件中script代码块引入的upload.js

代码如下:

import {request, METHOD} from '@/utils/request'

export function UploadOSS(data) {
  return request('/upload/oss', METHOD.POST, data,{ headers: {'Content-Type': 'multipart/form-data'},})
}

export default {
  UploadOSS,
}

3.6 导出组件

新建index.js
代码如下:

import MyQuillEditor from './MyQuillEditor'
export default MyQuillEditor

3.6 目录结构如下:

在这里插入图片描述

4 在表单form中使用封装好的MyQuillEditor组件

<template>
	<a-form :form="form" >
		<a-row :gutter="24">
	        <a-col :md="24" :sm="24" pull="2">
	            <a-form-item
	                    label="商品详情"
	                    :labelCol="{span: 4}"
	                    :wrapperCol="{span: 18}"
	            >
	                <my-quill-editor  v-decorator="['details',{ rules: [{required: true, message: '请输入详情!'}] }]" />
	            </a-form-item>
	        </a-col>
	    </a-row>
	</a-from>
</template>
<script>
	import MyQuillEditor from '@/components/myQuillEditor'
	export default {
		components: {MyQuillEditor},
		 data() {
		 	form: this.$form.createForm(this),
		 }
	}
</script>

4 解决Quill引入报错的问题

引入quill时控制台可能会报错,需要在vue.config.js中做配置
代码如下:

module.exports = {
	chainWebpack: config => {
	    //解决Quill引入报错的问题
	    config.plugin('provide').use(webpack.ProvidePlugin, [{
	      'window.Quill': 'quill/dist/quill.js',
	      'Quill': 'quill/dist/quill.js'
	    }])
    }
}

最终效果

Logo

前往低代码交流专区

更多推荐