
我们在上一篇文章之中,实现了在首页调用侧边栏的 最近发布 栏目,详细修改教程,大家可以参阅 安知鱼主题侧边栏最近发布的修改 这篇文章,我们今天来给侧边栏添加一个 随机发布 的栏目,在很多传统的博客中,这个栏目不多见,只是一些门户网站中常见。

代码借助了安知鱼主题默认的 最近发布 的 CSS 样式,所以有些内容与默认的 最近发布 板块类似,下面我们来看实现的过程。
1.添加主题设置
打开主题文件夹下面的 _config.yml 文件,搜索 # aside (侧边栏) ,在 aside: 里面添加如下设置
1 2 3 4 5 6
| # 新增:随机文章组件配置 card_random_post: enable: true # 是否开启随机文章组件 limit: 5 # 显示的随机文章数量 date_enable: true # 是否显示文章发布日期 date_format: "YYYY-MM-DD" # 日期格式
|
2.添加标题设置
打开根目录下面的 /themes/anzhiyu/languages 文件夹,在里面找到 default.yml 文件,打开之后找到如下代码
1 2 3 4 5 6 7 8 9
| aside: articles: 文章 tags: 标签 categories: 分类 card_announcement: 公告 card_categories: 分类 card_tags: 标签 card_archives: 归档 card_recent_post: 最近发布
|
代码之后还有很多行代码,主要是在
下面,添加如下代码
添加之后的效果
1 2 3 4 5 6 7 8 9 10 11
| aside: articles: 文章 tags: 标签 categories: 分类 card_announcement: 公告 card_categories: 分类 card_tags: 标签 card_archives: 归档 card_recent_post: 最近发布 card_random_post: 随机推荐 card_webinfo:
|
3.添加随机调用代码
打开 /themes/anzhiyu/layout/includes/widget 文件夹,在里面新建 card_random_post.pug 文件,之后复制下面的代码粘贴到文件中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
| //- 侧边栏随机文章卡片(稳定版:逻辑清晰+刷新即变+样式统一) if theme.aside.card_random_post.enable && site.posts.length > 0 .card-widget.card-recent-post .item-headline
i.anzhiyufont.anzhiyu-icon-dice-d20 span= _p('aside.card_random_post') //- 1. 隐藏的完整文章数据源(彻底隐藏,不显示) #random-posts-data.random-posts-data - site.posts.each(function(article) { // 逐行声明变量,逻辑清晰,避免Pug解析报错 - var articleLink = url_for(article.link || article.path); - var articleTitle = article.title || _p('no_title'); - var articleCover = article.cover ? url_for(article.cover) : ''; - var noCoverClass = article.cover === false || !theme.cover.aside_enable ? 'no-cover' : ''; - var articleDate = date(article.date, config.date_format); - var articleUpdated = date(article.updated, config.date_format); - var articleDateXml = date_xml(article.date); - var articleUpdatedXml = date_xml(article.updated);
// 用多个data属性存储数据,绕开Pug解析JSON的坑 div( data-link=articleLink, data-title=articleTitle, data-cover=articleCover, data-no-cover=noCoverClass, data-date=articleDate, data-updated=articleUpdated, data-date-xml=articleDateXml, data-updated-xml=articleUpdatedXml ) - })
//- 2. 随机文章显示容器 #random-posts-display.aside-list
//- 3. 前端随机逻辑(ES5写法,稳定无兼容问题) script. // 页面加载完成后执行 window.onload = function() { // 读取主题配置参数 var showLimit = !{theme.aside.card_random_post.limit === 0 ? site.posts.length : theme.aside.card_random_post.limit || 5}; var sortType = !{JSON.stringify(theme.aside.card_random_post.sort || 'date')}; var errorImage = !{JSON.stringify(url_for(theme.error_img.post_page))}; var textUpdated = !{JSON.stringify(_p('post.updated') || '更新于')}; var textCreated = !{JSON.stringify(_p('post.created') || '创建于')};
// 步骤1:获取所有文章数据 var allPostNodes = document.querySelectorAll('#random-posts-data > div'); var allPosts = []; for (var i = 0; i < allPostNodes.length; i++) { var node = allPostNodes[i]; allPosts.push({ link: node.dataset.link, title: node.dataset.title, cover: node.dataset.cover, noCover: node.dataset.noCover, date: node.dataset.date, updated: node.dataset.updated, dateXml: node.dataset.dateXml, updatedXml: node.dataset.updatedXml }); }
// 步骤2:Fisher-Yates洗牌算法(经典随机,结果均匀) for (var j = allPosts.length - 1; j > 0; j--) { var randomIndex = Math.floor(Math.random() * (j + 1)); var temp = allPosts[j]; allPosts[j] = allPosts[randomIndex]; allPosts[randomIndex] = temp; }
// 步骤3:截取需要显示的数量 var randomPosts = allPosts.slice(0, showLimit);
// 步骤4:渲染到页面 var displayContainer = document.getElementById('random-posts-display'); var htmlContent = ''; for (var k = 0; k < randomPosts.length; k++) { var post = randomPosts[k]; // 拼接封面图HTML var coverHtml = ''; if (post.cover && post.noCover === '') { coverHtml = '<a class="thumbnail" href="' + post.link + '" title="' + post.title + '">' + '<img src="' + post.cover + '" onerror="this.onerror=null;this.src=\'' + errorImage + '\'" alt="' + post.title + '">' + '</a>'; }
// 拼接日期HTML var dateHtml = ''; if (sortType === 'updated') { dateHtml = '<time datetime="' + post.updatedXml + '" title="' + textUpdated + ' ' + post.updated + '">' + post.updated + '</time>'; } else { dateHtml = '<time datetime="' + post.dateXml + '" title="' + textCreated + ' ' + post.date + '">' + post.date + '</time>'; }
// 拼接单篇文章的完整HTML htmlContent += '<div class="aside-list-item ' + post.noCover + '">' + coverHtml + '<div class="content">' + '<a class="title" href="' + post.link + '" title="' + post.title + '">' + post.title + '</a>' + dateHtml + '</div>' + '</div>'; }
// 把拼接好的HTML插入显示容器 displayContainer.innerHTML = htmlContent; };
|
这段代码主要作用,就是随机生成文章,显示在页面中,通过 JS 调用,刷新页面重建数据。
然后打开同文件夹下的 index.pug 将 card_random_post.pug 文件引入到模板中,在这个文件夹中,找到如下代码
1 2 3 4 5
| else //- page !=partial('includes/widget/card_author', {}, {cache: true}) !=partial('includes/widget/card_announcement', {}, {cache: true}) !=partial('includes/widget/card_weixin', {}, {cache: true})
|
你如果修改过这个文件,应该清除把 card_random_post.pug 放到哪里,我把这个 card_random_post.pug 文件放在了
1
| !=partial('includes/widget/card_weixin', {}, {cache: true})
|
代码下面,我的代码如下
1 2 3 4 5 6 7
| else //- page !=partial('includes/widget/card_author', {}, {cache: true}) !=partial('includes/widget/card_weixin', {}, {cache: true}) !=partial('includes/widget/card_announcement', {}, {cache: true}) !=partial('includes/widget/card_recent_post', {}, {cache: true}) !=partial('includes/widget/card_random_post', {}, {cache: true})
|
到这里代码文件修改完成,我顺便调用了 最近发布 板块。
在这里顺便记录一下安知鱼主题的 iconfont 库的链接地址,因为需要修改这个板块的图标,所以查阅了一下
1
| https://www.iconfont.cn/collections/detail?cid=44481
|
使用起来会很方便的。
4.补充精简代码
补充的这份代码,没有 JS 调用,仅在 Hexo 编译时生成一次随机列表,刷新网页不会变化。
核心原因是 随机逻辑在服务端编译阶段执行 ,而且只是仅执行了一次,而非在浏览器端每次刷新时执行。分享给大家只是为了做一个标记
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| //- 侧边栏随机文章卡片(沿用最新文章样式) if theme.aside.card_random_post.enable && site.posts.length > 0 .card-widget.card-recent-post .item-headline i.anzhiyufont.anzhiyu-icon-dice-d20 span= _p('aside.card_random_post') .aside-list //- 核心:Fisher-Yates 洗牌算法打乱文章列表 - // 将Hexo的posts集合转为普通数组 let postsArray = site.posts.toArray(); // 随机打乱数组(保证随机性均匀) for (let i = postsArray.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [postsArray[i], postsArray[j]] = [postsArray[j], postsArray[i]]; } // 读取配置的显示数量(和原组件逻辑一致) let postLimit = theme.aside.card_random_post.limit === 0 ? postsArray.length : theme.aside.card_random_post.limit || 5; // 截取指定数量的随机文章 let randomPosts = postsArray.slice(0, postLimit); //- 渲染随机文章列表(完全复用原有样式结构) - randomPosts.forEach(function(article){ - let link = article.link || article.path - let title = article.title || _p('no_title') // 复用封面图显示逻辑(是否显示封面、无封面样式) - let no_cover = article.cover === false || !theme.cover.aside_enable ? 'no-cover' : '' - let post_cover = article.cover .aside-list-item(class=no_cover) // 封面图区域 if post_cover && theme.cover.aside_enable a.thumbnail(href=url_for(link) title=title) img(src=url_for(post_cover) onerror=`this.onerror=null;this.src='${url_for(theme.error_img.post_page)}'` alt=title) // 标题和日期区域 .content a.title(href=url_for(link) title=title)= title // 日期显示逻辑(支持按创建时间/更新时间显示) if theme.aside.card_random_post.sort === 'updated' time(datetime=date_xml(article.updated) title=_p('post.updated') + ' ' + full_date(article.updated)) #[=date(article.updated, config.date_format)] else time(datetime=date_xml(article.date) title=_p('post.created') + ' ' + full_date(article.date)) #[=date(article.date, config.date_format)] - })
|
仅做记录,这份代码不如第一份代码调用数据灵活。
5.完美代码
第一份代码虽然强大,很好的解决了 JS 灵活调用,刷新网页就可以更换内容的痛点,但是起初状态是没有数据的,也就是打开网页之初没有数据,刷新才能够显示数据;第二组代码虽然写死了推荐内容,但是之初打开网页是有数据的,痛点是刷新网页数据不变化。于是有了完美代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
| //- 侧边栏随机文章卡片(优化版:服务端预渲染+客户端刷新更新) if theme.aside.card_random_post.enable && site.posts.length > 0 .card-widget.card-recent-post .item-headline i.anzhiyufont.anzhiyu-icon-dice-d20 span= _p('aside.card_random_post') //- 1. 服务端预渲染的随机文章(页面加载即可见) #random-posts-container.aside-list - // 将Hexo的posts集合转为普通数组 let postsArray = site.posts.toArray(); // Fisher-Yates 洗牌算法打乱数组 for (let i = postsArray.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [postsArray[i], postsArray[j]] = [postsArray[j], postsArray[i]]; } // 读取配置的显示数量 let postLimit = theme.aside.card_random_post.limit === 0 ? postsArray.length : theme.aside.card_random_post.limit || 5; // 截取指定数量的随机文章 let randomPosts = postsArray.slice(0, postLimit); //- 渲染服务端随机文章列表(复用原有样式) - randomPosts.forEach(function(article){ - let link = article.link || article.path - let title = article.title || _p('no_title') - let no_cover = article.cover === false || !theme.cover.aside_enable ? 'no-cover' : '' - let post_cover = article.cover .aside-list-item(class=no_cover) if post_cover && theme.cover.aside_enable a.thumbnail(href=url_for(link) title=title) img(src=url_for(post_cover) onerror=`this.onerror=null;this.src='${url_for(theme.error_img.post_page)}'` alt=title) .content a.title(href=url_for(link) title=title)= title if theme.aside.card_random_post.sort === 'updated' time(datetime=date_xml(article.updated) title=_p('post.updated') + ' ' + full_date(article.updated)) #[=date(article.updated, config.date_format)] else time(datetime=date_xml(article.date) title=_p('post.created') + ' ' + full_date(article.date)) #[=date(article.date, config.date_format)] - })
//- 2. 隐藏的完整文章数据源(供客户端JS使用) #random-posts-data.random-posts-data(style="display: none;") - site.posts.each(function(article) { - var articleLink = url_for(article.link || article.path); - var articleTitle = article.title || _p('no_title'); - var articleCover = article.cover ? url_for(article.cover) : ''; - var noCoverClass = article.cover === false || !theme.cover.aside_enable ? 'no-cover' : ''; - var articleDate = date(article.date, config.date_format); - var articleUpdated = date(article.updated, config.date_format); - var articleDateXml = date_xml(article.date); - var articleUpdatedXml = date_xml(article.updated);
div( data-link=articleLink, data-title=articleTitle, data-cover=articleCover, data-no-cover=noCoverClass, data-date=articleDate, data-updated=articleUpdated, data-date-xml=articleDateXml, data-updated-xml=articleUpdatedXml ) - })
//- 3. 客户端刷新逻辑(页面加载后立即更新) script. // 页面加载完成后执行 document.addEventListener('DOMContentLoaded', function() { // 读取主题配置参数 var showLimit = !{theme.aside.card_random_post.limit === 0 ? site.posts.length : theme.aside.card_random_post.limit || 5}; var sortType = !{JSON.stringify(theme.aside.card_random_post.sort || 'date')}; var errorImage = !{JSON.stringify(url_for(theme.error_img.post_page))}; var textUpdated = !{JSON.stringify(_p('post.updated') || '更新于')}; var textCreated = !{JSON.stringify(_p('post.created') || '创建于')};
// 步骤1:获取所有文章数据 var allPostNodes = document.querySelectorAll('#random-posts-data > div'); var allPosts = []; for (var i = 0; i < allPostNodes.length; i++) { var node = allPostNodes[i]; allPosts.push({ link: node.dataset.link, title: node.dataset.title, cover: node.dataset.cover, noCover: node.dataset.noCover, date: node.dataset.date, updated: node.dataset.updated, dateXml: node.dataset.dateXml, updatedXml: node.dataset.updatedXml }); }
// 步骤2:Fisher-Yates洗牌算法打乱 for (var j = allPosts.length - 1; j > 0; j--) { var randomIndex = Math.floor(Math.random() * (j + 1)); var temp = allPosts[j]; allPosts[j] = allPosts[randomIndex]; allPosts[randomIndex] = temp; }
// 步骤3:截取需要显示的数量 var randomPosts = allPosts.slice(0, showLimit);
// 步骤4:渲染新的随机文章列表 var displayContainer = document.getElementById('random-posts-container'); var htmlContent = ''; for (var k = 0; k < randomPosts.length; k++) { var post = randomPosts[k]; // 拼接封面图HTML var coverHtml = ''; if (post.cover && post.noCover === '') { coverHtml = '<a class="thumbnail" href="' + post.link + '" title="' + post.title + '">' + '<img src="' + post.cover + '" onerror="this.onerror=null;this.src=\'' + errorImage + '\'" alt="' + post.title + '">' + '</a>'; }
// 拼接日期HTML var dateHtml = ''; if (sortType === 'updated') { dateHtml = '<time datetime="' + post.updatedXml + '" title="' + textUpdated + ' ' + post.updated + '">' + post.updated + '</time>'; } else { dateHtml = '<time datetime="' + post.dateXml + '" title="' + textCreated + ' ' + post.date + '">' + post.date + '</time>'; }
// 拼接单篇文章的完整HTML htmlContent += '<div class="aside-list-item ' + post.noCover + '">' + coverHtml + '<div class="content">' + '<a class="title" href="' + post.link + '" title="' + post.title + '">' + post.title + '</a>' + dateHtml + '</div>' + '</div>'; }
// 替换原有内容(实现刷新更新) displayContainer.innerHTML = htmlContent; });
|
先在生成文件的时候预先渲染一组数据存放在 “随机推荐” 的栏目中,然后再通过 JS 重新调用另一组数据备用,这样一来就解决了起初没有数据,靠刷新才展现数据的痛点,完美!
5.检查效果生成
最后 Hexo 三连(hexo c && hexo g && hexo s)就可以看到文章中第二张图调用的效果。