废弃说明:
这个专栏的文章本意是记录笔者第一次搭建博客的过程,文章里里有很多地方的写法都不太恰当,现在已经废弃,关于SpringBoot + Vue 博客系列,笔者重新写了这个系列的文章,不敢说写的好,但是至少思路更加清晰,还在看SpringBoot + Vue 博客系列文章的朋友可以移步:https://blog.csdn.net/li3455277925/category_10341110.html,文章中有错误的地方或者大家有什么意见或建议都可以评论或者私信交流。
需求
在博客的“我的博文”组件中,我们需要将博客信息以列表形式显示,并且下方有分页功能。
后端分页的实现
- 创建
com.qianyucc.blog.model.vo.SimpleArticleVO
类,封装返回页面的文章信息
package com.qianyucc.blog.model.vo;
import lombok.*;
/**
* @author lijing
* @date 2019-10-12 14:29
* @description 简化版的文章信息
*/
@Data
public class SimpleArticleVO {
private Long id;
private String author;
private String title;
private String[] tags;
private Integer type;
private String category;
private String gmtUpdate;
private String tabloid;
private Integer comments;
private Integer views;
}
- 新建
com.qianyucc.blog.model.vo.PageInfoVO
类,用于封装分页信息
package com.qianyucc.blog.model.vo;
import lombok.*;
import java.util.*;
/**
* @author lijing
* @date 2019-10-12 14:34
* @description 储存分页信息
*/
@Data
public class PageInfoVO<T> {
private List<T> data;
private Integer totalPages;
private Integer currentPage;
private Long totalCount;
}
- 新建
com.qianyucc.blog.service.ArticleService
类,在里面编写业务层代码
package com.qianyucc.blog.service;
import com.qianyucc.blog.model.entity.*;
import com.qianyucc.blog.model.vo.*;
import com.qianyucc.blog.repository.*;
import com.qianyucc.blog.utils.*;
import org.springframework.beans.factory.annotation.*;
import org.springframework.data.domain.*;
import java.util.*;
/**
* @author lijing
* @date 2019-10-12 14:33
* @description 与文章相关的业务
*/
@Service
public class ArticleService {
private final int PAGE_SIZE = 5;
private final Sort GMT_UPDATE_DESC_SORT = new Sort(Sort.Direction.DESC, "gmtUpdate");
@Autowired
private ArticleRepository articleRepository;
/**
* 无条件分页查询文章信息
*
* @param pageNumber
* @return
*/
public PageInfoVO<SimpleArticleVO> pageArticles(Integer pageNumber) {
// 增加容错
pageNumber = pageNumber < 1 ? 0 : pageNumber - 1;
Pageable pageable = new PageRequest(pageNumber, PAGE_SIZE, GMT_UPDATE_DESC_SORT);
Page<ArticleDO> page = articleRepository.findAll(pageable);
PageInfoVO<SimpleArticleVO> pageInfoVO = new PageInfoVO<>();
pageInfoVO.setTotalCount(page.getTotalElements());
pageInfoVO.setTotalPages(page.getTotalPages());
pageInfoVO.setCurrentPage(pageNumber + 1);
ArrayList<SimpleArticleVO> simpleArticleVOS = ArticleUtil.jpaDosToSimpleArticleVOs(page.getContent());
pageInfoVO.setData(simpleArticleVOS);
return pageInfoVO;
}
}
上面用到两个自定义工具类:
BlogUtil
和ArticleUtil
分别封装了全局工具方法和与文章相关的工具方法
package com.qianyucc.blog.utils;
import cn.hutool.core.bean.*;
import com.qianyucc.blog.model.entity.*;
import com.qianyucc.blog.model.vo.*;
import java.util.*;
/**
* @author lijing
* @date 2019-10-12 14:37
* @description 与文章相关的工具类
*/
public class ArticleUtil {
private static final String DATE_PATTERN = "yyyy-MM-dd HH:mm";
/**
* 将一个ArticleDO对象列表,转化为SimpleArticleVO对象列表
*
* @param items Spring Data Jpa查询出来的集合
* @return
*/
public static ArrayList<SimpleArticleVO> jpaDosToSimpleArticleVOs(List<ArticleDO> items) {
ArrayList<SimpleArticleVO> res = new ArrayList<>();
items.forEach(article -> res.add(doToSimpleArticleVO(article)));
return res;
}
/**
* 将一个ArticleDO对象转换为SimpleArticleVO对象
*
* @param articleDO
* @return
*/
public static SimpleArticleVO doToSimpleArticleVO(ArticleDO articleDO) {
SimpleArticleVO simpleArticleVO = new SimpleArticleVO();
BeanUtil.copyProperties(articleDO, simpleArticleVO);
simpleArticleVO.setTags(articleDO.getTags().split(","));
simpleArticleVO.setGmtUpdate(BlogUtil.formatDate(articleDO.getGmtUpdate(), DATE_PATTERN));
return simpleArticleVO;
}
}
package com.qianyucc.blog.utils;
import java.time.*;
import java.time.format.*;
/**
* @author lijing
* @date 2019-10-12 14:40
* @description 整个项目的全局工具类
*/
public class BlogUtil {
/**
* java8格式化日期
*
* @param millis
* @param pattern
* @return
*/
public static String formatDate(Long millis, String pattern) {
Instant instant = Instant.ofEpochMilli(millis);
LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
String format = localDateTime.format(DateTimeFormatter.ofPattern(pattern));
return format;
}
}
- 编写ArticleController
package com.qianyucc.blog.controller.comm;
import com.qianyucc.blog.model.vo.*;
import com.qianyucc.blog.service.*;
import org.springframework.beans.factory.annotation.*;
import org.springframework.web.bind.annotation.*;
/**
* @author lijing
* @date 2019-10-12 14:49
* @description 与文章相关的api
*/
@RestController("commArticleController")
@RequestMapping("/api/comm/article")
public class ArticleController {
@Autowired
private ArticleService articleService;
@GetMapping("/getPageArticles")
public PageInfoVO<SimpleArticleVO> getPageArticles(@RequestParam(value = "pageNumber", defaultValue = "1") Integer pageNumber) {
PageInfoVO<SimpleArticleVO> pageInfoVO = articleService.pageArticles(pageNumber);
return pageInfoVO;
}
}
- 启动项目,提前在数据库中插入测试数据,使用postman进行测试:
响应信息如下:
前端展示信息
- 由于文章列表数据虽然展示在
articles
组件中,但是随后我们在搜索功能中通过导航栏上的搜索框也可以改变文章列表,所以这里又要使用到vuex
- 新建
/src/store/modules/articles.js
,存放文章列表的相关信息,如当前页码,总页数,所有文章数据等
const articles = {
state: {
data: null,
totalPages: 0,
currentPage: 1,
totalCount: 0,
queryString: null,
tag: null,
category: null,
condition: "all" //增加条件,all表示没有查询条件,tag表示根据标签查询得到的结果,queryString表示根据关键字查询得到的,category表示根据分类
},
mutations: {
setPageInfo(state, pageInfo) {
state.data = pageInfo.data;
state.totalPages = pageInfo.totalPages;
state.totalCount = pageInfo.totalCount;
state.currentPage = pageInfo.currentPage;
},
setQueryString(state, queryString) {
state.queryString = queryString;
},
setCurrentPage(state, pageNumber) {
state.currentPage = pageNumber;
},
// 这里需要注意的是,mutations传递的参数只能是一个,传递多个值的时候,只有第一个参数有值,其他参数全是null
setCondition(state, params) {
let condition = params.condition;
let info = params.info;
state.condition = condition;
switch (condition) {
case 'all':
state.tag = null;
state.category = null;
state.queryString = null;
break;
case 'tag':
state.tag = info;
break;
case 'category':
state.category = info;
break;
case 'queryString':
state.queryString = info;
break;
}
}
}
}
export default articles;
需要注意的是,这里的文章列表信息有四个条件
all
,tag
,aueryString
,category
分别代表无条件查询、根据标签查询,根据带有关键字的字符串查询、根据分类查询
-
然后在
/src/store/index.js
中导入
-
在
/src/request/api/url.js
中添加url
const baseUrl = "http://localhost:8886/";
const getUserInfoUrl = baseUrl + 'api/comm/user/getUserInfo';
const callbackUrl = baseUrl + 'api/comm/gitee/callback';
const getPageArticlesUrl = baseUrl + 'api/comm/article/getPageArticles';
export default {
getUserInfoUrl,
callbackUrl,
getPageArticlesUrl
};
- 在
/src/request/api/article.js
里面封装发送请求的方法
// 导入axios实例
import axios from "@/request/http"
// 导入所有url
import url from '@/request/api/url'
// 导入store(vuex)
import store from '@/store/index'
export default {
getArticles(callback) {
axios
.get(url.getArticlesUrl)
.then(callback)
.catch(err => {
console.log('getArticles Error');
});
},
getPageArticles(pageNumber, callback) {
axios
.get(url.getPageArticlesUrl, {
params: {
pageNumber: pageNumber
}
})
.then(callback)
.catch(err => {
console.log("getPageArticles Error");
});
},
getArticles(callback) {
let pageInfo = store.state.articles;
if (pageInfo.condition) {
switch (pageInfo.condition) {
case 'all':
this.getPageArticles(pageInfo.currentPage, callback);
break;
case 'tag':
// 根据tag查询
break;
case 'category':
// 根据分类查询
break;
case 'queryString':
// 根据带有关键字的字符串查询
break;
}
}
}
}
- 在
arricles.vue
中实现获取文章列表的逻辑代码
<script>
import { mapGetters, mapState, mapMutations } from "vuex";
import router from "@/router/index";
export default {
name: "articles",
data() {
return {};
},
methods: {
linkGen(pageNum) {
return pageNum === 1 ? "?" : `?page=${pageNum}`;
}
},
computed: {
...mapState({
articles: state => state.articles.data,
totalPages: state => state.articles.totalPages,
currentPage: state => state.articles.currentPage
})
},
watch: {
// 监听路由变化,用于查看文章之后点击浏览器回退键之后还会返回当前页面
$route(to, from) {
this.setCurrentPage(to.query.page || 1);
this.$api.article.getArticles(resp => {
this.setPageInfo(resp.data);
});
}
},
methods: {
...mapMutations(["setPageInfo", "setCurrentPage"]),
linkGen(pageNum) {
return `?page=${pageNum}`;
// return pageNum === 1 ? "?" : `?page=${pageNum}`;
}
},
created() {
let page = this.$route.query.page;
// 当用户点击刷新之后,仍能停留在当前页面
this.setCurrentPage(page || 1);
this.$api.article.getArticles(resp => {
this.setPageInfo(resp.data);
});
}
};
</script>
- 在
arricles.vue
中将数据渲染到页面
<template>
<b-card border-variant="light" header="我的博文" align="left">
<!-- fluid属性可以使container宽度最大化 -->
<b-container fluid>
<b-row>
<b-col cols="12" sm="12" md="12" lg="12" xl="12">
<b-card class="card" v-for="article in articles" :key="article.id">
<router-link to="/">
<b-card-title>{{article.title}}</b-card-title>
</router-link>
<b-card-text class="small text-muted">
<b-badge variant="info">{{article.type==1 ? '原创' : '转载'}}</b-badge>
作者:{{article.author}} 分类:{{article.category}}
</b-card-text>
<b-card-text>{{article.tabloid}}</b-card-text>
<b-card-text class="small text-muted">
<i class="icon iconfont icon-riqi"></i>{{article.gmtUpdate}}
<i class="icon iconfont icon-yuedu"></i>{{article.views}}
<i class="icon iconfont icon-iconfontpinglun"></i>{{article.comments}}
<div class="tag-box">
<b-link class="tag" variant="info" v-for="(tag,index) in article.tags" :key="index">
<i class="icon iconfont icon-tag"></i>{{tag}}
</b-link>
</div>
</b-card-text>
</b-card>
</b-col>
<b-col cols="12" sm="12" md="12" lg="12" xl="12">
<!-- 分页 -->
<div class="overflow-auto pagination">
<b-pagination-nav :link-gen="linkGen" :number-of-pages="totalPages" use-router :value="currentPage"></b-pagination-nav>
</div>
</b-col>
</b-row>
</b-container>
</b-card>
</template>
- 效果如下图: