在Hexo-fluid的分类页中增加说明(2)--注入法
之前发过一篇文章:在Hexo-fluid的分类页中增加说明,实现在分类页面增加说明介绍分类,是通过修改主题模板文件实习的,如果后期主题更新还需要手动再次修改,不太方便,这次实现不修改主题源码,数据驱动的内容注入
背景
在使用 Hexo + Fluid 主题时,我需要在分类页(/categories/)顶部添加一个“专栏说明”板块,用于展示不同分类的介绍、图标和自定义颜色。要求:
· 不修改主题源文件(便于后续升级)
· 内容通过配置文件管理(无需改动代码)
· 位置精确(位于分类列表之前)
经过探索,最终采用 Hexo 官方的 after_render:html 过滤器实现。下面分享完整的实现过程。
最终效果参见本站的分类页
一、整体思路
- 监听生成事件:使用 after_render:html 过滤器,在每个 HTML 文件生成后执行。
- 识别目标页面:判断当前文件路径是否为 categories/index.html。
- 注入样式:在前插入专栏卡片所需的 CSS。
- 读取数据:从 source/_data/ 目录下的 YAML 文件中读取专栏配置。
- 生成 HTML:遍历数据,生成与主题结构兼容的卡片 HTML。
- 插入到正确位置:通过正则匹配分类列表容器(
<div class="category-list">),将卡片 HTML 插入其前面。 - 返回修改后的内容。
二、具体实现
1. 引入 Font Awesome(可选)
本方案使用 Font Awesome 免费图标库。请确保你的主题已引入 Font Awesome CSS,例如在主题配置或全局布局中添加:
1
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css">
|
如果未引入,也可以在注入脚本中一并注入(见下文脚本注释)。
- 创建数据文件
在 source/_data/ 目录下新建:
column_descriptions.yml
1 2 3 4 5 6 7 8 9 10 11
| columns: - name: "技术随笔" subtitle: "编程与开发心得" icon: "fas fa-code" description: "分享前端、后端、DevOps 等技术实践。" color: "#1890ff" - name: "生活杂谈" subtitle: "日常思考与感悟" icon: "fas fa-pen-fancy" description: "记录生活中的点滴与读书笔记。" color: "#f5222d"
|
· icon 字段直接填写 Font Awesome 的完整类名(如 fas fa-code)。
· color 用于卡片边框、图标和高亮颜色。
注:name、subtitle、icon、description、color 请根据需求自行调整
- 编写注入脚本
在博客根目录创建 scripts/ 文件夹(如果没有),然后新建:
categories-inject.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
| const fs = require('fs'); const path = require('path'); const yaml = require('js-yaml');
hexo.extend.filter.register('after_render:html', function(str, data) { if (data.path !== 'categories/index.html') return str;
if (!str.includes('/css/column-cards.css')) { str = str.replace('</head>', '<link rel="stylesheet" href="/css/column-cards.css"></head>'); }
if (!str.includes('font-awesome')) { str = str.replace('</head>', '<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css"></head>'); }
const dataPath = path.join(hexo.source_dir, '_data', 'column_descriptions.yml'); let columns = []; if (fs.existsSync(dataPath)) { try { const content = fs.readFileSync(dataPath, 'utf8'); const yamlData = yaml.load(content); columns = yamlData && yamlData.columns ? yamlData.columns : []; } catch (e) { console.error('读取专栏数据失败:', e); } }
if (!columns.length) return str;
const cardsHtml = generateCardsHtml(columns);
const categoryListRegex = /<div\s+class="category-list"[^>]*>/i; const match = str.match(categoryListRegex); if (match) { const insertPosition = match.index; str = str.slice(0, insertPosition) + cardsHtml + str.slice(insertPosition); } else { str = str.replace('<body>', '<body>\n' + cardsHtml); }
return str; });
function generateCardsHtml(columns) { let html = '<div class="columns-container">'; for (const col of columns) { const color = col.color || '#1890ff'; const rgb = hexToRgb(color); html += ` <div class="column-card" style="--column-color: ${color}; --column-color-rgb: ${rgb.join(',')};"> <div class="card-header"> <i class="${col.icon || 'fas fa-circle-info'}"></i> <div> <h3>${escapeHtml(col.name)}专栏</h3> ${col.subtitle ? `<p class="subtitle">${escapeHtml(col.subtitle)}</p>` : ''} </div> </div> <div class="card-content"> <p>${escapeHtml(col.description)}</p> </div> </div> `; } html += '</div>'; return html; }
function hexToRgb(hex) { hex = hex.replace('#', ''); if (hex.length === 3) { hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2]; } const r = parseInt(hex.substring(0, 2), 16); const g = parseInt(hex.substring(2, 4), 16); const b = parseInt(hex.substring(4, 6), 16); return [r, g, b]; }
function escapeHtml(str) { if (!str) return ''; return str.replace(/[&<>]/g, function(m) { if (m === '&') return '&'; if (m === '<') return '<'; if (m === '>') return '>'; return m; }); }
|
- 编写卡片样式
在 source/css/ 目录下创建:
column-cards.css
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
| .columns-container { display: grid; gap: 1.8rem; margin: 2rem 0; }
.column-card { --column-color: #1890ff; --column-color-rgb: 24, 144, 255; --hover-color: #40a9ff;
padding: 1.5rem; border-radius: 12px; border-left: 4px solid var(--column-color); background: linear-gradient(135deg, rgba(var(--column-color-rgb), 0.05), rgba(var(--column-color-rgb), 0.01)); box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05); transition: all 0.3s ease; }
.column-card:hover { transform: translateY(-3px); box-shadow: 0 8px 20px rgba(0, 0, 0, 0.1); border-left-color: var(--hover-color); }
.card-header { display: flex; align-items: center; gap: 1rem; margin-bottom: 0.8rem; }
.card-header i { font-size: 2rem; color: var(--column-color); }
.card-header h3 { margin: 0; font-size: 1.4rem; font-weight: 600; color: var(--column-color); }
.subtitle { margin: 0; font-size: 0.9rem; color: #666; font-style: italic; }
.card-content p { margin: 0; line-height: 1.6; color: #333; }
@media (max-width: 768px) { .columns-container { gap: 1rem; } .card-header { flex-wrap: wrap; } }
|
- 安装依赖
脚本中使用了 js-yaml 解析 YAML 文件,需要安装:
1
| npm install js-yaml --save
|
如果你不想增加依赖,可以将数据文件改为 JSON 格式(column_descriptions.json),然后用 JSON.parse 读取。
- 测试
1
| hexo clean && hexo generate
|
打开 public/categories/index.html,检查:
1 2
| · <head> 中是否包含 <link rel="stylesheet" href="/css/column-cards.css"> · <div class="category-list"> 之前是否存在 .columns-container 结构
|
启动本地服务器 hexo server,访问分类页查看效果。
三、总结
通过 after_render:html 过滤器,实现了对 Hexo 主题的完全无侵入式修改:
这种思路不仅适用于 Fluid 主题,也可以推广到任何 Hexo 主题。
附:完整文件结构
1 2 3 4 5 6 7 8 9
| your-blog/ ├── scripts/ │ └── categories-inject.js ├── source/ │ ├── _data/ │ │ └── column_descriptions.yml │ └── css/ │ └── column-cards.css └── (其他Hexo文件)
|
希望本文可以对你有所帮助
鸣心/Write