前言
Waline
评论本身支持文章反应,只是样式不太好看。
giscus 评论文章反应的样式个人还是蛮喜欢的,但对于访客其实不算友好,需要有 Github
,非程序员不一定有,另外必须要登录才能评论,尝试下Hexo中仿制做一个类似的文章反应。
有时候看博文不想要评论或者不知道评论什么,使用这个可以快速便捷的表达。
结构
- 前端:
js
、html
、css
- 服务器:
Nodejs
、Nginx
- 数据库:
Leancloud
前端发送请求,服务器路由处理请求,从数据库中读取数据返回
其实也可以不用服务器作为中转,直接使用 Leancloud
的 API 执行请求,但不是很推荐,因为 KEY
会暴露在前端中
前端
html
写一组表情
<div id="reactions">
<button class="reaction" data-reaction="up">👍 <span class="count">0</span></button>
<button class="reaction" data-reaction="love">❤️ <span class="count">0</span></button>
<button class="reaction" data-reaction="emm">😑 <span class="count">0</span></button>
<button class="reaction" data-reaction="down">👎 <span class="count">0</span></button>
<button class="reaction" data-reaction="see">👀 <span class="count">0</span></button>
</div>
js
绑定点击事件
采取页面URL作为唯一值存储数据库,以后都是通过URL进行判断,修改路径的话需要修改数据库,否则值全部归0
采用localStorage
存储用户点赞状态
需要修改的地方:
- 15行:修改为自己服务器域名或者
ip
- 41行:修改为自己服务器域名或者
ip
document.addEventListener("DOMContentLoaded", function () {
const reactions = document.querySelectorAll(".reaction");
const postId = window.location.pathname; // 获取当前页面的路径作为postId
// 页面加载时检查并设置点赞状态
reactions.forEach((reaction) => {
const reactionType = reaction.getAttribute("data-reaction");
const key = `liked_${postId}_${reactionType}`;
if (localStorage.getItem(key)) {
reaction.classList.add("liked");
}
});
// 获取点赞数据
fetch(`http://xxx.xxx.xxx.xxx/like?postId=${encodeURIComponent(postId)}`, {
method: "GET",
})
.then((response) => {
if (!response.ok) {
throw new Error("Network response was not ok");
}
return response.json();
})
.then((data) => {
updateReactionCounts(data);
})
.catch((error) => {
console.error("Error fetching reaction data:", error);
});
reactions.forEach((reaction) => {
reaction.addEventListener("click", function () {
const reactionType = this.getAttribute("data-reaction");
const key = `liked_${postId}_${reactionType}`;
if (localStorage.getItem(key)) {
alert("您已经点赞过了!");
return;
}
fetch("http://xxx.xxx.xxx.xxx/like", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ postId, reactionType }),
})
.then((response) => {
if (!response.ok) {
throw new Error("Network response was not ok");
}
return response.json();
})
.then((data) => {
console.log(data);
updateReactionCount(reactionType, 1);
localStorage.setItem(key, true);
this.classList.add("liked");
})
.catch((error) => {
console.error("Error:", error);
});
});
});
// 更新点赞计数的函数
function updateReactionCount(reactionType, increment) {
const reactionCountElement = document.querySelector(
`.reaction[data-reaction="${reactionType}"] .count`
);
if (reactionCountElement) {
const currentCount = parseInt(reactionCountElement.textContent, 10) || 0;
reactionCountElement.textContent = currentCount + increment;
}
}
// 更新点赞计数的函数,用于初始化页面加载时的计数
function updateReactionCounts(reactions) {
reactions.forEach((reaction) => {
updateReactionCount(reaction.reactionType, reaction.count);
});
}
});
修改样式(根据自己需要进行更改,目前是契合个人主题)
/* reactions.css */
#reactions {
display: flex;
gap: 10px;
justify-content: center;
margin: 25px;
width:100%;
}
.reaction {
cursor: pointer;
padding: 5px;
border: none;
background-color: #f0f0f0;
border-radius: 5px;
}
.reaction:hover {
background-color: #e0e0e0;
}
.reaction.liked:hover {
background-color: #afd1ff;
}
html.skin-dark .reaction{
background-color: rgb(82 82 82);
}
/* 已点击样式 */
.reaction.liked,
html.skin-dark .reaction.liked {
background-color: #d0e4fe;
color: #000;
}
数据库
创建一个应用后,点击「结构化数据」-> 「创建 Class」
新建一个 Reactions
类
点击添加列,新建三个字段
postId
:类型选择“字符串”,用于存储文章的唯一 ID。reactionType
:类型选择“字符串”,用于存储点赞类型,如up
、love
等。count
:类型选择“数字”,用于存储点赞的数量,初始值设置为 0。
注:postId
虽然是作为唯一值,但建立字段的时候,不要勾选唯一值,否则会导致后期计数时候出错
完成字段建立后,记录 API KEY ,后续要用到
服务器
写一个nodejs
脚本处理作为路由,处理请求
然后使用Nginx对这个路由进行反代即可
路由
用于处理前端发送过来的请求
先安装依赖包
npm install express
npm install leancloud-storage
npm install cors
express
:框架leancloud-storage
:Leancloud的SDK,用于操作数据库cors
:配置CORS以允许跨域请求,调试啥的也要用到
下面是js
代码,需要修改的地方有:
- 10行:添加自己的域名,进行跨域请求放行
- 21-23行:修改为自己
Leancloud
的API Key
const express = require('express');
const cors = require('cors');
const AV = require('leancloud-storage');
const app = express();
const port = 3000;
// 配置CORS
const corsOptions = {
origin: ['http://localhost:4000'],
optionsSuccessStatus: 200
};
app.use(cors(corsOptions));
app.use(express.json());
// 配置Leancloud
AV.init({
appId: '',
appKey: '',
serverURL: 'https://bfrlyu2d.lc-cn-n1-shared.com'
});
// 获取点赞数据的路由
app.get('/like', (req, res) => {
const { postId } = req.query;
if (!postId) {
return res.status(400).json({ error: 'Missing postId' });
}
const query = new AV.Query('Reactions');
query.equalTo('postId', postId);
query.find().then(results => {
const reactionData = results.map(result => {
return {
reactionType: result.get('reactionType'),
count: result.get('count')
};
});
res.json(reactionData);
}).catch(error => {
console.error('Query error:', error);
res.status(500).json({ error: '查询失败' });
});
});
// 处理点赞的路由
app.post('/like', (req, res) => {
const { postId, reactionType } = req.body;
if (!postId || !reactionType) {
return res.status(400).json({ error: 'Missing postId or reactionType' });
}
const query = new AV.Query('Reactions');
query.equalTo('postId', postId);
query.equalTo('reactionType', reactionType);
query.find().then(results => {
if (results.length > 0) {
// 检查是否已经点赞
if (results[0].get('count') > 0) {
return res.status(400).json({ error: 'Already liked' });
}
results[0].increment('count', 1);
return results[0].save();
} else {
// 没有找到匹配的记录,创建新的记录
const reaction = new AV.Object('Reactions');
reaction.set('postId', postId);
reaction.set('reactionType', reactionType);
reaction.set('count', 1);
return reaction.save();
}
}).then(() => {
res.json({ message: '点赞成功' });
}).catch(error => {
res.status(500).json({ error: '点赞失败' });
});
});
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
保存成like.js
,使用nodejs运行即可
node like
反代
使用Nginx反代,第3行修改为自己域名或者服务器 ip
server {
listen 80;
server_name x.x.x.x;
location /like {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}