前言
在实际项目开发中,在后台系统中,不少功能都和“富文本”相关,比如商品的详情信息、文章等内容。在阿落的接触或参与的开发过程中,使用最多的就是百度富文本编辑器,这篇文章就来介绍百度富文本的使用。
本文章是基于实际项目提取出来的信息,故代码片段与文字描述与当时项目多有关联,阿落会尽可能的描述清晰。
1.方式一
使用SSM+JSP,前端框架使用LayUI。
(1)引入maven依赖
<!-- 说明:该版本仅为在实际项目开发时使用版本,不代表其他情况 -->
<dependency>
<groupId>ueditor</groupId>
<artifactId>ueditor</artifactId>
<version>1.1.2</version>
</dependency>
(2)下载百度富文本编辑器代码
①放置目录,例:src/webapp/js/ueditor,后续以此目录进行说明;
②结构如图:
(3)修改配置
①找到 src/webapp/js/ueditor/jsp/config.json 文件,是一个JSON文件,该文件可对图片、涂鸦、文件、视频等操作进行配置;
②imageActionName : 执行上传图片的action名称,如/api/upload,则该action名称为后台上传的一个接口或者在页面中进行处理(见后文);
③imageMaxSize : 上传大小限制,单位B;
④更多参数该文件都有对应注释,可自行修改;
(4)页面使用
//篇幅原因,删除部分信息...
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>平台管理系统</title>
//篇幅原因,省略部分非必要信息...
//LayUI的CSS引入
<link rel="stylesheet" href="/layuiadmin/layui/css/layui.css" media="all">
<link rel="stylesheet" href="/layuiadmin/style/admin.css" media="all">
</head>
<body>
<div class="layui-fluid">
<div class="layui-card">
//此处可以放置面包屑,篇幅原因已经删除
<div class="layui-card-body" style="padding-top: 40px;">
<form class="layui-form" id="newsAddForm" enctype="multipart/form-data">
//其他信息,比如标题、类型等
//富文本编辑器展现区域
<div class="layui-form-item">
<label class="layui-form-label"><span class="red">* </span>新闻内容</label>
<div class="layui-input-block">
//用一个隐藏的textarea文本域放富文本的内容,便于编辑时临时存放与编辑时回显处理
<textarea id="content" name="content" style="display: none">${news.content}</textarea>
//此处定义ID,大小可以在style中调整
<script id="articleEditor" type="text/plain" style="width:100%;height:400px;"></script>
</div>
</div>
</form>
</div>
</div>
</div>
//一些JS引用
...
//百度富文本编辑器的JS
<script type="text/javascript" src="/js/ueditor/ueditor.config.js"></script>
<script type="text/javascript" src="/js/ueditor/ueditor.all.min.js"></script>
<script type="text/javascript" src="/js/ueditor/lang/zh-cn/zh-cn.js"></script>
<script>
$(function () {
var ue = UE.getEditor('articleEditor');
UE.Editor.prototype._bkGetActionUrl = UE.Editor.prototype.getActionUrl;
UE.Editor.prototype.getActionUrl = function (action) {
//此处的action与config.json文件中imageActionName是不是一致?
//正常情况下,如果action与imageActionName一致,后台接口可以直接定义与action一致的接口来进行上传
//阿落此处分开处理,是因为当时项目原因,/api/upload用于图片类型的上传,
//而/base/uploadRichFile是对富文本内容中非图片上传
if (action == '/api/upload') {
return '/base/uploadRichFile';
} else {
return this._bkGetActionUrl.call(this, action);
}
};
UE.getEditor('articleEditor').ready(function () {
//编辑的时候,拿到隐藏的textarea文本域的文本,设置到富文本编辑器,这样才会在编辑器中显示内容
UE.getEditor('articleEditor').setContent($("#content").text());
})
});
</script>
</body>
</html>
2.方式二
使用SpringBoot框架,Thymeleaf模板引擎,前端框架使用LayUI。
在这种方式中,由于Thymeleaf模板与JSP有一定差异性,所以没有引用百度富文本编辑器对应的依赖,而是在项目中沿用该编辑器的配置方式进行配置,具体见下文。
(1)百度富文本编辑器源码处理
打开百度富文本编辑器的jar包(阿落使用的JD-GUI),按照包名结构复制到项目中,注意顶层包名一定是com.baidu.editor ;
图左侧为项目中的结构,图右侧为源码的jar包结构,可以看到是基本一一对应的。
(为什么说基本一一对应?为什么图左侧多了一个config包呢?看下文)
(2)下载百度富文本编辑器代码
①放置目录,例:src/webapp/js/ueditor,后续以此目录进行说明;
②结构如图:
(3)将百度富文本编辑器的配置文件在Java代码中处理
方式一中,对于图片/文件/视频等等上传,都是在config.json的JSON文件中配置,而在方式二中,由于当时项目原因(图方便),使用Java类进行处理。
这也是(1)中图片的com.baidu.ueditor.config这个包的原因。
package com.baidu.editor.config;
import lombok.Getter;
import lombok.ToString;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* 百度Ueditor配置信息
*
* @author 阿落学Java
*/
@Getter
@ToString
public class UeditorConfig {
/** 执行上传图片的action名称 */
private String imageActionName = "/upload/file/one";
/** 提交的图片表单名称 */
private String imageFieldName = "upfile";
/** 上传大小限制,单位B */
private Long imageMaxSize = 209715200L;
/** 上传图片格式显示 */
private List<String> imageAllowFiles = Stream.of(".png", ".jpg", ".jpeg", ".gif", ".bmp",".mkv", ".avi", ".rm", ".rmvb", ".mpeg", ".mpg", ".mov", ".wmv", ".mp4", ".mp3", ".wav").collect(Collectors.toList());
//篇幅原因,这里就不一一列举了,与config.json文件中的内容完全一致
}
(4)新增一个获取富文本获取配置接口
此接口的目的是,在进入一个引用了百度富文本编辑器的页面时,编辑器会获取配置,在方式一中,因为默认是通过/ueditor/jsp/controller.jsp中去获取config.json文件配置的,需要不需要这个步骤。
①修改获取配置请求路径
src/webapp/js/ueditor/ueditor.config.js文件中,serverUrl字段为服务器统一请求接口路径,可以看到默认是不是jsp/controller.jsp,此处我们改为/config;
②控制层接口
@RequestMapping("/config")
//UnAuthRequest此注解的作用是该接口不进行权限校验
//在实际项目中需根据使用的安全工具对应处理,如使用SpringSecurity则需要放行
@UnAuthRequest
public void getUeditorConfig(HttpServletRequest request, HttpServletResponse response) {
response.setContentType("application/json");
try {
//见③说明
String exec = new ActionEnter(request).exec();
PrintWriter writer = response.getWriter();
writer.write(exec);
writer.flush();
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
③获取配置方式修改
此处进入到exec()方法,就进入到了ActionEnter类,而exec()方法又会去执行这个类中的invoke()方法;
package com.baidu.editor;
import com.baidu.editor.define.ActionMap;
import com.baidu.editor.define.AppInfo;
import com.baidu.editor.define.BaseState;
import com.baidu.editor.define.State;
import com.baidu.editor.hunter.FileManager;
import com.baidu.editor.hunter.ImageHunter;
import com.baidu.editor.upload.Uploader;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
public class ActionEnter {
private HttpServletRequest request = null;
private String actionType = null;
private ConfigManager configManager = null;
public ActionEnter(HttpServletRequest request) {
this.request = request;
this.actionType = request.getParameter("action");
this.configManager = ConfigManager.getInstance();
}
public String exec() {
//会去执行invoke()方法
String callbackName = this.request.getParameter("callback");
if (callbackName != null) {
if (!validCallbackName(callbackName)) {
return new BaseState(false, AppInfo.ILLEGAL).toJSONString();
}
return callbackName + "(" + this.invoke() + ");";
} else {
return this.invoke();
}
}
public String invoke() {
if (actionType == null || !ActionMap.mapping.containsKey(actionType)) {
return new BaseState(false, AppInfo.INVALID_ACTION).toJSONString();
}
if (this.configManager == null || !this.configManager.valid()) {
return new BaseState(false, AppInfo.CONFIG_ERROR).toJSONString();
}
State state = null;
//去ActionMap中获取类型看是否为有效的请求类型
//ActionMap类在下方已贴出④
int actionCode = ActionMap.getType(this.actionType);
Map<String, Object> conf = null;
switch (actionCode) {
//这里可以看到获取全局配置获取图片上传配置都是在this.configManager中获取
//下方贴出ConfigManager类的代码⑤
case ActionMap.CONFIG:
return this.configManager.getUeditorConfig();
case ActionMap.UPLOAD_IMAGE:
conf = this.configManager.getConfig(actionCode);
state = new Uploader(request, conf).doExec();
break;
case ActionMap.UPLOAD_SCRAWL:
case ActionMap.UPLOAD_VIDEO:
conf = this.configManager.getConfig(actionCode);
state = new Uploader(request, conf).doExec();
break;
case ActionMap.UPLOAD_FILE:
conf = this.configManager.getConfig(actionCode);
state = new Uploader(request, conf).doExec();
break;
case ActionMap.CATCH_IMAGE:
conf = configManager.getConfig(actionCode);
String[] list = this.request.getParameterValues((String) conf.get("fieldName"));
state = new ImageHunter(conf).capture(list);
break;
case ActionMap.LIST_IMAGE:
case ActionMap.LIST_FILE:
conf = configManager.getConfig(actionCode);
int start = this.getStartIndex();
state = new FileManager(conf).listFile(start);
break;
}
return state.toJSONString();
}
public int getStartIndex() {
String start = this.request.getParameter("start");
try {
return Integer.parseInt(start);
} catch (Exception e) {
return 0;
}
}
/** callback参数验证 */
public boolean validCallbackName(String name) {
return name.matches("^[a-zA-Z_]+[\\w0-9_]*#34;);
}
}
④invoke()方法中,是取ActionMap类中获取一个类型,所以这里也贴出这个类的信息
public final class ActionMap {
public static final Map<String, Integer> mapping;
//获取配置请求
public static final int CONFIG = 0;
//上传图片请求
public static final int UPLOAD_IMAGE = 1;
public static final int UPLOAD_SCRAWL = 2;
public static final int UPLOAD_VIDEO = 3;
public static final int UPLOAD_FILE = 4;
public static final int CATCH_IMAGE = 5;
public static final int LIST_FILE = 6;
public static final int LIST_IMAGE = 7;
static {
mapping = new HashMap<String, Integer>(){{
//获取配置请求URI
put( "config", ActionMap.CONFIG );
//上传图片请求URI,需要和`UeditorConfig`类的`imageActionName`对应
put( "/upload/file/one", ActionMap.UPLOAD_IMAGE );
put( "uploadscrawl", ActionMap.UPLOAD_SCRAWL );
put( "uploadvideo", ActionMap.UPLOAD_VIDEO );
put( "uploadfile", ActionMap.UPLOAD_FILE );
put( "catchimage", ActionMap.CATCH_IMAGE );
put( "listfile", ActionMap.LIST_FILE );
put( "listimage", ActionMap.LIST_IMAGE );
}};
}
public static int getType ( String key ) {
return ActionMap.mapping.get( key );
}
}
⑤ConfigManager类的代码
package com.baidu.editor;
import com.alibaba.fastjson.JSON;
import com.baidu.editor.config.UeditorConfig;
import com.baidu.editor.define.ActionMap;
import java.util.HashMap;
import java.util.Map;
/**
* 百度Ueditor配置管理器
*
* @author 阿落学Java
*/
public final class ConfigManager {
/** 涂鸦上传filename定义 */
private final static String SCRAWL_FILE_NAME = "scrawl";
/** 远程图片抓取filename定义 */
private final static String REMOTE_FILE_NAME = "remote";
/** 百度富文本上传配置信息 */
private UeditorConfig ueditorConfig = new UeditorConfig();
/** 配置管理器构造工厂 */
public static ConfigManager getInstance() {
return new ConfigManager();
}
/** 验证配置文件加载是否正确 */
public boolean valid() {
return ueditorConfig != null;
}
public String getUeditorConfig() {
//获取全局配置,就是实例化一个UeditorConfig类,然后转JSON字符串
return JSON.toJSONString(ueditorConfig);
}
public Map<String, Object> getConfig(int type) {
Map<String, Object> conf = new HashMap<String, Object>(8);
String savePath = null;
switch (type) {
//图片上传配置
case ActionMap.UPLOAD_FILE:
conf.put("isBase64", "false");
conf.put("maxSize", ueditorConfig.getFileMaxSize());
conf.put("allowFiles", ueditorConfig.getFileAllowFiles());
conf.put("fieldName", ueditorConfig.getFileFieldName());
savePath = ueditorConfig.getFilePathFormat();
break;
case ActionMap.UPLOAD_IMAGE:
conf.put("isBase64", "false");
conf.put("maxSize", ueditorConfig.getImageMaxSize());
conf.put("allowFiles", ueditorConfig.getImageAllowFiles());
conf.put("fieldName", ueditorConfig.getFileFieldName());
savePath = ueditorConfig.getImagePathFormat();
break;
...
}
conf.put("savePath", savePath);
return conf;
}
}
(5)页面使用
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<title>编辑新闻</title>
//篇幅原因,删除一些非必要信息
</head>
<body>
<div class="layui-fluid">
<div class="layui-col-md12" style="margin-top: 20px">
<div class="layui-col-sm3 desc-label-bold">
<span class="red">* </span>内容
</div>
<div class="layui-col-sm8">
//隐藏的textarea文本域 指定ID
<textarea id="content" name="html" style="display: none"
th:text="${article?.html}"></textarea>
//编辑器 指定ID
<script id="articleEditor" type="text/plain"
style="width:100%;height:400px;"></script>
</div>
</div>
</div>
<script src="/layuiadmin/layui/layui.js"></script>
<script type="text/javascript" src="/js/jquery/jquery.min.js"></script>
<script type="text/javascript" src="/js/ueditor/ueditor.config.js"></script>
<script type="text/javascript" src="/js/ueditor/ueditor.all.min.js"></script>
<script type="text/javascript" src="/js/ueditor/lang/zh-cn/zh-cn.js"></script>
<script type="text/javascript" th:inline="none">
layui.config({
base: '/layuiadmin/' //静态资源所在路径
}).extend({
index: 'lib/index' //主入口模块
}).use(['index', 'form', 'upload', 'htmlCommon'], function () {
var form = layui.form, $ = layui.$, upload = layui.upload, htmlCommon = layui.htmlCommon;
//保存时
form.on('submit(submit-btn)', function (data) {
//这里是对富文本内容的一些字符转码
data.field.html = htmlCommon.htmlEncode(UE.getEditor('articleEditor').getContent());
//获取编辑器内容 赋值到context字段
data.field.context = UE.getEditor('articleEditor').getContentTxt();
var formData = data.field;
$.ajax({
url: "/application/article/edit",
data: formData,
dataType: "json",
type: "post",
success: function (data) {
if (data.status) {
layer.msg(data.desc, {icon: 1});
setTimeout(function () {
window.location.href = "/application/article";
}, 1000)
} else {
layer.msg(data.desc, {icon: 2});
}
}
})
})
});
$(function () {
var ue = UE.getEditor('articleEditor');
UE.Editor.prototype._bkGetActionUrl = UE.Editor.prototype.getActionUrl;
UE.Editor.prototype.getActionUrl = function (action) {
if (action == '/uploadFile') {
return '/upload/file/one';
} else {
return this._bkGetActionUrl.call(this, action);
}
};
UE.getEditor('articleEditor').ready(function () {
var content = $("#content").text();
content = htmlDecode(content);
UE.getEditor('articleEditor').setContent(content);
})
});
</script>
</body>
</html>
3.实际遇到的问题
(1)保存时提示富文本编辑的内容过大
一般是由于ueditor.config.js中配置限制的原因。
找到 src/webapp/js/ueditor/ueditor.config.js 文件,找到 maximumWords 字段,该字段为允许的最大字符数,修改到合适的数值。
4.结语
本篇内容就到这里了,对于富文本编辑器,后续有时间会继续研究,当前使用的方式个人觉得也不是非常便利,如果小伙伴们有更好的建议,一起探讨。
本文暂时没有评论,来添加一个吧(●'◡'●)