实现图书列表
首先,在src/main/webapp目录下创建images、resources、upload三个目录,且在resources目录下引入bootstrap、layui、jquery等前端插件
Bootstrap
Bootstrap是全球最受欢迎的前端组件库,由推特(TWitter)开源。
Bootstrap用于开发响应式布局、移动设备优先的WEB项目。
Bootstrap提供完整的HTML、CSS和JS开发工具集。
Bootstrap中文网
在src/main/webapp/WEB-INF/ftl目录下创建index.ftl文件,然后可以删除src/main/webapp目录下的index.html文件。
<!DOCTYPE html>
<html lang="en"><head>
<meta charset="UTF-8">
<title>书评网</title>
<meta name="viewport" content="width=device-width,initial-scale=1.0, maximum-scale=1.0,user-scalable=no">
<meta name="referrer" content="no-referrer">
<link rel="stylesheet" href="./resources/bootstrap/bootstrap.css">
<link rel="stylesheet" href="./resources/raty/lib/jquery.raty.css">
<script src="./resources/jquery.3.3.1.min.js"></script>
<script src="./resources/bootstrap/bootstrap.min.js"></script>
<script src="./resources/art-template.js"></script>
<script src="./resources/raty/lib/jquery.raty.js"></script>
<style>
.highlight {
color: red !important;
}
a:active{
text-decoration: none!important;
}
</style>
<style>
.container {
padding: 0px;
margin: 0px;
}
.row {
padding: 0px;
margin: 0px;
}
.col- * {
padding: 0px;
}
</style>
<#--定义模板-->
<script type="text/html" id="tpl">
<a href="/book/{{bookId}}" style="color: inherit">
<div class="row mt-2 book">
<div class="col-4 mb-2 pr-2">
<img class="img-fluid" src="{{cover}}">
</div>
<div class="col-8 mb-2 pl-0">
<h5 class="text-truncate">{{bookName}}</h5>
<div class="mb-2 bg-light small p-2 w-100 text-truncate">{{author}}</div>
<div class="mb-2 w-100">{{subTitle}}</div>
<p>
<span class="stars" data-score="{{evaluationScore}}" title="gorgeous"></span>
<span class="mt-2 ml-2">{{evaluationScore}}</span>
<span class="mt-2 ml-2">{{evaluationQuantity}}人已评</span>
</p>
</div>
</div>
</a>
<hr>
</script>
<script>
$.fn.raty.defaults.path ="./resources/raty/lib/images";
// loadMore()加载更多数据
// isReset参数设置为true,代表从第一页开始查询,否则按nextPage查询后续页
function loadMore(isReset){
if(isReset == true){
$("#bookList").html("");
$("#nextPage").val(1);
}
var nextPage = $("#nextPage").val();
var categoryId= $("#categoryId").val();
var order = $("#order").val();
$.ajax({
url : "/books" ,
data : {p:nextPage,"categoryId":categoryId , "order":order},
type : "get" ,
dataType : "json" ,
success : function(json){
console.info(json);
var list = json.records;
for(var i = 0 ; i < list.length ; i++){
var book = json.records[i];
// var html = "<li>" + book.bookName + "</li>";
// 将数据结合tpl模板,生成html
var html = template("tpl" , book);
console.info(html);
$("#bookList").append(html);
}
// 显示星型评价组件
$(".stars").raty({readOnly:true});
// 判断是否到最后一页
if(json.current < json.pages){
$("#nextPage").val(parseInt(json.current) + 1);
$("#btnMore").show();
$("#divNoMore").hide();
}else{
$("#btnMore").hide();
$("#divNoMore").show();
}
}
})
}
$(function(){
/*$.ajax({
url : "/books" ,
data : {p:1},
type : "get" ,
dataType : "json" ,
success : function(json){
console.info(json);
var list = json.records;
for(var i = 0 ; i < list.length ; i++){
var book = json.records[i];
// var html = "<li>" + book.bookName + "</li>";
// 将数据结合tpl模板,生成html
var html = template("tpl" , book);
console.info(html);
$("#bookList").append(html);
}
// 显示星型评价组件
$(".stars").raty({readOnly:true});
}
})*/
loadMore(true);
})
// 绑定加载更多按钮单击事件
$(function(){
$("#btnMore").click(function(){
loadMore();
})
$(".category").click(function () {
$(".category").removeClass("highlight");
$(".category").addClass("text-black-50");
$(this).addClass("highlight");
var categoryId = $(this).data("category");
$("#categoryId").val(categoryId);
loadMore(true);
})
$(".order").click(function(){
$(".order").removeClass("highlight");
$(".order").addClass("text-black-50");
$(this).addClass("highlight");
var order = $(this).data("order");
$("#order").val(order);
loadMore(true);
})
})
</script>
</head>
<body>
<div class="container">
<nav class="navbar navbar-light bg-white shadow mr-auto">
<ul class="nav">
<li class="nav-item">
<a href="/" style="color: #aaa;font-weight: bold">
书评网
</a>
</li>
</ul>
<#if loginMember??>
<h6 class="mt-1">
<img style="width: 2rem;margin-top: -5px" class="mr-1" src="./images/user_icon.png">${loginMember.nickname}
</h6>
<#else>
<a href="/login.html" class="btn btn-light btn-sm">
<img style="width: 2rem;margin-top: -5px" class="mr-1" src="./images/user_icon.png">登录
</a>
</#if>
</nav>
<div class="row mt-2">
<div class="col-8 mt-2">
<h4>热评好书推荐</h4>
</div>
<div class="col-8 mt-2">
<span data-category="-1" style="cursor: pointer" class="highlight font-weight-bold category">全部</span>
|
<#list categoryList as category>
<a style="cursor: pointer" data-category="${category.categoryId}" class="text-black-50 font-weight-bold category">${category.categoryName}</a>
<#if category_has_next>|</#if>
</#list>
</div>
<div class="col-8 mt-2">
<span data-order="quantity" style="cursor: pointer" class="order highlight font-weight-bold mr-3">按热度</span>
<span data-order="score" style="cursor: pointer" class="order text-black-50 mr-3 font-weight-bold">按评分</span>
</div>
</div>
<div class="d-none">
<input type="hidden" id="nextPage" value="2">
<input type="hidden" id="categoryId" value="-1">
<input type="hidden" id="order" value="quantity">
</div>
<div id="bookList">
</div>
<button type="button" id="btnMore" data-next-page="1" class="mb-5 btn btn-outline-primary btn-lg btn-block">
点击加载更多...
</button>
<div id="divNoMore" class="text-center text-black-50 mb-5" style="display: none;">没有其他数据了</div>
</div>
</body></html>
显式图书类别
在com.ql.reader.entity包下创建实体类
package com.ql.reader.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
/**
* 图书分类实体
*/
@TableName("category")
public class Category {
@TableId(type = IdType.AUTO)
private Long categoryId;
private String categoryName;
public Long getCategoryId() {
return categoryId;
}
public void setCategoryId(Long categoryId) {
this.categoryId = categoryId;
}
public String getCategoryName() {
return categoryName;
}
public void setCategoryName(String categoryName) {
this.categoryName = categoryName;
}
@Override
public String toString() {
return "Category{" +
"categoryId=" + categoryId +
", categoryName='" + categoryName + '\'' +
'}';
}
}
然后在com.ql.reader.mapper包下创建CategoryMapper接口
package com.ql.reader.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ql.reader.entity.Category;
/**
* 图书分类Mapper接口
*/
public interface CategoryMapper extends BaseMapper<Category> {
}
在src/main/resources/mappers目录下创建category.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ql.reader.mapper.CategoryMapper">
</mapper>
然后在com.ql.reader.service包下创建接口CategoryService接口,且在com.ql.reader.service.impl包下创建它的实现类
package com.ql.reader.service;
import com.ql.reader.entity.Category;
import java.util.List;
public interface CategoryService {
public List<Category> selectAll();
}
package com.ql.reader.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.ql.reader.entity.Category;
import com.ql.reader.mapper.CategoryMapper;
import com.ql.reader.service.CategoryService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.List;
@Service("categoryService")
@Transactional(propagation = Propagation.NOT_SUPPORTED,readOnly = true)
public class CategoryServiceImpl implements CategoryService {
@Resource
private CategoryMapper categoryMapper;
/**
* 查询所有图书分类
* @return 图书分类List
*/
public List<Category> selectAll() {
List<Category> list = categoryMapper.selectList(new QueryWrapper<Category>());
return list;
}
}
然后在src/test/java/com/ql/reader/service/impl目录下编写测试用例,并运行
package com.ql.reader.service.impl;
import com.ql.reader.entity.Category;
import com.ql.reader.service.CategoryService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.annotation.Resource;
import java.util.List;
import static org.junit.Assert.*;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class CategoryServiceImplTest {
@Resource
private CategoryService categoryService;
@Test
public void selectAll() {
List<Category> list = categoryService.selectAll();
System.out.println(list);
}
}
测试没问题,在com.ql.reader.controller包下创建BookController.java
package com.ql.reader.controller;
import com.ql.reader.entity.Category;
import com.ql.reader.service.CategoryService;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.ModelAndView;
import javax.annotation.Resource;
import java.util.List;
@Controller
public class BookController {
@Resource
private CategoryService categoryService;
/**
* 显式首页
* @return
*/
@GetMapping("/")
public ModelAndView showIndex(){
ModelAndView mav = new ModelAndView("/index");
List<Category> list = categoryService.selectAll();
mav.addObject("categoryList", list);
return mav;
}
}
然后运行项目,在浏览器中访问http://localhost:8080/分类信息显式成功。
实现图书分页查询
在com.ql.reader.entity包下创建实体类
package com.ql.reader.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
@TableName("book")
public class Book {
// 图书编号
// 映射相应主键,并设置为自增
@TableId(type = IdType.AUTO)
private Long bookId;
// 书名
private String bookName;
// 子标题
private String subTitle;
// 作者
private String author;
// 封面图片URL
private String cover;
// 图书详情
private String description;
// 分类编号
private Long categoryId;
// 图书评分
private float evaluationScore;
// 评价数量
private Integer evaluationQuantity;
public Long getBookId() {
return bookId;
}
public void setBookId(Long bookId) {
this.bookId = bookId;
}
public String getBookName() {
return bookName;
}
public void setBookName(String bookName) {
this.bookName = bookName;
}
public String getSubTitle() {
return subTitle;
}
public void setSubTitle(String subTitle) {
this.subTitle = subTitle;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public String getCover() {
return cover;
}
public void setCover(String cover) {
this.cover = cover;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Long getCategoryId() {
return categoryId;
}
public void setCategoryId(Long categoryId) {
this.categoryId = categoryId;
}
public float getEvaluationScore() {
return evaluationScore;
}
public void setEvaluationScore(float evaluationScore) {
this.evaluationScore = evaluationScore;
}
public Integer getEvaluationQuantity() {
return evaluationQuantity;
}
public void setEvaluationQuantity(Integer evaluationQuantity) {
this.evaluationQuantity = evaluationQuantity;
}
}
然后在com.ql.reader.mapper包下创建BookMapper 接口
package com.ql.reader.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ql.reader.entity.Book;
public interface BookMapper extends BaseMapper<Book> {
}
然后在src/main/resources/mappers目录下创建book.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ql.reader.mapper.BookMapper">
</mapper>
在com.ql.reader.service包下创建接口BookService,且在com.ql.reader.service.impl创建它的实现类
package com.ql.reader.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.ql.reader.entity.Book;
/**
* 图书服务
*/
public interface BookService {
/**
* 分页查询图书
* @param page 页号
* @param rows 每页记录数
* @return 分页对象
*/
public IPage<Book> paging(Integer page, Integer rows);
}
package com.ql.reader.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ql.reader.entity.Book;
import com.ql.reader.mapper.BookMapper;
import com.ql.reader.service.BookService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
@Service("bookService")
@Transactional(propagation = Propagation.NOT_SUPPORTED, readOnly = true)
public class BookServiceImpl implements BookService {
@Resource
private BookMapper bookMapper;
/**
* 分页查询图书
* @param page 页号
* @param rows 每页记录数
* @return 分页对象
*/
public IPage<Book> paging(Integer page, Integer rows) {
Page<Book> p = new Page<Book>(page, rows);
QueryWrapper<Book> queryWrapper = new QueryWrapper<Book>();
IPage<Book> pageObject = bookMapper.selectPage(p, queryWrapper);
return pageObject;
}
}
然后在src/test/java/com/ql/reader/service/impl目录下编写测试用例,并运行
package com.ql.reader.service.impl;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.ql.reader.entity.Book;
import com.ql.reader.service.BookService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.annotation.Resource;
import java.util.List;
import static org.junit.Assert.*;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class BookServiceImplTest {
@Resource
private BookService bookService;
@Test
public void paging() {
IPage<Book> pageObject = bookService.paging(2, 10);
List<Book> records = pageObject.getRecords();
for (Book book: records){
System.out.println(book.getBookId()+":"+book.getBookName());
}
System.out.println("总页数:"+pageObject.getPages());
System.out.println("总记录数"+pageObject.getTotal());
}
}
然后打开com.ql.reader.controller.BookController.java添加分页查询方法
/**
* 分页查询图书列表
* @param p 页号
* @return 分页对象
*/
@GetMapping("/books")
@ResponseBody
public IPage<Book> selectBook(Integer p){
if(p==null){
p=1;
}
IPage<Book> pageObject = bookService.paging(p, 10);
return pageObject;
}
实现图书分页前端部分需要用到JS模板引擎及星星评分组件。
Art-Template 腾讯JS模板引擎
Art-Template是一款开源的JS模板引擎,其主要用途为高效的基于模板来生成HTML片段。
具体前端代码为
<#--引入jquery组件-->
<script src="./resources/jquery.3.3.1.min.js"></script>
<#--引入JS模板引擎-->
<script src="./resources/art-template.js">
</script>
<#--引入星星评分组件-->
<script src="./resources/raty/lib/jquery.raty.js"></script>
<#--定义模板-->
<script type="text/html" id="tpl">
<a href="/book/{{bookId}}" style="color: inherit">
<div class="row mt-2 book">
<div class="col-4 mb-2 pr-2">
<img class="img-fluid" src="{{cover}}"/>
</div>
<div class="col-8 mb-2 pl-0">
<h5 class="text-truncate">{{bookName}}</h5>
<div class="mb-2 bg-light small p-2 w-100 text-truncate">{{author}}</div>
<div class="mb-2 w-100">{{subTitle}}</div>
<p>
<span class="stars" data-score="{{evaluationScore}}" title="gorgeous"></span>
<span class="mt-2 ml-2">{{evaluationScore}}</span>
<span class="mt-2 ml-2">{{evaluationQuantity}}人已评</span>
</p>
</div>
</div>
</a>
<hr>
</script>
<script>
$.fn.raty.defaults.path ="./resources/raty/lib/images";
// loadMore()加载更多数据
// isReset参数设置为true,代表从第一页开始查询,否则按nextPage查询后续页
function loadMore(isReset){
if(isReset == true){
$("#bookList").html("");
$("#nextPage").val(1);
}
var nextPage = $("#nextPage").val();
var categoryId= $("#categoryId").val();
var order = $("#order").val();
$.ajax({
url : "/books" ,
data : {p:nextPage,"categoryId":categoryId , "order":order},
type : "get" ,
dataType : "json" ,
success : function(json){
console.info(json);
var list = json.records;
for(var i = 0 ; i < list.length ; i++){
var book = json.records[i];
// var html = "<li>" + book.bookName + "</li>";
// 将数据结合tpl模板,生成html
var html = template("tpl" , book);
console.info(html);
$("#bookList").append(html);
}
// 显示星型评价组件
$(".stars").raty({readOnly:true});
// 判断是否到最后一页
if(json.current < json.pages){
$("#nextPage").val(parseInt(json.current) + 1);
$("#btnMore").show();
$("#divNoMore").hide();
}else{
$("#btnMore").hide();
$("#divNoMore").show();
}
}
})
}
$(function(){
/*$.ajax({
url : "/books" ,
data : {p:1},
type : "get" ,
dataType : "json" ,
success : function(json){
console.info(json);
var list = json.records;
for(var i = 0 ; i < list.length ; i++){
var book = json.records[i];
// var html = "<li>" + book.bookName + "</li>";
// 将数据结合tpl模板,生成html
var html = template("tpl" , book);
console.info(html);
$("#bookList").append(html);
}
// 显示星型评价组件
$(".stars").raty({readOnly:true});
}
})*/
loadMore(true);
})
$(function(){
// 绑定加载更多按钮单击事件
$("#btnMore").click(function(){
loadMore();
})
})
</script>
然后运行项目,在浏览器中访问http://localhost:8080/并按F12把浏览器调成手机模式
鼠标滚轮往下滑到页面底部,点击“点击加载更多”按钮
加载5次后查出了所有数据,页底也没有加载按钮了。