Vue2+Quill富文本編輯器詳解
更新時(shí)間:2025年08月06日 12:18:55 作者:左邊的天堂
文章指出Quill 2.0.0-dev.4與2.0.2版本存在兼容性問(wèn)題,原因未知,主要步驟包括引入依賴、創(chuàng)建編輯器組件、使用及效果測(cè)試
本例quill使用的版本是2.0.0-dev.4,2.0.2測(cè)試下來(lái)有很多不兼容的地方,原因未知。
1、先引入依賴
npm install quill@2.0.0-dev.4
2、創(chuàng)建編輯器組件
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: {
/* 編輯器的內(nèi)容 */
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}], // 標(biāo)題,鍵值對(duì)的形式;1、2表示字體大小
[{list: "ordered"}, {list: "bullet"}], //列表
[{script: "sub"}, {script: "super"}], // 上下標(biāo)
[
{table: 'TD'},
{'table-insert-row': 'TIR'},
{'table-insert-column': 'TIC'},
{'table-delete-row': 'TDR'},
{'table-delete-column': 'TDC'}
],
[{indent: "-1"}, {indent: "+1"}], // 縮進(jìn)
[{direction: "rtl"}], // 文本方向
[{'size': ['12px', '14px', '16px', '20px', '24px', '32px']}], // 字體大小
[{header: [1, 2, 3, 4, 5, 6, false]}], //幾級(jí)標(biāo)題
[{color: []}, {background: []}], // 字體顏色,字體背景顏色
[{'font': ['SimSun', 'SimHei', 'Microsoft-YaHei', 'KaiTi', 'FangSong', 'Arial']}], //字體
[{align: []}], //對(duì)齊方式
["clean"], //清除字體樣式
["link", "image"], //上傳圖片、上傳視頻
],
handlers: {
//實(shí)現(xiàn)首行縮進(jìn)的功能
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: "請(qǐng)輸入內(nèi)容",
},
// 圖標(biāo)顯示對(duì)應(yīng)的文字提示
titleConfig: [
{Choice: '.ql-wordBox', title: '首行縮進(jìn)'},
{Choice: '.ql-insertMetric', title: '跳轉(zhuǎn)配置'},
{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: '編號(hào)列表'},
{Choice: '.ql-list[value="bullet"]', title: '項(xiàng)目列表'},
{Choice: '.ql-direction', title: '文本方向'},
{Choice: '.ql-header[value="1"]', title: 'h1'},
{Choice: '.ql-header[value="2"]', title: 'h2'},
{Choice: '.ql-align', title: '對(duì)齊方式'},
{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: '下標(biāo)'},
{Choice: '.ql-script[value="super"]', title: '上標(biāo)'},
{Choice: '.ql-indent[value="-1"]', title: '向左縮進(jìn)'},
{Choice: '.ql-indent[value="+1"]', title: '向右縮進(jìn)'},
{Choice: '.ql-header .ql-picker-label', title: '標(biāo)題大小'},
{Choice: '.ql-align .ql-picker-item:first-child', title: '居左對(duì)齊'},
{Choice: '.ql-align .ql-picker-item[data-value="center"]', title: '居中對(duì)齊'},
{Choice: '.ql-align .ql-picker-item[data-value="right"]', title: '居右對(duì)齊'},
{Choice: '.ql-align .ql-picker-item[data-value="justify"]', title: '兩端對(duì)齊'},
{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() {
// 設(shè)置寬高
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) {
//這里是特殊處理,因?yàn)槭青]件模板,保存的html會(huì)丟失表格樣式,需要在內(nèi)容前面加上
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);
});
// 首行縮進(jìn)的圖標(biāo)
document.querySelector('.ql-wordBox').innerHTML = '<i class="el-icon-s-unfold"/>'
// 鼠標(biāo)懸浮在圖標(biāo)上顯示對(duì)應(yīng)的標(biāo)題
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;
}
/*設(shè)置字體和字體大小*/
.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: '一級(jí)標(biāo)題';
}
.ql-picker.ql-header .ql-picker-label[data-value='2']::before, .ql-picker.ql-header .ql-picker-item[data-value='2']::before {
content: '二級(jí)標(biāo)題';
}
.ql-picker.ql-header .ql-picker-label[data-value='3']::before, .ql-picker.ql-header .ql-picker-item[data-value='3']::before {
content: '三級(jí)標(biāo)題';
}
.ql-picker.ql-header .ql-picker-label[data-value='4']::before, .ql-picker.ql-header .ql-picker-item[data-value='4']::before {
content: '四級(jí)標(biāo)題';
}
.ql-picker.ql-header .ql-picker-label[data-value='5']::before, .ql-picker.ql-header .ql-picker-item[data-value='5']::before {
content: '五級(jí)標(biāo)題';
}
.ql-picker.ql-header .ql-picker-label[data-value='6']::before, .ql-picker.ql-header .ql-picker-item[data-value='6']::before {
content: '六級(jí)標(biāo)題';
}
.ql-picker.ql-header .ql-picker-label::before, .ql-picker.ql-header .ql-picker-item::before {
content: '標(biāo)準(zhǔn)';
}
.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';
}
/* 設(shè)置表格操作的幾個(gè)按鈕圖標(biāo),可以在 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 {
// 表單參數(shù)
form: {
content: undefined
},
rules: {
content: [
{required: true, message: '請(qǐng)輸入郵件模板', trigger: 'blur'},
],
},
}
},
mounted() {
this.$nextTick(() => {
// 加載數(shù)據(jù)
})
},
methods: {
reset() {
// ... ...
},
submitForm: function () {
this.$refs["form"].validate(valid => {
if (valid) {
// ... ...
}
})
}
}
}
</script>
4、實(shí)際效果

總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
element多個(gè)表單校驗(yàn)的實(shí)現(xiàn)
在項(xiàng)目中,經(jīng)常會(huì)遇到表單檢驗(yàn),在這里我分享在實(shí)際項(xiàng)目中遇到多個(gè)表單同時(shí)進(jìn)行校驗(yàn)以及我的解決方法,感興趣的可以了解一下2021-05-05
Vue實(shí)現(xiàn)點(diǎn)擊圖片放大顯示功能
這篇文章主要為大家詳細(xì)介紹了如何利用Vue實(shí)現(xiàn)點(diǎn)擊圖片放大顯示功能,文中的示例代碼講解詳細(xì),具有一定的參考價(jià)值,感興趣的可以了解一下2023-03-03
vue項(xiàng)目下,如何用命令直接修復(fù)ESLint報(bào)錯(cuò)
這篇文章主要介紹了vue項(xiàng)目下,如何用命令直接修復(fù)ESLint報(bào)錯(cuò),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-04-04
VUE-ElementUI?時(shí)間區(qū)間選擇器的使用
這篇文章主要介紹了VUE-ElementUI?時(shí)間區(qū)間選擇器的使用,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-05-05
Vue 報(bào)錯(cuò)Error: No PostCSS Config foun
這篇文章主要介紹了Vue 報(bào)錯(cuò)Error: No PostCSS Config found問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-07-07
vue實(shí)現(xiàn)表單未編輯或未保存離開(kāi)彈窗提示功能
這篇文章主要介紹了vue實(shí)現(xiàn)表單未編輯或未保存離開(kāi)彈窗提示功能,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-04-04

