.net core vue+wangEditor (双向绑定) 上传图片和视频功能
最终效果,是这样的,现在开始记录怎么做:
开始 npm 安装 wangEditor
安装好后,
因为要用vue 双向绑定 ,所以 我就把wangwangEditor 做成了一个封装组件,先看一下目录 :
我是把wangEditor写在了my-components这个项目下,新建一个 vue组件,代码如下:
<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>
然后,再在主页面上,引用 ,和感觉和后端差不多
<template>
<editorElem :catchData="catchData" :content="channelForm.content"></editorElem> (:content 这里就是双向绑定)
</template>
import editorElem from "../../my-components/Editor.vue";
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