1.挑個富文本編輯器 首先針對自己項目的類型,確定自己要用啥編輯器。 1.1 wangeditor 如果一般類似博客這種項目不需要花里胡哨的,功能也不要求賊多的,推薦一下wangeditor(點擊跳轉)。能覆蓋基本上所有的常見操作,輕量化,開源,有中文文檔。 ▽wangeditor效果圖 1.2 t ...
1.挑個富文本編輯器
首先針對自己項目的類型,確定自己要用啥編輯器。
1.1 wangeditor
如果一般類似博客這種項目不需要花里胡哨的,功能也不要求賊多的,推薦一下wangeditor(點擊跳轉)。能覆蓋基本上所有的常見操作,輕量化,開源,有中文文檔。
▽wangeditor效果圖
1.2 tinyMCE
如果需要複雜的編輯器,推薦tinyMCE(點擊跳轉),同樣也非常簡單和優雅,但是文檔是英文的,配合chrome的翻譯,基本上能看懂,而且tinyMCE有另外一個好處,word上的東西基本上都可以保存格式複製到編輯器里,可以比較方便的轉移。
▽tinyMCE效果圖
剩下的很多編輯器,但是大多沒接觸過,推薦的這兩個,一個簡潔夠用,一個功能齊全,能覆蓋90%以上的場景了,所以就不推薦別的了。另外本文主要講wangeditor。
2.項目準備(wangeditor)
本文選用框架的是nuxt.js,和vue-cli基本一致。都0202年了,如果不是模塊化的項目請參考下wangeditor的文檔。
既然談到模塊化項目,那麼將編輯器設為組件那就是非常必要的了。
但是有一個問題,vue自帶的數據雙向綁定是不支持組件內外之間的雙向傳遞的(其實支持的,就是我不知道),也就是說,如果你將富文本編輯器封裝在組件B中,你在A頁面用組件B去寫入數據C,這個C數據只能在B的組件頁面中獲取,無法直接在A頁面中取到。但是項目邏輯不可能在組件的B頁面上去執行,一定是在A頁面去完成邏輯,所以一定要在A中獲取到數據。
為瞭解決這個問題,我們需要回顧一下vue的v-model和父子組件的傳值方式。如果你瞭解組件數據的雙向綁定和它的原理,可以忽略下麵,直接到最後的代碼處就行了。
2.1 v-model
這個很基礎了,就簡單放個例子好了。
1 <input type="text" v-model="message"> 2 <p>{{message}}</p>
這個例子其實等價於
1 <input type="text" :value="message" @input="message = $event.target.value">
挺好理解的,① v-bind:"message" 等於給input賦值,② @input="message = $event.target.value" 等於給message賦值,所以這樣才實現了雙向綁定,也就是修改message時會觸發v-bind讓Input變換值,在Input輸入時能觸發input事件來改變message的值。
2.2實現組件v-model
那現在我搞一個組件,給他設上v-bind ,然後順便監聽他的input,這兩步可以化為一步v-model="price",也可以看非組件時候的"② @input="message = $event.target.value" 等於給message賦值,"。因為監聽了input會將數據賦給price,然後通過v-bind會將price的值賦給組件內部的props中的value屬性。
1 <div id="app"> 2 <price-input :value="price" @input="onInput"></price-input> 3 </div>
1 var app = new Vue({ 2 el: '#app', 3 data(){ 4 price: '' 5 }, 6 methods: { 7 onInput(val){ 8 this.price = val; 9 } 10 } 11 })
我們現在已經完成第二步,現在只需要完成第一步就行了,第一步體現在組件上就是接收第二步傳進來的信息,然後通過$emit再將它傳出去。即完成了組件上的數據雙向綁定。組件內部代碼如下:
1 Vue.component('custom-input',{ 2 template: `<input :value='value' @input='updateVal($event.target.value)' type='text'></input>`, 3 props: ['value'], 4 methods: { 5 updateVal(val){ 6 this.$emit('input', val); 7 } 8 } 9 });
ps:現在$emit('input',val)中的input方法是指組件外的@input="oninput"而不是指組件內部的@input='updateVal($event.target.value)'
2.3 model
但是現在還有一個問題,預設情況下,組件上的 v-model 會把 value 用作 prop 且把 input 用作 event。所以當我們在一個自定義組件上使用v-model並不能實現雙向綁定,因為自定的組件並沒有預設的value和input事件,在使用時,我們需要按照上面那樣顯式的去聲明定義這些東西。這時,model選項就派上用場了,在定義組件的時候,指定prop的值和監聽的事件。
1 Vue.component('my-input',{
template: `<input type="text" :value="uname" @input="updateVal($event.target.value)">`,
2 props: {
3 // 如果你想在組件上使用v-model的話,此處的uname應該叫value
4 uname: {
5 type: String,
6 default: 'tom'
7 }
8 },
9 methods: {
10 updateVal(val){
11 // 如果你想在組件上使用v-model的話,此處的changeXXX應該叫input
12 this.$emit('changeXXX',val)
13 }
14 }
15 })
1 <my-input v-model="name" value='some value'></my-input>
△ 在這種情況下,v-model的綁定是失效的,所以只能改成拆解寫法:
1 <my-input :uname='name' @changeXXX='val => {foo = val}' value='some value'></my-input>
要想使用v-model這時候只能在vue.component的參數對象裡加一個Model對象,指出你想要value叫uname, input叫changeXXX。然後快樂的使用v-model就可以了,代碼如下:
1 Vue.component('my-input',{ 2 model: { 3 prop: 'uname', 4 event: 'changeXXX' 5 }, 6 props: { 7 uname: { 8 type: String, 9 default: 'tom' 10 } 11 }, 12 methods: { 13 updateVal(val){ 14 this.$emit('changeXXX',val) 15 } 16 } 17 })
這樣v-model 就可以生效了。另外如果你用v-model的話,event的名字沒有什麼特殊要求,你填什麼都可以。
3.實戰
3.1 安裝和導入
直接npm安裝, $ npm install wangeditor
安裝好了在components/public下新建editor.vue文件下導入文件,如果直接require會出現navigator is not defined 的錯誤,搜了一下可能是因為nuxt.js服務端渲染的問題,那時候還沒有navigator這個對象,所以導致出現這個錯誤。所以引用時要加個判斷。 const E = process.browser ? require("wangeditor") : undefined;
3.2 項目代碼
代碼整體如下:
1 <template lang="html"> 2 <div class="editor"> 3 <div ref="toolbar" class="toolbar"></div> 4 <div ref="editor" class="text"></div> 5 </div> 6 </template> 7 8 <script> 9 // import E from "wangeditor"; 10 const E = process.browser ? require("wangeditor") : undefined; 11 export default { 12 data() { 13 return { 14 // uploadPath, 15 editor: null, 16 info_: null 17 }; 18 }, 19 model: { 20 prop: "value", 21 event: "change" 22 }, 23 props: { 24 value: { 25 type: String, 26 default: "" 27 }, 28 isClear: { 29 type: Boolean, 30 default: false 31 } 32 }, 33 watch: { 34 isClear(val) { 35 // 觸發清除文本域內容 36 if (val) { 37 this.editor.txt.clear(); 38 this.info_ = null; 39 } 40 }, 41 value: function(value) { 42 if (value !== this.editor.txt.html()) { 43 this.editor.txt.html(this.value); 44 } 45 } 46 }, 47 mounted() { 48 this.seteditor(); 49 this.editor.txt.html(this.value); 50 }, 51 methods: { 52 seteditor() { 53 // http://192.168.2.125:8080/admin/storage/create 54 this.editor = new E(this.$refs.toolbar, this.$refs.editor); 55 this.editor.customConfig.uploadImgShowBase64 = true; // base 64 存儲圖片 56 // this.editor.customConfig.uploadImgServer = 57 // ""; // 配置伺服器端地址 58 // this.editor.customConfig.uploadImgHeaders = {}; // 自定義 header 59 // this.editor.customConfig.uploadFileName = "file"; // 後端接受上傳文件的參數名 60 // this.editor.customConfig.uploadImgMaxSize = 2 * 1024 * 1024; // 將圖片大小限製為 2M 61 // this.editor.customConfig.uploadImgMaxLength = 6; // 限制一次最多上傳 3 張圖片 62 // this.editor.customConfig.uploadImgTimeout = 3 * 60 * 1000; // 設置超時時間 63 64 // 配置菜單 65 this.editor.customConfig.menus = [ 66 "head", // 標題 67 "bold", // 粗體 68 "fontSize", // 字型大小 69 "fontName", // 字體 70 "italic", // 斜體 71 "underline", // 下劃線 72 "strikeThrough", // 刪除線 73 "foreColor", // 文字顏色 74 "backColor", // 背景顏色 75 "link", // 插入鏈接 76 "list", // 列表 77 "justify", // 對齊方式 78 "quote", // 引用 79 "emoticon", // 表情 80 "image", // 插入圖片 81 "table", // 表格 82 "video", // 插入視頻 83 "code", // 插入代碼 84 "undo", // 撤銷 85 "redo", // 重覆 86 "fullscreen" // 全屏 87 ]; 88 89 this.editor.customConfig.uploadImgHooks = { 90 fail: (xhr, editor, result) => { 91 // 插入圖片失敗回調 92 }, 93 success: (xhr, editor, result) => { 94 // 圖片上傳成功回調 95 }, 96 timeout: (xhr, editor) => { 97 // 網路超時的回調 98 }, 99 error: (xhr, editor) => { 100 // 圖片上傳錯誤的回調 101 }, 102 customInsert: (insertImg, result, editor) => { 103 // 圖片上傳成功,插入圖片的回調 104 } 105 }; 106 this.editor.customConfig.onchange = html => { 107 this.info_ = html; // 綁定當前逐漸地值 108 this.$emit("change", this.info_); // 將內容同步到父組件中 109 }; 110 // 創建富文本編輯器 111 this.editor.create(); 112 } 113 } 114 }; 115 </script> 116 117 <style lang="css"> 118 .editor { 119 width: 100%; 120 margin: 0 auto; 121 position: relative; 122 z-index: 0; 123 } 124 .toolbar { 125 border: 1px solid #ccc; 126 } 127 .text { 128 border: 1px solid #ccc; 129 min-height: 500px; 130 } 131 </style>
頁面代碼 :
1 <template> 2 <div> 3 <editor-bar 4 v-model="detail" 5 :isClear="isClear" 6 @change="change" 7 ></editor-bar> 8 9 <button @click="send">點我!</button> 10 </div> 11 </template> 12 <script> 13 import EditorBar from "../components/public/editor"; 14 15 export default { 16 components: { EditorBar }, 17 data() { 18 return { 19 isClear: false, 20 detail: "" 21 }; 22 }, 23 methods: { 24 change(val) { 25 console.log(val); 26 }, 27 send() { 28 alert(this.detail); 29 } 30 } 31 }; 32 </script> 33 34 <style></style>
直接複製就可以用了,更多功能可以參考文檔(點我!)。