Vue2+Quill富文本编辑器详解
作者:左边的天堂
文章指出Quill 2.0.0-dev.4与2.0.2版本存在兼容性问题,原因未知,主要步骤包括引入依赖、创建编辑器组件、使用及效果测试
本例quill使用的版本是2.0.0-dev.4,2.0.2测试下来有很多不兼容的地方,原因未知。
1、先引入依赖
npm install quill@2.0.0-dev.4
2、创建编辑器组件
components/Editor.vue
<template> <div> <div class="editor" ref="editor" :style="styles"></div> </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"; export default { name: "Editor", props: { /* 编辑器的内容 */ value: { type: String, default: "", }, /* 高度 */ height: { type: Number, default: 600, }, /* 最小高度 */ minHeight: { type: Number, default: 400, }, }, data() { return { quill: null, currentValue: "", options: { theme: "snow", bounds: document.body, debug: "warn", modules: { table: true, // 工具栏配置 toolbar: { container: [ ["wordBox", "bold", "italic", "underline", "strike"], //加粗,斜体,下划线,删除线 ["blockquote", "code-block"], //引用,代码块 [{header: 1}, {header: 2}], // 标题,键值对的形式;1、2表示字体大小 [{list: "ordered"}, {list: "bullet"}], //列表 [{script: "sub"}, {script: "super"}], // 上下标 [ {table: 'TD'}, {'table-insert-row': 'TIR'}, {'table-insert-column': 'TIC'}, {'table-delete-row': 'TDR'}, {'table-delete-column': 'TDC'} ], [{indent: "-1"}, {indent: "+1"}], // 缩进 [{direction: "rtl"}], // 文本方向 [{'size': ['12px', '14px', '16px', '20px', '24px', '32px']}], // 字体大小 [{header: [1, 2, 3, 4, 5, 6, false]}], //几级标题 [{color: []}, {background: []}], // 字体颜色,字体背景颜色 [{'font': ['SimSun', 'SimHei', 'Microsoft-YaHei', 'KaiTi', 'FangSong', 'Arial']}], //字体 [{align: []}], //对齐方式 ["clean"], //清除字体样式 ["link", "image"], //上传图片、上传视频 ], handlers: { //实现首行缩进的功能 wordBox: function (val) { let range = this.quill.getSelection(); this.quill.insertText(range.index, '\t', {'style': 'text-indent: 2em;'}); }, //增加表格 table: function (val) { this.quill.getModule('table').insertTable(2, 3) }, //插入行 'table-insert-row': function () { this.quill.getModule('table').insertRowBelow() }, //插入列 'table-insert-column': function () { this.quill.getModule('table').insertColumnRight() }, //删除行 'table-delete-row': function () { this.quill.getModule('table').deleteRow() }, //删除列 'table-delete-column': function () { this.quill.getModule('table').deleteColumn() } } } }, placeholder: "请输入内容", }, // 图标显示对应的文字提示 titleConfig: [ {Choice: '.ql-wordBox', title: '首行缩进'}, {Choice: '.ql-insertMetric', title: '跳转配置'}, {Choice: '.ql-bold', title: '加粗'}, {Choice: '.ql-italic', title: '斜体'}, {Choice: '.ql-underline', title: '下划线'}, {Choice: '.ql-header', title: '段落格式'}, {Choice: '.ql-strike', title: '删除线'}, {Choice: '.ql-blockquote', title: '块引用'}, {Choice: '.ql-code', title: '插入代码'}, {Choice: '.ql-code-block', title: '插入代码段'}, {Choice: '.ql-font', title: '字体'}, {Choice: '.ql-size', title: '字体大小'}, {Choice: '.ql-list[value="ordered"]', title: '编号列表'}, {Choice: '.ql-list[value="bullet"]', title: '项目列表'}, {Choice: '.ql-direction', title: '文本方向'}, {Choice: '.ql-header[value="1"]', title: 'h1'}, {Choice: '.ql-header[value="2"]', title: 'h2'}, {Choice: '.ql-align', title: '对齐方式'}, {Choice: '.ql-color', title: '字体颜色'}, {Choice: '.ql-background', title: '背景颜色'}, {Choice: '.ql-image', title: '图像'}, {Choice: '.ql-video', title: '视频'}, {Choice: '.ql-link', title: '添加链接'}, {Choice: '.ql-formula', title: '插入公式'}, {Choice: '.ql-clean', title: '清除字体格式'}, {Choice: '.ql-script[value="sub"]', title: '下标'}, {Choice: '.ql-script[value="super"]', title: '上标'}, {Choice: '.ql-indent[value="-1"]', title: '向左缩进'}, {Choice: '.ql-indent[value="+1"]', title: '向右缩进'}, {Choice: '.ql-header .ql-picker-label', title: '标题大小'}, {Choice: '.ql-align .ql-picker-item:first-child', title: '居左对齐'}, {Choice: '.ql-align .ql-picker-item[data-value="center"]', title: '居中对齐'}, {Choice: '.ql-align .ql-picker-item[data-value="right"]', title: '居右对齐'}, {Choice: '.ql-align .ql-picker-item[data-value="justify"]', title: '两端对齐'}, {Choice: '.ql-table', title: '添加表格'}, {Choice: '.ql-table-insert-row', title: '插入行'}, {Choice: '.ql-table-insert-column', title: '插入列'}, {Choice: '.ql-table-delete-row', title: '删除行'}, {Choice: '.ql-table-delete-column', title: '删除列'}, ] } }, computed: { styles() { // 设置宽高 let style = {width: '1200px'}; 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 === '<p><br></p>') ? "" : val; if (this.quill) { this.quill.clipboard.dangerouslyPasteHTML(this.currentValue); } } }, immediate: true, }, }, mounted() { this.init(); }, beforeDestroy() { this.quill = null; }, methods: { init() { // 初始化 const editor = this.$refs.editor; this.quill = new Quill(editor, this.options); this.quill.clipboard.dangerouslyPasteHTML(this.currentValue); this.quill.on("text-change", (delta, oldDelta, source) => { let html = this.$refs.editor.children[0].innerHTML; if (html.indexOf('<table>') !== -1) { //这里是特殊处理,因为是邮件模板,保存的html会丢失表格样式,需要在内容前面加上 let _class = "<head>" +"<style>" +"table {table-layout: fixed; width: 100%; border-collapse: collapse; }" +"td, th { padding: 2px 5px; border: 1px solid #000; outline: none; display: table-cell; vertical-align: inherit; unicode-bidi: isolate;}" +"img { max-width: 100%; height: auto; }" +"</style>" +"</head>"; html = _class + html; } this.currentValue = html; const text = this.quill.getText(); const quill = this.quill; 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); }); // 首行缩进的图标 document.querySelector('.ql-wordBox').innerHTML = '<i class="el-icon-s-unfold"/>' // 鼠标悬浮在图标上显示对应的标题 this.titleConfig.forEach(item => { const tip = document.querySelector(item.Choice) if (tip) { tip.setAttribute('title', item.title) } }); }, }, }; </script> <style lang="less" scoped> ::v-deep { .ql-toolbar { /* 要与styles()方法中的width保持一致 */ width: 1200px; } /*设置字体和字体大小*/ .ql-picker.ql-font .ql-picker-label[data-value=SimSun]::before, .ql-picker.ql-font .ql-picker-item[data-value=SimSun]::before { content: "宋体"; font-family: "SimSun" !important; } .ql-picker.ql-font .ql-picker-label[data-value=SimHei]::before, .ql-picker.ql-font .ql-picker-item[data-value=SimHei]::before { content: "黑体"; font-family: "SimHei"; } .ql-picker.ql-font .ql-picker-label[data-value=Microsoft-YaHei]::before, .ql-picker.ql-font .ql-picker-item[data-value=Microsoft-YaHei]::before { content: "微软雅黑"; font-family: "Microsoft YaHei"; } .ql-picker.ql-font .ql-picker-label[data-value=KaiTi]::before, .ql-picker.ql-font .ql-picker-item[data-value=KaiTi]::before { content: "楷体"; font-family: "KaiTi" !important; } .ql-picker.ql-font .ql-picker-label[data-value=FangSong]::before, .ql-picker.ql-font .ql-picker-item[data-value=FangSong]::before { content: "仿宋"; font-family: "FangSong"; } .ql-picker.ql-header .ql-picker-label[data-value='1']::before, .ql-picker.ql-header .ql-picker-item[data-value='1']::before { content: '一级标题'; } .ql-picker.ql-header .ql-picker-label[data-value='2']::before, .ql-picker.ql-header .ql-picker-item[data-value='2']::before { content: '二级标题'; } .ql-picker.ql-header .ql-picker-label[data-value='3']::before, .ql-picker.ql-header .ql-picker-item[data-value='3']::before { content: '三级标题'; } .ql-picker.ql-header .ql-picker-label[data-value='4']::before, .ql-picker.ql-header .ql-picker-item[data-value='4']::before { content: '四级标题'; } .ql-picker.ql-header .ql-picker-label[data-value='5']::before, .ql-picker.ql-header .ql-picker-item[data-value='5']::before { content: '五级标题'; } .ql-picker.ql-header .ql-picker-label[data-value='6']::before, .ql-picker.ql-header .ql-picker-item[data-value='6']::before { content: '六级标题'; } .ql-picker.ql-header .ql-picker-label::before, .ql-picker.ql-header .ql-picker-item::before { content: '标准'; } .ql-picker.ql-size .ql-picker-label[data-value='12px']::before, .ql-picker.ql-size .ql-picker-item[data-value='12px']::before { content: '12px'; } .ql-picker.ql-size .ql-picker-label[data-value='14px']::before, .ql-picker.ql-size .ql-picker-item[data-value='14px']::before { content: '14px'; } .ql-picker.ql-size .ql-picker-label[data-value='16px']::before, .ql-picker.ql-size .ql-picker-item[data-value='16px']::before { content: '16px'; } .ql-picker.ql-size .ql-picker-label[data-value='20px']::before, .ql-picker.ql-size .ql-picker-item[data-value='20px']::before { content: '20px'; } .ql-picker.ql-size .ql-picker-label[data-value='24px']::before, .ql-picker.ql-size .ql-picker-item[data-value='24px']::before { content: '24px'; } .ql-picker.ql-size .ql-picker-label[data-value='32px']::before, .ql-picker.ql-size .ql-picker-item[data-value='32px']::before { content: '32px'; } .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'; } /* 设置表格操作的几个按钮图标,可以在 www.iconfont.cn 找 */ .ql-toolbar .ql-table-insert-row { background-image: url('../assets/icon/insert_row.svg'); background-size: 14px 14px; background-repeat: no-repeat; background-position: center; } .ql-toolbar .ql-table-insert-column { background-image: url('../assets/icon/insert_column.svg'); background-size: 14px 14px; background-repeat: no-repeat; background-position: center; } .ql-toolbar .ql-table-delete-row { background-image: url('../assets/icon/delete_row.svg'); background-size: 14px 14px; background-repeat: no-repeat; background-position: center; } .ql-toolbar .ql-table-delete-column { background-image: url('../assets/icon/delete_column.svg'); background-size: 14px 14px; background-repeat: no-repeat; background-position: center; } } </style>
3、使用
<template> <div class="app-container"> <div class="drawer-bd"> <el-form ref="form" :model="form" :rules="rules" label-position="top"> <el-form-item label="邮件模板" prop="content"> <editor ref="editor" :value="form.content" v-model="form.content"/> </el-form-item> </el-form> </div> <div class="drawer-ft"> <el-button @click="reset">清 空</el-button> <el-button type="primary" @click="submitForm">保 存</el-button> </div> </div> </template> <script> import Editor from "@/components/Editor"; export default { name: 'Demo', components: {Editor}, data() { return { // 表单参数 form: { content: undefined }, rules: { content: [ {required: true, message: '请输入邮件模板', trigger: 'blur'}, ], }, } }, mounted() { this.$nextTick(() => { // 加载数据 }) }, methods: { reset() { // ... ... }, submitForm: function () { this.$refs["form"].validate(valid => { if (valid) { // ... ... } }) } } } </script>
4、实际效果
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。