Li's AI Summary
Li's AI
文章介绍了如何通过WalineAPI获取网站最新评论数据并展示在组件中,首先,浏览了Waline的官方接口文档,发现可以通过API返回最新评论数据,然后,编写了一个适用于当前主题的评论展示组件,并在GitHub上开源了代码片段,最后,添加了必要的文件内容,包括导入RecentComments组件和定义组件结构。
本摘要由AI生成,仅供参考,内容准确性请以原文为准。
起因#
浏览 Waline的官方接口文档 ↗ 时发现通过 /api/comment?type=recent 接口能返回网站最新评论数据,于是“奴役”AI给我写了一个适用当前主题的评论展示组件,目前没出啥大差错🤣
引入#
代码片段Gist开源地址:Recent Comments for Astro-Theme-Pure ↗
recentcomment.ts文件#
在 src/plugins 目录下新建 recentcomment.ts 文件并填入以下内容(注意需要替换的部分):
recentcomment.ts
/**
* 获取并处理最新评论数据
* 通过Waline API获取最新评论并格式化显示
*/
// 定义评论数据接口
interface WalineComment {
nick: string // 评论者昵称
comment: string // 评论内容
url: string // 评论页面URL
avatar: string // 头像URL
time: number // 评论时间戳
like?: number // 点赞数
addr?: string // 地址
}
// 定义API响应接口
interface WalineResponse {
data: WalineComment[]
}
/**
* 获取最新评论数据
* @param limit 获取评论数量限制
* @returns 格式化后的评论数据
*/
export async function fetchRecentComments(limit: number = 5): Promise<WalineComment[]> {
try {
// <Waline后端地址> 请将该部分替换为你自己搭建的Waline后端服务地址
const response = await fetch('https://<Waline后端地址>/api/comment?type=recent')
if (!response.ok) {
throw new Error(`获取评论失败: ${response.status}`)
}
const data: WalineResponse = await response.json()
return data.data.slice(0, limit)
} catch (error) {
console.error('获取最新评论出错:', error)
return []
}
}
/**
* 格式化评论时间
* @param timestamp 时间戳
* @returns 格式化后的时间字符串
*/
export function formatCommentTime(timestamp: number): string {
const date = new Date(timestamp)
const now = new Date()
const diff = now.getTime() - date.getTime()
// 一分钟内
if (diff < 60 * 1000) {
return '刚刚'
}
// 一小时内
if (diff < 60 * 60 * 1000) {
return `${Math.floor(diff / (60 * 1000))}分钟前`
}
// 一天内
if (diff < 24 * 60 * 60 * 1000) {
return `${Math.floor(diff / (60 * 60 * 1000))}小时前`
}
// 一周内
if (diff < 7 * 24 * 60 * 60 * 1000) {
return `${Math.floor(diff / (24 * 60 * 60 * 1000))}天前`
}
// 其他情况显示具体日期
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`
}
/**
* 清理HTML标签
* @param html HTML字符串
* @returns 清理后的纯文本
*/
export function stripHtml(html: string): string {
// 使用正则表达式替代DOM操作,可在服务器端运行
return html.replace(/<[^>]*>/g, '')
}
/**
* 截断文本
* @param text 原始文本
* @param length 最大长度
* @returns 截断后的文本
*/
export function truncateText(text: string, length: number = 100): string {
if (text.length <= length) return text
return text.substring(0, length) + '...'
}tsrc.css文件#
在 src/assets/styles 目录下新建 rc.css 文件并填入以下内容:
rc.css
/* 最新评论组件样式 */
.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;
}
/* 响应式设计 */
@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;
}
}cssRecentComments.astro文件#
- 在目录下
src/components目录下新建RecentComments.astro文件并填入以下内容:
RecentComments.astro
---
import { Icon } from 'astro-pure/user'
import {
fetchRecentComments,
formatCommentTime,
stripHtml,
truncateText
} from '@/plugins/recentcomment'
// 获取最新评论数据
const comments = await fetchRecentComments(5)
---
<div class='recent-comments'>
<div class='recent-comments-title'>
<Icon name='list' />
<span>最新评论</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'>
查看详情 →
</a>
</div>
</div>
))
) : (
<div class='comment-empty'>暂无评论数据</div>
)
}
</div>
</div>
<script>
// 客户端清理HTML标签
document.addEventListener('astro:page-load', () => {
const commentTexts = document.querySelectorAll('.comment-text')
commentTexts.forEach((element) => {
// 确保链接在新窗口打开
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>astro使用#
在Pure主题主页中使用#
增添 src/pages/index.astro 文件内容如下:
index.astro
---
import RecentComments from '@/components/RecentComments.astro' //引入组件
---
<PageLayout meta={{ title: '首页' }} 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'
>
<!--省略中间代码-->
<div class='md:w-1/3 md:mt-0'>
<RecentComments />
</div>
<!--省略此后代码-->
</div>
<Quote class='mt-12' />
</main>
</PageLayout>astro最小化使用#
---
import RecentComments from '@/components/RecentComments.astro'
---
<RecentComments />astro