11.15 实现写文章模块
我们在列表页上面添加一个“写文章”的入口:
1. <a href="addArticleView" target="_blank" class="btn btn-primary pull-right add-article">写文章</a>
其中,btn btn-primary pull-right 这三个css样式类是Flat UI组件的。add-article是我们自定义的样式类:
1. .add-article {
2. margin: 20px;
3. }
下面我们来写新建文章的页面。我们写文章的跳转页面路径是 <a href="addArticleView">, 我们先来新建一个写文章页面addArticleView.ftl:
1. <!DOCTYPE html>
2. <html>
3. <#include "head.ftl">
4. <body>
5. <div class="container">
6. <h2>写文章</h2>
8. <form id="addArticleForm" class="form-horizontal">
9. <div class="form-group">
10. <input type="text" name="title" class="form-control" placeholder="文章标题">
11. </div>
13. <div class="form-group">
14. <textarea id="articleContentEditor" type="text" name="content" class="form-control" rows="20"
15. placeholder=""></textarea>
16. </div>
18. <div class="form-group save-article">
19. <div class="col-sm-offset-2 col-sm-10">
20. <button type="submit" class="btn btn-primary" id="addArticleBtn">保存并发表</button>
21. </div>
22. </div>
23. </form>
24. </div>
25. </body>
26. </html>
然后,再添加控制器请求转发。这里我们使用集成WebMvcConfigurerAdapter类,重写实现addViewControllers方法的方式来添加一个不带数据传输的,单纯的请求转发的跳转View的RequestMapping Controller:
1. @Configuration
2. class WebMvcConfig : WebMvcConfigurerAdapter() {
3. // 注册简单请求转发跳转View的RequestMapping Controller
4. override fun addViewControllers(registry: ViewControllerRegistry?) {
5. //写文章的RequestMapping
6. registry?.addViewController("addArticleView")?.setViewName("addArticleView")
7. }
8. }
这样前端浏览器来的请求addArticle会直接映射转发到视图addArticle.ftl文件渲染解析。
重启应用,进入到我们的写文章的页面,如下图:

11.15.1 加上导航栏
为了方便页面之间的跳转,我们给我们的博客站点加上导航栏,我们新建一个navbar.ftl文件,内容如下:
1. <nav class="navbar navbar-default" role="navigation">
2. <div class="container-fluid">
3. <div class="navbar-header">
4. <button type="button" class="navbar-toggle" data-toggle="collapse"
5. data-target="#example-navbar-collapse">
6. <span class="sr-only">切换导航</span>
7. <span class="icon-bar"></span>
8. <span class="icon-bar"></span>
9. <span class="icon-bar"></span>
10. </button>
11. <a class="navbar-brand" href="#">我的博客</a>
12. </div>
13. <div class="collapse navbar-collapse" id="example-navbar-collapse">
14. <ul class="nav navbar-nav">
15. <li class=""><a href="listAllArticleView">文章列表</a></li>
16. <li class="active"><a href="addArticleView">写文章</a></li>
17. <li><a href="#">关于</a></li>
18. <li class="dropdown">
19. <a href="http://www.jianshu.com/nb/12976878" class="dropdown-toggle" data-toggle="dropdown">
20. Kotlin <b class="caret"></b>
21. </a>
22. <ul class="dropdown-menu">
23. <li><a href="http://www.jianshu.com/nb/12976878" target="_blank">Kotlin极简教程</a></li>
24. <li class="divider"></li>
25. <li><a href="#">Java</a></li>
26. <li><a href="#">Scala</a></li>
27. <li><a href="#">Groovy</a></li>
28. <li class="divider"></li>
29. <li><a href="http://www.jianshu.com/nb/12066555" target="_blank">SpringBoot极简教程</a></li>
30. </ul>
31. </li>
32. </ul>
33. </div>
34. </div>
35. </nav>
11.15.2 抽取公共模板文件
考虑到head.ftl、navbar.ftl都是公共的文件,我们把他们单独放到一个common目录下。
然后,我们分别在addArticleView.ftl、listAllArticleView.ftl引用如下:
1. <!DOCTYPE html>
2. <html>
3. <#include "common/head.ftl">
4. <body>
5. <#include "common/navbar.ftl">
加入了导航栏之后,我们的页面现在更加美观了:


11.15.3 写文章的控制器层接口
控制器层保存接口:
1. @PostMapping("saveArticle")
2. @ResponseBody
3. fun saveArticle(article: Article): Article? {
4. article.gmtCreated = Date()
5. article.gmtModified = Date()
6. article.version = 0
7. return articleRepository?.save(article)
8. }
另外,为了支持较多内容的文章,我们把文章内容字段设置成LONGTEXT:
1. ALTER TABLE `blog`.`article`
2. CHANGE COLUMN `content` `content` LONGTEXT NULL DEFAULT NULL ;
11.15.4 前端 ajax 请求
我们在blog.js中加上ajax POST请求后端接口的逻辑:
1. $(function () {
3. $('#addArticleBtn').on('click', function () {
4. saveArticle()
5. })
7. function saveArticle() {
8. $.ajax({
9. url: "saveArticle",
10. data: $('#addArticleForm').serialize(),
11. type: "POST",
12. async: false,
13. success: function (resp) {
14. if (resp) {
15. saveArticleSuccess(resp)
16. } else {
17. saveArticleFail()
18. }
19. },
20. error: function () {
21. saveArticleFail()
22. }
23. })
24. }
26. function saveArticleSuccess(resp) {
27. alert('保存成功: ' + JSON.stringify(resp))
28. window.open('detailArticleView?id=' + resp.id)
29. }
31. function saveArticleFail() {
32. alert("保存失败!")
33. }
35. })
11.15.5 文章详情页
保存成功后,我们默认跳转该文章详情页。 后端控制器代码:
1. @GetMapping("detailArticleView")
2. fun detailArticleView(id: Long, model: Model): ModelAndView {
3. model.addAttribute("article", articleRepository?.findById(id)?.get())
4. return ModelAndView("detailArticleView")
5. }
注意,这里的articleRepository?.findById(id) 方法返回的是Optional
, 我们调用其get()方法,返回真正的Article实体对象。
前端视图detailArticleView.ftl代码:
1. <!DOCTYPE html>
2. <html>
3. <#include "common/head.ftl">
4. <body>
5. <#include "common/navbar.ftl">
6. <div class="container">
7. <h3>${article.title}</h3>
8. <h6>${article.author}</h6>
9. <h6>${article.gmtModified}</h6>
10. <div>${article.content}</div>
11. </div>
12. </body>
13. </html>
我们在文章列表页中,给每篇文章标题加上跳转文章详情的超链接:
1. <td><a target="_blank" href="detailArticleView?id=${article.id}">${article.title}</a></td>
现在我们的文章列表页面如下:

点击一篇文章标题,即可进入详情页:

