最终效果,是这样的,现在开始记录怎么做:
开始 npm 安装 wangEditor
安装好后,
因为要用vue 双向绑定 ,所以 我就把wangwangEditor 做成了一个封装组件,先看一下目录 :
我是把wangEditor写在了my-components这个项目下,新建一个 vue组件,代码如下:
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')
}
}
}
然后,再在主页面上,引用 ,和感觉和后端差不多
(:content 这里就是双向绑定)
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 = $('
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:'
'
'\n
'
'
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', '');//验证视频 url 是否有效,无效的话给出提示
var video = document.createElement('video');
video.οnlοad=function () {var callback =config.linkVideoCallback;if (callback && typeof callback === 'function') {
callback(link);
}
video= null;
};
video.οnerrοr=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.οnabοrt=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
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 = newFormData();
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 = newXMLHttpRequest();
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.οnprοgress=function (e) {var percent = void 0;//进度条
var progressBar = newProgress(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 TaskOnPostUpload([FromServices]IHostingEnvironment environment)
{
List fileUrl = new List();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 infiles)
{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 = newFileStream(path, FileMode.CreateNew))
{awaitformFile.CopyToAsync(stream);
fileUrl.Add($"/api/Media/ShowNoticeImg?filePath={fileName}");
}
}
}return new JsonResult(TmpUrl.SuccessInfo("ok!", fileUrl));
}#endregion
#region 上传视频 OnPostUploadVideo[HttpPost]public async TaskOnPostUploadVideo([FromServices]IHostingEnvironment environment)
{
List fileUrl = new List();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 infiles)
{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 = newFileStream(path, FileMode.CreateNew))
{awaitformFile.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(stringfilePath)
{var contentTypeStr = "image/jpeg";string webRootPath = Path.Combine(Directory.GetCurrentDirectory(), $"wwwroot\\upload\\images\\{filePath}");using (var sw = newFileStream(webRootPath, FileMode.Open))
{var bytes = new byte[sw.Length];
sw.Read(bytes,0, bytes.Length);
sw.Close();return newFileContentResult(bytes, contentTypeStr);
}
}#endregion