Vue項目中最簡單的使用集成UEditor方式,含圖片上傳

本文永久連接:github.com/HaoChuan942…javascript

前言

封面是UEditor百度指數 折線圖。雖然今天已是 2018 年,且優秀的富文本編輯器層出不窮(包括移動端),但從圖中能夠看出UEditor仍然維持着較高的搜索熱度。而很多公司和我的也仍然在項目中使用UEditor。目前,UEditor官網的最後一次版本更新是 1.4.3.3,這已是 2016 年的事情了,而今天的前端開發,不少小夥伴都在使用VueReact 這種組件化的前端框架。這就致使在這些「現代」框架中集成UEditor變得很不平滑。因此纔會有下圖這些大量介紹如何在Vue項目中集成UEditor的博客:php

爲了提升代碼的可複用性,也爲了儘量的不在業務代碼中參雜UEditor的相關操做,我在幾個月前,公司項目的開發中擼了一個組件,能夠經過v-model雙向綁定的方式來使用UEditor,簡單到就像使用input框同樣。當我擼完,感受很是的Vue範兒。並且看了很多博客和GitHub項目,都沒有相似的實現。因而我決定發佈到 npm 上,幫助一衆還在思考如何把UEditor集成到Vue項目中的小夥伴。幾個月下來,基本已經穩定,因此,今天經過這篇博客,分享給你們。css

先看效果圖:html

點擊預覽 倉庫地址前端

Installation

npm i vue-ueditor-wrap
# 或者
yarn add vue-ueditor-wrap
複製代碼

Quick Start

基於 vue-cli 2.x 的完整 DEMO
基於 Nuxt 的服務端渲染 DEMOvue

  1. 下載 UEditorjava

    下載最新編譯的 UEditor。官網目前最新的版本是1.4.3.3,存在諸多 BUG,例如 Issue1,且官方再也不積極維護。爲了世界的和平,針對一些常見 BUG,我進行了修復,並把編譯好的文件放在了本倉庫的 assets/downloads 目錄下,你能夠放心下載,固然你也能夠本身 clone 官方源碼編譯git

    將下載的壓縮包解壓並重命名爲 UEditor(只須要選擇一個你須要的版本,好比 utf8-php),放入你項目的 static 目錄下。github

    若是你使用的是 vue-cli 3.x,能夠把 UEditor 文件夾放入項目的 public 目錄下。web

  2. 引入VueUeditorWrap組件

    import VueUeditorWrap from 'vue-ueditor-wrap' // ES6 Module
    // 或者
    const VueUeditorWrap = require('vue-ueditor-wrap') // CommonJS
    複製代碼

    你也能夠經過直接引入 CDN 連接的方式來使用,它會暴露一個全局的 VueUeditorWrap 變量(具體如何使用你能夠閱讀個人這篇博客或參考這個倉庫)。

    <script src="https://cdn.jsdelivr.net/npm/vue-ueditor-wrap@latest/lib/vue-ueditor-wrap.min.js"></script>
    複製代碼
  3. 註冊組件

    components: {
      VueUeditorWrap
    }
    // 或者在 main.js 裏將它註冊爲全局組件
    Vue.component('vue-ueditor-wrap', VueUeditorWrap)
    複製代碼
  4. v-model綁定數據

    <vue-ueditor-wrap v-model="msg"></vue-ueditor-wrap>
    複製代碼
    data () {
      return {
        msg: '<h2>Vue + UEditor + v-model雙向綁定</h2>'
      }
    }
    複製代碼

    至此你已經能夠在頁面中看到一個初始化以後的 UEditor 了,而且它已經成功和數據綁定了!👏👏👏

  5. 根據項目需求修改配置,完整配置選項查看 ueditor.config.js 源碼或 官方文檔

    <vue-ueditor-wrap v-model="msg" :config="myConfig"></vue-ueditor-wrap>
    複製代碼
    data () {
      return {
        msg: '<h2>Vue + UEditor + v-model雙向綁定</h2>',
        myConfig: {
          // 編輯器不自動被內容撐高
          autoHeightEnabled: false,
          // 初始容器高度
          initialFrameHeight: 240,
          // 初始容器寬度
          initialFrameWidth: '100%',
          // 上傳文件接口(這個地址是我爲了方便各位體驗文件上傳功能搭建的臨時接口,請勿在生產環境使用!!!)
          serverUrl: 'http://35.201.165.105:8000/controller.php',
          // UEditor 資源文件的存放路徑,若是你使用的是 vue-cli 生成的項目,一般不須要設置該選項,vue-ueditor-wrap 會自動處理常見的狀況,若是須要特殊配置,參考下方的常見問題2
          UEDITOR_HOME_URL: '/static/UEditor/'
        }
      }
    }
    複製代碼

Advanced

  1. 如何獲取 UEditor 實例?

    <vue-ueditor-wrap @ready="ready"></vue-ueditor-wrap>
    複製代碼
    methods: {
      ready (editorInstance) {
        console.log(`編輯器實例${editorInstance.key}: `, editorInstance)
      }
    }
    複製代碼
  2. 設置是否在組件的 beforeDestroy 鉤子裏銷燬 UEditor 實例

    <vue-ueditor-wrap :destroy="true"></vue-ueditor-wrap>
    複製代碼
  3. 選取 v-model 的實現方式。雙向綁定的實現依賴對編輯器內容變化的監聽,因爲監聽方式的不一樣,會帶來監聽效果的差別性,你能夠自行選擇,但建議使用開箱即用的默認值。

    <vue-ueditor-wrap mode="listener"></vue-ueditor-wrap>
    複製代碼

    可選值:observerlistener

    默認值:observer

    參數說明:

    1. observer 模式藉助 MutationObserver API。優勢在於監聽的準確性,缺點在於它會帶來一點額外的性能開銷。你能夠經過 observerDebounceTime 屬性設置觸發間隔,還能夠經過 observerOptions 屬性有選擇的設置 MutationObserver 的監聽行爲。該 API 只兼容到 IE11+,但 vue-ueditor-wrap 會在不支持的瀏覽器中自動啓用 listener 模式。

      <vue-ueditor-wrap mode="observer" :observerDebounceTime="100" :observerOptions="{ attributes: true, characterData: true, childList: true, subtree: true }" >
      </vue-ueditor-wrap>
      複製代碼
    2. listener 模式藉助 UEditor 的 contentChange 事件,優勢在於依賴官方提供的事件 API,無需額外的性能消耗,兼容性更好,但缺點在於監聽的準確性並不高,存在以下方 [常見問題 5] 中的提到的 BUG。

  4. 是否支持 Vue SSR

    2.4.0 版本開始支持服務端渲染!本組件提供對 Nuxt 項目開箱即用的支持。但若是你是本身搭建的 Vue SSR 項目,你可能須要自行區分服務端和客戶端環境並結合 forceInit 屬性強制初始化編輯器,但大機率你用不到該屬性,即便是本身搭建的 SSR 項目,更多問題歡迎提交 ISSUE。

  5. 如何進行二次開發(添加自定義按鈕、彈窗等)?

    本組件提供了 beforeInit 鉤子,它會在 UEditor 的 scripts 加載完畢以後、編輯器初始化以前觸發,你能夠在此時機,經過操做 window.UE 對象,來進行諸如添加自定義按鈕、彈窗等的二次開發。beforeInit 的觸發函數以 編輯器 id 和 配置參數 做爲入參。下面提供了一個簡單的自定義按鈕和自定義彈窗的示例,DEMO 倉庫中也提供了自定義「表格居中」按鈕的示例,若是有更多二次開發的需求,你能夠參考官方 API 或者 UEditor 源碼 中的示例。

    自定義按鈕 Demo
    <vue-ueditor-wrap v-model="msg" @beforeInit="addCustomButtom"></vue-ueditor-wrap>
    複製代碼
    addCustomButtom (editorId) {
      window.UE.registerUI('test-button', function (editor, uiName) {
        // 註冊按鈕執行時的 command 命令,使用命令默認就會帶有回退操做
        editor.registerCommand(uiName, {
          execCommand: function () {
            editor.execCommand('inserthtml', `<span>這是一段由自定義按鈕添加的文字</span>`)
          }
        })
    
        // 建立一個 button
        var btn = new window.UE.ui.Button({
          // 按鈕的名字
          name: uiName,
          // 提示
          title: '鼠標懸停時的提示文字',
          // 須要添加的額外樣式,可指定 icon 圖標,圖標路徑參考常見問題 2
          cssRules: "background-image: url('/test-button.png') !important;background-size: cover;",
          // 點擊時執行的命令
          onclick: function () {
            // 這裏能夠不用執行命令,作你本身的操做也可
            editor.execCommand(uiName)
          }
        })
    
        // 當點到編輯內容上時,按鈕要作的狀態反射
        editor.addListener('selectionchange', function () {
          var state = editor.queryCommandState(uiName)
          if (state === -1) {
            btn.setDisabled(true)
            btn.setChecked(false)
          } else {
            btn.setDisabled(false)
            btn.setChecked(state)
          }
        })
    
        // 由於你是添加 button,因此須要返回這個 button
        return btn
      }, 0 /* 指定添加到工具欄上的哪一個位置,默認時追加到最後 */, editorId /* 指定這個 UI 是哪一個編輯器實例上的,默認是頁面上全部的編輯器都會添加這個按鈕 */)
    }
    複製代碼
    自定義彈窗 Demo
    <vue-ueditor-wrap v-model="msg" @beforeInit="addCustomDialog"></vue-ueditor-wrap>
    複製代碼
    addCustomDialog (editorId) {
      window.UE.registerUI('test-dialog', function (editor, uiName) {
        // 建立 dialog
        var dialog = new window.UE.ui.Dialog({
          // 指定彈出層中頁面的路徑,這裏只能支持頁面,路徑參考常見問題 2
          iframeUrl: '/customizeDialogPage.html',
          // 須要指定當前的編輯器實例
          editor: editor,
          // 指定 dialog 的名字
          name: uiName,
          // dialog 的標題
          title: '這是一個自定義的 Dialog 浮層',
          // 指定 dialog 的外圍樣式
          cssRules: 'width:600px;height:300px;',
          // 若是給出了 buttons 就表明 dialog 有肯定和取消
          buttons: [
            {
              className: 'edui-okbutton',
              label: '肯定',
              onclick: function () {
                dialog.close(true)
              }
            },
            {
              className: 'edui-cancelbutton',
              label: '取消',
              onclick: function () {
                dialog.close(false)
              }
            }
          ]
        })
    
        // 參考上面的自定義按鈕
        var btn = new window.UE.ui.Button({
          name: 'dialog-button',
          title: '鼠標懸停時的提示文字',
          cssRules: `background-image: url('/test-dialog.png') !important;background-size: cover;`,
          onclick: function () {
            // 渲染dialog
            dialog.render()
            dialog.open()
          }
        })
    
        return btn
      }, 0 /* 指定添加到工具欄上的那個位置,默認時追加到最後 */, editorId /* 指定這個UI是哪一個編輯器實例上的,默認是頁面上全部的編輯器都會添加這個按鈕 */)
    }
    複製代碼

    彈出層中的 HTML 頁面 customizeDialogPage.html

    <!DOCTYPE html>
    <html>
    
    <head>
      <meta charset="UTF-8">
      <title>Title</title>
      <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
      <meta name="renderer" content="webkit">
      <!--頁面中必定要引入internal.js爲了能直接使用當前打開dialog的實例變量-->
      <!--internal.js默認是放到 UEditor/dialogs 目錄下的-->
      <script type="text/javascript" src="./UEditor/dialogs/internal.js"></script>
    </head>
    
    <body>
      <h1>hello vue-ueditor-wrap</h1>
      <script> //能夠直接使用如下全局變量 //當前打開dialog的實例變量 console.log('editor: ' + editor); //一些經常使用工具 console.log('domUtils: ' + domUtils); console.log('utils: ' + utils); console.log('browser: ' + browser); dialog.onok = function() { editor.execCommand('inserthtml', '<span>我點擊了肯定</span>'); }; dialog.oncancel = function() { editor.execCommand('inserthtml', '<span>我點擊了取消</span>'); }; </script>
    </body>
    
    </html>
    複製代碼

Features

  1. v-model 雙向數據綁定!你不須要考慮實例化,也不須要考慮什麼時候 getContent,什麼時候setContent,簡單到像使用 input 框同樣!

  2. 徹底聽從官方 API,全部的配置參數和實例方法與官方徹底一致。經過給 vue-ueditor-wrap 組件的 config 屬性傳遞一個對象,你就能夠獲得一個徹底獨立配置的 UEditor 編輯器。經過監聽 ready 事件你就能夠獲得初始化後的 UEditor 實例並執行實例上的各類方法。

  3. 自動添加依賴文件。你不須要本身在 index.htmlmain.js 裏引入 UEditor 的 JS 文件。更重要的是即便你在一個頁面裏同時使用多個 vue-ueditor-wrap 組件,它所依賴的 JS 文件也只會加載一次。這麼作的緣由在於你不須要當用戶一打開項目就先加載大量 UEditor 相關的資源,全部的資源文件只會在 vue-ueditor-wrap 組件第一次被激活時才加載。固然,若是你在 index.htmlmain.js 裏引入了相關資源,vue-ueditor-wrap 也會準確判斷,你不用擔憂它會重複加載。

  4. 每一個 vue-ueditor-wrap 組件是徹底獨立的。你甚至能夠在上面使用 v-for 指令一次渲染 99個 兔斯基(不要忘記添加 key 值)。

FAQ(常見問題)

  1. 是否支持 IE 等低版本瀏覽器?

    Vue 相同,總體支持到 IE9+👏👏👏

  2. 爲何我會看到這個報錯?

    這是 UEDITOR_HOME_URL 參數配置錯誤致使的。在 vue cli 2.x 生成的項目中使用本組件,默認值是 '/static/UEditor/',在 vue cli 3.x 生成的項目中,默認值是 process.env.BASE_URL + 'UEditor/' 。但這並不能知足全部狀況。例如你的項目不是部署在網站根目錄下,如"http://www.example.com/my-app/",你可能須要設置爲"/my-app/static/UEditor/"。是否使用了相對路徑、路由是否使用 history 模式、服務器配置是否正確等等都有可能會產生影響。總而言之:不管本地開發和部署到服務器,你所指定的 UEditor 資源文件是須要真實存在的,vue-ueditor-wrap 也會在 JS 加載失敗時經過 console 輸出它試圖去加載的資源文件的完整路徑,你能夠藉此分析如何填寫。當須要區分環境時,你能夠經過判斷 process.env.NODE_ENV 來分別設置。

  3. 我該如何上傳圖片和文件?爲何我會看到後臺配置項返回格式出錯

    上傳圖片、文件等功能是須要與後臺配合的,而你沒有給 config 屬性傳遞正確的 serverUrl ,我提供了http://35.201.165.105:8000/controller.php 的臨時接口,你能夠用於測試,但切忌在生產環境使用!!! 關於如何搭建上傳接口,能夠參考官方文檔

  4. 單圖片跨域上傳失敗!

    UEditor 的單圖上傳是經過 Form 表單 + iframe 的方式實現的,但因爲同源策略的限制,父頁面沒法訪問跨域 iframe 的文檔內容,因此會出現單圖片跨域上傳失敗的問題。我經過 XHR 重構了單圖上傳的方式,下載最新編譯的 UEditor 資源文件便可在 IE10+ 的瀏覽器中實現單圖跨域上傳了。具體細節,點此查看。固然你也能夠經過配置 toolbars 參數來隱藏單圖片上傳按鈕,並結合上面介紹的「自定義按鈕」,曲線救國,如下代碼僅供參考。

    var input = document.createElement('input')
    input.type = "file"
    input.style.display = 'none'
    document.body.appendChild(input)
    input.click()
    input.addEventListener('change',(e)=>{
        // 利用 AJAX 上傳,上傳成功以後銷燬 DOM
        console.log(e.target.files)
    })
    複製代碼
  5. 爲何我輸入的"? ! $ #" 這些特殊字符,沒有成功綁定?

    當你使用 listener 模式時,因爲 v-model 的實現是基於對 UEditor 實例上 contentChange 事件的監聽,而你輸入這些特殊字符時一般是按住 shift 鍵的,UEditor 自己的 contentChangeshift 鍵按住時不會觸發,你也能夠嘗試同時按下多個鍵,你會發現 contentChange 只觸發一次。你可使用 observer 模式或移步 UEditor

  6. 單圖片上傳後 v-model 綁定的是 loading 小圖標。

    這個也是 UEditorBUG。我最新編輯的版本,修復了官方的這個 BUG,若是你使用的是官網下載的資源文件,請替換資源文件或參考 Issue1

更多問題,歡迎提交 ISSUE 或者去 聊天室 提問。但因爲這是一個我的維護的項目,我平時也有本身的工做,因此並不能保證及時解決大家的全部問題,若是小夥伴們有好的建議或更炫酷的操做,也歡迎 PR,若是你以爲這個組件給你的開發帶來了實實在在的方便,也很是感謝你的Star,固然還有咖啡:

代碼修改請遵循指定的 ESLint 規則,PR 以前請先執行 npm run lint 進行代碼風格檢測,大部分語法細節能夠經過 npm run fix 修正,構建以後,記得修改 package.json 裏的版本號,方便我 Review 經過後麻溜溜的發佈到 npm

總結

雖然這是一次很小的創新,UEditor也多是一個過氣的富文本編輯器。可是在維護這個項目以及幫助一衆小夥伴解決ISSUE的過程當中,我成長了不少。最令我感動的是很多小夥伴還給我郵箱發了感謝信,並且我還發現確實已經有一些人開始在項目中用了。這種被他人承認,以及幫助別人的快樂真的只有體會過的人才知道。也就在前不久,我決定開始在掘金寫博客,雖然一些東西寫的不那麼好,或者本身認知有錯誤,但總有一羣熱心且優秀的小夥伴,會在評論區指正以及給出寶貴的意見。分享是快樂的!因此,個人這篇文章也權當拋磚引玉,若是小夥伴們有好的建議或更炫酷的操做,也歡迎PR,不過PR以前請先執行npm run lint進行代碼風格檢測,大部分語法細節也能夠經過npm run fix修正,也要記得修改package.json的版本號version,方便我直接發佈到npm。固然若是你有好用的富文本編輯器,也能夠在評論區推薦。

相關文章
相關標籤/搜索