Hiển thị bình luận mới nhất thông qua API Waline

1072 từ
5 phút
Hiển thị bình luận mới nhất thông qua API Waline

Nguồn API#

Khi xem tài liệu API chính thức của Waline, tôi phát hiện ra rằng /api/comment?type=recentAPI có thể trả về dữ liệu bình luận mới nhất từ trang web. Vì vậy, tôi đã “thuần hóa” AI để viết một thành phần hiển thị bình luận phù hợp với chủ đề hiện tại, và cho đến nay, nó vẫn chưa gặp phải bất kỳ vấn đề lớn nào.

Tệp recentcomment.ts#

src/plugins Tạo một tệp mới trong thư mục recentcomment.ts và nhập nội dung sau ( lưu ý những phần cần thay thế ):

recentcomment.ts
/**
* Lấy và xử lý dữ liệu bình luận mới nhất
* Lấy bình luận mới nhất thông qua Waline API và định dạng hiển thị
*/
// Định nghĩa interface cho dữ liệu bình luận
interface WalineComment {
nick: string; // Tên hiệu người bình luận
comment: string; // Nội dung bình luận
url: string; // URL trang bình luận
avatar: string; // URL avatar
time: number; // Timestamp thời gian bình luận
like?: number; // Số lượt thích
addr?: string; // Địa chỉ
}
// Định nghĩa interface cho phản hồi API
interface WalineResponse {
data: WalineComment[];
}
/**
* Lấy dữ liệu bình luận mới nhất
* @param limit Giới hạn số lượng bình luận
* @returns Dữ liệu bình luận đã được định dạng
*/
export async function fetchRecentComments(limit: number = 5): Promise<WalineComment[]> {
try {
// Địa chỉ backend Waline Hãy thay thế phần này bằng địa chỉ dịch vụ backend Waline bạn <!-- [!code highlight:2] -->
const response = await fetch('https://<Waline Domain của bạn>/api/comment?type=recent');
if (!response.ok) {
throw new Error(`Lấy bình luận thất bại: ${response.status}`);
}
const data: WalineResponse = await response.json();
return data.data.slice(0, limit);
} catch (error) {
console.error('Lỗi khi lấy bình luận mới nhất:', error);
return [];
}
}
/**
* Định dạng thời gian bình luận
* @param timestamp Timestamp
* @returns Chuỗi thời gian đã định dạng
*/
export function formatCommentTime(timestamp: number): string {
const date = new Date(timestamp);
const now = new Date();
const diff = now.getTime() - date.getTime();
// Trong vòng một phút
if (diff < 60 * 1000) {
return 'Vừa xong';
}
// Trong vòng một giờ
if (diff < 60 * 60 * 1000) {
return `${Math.floor(diff / (60 * 1000))} phút trước`;
}
// Trong vòng một ngày
if (diff < 24 * 60 * 60 * 1000) {
return `${Math.floor(diff / (60 * 60 * 1000))} giờ trước`;
}
// Trong vòng một tuần
if (diff < 7 * 24 * 60 * 60 * 1000) {
return `${Math.floor(diff / (24 * 60 * 60 * 1000))} ngày trước`;
}
// Các trường hợp khác hiển thị ngày cụ thể
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
}
/**
* Làm sạch thẻ HTML
* @param html Chuỗi HTML
* @returns Văn bản thuần đã được làm sạch
*/
export function stripHtml(html: string): string {
// Sử dụng biểu thức chính quy thay thế thao tác DOM, có thể chạy ở phía server
return html.replace(/<[^>]*>/g, '');
}
/**
* Cắt ngắn văn bản
* @param text Văn bản gốc
* @param length Độ dài tối đa
* @returns Văn bản đã được cắt ngắn
*/
export function truncateText(text: string, length: number = 100): string {
if (text.length <= length) return text;
return text.substring(0, length) + '...';
}

Tệp rc.css#

src/assets/styles Tạo một tệp mới trong thư mục rc.css và điền nội dung sau vào:

rc.css
/* Kiểu thành phần bình luận mới nhất */
.recent-comments {
background-color: hsl(var(--background) / var(--un-bg-opacity, 1));
border: 1px solid hsl(var(--border) / var(--un-border-opacity, 1));
border-radius: var(--radius);
padding: 1rem;
margin-bottom: 1.5rem;
margin-top: 0;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
}
.recent-comments-title {
font-size: 1.2rem;
font-weight: 500;
margin-bottom: 1rem;
color: hsl(var(--foreground) / var(--un-text-opacity, 1));
display: flex;
align-items: center;
gap: 0.5rem;
}
.recent-comments-title svg {
width: 1.2rem;
height: 1.2rem;
}
.comment-list {
display: flex;
flex-direction: column;
gap: 1rem;
}
.comment-item {
display: flex;
gap: 0.75rem;
padding-bottom: 1rem;
border-bottom: 1px solid hsl(var(--border) / 0.5);
}
.comment-item:last-child {
border-bottom: none;
padding-bottom: 0;
}
.comment-avatar {
width: 2.5rem;
height: 2.5rem;
border-radius: 50%;
overflow: hidden;
flex-shrink: 0;
}
.comment-avatar img {
width: 100%;
height: 100%;
object-fit: cover;
}
.comment-content {
flex: 1;
min-width: 0;
}
.comment-meta {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.25rem;
}
.comment-author {
font-weight: 500;
color: hsl(var(--foreground) / var(--un-text-opacity, 1));
font-size: 0.9rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.comment-time {
font-size: 0.8rem;
color: hsl(var(--muted-foreground) / var(--un-text-opacity, 1));
}
.comment-text {
font-size: 0.9rem;
color: hsl(var(--muted-foreground) / var(--un-text-opacity, 1));
line-height: 1.5;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
word-break: break-word;
}
.comment-link {
display: block;
font-size: 0.8rem;
color: hsl(var(--primary) / var(--un-text-opacity, 1));
margin-top: 0.25rem;
text-decoration: none;
transition: opacity 0.2s;
}
.comment-link:hover {
opacity: 0.8;
}
.comment-empty {
text-align: center;
padding: 1rem 0;
color: hsl(var(--muted-foreground) / var(--un-text-opacity, 1));
font-size: 0.9rem;
}
/* Thiết kế đáp ứng */
@media (max-width: 768px) {
.comment-avatar {
width: 2rem;
height: 2rem;
}
.comment-author {
font-size: 0.85rem;
}
.comment-time {
font-size: 0.75rem;
}
.comment-text {
font-size: 0.85rem;
-webkit-line-clamp: 2;
}
}

Tệp RecentComments.astro#

src/components Tạo một tệp mới trong thư mục RecentComments.astro và nhập nội dung sau:

RecentComments.astro
---
import { fetchRecentComments, formatCommentTime, stripHtml, truncateText } from '@/plugins/recentcomment';
import { Icon } from 'astro/user';
// Lấy dữ liệu bình luận mới nhất
const comments = await fetchRecentComments(5);
---
<div class="recent-comments">
<div class="recent-comments-title">
<Icon name="list" />
<span>Bình luận mới nhất</span>
</div>
<div class="comment-list">
{comments.length > 0 ? (
comments.map((comment) => (
<div class="comment-item">
<div class="comment-avatar">
<img src={comment.avatar} alt={comment.nick} loading="lazy" />
</div>
<div class="comment-content">
<div class="comment-meta">
<span class="comment-author">{comment.nick}</span>
<span class="comment-time">{formatCommentTime(comment.time)}</span>
</div>
<div class="comment-text" set:html={truncateText(stripHtml(comment.comment), 100)} />
<a href={comment.url ? comment.url.replace(/\/$/, '') : '#'} class="comment-link">Xem chi tiết →</a>
</div>
</div>
))
) : (
<div class="comment-empty">Chưa có dữ liệu bình luận</div>
)}
</div>
</div>
<script>
// Dọn dẹp thẻ HTML phía client
document.addEventListener('astro:page-load', () => {
const commentTexts = document.querySelectorAll('.comment-text');
commentTexts.forEach(element => {
// Đảm bảo liên kết mở trong cửa sổ mới
const links = element.querySelectorAll('a');
links.forEach(link => {
link.setAttribute('target', '_blank');
link.setAttribute('rel', 'noopener noreferrer');
});
});
});
</script>
<style is:global>
@import '@/assets/styles/rc.css';
</style>

Sử dụng#

Thêm src/pages/index.astro nội dung sau vào tệp:

index.astro
---
import RecentComments from '@/components/RecentComments.astro' //Nhập thành phần # [!code ++]
---
<PageLayout meta={{ title: 'Home' }} highlightColor='#FFF8DC'>
<main class='flex w-full flex-col items-center'>
<div id='content' class='animate flex flex-col md:flex-row gap-y-10 md:gap-x-6 md:w-4/5 lg:w-5/6'>
<!--RecentComments-->
<div class='md:w-1/3 md:mt-0'> # [!code ++]
<RecentComments /> # [!code ++]
</div> # [!code ++]
<!--RecentComments-->
</div>
<Quote class='mt-12' />
</main>
</PageLayout>

Giảm thiểu việc sử dụng#

---
import RecentComments from '@/components/RecentComments.astro'
---
<RecentComments />

Ủng Hộ & Chia Sẻ

Nếu bài viết này giúp ích cho bạn, hãy chia sẻ hoặc ủng hộ nhé!

Tài Trợ
Hiển thị bình luận mới nhất thông qua API Waline
https://thinhem.id.vn/posts/hien-thi-binh-luan-moi-nhat/
Tác Giả
Duy Thịnh
Đăng lúc
2025-11-21
Giấy Phép
CC BY-NC-SA 4.0

Bình Luận

Profile Image of the Author
Duy Thịnh
Hello, I'm Thinhem.
Chào mừng đến với blog của tôi 🎉
Đây là nơi mình sẽ chia sẻ những album, cách sắp xếp và tóm tắt tài liệu hàng ngày và tâm sự của mình, hy vọng chúng sẽ hữu ích cho các bạn :)💖
Âm Nhạc
Ảnh Bìa

Âm Nhạc

Không có bài đang phát

0:00 0:00
Không có lời bài hát
Bạn bè
Bình luận mới nhất
Đang tải...
Danh Mục
Thẻ
Thống Kê Trang Web
Bài Viết
26
Danh Mục
9
Thẻ
83
Tổng Số Từ
46,711
Ngày Hoạt Động
0 ngày
Hoạt Động Gần Nhất
0 ngày trước

Mục Lục