最終效果,是這樣的,如今開始記錄怎麼作:html
開始 npm 安裝 wangEditorvue
安裝好後,node
由於要用vue 雙向綁定 ,因此 我就把wangwangEditor 作成了一個封裝組件,先看一下目錄 :web
我是把wangEditor寫在了my-components這個項目下,新建一個 vue組件,代碼以下:chrome
<template> <div id="wangeditor"> <div ref="editorElem" style="text-align:left"></div> </div> </template> <script> import E from 'wangeditor' export default { name: 'editorElem', data() { return { editor: null, editorContent: '' } }, props: ['catchData', 'content'], // 接收父組件的方法 watch: { content() { this.editor.txt.html(this.content) } }, mounted() { var imgUrl = ""; this.editor = new E(this.$refs.editorElem) this.editor.customConfig.onchange = (html) => { this.editorContent = html this.catchData(this.editorContent) // 把這個html經過catchData的方法傳入父組件 } this.editor.customConfig.uploadImgServer = '/api/Media/OnPostUpload' this.editor.customConfig.uploadVideoServer="/api/Media/OnPostUploadVideo" // 或 /node_modules/wangeditor/release/wangEditor.js 裏直接寫上傳視頻接口 // 下面是最重要的的方法 this.editor.customConfig.uploadImgHooks = { before: function (xhr, editor, files) { // 圖片上傳以前觸發 // xhr 是 XMLHttpRequst 對象,editor 是編輯器對象,files 是選擇的圖片文件 // 若是返回的結果是 {prevent: true, msg: 'xxxx'} 則表示用戶放棄上傳 // return { // prevent: true, // msg: '放棄上傳' // } }, success: function (xhr, editor, result) { // 圖片上傳並返回結果,圖片插入成功以後觸發 // xhr 是 XMLHttpRequst 對象,editor 是編輯器對象,result 是服務器端返回的結果 this.imgUrl = Object.values(result.data).toString() }, fail: function (xhr, editor, result) { debugger; var res = xhr.data; // 圖片上傳並返回結果,但圖片插入錯誤時觸發 // xhr 是 XMLHttpRequst 對象,editor 是編輯器對象,result 是服務器端返回的結果 }, error: function (xhr, editor) { debugger; // 圖片上傳出錯時觸發 // xhr 是 XMLHttpRequst 對象,editor 是編輯器對象 }, timeout: function (xhr, editor) { // 圖片上傳超時時觸發 // xhr 是 XMLHttpRequst 對象,editor 是編輯器對象 }, // 若是服務器端返回的不是 {errno:0, data: [...]} 這種格式,可以使用該配置 // (可是,服務器端返回的必須是一個 JSON 格式字符串!!!不然會報錯) customInsert: function (insertImg, result, editor) { // 圖片上傳並返回結果,自定義插入圖片的事件(而不是編輯器自動插入圖片!!!) // insertImg 是插入圖片的函數,editor 是編輯器對象,result 是服務器端返回的結果 // 舉例:假如上傳圖片成功後,服務器端返回的是 {url:'....'} 這種格式,便可這樣插入圖片: let url = Object.values(result.data) // result.data就是服務器返回的圖片名字和連接 JSON.stringify(url) // 在這裏轉成JSON格式 insertImg(url) // result 必須是一個 JSON 格式字符串!!!不然報錯 }, }; this.editor.customConfig.debug = true; this.editor.create() // 建立富文本實例 if (!this.content) { this.editor.txt.html('請編輯內容1') } } } </script>
而後,再在主頁面上,引用 ,和感受和後端差很少npm
<template>
<editorElem :catchData="catchData" :content="channelForm.content"></editorElem> (:content 這裏就是雙向綁定)
</template>後端
import editorElem from "../../my-components/Editor.vue";api
export default {
name: "editor",
components: {
editorElem
}跨域
}服務器
接着,開始改wangEidotr的視頻上傳代碼,原wangEidotr上傳視頻用的不怎麼好,因此我就去網上找了大神的( https://blog.csdn.net/m0_37885651/article/details/83660206 )代碼修改了一下,
先找到wangEidotr.js
// /* menu - video */ // 構造函數 function Video(editor) { this.editor = editor; this.$elem = $('<div class="w-e-menu"><i class="w-e-icon-play"><i/></div>'); this.type = 'panel'; // 當前是否 active 狀態 this._active = false; } // 原型 Video.prototype = { constructor: Video, onClick: function onClick() { this._createInsertPanel(); }, _createInsertPanel: function _createInsertPanel() { var editor = this.editor; var uploadVideo = editor.uploadVideo; var config = editor.config; // id var upTriggerId = getRandom('up-trigger'); var upFileId = getRandom('up-file'); // tabs 的配置 var tabsConfig = [{ title: '上傳 video', tpl: '<div class="w-e-up-img-container">\n ' + '<div id="' + upTriggerId + '" class="w-e-up-btn">\n ' + '<i class="w-e-icon-upload2"></i>\n </div>\n ' + '<div style="display:none;">\n <input id="' + upFileId + '" type="file" multiple="multiple" accept="audio/mp4, video/mp4"/>\n ' + '</div>\n </div>', events: [{ // 觸發選擇視頻 selector: '#' + upTriggerId, type: 'click', fn: function fn() { var $file = $('#' + upFileId); var fileElem = $file[0]; if (fileElem) { fileElem.click(); } else { // 返回 true 可關閉 panel return true; } } }, { // 選擇視頻完畢 selector: '#' + upFileId, type: 'change', fn: function fn() { var $file = $('#' + upFileId); var fileElem = $file[0]; if (!fileElem) { // 返回 true 可關閉 panel return true; } // 獲取選中的 file 對象列表 var fileList = fileElem.files; if (fileList.length) { uploadVideo.uploadVideo(fileList); } // 返回 true 可關閉 panel return true; } }] } ]; // tabs end // 判斷 tabs 的顯示 var tabsConfigResult = []; tabsConfigResult.push(tabsConfig[0]); // 建立 panel 並顯示 var panel = new Panel(this, { width: 300, tabs: tabsConfigResult }); panel.show(); // 記錄屬性 this.panel = panel; }, // 試圖改變 active 狀態 tryChangeActive: function tryChangeActive(e) { var editor = this.editor; var $elem = this.$elem; if (editor._selectedImg) { this._active = true; $elem.addClass('w-e-active'); } else { this._active = false; $elem.removeClass('w-e-active'); } } }; /* 全部菜單的彙總 */ // 存儲菜單的構造函數 var MenuConstructors = {}; MenuConstructors.video = Video;
// 構造函數 function UploadVideo(editor) { this.editor = editor; } // 原型 UploadVideo.prototype = { constructor: UploadVideo, // 根據 debug 彈出不一樣的信息 _alert: function _alert(alertInfo, debugInfo) { var editor = this.editor; var debug = editor.config.debug; // var debug = true; var customAlert = editor.config.customAlert; if (debug) { throw new Error('wangEditor: ' + (debugInfo || alertInfo)); } else { if (customAlert && typeof customAlert === 'function') { customAlert(alertInfo); } else { alert(alertInfo); } } }, //插入視頻的方法 須要單獨定義 insertLinkVideo:function(link){ var _this3 = this; if (!link) { return; } var editor = this.editor; var config = editor.config; // 校驗格式 var linkVideoCheck = config.linkVideoCheck; var checkResult = void 0; if (linkVideoCheck && linkVideoCheck === 'function') { checkResult = linkVideoCheck(link); if (typeof checkResult === 'string') { // 校驗失敗,提示信息 alert(checkResult); return; } } editor.cmd.do('insertHTML', '<video src="' + link + '" style="width:50%;height: 50%;" controls autobuffer autoplay muted/>'); // 驗證視頻 url 是否有效,無效的話給出提示 var video = document.createElement('video'); video.onload = function () { var callback = config.linkVideoCallback; if (callback && typeof callback === 'function') { callback(link); } video = null; }; video.onerror = function () { video = null; // 沒法成功下載圖片 _this2._alert('插入視頻錯誤', 'wangEditor: \u63D2\u5165\u56FE\u7247\u51FA\u9519\uFF0C\u56FE\u7247\u94FE\u63A5\u662F "' + link + '"\uFF0C\u4E0B\u8F7D\u8BE5\u94FE\u63A5\u5931\u8D25'); return; }; video.onabort = function () { video = null; }; video.src = link; }, // 上傳視頻 uploadVideo: function uploadVideo(files) { var _this3 = this; if (!files || !files.length) { return; } // ------------------------------ 獲取配置信息 ------------------------------ var editor = this.editor; var config = editor.config; var uploadVideoServer = "/video/uploadVideo";//上傳地址 var maxSize = 100 * 1024 * 1024; //100M var maxSizeM = maxSize / 1000 / 1000; var maxLength = 1; var uploadFileName = "file"; var uploadVideoParams = config.uploadVideoParams || {}; var uploadVideoHeaders = {}; var hooks =config.uploadImgHooks || {}; var timeout = 5 * 60 * 1000; //5 min var withCredentials = config.withCredentials; if (withCredentials == null) { withCredentials = false; } // ------------------------------ 驗證文件信息 ------------------------------ var resultFiles = []; var errInfo = []; arrForEach(files, function (file) { var name = file.name; var size = file.size; // chrome 低版本 name === undefined if (!name || !size) { return; } if (/\.(mp4)$/i.test(name) === false) { // 後綴名不合法,不是視頻 errInfo.push('\u3010' + name + '\u3011\u4e0d\u662f\u89c6\u9891'); return; } if (maxSize < size) { // 上傳視頻過大 errInfo.push('\u3010' + name + '\u3011\u5927\u4E8E ' + maxSizeM + 'M'); return; } // 驗證經過的加入結果列表 resultFiles.push(file); }); // 拋出驗證信息 if (errInfo.length) { this._alert('視頻驗證未經過: \n' + errInfo.join('\n')); return; } if (resultFiles.length > maxLength) { this._alert('一次最多上傳' + maxLength + '個視頻'); return; } // ------------------------------ 自定義上傳 ------------------------------ // 添加視頻數據 var formdata = new FormData(); arrForEach(resultFiles, function (file) { var name = uploadFileName || file.name; formdata.append(name, file); }); // ------------------------------ 上傳視頻 ------------------------------ if (uploadVideoServer && typeof uploadVideoServer === 'string') { // 添加參數 var uploadVideoServer = uploadVideoServer.split('#'); uploadVideoServer = uploadVideoServer[0]; var uploadVideoServerHash = uploadVideoServer[1] || ''; objForEach(uploadVideoParams, function (key, val) { val = encodeURIComponent(val); // 第一,將參數拼接到 url 中 if (uploadVideoParamsWithUrl) { if (uploadVideoServer.indexOf('?') > 0) { uploadVideoServer += '&'; } else { uploadVideoServer += '?'; } uploadVideoServer = uploadVideoServer + key + '=' + val; } // 第二,將參數添加到 formdata 中 formdata.append(key, val); }); if (uploadVideoServerHash) { uploadVideoServer += '#' + uploadVideoServerHash; } // 定義 xhr var xhr = new XMLHttpRequest(); xhr.open('POST', uploadVideoServer); // 設置超時 xhr.timeout = timeout; xhr.ontimeout = function () { // hook - timeout if (hooks.timeout && typeof hooks.timeout === 'function') { hooks.timeout(xhr, editor); } _this3._alert('上傳視頻超時'); }; // 監控 progress if (xhr.upload) { xhr.upload.onprogress = function (e) { var percent = void 0; // 進度條 var progressBar = new Progress(editor); if (e.lengthComputable) { percent = e.loaded / e.total; progressBar.show(percent); } }; } // 返回數據 xhr.onreadystatechange = function () { var result = void 0; if (xhr.readyState === 4) { if (xhr.status < 200 || xhr.status >= 300) { // hook - error if (hooks.error && typeof hooks.error === 'function') { hooks.error(xhr, editor); } // xhr 返回狀態錯誤 _this3._alert('上傳視頻發生錯誤', '\u4E0A\u4F20\u56FE\u7247\u53D1\u751F\u9519\u8BEF\uFF0C\u670D\u52A1\u5668\u8FD4\u56DE\u72B6\u6001\u662F ' + xhr.status); return; } result = xhr.responseText; if ((typeof result === 'undefined' ? 'undefined' : _typeof(result)) !== 'object') { try { console.log(result); result = JSON.parse(result); } catch (ex) { // hook - fail if (hooks.fail && typeof hooks.fail === 'function') { hooks.fail(xhr, editor, result); } _this3._alert('上傳視頻失敗', '上傳視頻返回結果錯誤,返回結果是: ' + result); return; } } if (!hooks.customInsert && result.errno == '0') { // hook - fail if (hooks.fail && typeof hooks.fail === 'function') { hooks.fail(xhr, editor, result); } // 數據錯誤 _this3._alert('上傳視頻失敗', '上傳視頻返回結果錯誤,返回結果 errno=' + result.errno); } else { if (hooks.customInsert && typeof hooks.customInsert === 'function') { hooks.customInsert(_this3.insertLinkVideo.bind(_this3), result, editor); } else { // 將視頻插入編輯器 var data = result || []; // data.forEach(function (link) { // console.log(link); // // }); _this3.insertLinkVideo(data.url); } // hook - success if (hooks.success && typeof hooks.success === 'function') { hooks.success(xhr, editor, result); } } } }; // hook - before if (hooks.before && typeof hooks.before === 'function') { var beforeResult = hooks.before(xhr, editor, resultFiles); if (beforeResult && (typeof beforeResult === 'undefined' ? 'undefined' : _typeof(beforeResult)) === 'object') { if (beforeResult.prevent) { // 若是返回的結果是 {prevent: true, msg: 'xxxx'} 則表示用戶放棄上傳 this._alert(beforeResult.msg); return; } } } // 自定義 headers objForEach(uploadVideoHeaders, function (key, val) { xhr.setRequestHeader(key, val); }); // 跨域傳 cookie xhr.withCredentials = withCredentials; // 發送請求 xhr.send(formdata); // 注意,要 return 。不去操做接下來的 base64 顯示方式 return; } } };
// 修改原型 Editor.prototype = { constructor: Editor, // 添加視頻上傳 _initUploadVideo: function _initUploadVideo() { this.uploadVideo = new UploadVideo(this); }, // 建立編輯器 create: function create() { // 添加 視頻上傳 this._initUploadVideo(); }, };
1,源碼裏找到Video 的構造函數 還有它的prototype,替換成第一部分。(有版本是同樣的,能夠不替換)
2,找到UploadImg的構造函數,還有它的prototype,這是上傳圖片的,相應的 你在這後面 加上第二部分UploadVideo的構造和原型,這是專門寫的上傳視頻的。
3,在Editor原型上加個方法,_initUploadVideo , 而後在 建立編輯器 create 裏面執行 this._initUploadVideo() ,加上就能夠。
下面是後端代碼:
#region 上傳圖片 OnPostUpload [HttpPost] public async Task<IActionResult> OnPostUpload([FromServices]IHostingEnvironment environment) { List<string> fileUrl = new List<string>(); var files = Request.Form.Files; if (string.IsNullOrWhiteSpace(environment.WebRootPath)) { environment.WebRootPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot"); } string webRootPath = environment.WebRootPath; string filePath = Path.Combine(webRootPath + "\\upload\\images"); if (!Directory.Exists(filePath)) { Directory.CreateDirectory(filePath); } foreach (var formFile in files) { if (formFile.Length > 0) { var ext = Path.GetExtension(formFile.FileName); if (!pictureFormatArray.Contains(ext.Split('.')[1])) { return new JsonResult(TmpUrl.ErrorInfo("圖片格式不正確!", null)); } var fileName = Guid.NewGuid().ToString() + ext; var path = Path.Combine(webRootPath + "\\upload\\images", fileName); using (var stream = new FileStream(path, FileMode.CreateNew)) { await formFile.CopyToAsync(stream); fileUrl.Add($"/api/Media/ShowNoticeImg?filePath={fileName}"); } } } return new JsonResult(TmpUrl.SuccessInfo("ok!", fileUrl)); } #endregion #region 上傳視頻 OnPostUploadVideo [HttpPost] public async Task<IActionResult> OnPostUploadVideo([FromServices]IHostingEnvironment environment) { List<string> fileUrl = new List<string>(); var files = Request.Form.Files; if (string.IsNullOrWhiteSpace(environment.WebRootPath)) { environment.WebRootPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot"); } string webRootPath = environment.WebRootPath; string filePath = Path.Combine(webRootPath + "\\upload\\videos"); if (!Directory.Exists(filePath)) { Directory.CreateDirectory(filePath); } foreach (var formFile in files) { if (formFile.Length > 0) { var ext = Path.GetExtension(formFile.FileName); if (!videoFormatArray.Contains(ext.Split('.')[1])) { return new JsonResult(TmpUrl.ErrorInfo("視頻格式不正確!", null)); } var fileName = Guid.NewGuid().ToString() + ext; var path = Path.Combine(webRootPath + "\\upload\\videos", fileName); using (var stream = new FileStream(path, FileMode.CreateNew)) { await formFile.CopyToAsync(stream); //fileUrl.Add($"/upload/videos/{fileName}"); fileUrl.Add("http://localhost:15429/upload/videos/8e11ae8e-8ecc-4b7c-afac-43601530493f.mp4"); } } } return new JsonResult(TmpUrl.SuccessInfo("ok!", fileUrl)); } #endregion #region 獲取圖片流 ShowNoticeImg public IActionResult ShowNoticeImg(string filePath) { var contentTypeStr = "image/jpeg"; string webRootPath = Path.Combine(Directory.GetCurrentDirectory(), $"wwwroot\\upload\\images\\{filePath}"); using (var sw = new FileStream(webRootPath, FileMode.Open)) { var bytes = new byte[sw.Length]; sw.Read(bytes, 0, bytes.Length); sw.Close(); return new FileContentResult(bytes, contentTypeStr); } } #endregion