練習使用 spring-boot-starter-thymeleaf 跟 vuejs 來做個簡易管理後台
需要的組件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.yysport.pms</groupId>
<artifactId>pms</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>yysport-pmsAdmin</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<swagger.version>2.6.1</swagger.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>${swagger.version}</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>${swagger.version}</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-data-rest</artifactId>
<version>${swagger.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>6.0.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
在組態部分定義了錯誤頁面,注意這邊頁面會用類似 forward 效果吐回 src\main\resources\static\404.html 的內容
@Configuration
public class ServiceConfig {
@Bean
public EmbeddedServletContainerCustomizer containerCustomizer() {
return new EmbeddedServletContainerCustomizer() {
@Override
public void customize(ConfigurableEmbeddedServletContainer container) {
ErrorPage error401Page = new ErrorPage(HttpStatus.UNAUTHORIZED, "/401.html");
ErrorPage error404Page = new ErrorPage(HttpStatus.NOT_FOUND, "/404.html");
ErrorPage error500Page = new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/500.html");
container.addErrorPages(error401Page, error404Page, error500Page);
}
};
}
}
接下來寫模板控制
@Controller
public class IndexController {
@RequestMapping("index")
public String index(@RequestParam(value="name", required=false, defaultValue="Sam") String name, Model model) {
model.addAttribute("name", name);
return "index";
}
@RequestMapping("")
public String root(@RequestParam(value="name", required=false, defaultValue="Sam") String name, Model model) {
model.addAttribute("name", name);
return "index";
}
}
這樣的寫法會轉到 src\main\resources\templates\index.html 由 thymeleaf 來渲染頁面
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<p th:inline="text">Hello, [[${name}]]!</p>
</body>
</html>
目前 SpringBoot 配置的版本是 thymeleaf 2.1.5 語法文件請參考
thymeleaf tutorials 2.1
但是 thymeleaf 預設的 html5 格式檢查相當嚴謹,像剛剛上面的 html 會造成解析錯誤如下
org.xml.sax.SAXParseException: 元素類型 "meta" 必須由配對的結束標記 "</meta>" 終止。
at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.createSAXParseException(ErrorHandlerWrapper.java:203) ~[na:1.8.0_112]
at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.fatalError(ErrorHandlerWrapper.java:177) ~[na:1.8.0_112]
at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:400) ~[na:1.8.0_112]
at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:327) ~[na:1.8.0_112]
at com.sun.org.apache.xerces.internal.impl.XMLScanner.reportFatalError(XMLScanner.java:1472) ~[na:1.8.0_112]
除了太嚴謹會有點麻煩,還有另一個原因是之後加上 vue 後你還是會發生解析錯誤,所以我們來放寬一下限制
application.yml 增加
spring.
thymeleaf:
mode: LEGACYHTML5
還要加上依賴
<dependency>
<groupId>net.sourceforge.nekohtml</groupId>
<artifactId>nekohtml</artifactId>
<version>1.9.22</version>
</dependency>
重新啟動時就不會再解析錯誤了
現在我們來加上 vue 來開發前端,先在 pom.xml 中加上 webjars 的 vue
<dependency>
<groupId>org.webjars</groupId>
<artifactId>vue</artifactId>
<version>2.1.3</version>
</dependency>
再把我們原來的 index.html 搭配 veu 的範例 Handling-User-Input 改造一下結果如下
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<title>Title</title>
<script src="https://unpkg.com/vue/dist/vue.js" th:src="@{/webjars/vue/2.1.3/vue.js}"></script>
</head>
<body>
<div id="app-6">
<p>{{ message }}</p>
<input v-model="message">
</div>
<script th:inline="javascript">
var username = /*[[${name}]]*/ 'No Body';
var app6 = new Vue({
el: '#app-6',
data: {
message: 'Hello ' + username + ' !'
}
})
</script>
</body>
</html>
thymeleaf 最大一點好處就是沒有後端應用來渲染頁面可以正常執行,但是會稍微比以前多花點心思來寫,但好處是你們家前後分離開發的話,你就可以讓前端來獨立作業,比如說像上面這個範例
在 Server 上執行
前端工程師直接開啟index.html
javascript 都沒有崩壞~~!! 灑花 。:.゚ヽ(*´∀`)ノ゚.:。
這樣你就可以脫離前端開發的工作,專心開發後端部分
ps.如果你們沒有前端工程師的話,就忘了這段吧,你自己一個人寫方便就好
再使用 js 來操作 ajax 取得資料
官方原本有開發一個 ajax 套件 vue-resource,但是之後不會再維護了,為什麼不維護請參考下面
为何放弃vue-resource
尤大的原话:
最近团队讨论了一下,Ajax 本身跟 Vue 并没有什么需要特别整合的地方,使用 fetch polyfill 或是 axios、superagent 等等都可以起到同等的效果,vue-resource 提供的价值和其维护成本相比并不划算,所以决定在不久以后取消对 vue-resource 的官方推荐。已有的用户可以继续使用,但以后不再把 vue-resource 作为官方的 ajax 方案。
知乎链接:https://www.zhihu.com/question/52418455/answer/130535375
官方網頁沒有找到特別推薦的 ajax 方案,但是有 axios 的範例,有些部落格是說官方推薦的是 axios ,寫起來是差不多就一起練一下
Spring 中增加一個回覆時間的資料
@RestController
public class TimeController {
@GetMapping(path = "time")
public Map getName() {
Map map = new HashMap();
map.put("name", "Sam");
map.put("time", new Date());
return map;
}
}
指定時間轉換的格式
spring:
jackson:
date-format: com.fasterxml.jackson.databind.util.ISO8601DateFormat
time-zone: UTC
使用 webjar 加入前端 js
<dependency>
<groupId>org.webjars.bower</groupId>
<artifactId>vue-resource</artifactId>
<version>1.0.3</version>
</dependency>
<dependency>
<groupId>org.webjars.bower</groupId>
<artifactId>axios</artifactId>
<version>0.15.2</version>
</dependency>
前端網頁
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>Title</title>
<script src="https://unpkg.com/vue/dist/vue.js" th:src="@{/webjars/vue/2.1.3/vue.js}"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js" th:src="@{/webjars/axios/0.15.2/dist/axios.js}"></script>
<script src="https://cdn.jsdelivr.net/vue.resource/1.0.3/vue-resource.min.js"
th:src="@{/webjars/vue-resource/1.0.3/dist/vue-resource.js}"></script>
</head>
<body>
<div id="app-6">
<p>{{ message }}</p>
<input v-model="message">
</div>
<script th:inline="javascript">
var username = /*[[${name}]]*/ 'No Body';
var app6 = new Vue({
el: '#app-6',
data: {
message: 'Hello ' + username + ' !',
apiUrl: '/time'
},
mounted: function () {
// 只是定時器
setInterval(this.gettime, 1000);
//this.cycle();
},
methods: {
// 只是定時器
cycle: function () {
this.gettime();
//setTimeout(this.cycle, 1000);
},
gettime: function () {
// use axios
var self = this;
axios.get(this.apiUrl)
.then(function (response) {
self.message = response.data.name + ', Time is ' + response.data.time;
})
.catch(function (error) {
console.log(error);
});
// use vue-resource
// this.$http.get(this.apiUrl, {}, {
// headers: {}
// }).then(function (response) {
// // 這裡是處理正確的回調
// this.message = response.data.name + ', Time is ' + response.data.time
// // this.articles = response.data["subjects"] 也可以
// }, function (response) {
// // 這裡是處理錯誤的回調
// console.log(response)
// });
}
}
})
</script>
</body>
</html>
說明一下改了什麼
<script src="https://unpkg.com/axios/dist/axios.min.js" th:src="@{/webjars/axios/0.15.2/dist/axios.js}"></script>
<script src="https://cdn.jsdelivr.net/vue.resource/1.0.3/vue-resource.min.js"
th:src="@{/webjars/vue-resource/1.0.3/dist/vue-resource.js}"></script>
vue 初始化
mounted: function () {
this.cycle();
}
// use axios
var self = this;
axios.get(this.apiUrl)
.then(function (response) {
self.message = response.data.name + ', Time is ' + response.data.time;
})
.catch(function (error) {
console.log(error);
});
this.$http.get(this.apiUrl, {}, {
headers: {}
}).then(function (response) {
// 這裡是處理正確的回調
this.message = response.data.name + ', Time is ' + response.data.time
// this.articles = response.data["subjects"] 也可以
}, function (response) {
// 這裡是處理錯誤的回調
console.log(response)
});
其實都算好寫啦,差別是 vue-resource 裡面的 this 可以很輕易指到 vue 物件裡面的 data,而 axios 裡面的 this 應該是指到 axios 自己吧,不過這一點點沒什麼影響開發。
完成圖
這樣一個簡單的後台就可以取得很多資訊了