1. 问题列表功能实现
1.1 index.html代码修改
<div class="container-fluid main">
<div class="row">
<div class="col-lg-9 col-md-12 col-sm-12 col-xs-12">
<h2><span class="glyphicon glyphicon-list" aria-hidden="true"></span> 发现</h2>
<hr>
<div class="media" th:each="question : ${questions}">
<div class="media-left">
<a href="">
<img th:src="${question.user.getAvatarUrl()}" class="media-object img-rounded">
</a>
</div>
<div class="media-body">
<h4 class="media-heading" th:text="${question.title}"></h4>
<span th:text="${question.description}"></span>
<span class="text-desc">[[${question.commentCount}]] 个回复 • [[${question.viewCount}]] 次浏览 • [[${#dates.format(question.gmtCreate,'dd MM yyyy')}]]</span>
</div>
</div>
</div>
<div class="col-lg-3 col-md-12 col-sm-12 col-xs-12">
<h3>热门话题</h3>
</div>
</div>
</div>
1.2 自定义图片样式
body{
background-color: #efefef;
}
.main{
margin: 30px;
background-color: white;
}
.btn-publish{
float: right;
margin-bottom: 15px;
}
.media-object{
width: 38px;
height: 38px;
}
.text-desc{
font-size:12px;
font-weight:normal;
color:#999;
}
不要忘记引用样式
<link rel="stylesheet" href="css/community.css"/>
添加跳到首页的连接
1.3 创建QuestionDTO对象
对应数据库对象Question
唯一的区别是多个User对象属性,它是属于传输层的
1.4 indexController编写
package life.guohui.community.controller;
@Controller
public class IndexController {
@Autowired
private UserMapper userMapper;
@Autowired
private QuestionService questionService;
@GetMapping("/")
public String index(HttpServletRequest request,
Model model){
Cookie[] cookies = request.getCookies();
if(cookies != null && cookies.length != 0){
for(Cookie cookie: cookies){
if(cookie.getName().equals("token")){
String token = cookie.getValue();
User user = userMapper.findByToken(token);
if(user != null){
request.getSession().setAttribute("user",user);
}
break;
}
}
}
List<QuestionDTO> questionList = questionService.list();
model.addAttribute("questions",questionList);
return "index";
}
}
由于返回的是QuestionDTO
对象,需要service层解决通过creator
字段查找用户对象中的头像图片
1.5 创建QuestionService
1.6 Mybatis需要开启驼峰命名规则
# 开启驼峰命名规则
mybatis.configuration.map-underscore-to-camel-case=true
1.7 页面效果
2. 分页实现
2.1 数据库添加数据
2.2 indexController
增加参数,page:当前页码,size:每页显示数
package life.guohui.community.controller;
@Controller
public class IndexController {
@Autowired
private UserMapper userMapper;
@Autowired
private QuestionService questionService;
@GetMapping("/")
public String index(HttpServletRequest request,
Model model,
@RequestParam(name = "page",defaultValue = "1")Integer page,
@RequestParam(name = "size",defaultValue = "2")Integer size){
Cookie[] cookies = request.getCookies();
if(cookies != null && cookies.length != 0){
for(Cookie cookie: cookies){
if(cookie.getName().equals("token")){
String token = cookie.getValue();
User user = userMapper.findByToken(token);
if(user != null){
request.getSession().setAttribute("user",user);
}
break;
}
}
}
PaginationDTO pagination = questionService.list(page,size);
model.addAttribute("pagination",pagination);
return "index";
}
}
2.3 创建分页PaginationDTO
不用插件,自己实现分页。
-
pages
:要显示的所有页码。 -
questions
:每页的所有数据放到List集合中。 -
page
:当前页码。 -
totalPage
:总页数
package life.guohui.community.dto;
@Data
public class PaginationDTO {
private List<QuestionDTO> questions;
private boolean showPrevious;
private boolean showFirstPage;
private boolean showNext;
private boolean showEndPage;
private Integer page;
private Integer totalPage;
private List<Integer> pages = new ArrayList<>();
public void setPagination(Integer totalCount, Integer page, Integer size) {
if(totalCount % size == 0){
totalPage = totalCount/size;
}else{
totalPage = totalCount/size + 1;
}
if(page<1){
page = 1;
}
if(page>totalPage){
page = totalPage;
}
this.page = page;
pages.add(page);
for(int i = 1;i <=3; i++){
if(page-i>0){
pages.add(0,page-i);
}
if(page+i <= totalPage){
pages.add(page+i);
}
}
//是否展示上一页
if(page == 1){
showPrevious = false;
}else {
showPrevious = true;
}
//是否展示下一页
if(page == totalPage){
showNext = false;
}else{
showNext = true;
}
//是否展示第一页
if(pages.contains(1)){
showFirstPage = false;
}else{
showFirstPage = true;
}
//是否展示最后一页
if(pages.contains(totalPage)){
showEndPage = false;
}else{
showEndPage = true;
}
}
}
2.4 Service层返回分页数据
2.5 查总数mapper
package life.guohui.community.mapper;
@Mapper
public interface QuestionMapper {
......
@Select("select count(1) from question")
Integer count();
}
2.6 分页mapper
package life.guohui.community.mapper;
@Mapper
public interface QuestionMapper {
......
@Select("select * from question limit #{offset},#{size}")
List<Question> list(@Param(value = "offset") Integer offset,@Param(value = "size") Integer size);
......
}
2.7 引入分页HTML
当前页码高亮
<nav aria-label="Page navigation">
<ul class="pagination">
<li>
<a href="/?page=1" aria-label="Previous" th:if="${pagination.showPrevious}">
<span aria-hidden="true"><<</span>
</a>
</li>
<li>
<a th:href="@{/(page=${pagination.page - 1})}" aria-label="Previous"
th:if="${pagination.showFirstPage}">
<span aria-hidden="true"><</span>
</a>
</li>
<li th:each="page : ${pagination.pages}" th:class="${pagination.page == page}?'active':''">
<a th:href="@{/(page=${page})}" th:text="${page}">
<span aria-hidden="true"><</span>
</a>
</li>
<li>
<a th:href="@{/(page=${pagination.page + 1})}" aria-label="Previous"
th:if="${pagination.showNext}">
<span aria-hidden="true">></span>
</a>
</li>
<li>
<a th:href="@{/(page=${pagination.totalPage})}" aria-label="Previous"
th:if="${pagination.showEndPage}">
<span aria-hidden="true">>></span>
</a>
</li>
</ul>
</nav>
3. 我的问题列表功能实现
3.1 js效果
加入js文件
页面中引入文件
<script src="js/jquery-3.4.1.min.js"></script>
3.2 提取公共页面
新建HTML文件
<div th:fragment="nav"><!--给模块命名-->
在index.html和publish.html引入
<div th:insert="~{navigation :: nav}"></div>
模块代码
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<div th:fragment="nav">
<nav class="navbar navbar-default">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse"
data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only">Code社区</span>
</button>
<a class="navbar-brand" href="/">Code社区</a>
</div>
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<form class="navbar-form navbar-left">
<div class="form-group">
<input type="text" class="form-control" placeholder="搜索问题">
</div>
<button type="submit" class="btn btn-default">搜索</button>
</form>
<ul class="nav navbar-nav navbar-right">
<li th:if="${session.user != null}">
<a href="/publish">发布</a>
</li>
<li class="dropdown" th:if="${session.user != null}">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
aria-expanded="false">
<span th:text="${session.user.getName()}"></span>
<span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="/profile/question">我的问题</a></li>
<li><a href="#">个人资料</a></li>
<li><a href="#">退出登陆</a></li>
</ul>
</li>
<li th:unless="${session.user != null}">
<a href="https://github.com/login/oauth/authorize?client_id=Iv1.bf5154208e60707f&redirect_uri=http://localhost:8887/callback&scope=user&state=1">登陆</a>
</li>
</ul>
</div>
</div>
</nav>
</div>
3.3 创建profile.html
采用这个样式
<nav aria-label="Page navigation">
<ul class="pagination">
<li>
<a href="#" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
<li><a href="#">1</a></li>
<li><a href="#">2</a></li>
<li><a href="#">3</a></li>
<li><a href="#">4</a></li>
<li><a href="#">5</a></li>
<li>
<a href="#" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
</ul>
</nav>
3.4 ProfileController
a. 选中模块高亮通过参数传递@GetMapping("/profile/{action}")
b. 通过cookie判断User,分页
package life.guohui.community.controller;
@Controller
public class ProfileController {
@Autowired
private UserMapper userMapper;
@Autowired
private QuestionService questionService;
@GetMapping("/profile/{action}")
public String profile(@PathVariable(name = "action") String action,
Model model,
HttpServletRequest request,
@RequestParam(name = "page",defaultValue = "1")Integer page,
@RequestParam(name = "size",defaultValue = "5")Integer size){
User user = null;
Cookie[] cookies = request.getCookies();
if(cookies != null && cookies.length != 0){
for(Cookie cookie: cookies){
if(cookie.getName().equals("token")){
String token = cookie.getValue();
user = userMapper.findByToken(token);
if(user != null){
request.getSession().setAttribute("user",user);
}
break;
}
}
}
if(user == null){
return "redirect:/";
}
if("questions".equals(action)){
model.addAttribute("section","questions");
model.addAttribute("sectionName","我的提问");
}else if("replies".equals(action)){
model.addAttribute("section","replies");
model.addAttribute("sectionName","最新回复");
}
PaginationDTO paginationDTO = questionService.listByUserId(user.getId(), page, size);
model.addAttribute("pagination",paginationDTO);
return "profile";
}
}
3.5 自定义样式
3.6 QuestionService
创建两个方法listByUserId
,countByUserId
package life.guohui.community.mapper;
@Mapper
public interface QuestionMapper {
......
@Select("select * from question where creator = #{userId} limit #{offset},#{size}")
List<Question> listByUserId(@Param("userId") Integer userId, @Param(value = "offset") Integer offset, @Param(value = "size") Integer size);
@Select("select count(1) from question where creator = #{userId}")
Integer countByUserId(@Param("userId") Integer userId);
}
a 拿到总记录数totalCount
通过计算拿到总页数totalPage
if(totalCount % size == 0){
totalPage = totalCount/size;
}else{
totalPage = totalCount/size + 1;
}
b. 页码不能出现负数或超过总页数
if(page<1){
page = 1;
}
if(page>totalPage){
page = totalPage;
}
c. 修改PaginationDTO类
修改setPagination方法,通过总页数totalPage
和当前页page
算出显示页码pages
package life.guohui.community.dto;
@Data
public class PaginationDTO {
private List<QuestionDTO> questions;
private boolean showPrevious;
private boolean showFirstPage;
private boolean showNext;
private boolean showEndPage;
private Integer page;
private Integer totalPage;
private List<Integer> pages = new ArrayList<>();
public void setPagination(Integer totalPage, Integer page) {
this.totalPage = totalPage;
this.page = page;
pages.add(page);
for(int i = 1;i <=3; i++){
if(page-i>0){
pages.add(0,page-i);
}
if(page+i <= totalPage){
pages.add(page+i);
}
}
//是否展示上一页
if(page == 1){
showPrevious = false;
}else {
showPrevious = true;
}
//是否展示下一页
if(page == totalPage){
showNext = false;
}else{
showNext = true;
}
//是否展示第一页
if(pages.contains(1)){
showFirstPage = false;
}else{
showFirstPage = true;
}
//是否展示最后一页
if(pages.contains(totalPage)){
showEndPage = false;
}else{
showEndPage = true;
}
}
}
d. 通过size和page计算offset
Integer offset = size * (page - 1);
e. 所有代码
package life.guohui.community.service;
@Service
public class QuestionService {
@Autowired
private QuestionMapper questionMapper;
@Autowired
private UserMapper userMapper;
public PaginationDTO listByUserId(Integer userId, Integer page, Integer size) {
PaginationDTO paginationDTO = new PaginationDTO();
Integer totalPage;
//拿到总数
Integer totalCount = questionMapper.countByUserId(userId);
if(totalCount % size == 0){totalPage = totalCount/size;}else{totalPage = totalCount/size + 1;}
if(page<1){ page = 1; }
if(page>totalPage){ page = totalPage; }
paginationDTO.setPagination(totalPage,page);
Integer offset = size * (page - 1);
List<Question> questions = questionMapper.listByUserId(userId,offset,size);
List<QuestionDTO> questionDTOList = new ArrayList<>();
for(Question question : questions){
User user = userMapper.findById(question.getCreator());
QuestionDTO questionDTO = new QuestionDTO();
BeanUtils.copyProperties(question,questionDTO);
questionDTO.setUser(user);
questionDTOList.add(questionDTO);
}
paginationDTO.setQuestions(questionDTOList);
return paginationDTO;
}
}
f. 由于PaginationDTO修改,所以list方法也要修改
3.7 页面部分高亮显示
a. 地址正确显示
b. 链接的写法
<a th:href="@{'/profile/'+${section}(page=${1})}
对应@GetMapping("/profile/{action}")
c. 我的问题,最新回复高亮改变样式
<a href="/profile/questions"
th:class="${section == 'questions'}? 'active list-group-item' : 'list-group-item'">我的问题</a>
<a href="/profile/replies"
th:class="${section == 'replies'}? 'active list-group-item' : 'list-group-item'">
最新回复
</a>
d. profile.html页面
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
......
<body>
<div th:insert="~{navigation :: nav}"></div>
<div class="container-fluid main profile">
<div class="row">
<div class="col-lg-9 col-md-12 col-sm-12 col-xs-12">
<h2><span th:text="${sectionName}"></span></h2>
<hr>
<div class="media" th:each="question : ${pagination.questions}">
<div class="media-left">
<a href="">
<img th:src="${question.user.getAvatarUrl()}" class="media-object img-rounded">
</a>
</div>
<div class="media-body">
<h4 class="media-heading" th:text="${question.title}"></h4>
<span class="text-desc">[[${question.commentCount}]] 个回复 • [[${question.viewCount}]] 次浏览 • [[${#dates.format(question.gmtCreate,'yyyy-MM-dd HH:mm')}]]</span>
</div>
</div>
<nav aria-label="Page navigation">
<ul class="pagination">
<li th:if="${pagination.showFirstPage}">
<a th:href="@{'/profile/'+${section}(page=${1})}" aria-label="Previous"
>
<span aria-hidden="true"><<</span>
</a>
</li>
<li>
<a th:href="@{'/profile/'+${section}(page=${pagination.page - 1})}" aria-label="Previous" th:if="${pagination.showPrevious}">
<span aria-hidden="true"><</span>
</a>
</li>
<li th:each="page : ${pagination.pages}" th:class="${pagination.page == page}?'active':''">
<a th:href="@{'/profile/'+${section}(page=${page})}" th:text="${page}">
<span aria-hidden="true"><</span>
</a>
</li>
<li>
<a th:href="@{'/profile/'+${section}(page=${pagination.page + 1})}" aria-label="Previous"
th:if="${pagination.showNext}">
<span aria-hidden="true">></span>
</a>
</li>
<li>
<a th:href="@{'/profile/'+${section}(page=${pagination.totalPage})}" aria-label="Previous"
th:if="${pagination.showEndPage}">
<span aria-hidden="true">>></span>
</a>
</li>
</ul>
</nav>
</div>
<div class="col-lg-3 col-md-12 col-sm-12 col-xs-12">
<div class="list-group section">
<a href="/profile/questions"
th:class="${section == 'questions'}? 'active list-group-item' : 'list-group-item'">我的问题</a>
<a href="/profile/replies"
th:class="${section == 'replies'}? 'active list-group-item' : 'list-group-item'">
最新回复
<span class="badge" th:text="${session.unreadCount}"></span>
</a>
</div>
</div>
</div>
</div>
</body>
</html>
网友评论