feat: 实现文章点赞功能及相关API和UI组件

- 新增点赞功能API接口及数据库表结构
- 添加点赞状态管理及UI交互组件
- 实现点赞统计和列表查询功能
- 优化评论组件布局,将点赞按钮加入头部区域
This commit is contained in:
anghunk
2026-01-22 17:40:12 +08:00
parent 83088a3837
commit 103e0ceb25
9 changed files with 675 additions and 20 deletions

View File

@@ -0,0 +1,37 @@
import type { Context } from 'hono';
import type { Bindings } from '../../bindings';
type LikeStatsItem = {
page_slug: string;
page_title: string | null;
page_url: string | null;
likes: number;
};
export const getLikeStats = async (c: Context<{ Bindings: Bindings }>) => {
try {
await c.env.CWD_DB.prepare(
'CREATE TABLE IF NOT EXISTS Likes (id INTEGER PRIMARY KEY AUTOINCREMENT, page_slug TEXT NOT NULL, user_id TEXT NOT NULL, created_at INTEGER NOT NULL, UNIQUE(page_slug, user_id))'
).run();
await c.env.CWD_DB.prepare(
'CREATE TABLE IF NOT EXISTS page_stats (id INTEGER PRIMARY KEY AUTOINCREMENT, post_slug TEXT UNIQUE NOT NULL, post_title TEXT, post_url TEXT, pv INTEGER NOT NULL DEFAULT 0, last_visit_at INTEGER, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL)'
).run();
const { results } = await c.env.CWD_DB.prepare(
'SELECT l.page_slug, COALESCE(p.post_title, NULL) AS page_title, COALESCE(p.post_url, NULL) AS page_url, COUNT(*) AS likes FROM Likes l LEFT JOIN page_stats p ON p.post_slug = l.page_slug GROUP BY l.page_slug, p.post_title, p.post_url ORDER BY likes DESC LIMIT 50'
).all<LikeStatsItem>();
const items = results.map((row) => ({
pageSlug: row.page_slug,
pageTitle: row.page_title,
pageUrl: row.page_url,
likes: row.likes
}));
return c.json({ items });
} catch (e: any) {
return c.json({ message: e.message || '获取点赞统计失败' }, 500);
}
};

View File

@@ -0,0 +1,95 @@
import type { Context } from 'hono';
import type { Bindings } from '../../bindings';
type LikeItem = {
id: number;
page_slug: string;
user_id: string;
created_at: number;
};
export const listLikes = async (c: Context<{ Bindings: Bindings }>) => {
try {
const page = parseInt(c.req.query('page') || '1', 10) || 1;
const limit = 20;
const offset = (page - 1) * limit;
const rawPageSlug = c.req.query('page_slug') || c.req.query('pageSlug') || '';
const pageSlug = rawPageSlug.trim();
const rawUserId = c.req.query('user_id') || c.req.query('userId') || '';
const userId = rawUserId.trim();
const rawStart = c.req.query('start') || '';
const rawEnd = c.req.query('end') || '';
const whereSql: string[] = [];
const params: (string | number)[] = [];
if (pageSlug) {
whereSql.push('page_slug = ?');
params.push(pageSlug);
}
if (userId) {
whereSql.push('user_id = ?');
params.push(userId);
}
if (rawStart) {
const startTs = Number(rawStart);
if (Number.isFinite(startTs)) {
whereSql.push('created_at >= ?');
params.push(startTs);
}
}
if (rawEnd) {
const endTs = Number(rawEnd);
if (Number.isFinite(endTs)) {
whereSql.push('created_at <= ?');
params.push(endTs);
}
}
const whereClause = whereSql.length ? `WHERE ${whereSql.join(' AND ')}` : '';
await c.env.CWD_DB.prepare(
'CREATE TABLE IF NOT EXISTS Likes (id INTEGER PRIMARY KEY AUTOINCREMENT, page_slug TEXT NOT NULL, user_id TEXT NOT NULL, created_at INTEGER NOT NULL, UNIQUE(page_slug, user_id))'
).run();
const totalRow = await c.env.CWD_DB.prepare(
`SELECT COUNT(*) AS count FROM Likes ${whereClause}`
)
.bind(...params)
.first<{ count: number }>();
const { results } = await c.env.CWD_DB.prepare(
`SELECT id, page_slug, user_id, created_at FROM Likes ${whereClause} ORDER BY created_at DESC LIMIT ? OFFSET ?`
)
.bind(...params, limit, offset)
.all<LikeItem>();
const data = results.map((row) => ({
id: row.id,
pageSlug: row.page_slug,
userId: row.user_id,
createdAt: row.created_at
}));
const totalCount = totalRow?.count || 0;
const totalPages = Math.max(1, Math.ceil(totalCount / limit));
return c.json({
data,
pagination: {
page,
limit,
total: totalPages
}
});
} catch (e: any) {
return c.json({ message: e.message || '获取点赞记录失败' }, 500);
}
};