最终效果,是这样的,现在开始记录怎么做:

开始 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[];
if (fileElem) {
fileElem.click();
} else {
// 返回 true 可关闭 panel
return true;
}
}
}, {
// 选择视频完毕
selector: '#' + upFileId,
type: 'change',
fn: function fn() {
var $file = $('#' + upFileId);
var fileElem = $file[];
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[]); // 创建 panel 并显示
var panel = new Panel(this, {
width: ,
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 ;
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 = * * ; //100M
var maxSizeM = maxSize / / ;
var maxLength = ;
var uploadFileName = "file";
var uploadVideoParams = config.uploadVideoParams || {};
var uploadVideoHeaders = {};
var hooks =config.uploadImgHooks || {};
var timeout = * * ; //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[];
var uploadVideoServerHash = uploadVideoServer[] || '';
objForEach(uploadVideoParams, function (key, val) {
val = encodeURIComponent(val); // 第一,将参数拼接到 url 中
if (uploadVideoParamsWithUrl) {
if (uploadVideoServer.indexOf('?') > ) {
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 ;
// 进度条
var progressBar = new Progress(editor);
if (e.lengthComputable) {
percent = e.loaded / e.total;
progressBar.show(percent);
}
};
} // 返回数据
xhr.onreadystatechange = function () {
var result = void ;
if (xhr.readyState === ) {
if (xhr.status < || xhr.status >= ) {
// 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 == '') {
// 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 > )
{
var ext = Path.GetExtension(formFile.FileName);
if (!pictureFormatArray.Contains(ext.Split('.')[]))
{
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 > )
{
var ext = Path.GetExtension(formFile.FileName);
if (!videoFormatArray.Contains(ext.Split('.')[]))
{
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, , bytes.Length);
sw.Close();
return new FileContentResult(bytes, contentTypeStr);
}
}
#endregion

.net core vue+wangEditor (双向绑定) 上传图片和视频功能的更多相关文章

  1. Vue.js双向绑定的实现原理

    Vue.js最核心的功能有两个,一是响应式的数据绑定系统,二是组件系统.本文仅探究几乎所有Vue的开篇介绍都会提到的hello world双向绑定是怎样实现的.先讲涉及的知识点,再参考源码,用尽可能少 ...

  2. Vue.js双向绑定的实现原理和模板引擎实现原理(##########################################)

    Vue.js双向绑定的实现原理 解析 神奇的 Object.defineProperty 这个方法了不起啊..vue.js和avalon.js 都是通过它实现双向绑定的..而且Object.obser ...

  3. vue的双向绑定原理及实现

    前言 使用vue也好有一段时间了,虽然对其双向绑定原理也有了解个大概,但也没好好探究下其原理实现,所以这次特意花了几晚时间查阅资料和阅读相关源码,自己也实现一个简单版vue的双向绑定版本,先上个成果图 ...

  4. 西安电话面试:谈谈Vue数据双向绑定原理,看看你的回答能打几分

    最近我参加了一次来自西安的电话面试(第二轮,技术面),是大厂还是小作坊我在这里按下不表,先来说说这次电面给我留下印象较深的几道面试题,这次先来谈谈Vue的数据双向绑定原理. 情景再现: 当我手机铃声响 ...

  5. Vue数据双向绑定原理及简单实现

    嘿,Goodgirl and GoodBoy,点进来了就看完点个赞再go. Vue这个框架就不简单介绍了,它最大的特性就是数据的双向绑定以及虚拟dom.核心就是用数据来驱动视图层的改变.先看一段代码. ...

  6. vue数据双向绑定

    Vue的双向绑定是通过数据劫持结合发布-订阅者模式实现的,即通过Object.defineProperty监听各个属性的setter,然后通知订阅者属性发生变化,触发相应的回调. 整个过程分为以下几步 ...

  7. 【学习笔记】剖析MVVM框架,简单实现Vue数据双向绑定

    前言: 学习前端也有半年多了,个人的学习欲望还比较强烈,很喜欢那种新知识在自己的演练下一点点实现的过程.最近一直在学vue框架,像网上大佬说的,入门容易深究难.不管是跟着开发文档学还是视频教程,按步骤 ...

  8. vue数据双向绑定原理

    vue的数据双向绑定的小例子: .html <!DOCTYPE html> <html> <head> <meta charset=utf-> < ...

  9. Vue.js双向绑定原理

    Vue.js最核心的功能有两个,一个是响应式的数据绑定系统,另一个是组件系统.本文仅仅探究双向绑定是怎样实现的.先讲涉及的知识点,再用简化的代码实现一个简单的hello world示例. 一.访问器属 ...

随机推荐

  1. 新手入门必看:VectorDraw 常见问题整理大全(一)

    VectorDraw Developer Framework(VDF)是一个用于应用程序可视化的图形引擎库.有了VDF提供的功能,您可以轻松地创建.编辑.管理.输出.输入和打印2D和3D图形文件.该库 ...

  2. 困扰了2天的问题,终于解决了。VB6的MSComCtl.ocx在32位Win7显示对象库未注册

    解决方案在这里,中文的资料真的挺垃圾的.(重启几次之后又不行了....怎么回事???) 安装.net framework4.0以上版本, C:\Windows\System32, C:\Windows ...

  3. odoo10学习笔记十:Actions

    转载请注明原文地址:https://www.cnblogs.com/ygj0930/p/11189319.html actions定义了系统对于用户的操作的响应:登录.按钮.选择项目等. 一:窗口ac ...

  4. webpack开发指南1

    怎么安装Webpack 安装node.js 首先需要安装Node.js,node自带了包管理工具npm. 安装webpack 使用npm install webpack -g,webpack全局安装到 ...

  5. 69.x的平方根 (平)(简单)

    实现 int sqrt(int x) 函数. 计算并返回 x 的平方根,其中 x 是非负整数. 由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去. 示例 1: 输入: 4输出: 2示例 2: ...

  6. Nginx的代理配置(六)

    一.正向代理 1. 指令说明 (1) resolver 这个用于设置DNS服务器的ip .DNS服务器的主要工作是进行域名解析,将域名映射为对应IP地址. 语法:resolver address .. ...

  7. 生产器&迭代器

    生成器 列表生成器:简洁代码 >>> a = [i+1 for i in range(10)] >>> a [1, 2, 3, 4, 5, 6, 7, 8, 9, ...

  8. 利用Tengine在树莓派上跑深度学习网络

    树莓派是国内比较流行的一款卡片式计算机,但是受限于其硬件配置,用树莓派玩深度学习似乎有些艰难.最近OPENAI为嵌入式设备推出了一款AI框架Tengine,其对于配置的要求相比传统框架降低了很多,我尝 ...

  9. 基于CAS实现无锁结构

    杨乾成 2017310500302 一.题目要求 基于CAS(Compare and Swap)实现一个无锁结构,可考虑queue,stack,hashmap,freelist等. 能够支持多个线程同 ...

  10. TCP三次握手及四次断开,TCP有限状态机

    TCP 的连接建立 上图画出了 TCP 建立连接的过程.假定主机 A 是 TCP 客户端,B是服务端.最初两端的 TCP 进程都处于 CLOSED 状态.图中在主机下面的是 TCP进程所处的状态.A ...