vuepress 搭建了一个自己的技术博客,记录自己平时工作和学习中的一些经验总结,但是因为 vuepress 本身是为了方便我们快速搭建技术文档的,直接用来做博客总觉得少了点啥东西,怎么看都像一个文档网站。
本来是打算自己开发一个博客类的 vuepress 主题的,但一直也没想好怎么去做,前几天又突然想要给博客先生成一个文章列表。vuepress 默认的文章详情里是有一个 lastUpdatedTime 最新更新时间的,于是顺藤摸瓜先去找出 vuepress 里的这个 lastUpdatedTime 是咋获取到的。
在 node_modules\@vuepress\plugin-git\lib\node\utils\getUpdatedTime.js 文件里找到了具体的实现方式,其实就是获取的 git 提交日志里的时间,代码如下:
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getUpdatedTime = void 0;
const execa = require("execa");
/**
* Get unix timestamp in milliseconds of the last commit
*/
const getUpdatedTime = async (filePath, cwd) => {
const { stdout } = await execa('git', ['--no-pager', 'log', '-1', '--format=%at', filePath], {
cwd,
});
return Number.parseInt(stdout, 10) * 1000;
};
exports.getUpdatedTime = getUpdatedTime;
但是生成文章列表我们肯定是想按照创建文章的时间倒序生成,其实在 getUpdatedTime.js 同级目录里还有一个 node_modules\@vuepress\plugin-git\lib\node\utils\getCreatedTime.js,用这个方法我们就能拿到 markdown 文件的 git 创建时间,然后直接根据这个时间来生成文章列表就可以了。
具体实现步骤:
在 .vuepress/components 组件目录下新建一个文章列表 article-list.vue 组件,因为想要做成一个分页列表,这里在自己封装了一个 pagination 分页组件,直接引用第三方组件库里的分页组件也一样。
注意这个列表组件里相当于只是一个模板组件,后面想要生成文章数据的时候,只用通过正则去替换掉 init 方法里 articleList 的赋值,默认是个空数组。
article-list 文章列表组件
<template>
<div>
<div class="article-list">
<a
v-for="(item, index) in pageList"
:key="index"
class="article-item"
:href="item.link"
>
<p class="title">{{ item.title }}</p>
<p class="time">{{ item.createTime }}</p>
</a>
</div>
<!-- 分页 -->
<pagination
:page-no="page.index"
:page-size="page.size"
:total="page.total"
:continues="3"
@change-page-no="getPageNo"
@change-page-size="getPageSize"
/>
</div>
</template>
<script>
import pagination from './pagination.vue'
export default {
components: {
pagination,
},
name: 'article-list',
data() {
return {
articleList: [],
pageList: [],
page: {
index: 1,
size: 10,
total: 0,
},
}
},
created() {
this.init()
},
methods: {
init() {
this.articleList=[]
this.page.total = this.articleList.length
this.getList()
},
getPageNo(i) {
this.page.index = i
this.getList()
},
getPageSize(size) {
this.page.size = size
this.page.index = 1
this.getList()
},
getList() {
const { index, size } = this.page
this.pageList = this.articleList.slice((index - 1) * size, index * size)
}
}
}
</script>
<style lang='scss' scoped>
.article-list {
.article-item {
display: flex;
justify-content: space-between;
align-items: center;
&:not(:last-child) {
border-bottom: 1px dashed var(--c-border);
}
.title {
flex: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: var(--c-text);
padding-left: 16px;
position: relative;
&:before {
content: "";
position: absolute;
top: 50%;
left: 2px;
width: 6px;
height: 6px;
transform: rotate(45deg);
background: var(--c-brand);
margin-top: -3px;
}
}
.time {
margin-left: 20px;
font-size: 14px;
font-weight: normal;
color: var(--c-text-lightest);
}
}
}
</style>
pagination 分页组件
<template>
<div class="pagination">
<button
:disabled="pageNo === 1"
@click="$emit('getPageNo', pageNo - 1)"
>
上一页
</button>
<button
v-if="startAndEndIndex.start > 1"
@click="$emit('change-page-no', 1)"
:class="{ active: pageNo === 1 }"
>
1
</button>
<button v-if="startAndEndIndex.start > 2">···</button>
<!-- 连续的页码 -->
<template v-for="(page, index) in startAndEndIndex.end">
<button
v-if="page >= startAndEndIndex.start"
:key="index"
:class="{ active: pageNo === page }"
@click="$emit('change-page-no', page)"
>
{{ page }}
</button>
</template>
<button v-if="startAndEndIndex.end < totalPage - 1">···</button>
<button
v-if="startAndEndIndex.end < totalPage"
:class="{ active: pageNo === totalPage }"
@click="$emit('change-page-no', totalPage)"
>
{{ totalPage }}
</button>
<button
:disabled="pageNo === totalPage"
@click="$emit('change-page-no', pageNo + 1)"
>
下一页
</button>
<select
v-model="size"
class="select"
@change="$emit('change-page-size', size)"
>
<option v-for="s in pageSizes" :key="s" :value="s">{{ s }} 条/页</option>
</select>
<span class="total">共 {{ total }} 条</span>
</div>
</template>
<script>
export default {
name: 'pagination',
props: {
pageNo: { // 页码
type: Number,
default: 1,
},
pageSize: { // 每页个数
type: Number,
default: 10,
},
total: { // 总条数
type: Number,
default: 0,
},
continues: { // 页码连续出现的个数
type: Number,
default: 5,
},
pageSizes: { // 每页显示个数选择器选项
type: Array,
default: [10, 20, 30, 40],
},
},
data() {
return {
size: 10,
}
},
computed: {
// 总页数
totalPage() {
return Math.ceil(this.total / this.pageSize)
},
// 计算出连续页码的起始数字与结束的数字
startAndEndIndex() {
const { continues, pageNo, totalPage } = this
let start = 0, end = 0
// 即总页数 < 连续页码
if (continues > totalPage) {
start = 1
end = totalPage
} else {
start = pageNo - parseInt(continues / 2)
end = pageNo + parseInt(continues / 2)
if (start < 1) {
start = 1
end = continues
}
if (end > totalPage) {
start = totalPage - continues + 1
end = totalPage
}
}
return { start, end }
},
},
}
</script>
<style lang="scss" scoped>
.pagination {
font-size: 13px;
color: var(--c-text);
text-align: center;
margin: 10px 0 40px;
button {
min-width: 32px;
height: 28px;
padding: 0 8px;
margin: 10px 5px 0;
border: 0;
border-radius: 2px;
background: var(--c-bg-light);
outline: none;
display: inline-block;
box-sizing: border-box;
vertical-align: top;
color: var(--c-text);
cursor: pointer;
&[disabled] {
color: #c0c4cc;
cursor: not-allowed;
}
&.active {
cursor: not-allowed;
background: var(--c-brand);
color: #fff;
}
}
.total {
display: inline-block;
margin-top: 10px;
margin-left: 10px;
}
.select {
appearance: none;
-webkit-appearance: none;
outline: none;
cursor: pointer;
background: var(--c-bg);
border: 1px solid var(--c-border);
border-radius: 2px;
padding: 0 8px;
margin-left: 5px;
margin-top: 10px;
color: var(--c-text);
line-height: 26px;
&::-ms-expand,
&::-webkit-scrollbar,
&::-webkit-scrollbar-button {
display: none;
}
}
}
</style>
博客之前的侧边栏菜单 sidebar 是直接通过 sidebar.js 这个文件单独处理的,里面有直接去遍历博文目录,所以直接可以在里面来同时生成文章列表就行了,完整代码如下:
const fs = require('node:fs')
const path = require('node:path')
const IGNORE_FILE = ['guide.md', '.DS_Store'] // 不需要处理的文件
// 参考 @vuepress/plugin-git 插件通过 git log 获取文件新建和修改时间信息
const execa = require('execa')
const getCreatedTime = async (filePath, cwd) => {
const { stdout } = await execa('git', ['--no-pager', 'log', '--diff-filter=A', '--format=%at', filePath], {
cwd,
})
return Number.parseInt(stdout, 10) * 1000
}
let articleList = []
// 自动读取 note 文件夹目录生成侧边栏菜单
let sidebar = [{ text: 'home', link: '/note/guide' }]
const menuList = fs.readdirSync(path.join(__dirname, '../note'))
menuList.map(m => {
if (!IGNORE_FILE.includes(m)) {
let posts = fs.readdirSync(path.join(__dirname, '../note/' + m))
let children = []
posts.map(async (n) => {
if (!IGNORE_FILE.includes(n)) {
children.push({
text: n,
link: `/note/${m}/${n}/index.md`
})
let createTimestamp = await getCreatedTime(path.join(__dirname, `../note/${m}/${n}/index.md`))
if (isNaN(createTimestamp)) createTimestamp = new Date().getTime()
const date = new Date(createTimestamp)
const year = date.getFullYear()
let month = date.getMonth() + 1
if (month < 10) month = '0' + month
let day = date.getDate()
if (day < 10) day = '0' + day
articleList.push({
title: n,
createTimestamp,
createTime: `${year}-${month}-${day}`,
link: `/note/${m}/${n}/index.html` // 注意这里路径不能用和 sidebar 一样的 md 文件
})
}
})
sidebar.push({
text: m,
collapsible: true,
children
})
}
})
// 写入首页 article-list.vue 文章列表组件数据
fs.readFile(path.join(__dirname, './components/article-list.vue'), 'utf-8', async (err, data) => {
if (err) return console.error(err)
// 按发布时间排下序
articleList.sort((a, b) => {
return b.createTimestamp - a.createTimestamp
})
let newTxt = data.replace(/this\.articleList=\[[\S\s]*\]/, `this.articleList=${JSON.stringify(articleList, null, 2)}`)
fs.writeFile(path.join(__dirname, './components/article-list.vue'), newTxt, (err, data) => {
if (err) return console.error(err)
})
})
module.exports = sidebar
开始直接在生成列表数据时直接生成的 template 模板标签里的内容,这样就有点类似后端里的模板技术、jsp之类的,不过现在都是前后端分离,为了好维护最终还是改成只去替换组件里的 articleList 列表数据,这样 article-list 组件里可以随意修改布局样式交互这些, sidebar 只是提供对应的数据。
最后直接在博客首页的 markdown 文件里引入 article-list 组件就行了:
---
home: true
heroImage: /images/logo.png
heroText:
tagline: web • uniapp • flutter • electron • wordpress • node • java
---
<article-list />
最终的博客首页文章列表展示效果图:
还是比较满意的,后面还可以从内容里提取出分类、摘要、图片、作者这些信息,让列表更加的丰富,等有空了再来完善。
者:HelloGitHub-追梦人物
在 通过 Django Pagination 实现简单分页中,我们实现了一个简单的分页导航。但效果有点差强人意,我们只能点上一页和下一页的按钮进行翻页。比较完善的分页效果应该像下面这样,但想实现这样一种效果,Django Pagination 内置的 API 已无能为力。接下来我们将通过拓展 Django Pagination 来实现下图这样比较完善的分页效果。
一个比较完善的分页效果应该具有以下特性,就像上图展示的那样,很多网站都采用了类似这种的分页导航方式。
如果需要自己来实现分页效果,我们会怎么做呢?先来分析一下导航条的组成部分,可以看到整个分页导航条其实可以分成 7 个部分:
因此我们的思路是,在视图中依据上述规则生成页码列表,然后在模板中循环显示页码列表就可以了。有了思路,实现起来其实也并不很难。不过对于这类常见需求,别人早就帮我们实现好了,本着不重复造轮子的原则,直接拿来用就好。
我们第一次开始接触 django 第三方拓展,在此之前我们一直都基于 django 本身我们提供的功能在开发,然而 django 强大的地方就在于海量的第三方应用供我们挑选,几乎大部分 web 开发中的需求,django 都能找到他人已经写好的第三方应用,拿来即用。
事实上,正确的 django 开发姿势应该是这样的:
以我们的分页功能举例:
首先我们上面分析了分页需求的实现。然后我在 GitHub 上通过 django pagination 关键词进行搜索,在比较了多个 star 数比较高的项目后,发现 django-pure-pagination 文档最清晰,使用最简单,因此决定将这个应用集成到我们的博客来。值得一提的是,尽管这个应用显示作者最后一次更新代码在 4 年前,但我粗略浏览了一下源码,发现其依赖的 django api 4 年来异常稳定,所以确保能在 django 2.2 中使用。
接下来我们就来使用它,首先安装它:
$ pipenv install django-pure-pagination
然后将它注册到 INSTALLED_APPS 里:
INSTALLED_APPS = [
# ...
'pure_pagination', # 分页
'blog.apps.BlogConfig', # 注册 blog 应用
'comments.apps.CommentsConfig', # 注册 comments 应用
]
修改一下 IndexView,让它继承 django-pure-pagination 提供的 PaginationMixin,这个混入类将为我们提供上述提到的分页功能。
class IndexView(PaginationMixin, ListView):
model = Post
template_name = 'blog/index.html'
context_object_name = 'post_list'
paginate_by = 10
然后我们可以在 common.py 配置中配置一下分页的效果,这是 django-pure-pagination 提供的配置项,用于个性化配置分页效果:
# django-pure-pagination 分页设置
PAGINATION_SETTINGS = {
'PAGE_RANGE_DISPLAYED': 4, # 分页条当前页前后应该显示的总页数(两边均匀分布,因此要设置为偶数),
'MARGIN_PAGES_DISPLAYED': 2, # 分页条开头和结尾显示的页数
'SHOW_FIRST_PAGE_WHEN_INVALID': True, # 当请求了不存在页,显示第一页
}
在模板中需要分页的地方,调用分页对象的 render 方法就可以了,比如在 index.html 中:
{% if is_paginated %}
{{ page_obj.render }}
{% endif %}
注意这里 page_obj 是分页后的对象列表,具体请参考上一篇文章的讲解。render 方法会自动帮我们渲染一个预先定义好的分页条,至此,分页功能就完成了。
有时候预定义的分页条并不能满足我们的需求,我们可以通过自定义的模板来覆盖预定义的模板。django 查找模板的顺序是,首先在项目配置的模板根路径寻找(我们项目中配的是 templates 文件夹),没有找到的话,再去应用的 templates 目录下寻找。分页模板预定义的路径为 pure_pagination/pagination.html,所以我们可以在项目模板根路径下建立一个一模一样的文件结构,这样 django 就会首先找到我们的模板,从而应用我们自定义的模板,而不是预定义的模板。
在 templates 目录下新建一个 pure_pagination\ 目录,然后建立一个 pagination.html 文件。
接下来便是在模板中设置分页导航了,将导航条的七个部分的数据一一展现即可,示例代码如下:
<div class="text-center pagination" style="width: 100%">
<ul>
{% if page_obj.has_previous %}
<li><a href="?{{ page_obj.previous_page_number.querystring }}"
class="prev">‹‹ </a></li>
{% else %}
<li><span class="disabled prev">‹‹ </span></li>
{% endif %}
{% for page in page_obj.pages %}
{% if page %}
{% ifequal page page_obj.number %}
<li class="current"><a href="#">{{ page }}</a></li>
{% else %}
<li><a href="?{{ page.querystring }}" class="page">{{ page }}</a></li>
{% endifequal %}
{% else %}
...
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<li><a href="?{{ page_obj.next_page_number.querystring }}" class="next"> ››</a>
</li>
{% else %}
<li><span class="disabled next"> ››</span></li>
{% endif %}
</ul>
</div>
多添加几篇文章,在示例中就可以看到分页效果了。要使分页导航更加美观,通过设置其 CSS 样式即可。
『讲解开源项目系列』——让对开源项目感兴趣的人不再畏惧、让开源项目的发起者不再孤单。跟着我们的文章,你会发现编程的乐趣、使用和发现参与开源项目如此简单。欢迎留言联系我们、加入我们,让更多人爱上开源、贡献开源
击右上方红色按钮关注“web秀”,让你真正秀起来
作为前端开发,很多时候,后台给的数据不是我们想要的,遇到好说话的,给你改,遇到那种不好说话的,只能自己动手了。今天就来给大家讲讲前端如何来模拟分页,下面注释很详细,这里就不做过多的介绍了。
<div id="docList"></div> <div class="more" id="docLoadMore">加载更多</div>
var data = { // 分页数据 docPages: { pageNo: 1, // 当前页码 pageSize: 3 // 一页多少条数据 }, // 模拟数据 docList: [{ name: '这是我的第1篇文章' },{ name: '这是我的第2篇文章' },{ name: '这是我的第3篇文章' },{ name: '这是我的第4篇文章' },{ name: '这是我的第5篇文章' },{ name: '这是我的第6篇文章' },{ name: '这是我的第7篇文章' },{ name: '这是我的第8篇文章' }] } $('#docLoadMore').on('click',function(){ function getHtml(name) { // html模板 var tpl = '<div class="load-li dis-flex clearfix">'+ '<span class="align-left">'+ '<img src="../images/productDetails/yh_product_wendang_icon@2x.png" / class="word-icon">'+ '</span>'+ '<span class="color-66 flex1 li-content fs-01">'+name+'</span>'+ '<span class="align-right">'+ '<img src="../images/productDetails/yh_product_xiazai_icon@2x.png" / class="load-icon">'+ '</span>'+ '</div>'; return tpl; } var pageNo = data.docPages.pageNo; var pageSize = data.docPages.pageSize; // 计算最多分多少页 // 总条数 / 一页多少条 = 可以分多少页 如果是小数 向上取整(Math.ceil) let maxPage = Math.ceil(data.docList.length / pageSize); // 如果页面大于总页数return if (pageNo > maxPage) { console.log('没有更多了'); return; } // 计算第n页时取第几条到第几条数据 var startIndex = (pageNo-1) * pageSize; // 下标从0开始,所以-1 // 1:(1-1)*2=>0 // 2:(2-1)*2=>2 // 3:(3-1)*2=>4 // 4:(4-1)*2=>6 var endIndex = pageNo * pageSize - 1; // 1: 1*2=>2 // 2: 2*2=>4 // 3: 3*2=>6 data.docPages.pageNo ++; // 根据下标找到对应的页码的数据 var newPage = vm.data.docList.slice(startIndex, endIndex+1); let html = ''; newPage.map(function(item){ html += getHtml(item.name); }); // 向元素后面插入准备好的html内容 $('#docList').append(html); })
中间有部分是模板,看的不是很清楚,看下面截图。
上面代码没有初始化第一页数据,点击一下才会出来第一页的数据,所以可以在页面加载完毕,自动触发一下点击事件
$('#docLoadMore').trigger('click');
是不是很简单了,只要自己动手操作,发现事实都很简单。
喜欢小编的点击关注,了解更多知识!
源码地址请点击下方“了解更多”
*请认真填写需求信息,我们会在24小时内与您取得联系。