记账管理项目
项目git地址:https://gitee.com/pdh_gitee/bill-manager.git
此项目简单的应用到的技术栈:springboot,mybatis和tk mybatis(持久化),thymeleaf(前端页面展示)和一些简单的前端基础知识。零基础即可入门,实现对springboot,mybatis和tk mybatis(持久化),thymeleaf的了解,也可在此基础之上,查看相关的文档,更进一步的了解学习这些知识。下面给两个文档:
SpringBoot中文文档:http://felord.cn/_doc/_springboot/2.1.5.RELEASE/_book/
MyBatis中文文档:https://mybatis.org/mybatis-3/zh/index.html
文章目录
0.项目介绍
基于springboot,mybatis和tk mybatis(持久化),thymeleaf(前端页面展示),实现简单的记账管理功能,数据表有两张(bill和bill_type),bill表包括编号,标题,记账时间,账单类型,金额,解释;bill_type表应包括账单类型编号,账单类型名称。两表的详细信息如下:
0.1 bill_type表
bill_type表的详细信息
字段名 | 中文 | 类型 | 长度 | 约束 | 默认值 | 备注 |
---|---|---|---|---|---|---|
id | 类型编号 | bigint | 20 | 主键、自增、非空 | - | |
name | 类型名称 | varchar | 100 | null |
bill_type表索引
索引名 | 字段 | 索引类型 | 索引方法 | 注释 |
---|---|---|---|---|
id(默认字段) | id | PRIMARY | BTREE | 创建主键时自动生成,无需手动创建 |
建表语句
CREATE TABLE `bill_type` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '类型编号',
`name` varchar(100) DEFAULT NULL COMMENT '类型名称',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1;
0.2 bill_type表
bill表的详细信息
字段名 | 中文 | 类型 | 长度 | 约束 | 默认值 | 备注 |
---|---|---|---|---|---|---|
id | 账单编号 | bigint | 20 | 主键、自增、非空 | - | |
title | 账单标题 | varchar | 100 | null | ||
bill_time | 记账时间 | date | - | null | ||
type_id | 账单类型 | bigint | 20 | null | ||
price | 金额 | double | 10 | null | 长度为10,小数2位 | |
explain | 说明 | varchar | 100 | null |
bill_type表索引
foreign key:外键约束,其中bill表中的type_id是bill_type中的id主键。
索引名 | 字段 | 索引类型 | 索引方法 | 注释 |
---|---|---|---|---|
id(默认字段) | id | PRIMARY | BTREE | 创建主键时自动生成,无需手动创建 |
fk_type_bill | type_id | KEY | BTREE | 普通索引 |
fk_type_bill | type_id、id | CONSTRAINT | BTREE | 外键约束,type_id对应bill_type中的id |
建表语句
CREATE TABLE `bill` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '账单编号',
`title` varchar(100) DEFAULT NULL COMMENT '账单标题',
`bill_time` date DEFAULT NULL COMMENT '记账时间',
`type_id` bigint(20) DEFAULT NULL COMMENT '账单类型',
`price` double(10,2) DEFAULT NULL COMMENT '金额',
`explain` varchar(100) DEFAULT NULL COMMENT '说明',
PRIMARY KEY (`id`),
KEY `fk_type_bill` (`type_id`),
CONSTRAINT `fk_type_bill` FOREIGN KEY (`type_id`) REFERENCES `bill_type` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1;
1.项目搭建
我的环境:Win10、IDEA、JDK11、MAVEN,有网络。
新建一个SpringBoot项目(启动类存在,若不存在得先搭建),命名为bill-manager,下面是pom.xml配置文件中的依赖:
<dependencies>
<!--web依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mybatis通用mapper,引入tk.mybatis就不需要再引入mybatis依赖,因为tk.mybatis包括mybatis了-->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>2.0.3</version>
</dependency>
<!--mysql依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--页面的模板引擎thymeleaf-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!-- 分页插件-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.3</version>
</dependency>
</dependencies>
之后在java包下建立entity、dao、service、controller、utils包,存放相关的类,若resouce包下没有自动生成static和templates包,手动创建(详细的查看项目源码)。
2.连接数据库
数据库yml配置,application.yml文件,放在resources包下
# mysql连接池 我的数据库没设密码
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
username: root
password:
url: jdbc:mysql://localhost:3306/bill-manager
# 关掉thymeleaf缓存
thymeleaf:
cache: false
# 整合mybatis
mybatis:
type-aliases-package: com.pdh.entity #别名搜索配置
mapper-locations: classpath:/mybatis/*.xml # 放置配置文件
之后就是执行sql脚本,bill-manager.sql文件在登陆数据库之后执行即可(会自动建库、建表、插入数据)。
3.创建实体类
实体类与数据库对应,在entity包下,还需生成getter/setter方法:
BillType类
package com.pdh.entity;
import javax.persistence.*;
import java.util.Date;
/**
* @Author: 彭_德华
* @Date: 2021-08-30 10:17
* @Description:
*/
@Table(name = "bill")
public class Bill {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
@Column(name = "title")
private String title;
@Column(name = "bill_time")
private Date billTime;
@Column(name = "type_id")
private Long typeId;
@Column(name = "price")
private Double price;
@Column(name = "explain")
private String explain;
/**
* @Transient 瞬时属性,与字段没有映射
* 类型名称:用于查询
*/
@Transient
private String typeName;
/**
* 开始时间:用于查询
*/
@Transient
private Date dateBegin;
/**
* 结束时间:用于查询
*/
@Transient
private Date dateEnd;
}
Bill类
package com.pdh.entity;
import javax.persistence.*;
/**
* @Author: 彭_德华
* @Date: 2021-08-30 9:59
* @Description:
*/
@Table(name = "bill_type")
public class BillType {
/**
* @Id 表示为主键字段
* @GeneratedValue(strategy = GenerationType.IDENTITY) 自增主键策略
* @Column(name = "id_") 与数据库中的字段匹配
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
@Column(name = "name")
private String name;
}
4.编写dao层
这里采用动态sql语句实现。dao层继承Maper类,减少个人代码开发。
4.1 BillMapper和TypeMapper接口
package com.pdh.dao;
import com.pdh.entity.Bill;
import tk.mybatis.mapper.common.Mapper;
import java.util.List;
/**
* @Author: 彭_德华
* @Date: 2021-08-30 10:36
* @Description:
*/
public interface BillMapper extends Mapper<Bill> {
public List<Bill> select(Bill bill);
}
package com.pdh.dao;
import com.pdh.entity.BillType;
import tk.mybatis.mapper.common.Mapper;
/**
* @Author: 彭_德华
* @Date: 2021-08-30 10:34
* @Description: 目前还没有加入方法,有需求可以加入
*/
public interface TypeMapper extends Mapper<BillType> {
}
4.2 BillMapper.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.pdh.dao.BillMapper">
<!--原生sql语句-->
<sql id="selectSql">
SELECT
b.id as id,
b.title as title,
b.bill_time as billTime,
b.type_id as typeId,
b.price as price,
b.explain as `explain`,/*explain是mysql关键字,使用的话必须的使用``括住*/
t.name as typeName
FROM
bill as b
left join
bill_type as t
on
b.type_id = t.id
</sql>
<!--插入where条件,实现动态sql-->
<select id="select" resultType="com.pdh.entity.Bill">
<include refid="selectSql"/>
<where>
/*判断 为true就执行,false就跳过*/
<if test="typeId !=null">
b.type_id = #{typeId}
</if>
<if test="title !=null">
and b.title like '%${title}%'
</if>
<if test="dateBegin !=null">
and b.bill_time >= #{dateBegin}
</if>
<if test="dateEnd !=null">
and b.bill_time <= #{dateEnd}
</if>
</where>
</select>
</mapper>
编写service层
5.编写service层
此层作为dao层和controller层的中间层,作用就是减少dao和controller层的耦合。有两个service类:
5.1 BillService类
package com.pdh.service;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.pdh.dao.BillMapper;
import com.pdh.entity.Bill;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @Author: 彭_德华
* @Date: 2021-08-30 11:02
* @Description:
*/
@Service
public class BillService {
@Autowired
private BillMapper billMapper;
/**
* 查询
* @param bill
* @return
*/
public List<Bill> list(Bill bill){
return billMapper.select(bill);
}
/**
* 添加Bill数据
* @param bill
* @return
*/
public int add(Bill bill){
//调用Mapper类的方法,内置的常用方法
return billMapper.insert(bill);
}
/**
* 根据主键id获取数
* @param id
* @return
*/
public Bill get(Long id) {
return billMapper.selectByPrimaryKey(id);
}
/**
* 更新单个数据
* @param bill
* @return
*/
public int update(Bill bill){
return billMapper.updateByPrimaryKey(bill);
}
/**
* 删除数据
* @param id
* @return
*/
public int delete(Long id){
return billMapper.deleteByPrimaryKey(id);
}
/**
* 分页操作
* @param bill
* @param pageNum
* @param pageSize
* @return
*/
public PageInfo<Bill> listPage(Bill bill,int pageNum,int pageSize){
/**
* 使用PageHelper进行后端分页,获取到指定的数据。比如
* 指定bill类中的某一数据
* 指定页数
* 指定某一页的数据量
*/
return PageHelper.startPage(pageNum,pageSize).doSelectPageInfo(() -> {
billMapper.select(bill);
});
}
}
5.2 TypeService类
package com.pdh.service;
import com.pdh.dao.TypeMapper;
import com.pdh.entity.BillType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @Author: 彭_德华
* @Date: 2021-08-30 11:09
* @Description:
*/
@Service
public class TypeService {
@Autowired
private TypeMapper typeMapper;
/**
* 获取BillType的列表
* @return
*/
public List<BillType> list(){
//直接调用tk.mybatis内置接口,实现查询操作
return typeMapper.selectAll();
}
}
6.编写controller类
在编写controller之前,先来了解一下下面的知识:
1.Model接口的作用:从控制层直接返回前端所需要的数据。spring自动为Model创建实例,并作为controller的入参,可以结合下面的写法理解运用。
2.重定向和转发:两者是页面跳转的两种方式。重定向会丢失request的信息,浏览器会打开一个新的地址(新的请求);转发不会丢失request信息,可在多个页面交互过程中实现数据共享。
BillController类
- @RestController等于@Controller+@ResponseBody,表示返回json数据,不能跳转页面,所以要跳转页面,就不能使用@RestController。
- 另外,@RequestMapping()匹配所有类型的请求,建议使用@GetMapping()或者@PostMapping()注解指定的请求,符合之后的Swagger接口测试要求。
package com.pdh.contoller;
import com.github.pagehelper.PageInfo;
import com.pdh.entity.Bill;
import com.pdh.entity.BillType;
import com.pdh.service.BillService;
import com.pdh.service.TypeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* @Author: 彭_德华
* @Date: 2021-08-30 13:38
* @Description: 后端处理差不多就是这样,跟多的是前端处理
*/
@Controller
@RequestMapping("/bill") //根注解
public class BillController {
@Autowired
private TypeService typeService;
@Autowired
private BillService billService;
/**
* 查询
* @param bill 接收页面请求的参数
* @param model 把查询的结果放到model中,此model会响应到前端数据
* @return
*/
@RequestMapping("/list")
public String list(Bill bill, Model model){
List<BillType> types = typeService.list();
//在数据展示的时候,需要直接获取到类型列表
model.addAttribute("types",types);
//获取账单列表
List<Bill> bills = billService.list(bill);
model.addAttribute("bills",bills);
/**
* 跳转到此路径进行数据显示,相当于发起此请求
* 遵循HTML5规范,不能写成
* return "/bill/list";
*/
return "bill/list";
}
/**
* 分页查询
* @param bill 接收页面请求的参数
* @param model 把查询的结果放到model中
* @return
*/
@RequestMapping("/list-page")
public String listPage(
/**
* RequestParam:匹配请求中的参数,传递的名称与之对应即可
* 接收前端发过来的name属性,且字符必须得一一对应
*/
@RequestParam(defaultValue = "1") int pageNum,
@RequestParam(defaultValue = "10") int pageSize,
Bill bill,
Model model){
List<BillType> types = typeService.list();
model.addAttribute("types",types);
PageInfo page = billService.listPage(bill,pageNum,pageSize);
model.addAttribute("page",page);
return "bill/list-page";
}
/**
* 跳转到添加页面
* @param model 获取对应的数据存储在model中,model拿到数据后进行响应
* @return
*/
@RequestMapping("/toAdd")
public String toAdd(Model model){
List<BillType> types = typeService.list();
model.addAttribute("types",types);
return "bill/add";
}
/**
* 添加
* 添加完就跳转到list页面
* @param bill
* @return
*/
@RequestMapping("/add")
public String add(Bill bill){
billService.add(bill);
//这里是重定向到此页面
return "redirect:/bill/list-page";
}
/**
* 删除
* 传入id
* @param id
* @return
*/
@RequestMapping("/delete/{id}") // delete/{id} 这种请求格式,用占位符传参
public String delete(@PathVariable("id") Long id){
billService.delete(id);
return "redirect:/bill/list-page";
}
/**
* 修改
* 跳转到修改页面
* @param id
* @param model
* @return
*/
@RequestMapping("/toUpdate/{id}")
public String toUpdate(
//@PathVariable注解匹配请求中传递的参数
@PathVariable("id") Long id
,Model model){
List<BillType> types = typeService.list();
model.addAttribute("types",types);
Bill bill = billService.get(id);
model.addAttribute("bill",bill);
return "bill/update";
}
/**
* 修改
* @param bill
* @return
*/
@RequestMapping("/update")
public String update(Bill bill){
billService.update(bill);
return "redirect:/bill/list-page";
}
}
7.日期处理器工具
DateConverterConfig(日期转换配置)
package com.pdh.utils;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* @Author: 彭_德华
* @Date: 2021-08-30 15:03
* @Description:
*/
@Component
public class DateConverterConfig implements Converter<String, Date> {
private static final List<String> formarts = new ArrayList<>(4);
static {
formarts.add("yyyy-MM");
formarts.add("yyyy-MM-dd");
formarts.add("yyyy-MM-dd hh:mm");
formarts.add("yyyy-MM-dd hh:mm:ss");
}
@Override
public Date convert(String source) {
String value = source.trim();
if ("".equals(value)) {
return null;
}
if (source.matches("^\\d{4}-\\d{1,2}$")) {
return parseDate(source, formarts.get(0));
} else if (source.matches("^\\d{4}-\\d{1,2}-\\d{1,2}$")) {
return parseDate(source, formarts.get(1));
} else if (source.matches("^\\d{4}-\\d{1,2}-\\d{1,2} {1}\\d{1,2}:\\d{1,2}$")) {
return parseDate(source, formarts.get(2));
} else if (source.matches("^\\d{4}-\\d{1,2}-\\d{1,2} {1}\\d{1,2}:\\d{1,2}:\\d{1,2}$")) {
return parseDate(source, formarts.get(3));
} else {
throw new IllegalArgumentException("Invalid boolean value '" + source + "'");
}
}
/**
* 格式化日期
*
* @param dateStr String 字符型日期
* @param format String 格式
* @return Date 日期
*/
public Date parseDate(String dateStr, String format) {
Date date = null;
try {
DateFormat dateFormat = new SimpleDateFormat(format);
date = dateFormat.parse(dateStr);
} catch (Exception e) {
}
return date;
}
}
8.前端页面处理
对前端页面的处理也没有多少说的,看源码就理解了。
增删改查处理出来即可,前端给到我们页面后,我们只需要加上对应的回显操作即可。这里使用到thymeleaf。先得导入static静态资源css和js包。
说一下这个目录的问题,静态的数据都放在resource包下的static包下,页面放在templates包下(controller会自动跳转),其它资源都得规范化
list.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>userList</title>
<link rel="stylesheet" th:href="@{/css/bootstrap.css}"></link>
<script type="text/javascript" th:src="@{/js/My97DatePicker/WdatePicker.js}"></script>
<script type="text/javascript" src="/js/My97DatePicker/lang/zh-cn.js"></script>
<script type="text/javascript" th:src="@{/js/jquery/jquery-1.10.2.min.js}"></script>
</head>
<body class="container">
<br/>
<h1>账单列表</h1>
<br/><br/>
<div class="with:80%">
<!-- TODO 回显查询数据 ,已解决-->
<form class="form-inline" id="qf" method="post">
<div class="form-group">
<label for="typeId" class="control-label">类型</label>
<select name="typeId" id="typeId" class="form-control">
<option value="">全部</option>
<!--显示下拉框-->
<option th:each="t:${types}" th:value="${t.id}" th:text="${t.name}" th:selected="${t.id} +'' == ${#strings.trim(param.typeId)}"></option>
</select>
</div>
<div class="form-group">
<label for="dateBegin" class="control-label" >开始时间</label>
<!--添加th:value="${param.dateBegin}" 支持数据回显-->
<input type="text" class="form-control" name="dateBegin" id="dateBegin" th:value="${param.dateBegin}" placeholder="开始时间" onclick="WdatePicker()"/>
</div>
<div class="form-group">
<label for="dateEnd" class="control-label">结束时间</label>
<input type="text" class="form-control" name="dateEnd" id="dateEnd" th:value="${param.dateEnd}" placeholder="结束时间" onclick="WdatePicker()"/>
</div>
<div class="form-group">
<input type="submit" value="查询" class="btn btn-info" />
<input type="reset" value="重置" class="btn btn-info" />
<a href="/bill/toAdd" th:href="@{/bill/toAdd}" class="btn btn-info">添加</a>
</div>
</form>
</div>
<br/>
<div class="with:80%">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>#</th>
<th>标题</th>
<th>时间</th>
<th>金额</th>
<th>类别</th>
<th>说明</th>
<th>操作</th>
</tr>
</thead>
<tbody >
<!-- TODO 回显查询结果 , 已解决-->
<!--使用thymeleaf显示数据-->
<tr th:each="b,status: ${bills}" th:object="${b}">
<td th:text="${b.id}">id</td>
<td th:text="*{title}">title</td>
<td th:text="${#dates.format(b.billTime,'yyyy-MM-dd')}">time</td>
<td th:text="${b.price}">price</td>
<td th:text="${b.typeName}">typeName</td>
<td th:text="${b.explain}">explain</td>
<td>
<a th:href="|/bill/delete/*{id}|">删除</a>
<a th:href="|/bill/toUpdate/*{id}|">修改</a>
<a th:href="|/bill/list-page|">进入分页页面</a>
</td>
</tr>
</tbody>
</table>
</div>
</body>
</html>
之后的页面就是参看一下源码。
9.分页操作
分为前端和后端分页处理,前后端同时实现分页。后端使用pagehelper分页插件,前端使用thymeleaf实现。
9.1 后端分页实现
引入pageHelper分页插件starter,注意:必须引入pagehelper的启动器,不能直接引入pagehelper。分页插件提供了目前分页会用到的许多数据,直接获取数据即可实现分页功能。
<!-- 分页插件-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.3</version>
</dependency>
之后可以编写一个测试类对分页效果进行测试,在测试类中,得到这些常用的数据进行展示。直接测试Service即可:
@Test
void listPage() {
Bill bill = new Bill();
PageInfo<Bill> page = PageHelper.startPage(2,10).doSelectPageInfo(() -> {
service.list(bill);
});
page.getList().forEach(b -> {
System.out.println(b.getId()+":"+b.getTitle());
});
System.out.println("总行数=" + page.getTotal());
System.out.println("当前页=" + page.getPageNum());
System.out.println("每页行数=" + page.getPageSize());
System.out.println("总页数=" + page.getPages());
System.out.println("起始行数=" + page.getStartRow());
System.out.println("是第一页=" + page.isIsFirstPage());
System.out.println("是最后页=" + page.isIsLastPage());
System.out.println("还有下一页=" + page.isHasNextPage());
System.out.println("还有上一页=" + page.isHasPreviousPage());
System.out.println("页码列表" + Arrays.toString(page.getNavigatepageNums()));
}
下面就直接开始分页的应用了。在service中加入分页方法listPage
/**
* 分页操作
* @param b
* @param pageNum
* @param pageSize
* @return
*/
public PageInfo<Bill> listPage(Bill b,int pageNum,int pageSize){
return PageHelper.startPage(pageNum,pageSize).doSelectPageInfo(() -> {
billMapper.select(b);
});
}
编写controller类中分页操作
前后端参数名必须一一对应,会自动接收数据
/**
* 分页查询
* @param bill 接收页面请求的参数
* @param model 把查询的结果放到model中
* @return
*/
@RequestMapping("/list-page")
public String listPage(
/**
* RequestParam:匹配请求中的参数,传递的名称与之对应即可
* 接收前端发过来的name属性,且字符必须得一一对应
*/
@RequestParam(defaultValue = "1") int pageNum,
@RequestParam(defaultValue = "10") int pageSize,
Bill bill,
Model model){
List<BillType> types = typeService.list();
model.addAttribute("types",types);
PageInfo page = billService.listPage(bill,pageNum,pageSize);
model.addAttribute("page",page);
return "bill/list-page";
}
下面就是在前端页面处理这个分页问题了。
9.2 前端分页
list-page.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>userList</title>
<link rel="stylesheet" th:href="@{/css/bootstrap.css}"></link>
<script type="text/javascript" th:src="@{/js/My97DatePicker/WdatePicker.js}"></script>
<script type="text/javascript" src="/js/My97DatePicker/lang/zh-cn.js"></script>
<script type="text/javascript" th:src="@{/js/jquery/jquery-1.10.2.min.js}"></script>
</head>
<body class="container">
<br/>
<h1>账单列表</h1>
<br/><br/>
<div class="with:80%">
<form class="form-inline" id="qf" th:action="@{/bill/list-page}" method="post">
<!--隐藏这两个数据-->
<input name="pageNum" id="pageNum" type="hidden" th:value="${page.pageNum}">
<input name="pageSize" id="pageSize" type="hidden" th:value="${page.pageSize}">
<!--分页相关参数-->
<div class="form-group">
<label for="typeId" class="control-label">类型</label>
<select name="typeId" id="typeId" class="form-control">
<option value="">全部</option>
<option th:each="t:${types}" th:value="${t.id}" th:text="${t.name}" th:selected="${t.id} +'' == ${#strings.trim(param.typeId)}"></option>
</select>
</div>
<div class="form-group">
<label for="dateBegin" class="control-label" >开始时间</label>
<input type="text" class="form-control" name="dateBegin" id="dateBegin" th:value="${param.dateBegin}" placeholder="开始时间" onclick="WdatePicker()"/>
</div>
<div class="form-group">
<label for="dateEnd" class="control-label">结束时间</label>
<input type="text" class="form-control" name="dateEnd" id="dateEnd" th:value="${param.dateEnd}" placeholder="结束时间" onclick="WdatePicker()"/>
</div>
<div class="form-group">
<input type="submit" value="查询" class="btn btn-info" />
<input type="reset" value="重置" class="btn btn-info" />
<a href="/bill/toAdd" th:href="@{/bill/toAdd}" class="btn btn-info">添加</a>
</div>
</form>
</div>
<br/>
<div class="with:80%">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>#</th>
<th>标题</th>
<th>时间</th>
<th>金额</th>
<th>类别</th>
<th>说明</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<!-- TODO 页面循环-->
<!--使用thymeleaf显示数据-->
<tr th:each="b, bstatus : ${page.list}" th:style="${bstatus.odd} ? 'background-color: #FFCE44'">
<th scope="row" th:text="${b.id}">id</th>
<td th:text="${b.title}">name</td>
<td th:text="${b.billTime} ? ${#dates.format(b.billTime, 'yyyy-MM-dd')}">time</td>
<td th:text="${b.price}">price</td>
<td th:text="${b.typeName}">typeName</td>
<td th:text="${b.explain}">explain</td>
<td>
<a th:href="|/bill/toUpdate/${b.id}|">修改</a>
<a th:href="|/bill/delete/*{b.id}|">删除</a>
<a th:href="|/bill/list|">进入未分页页面</a>
</td>
</tr>
</tbody>
</table>
</div>
<!-- TODO 分页工具类-->
<ul class="pagination">
<li><button class="btn btn-default" id="first">第一页</button></li>
<li><button class="btn btn-default" id="prev">上一页</button></li>
<li th:each="p:${page.navigatepageNums}"> <!--navigatepageNums:导航页-->
<!--显示页码, name很重要-->
<button class="btn btn-default" id="bottomPageNum" name="pn" th:text="${p}" th:disabled="(${p} == ${page.pageNum})"></button>
</li>
<li><button class="btn btn-default" id="next">下一页</button></li>
<li><button class="btn btn-default" id="last">最后页</button></li>
</ul>
<!-- TODO 分页的js代码-->
<script>
$(function (){
//通过内联js得到分页相关数据
var pageNum = [[${page.pageNum}]]; //当前页
var pageCount = [[${page.pages}]]; //总页数
var hasNextPage = [[${page.hasNextPage}]]; // true 有下一页
var hasPreviousPage = [[${page.hasPreviousPage}]]; // true 有上一页
//按钮事件
// 下一页
$("#next").click(function (){
$("#pageNum").val(pageNum + 1);
$("#qf").submit();
});
// 上一页
$("#prev").click(function (){
$("#pageNum").val(pageNum - 1);
$("#qf").submit();
});
// 第一页
$("#first").click(function (){
$("#pageNum").val(1);
$("#qf").submit();
});
// 最后一页
$("#last").click(function (){
$("#pageNum").val(pageCount);
$("#qf").submit();
});
//点击页码跳转
$("button[name='pn']").click(function (){
$("#pageNum").val($(this).html());
$("#qf").submit();
});
//控制按钮状态
if(!hasNextPage){
$("#last").prop("disabled",true);
$("#next").prop("disabled",true);
}
if(!hasPreviousPage){
$("#first").prop("disabled",true);
$("#prev").prop("disabled",true);
}
})
</script>
</body>
</html>
之后修改更新、添加数据后跳转到此请求即可进行显示。
10.结语
springboot,mybatis和tk mybatis(持久化),thymeleaf(前端页面展示)知识点非常的多,但是在刚刚开始学习的时候,无从下手最让人头疼,如果还有实现分页需求,作为一个后端方向的学生,实在是不好学习这些知识点啊。这里就用一个极其简单的项目,入门了解。