thymeleaf集成layui(layui+thymeleaf)

前言

在实际项目开发中,在后台系统中,不少功能都和“富文本”相关,比如商品的详情信息、文章等内容。在阿落的接触或参与的开发过程中,使用最多的就是百度富文本编辑器,这篇文章就来介绍百度富文本的使用。

本文章是基于实际项目提取出来的信息,故代码片段与文字描述与当时项目多有关联,阿落会尽可能的描述清晰。

1.方式一

使用SSM+JSP,前端框架使用LayUI。

(1)引入maven依赖

<!-- 说明:该版本仅为在实际项目开发时使用版本,不代表其他情况 -->
<dependency>
  <groupId>ueditor</groupId>
  <artifactId>ueditor</artifactId>
  <version>1.1.2</version>
</dependency>

(2)下载百度富文本编辑器代码

①放置目录,例:src/webapp/js/ueditor,后续以此目录进行说明;

 

②结构如图:

thymeleaf集成layui(layui+thymeleaf)

结构

(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包呢?看下文)

thymeleaf集成layui(layui+thymeleaf)

(2)下载百度富文本编辑器代码

①放置目录,例:src/webapp/js/ueditor,后续以此目录进行说明;

 

②结构如图:

thymeleaf集成layui(layui+thymeleaf)

(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.结语

本篇内容就到这里了,对于富文本编辑器,后续有时间会继续研究,当前使用的方式个人觉得也不是非常便利,如果小伙伴们有更好的建议,一起探讨。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发表评论

登录后才能评论