Ueditor是一个富文本编辑器,富文本编辑器的功能就是,你在编辑器里编辑后提交的内容,是有格式的 ,因为内容是一串HTML标签语言,当然你也可以自定义提交的内容。找了好多富文本编辑器,最终选了百度的,因为他功能多,虽然界面丑一点,不像开源中国的富文本编辑器这么好看,但是功能比开源中国的编辑器多多了。
一、前端实现
下载源码:Ueditor - 下载
下载jsp的那个,然后把目录下的文件复制到你的项目里,这样的话后面使用的时候,引用的文件就不会报404错误了。
实例化编辑器的时候,有很多属性,都是Ueditor的前端配置项,还有toolbars属性,是Ueditor的自定义菜单,就是编辑器的那些功能按钮,这里的代码不是最全的功能,最全的前端配置项属性请参考文档:Ueditor官方文档 - 前端配置项说明,需要注意的是使用上传功能需要配置serverUrl属性,是服务器统一接口请求路径,具体的功能后面再说。
页面代码:
<!doctype html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<!-- UEditor配置文件 -->
<script type="text/javascript" src="/static/ueditor/ueditor.config.js"></script>
<!-- UEditor编辑器源码文件 -->
<script type="text/javascript" src="/static/ueditor/ueditor.all.js"> </script>
<!--建议手动加在语言,避免在ie下有时因为加载语言失败导致编辑器加载失败-->
<!--这里加载的语言文件会覆盖你在配置项目里添加的语言类型,比如你在配置项目里配置的是英文,这里加载的中文,那最后就是中文-->
<script type="text/javascript" src="/static/ueditor/zh-cn.js"></script>
<script type="text/javascript" src="/static/js/jquery-1.7.1.min.js"></script>
</head>
<body>
<div id="wrapper">
<div id="page-wrapper">
<div class="row">
<div class="col-lg-12">
<h1 class="page-header">发布新闻</h1>
</div>
</div>
<button id="getContentBtn">获得内容</button>
<button id="submitNewsBtn">提交新闻</button>
<form>
<div class="form-group">
<p class="help-block">(图片格式: *.png, *.jpg, *.jpeg, *.gif, *.bmp; 大小限制: 5MB)</p>
<p class="help-block">(视频格式: *.mp4; 大小限制: 100MB)</p>
<!-- 富文本编辑器 -->
<div id="newsEditor"></div>
</div>
</form>
</div>
</div>
#parse("/views/common/footer.vm")
<script type="text/javascript">
//实例化编辑器
//建议使用工厂方法getEditor创建和引用编辑器实例,如果在某个闭包下引用该编辑器,直接调用UE.getEditor('editor')就能拿到相关的实例
var ue = UE.getEditor('newsEditor', {// id="newsEditor"
toolbars:[// 自定义菜单
[// 第一行
'paragraph', //段落格式
'fontfamily', //字体
'fontsize', //字号
'customstyle', //自定义标题
'bold', //加粗
'italic', //斜体
'underline', //下划线
'strikethrough', //删除线
'fontborder', //字符边框
'|', // 分隔符
'touppercase', //字母大写
'tolowercase', //字母小写
'|', // 分隔符
'superscript', //上标
'subscript', //下标
'|', // 分隔符
'justifyleft', //居左对齐
'justifyright', //居右对齐
'justifycenter', //居中对齐
'justifyjustify', //两端对齐
'|', // 分隔符
'rowspacingtop', //段前距
'rowspacingbottom', //段后距
'lineheight', //行间距
'|', // 分隔符
'forecolor', //字体颜色
'backcolor', //背景色
'|', // 分隔符
'cleardoc', //清空文档
'fullscreen', //全屏
],
[// 第二行
'inserttable', //插入表格
'deletetable', //删除表格
'insertparagraphbeforetable', //"表格前插入行"
'insertrow', //前插入行
'insertcol', //前插入列
'deleterow', //删除行
'deletecol', //删除列
'mergecells', //合并多个单元格
'mergeright', //右合并单元格
'mergedown', //下合并单元格
'splittorows', //拆分成行
'splittocols', //拆分成列
'|', // 分隔符
'simpleupload', //单图上传
'insertvideo', //视频
'map', //Baidu地图
'emotion', //表情
'spechars', //特殊字符
'|', // 分隔符
'imagenone', //默认
'imageleft', //左浮动
'imageright', //右浮动
'imagecenter', //居中
'|', // 分隔符
'link', //超链接
'unlink', //取消链接
'anchor', //锚点
],
[// 第三行
'undo', //撤销
'redo', //重做
'|', // 分隔符
'insertorderedlist', //有序列表
'insertunorderedlist', //无序列表
'|', // 分隔符
'indent', //首行缩进
'directionalityltr', //从左向右输入
'directionalityrtl', //从右向左输入
'|', // 分隔符
'horizontal', //分隔线
'pagebreak', //分页
'template', //模板
'|', // 分隔符
'background', //背景
'pasteplain', //纯文本粘贴模式
'removeformat', //清除格式
'autotypeset', //自动排版
'blockquote', //引用
'|', // 分隔符
'date', //日期
'time', //时间
'|', // 分隔符
'selectall', //全选
'print', //打印
'preview', //预览
'drafts', // 从草稿箱加载
'help', //帮助
'source', //源代码
]
],
initialContent: '',//初始化编辑器的内容
initialFrameHeight: 700, // 编辑器高度
initialFrameWidth: 900, // 编辑器默认宽度
autoHeightEnabled: false, // 关闭自动长高
enableAutoSave: true, // 自动保存
saveInterval: 1000, // 自动保存时间 单位: 毫秒
serverUrl: '/news/hotNews/hotNews/ueHotNewsServer' // 服务器统一请求接口路径
});
$(document).ready(function() {
$('#getContentBtn').click(function() {
getContent();
});
$('#submitNewsBtn').click(function() {
ueSubmit();
});
});
function getContent() {
var arr = [];
arr.push("使用editor.getContent()方法可以获得编辑器的内容");
arr.push("内容为:");
arr.push(UE.getEditor('newsEditor').getContent());
alert(arr.join("\n"));
}
function ueSubmit() {// 提交表单
var newsHtml = '<!DOCTYPE html>' +
'<html>' +
'<head>' +
'<meta http-equiv="content-type" content="text/html;charset=UTF-8"/>' +
'</head>' +
'<body>' +
UE.getEditor('newsEditor').getContent() +
'</body>' +
'</html>';
$.ajax({
type : 'POST',
url : '/news/hotNews/hotNews/ueSubmit',
data : {
'newsHtml': newsHtml
},
success : function(data, textStatus) {
alert("提交成功");
},
error : function() {
alert("提交失败: ajax is error.");
}
});
}
</script>
</body>
</html>
页面效果是这样的:
二、后台实现
上文提到的serverUrl属性,你后台就要给对应一个方法匹配给他,GET和POST请求都有,这里的话我就做了最简单的功能,单图上传和单视频上传,后台也不难的,只要你的格式对了,就没有问题。
首先,你要读取配置文件,传给前端,配置文件就是他的config.json文件,里面是一些功能的配置,你可以修改的,我就是修改了单图上传和单视频上传,他的注释写得很清晰,我自己也写了注释,就不过多描述了。
配置文件:
/* 前后端通信相关的配置,注释只允许使用多行方式 */
{
/* 上传图片配置项 */
"imageActionName": "uploadimage", /* 传给后台的action参数值 */
"imageFieldName": "upfile", /* 提交的图片表单名称 */
"imageMaxSize": 5242880, /* 上传大小限制: 5MB, 单位B */
"imageAllowFiles": [".png", ".jpg", ".jpeg", ".gif", ".bmp"], /* 上传图片格式显示 */
"imageCompressEnable": false, /* 是否压缩图片,默认是true */
"imageCompressBorder": 1600, /* 图片压缩最长边限制 */
"imageInsertAlign": "none", /* 插入的图片浮动方式 */
"imageUrlPrefix": "", /* 图片访问路径前缀 */
"imagePathFormat": "/static/ueditor/tmpUpFile/image/{yyyy}{mm}{dd}_{time}_{rand:6}", /* 上传保存路径,可以自定义保存路径和文件名格式 */
/* 最后一个'/'后是文件名, 如: 20170914_1505357381_821359.jpg */
/* {filename} 会替换成原文件名,配置这项需要注意中文乱码问题 */
/* {rand:6} 会替换成随机数,后面的数字是随机数的位数 */
/* {time} 会替换成时间戳 */
/* {yyyy} 会替换成四位年份 */
/* {yy} 会替换成两位年份 */
/* {mm} 会替换成两位月份 */
/* {dd} 会替换成两位日期 */
/* {hh} 会替换成两位小时 */
/* {ii} 会替换成两位分钟 */
/* {ss} 会替换成两位秒 */
/* 非法字符 \ : * ? " < > | */
/* 具请体看线上文档: fex.baidu.com/ueditor/#use-format_upload_filename */
/*=====读取字节流时若为空中断读取分割线=====*/
/* 涂鸦图片上传配置项 */
"scrawlActionName": "uploadscrawl", /* 传给后台的action参数值 */
"scrawlFieldName": "upfile", /* 提交的图片表单名称 */
"scrawlPathFormat": "/static/ueditor/tmpUpFile/scrawl/{yyyy}{mm}{dd}_{time}_{rand:6}", /* 上传保存路径,可以自定义保存路径和文件名格式 */
"scrawlMaxSize": 5242880, /* 上传大小限制: 5MB, 单位B */
"scrawlUrlPrefix": "", /* 图片访问路径前缀 */
"scrawlInsertAlign": "none",
/*=====读取字节流时若为空中断读取分割线=====*/
/* 截图工具上传 */
"snapscreenActionName": "uploadimage", /* 传给后台的action参数值 */
"snapscreenPathFormat": "/ueditor/jsp/upload/image/{yyyy}{mm}{dd}/{time}{rand:6}", /* 上传保存路径,可以自定义保存路径和文件名格式 */
"snapscreenUrlPrefix": "", /* 图片访问路径前缀 */
"snapscreenInsertAlign": "none", /* 插入的图片浮动方式 */
/*=====读取字节流时若为空中断读取分割线=====*/
/* 抓取远程图片配置 */
"catcherLocalDomain": ["127.0.0.1", "localhost", "img.baidu.com"],
"catcherActionName": "catchimage", /* 传给后台的action参数值 */
"catcherFieldName": "source", /* 提交的图片列表表单名称 */
"catcherPathFormat": "/ueditor/jsp/upload/image/{yyyy}{mm}{dd}/{time}{rand:6}", /* 上传保存路径,可以自定义保存路径和文件名格式 */
"catcherUrlPrefix": "", /* 图片访问路径前缀 */
"catcherMaxSize": 2048000, /* 上传大小限制,单位B */
"catcherAllowFiles": [".png", ".jpg", ".jpeg", ".gif", ".bmp"], /* 抓取图片格式显示 */
/*=====读取字节流时若为空中断读取分割线=====*/
/* 上传视频配置 */
"videoActionName": "uploadvideo", /* 传给后台的action参数值 */
"videoFieldName": "upfile", /* 提交的视频表单名称 */
"videoPathFormat": "/static/ueditor/tmpUpFile/video/{yyyy}{mm}{dd}_{time}_{rand:6}", /* 上传保存路径,可以自定义保存路径和文件名格式 */
"videoUrlPrefix": "", /* 视频访问路径前缀 */
"videoMaxSize": 104857600, /* 上传大小限制: 100MB, 单位B */
"videoAllowFiles": [".mp4"], /* 上传视频格式显示 */
/*=====读取字节流时若为空中断读取分割线=====*/
/* 上传文件配置 */
"fileActionName": "uploadfile", /* 传给后台的action参数值 */
"fileFieldName": "upfile", /* 提交的文件表单名称 */
"filePathFormat": "/ueditor/jsp/upload/file/{yyyy}{mm}{dd}/{time}{rand:6}", /* 上传保存路径,可以自定义保存路径和文件名格式 */
"fileUrlPrefix": "", /* 文件访问路径前缀 */
"fileMaxSize": 51200000, /* 上传大小限制,单位B,默认50MB */
"fileAllowFiles": [
".png", ".jpg", ".jpeg", ".gif", ".bmp",
".flv", ".swf", ".mkv", ".avi", ".rm", ".rmvb", ".mpeg", ".mpg",
".ogg", ".ogv", ".mov", ".wmv", ".mp4", ".webm", ".mp3", ".wav", ".mid",
".rar", ".zip", ".tar", ".gz", ".7z", ".bz2", ".cab", ".iso",
".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".pdf", ".txt", ".md", ".xml"
], /* 上传文件格式显示 */
/*=====读取字节流时若为空中断读取分割线=====*/
/* 列出指定目录下的图片 */
"imageManagerActionName": "listimage", /* 传给后台的action参数值 */
"imageManagerListPath": "/ueditor/jsp/upload/image/", /* 指定要列出图片的目录 */
"imageManagerListSize": 20, /* 每次列出文件数量 */
"imageManagerUrlPrefix": "", /* 图片访问路径前缀 */
"imageManagerInsertAlign": "none", /* 插入的图片浮动方式 */
"imageManagerAllowFiles": [".png", ".jpg", ".jpeg", ".gif", ".bmp"], /* 列出的文件类型 */
/*=====读取字节流时若为空中断读取分割线=====*/
/* 列出指定目录下的文件 */
"fileManagerActionName": "listfile", /* 传给后台的action参数值 */
"fileManagerListPath": "/ueditor/jsp/upload/file/", /* 指定要列出文件的目录 */
"fileManagerUrlPrefix": "", /* 文件访问路径前缀 */
"fileManagerListSize": 20, /* 每次列出文件数量 */
"fileManagerAllowFiles": [
".png", ".jpg", ".jpeg", ".gif", ".bmp",
".flv", ".swf", ".mkv", ".avi", ".rm", ".rmvb", ".mpeg", ".mpg",
".ogg", ".ogv", ".mov", ".wmv", ".mp4", ".webm", ".mp3", ".wav", ".mid",
".rar", ".zip", ".tar", ".gz", ".7z", ".bz2", ".cab", ".iso",
".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".pdf", ".txt", ".md", ".xml"
] /* 列出的文件类型 */
/*=====读取字节流时若为空中断读取分割线=====*/
}
然后你需要在后台方法里读取这个配置文件,编辑器第一次加载时就会传递action="config"的参数来取得配置信息,GET类型的请求,这里我就做了最简单的Java IO读取信息。
后台代码:
/**
* 百度UEditor服务器统一请求接口路径(配置文件、上传等)
*
* 参考统一请求格式说明: http://fexteam.gz01.bdysite.com/ueditor/#dev-request_specification
* @param action UEditor请求参数名, 按值分类
* @param upfile 上传的文件
* @version 1.0
* @author ericzhan1993@foxmail.com
* @date 2017-09-15
*/
@Get("ueHotNewsServer")
@Post("ueHotNewsServer")
public String ueHotNewsServer(@Param("action") String action, @Param("upfile") MultipartFile upfile,
HttpServletRequest request) {
logger.info("[后台] - [新闻管理] - [发布新闻] - [UEditor编辑器] - [serverUrl]");
System.err.println("action==========" + action);
if (action.equals("config")) {// 读取配置文件
try {
String realPath = "/static/ueditor/config.json";// 配置文件到这里修改
System.err.println("配置文件根相对路径: /WebRoot" + realPath);
// 读取UEditor配置文件: config.json(可修改)
String filePath = inv.getServletContext().getRealPath(realPath);
BufferedReader br = new BufferedReader(new FileReader(filePath));
StringBuilder sb = new StringBuilder();
//读取所有行
String line = null;
while(true) {
line = br.readLine();// FIXME 每次都创建一个新的String对象, 很浪费性能
if(StringUtils.isEmpty(line)){
break;
}
sb.append(line);
}
br.close();// 防止内存泄漏
return "@json:" + sb.toString();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
} else if (action.equals("uploadimage")) {// 上传图片
String ossFilePath = OSSFileUtil.OSSPutObject(Global.OSSIMAGE, String.valueOf(System.currentTimeMillis()), upfile, "activityfile");
String httpPath = OSSFileUtil.getUrl("mmdimage", "activityfile/" + ossFilePath);
resultJsonMap = new HashMap<String, String>();
resultJsonMap.put("state", "SUCCESS");
resultJsonMap.put("url", httpPath);
resultJsonMap.put("title", ossFilePath);
return "@json:" + JSON.toJSONString(resultJsonMap);
} else if (action.equals("uploadvideo")) {// 上传视频
String ossFilePath = OSSFileUtil.OSSPutObject(Global.OSSIMAGE, String.valueOf(System.currentTimeMillis()), upfile, "activityfile");
String httpPath = OSSFileUtil.getUrl("mmdimage", "activityfile/" + ossFilePath);
resultJsonMap = new HashMap<String, String>();
resultJsonMap.put("state", "SUCCESS");
resultJsonMap.put("url", httpPath);
resultJsonMap.put("title", ossFilePath);
return "@json:" + JSON.toJSONString(resultJsonMap);
}
return null;
}
基本原理就是他给你一个action的参数名,你根据值来判断是什么行为,然后把返回值给他就行了,返回值格式参考:Ueditor官方文档 - 后端请求规范,需要注意的是,我这里用的是阿里的OSS文件存储,返回值是一个http路径,如果你需要放在本地的话只要给他一个本地读取的路径即可,因为编辑器上传图片后,前端是以HTML来展示的,如:<img src="路径">,上传视频也是同理。
三、编辑器功能
常用API,可以参考下面这个官方Demo,或直接参考:官方API文档
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>完整demo</title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<script type="text/javascript" charset="utf-8" src="../ueditor.config.js"></script>
<script type="text/javascript" charset="utf-8" src="editor_api.js"> </script>
<!--建议手动加在语言,避免在ie下有时因为加载语言失败导致编辑器加载失败-->
<!--这里加载的语言文件会覆盖你在配置项目里添加的语言类型,比如你在配置项目里配置的是英文,这里加载的中文,那最后就是中文-->
<script type="text/javascript" charset="utf-8" src="../lang/zh-cn/zh-cn.js"></script>
<style type="text/css">
div{
width:100%;
}
</style>
</head>
<body>
<div>
<h1>完整demo</h1>
<script id="editor" type="text/plain" style="width:1024px;height:500px;"></script>
</div>
<div id="btns">
<div>
<button onclick="getAllHtml()">获得整个html的内容</button>
<button onclick="getContent()">获得内容</button>
<button onclick="setContent()">写入内容</button>
<button onclick="setContent(true)">追加内容</button>
<button onclick="getContentTxt()">获得纯文本</button>
<button onclick="getPlainTxt()">获得带格式的纯文本</button>
<button onclick="hasContent()">判断是否有内容</button>
<button onclick="setFocus()">使编辑器获得焦点</button>
<button onmousedown="isFocus(event)">编辑器是否获得焦点</button>
<button onmousedown="setblur(event)" >编辑器失去焦点</button>
</div>
<div>
<button onclick="getText()">获得当前选中的文本</button>
<button onclick="insertHtml()">插入给定的内容</button>
<button id="enable" onclick="setEnabled()">可以编辑</button>
<button onclick="setDisabled()">不可编辑</button>
<button onclick=" UE.getEditor('editor').setHide()">隐藏编辑器</button>
<button onclick=" UE.getEditor('editor').setShow()">显示编辑器</button>
<button onclick=" UE.getEditor('editor').setHeight(300)">设置高度为300默认关闭了自动长高</button>
</div>
<div>
<button onclick="getLocalData()" >获取草稿箱内容</button>
<button onclick="clearLocalData()" >清空草稿箱</button>
</div>
</div>
<div>
<button onclick="createEditor()">
创建编辑器</button>
<button onclick="deleteEditor()">
删除编辑器</button>
</div>
<script type="text/javascript">
//实例化编辑器
//建议使用工厂方法getEditor创建和引用编辑器实例,如果在某个闭包下引用该编辑器,直接调用UE.getEditor('editor')就能拿到相关的实例
var ue = UE.getEditor('editor');
function isFocus(e){
alert(UE.getEditor('editor').isFocus());
UE.dom.domUtils.preventDefault(e)
}
function setblur(e){
UE.getEditor('editor').blur();
UE.dom.domUtils.preventDefault(e)
}
function insertHtml() {
var value = prompt('插入html代码', '');
UE.getEditor('editor').execCommand('insertHtml', value)
}
function createEditor() {
enableBtn();
UE.getEditor('editor');
}
function getAllHtml() {
alert(UE.getEditor('editor').getAllHtml())
}
function getContent() {
var arr = [];
arr.push("使用editor.getContent()方法可以获得编辑器的内容");
arr.push("内容为:");
arr.push(UE.getEditor('editor').getContent());
alert(arr.join("\n"));
}
function getPlainTxt() {
var arr = [];
arr.push("使用editor.getPlainTxt()方法可以获得编辑器的带格式的纯文本内容");
arr.push("内容为:");
arr.push(UE.getEditor('editor').getPlainTxt());
alert(arr.join('\n'))
}
function setContent(isAppendTo) {
var arr = [];
arr.push("使用editor.setContent('欢迎使用ueditor')方法可以设置编辑器的内容");
UE.getEditor('editor').setContent('欢迎使用ueditor', isAppendTo);
alert(arr.join("\n"));
}
function setDisabled() {
UE.getEditor('editor').setDisabled('fullscreen');
disableBtn("enable");
}
function setEnabled() {
UE.getEditor('editor').setEnabled();
enableBtn();
}
function getText() {
//当你点击按钮时编辑区域已经失去了焦点,如果直接用getText将不会得到内容,所以要在选回来,然后取得内容
var range = UE.getEditor('editor').selection.getRange();
range.select();
var txt = UE.getEditor('editor').selection.getText();
alert(txt)
}
function getContentTxt() {
var arr = [];
arr.push("使用editor.getContentTxt()方法可以获得编辑器的纯文本内容");
arr.push("编辑器的纯文本内容为:");
arr.push(UE.getEditor('editor').getContentTxt());
alert(arr.join("\n"));
}
function hasContent() {
var arr = [];
arr.push("使用editor.hasContents()方法判断编辑器里是否有内容");
arr.push("判断结果为:");
arr.push(UE.getEditor('editor').hasContents());
alert(arr.join("\n"));
}
function setFocus() {
UE.getEditor('editor').focus();
}
function deleteEditor() {
disableBtn();
UE.getEditor('editor').destroy();
}
function disableBtn(str) {
var div = document.getElementById('btns');
var btns = UE.dom.domUtils.getElementsByTagName(div, "button");
for (var i = 0, btn; btn = btns[i++];) {
if (btn.id == str) {
UE.dom.domUtils.removeAttributes(btn, ["disabled"]);
} else {
btn.setAttribute("disabled", "true");
}
}
}
function enableBtn() {
var div = document.getElementById('btns');
var btns = UE.dom.domUtils.getElementsByTagName(div, "button");
for (var i = 0, btn; btn = btns[i++];) {
UE.dom.domUtils.removeAttributes(btn, ["disabled"]);
}
}
function getLocalData () {
alert(UE.getEditor('editor').execCommand( "getlocaldata" ));
}
function clearLocalData () {
UE.getEditor('editor').execCommand( "clearlocaldata" );
alert("已清空草稿箱")
}
</script>
</body>
</html>
四、给编辑器赋值
// 加载后台游戏内容,并显示在编辑器中
function loadUEContent() {
// 游戏内容
var content = '$!{gameDO.content}';
// 判断ueditor 编辑器是否创建成功(此处editGameUe是你之前声明的UE变量)
editGameUe.addListener("ready", function () {
// 只有ueditor编辑器创建成功了以后才可以使用, 向ueditor中传值
editGameUe.setContent(content);
});
}
到此,这篇简单的Ueditor学习笔记就结束了,以后有机会补上别的功能,谢谢!