Vue-Quill-Editor
主流富文本編輯器對比
前言:vue中很多項目都需要用到富文本編輯器,在使用了ueditor和tinymce后,發現並不理想。所以果斷使用vue-quill-editor來實現。
-
wangEditor(國產,基於javascript和css開發的web富文本編輯器,開源免費)優勢:輕量簡介,最重要的是開源且中文文檔齊全。缺點:更新不及時。沒有強大的開發團隊支撐。
-
UEditor(百度)優勢:插件多,基本曼度各種需求,由百度web前端研發部開發。缺點:插件提交較大,網頁加載速度相對就慢了些。使用復雜。屬於前后端不分離插件。在使用時需要配置后端的一些東西,使用不便。
-
Kindeditor () 優勢:文檔齊全,為中文,閱讀方便。缺點:圖片上傳存在問題,上傳歷史過多,會全部加載,導致瀏覽器卡頓。
-
補充:Tinymce也是一款不錯的富文本編輯器,種植,各有優勢和劣勢,關鍵是選擇一款最適合的就好。因為筆者在開發vue,所以直接使用vue-quill-editor較為方便些。具體看情況使用。
vue-quill-editor基本配置
npm install vue-quill-editor -s
main.js中引入
import VueQuillEditor from 'vue-quill-editor'
import 'quill/dist/quill.core.css'
import 'quill/dist/quill.snow.css'
import 'quill/dist/quill.bubble.css'
Vue.use(VueQuillEditor);
使用
需要注意的是toolbar的配置
1. 只需要填寫功能名的
加粗 - bold;
斜體 - italic
下划線 - underline
刪除線 - strike
引用- blockquote
代碼塊 - code-block
公式 - formula
圖片 - image
視頻 - video
清除字體樣式- clean
這一類的引用 直接['name','name']這種格式就好了
2. 需要有默認值的
標題 - header
[{ 'header': 1 }, { 'header': 2 }] 值字體大小
列表 - list
[{ 'list': 'ordered'}, { 'list': 'bullet' }], 值ordered,bullet
上標/下標 - script
[{ 'script': 'sub'}, { 'script': 'super' }], 值sub,super
縮進 - indent
[{ 'indent': '-1'}, { 'indent': '+1' }], 值-1,+1等
文本方向 - direction
[{'direction':'rtl'}]
結構
<template>
<quill-editor class="editor"
ref="myTextEditor"
v-model="content"
:options="editorOption"
@blur="onEditorBlur($event)"
@focus="onEditorFocus($event)"
@ready="onEditorReady($event)"
@change="onEditorChange($event)">
</quill-editor>
</template>
<script>
export default {
data () {
return {
content: null,
editorOption: {
modules: {
toolbar: [
["bold", "italic", "underline", "strike"], // 加粗 斜體 下划線 刪除線
["blockquote", "code-block"], // 引用 代碼塊
[{ header: 1 }, { header: 2 }], // 1、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"] // 鏈接、圖片、視頻
], //工具菜單欄配置
},
placeholder: '請在這里添加產品描述', //提示
readyOnly: false, //是否只讀
theme: 'snow', //主題 snow/bubble
syntax: true, //語法檢測
}
}
},
methods: {
// 失去焦點
onEditorBlur(editor) {},
// 獲得焦點
onEditorFocus(editor) {},
// 開始
onEditorReady(editor) {},
// 值發生變化
onEditorChange(editor) {
this.content = editor.html;
console.log(editor);
},
},
computed: {
editor() {
return this.$refs.myTextEditor.quillEditor;
}
},
mounted() {
// console.log('this is my editor',this.editor);
}
}
</script>
漢化
漢化只需要更改toolbar工具欄中的樣式即可實現
<style>
.editor {
line-height: normal !important;
height: 800px;
}
.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>
實現以上配置后就可以看到效果如圖:
以上就是vue-quill-editor的基本配置了。
圖片上傳的配置
因為圖片這塊,大多情況下,我們都不需要base64格式的,所以我們需要將圖片通過OSS換取網絡路徑然后發送給后端和回顯
更換quill-editor的點擊事件為自定義事件
這里借助element-ui的圖片功能,因為其功能齊全,圖片上傳前,上傳后,都有交互效果的處理,所以可以選擇性使用
editorOption: {
modules: {
toolbar: {
handlers: {
image: function(value) {
if (value) {
// 觸發input框選擇圖片文件
document.querySelector(".avatar-uploader input").click();//自定義元素的點擊事件
} else {
this.quill.format("image", false);
}
},
// link: function(value) {
// if (value) {
// var href = prompt('請輸入url');
// this.quill.format("link", href);
// } else {
// this.quill.format("link", false);
// }
// },
}
}
}
},
而后在自定義的元素上寫入點擊事件,然后將該元素隱藏掉。點擊quill-editor的圖片上傳時,實際點擊了自定義的圖片上傳,而后在返回網絡路徑后將圖片插入富文本編輯器即可。
插入返回的網絡圖片路徑(這里借助的是element-ui)
uploadSuccess(res, file) {
// res為圖片服務器返回的數據
// 獲取富文本組件實例
let quill = this.$refs.myQuillEditor.quill;
// 如果上傳成功
if (res.code == 200) {
// 獲取光標所在位置
let length = quill.getSelection().index;
// 插入圖片 res.url為服務器返回的圖片地址
quill.insertEmbed(length, "image", res.url);
// 調整光標到最后
quill.setSelection(length + 1);
} else {
this.$message.error("圖片插入失敗");
}
// loading動畫消失
this.quillUpdateImg = false;
},
以上就是主要思路及代碼,如果還是不懂就看下面vue組件的源碼(也可直接使用,前提是下載了element-ui)
組件封裝源碼及引用
<template>
<div>
<!-- 圖片上傳組件輔助-->
<el-upload
class="avatar-uploader"
:action="serverUrl"
name="file"
:headers="header"
:show-file-list="false"
list-type="picture"
:multiple="false"
:on-success="uploadSuccess"
:on-error="uploadError"
:before-upload="beforeUpload">
</el-upload>
<quill-editor
class="editor"
v-model="content"
ref="myQuillEditor"
:options="editorOption"
@blur="onEditorBlur($event)" @focus="onEditorFocus($event)"
@change="onEditorChange($event)">
</quill-editor>
</div>
</template>
<script>
// 工具欄配置
const toolbarOptions = [
["bold", "italic", "underline", "strike"], // 加粗 斜體 下划線 刪除線
["blockquote", "code-block"], // 引用 代碼塊
[{ header: 1 }, { header: 2 }], // 1、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"] // 鏈接、圖片、視頻
];
import { quillEditor } from "vue-quill-editor";
import "quill/dist/quill.core.css";
import "quill/dist/quill.snow.css";
import "quill/dist/quill.bubble.css";
export default {
props: {
/*編輯器的內容*/
value: {
type: String
},
/*圖片大小*/
maxSize: {
type: Number,
default: 4000 //kb
}
},
components: {
quillEditor
},
data() {
return {
content: this.value,
quillUpdateImg: false, // 根據圖片上傳狀態來確定是否顯示loading動畫,剛開始是false,不顯示
editorOption: {
theme: "snow", // or 'bubble'
placeholder: "您想說點什么?",
modules: {
toolbar: {
container: toolbarOptions,
// container: "#toolbar",
handlers: {
image: function(value) {
if (value) {
// 觸發input框選擇圖片文件
document.querySelector(".avatar-uploader input").click();
} else {
this.quill.format("image", false);
}
},
// link: function(value) {
// if (value) {
// var href = prompt('請輸入url');
// this.quill.format("link", href);
// } else {
// this.quill.format("link", false);
// }
// },
}
}
}
},
serverUrl: "https://testihospitalapi.ebaiyihui.com/oss/api/file/store/v1/saveFile", // 這里寫你要上傳的圖片服務器地址
header: {
// token: sessionStorage.token
} // 有的圖片服務器要求請求頭需要有token
};
},
methods: {
onEditorBlur() {
//失去焦點事件
},
onEditorFocus() {
//獲得焦點事件
},
onEditorChange() {
//內容改變事件
this.$emit("input", this.content);
},
// 富文本圖片上傳前
beforeUpload() {
// 顯示loading動畫
this.quillUpdateImg = true;
},
uploadSuccess(res, file) {
// res為圖片服務器返回的數據
// 獲取富文本組件實例
let quill = this.$refs.myQuillEditor.quill;
// 如果上傳成功
if (res.code == 200) {
// 獲取光標所在位置
let length = quill.getSelection().index;
// 插入圖片 res.url為服務器返回的圖片地址
quill.insertEmbed(length, "image", res.result.url);
// 調整光標到最后
quill.setSelection(length + 1);
} else {
this.$message.error("圖片插入失敗");
}
// loading動畫消失
this.quillUpdateImg = false;
},
// 富文本圖片上傳失敗
uploadError() {
// loading動畫消失
this.quillUpdateImg = false;
this.$message.error("圖片插入失敗");
}
}
};
</script>
<style>
.editor {
line-height: normal !important;
height: 800px;
}
.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>
引入:
<template>
<Editor v-model="article.content"/>
</template>
<script>
import Editor from './quillEditor'
export default {
components: {
Editor
},
data() {
return {
article: {
content: '',
}
}
}
}
</script>
<style>
</style>
VUE組件封裝
<template>
<quill-editor
class="editor"
ref="myTextEditor"
v-model="content"
:options="editorOption"
@change="onEditorChange($event)"
></quill-editor>
</template>
<script>
const toolbar = [
["bold", "italic", "underline", "strike"], // 加粗 斜體 下划線 刪除線
["blockquote", "code-block"], // 引用 代碼塊
[{ header: 1 }, { header: 2 }], // 1、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"] // 鏈接、圖片、視頻
]; //工具菜單欄配置
export default {
data() {
return {
content: null, //內容
editorOption: {
modules: {
toolbar
},
placeholder: "", //提示
readyOnly: false, //是否只讀
theme: "snow", //主題 snow/bubble
syntax: true //語法檢測
}
};
},
methods: {
// 值發生變化
onEditorChange(editor) {
this.content = editor.html;
this.$emit("content", editor.html);
}
}
};
</script>
<style>
.editor {
line-height: normal !important;
height: 800px;
margin-bottom: 30px;
}
.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>
還有一種方式是不借助element-ui來實現圖片上傳,這一過程無非就是圖片上傳OSS換取網絡路徑,這一塊,咱們其實可以自定義圖片上傳組件。這里就不做闡述了,筆者的另一篇圖片上傳的組件文章(https://blog.csdn.net/my_new_way/article/details/105812646),大家在這里就可以使用起來。