Spring Boot 和 AdminLTE 的集成
AdminLTE 是个开源的管理模板,首页:http://adminlte.io/,GitHub地址:https://github.com/almasaeed2010/AdminLTE,目前有 2w+ star,100 多个 contributor,可以说是非常出名的开源管理模板了。
这篇描述将 AdminLTE 和 Spring Boot 整合起来进行后台开发并利用 AdminLTE 的前端组件、模板、布局。
- 使用的 AdminLTE 版本 v2.4.3 (2018-02-22)
- 项目地址:https://github.com/yingw/adminlte-boot-template
下面将介绍三种前后端集成的方式
- static:所有 AdminLTE 的静态资源直接集成到项目
- webjars:用 Webjars 将前端库集成到后端 maven 的依赖 jar 中
- bower:用 Bower 将前端依赖定义,在项目编译过程中创建前端资源
1. 初步集成
1.1. 创建 Spring Boot 项目
从 http://start.spring.io/ 或者 IDE 中创建一个 Spring Boot 项目,依赖:Web、Thymeleaf、DevTools
1.2. 下载 AdminLTE
从 AdminLTE 的 GitHub release 页 下载项目(v2.4.3),解压
- 拷贝 pages 目录、index.html、index2.html、start.html 到
templates
目录 - 拷贝 dist 下 css、img、js 目录、bower_components、plugins 目录到
static
目录注意的是 bower_components 内有上千文件,拷贝、编译都很慢,虽然用后面的 webjars 依赖可以避免,目前还是需要的,在项目代码中没有放,请自行拷贝。
1.3. 修正路径
批量替换 pages 下面页面的一些相对路径,主要是 css、js、img 的引用
../../dist/
替换为../../
../dist/
替换为../
"dist/
替换为"./
- 还有一些上级目录的如 index.html 替换
1.4. 动态跳转控制器
创建一个动态跳转控制器 AdminController
/**
* @author yinguowei@gmail.com 2018/3/27.
*/
@Controller
class AdminController {
private static Logger logger = LoggerFactory.getLogger(AdminController.class);
@GetMapping("/")
public String home() {
return "redirect:index2.html";
}
@GetMapping(value = {"/**/*.html"})
public String route(HttpServletRequest request) {
logger.debug("AdminController.route: request.getRequestURI() = {}", request.getRequestURI());
String path = request.getRequestURI();
return path;
}
}
设置些属性 application.properties
spring.thymeleaf.cache=false
spring.thymeleaf.suffix=
设置了这个控制器后所有的 .html
就可以跳转到对应的视图。如果将来不希望通过 .html 后缀访问 url,而是采用类似 restful 的地址,可以去掉 spring.thymeleaf.suffix
定义(默认是 .html
),并在解析时候去掉 .html
@RequestMapping(value = {"/**/*.html"})
public String route(HttpServletRequest request) {
logger.debug("DynamicController.route: request.getRequestURI() = {}", request.getRequestURI());
String path = request.getRequestURI();
return path.substring(1, path.length() - 5); // remove "/" and ".html"
}
1.5. 字体和主题
临时去掉 Googleapi 字体
由于 googleapi 的字体可能被墙,建议全部注释
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,700,300italic,400italic,600italic">
加上雅黑字体
并在主题内加上 AdminLTE.css 和 AdminLTE.min.css
font-family: 'Microsoft YaHei UI', 'Source Sans Pro', 'Helvetica Neue', Helvetica, Arial, sans-serif;
来使用客户端的微软雅黑字体
主题替换
如果需要主题,定制专门的 skin 和 skin.min.css
替换
<link rel="stylesheet" href="/css/skins/_all-skins.min.css">
为
<link rel="stylesheet" href="dist/css/skins/skin-blue.min.css">
生成 min.css
TBD,有在线工具
1.6. 其他修正
部分页面由于 json/javascript 格式问题和 Thymeleaf 冲突,比如 charts/flot.html 改成:
<script th:inline="none">
1.7. 静态测试
Thymeleaf 的一大好处就是可以直接打开 html 进行页面测试而不需要启动服务器。编辑各 stylesheet 和 javascript 的应用地址,改为和当前页面相对,如:
<html xmlns:th="http://www.thymeleaf.org">
<link rel="stylesheet" href="../static/bower_components/bootstrap/dist/css/bootstrap.min.css" th:href="{/bower_components/bootstrap/dist/css/bootstrap.min.css}">
<script src="../static/bower_components/jquery/dist/jquery.min.js" th:src="@{/bower_components/jquery/dist/jquery.min.js}"></script>
但注意这一步也可以不用急着改太多页面,而是只改一两个测试下,因为后面要放到 Layout 公共页面统一改。
小结
经过这一系列集成,已经可以跑起来测试 http://localhost:8080,会自动跳转到 index2.html,可能有些路径的修正不完善或有问题,观察控制台 404 异常修正。
2. Thymeleaf Layout
Layout 是让多个页面共用一套页面模板,这样很多重复的代码就不用重复编码了。
引入 pom 依赖
<dependency>
<groupId>nz.net.ultraq.thymeleaf</groupId>
<artifactId>thymeleaf-layout-dialect</artifactId>
</dependency>
在 layout 目录创建 layout.html
,可以用 index2.html 拷贝过去修改
2.1 layout/layout.html
标题
<title layout:title-pattern="$LAYOUT_TITLE | $CONTENT_TITLE">AdminLTE 2</title>
修改原来的相对地址,去掉 static,只保留大部分页面都要用的如 bootstrap, jquery, datatable
<!-- Bootstrap 3.3.7 -->
<link rel="stylesheet" href="/bower_components/bootstrap/dist/css/bootstrap.min.css">
<!-- Font Awesome -->
<link rel="stylesheet" href="/bower_components/font-awesome/css/font-awesome.min.css">
<!-- Ionicons -->
<link rel="stylesheet" href="/bower_components/Ionicons/css/ionicons.min.css">
<!-- jvectormap -->
<link rel="stylesheet" href="/bower_components/jvectormap/jquery-jvectormap.css">
<!-- Theme style -->
<link rel="stylesheet" href="/css/AdminLTE.min.css">
<!-- AdminLTE Skins. Choose a skin from the css/skins
folder instead of downloading all of them to reduce the load. -->
<link rel="stylesheet" href="/css/skins/_all-skins.min.css">
修改菜单可以动态根据传入变量选中
<li class="treeview" th:classappend="${menu=='index' || menu=='index2' ? 'active menu-open' : ''}">
<ul class="treeview-menu">
<li th:classappend="${menu=='index' ? 'active' : ''}"><a href="/index.html"><i class="fa fa-circle-o"></i> Dashboard v1</a></li>
修改 img 的相对地址,如
<img class="direct-chat-img" src="../static/img/user1-128x128.jpg" th:src="@{/img/user1-128x128.jpg}" alt="message user image">
js
<!-- jQuery 3 -->
<script src="/bower_components/jquery/dist/jquery.min.js"></script>
<!-- Bootstrap 3.3.7 -->
<script src="/bower_components/bootstrap/dist/js/bootstrap.min.js"></script>
<!-- AdminLTE App -->
<script src="/js/adminlte.min.js"></script>
<!-- Optionally -->
<!-- Slimscroll -->
<script src="/bower_components/jquery-slimscroll/jquery.slimscroll.min.js"></script>
<!-- FastClick -->
<script src="/bower_components/fastclick/lib/fastclick.js"></script>
<!-- AdminLTE for demo purposes -->
<script src="/js/demo.js"></script>
<div layout:fragment="customScript" th:remove="tag">
</div>
如果想单独测试 layout.html,跟下面 index2 一样修改加上静态和动态的地址
声明插入点
<div class="content-wrapper" layout:fragment="content">
...
<div layout:fragment="customScript" th:remove="tag">
</tag>
2.2 index2.html
修改 index2.html、start.html、blank.html 来使用 layout
头
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{layout/layout.html}"
th:with="menu='index2'">
stylesheet 和 javascript
为了本地测试,保留 stylesheet 和 javascript,但是加上 th:remove="all"
在解析时自己删掉
<!-- Bootstrap 3.3.7 -->
<link rel="stylesheet" href="../static/bower_components/bootstrap/dist/css/bootstrap.min.css" th:remove="all">
<!-- Font Awesome -->
<link rel="stylesheet" href="../static/bower_components/font-awesome/css/font-awesome.min.css" th:remove="all">
<!-- Ionicons -->
<link rel="stylesheet" href="../static/bower_components/Ionicons/css/ionicons.min.css" th:remove="all">
<!-- Theme style -->
<link rel="stylesheet" href="../static/css/AdminLTE.min.css" th:remove="all">
<!-- AdminLTE Skins. -->
<link rel="stylesheet" href="../static/css/skins/_all-skins.min.css" th:remove="all">
<!-- jvectormap -->
<link rel="stylesheet" href="../static/bower_components/jvectormap/jquery-jvectormap.css" th:remove="all">
<!-- 表格 -->
<link rel="stylesheet" href="../static/bower_components/datatables.net-bs/css/dataTables.bootstrap.min.css" th:remove="all">
<!-- PACE -->
<link rel="stylesheet" href="../static/bower_components/PACE/themes/blue/pace-theme-minimal.css" th:remove="all">
js,页面自定义的 js 可以放到 customScript fragment 里面去
<!-- jQuery 3 -->
<script src="../static/bower_components/jquery/dist/jquery.min.js" th:remove="all"></script>
<!-- Bootstrap 3.3.7 -->
<script src="../static/bower_components/bootstrap/dist/js/bootstrap.min.js" th:remove="all"></script>
<!-- FastClick -->
<script src="../static/bower_components/fastclick/lib/fastclick.js" th:remove="all"></script>
<!-- AdminLTE App -->
<script src="../static/js/adminlte.min.js" th:remove="all"></script>
<div layout:fragment="customScript">
<!-- Sparkline -->
<script src="../static/bower_components/jquery-sparkline/dist/jquery.sparkline.min.js" th:src="@{/bower_components/jquery-sparkline/dist/jquery.sparkline.min.js}"></script>
<!-- jvectormap -->
<script src="../static/plugins/jvectormap/jquery-jvectormap-1.2.2.min.js" th:src="@{/plugins/jvectormap/jquery-jvectormap-1.2.2.min.js}"></script>
<script src="../static/plugins/jvectormap/jquery-jvectormap-world-mill-en.js" th:src="@{/plugins/jvectormap/jquery-jvectormap-world-mill-en.js}"></script>
<!-- SlimScroll -->
<script src="../static/bower_components/jquery-slimscroll/jquery.slimscroll.min.js" th:src="@{/bower_components/jquery-slimscroll/jquery.slimscroll.min.js}"></script>
<!-- ChartJS -->
<script src="../static/bower_components/chart.js/Chart.js" th:src="@{/bower_components/chart.js/Chart.js}"></script>
<!-- AdminLTE dashboard demo (This is only for demo purposes) -->
<script src="../static/js/pages/dashboard2.js" th:src="@{/js/pages/dashboard2.js}"></script>
<!-- AdminLTE for demo purposes -->
<script src="../static/js/demo.js" th:src="@{/js/demo.js}"></script>
</div>
删掉不用的节点
删掉:navbar-custom-menu
、main-sidebar
(也可以保留菜单测试)、main-footer
、control-sidebar
、control-sidebar-bg
内容声明
在 content-wrapper 上声明
<div class="content-wrapper" layout:fragment="content">
2.3 login layout
还有些 404、500 页面使用的是和 layout 完全不同的布局,也可以提炼一个 layout-login
,然后 login.html、logout.html、404、500 等都使用这个布局,这里就不重复了。
总结
通过使用 layout,可以将大部分页面共用代码提取出来,通过 fragment 插入页面的定制内容。但是这里大部分页面将来都不会是项目内必须使用的,就不全部改了。
3. Webjars
上面介绍的基本就是静态集成的全部内容,不过前面提到,要把 bower_componenets 这个几千个文件夹的第三方 js、css 依赖库放到项目管理中,总是不太方便的,对于后端开发来说,最好前端的组件也是几个依赖来管理就最简单了,webjars 就是以这个目标设计的工具。
3.1 Webjars 依赖
根据项目使用的库的版本,到 webjars 官网去搜索对应的定义即可,再把 maven 定义拷进来。
例如:
<!--bootstrap-->
<dependency>
<groupId>org.webjars.bower</groupId>
<artifactId>bootstrap</artifactId>
<version>3.3.7</version>
</dependency>
所有的声明
<!-- Webjars begin -->
<!--bootstrap-->
<dependency>
<groupId>org.webjars.bower</groupId>
<artifactId>bootstrap</artifactId>
<version>3.3.7</version>
</dependency>
<!--font-awesome-->
<dependency>
<groupId>org.webjars.bower</groupId>
<artifactId>font-awesome</artifactId>
<version>4.7.0</version>
</dependency>
<!--ionicons-->
<dependency>
<groupId>org.webjars.bower</groupId>
<artifactId>ionicons</artifactId>
<version>2.0.1</version>
</dependency>
<!--morrisjs-->
<dependency>
<groupId>org.webjars.bower</groupId>
<artifactId>morrisjs</artifactId>
<version>0.5.1</version>
</dependency>
<dependency>
<groupId>org.webjars.bower</groupId>
<artifactId>raphael</artifactId>
<version>2.2.7</version>
</dependency>
<!--sparkline-->
<dependency>
<groupId>org.webjars.bower</groupId>
<artifactId>jquery-sparkline</artifactId>
<version>2.1.3</version>
</dependency>
<!--jvectormap-->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jvectormap</artifactId>
<version>2.0.4</version>
</dependency>
<dependency>
<groupId>org.webjars.bower</groupId>
<artifactId>bower-jvectormap</artifactId>
<version>1.2.2</version>
</dependency>
<!--datepicker-->
<dependency>
<groupId>org.webjars.bower</groupId>
<artifactId>bootstrap-datepicker</artifactId>
<version>1.7.1</version>
</dependency>
<!--daterangepicker-->
<dependency>
<groupId>org.webjars.bower</groupId>
<artifactId>bootstrap-daterangepicker</artifactId>
<version>2.1.27</version>
</dependency>
<!--moment-->
<dependency>
<groupId>org.webjars.bower</groupId>
<artifactId>moment</artifactId>
<version>2.20.1</version>
</dependency>
<!--knob-->
<dependency>
<groupId>org.webjars.bower</groupId>
<artifactId>jquery-knob</artifactId>
<version>1.2.13</version>
</dependency>
<!--chartjs-->
<dependency>
<groupId>org.webjars.bower</groupId>
<artifactId>chartjs</artifactId>
<version>1.0.2</version>
</dependency>
<!--icheck-->
<dependency>
<groupId>org.webjars.bower</groupId>
<artifactId>iCheck</artifactId>
<version>1.0.2</version>
</dependency>
<!--Flot.js-->
<dependency>
<groupId>org.webjars.bower</groupId>
<artifactId>flot</artifactId>
<version>0.8.3</version>
</dependency>
<!--color picker-->
<dependency>
<groupId>org.webjars.bower</groupId>
<artifactId>bootstrap-colorpicker</artifactId>
<version>2.5.1</version>
</dependency>
<!--select2-->
<dependency>
<groupId>org.webjars.bower</groupId>
<artifactId>select2</artifactId>
<version>4.0.5</version>
</dependency>
<!--ckeditor-->
<dependency>
<groupId>org.webjars.npm</groupId>
<artifactId>ckeditor</artifactId>
<version>4.8.0</version>
</dependency>
<!--PACE (update from original plugins-->
<dependency>
<groupId>org.webjars.bower</groupId>
<artifactId>pace</artifactId>
<version>1.0.2</version>
</dependency>
<!--datatables-->
<dependency>
<groupId>org.webjars.bower</groupId>
<artifactId>datatables.net</artifactId>
<version>1.10.16</version>
</dependency>
<dependency>
<groupId>org.webjars.bower</groupId>
<artifactId>datatables.net-bs</artifactId>
<version>2.1.1</version>
</dependency>
<!--fastclick-->
<dependency>
<groupId>org.webjars.bower</groupId>
<artifactId>fastclick</artifactId>
<version>1.0.6</version>
</dependency>
<!--jquery-->
<dependency>
<groupId>org.webjars.bower</groupId>
<artifactId>jquery</artifactId>
<version>3.3.1</version>
</dependency>
<!--jquery-ui-->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery-ui</artifactId>
<version>1.11.4</version>
</dependency>
<!--metisMenu-->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>metisMenu</artifactId>
<version>2.7.0</version>
</dependency>
<!--slimScroll-->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jQuery-slimScroll</artifactId>
<version>1.3.8</version>
</dependency>
<!--pace (updated from Plugins)-->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>pace</artifactId>
<version>1.0.2</version>
</dependency>
<!-- sweetalert -->
<dependency>
<groupId>org.webjars.npm</groupId>
<artifactId>sweetalert</artifactId>
<version>2.1.0</version>
</dependency>
<!-- Webjars end -->
3.2 修正路径
替换 bower_components 为 /webjars,可以直接从 /webjars 开始应用这些依赖,如:
<link rel="stylesheet" href="/webjars/bootstrap/3.3.7/dist/css/bootstrap.min.css">
注意个别有大小写问题:Ionicons,等等
有些 plugins 目录的依赖也可以通过 webjars 改掉,可能是作者稍微做了修改,如 pace
注意将来要是用上 Spring Security,记得把 webjars 上下文的资源设置为可以访问
..., "/webjars/**").permitAll();
3.3 Webjars Locator
还有个 Webjars 的 Locator 可以用来自动检索版本,写引用 url 的时候就可以连版本号也省略了。
但是会没有代码提示功能,而且只在版本需要经常升级不想动代码的时候才有用,意义不大。
<!--webjars-locator-->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>webjars-locator</artifactId>
<version>0.30</version>
</dependency>
3.4 使用 webjars 的限制
使用 webjars,也有缺点,就是在静态测试的时候,没法直接引用 jar 内的样式和脚本,所以用 webjars 的时候就不能做静态测试了;好处是不用在代码库中存着那上千个依赖。各有取舍吧。
4. Bower
最后一种方式,还可以使用 Bower 来在编译时生成依赖文件。需要用到 frontend-maven-plugin,安装 node 和 bower
4.1 node 配置
package.json
{
"name": "BootifulAdminLTE",
"version": "1.0.0",
"description": "Bootiful-AdminLTE application",
"dependencies": {
"bower": "~1.8.4"
},
"repository": {
"type": "git",
"url": "git+https://github.com/yingw/bootiful-adminlte.git"
},
"author": "yinguowei",
"license": "MIT"
}
- 也可以根目录执行
npm init
,创建package.json
,加上 bower:npm install bower --save
- 建议加上 description、repository,否则会有一些告警
4.2 Bower 配置
AdminLTE 本身就用了 bower 来管理依赖,拷贝项目中的 bower.json 到根目录,创建 .bowerrc
bower.json
{
"name": "admin-lte",
"homepage": "https://adminlte.io",
"authors": [
"Abdullah Almsaeed <abdullah@almsaeedstudio.com>"
],
"description": "Admin dashboard and control panel template",
"main": [
"index2.html",
"dist/css/AdminLTE.css",
"dist/js/adminlte.js",
"build/less/AdminLTE.less"
],
"keywords": [
"css",
"js",
"html",
"template",
"admin",
"bootstrap",
"theme",
"backend",
"responsive"
],
"license": "MIT",
"ignore": [
"/.*",
"node_modules",
"bower_components",
"composer.json",
"documentation"
],
"dependencies": {
"bootstrap-slider": "*",
"chart.js": "1.0.*",
"ckeditor": "^4.7.0",
"bootstrap-colorpicker": "^2.5.1",
"bootstrap": "^3.3.7",
"jquery": "^3.2.1",
"datatables.net": "^1.10.15",
"datatables.net-bs": "^2.1.1",
"bootstrap-datepicker": "^1.7.0",
"bootstrap-daterangepicker": "^2.1.25",
"moment": "^2.18.1",
"fastclick": "^1.0.6",
"Flot": "flot#^0.8.3",
"fullcalendar": "^3.4.0",
"inputmask": "jquery.inputmask#^3.3.7",
"ion.rangeSlider": "ionrangeslider#^2.2.0",
"jvectormap": "^2.0.4",
"jquery-knob": "^1.2.13",
"morris.js": "^0.5.1",
"PACE": "pace#^1.0.2",
"select2": "^4.0.3",
"jquery-slimscroll": "slimscroll#^1.3.8",
"bootstrap-timepicker": "^0.5.2",
"jquery-sparkline": "^2.1.3",
"font-awesome": "^4.7.0",
"Ionicons": "ionicons#^2.0.1",
"jquery-ui": "1.11.4"
},
"resolutions": {
"jquery": "^3.2.1"
}
}
可以在最后再加入些从 plugins 切换来的依赖
"jquery-validation": "^1.17.0",
"datatables-plugins": "^1.10.15",
"iCheck": "^1.0.2",
"html5shiv": "^3.7.3",
"respond": "Respond#^1.4.2",
"datatables-i18n": "^1.0.4",
"zTree": "^3.5.33",
"switchery": "^0.8.2"
有版本冲突的话可以在最后的 resolutions 里面添加定义
"resolutions": {
"fastclick": "^1.0.6",
"jquery": "^3.2.1"
}
.bowerrc
{
"directory" : "src/main/resources/static/bower_components",
"allow_root": true
}
指定输出目录(默认根目录),并允许 root 执行(在 Linux 下打包用)
4.3 frontend-maven-plugin
使用 frontend-maven-plugin 来安装 Node 和 Bower。
在 pom 里面定义
<properties>
<frontend-maven-plugin.version>1.6</frontend-maven-plugin.version>
<node.version>v8.10.0</node.version>
<npm.version>5.6.0</npm.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>com.github.eirslett</groupId>
<artifactId>frontend-maven-plugin</artifactId>
<version>${frontend-maven-plugin.version}</version>
<executions>
<execution>
<id>install node and npm</id>
<goals>
<goal>install-node-and-npm</goal>
</goals>
<configuration>
<nodeVersion>${node.version}</nodeVersion>
<npmVersion>${npm.version}</npmVersion>
<!-- if you don't need to download from these 3rd party registry, comments these -->
<nodeDownloadRoot>https://npm.taobao.org/mirrors/node/</nodeDownloadRoot>
<npmDownloadRoot>https://registry.npm.taobao.org/npm/-/</npmDownloadRoot>
</configuration>
</execution>
<execution>
<id>npm install</id>
<goals>
<goal>npm</goal>
</goals>
<configuration>
<npmInheritsProxyConfigFromMaven>false</npmInheritsProxyConfigFromMaven>
</configuration>
</execution>
<execution>
<id>bower install</id>
<goals>
<goal>bower</goal>
</goals>
<configuration>
<bowerInheritsProxyConfigFromMaven>false</bowerInheritsProxyConfigFromMaven>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
- install-node-and-npm: 安装 node 和 npm,并使用的淘宝镜像
- npm: 执行 npm install,禁用 maven 的代理
- bower: 执行 bower install,禁用 maven 的代理
这样就会在 static 目录创建出 bower_components 并下载好所有依赖,第一次比较慢,之后就快了。
运行 mvn spring-boot:run
就可以编译执行
使用本项目
clone 后,由于 master 分支是基于静态依赖,但是没有提交 bower_components 目录。可以:
- 第一种方式自己下载 AdminLTE 后解压该目录到 static 目录
- 第二种方式直接用 webjars 分支
- 第三种方式先切换到 bower 分支,编译完成就自动下载在 static 目录了,再切换回 static 目录或者直接使用 bower 分支
个人建议用 webjars 分支,如果对 node、bower 很熟悉也不在乎超多文件打包就使用 bower 分支。
详细的命令查看项目首页 README:https://github.com/yingw/bootiful-adminlte.git