feat: 优化首页帖子列表并新增右侧栏最新注册组件

首页列表精简 meta 与统计展示,板块色标前移并淡化;有回复时显示最后回复人。右侧栏新增最新注册(4 列头像网格),友链改为标签块并排换行。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-27 18:50:09 +08:00
parent 2208af7070
commit 7cc574f9a1
17 changed files with 594 additions and 113 deletions

View File

@@ -1,9 +1,9 @@
import { useMemo } from 'react';
import { ListTree, MessageCircle, Tags, Link2 } from 'lucide-react';
import { ListTree, MessageCircle, Tags, Link2, UserPlus } from 'lucide-react';
import { useLocation, useSearchParams, useNavigate } from 'react-router-dom';
import { Skeleton } from '@/components/ui/skeleton';
import { Button } from '@/components/ui/button';
import type { AsideWidget, RecentComment, TagCount, User, ForumStats, FriendLink } from '../api/types';
import type { AsideWidget, RecentComment, RecentUser, TagCount, User, ForumStats, FriendLink } from '../api/types';
import type { PostHeading } from '../utils/postHeadings';
import { useSiteBranding } from '../hooks/useSiteBranding';
import { formatShortDateTime, formatTime } from '../utils/content';
@@ -25,6 +25,7 @@ export type PostDetailAside = {
interface Props {
recentComments: RecentComment[];
recentUsers: RecentUser[];
tags?: TagCount[];
tagsLoading?: boolean;
stats?: ForumStats | null;
@@ -53,8 +54,22 @@ function CommentSkeleton() {
);
}
function UserSkeleton() {
return (
<div className="widget-recent-users-grid" aria-busy="true" aria-label="用户加载中">
{Array.from({ length: 8 }, (_, i) => (
<div key={i} className="widget-recent-user-cell widget-recent-user-cell--skeleton">
<Skeleton className="skeleton--recent-user-avatar" />
<Skeleton className="skeleton--recent-user-name" style={{ width: `${48 + (i % 3) * 10}%` }} />
</div>
))}
</div>
);
}
export default function RightPanel({
recentComments,
recentUsers,
tags = [],
tagsLoading = false,
stats = null,
@@ -69,6 +84,7 @@ export default function RightPanel({
const [params] = useSearchParams();
const activeTag = params.get('tag') || '';
const commentList = recentComments?.slice(0, 6) ?? [];
const userList = recentUsers?.slice(0, 8) ?? [];
const friendLinks = (branding.friend_links ?? []).filter(
(l: FriendLink) => l.name?.trim() && l.url?.trim(),
);
@@ -118,7 +134,7 @@ export default function RightPanel({
</Button>
</div>
<div className="widget-card-body">
<div className="widget-card-body widget-card-body--friend-links">
{friendLinks.length === 0 ? (
<div className="widget-empty"></div>
) : (
@@ -126,7 +142,9 @@ export default function RightPanel({
<ul className="widget-friend-links-list">
{friendLinks.slice(0, 8).map((link: FriendLink) => (
<li key={`${link.name}-${link.url}`}>
<a href={link.url} target="_blank" rel="noopener noreferrer">{link.name}</a>
<a href={link.url} target="_blank" rel="noopener noreferrer" title={link.name}>
{link.name}
</a>
</li>
))}
</ul>
@@ -212,6 +230,41 @@ export default function RightPanel({
</div>
</div>
);
case 'recent_users':
return (
<div key="recent_users" className="widget-card widget-card--users">
<div className="widget-card-head">
<UserPlus className="widget-card-icon widget-card-icon--users" aria-hidden />
</div>
<div className="widget-card-body widget-card-body--users">
{loading && userList.length === 0 ? (
<UserSkeleton />
) : userList.length === 0 ? (
<div className="widget-empty"></div>
) : (
<div className="widget-recent-users-grid">
{userList.map(item => (
<UserLink
key={item.id}
user={{ id: item.id, nickname: item.nickname, avatar: item.avatar }}
className="widget-recent-user-cell"
showBadges={false}
title={item.nickname}
>
<span className="widget-recent-user-avatar" aria-hidden>
{item.avatar
? <img src={item.avatar} alt="" loading="lazy" decoding="async" />
: (item.nickname?.[0] || '?')}
</span>
<span className="widget-recent-user-name">{item.nickname}</span>
</UserLink>
))}
</div>
)}
</div>
</div>
);
default:
return null;
}