前言
在博客中,除了长篇的技术文章,我们有时也想分享一些简短的想法、生活感悟或者日常动态。说说功能就像微博、朋友圈一样,可以让我们快速记录生活中的点滴,让博客更加生动有趣。
本教程将教你如何在 Hexo + Butterfly 主题中实现一个完整的说说功能。
功能特点
✨ 核心功能:
- 📝 支持发布简短动态
- 📅 自动按时间降序排序
- 🕐 智能时间显示(刚刚、几分钟前、具体日期)
- 📍 支持显示发布来源(Web、手机等)
- 🎨 精美的卡片式设计
- 📱 完美的响应式布局
- 🌙 深色模式适配
- ✨ 流畅的动画效果
实现步骤
第一步:创建说说页面
使用 Hexo 命令创建说说页面:
编辑 source/talking/index.md,设置页面类型:
1 2 3 4 5 6 7 8 9 10
| --- title: 说说 date: 2026-01-07 18:35:09 type: talking comments: true ---
<div id="talking-container"> <div class="talking-loading">加载中...</div> </div>
|
第二步:创建数据文件
在 source/_data/ 目录下创建 talking.yml 文件(如果 _data 目录不存在,需要先创建):
1 2 3 4 5 6 7 8 9 10 11
| - content: 今天天气真好,适合写代码!☀️ date: 2026-01-07 10:00:00 from: Web
- content: 刚刚完成了 Hexo 博客的 PWA 功能配置,现在支持离线访问了!🎉 date: 2026-01-07 14:30:00 from: Web
- content: 学习新技术的过程虽然辛苦,但看到成果的那一刻真的很有成就感 💪 date: 2026-01-06 20:15:00 from: Web
|
数据格式说明:
content: 说说内容(支持 emoji)
date: 发布时间(格式:YYYY-MM-DD HH:mm:ss)
from: 发布来源(可选,如:Web、iPhone、Android)
第三步:创建生成器脚本
在 scripts/ 目录下创建 talking-generator.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
| const yaml = require('js-yaml'); const fs = require('fs'); const path = require('path');
hexo.extend.generator.register('talking',ction(locals) { const talkingPath = path.join(hexo.source_dir, '_data', 'talking.yml');
if (!fs.existsSync(talkingPath)) { return []; }
try { consa = yaml.load(fs.readFileSync(talkingPath, 'utf8'));
if (Array.isArray(talkingData)) { talkingData.sort((a, b) => new Date(b.date) - new Date(a.date)); }
return [{ path: 'talking.json', data: JSON.stringify(talkingData || []) }]; } catch (error) { console.error('生成说说数据失败:', error); return []; } });
|
脚本功能:
- 读取
talking.yml 文件
- 将 YAML 数据转换为 JSON 格式
- 按日期降序排序
- 生成
talking.json 供前端调用
第四步:创建样式文件
在 source/css/ 目录下创建 talking.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 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
| #talking-container { max-width: 800px; margin: 0 auto; padding: 20px; }
.talking-item { background: var(--card-bg); border-radius: 12px; padding: 20px; margin-bottom: 20px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); transition: all 0.3s ease; animation: fadeInUp 0.6s ease-out; }
.talking-item:hover { transform: translateY(-5px); box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15); }
.talking-content { font-size: 16px; line-height: 1.8; color: var(--font-color); margin-bottom: 15px; word-wrap: break-word; }
.talking-meta { display: flex; justify-content: space-between; align-items: center; font-size: 14px; color: var(--font-color); opacity: 0.7; }
.talking-date { display: flex; align-items: center; }
.talking-date i { margin-right: 5px; }
.talking-from { display: flex; align-items: center; }
.talking-from i { margin-right: 5px; }
@keyframes fadeInUp { from { opacity: 0; transform: translateY(30px); } to { opacity: 1; transform: translateY(0); } }
@media screen and (max-width: 768px) { #talking-container { padding: 10px; }
.talking-item { padding: 15px; }
.talking-content { font-size: 14px; }
.talking-meta { flex-direction: column; align-items: flex-start; gap: 8px; } }
[data-theme="dark"] .talking-item { background: var(--card-bg); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); }
[data-theme="dark"] .talking-item:hover { box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4); }
|
样式特点:
- 使用 CSS 变量适配主题
- 卡片式设计,悬停效果
- 渐入动画
- 响应式布局
- 深色模式支持
第五步:创建 JavaScript 文件
在 source/js/ 目录下创建 talking.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
| document.addEventListener('DOMContentLoaded', function() { if (window.location.pathname.includes('/talking/')) { loadTalking(); } });
function loadTalking() { fetch('/talking.json') .then(response => response.json()) .then(data => { renderTalking(data); }) .catch(error => { console.error('加载说说数据失败:', error); showError(); }); }
function renderTalking(talkingList) { const container = document.getElementById('talking-container');
if (!container) { console.error('找不到说说容器'); return; }
if (!talkingList || talkingList.length === 0) { container.innerHTML = '<div class="talking-empty">暂无说说</div>'; return; }
let html = ''; talkingList.forEach((item, index) => { html += ` <div class="talking-item" style="animation-delay: ${index * 0.1}s"> <div class="talking-content">${escapeHtml(item.content)}</div> <div class="talking-meta"> <div class="talking-date"> <i class="far fa-clock"></i> <span>${formatDate(item.date)}</span> </div> <div class="talking-from"> <i class="fas fa-map-marker-alt"></i> <span>${escapeHtml(item.from || 'Web')}</span> </div> </div> </div> `; });
container.innerHT html; }
function formatDate(dateString) { const date = new Date(dateString); const now = new Date(); const diff = now - date;
if (diff < 60000) { return '刚刚'; }
if (diff < 3600000) { return Math.floor(diff / 60000) + ' 分钟前'; }
if (diff < 86400000) { return Math.floor(diff / 3600000) + ' 小时前'; }
if (diff < 604800000) { return Math.floor(diff / 86400000) + ' 天前'; }
const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); const hours = String(date.getHours()).padStart(2, '0'); const minutes = String(date.getMinutes()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}`; }
function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; }
function showError() { const container = document.getElementById('talking-container'); if (container) { container.innerHTML = ` <div class="talking-error"> <i class="fas fa-exclamation-triangle"></i> <p>加载说说失败,请稍后再试</p> </div> `; } }
|
功能说明:
- 自动检测说说页面并加载数据
- 智能时间格式化(相对时间和绝对时间)
- HTML 转义防止 XSS 攻击
- 错误处理和友好提示
- 渐进式动画效果
第六步:注入 CSS 和 JS
编辑 _config.butterfly.yml,在 inject 部分添加:
1 2 3 4 5
| inject: head: - <link rel="stylesheet" href="/css/talking.css"> bottom: - <script src="/js/talking.js"></script>
|
第七步:添加导航菜单
在 _config.butterfly.yml 的 menu 部分添加说说入口:
1 2 3 4 5 6
| menu: 首页: / || fas fa-home 时间轴: /archives/ || fas fa-archive 标签: /tags/ || fas fa-tags 分类: /categories/ || fas fa-folder-open 说说: /talking/ || fas fa-comments
|
第八步:生成并测试
1 2 3 4 5 6 7 8
| hexo clean
hexo generate
hexo server
|
访问 http://localhost:4000/talking/ 查看效果。
使用说明
发布新说说
编辑 source/_data/talking.yml,在文件开头添加新的说说:
1 2 3
| - content: 这是一条新的说说 🎉 date: 2026-01-08 10:00:00 from: Web
|
然后重新生成网站:
1
| hexo clean && hexo generate
|
时间格式说明
时间显示规则:
- 刚刚:1分钟内
- X 分钟前:1小时内
- X 小时前:24小时内
- X 天前:7天内
- 具体日期:超过7天显示
YYYY-MM-DD HH:mm
自定义样式
你可以修改 talking.css 来自定义样式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| .talking-item { background: #f8f9fa; }
.talking-content { color: #333; }
.talking-item:hover { transform: translateY(-8px); box-shadow: 0 6px 20px rgba(0, 0, 0, 0.2); }
|
进阶功能
1. 添加图片支持
修改数据格式:
1 2 3 4 5 6
| - content: 今天拍到的美丽日落 🌅 date: 2026-01-08 18:30:00 from: iPhone images: - /images/sunset1.jpg - /images/sunset2.jpg
|
修改 talking.js 的渲染函数:
1 2 3 4 5 6 7 8
| if (item.images && item.images.length > 0) { html += '<div class="talking-images">'; item.images.forEach(img => { html += `<img src="${img}" alt="说说图片" class="talking-image">`; }); html += '</div>'; }
|
2. 添加点赞功能
可以集成 LeanCloud 或其他后端服务实现点赞功能。
3. 添加评论功能
说说页面已经支持评论,你可以在每条说说下添加评论区。
4. 添加标签分类
1 2 3 4 5 6
| - content: 学习 Vue 3 的新特性 date: 2026-01-08 10:00:00 from: Web tags: - 前端 - Vue
|
常见问题
Q1: 说说不显示怎么办?
检查步骤:
- 确认
talking.yml 文件存在且格式正确
- 检查浏览器控制台是否有错误
- 确认
talking.json 是否生成(在 public/ 目录下)
- 检查 CSS 和 JS 是否正确注入
Q2: 时间显示不正确?
确保 talking.yml 中的日期格式正确:YYYY-MM-DD HH:mm:ss
Q3: 样式显示异常?
- 清理缓存:
hexo clean
- 检查 CSS 文件路径是否正确
- 确认主题变量是否定义
Q4: 如何批量导入说说?
可以编写脚本从其他平台(如微博、Twitter)导出数据,然后转换为 YAML 格式。
性能优化
1. 分页加载
当说说数量较多时,可以实现分页:
1 2 3 4 5 6 7 8 9
| const pageSize = 10; let currentPage = 1;
function renderPage(data, page) { const start = (page - 1) * pageSize; const end = start + pageSize; const pageData = data.slice(start, end); renderTalking(pageData); }
|
2. 懒加载图片
如果说说包含图片,使用懒加载提升性能:
1
| <img src="placeholder.jpg" data-src="${img}" class="lazyload">
|
3. 缓存优化
利用浏览器缓存减少请求:
1 2 3 4
| const cachedData = localStorage.getItem('talking-cache'); if (cachedData) { renderTalking(JSON.parse(cachedData)); }
|
总结
通过本教程,我们实现了一个完整的说说功能,包括:
✅ 数据管理:使用 YAML 文件管理说说数据
✅ 自动生成:通过 Hexo 生成器自动转换为 JSON
✅ 页面渲染:动态加载和渲染说说列表
✅ 样式设计:精美的卡片式设计和动画效果
✅ 响应式布局:完美适配各种设备
✅ 深色模式:自动适配主题模式
说说功能让博客更加生动有趣,可以记录生活中的点滴,与访客分享日常。你可以根据自己的需求进一步扩展功能,如添加图片、标签、点赞等。
相关文章
💡 提示:如果你觉得这篇教程对你有帮助,欢迎点赞、收藏和分享!有任何问题欢迎在评论区留言。