《深入實踐Spring Boot》第3章 Spring Boot界面設計

第3章 Spring Boot界面設計

用Spring Boot框架設計Web顯示界面,我們還是使用MVC(Model View Controller,模型-視圖-控制器)的概念,將數據管理、事件控制和界面顯示進行分層處理,實現多層結構設計。界面設計,即視圖的設計,主要是組織和處理顯示的內容,界面上的事件響應最終交給了控制器進行處理,由控制器決定是否調用模型進行數據的存取操作,然後再將結果返回給合適的視圖顯示。

本章的實例工程使用分模塊管理,如表3-1所示,即將數據管理獨立成爲一個工程模塊,專門負責數據庫方面的功能呢。而界面設計模塊主要負責控制器和視圖設計方面的功能。

項目 工程 功能
數據管理模塊 data 實現使用Neo4j數據庫
界面設計模塊 webui 控制器和視圖設計

3.1 模型設計

數據管理模塊實現了MVC中模塊的設計,主要負責實體建模和數據庫持久化等方面的功能。在本章的實例中,將使用上一章的Neo4j數據庫的例子,對電影數據進行管理。回顧一下,有兩個節點實體(電影和演員)和一個關係實體(角色)。其中,關係實體體現了節點實體之間的關係,即一個演員在一部電影中扮演一個角色。實體建模和持久化與上一章的實現差不多。只不過爲了適應本章的內容,電影節點實體和角色關係實體的建模在屬性上做了些許調整。另外針對Neo4j數據庫的分頁查詢也做了一些調整和優化。

3.1.1 節點實體建模

如代碼清單3-1所示,在電影節點實體建模中做了一些調整,即增加一個photo屬性,用來存放電影劇照,並將關係類型更改爲“扮演”。需要注意的是Neo4j還沒有日期格式的數據類型,所以在讀取日期類型的數據時,使用註解@DateTimeFormat進行格式轉換,而在保存時,使用註解@DateLong將它轉換成Long類型的數據進行存儲。

package com.test.data.domain;

import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.voodoodyne.jackson.jsog.JSOGGenerator;
import org.neo4j.ogm.annotation.GraphId;
import org.neo4j.ogm.annotation.NodeEntity;
import org.neo4j.ogm.annotation.Relationship;
import org.neo4j.ogm.annotation.typeconversion.DateLong;
import org.springframework.format.annotation.DateTimeFormat;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

@JsonIdentityInfo(generator = JSOGGenerator.class)
@NodeEntity
public class Movie {
    @GraphId
    Long id;
    private String name;
    private String photo;
    @DateLong
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date createDate;

    @Relationship(type = "扮演", direction = Relationship.INCOMING)
    List<Role> roles = new ArrayList<>();

    public Role addRole(Actor actor, String name) {
        Role role = new Role(actor, this, name);
        this.roles.add(role);
        return role;
    }

    public Movie() {
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public List<Role> getRoles() {
        return roles;
    }

    public void setRoles(List<Role> roles) {
        this.roles = roles;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPhoto() {
        return photo;
    }

    public void setPhoto(String photo) {
        this.photo = photo;
    }

    public Date getCreateDate() {
        return createDate;
    }

    public void setCreateDate(Date createDate) {
        this.createDate = createDate;
    }
}

3.1.2 關係實體建模

電影實體對應的角色關係實體建模的關係類型也同樣做了調整而改爲“扮演”,如代碼清單3-2所示。

package com.test.data.domain;

import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.voodoodyne.jackson.jsog.JSOGGenerator;
import org.neo4j.ogm.annotation.EndNode;
import org.neo4j.ogm.annotation.GraphId;
import org.neo4j.ogm.annotation.RelationshipEntity;
import org.neo4j.ogm.annotation.StartNode;

@JsonIdentityInfo(generator = JSOGGenerator.class)
@RelationshipEntity(type = "扮演")
public class Role {
    @GraphId
    Long id;
    String name;
    @StartNode
    Actor actor;
    @EndNode
    Movie movie;

    public Role() {
    }

    public Role(Actor actor, Movie movie, String name) {
        this.actor = actor;
        this.movie = movie;
        this.name = name;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Actor getActor() {
        return actor;
    }

    public Movie getMovie() {
        return movie;
    }

}

3.1.3 分頁查詢設計

對於新型的Neo4j數據庫來說,由於它的資源庫遵循了JPA的規範標準來設計,在分頁查詢方面有的地方還不是很完善,所以在分頁查詢中,設計了一個服務類來處理,如代碼清單3-3所示。其中,使用Class<T>傳入調用的實體對象,使用Pageable傳入頁數設定和排序字段設定的參數,使用Filters傳入查詢的一些節點屬性設定的參數。

package com.test.data.service;

import org.neo4j.ogm.cypher.Filters;
import org.neo4j.ogm.cypher.query.Pagination;
import org.neo4j.ogm.cypher.query.SortOrder;
import org.neo4j.ogm.session.Session;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

@Service
public class PagesService<T> {
    @Autowired
    private Session session;


    public Page<T> findAll(Class<T> clazz, Pageable pageable, Filters filters) {
        Collection data = this.session.loadAll(clazz, filters, convert(pageable.getSort()), new Pagination(pageable.getPageNumber(), pageable.getPageSize()), 1);
        return updatePage(pageable, new ArrayList(data));
    }

    private Page<T> updatePage(Pageable pageable, List<T> results) {
        int pageSize = pageable.getPageSize();
        int pageOffset = pageable.getOffset();
        int total = pageOffset + results.size() + (results.size() == pageSize ? pageSize : 0);
        return new PageImpl(results, pageable, (long) total);
    }

    private SortOrder convert(Sort sort) {
        SortOrder sortOrder = new SortOrder();
        if (sort != null) {
            Iterator var3 = sort.iterator();

            while (var3.hasNext()) {
                Sort.Order order = (Sort.Order) var3.next();
                if (order.isAscending()) {
                    sortOrder.add(new String[]{order.getProperty()});
                } else {
                    sortOrder.add(SortOrder.Direction.DESC, new String[]{order.getProperty()});
                }
            }
        }
        return sortOrder;
    }
}

3.2 控制器設計

怎樣將視圖上的操作與模型——數據管理模塊聯繫起來,這中間始終是控制器在起着通信橋樑的作用,它響應視圖上的操作事件,然後根據需要決定是否訪問數據管理模塊,最後再將結果返回給合適的視圖,由視圖處理顯示。下面將按照電影控制器的設計來說明控制器中增刪改查的實現方法,演員控制器的設計與此類似,不再贅述。

3.2.1 新建控制器

接收新建電影的請求,以及輸入一部電影的數據後的最後提交,由新建控制器進行處理。在控制器上將執行兩個操作,第一個操作將返回一個新建電影的視圖,第二個操作接收界面中的輸入數據,並調用數據管理模塊進行保存,如代碼清單3-4所示。其中,create函數將返回一個新建電影的視圖,它不調用數據管理模塊,save函數將需要保存的數據通過調用數據管理模塊存儲至數據庫中,並返回一個成功標誌。注意,爲了簡化設計,將電影劇照的圖片文件做了預定義處理。

@RequestMapping("/new")
public ModelAndView create(ModelMap model) {
    String[] files = {"/images/movie/西遊記.jpg", "/images/movie/西遊記續集.jpg"};
    model.addAttribute("files", files);
    return new ModelAndView("movie/new");
}


@RequestMapping(value = "/save", method = RequestMethod.POST)
public String save(Movie movie) {
    movieRepository.save(movie);
    logger.info("新增->ID={}", movie.getId());
    return "1";
}

3.2.2 查看控制器

查看一個電影的詳細信息時,控制器首先使用請求的電影ID向數據管理模塊請求數據,然後將取得的數據輸出到一個顯示視圖上,如代碼清單3-5所示。

@RequestMapping(value = "/{id}")
public ModelAndView show(ModelMap model, @PathVariable Long id) {
    Movie movie = movieRepository.findOne(id);
    model.addAttribute("movie", movie);
    return new ModelAndView("movie/show");
}

3.2.3 修改控制器

若要實現對電影的修改及保存操作,需要先將電影的數據顯示在視圖界面上,然後接收界面的操作,調用數據管理模塊將更改的數據保存至數據庫中,如代碼清單3-6所示。其中,爲了簡化設計,將劇照中的圖片文件和電影角色名稱做了預定義處理。修改數據時,由於從界面傳回的電影對象中,丟失了其角色關係的數據(這是OGM的缺點),所以再次查詢一次數據庫,以取得一個電影的完整數據,然後在執行修改的操作。

@RequestMapping(value = "/edit/{id}")
public ModelAndView update(ModelMap model, @PathVariable Long id) {
    Movie movie = movieRepository.findOne(id);
    String[] files = {"/images/movie/西遊記.jpg", "/images/movie/西遊記續集.jpg"};
    String[] rolelist = new String[]{"唐僧", "孫悟空", "豬八戒", "沙僧"};
    Iterable<Actor> actors = actorRepository.findAll();

    model.addAttribute("files", files);
    model.addAttribute("rolelist", rolelist);
    model.addAttribute("movie", movie);
    model.addAttribute("actors", actors);

    return new ModelAndView("movie/edit");
}

@RequestMapping(method = RequestMethod.POST, value = "/update")
public String update(Movie movie, HttpServletRequest request) {
    String rolename = request.getParameter("rolename");
    String actorid = request.getParameter("actorid");

    Movie old = movieRepository.findOne(movie.getId());
    old.setName(movie.getName());
    old.setPhoto(movie.getPhoto());
    old.setCreateDate(movie.getCreateDate());

    if (!StringUtils.isEmpty(rolename) && !StringUtils.isEmpty(actorid)) {
        Actor actor = actorRepository.findOne(new Long(actorid));
        old.addRole(actor, rolename);
    }
    movieRepository.save(old);
    logger.info("修改->ID=" + old.getId());
    return "1";
}

3.2.4 刪除控制器

刪除電影時,從界面上接收電影的ID參數,然後調用數據管理模塊將電影刪除,如代碼清單3-7所示。

@RequestMapping(value = "/delete/{id}", method = RequestMethod.GET)
public String delete(@PathVariable Long id) {
    Movie movie = movieRepository.findOne(id);
    movieRepository.delete(movie);
    logger.info("刪除->ID=" + id);
    return "1";
}

3.2.5 分頁查詢控制器

列表數據的查詢使用分頁的方法,按提供的查詢字段參數、頁面、頁大小及其排序字段等參數,通過調用數據管理模塊進行查詢,然後返回一個分頁對象Page,如代碼清單3-8所示。這裏的分頁查詢調用了3.1.3節定義的分頁查詢服務類。

@RequestMapping(value = "/list")
public Page<Movie> list(HttpServletRequest request) {
    String name = request.getParameter("name");
    String page = request.getParameter("page");
    String size = request.getParameter("size");
    Pageable pageable = new PageRequest(page == null ? 0 : Integer.parseInt(page), size == null ? 10 : Integer.parseInt(size),
                                        new Sort(Sort.Direction.DESC, "id"));

    Filters filters = new Filters();
    if (!StringUtils.isEmpty(name)) {
        Filter filter = new Filter("name", name);
        filters.add(filter);
    }

    return pagesService.findAll(Movie.class, pageable, filters);
}

3.3 使用Thymeleaf模板

完成了模型和控制器的設計之後,接下來的工作就是視圖設計了。在視圖設計中主要使用Thymeleaf模板來實現。在進行視圖設計之前,先了解一下Thymeleaf模板的功能。

Thymeleaf是一個優秀的面向Java的XML/XHTML/HTML5頁面模板,並具有豐富的標籤語言和函數。使用Spring Boot框架進行界面設計,一般都會選擇Thymeleaf模板。

3.3.1 Thymeleaf配置

要使用Thymeleaf模板,首先,必須在工程的Maven管理中引入它的依賴:“spring-boot-starter-thymeleaf”,如代碼清單3-9所示。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

其次,必須配置使用Thymeleaf模板的一些參數。在一般的Web項目中都會使用如代碼清單3-10所示的配置,其中,prefix指定了HTML文件存放在webapp的/WEB-INF/views/目錄下面,或者也可以指定其他路徑,其他一些參數的設置其實是使用了Thymeleaf的默認配置。

在實例中,爲了更方便將項目發佈成jar文件,我們將使用Thymeleaf自動配置中的默認配置選項,即只要在資源文件夾resources中增加一個templates目錄即可,這個目錄用來存放HTML文件。

spring:
    thymeleaf:
        prefix: /WEB-INF/views/
        suffix: .html
        mode: HTML5
        encoding: UTF-8
        content-type: text/html
        cache: false

注意:如果工程中增加了Thymeleaf的依賴,而沒有進行任何配置,或者增加默認的目錄,啓動應用時就會報錯。

3.3.2 Thymeleaf功能簡介

在HTML頁面上使用Thymeleaf標籤語言,用一個簡單的關鍵字“th”來標註。使用Thymeleaf標籤語言的典型例子如下:

<h3 th:text="${actor.name}"></h3>
<img th:src="@{/images/logo.png}"/>

其中,th:text指定了在標籤<h3>中顯示的文本,它的值來自於關鍵字“$”所引用的內存常量,th:src設定了標籤<img>的圖片文件的鏈接地址,既可以是絕對路徑,也可以是相對路徑。下面列出了Thymeleaf的一些主要標籤和函數。

th:text,顯示文本。
th:utext:和th:text的區別是針對"unescaped text"。
th:attr:設置標籤屬性。
th:if or th:unless:條件判斷語句
th:switch,th:case:選擇語句。
th:each:循環語句。

#dates:日期函數。
#calendars:日曆函數。
#numbers:數字函數。
#strings:字符串函數。
#objects:對象函數。
#bools:邏輯函數。
#arrays:數組函數。
#lists:列表函數。

本章的實例工程將在視圖設計中使用Thymeleaf的下列幾個主要功能,而有關Thymeleaf的詳細說明和介紹可以訪問它的官方網站https://www.thymeleaf.org/,以獲得更多的幫助。

1. 使用功能函數

Thymeleaf有一些日期功能函數、字符串函數、數組函數、列表函數等,代碼清單3-11是Thymeleaf使用日期函數的一個例子,#dates.format是一個日期格式化的使用實例,它將電影的創建日期格式化爲中文環境的使用格式“‘yyyy-MM-dd HH:mm:ss’”。

th:value="${movie.createDate} ? ${#dates.format(movie.createDate,'yyyy-MM-dd HH:mm:ss')} :''"

2. 使用編程語句

Thymeleaf有條件語句、選擇語句、循環語句等。代碼清單3-12使用each循環語句來顯示一個數據列表,即在下拉列表框中使用循環語句來顯示所有的演員列表。

<select name="actorid" id="actorid">
    <option value="">選擇演員</option>
    <option th:each="actor:${actors}"
            th:value="${actor.id}"
            th:text="${actor.name}">
    </option>
</select>

3. 使用頁面框架模板

Thymeleaf的頁面框架模板是比較優秀的功能。預先定義一個layout,它具有頁眉、頁腳、提示欄、導航欄和內容顯示等區域,如代碼清單3-13所示。其中,layout:fragment="prompt"是一個提示欄,它可以讓引用的視圖替換顯示的內容;fragments/nav :: nav是一個導航欄並指定了視圖文件,也就是說它不能被引用的視圖替換內容;layout:fragment="content"是一個主要內容顯示區域,它也能由引用的視圖替換顯示內容;fragments/footer :: footer是一個頁腳定義並且指定了視圖文件,即不被引用的視圖替換顯示內容。這樣設計出來的頁面模板框架如圖3-1所示。

<div class="headerBox">
    <div class="topBox">
        <div class="topLogo f-left">
            <a href="#"><img th:src="@{/images/logo.png}"/></a>
        </div>
        <div class="new-nav">
            <h3>電影頻道</h3>
        </div>
    </div>
</div>
<div class="locationLine" layout:fragment="prompt">
    當前位置:首頁 &gt; <em>頁面</em>
</div>
<table class="globalMainBox" style="position:relative;z-index:1">
    <tr>
        <td class="columnLeftBox" valign="top">
            <div th:replace="fragments/nav :: nav"></div>
        </td>
        <td class="whiteSpace"></td>
        <td class="rightColumnBox" valign="top">
            <div layout:fragment="content"></div>
        </td>
    </tr>
</table>

<div class="footBox" th:replace="fragments/footer :: footer"></div>

有了頁面模板之後,就可以在一個主頁面視圖上引用上面的layout,並替換它的提示欄prompt和主要內容顯示區域content,其他頁眉、頁腳和導航欄卻保持同樣的內容,如代碼清單3-14所示。這樣就可以設計出一個使用共用模板的具有統一風格特徵的界面。

<!DOCTYPE html>
<html xmlns:th="http://www.w3.org/1999/xhtml" xmlns:layout="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <meta http-equiv="pragma" content="no-cache"/>
    <meta http-equiv="Cache-Control" content="no-cache"/>
    <meta http-equiv="Cache-Control" content="no-store"/>
    <meta http-equiv="Expires" content="0"/>
    <title layout:title-pattern="$DECORATOR_TITLE - $CONTENT_TITLE">UI演示</title>
    <link th:href="@{/styles/global.css}" rel="stylesheet" type="text/css"/>
    <script th:src="@{/scripts/jquery.min.js}"></script>
    <script th:src="@{/scripts/artDialog/artDialog.js}"/>
    <script th:src="@{/scripts/common.js}"></script>
    <script th:src="@{/scripts/public/public.js}"></script>
    <script th:src="@{/scripts/public/json2.js}"></script>
    <script type="text/javascript" th:src="@{/scripts/validate/jquery.validate.min.js}"></script>
    <script type="text/javascript" th:src="@{/scripts/validate/chinese.js}"></script>
</head>
<body>
    <div class="headerBox">
        <div class="topBox">
            <div class="topLogo f-left">
                <a href="#"><img th:src="@{/images/logo.png}"/></a>
            </div>
            <div class="new-nav">
                <h3>電影頻道</h3>
            </div>
        </div>
    </div>
    <div class="locationLine" layout:fragment="prompt">
        當前位置:首頁 &gt; <em>頁面</em>
    </div>
    <table class="globalMainBox" style="position:relative;z-index:1">
        <tr>
            <td class="columnLeftBox" valign="top">
                <div th:replace="fragments/nav :: nav"></div>
            </td>
            <td class="whiteSpace"></td>
            <td class="rightColumnBox" valign="top">
                <div layout:fragment="content"></div>
            </td>
        </tr>
    </table>

    <div class="footBox" th:replace="fragments/footer :: footer"></div>

</body>
</html>

3.4 視圖設計

視圖設計包括列表視圖、新建視圖、查看視圖、修改視圖和刪除視圖設計等5個方面有關數據和增刪改查的內容。

我們知道,視圖上的數據存取不是直接與模型打交道,而是通過控制器來處理。在視圖中對於控制器的請求,大多使用jQuery的方式來實現。jQuery是一個優秀的JavaScript程序庫,並且具有很好的兼容性,幾乎兼容了現有的所有瀏覽器。

下面的視圖設計將以電影的視圖設計爲例說明,演員的視圖設計與此類似,不再贅述。

3.4.1 列表視圖設計

電影的列表視圖是電影視圖的主頁,它引用了3.3節使用Thymeleaf設計的頁面框架模板layout.html,在這裏主要實現對數據的分頁查詢請求和列表數據顯示,並提供了一部電影的新建、查看、修改和刪除等超鏈接。

1. 分頁設計

電影的列表視圖的分頁設計使用了“jquery.pagination.js”分頁插件,編寫代碼清單3-15所示的腳本,其中getOpt定義了分頁工具條的一些基本數學,pageaction通過“./list”調用控制器取得分頁數據列表,fillData函數將列表數據填充到HTML控件tbodyContent中。

//分頁的參數設置
var getOpt = function () {
    var opt = {
        items_per_page: 10, //每頁記錄數
        num_display_entries: 3, //中間顯示的頁數個數 默認爲10
        current_page: 0,    //當前頁
        num_edge_entries: 1, //頭尾顯示的頁數個數 默認爲0
        link_to: "javascript:void(0)",
        prev_text: "上頁",
        next_text: "下頁",
        load_first_page: true,
        show_total_info: true,
        show_first_last: true,
        first_text: "首頁",
        last_text: "尾頁",
        hasSelect: false,
        callback: pageselectCallback //回調函數
    }
    return opt;
}
//分頁開始
var currentPageData = null;
var pageaction = function () {
    $.get('./list?t=' + new Date().getTime(), {
        name: $("#name").val()
    }, function (data) {
        currentPageData = data.content;
        $(".pagination").pagination(data.totalElements, getOpt());
    });
}

var pageselectCallback = function (page_index, jq, size) {
    if (currentPageData != null) {
        fillData(currentPageData);
        currentPageData = null;
    } else
        $.get('./list?t=' + new Date().getTime(), {
            size: size, page: page_index, name: $("#name").val()
        }, function (data) {
            fillData(data.content);
        });
}

//填充分頁數據
function fillData(data) {
    var $list = $('#tbodyContent').empty();
    $.each(data, function (k, v) {
        var html = "";
        html += '<tr> ' +
            '<td>' + (v.id == null ? '' : v.id) + '</td>' +
            '<td>' + (v.name == null ? '' : v.name) + '</td>' +
            '<td>' + (v.born == null ? '' : getSmpFormatDateByLong(v.born, true)) + '</td>';
        html += '<td><a class="c-50a73f mlr-6" href="javascript:void(0)" onclick="detail(\'' + v.id + '\')">查看</a><a class="c-50a73f mlr-6" href="javascript:void(0)" onclick="edit(\'' + v.id + '\')">修改</a><a class="c-50a73f mlr-6" href="javascript:void(0)" onclick="del(\'' + v.id + '\')">刪除</a></td>';
        html += '</tr>';

        $list.append($(html));
    });
}

//分頁結束

2. 列表頁面設計

電影列表的顯示頁面主要定義了列表字段的名稱和提供顯示數據內容的控件ID,即tbodyContent,如代碼清單3-16所示。

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.w3.org/1999/xhtml"
      layout:decorator="fragments/layout">
<head>
    <title>電影管理</title>

    <link th:href="@{/scripts/pagination/pagination.css}" rel="stylesheet" type="text/css"/>
    <link th:href="@{/scripts/artDialog/default.css}" rel="stylesheet" type="text/css"/>
    <link th:href="@{/scripts/My97DatePicker/skin/WdatePicker.css}" rel="stylesheet" type="text/css"/>
    <link th:href="@{/styles/index.css}" rel="stylesheet" type="text/css"/>

    <script th:src="@{/scripts/pagination/jquery.pagination.js}"></script>
    <script th:src="@{/scripts/jquery.smartselect-1.1.min.js}"></script>
    <script th:src="@{/scripts/My97DatePicker/WdatePicker.js}"></script>
    <script th:src="@{/scripts/movie/list.js}"></script>
</head>
<body>
    <div class="locationLine" layout:fragment="prompt">
        當前位置:首頁 &gt; <em>電影管理</em>
    </div>

    <div class="statisticBox w-782" layout:fragment="content">
        <form id="queryForm" method="get">
            <div class="radiusGrayBox782">
                <div class="radiusGrayTop782"></div>
                <div class="radiusGrayMid782">
                    <div class="dataSearchBox forUserRadius">
                        <ul>
                            <li>
                                <label class="preInpTxt f-left">電影名稱</label>
                                <input type="text" class="inp-list f-left w-200" placeholder="按電影名稱搜索" id="name"
                                       name="name"/>
                            </li>
                            <li>
                                <a href="javascript:void(0)" class="blueBtn-62X30 f-right" id="searchBtn">查詢</a>
                            </li>
                        </ul>
                    </div>
                </div>
            </div>
        </form>
        <div class="newBtnBox">
            <input type="hidden" id="m_ck"/>
            <a id="addBtn" class="blueBtn-62X30" href="javascript:void(0)">新增</a>
        </div>
        <div class="dataDetailList mt-12">
            <table id="results" class="dataListTab">
                <thead>
                <tr>
                    <th>ID</th>
                    <th>電影</th>
                    <th>出版日期</th>
                    <th>操作</th>
                </tr>
                </thead>
                <tbody id="tbodyContent">
                </tbody>
            </table>
            <div class="tableFootLine">
                <div class="pagebarList pagination"/>
            </div>
        </div>
    </div>

</body>
</html>

3. 列表視圖設計效果

電影數據列表視圖設計的最終顯示效果如圖3-2所示。

3.4.2 新建視圖設計

1. 新建對話框設計

新建電影時,在電影主頁中打開一個對話框顯示新建的操作界面,對話框設計應用了“artDialog.js”的對話框插件,然後編寫一個腳本來打開對話框,如代碼清單3-17所示。其中“./new”是連接控制器的請求URL,注意這裏使用了相對路徑,這個URL通過“$.get”請求返回新建電影的HTML頁面,請求鏈接中的ts參數傳遞的是當前時間,這是爲了保證該連接是一個全新的鏈接,以使瀏覽器能顯示一個最新的內容頁面。

function create() {
    $.get("./new", {ts: new Date().getTime()}, function (data) {
        art.dialog({
            lock: true,
            opacity: 0.3,
            title: "新增",
            width: '750px',
            height: 'auto',
            left: '50%',
            top: '50%',
            content: data,
            esc: true,
            init: function () {
                artdialog = this;
            },
            close: function () {
                artdialog = null;
            }
        });
    });
}

2. 新建頁面設計

新建電影的頁面設計,如代碼清單3-18所示,這裏只是部分HTML編碼,其中的日期控件使用“WdatePicker.js”插件來實現。對於一部電影來說,我們需要輸入名稱、劇照和日期三個屬性,其中劇照的圖片下拉列表使用“imageselect.js”圖片下拉列表框插件來實現,並且爲了簡化設計,劇照中的圖片文件使用了預先定義的文件,這裏只要選擇使用哪一個圖片即可。

<link th:href="@{/styles/imageselect.css}" rel="stylesheet" type="text/css"/>
<script th:src="@{/scripts/imageselect.js}"></script>
<script th:src="@{/scripts/movie/new.js}"></script>

<form id="saveForm" action="./save" method="post">
    <table class="addNewInfList">
        <tr>
            <th>名稱</th>
            <td><input class="inp-list w-200 clear-mr f-left" type="text" id="name" name="name" maxlength="120"/></td>
            <th>劇照</th>
            <td width="240">
                <select name="photo" id="photo">
                    <option th:each="file:${files}"
                            th:value="${file}"
                            th:text="${file}">
                    </option>
                </select>
            </td>
        </tr>
        <tr>
            <th>日期</th>
            <td>
                <input onfocus="WdatePicker({dateFmt:'yyyy-MM-dd HH:mm:ss'})" type="text"
                       class="inp-list w-200 clear-mr f-left" id="createDate" name="createDate"/>
            </td>
        </tr>
    </table>
    <div class="bottomBtnBox">
        <a class="btn-93X38 saveBtn" href="javascript:void(0)">確定</a>
        <a class="btn-93X38 backBtn" href="javascript: closeDialog()">返回</a>
    </div>
</form>
<script type="text/javascript">
    $(document).ready(function () {
        $('select[name=photo]').ImageSelect({dropdownWidth: 425});
    });
</script>

3. 修改視圖的設計效果

最終完成的修改電影視圖的顯示效果如圖3-5所示。

3.4.5 刪除視圖設計

1. 刪除確認對話框

如果有刪除的操作,首先要給出確認提示框,只有用戶單擊確認後才能刪除數據,否則將不做任何操作。確認提示框是調用了Windows中的確認對話框,如代碼清單3-24所示。

function del(id) {
    if (!confirm("您確定刪除此記錄嗎?")) {
        return false;
    }
    $.get("./delete/" + id, {ts: new Date().getTime()}, function (data) {
        if (data == 1) {
            alert("刪除成功");
            pageaction();
        } else {
            alert(data);
        }
    });
}

2. 刪除確認設計效果

執行刪除操作的確認效果如圖3-6所示。

3.5 運行與發佈

本章實例工程的完整代碼可以通過IDEA從GitHub中檢出。Spring Boot需要一個啓動程序作爲應用的入口,在webui模塊中,我們設計了一個入口程序,如代碼清單3-25所示。使用這個入口程序,就可以調試和發佈工程了。

package com.test.webui;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@ComponentScan(basePackages = "com.test")
public class WebuiApp {
    public static void main(String[] args) {
        SpringApplication.run(WebuiApp.class, args);
    }
}

通過在IDEA中打開Run/Debug Configurations對話框,增加一個Spring Boot配置,模塊選擇webui,工作目錄選擇模塊webui所在的路徑,主程序選擇WebuiApp,並將配置保存爲webui。然後在IDEA中運行該配置項目webui,即可啓動應用進行調試。

如果要發佈應用,可以在IDEA的Run/Debug Configurations對話框中增加一個Maven打包配置項目,工作目錄選擇工程的根目錄,命令行中輸入指令:clean package-D skipTests,並將配置保存爲mvn。然後運行這個配置項目mvn進行打包,打包成功後,在“webui/target”目錄中將生產webui-1.0-SNAPSHOT.jar。要運行這個程序包,可以打開一個命令行窗口,將路徑切換到webui-1.0-SNAPSHOT.jar所在的目錄,使用下列指定即可運行應用。

java -jar webui-1.0-SNAPSHOT.jar

最後可使用下面的URL進行訪問:

http://localhost:8080

在實例中增加了一些數據之後,在Neo4j數據庫客戶端中單擊”扮演“關係,也可以看到電影和演員的關係圖,如圖3-7所示。

3.6 小結

本章介紹了使用MVC的多層結構方式,以及在Spring Boot進行Web界面設計的方法,並且使用Thymeleaf模板設計了一個Web頁面的頁面框架。Web頁面設計的一些細節,更多的是使用了HTML編碼和JavaScript腳本,而HTML離不開CSS的支持,JavaScript更是藉助於jQuery及其各種插件的功能。讀者如需深入瞭解這方面的知識和技術,可查找相關的知識進行學習和研究。這裏主要使用Thymeleaf模板工具來設計整體界面以及組織和處理數據的顯示。

有了顯示界面之後,對數據庫的操作就更爲方便和直觀了。下一章將介紹如何使用一些技術手段來提升訪問數據庫的性能,以及怎樣擴展訪問數據庫的功能。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章