近期公司用到了wangEditor作为富文本使用,但是出现了点问题,原来的wangEditor插件只支持网络图片和网络视频的插入,现在要求新增本地图片和视频的导入,于是特地去查看了手册:wangEditor3使用手册;
关于图片上传,插件用起来很简单:手册是这么介绍的
上传图片tab:
<div id="div1">
<p>欢迎使用 wangEditor 富文本编辑器</p>
</div>
<script type="text/javascript" src="/wangEditor.min.js"></script>
<script type="text/javascript">
var E = window.wangEditor
var editor = new E('#div1')
// 下面两个配置,使用其中一个即可显示“上传图片”的tab。但是两者不要同时使用!!!
// editor.customConfig.uploadImgShowBase64 = true // 使用 base64 保存图片
// editor.customConfig.uploadImgServer = '/upload' // 上传图片到服务器
editor.create()
</script>
如果图片使用base64 保存的话,直接editor.customConfig.uploadImgShowBase64 = true 放开就行;
要是上传到服务器的话,后台要进行处理,并且要返回一个图片的url。
而且要以 map形式返回,map中存放data 和errno 即url和错误类型 如下:
其中/upload是上传图片的服务器端接口,接口返回的数据格式如下(实际返回数据时,不要加任何注释!!!)
{
// errno 即错误代码,0 表示没有错误。
// 如果有错误,errno != 0,可通过下文中的监听函数 fail 拿到该错误码进行自定义处理
"errno": 0,
// data 是一个数组,返回若干图片的线上地址
"data": [
"图片1地址",
"图片2地址",
"……"
]
}
隐藏“网络图片”tab
默认情况下,“网络图片”tab是一直存在的。如果不需要,可以参考一下示例来隐藏它。
<div id="div1">
<p>欢迎使用 wangEditor 富文本编辑器</p>
</div>
<script type="text/javascript" src="/wangEditor.min.js"></script>
<script type="text/javascript">
var E = window.wangEditor
var editor = new E('#div1')
// 隐藏“网络图片”tab
editor.customConfig.showLinkImg = false
editor.create()
</script>
重点来了,wangEditor3只支持网络视频的插入,并且要以 iframe 形式上传,要是上传本地视频,插件并不支持,必须修改源码实现:
重要代码如下:
// 构造函数
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 t = this;
var editor = this.editor;
var uploadVideo = editor.uploadVideo;
var config = editor.config;
// id
var upTriggerId = getRandom('up-trigger');
var upFileId = getRandom('up-file');
var e = getRandom("text-val");
var n = getRandom("btn");
// tabs 的配置
var tabsConfig = [{
title: '上传本地视频',
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;
}
}]
},
{
title: "插入视频",
tpl: '<div>\n <input id="' + e + '" type="text" class="block" placeholder="格式如:<iframe src=... ></iframe>"/>\n <div class="w-e-button-container">\n <button id="' + n + '" class="right">插入</button>\n </div>\n </div>',
events: [{
selector: "#" + n,
type: "click",
fn: function() {
var $linkUrl = $('#' + e);
var url = $linkUrl.val().trim();
return url && t._insertss(url),
!0
}
}]
}
]; // tabs end
// 判断 tabs 的显示
var tabsConfigResult = [];
if ((config.uploadImgShowBase64 || config.uploadImgServer || config.customUploadImg) && window.FileReader) {
// 显示“上传图片”
tabsConfigResult.push(tabsConfig[0]);
}
if (config.showLinkImg) {
// 显示“网络图片”
tabsConfigResult.push(tabsConfig[1]);
}
// 创建 panel 并显示
var panel = new Panel(this, {
width: 300,
tabs: tabsConfigResult
});
panel.show();
// 记录属性
this.panel = panel;
},
_insertss: function(t) {
this.editor.cmd.do("insertHTML", t + "<p><br></p>")
},
// 试图改变 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');
}
}
};
// 上传视频
uploadVideo: function uploadVideo(files) {
var _this3 = this;
if (!files || !files.length) {
return;
}
// ------------------------------ 获取配置信息 ------------------------------
var editor = this.editor;
var config = editor.config;
var uploadVideoServer = "/upload/videoUpload.action";//上传地址
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 {
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.data);
}
// 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;
}
}
};
后端上传方法:
/*富文本本地视频上传*/
@ResponseBody
@RequestMapping("/videoUpload")
public Map<String, Object> videoUpload(@RequestParam(value="file",required=false) MultipartFile file, HttpServletRequest request){
Map<String, Object> map = new HashMap<>();
File targetFile=null;
String url="";//返回存储路径
int code=1;
System.out.println(file);
String fileName=file.getOriginalFilename();//获取文件名加后缀
if(fileName!=null&&fileName!=""){
//文件本地存储位置 这里的urlService.MEDIA_MEDIA 是一个配置的地址 返回的就是一个上传目录
String path = urlService.MEDIA_MEDIA+ "thumbnail/t_news/video/";
String fileF = fileName.substring(fileName.lastIndexOf("."), fileName.length());//文件后缀
//根据日期生成一个新的文件名,防止文件名冲突
String fileQ=new Date().getTime()+"_"+new Random().nextInt(1000);
fileName=fileQ+fileF;//新的文件名
File file1 =new File(path);
//如果文件夹不存在则创建
if(!file1 .exists() && !file1.isDirectory()){
file1 .mkdir();
}
//将图片存入文件夹
targetFile = new File(file1, fileName);
try {
//将上传的文件写到服务器上指定的文件。
file.transferTo(targetFile);
//存入后获取url 这里的urlService.REST_BASE_URL 就是一个文件下载的方法地址 下载方法也是后台写的
//这里传了一个下载类型 和文件名后缀 还有id
url= urlService.REST_BASE_URL+"/file/file/getVideo.action?type=33,"+fileF+"&id="+fileQ;
map.put("data",url);
map.put("errno",0);
} catch (Exception e) {
e.printStackTrace();
}
}
return map;
}
后端下载方法:
//这个上传方法是一个公用方法,加了一个type来区分,你可以不传,我已经把其他没有用的type类型判断的方法删掉了
@RequestMapping(value="/getVideo")
public void getVideo (@RequestParam("id")String id,@RequestParam("type")String typeAndVideoName,
HttpServletResponse response,HttpServletRequest request){
byte[] data;
try {
File file = null;
//截取文件后缀
if (type.contains("33")) {
String str1=type.substring(0, type.indexOf(","));
String fileZ=type.substring(str1.length()+1, type.length());
//这里的 FILE_BASE_PATH是从配置文件中读取的 就是之前富文本上传的地址
file = new File(FILE_BASE_PATH + "thumbnail/t_news/video/" +id+fileZ);
data = FileUtil.readAsByteArray(file);
}
OutputStream outputSream = response.getOutputStream();
InputStream in = new ByteArrayInputStream(data);
int len = 0;
byte[] buf = new byte[1024];
while ((len = in.read(buf, 0, 1024)) != -1) {
outputSream.write(buf, 0, len);
}
outputSream.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
前端jsp页面直接加上 调用就可以:
var E = window.wangEditor;
var editor = new E('#editor');
editor.customConfig.menus =[
// 'head', // 标题
// 'bold', // 粗体
'fontSize', // 字号
'fontName', // 字体
'italic', // 斜体
'underline', // 下划线
'strikeThrough', // 删除线
'foreColor', // 文字颜色
'backColor', // 背景颜色
'link', // 插入链接
'list', // 列表
'justify', // 对齐方式
'quote', // 引用
// 'emoticon', // 表情
'image', // 插入图片
'table', // 表格
'video', // 插入视频
// 'code', // 插入代码
'undo', // 撤销
'redo' // 重复
];
// 本地上传图片(举例)
editor.customConfig.uploadImgShowBase64 = true;
// 隐藏“网络图片”tab
//editor.customConfig.showLinkImg = false;
editor.create();
我整理了一份完整的wangEditor.js,包括本地和网络图片,本地视频和网络视频的插入 需要的话可以下载 https://download.csdn.net/download/weixin_43832166/12109361
参考资料:https://blog.csdn.net/one_hwx/article/details/87652525