Hexo nexT主题添加相册

给博客添加一个相册页面,以展示自己拍摄的一些照片 (≖ᴗ≖)✧

_config.next.yml

首先新建hexo new page photos相册页面,将会在source/下创建photos/index.md,在其中添加type: photos

之后在主题_config.next.yml文件中对应位置menu里添加Photos: /photos/ || image ,这样生成后就能在页面的对应页面选项中有该相册Tab。

1
2
menu:
photos: /photos/ || fas fa-camera-retro

scripts

在博客根目录下新建scripts文件夹,里面将会存放相关js文件。

新建scripts/phototool.js文件,里面内容如下,主要功能是访问照片文件夹,获取每张照片的size和name,并生成对应的json文件:

命令:Git Bash中键入 node phototool.js生成json
注:若出现Error: Cannot find module 'axios'问题,请在Git Bash中键入对应命令npm install image-size进行安装。

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
const axios = require("axios");
const fs = require("fs");
const path = require("path");

// 配置项
const OWNER = "xxxxx"; // GitHub用户名
const REPO = "xxxx"; // 仓库名称
const BRANCH = "master"; // 分支名称
const TOKEN = "ghp_Czclu19lnkjrACxxxxxxxxxxxxxxxxxxxxx"; // 你的GitHub个人访问令牌
const API_URL = `https://api.github.com/repos/${OWNER}/${REPO}/contents`;
const OUTPATH = "source/images/picX";// 这里是输出图片json的目录
const GenerateImg = ["DevOps","docker","rabbitMQ","redis"]//这里是你需要生成仓库中指定的文件中的图片

// 递归获取文件信息
async function fetchFiles(dir = "") {
const url = dir ? `${API_URL}/${dir}?ref=${BRANCH}` : `${API_URL}?ref=${BRANCH}`;
try {
const { data } = await axios.get(url, {
headers: {
Authorization: `token ${TOKEN}`,
},
});

const tasks = data.map(async (item) => {
if (item.type === "file" && /\.(jpg|jpeg|png|webp|gif)$/i.test(item.name)) {
return {
name: item.name,
width: 0,
height: 0,
path: item.path,
size: item.size,
url: item.html_url
};
} else if (item.type === "dir") {
if (GenerateImg.includes(item.name)) {
// 如果是文件夹,递归处理
const folderData = await fetchFiles(item.path);
saveJsonToFile(item.name, folderData);
}
return null;
}
});

return (await Promise.all(tasks)).filter(Boolean); // 过滤掉空值
} catch (error) {
console.error(`获取目录 ${dir} 失败: `, error.message);
return [];
}
}

// 保存JSON到对应文件夹
function saveJsonToFile(folderName, data) {
const folderPath = path.join(OUTPATH, folderName);
if (!fs.existsSync(folderPath)) {
fs.mkdirSync(folderPath, { recursive: true });
}
if (data == null)
return
const filePath = path.join(folderPath, `${folderName}_info.json`);
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), "utf-8");
console.log(`JSON文件已保存到: ${filePath}`);
}

// 主函数
(async function main() {
console.log("开始获取GitHub图床数据喵...");
await fetchFiles();
//saveJsonToFile("", data); // 保存根目录的JSON
console.log("所有数据已处理完成喵!");
})();

文件样例如下:

生成文件的路径source/images/picX/docker/docker_info.json

1
2
3
4
5
6
7
8
9
10
[
{
"name": "Untitled-1.ic5ab26hd.webp",
"width": 0,
"height": 0,
"path": "docker/Untitled-1.ic5ab26hd.webp",
"size": 14830,
"url": "https://github.com/xxxxx/picx-xxxxxx/blob/master/docker/Untitled-1.ic5ab26hd.webp"
},
]

inline-tags.js

新建scripts/inline-tags.js文件,里面内容如下,主要功能是注入方法,根据指定的路径生成相册以及图片的Html元素:

inline-tags.js内容如下,主要功能是访问json文件内容,遍历每行数据,并在页面对应位置上放置代码,展示图片(其中图片链接为自个GitHub相册库中图片的链接):

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
'use strict';

// 初始化对象存储域名 cosDomain
const cosDomain = hexo.config.cos_domain;
const cos_base = hexo.config.cos_base;
// preconnect
hexo.extend.injector.register('head_begin', () => {
// const vendorsCdnUrl = new URL(hexo.config.theme_config.vendors.custom_cdn_url);
// const vendorsCdn = vendorsCdnUrl.protocol + '//' + vendorsCdnUrl.hostname;
// <link rel="preconnect" href="${vendorsCdn}" crossorigin="">
return `
<link rel="preconnect" href="${cosDomain}" crossorigin="">`;
});


// 着重号
hexo.extend.tag.register('dot', function (args) {
return `<span class="emphasis-point">${args.join(' ')}</span>`;
});

// 相册
// 如果输入是两个值,那么用 args[0],args[1] 分别代表
hexo.extend.tag.register('album', function (args) {
const photoSrc = cosDomain + '/';
const jsonSrc = cos_base + `/images/picX/${args}` + `/${args}_info.json`;
return `
<style>
.post-block {
padding-left: 10px;
padding-right: 10px;
}
</style>
<div class="album" photo-src="${photoSrc}" json-src="${jsonSrc}"></div>
`;
});


// 子页面列表
hexo.extend.tag.register('subpagebox', function ([args, delimiter = '|', comment = '%'], content) {
console.log(args, delimiter, comment, content);
const links = content.split('\n').filter(line => line.trim() !== '').map(line => {
const item = line.split(delimiter).map(arg => arg.trim());
const imageSource = cosDomain + '/' + item[1] + '/' + item[2];
if (item[0][0] === comment) return '';
return `
<div class="subpage-box-cover">
<a style="width: 100%;" href="${item[1]}/">
<p class="image-caption">${item[0]}</p>
<img alt="${item[0]}" src="${imageSource}">
</a>
</div>
`;
});
return `<div class="subpage-box">${links.join('')}</div>`;
}, true);

  • 添加配置cos_domain以及cos_base
    • 在Hexo的_config.yml文件当中添加
    • cos_domain: https://xxxxx/aaaaaa
      cos_base: https://xxxxx
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18

      ## 相册菜单

      ### 相册菜单主页面

      添加相册主页对应不同模块标签,如下图

      ![6a3f387807d0f0246e55a3f7e8c126fb.png](https://github.com/fantasy-ke/picx-images-hosting/Qexo/24/11/6a3f387807d0f0246e55a3f7e8c126fb.png "6a3f387807d0f0246e55a3f7e8c126fb.png")

      文件对应的结构目录

      ```yaml
      photos
      --Devops
      ----index.md
      --docker
      ----index.md
      --index.md

路径photos/index.md添加内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
---
title: 相册
date: 2024-11-18 15:41:38
type: "photos"
comments: false
---

<div class="text-center">岁月无声,光影留痕。</div>

<!-- inline-tags.js 注入的子页面方法subpagebox -->
<!-- 文件夹名称 | 文件夹名称 | 对应标签的主封面 -->
<!-- redis | redis | logo.491b7w38xo.webp -->

{% subpagebox %}
redis | redis | logo.491b7w38xo.webp
docker | docker | logo.1027b8euuh.webp
rabbitMQ | rabbitMQ | image.5j48e7lwua.webp
DevOps | DevOps | logo.7ax7942wpp.svg
{% endsubpagebox %}


相册菜单子页面

路径photos/docker/index.md添加内容如下

1
2
3
4
5
6
7
---
title: docker
description: <a href="../">光影集</a> / docker
---
<!-- inline-tags.js 注入的子页面方法album -->
{% album docker %}

添加子页面布局

body-end.njk

新增文件_data/body-end.njk 如下

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
{### 相册 ###}
{#
<script src="https://cdn.jsdelivr.net/npm/minigrid@3.1.1/dist/minigrid.min.js" integrity="sha256-oexHY81/KuGogn0rnUzhYExxPnIyzC4ErClSXE+jFa8=" crossorigin="anonymous"></script>
#}
<script src="/resources/minigrid.min.js"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script{{ pjax }} type="text/javascript">
var album = document.querySelector(".album");
if (album) {
// 相册列表 JSON 数据
var imgDataPath = album.getAttribute('json-src');
// 照片存储路径
var imgPath = album.getAttribute('photo-src');
// 最多显示数量
var imgMaxNum = 50;
// 获取窗口大小以决定图片宽度
var windowWidth = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
var imageWidth;

if (windowWidth < 768) {
imageWidth = 145; // 移动端图片宽度
} else {
imageWidth = 235;
}

// 生成相册
var linkDataPath = imgDataPath;
var photo = {
page: 1,
offset: imgMaxNum,
init: function () {
var that = this;
$.getJSON(linkDataPath, function (data) {
that.render(that.page, data);
});
},
render: function (page, data) {
var begin = (page - 1) * this.offset;
var end = page * this.offset;
if (begin >= data.length) return;
var imgNameWithPattern, imgName, imageSize, imageX, imageY, li = "";
for (var i = begin; i < end && i < data.length; i++) {
imgNameWithPattern = data[i].path;
imgName = data[i].name;
imageSize = data[i].size;
li += '<div class="card" style="width:' + imageWidth + 'px" >';
li += '<div class="album-photo" style="height:' + imageWidth + 'px" >';
li += '<a class="fancybox fancybox.image" href="' + imgPath + imgNameWithPattern + '" itemscope="" itemtype="http://schema.org/ImageObject" itemprop="url" data-fancybox="group" rel="group" data-caption="' + imgName + '" title="' + imgName + '">';
li += '<img data-src="' + imgPath + imgNameWithPattern + '" src="' + imgPath + imgNameWithPattern + '" alt="' + imgName + '" data-loaded="true">';
li += '</a>';
li += '</div>';
li += '</div>';
}
album.insertAdjacentHTML('beforeend', li);
this.minigrid();
},
minigrid: function () {
var grid = new Minigrid({
container: '.album',
item: '.card',
gutter: 12
});
grid.mount();
window.addEventListener('resize', function () {
grid.mount();
});
}
};
photo.init();
}
</script>

引入布局js minigrid.min.js可以使用远程直接引入

1
<script src="https://cdn.jsdelivr.net/npm/minigrid@3.1.1/dist/minigrid.min.js" integrity="sha256-oexHY81/KuGogn0rnUzhYExxPnIyzC4ErClSXE+jFa8=" crossorigin="anonymous"></script>

也可以下载下来本地引入

1
<script src="/resources/minigrid.min.js"></script>

新增相册页面样式

styles.styl

新增文件_data/styles.styl 如下

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
/**********************************************************/
/*********************** 子页面列表 *************************/
/**********************************************************/

.subpage-box {
display: flex;
flex-wrap: wrap;
gap: 3px;
margin-top: 1.5em;
margin-bottom: 3em;
}

.subpage-box-cover {
margin: 2px;
flex: 0 0 30%;
background: #333;
display: flex;
height: 200px;
}

.subpage-box-cover a {
border-bottom: none;
}

.subpage-box-cover img {
width: 100%;
height: 100%;
margin: 0;
object-fit: cover;
opacity: 0.9;

&:hover{
opacity: 0.7;
}
}

.subpage-box-cover .image-caption {
display: flex;
position: relative;
z-index: 999;
margin: 0;
width: 100%;
height: 0%;
transform: translate(-50%, -50%);
left: 50%;
top: 50%;
justify-content: center;
align-items: center;
font-size: 1.5em;
font-weight: bold;
color: white;
}


//对齐
.text-center {
text-align: center;
}

.text-left {
text-align: left;
}

.text-right {
text-align: right;
}

/**********************************************************/
/********************* 图片与相册 **************************/
/**********************************************************/

/* 图片阴影圆角 */
.posts-expand .post-body img {
/* border-radius: 4px; */
}

/* 图片标题 */
figcaption {
margin: -10px auto 1em;
color: #999;
font-size: 0.875em;
line-height: 1;
text-align: center;
}

/* 组图标题 */
.group-picture {
margin-bottom: 5px;
}

.group-picture-column .image-caption {
margin-top: 10px;
}

.group-picture .group-picture-row {
display: inline-flex;
}

/* 相册 */
.album {
width: 100%;
max-width: 1080px;
margin: 0 auto;
}

.album .card {
overflow: hidden;
transition: .3s ease-in-out;
}

/* 图片大小 */
img[src$='#200x'] { width:200px; }
img[src$='#250x'] { width:250px; }
img[src$='#300x'] { width:300px; }
img[src$='#350x'] { width:350px; }
img[src$='#400x'] { width:400px; }
img[src$='#450x'] { width:450px; }
img[src$='#500x'] { width:500px; }
img[src$='#550x'] { width:550px; }
img[src$='#600x'] { width:600px; }
img[src$='#650x'] { width:650px; }
img.fancybox-image { width:100%; }

替换主题文件

_config.next.yml

1
2
3
custom_file_path:
bodyEnd: source/_data/body-end.njk
style: source/_data/styles.styl

End

提交博客修改:

hexo clean
hexo g
hexo d

最后可以看看效果了。

最终的效果

350abd47866a11aa12d3b335c93769ac.png

350abd47866a11aa12d3b335c93769ac.png

总结

以上就是添加相册功能大概流程,因为步骤比较多,且是通过后期回忆步骤进行记录,所以可能存在些许问题,还请原谅,并请把出现的问题在本文下面的评论中点出,我会进行修改。

后续的实现:

  • 将照片上传至GitHub相册库时,由于照片分辨率较高,其都达到了两三M以上,上传速度较慢,导致上传进度缓慢。后期想通过代码将照片进行压缩后再上传至相册库。
  • 相册展示整个操作流程为:先上传照片到git库,再生成json文件,之后便是正常的clean、g、d,后期想把压缩、上传照片和生成json文件整合到一起。
  • 目前的照片展示都是所有照片一整块放一起进行瀑布流显示,后期想将照片根据其旅游场景或类别、时间不同进行分类至对应文件夹,并根据类别或时间线显式展示出不同文件夹下的照片。

参考链接:
hexo主题中添加相册功能