鉴权只认 cookie、SEO 只走 Next、sort 与推荐对齐;删除 Bearer、Go sitemap、post_type 回填等冗余路径。 Co-authored-by: Cursor <cursoragent@cursor.com>
308 lines
9.6 KiB
TypeScript
308 lines
9.6 KiB
TypeScript
import Link from "next/link";
|
|
import {
|
|
Flame,
|
|
Users,
|
|
MessageCircle,
|
|
Heart,
|
|
Shield,
|
|
LayoutGrid,
|
|
PenLine,
|
|
PenSquare,
|
|
BarChart3,
|
|
TrendingUp,
|
|
} from "lucide-react";
|
|
import type {
|
|
PostListItem,
|
|
ActiveUser,
|
|
BoardWithCount,
|
|
BoardModerator,
|
|
BoardSidebarStats,
|
|
CheckinStatus,
|
|
} from "@/lib/api";
|
|
import Avatar from "@/components/Avatar";
|
|
import { PulseCheckin } from "./CheckinCard";
|
|
import { formatCompact } from "@/lib/format";
|
|
|
|
/* ---------- 1. 板块管理员 ---------- */
|
|
|
|
function Moderators({ items }: { items: BoardModerator[] }) {
|
|
return (
|
|
<section className="j13-module" aria-label="板块管理员">
|
|
<p className="j13-module-title mb-2">
|
|
<Shield size={15} style={{ color: "var(--accent)" }} /> 板块管理员
|
|
</p>
|
|
{items.length === 0 ? (
|
|
<p className="meta px-0.5 py-1.5">暂无板块管理员</p>
|
|
) : (
|
|
<ul className="space-y-0.5">
|
|
{items.map((u) => {
|
|
const name = u.nickname || u.username;
|
|
return (
|
|
<li key={u.id}>
|
|
<Link href={`/u/${u.id}`} className="j13-raillink" data-tip={name}>
|
|
<Avatar name={name} src={u.avatar || undefined} size={30} />
|
|
<span className="min-w-0 flex-1 truncate text-[13px] font-medium" style={{ color: "var(--ink)" }}>
|
|
{name}
|
|
</span>
|
|
</Link>
|
|
</li>
|
|
);
|
|
})}
|
|
</ul>
|
|
)}
|
|
</section>
|
|
);
|
|
}
|
|
|
|
/* ---------- 2. 板块概况 ---------- */
|
|
|
|
function BoardIntro({ board }: { board: BoardWithCount }) {
|
|
return (
|
|
<section className="j13-module" aria-label="板块概况">
|
|
<p className="j13-module-title mb-2">
|
|
<LayoutGrid size={15} style={{ color: "var(--accent)" }} /> 板块概况
|
|
</p>
|
|
<p className="text-[15px] font-medium leading-snug" style={{ color: "var(--ink)" }}>
|
|
{board.name}
|
|
</p>
|
|
{board.description ? (
|
|
<p className="meta mt-1.5 text-[12.5px] leading-relaxed line-clamp-3">{board.description}</p>
|
|
) : (
|
|
<p className="meta mt-1.5">暂无板块介绍</p>
|
|
)}
|
|
<p className="meta mt-2 tabular-nums">讨论 {formatCompact(board.post_count)} 篇</p>
|
|
<div className="mt-3">
|
|
<Link href={`/compose?board=${board.id}`} className="btn btn-primary btn-sm w-full justify-center">
|
|
<PenSquare size={14} /> 在此发帖
|
|
</Link>
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|
|
|
|
/* ---------- 3. 板块数据 + 签到 ---------- */
|
|
|
|
function BoardPulse({
|
|
stats,
|
|
checkin,
|
|
}: {
|
|
stats: BoardSidebarStats;
|
|
checkin?: CheckinStatus | null;
|
|
}) {
|
|
const cells = [
|
|
{
|
|
icon: <PenLine size={12.5} />,
|
|
label: "话题总数",
|
|
value: formatCompact(stats.post_count),
|
|
foot: `+${formatCompact(stats.today_posts)} 今日`,
|
|
up: true,
|
|
},
|
|
{
|
|
icon: <TrendingUp size={12.5} />,
|
|
label: "今日新帖",
|
|
value: formatCompact(stats.today_posts),
|
|
foot: "本日新增",
|
|
up: stats.today_posts > 0,
|
|
},
|
|
{
|
|
icon: <MessageCircle size={12.5} />,
|
|
label: "回复总数",
|
|
value: formatCompact(stats.comment_count),
|
|
foot: `+${formatCompact(stats.today_comments)} 今日`,
|
|
up: true,
|
|
},
|
|
{
|
|
icon: <Heart size={12.5} />,
|
|
label: "今日回复",
|
|
value: formatCompact(stats.today_comments),
|
|
foot: "本日新增",
|
|
up: stats.today_comments > 0,
|
|
},
|
|
];
|
|
return (
|
|
<section className="j13-module" aria-label="板块数据">
|
|
<p className="j13-module-title">
|
|
<BarChart3 size={15} style={{ color: "var(--accent)" }} /> 板块数据
|
|
</p>
|
|
<div className="j13-pulse">
|
|
{cells.map((c) => (
|
|
<div key={c.label} className="j13-pulse-cell">
|
|
<p className="j13-pulse-lab">
|
|
<span style={{ color: "var(--accent)" }} aria-hidden="true">
|
|
{c.icon}
|
|
</span>
|
|
{c.label}
|
|
</p>
|
|
<p className="j13-pulse-num">{c.value}</p>
|
|
<p className={`j13-pulse-foot${c.up ? " is-up" : ""}`}>
|
|
{c.up && <TrendingUp size={10.5} aria-hidden="true" />}
|
|
{c.foot}
|
|
</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
<div className="j13-pulse-checkin">
|
|
<PulseCheckin initial={checkin} />
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|
|
|
|
/* ---------- 4. 本板热门 ---------- */
|
|
|
|
function BoardHot({ posts }: { posts: PostListItem[] }) {
|
|
return (
|
|
<section className="j13-module" aria-label="本板热门">
|
|
<p className="j13-module-title mb-1.5">
|
|
<Flame size={15} style={{ color: "var(--clay)" }} /> 本板热门
|
|
</p>
|
|
{posts.length === 0 ? (
|
|
<p className="meta px-1.5 py-3">本板还没有热门讨论</p>
|
|
) : (
|
|
<ol className="j13-ranklist">
|
|
{posts.map((p, i) => (
|
|
<li key={p.id}>
|
|
<Link href={`/post/${p.id}`} className="j13-rank group" data-tip={p.title}>
|
|
<span className="j13-rank-no">{i + 1}</span>
|
|
<span className="min-w-0 flex-1">
|
|
<span
|
|
className="block truncate text-[13px] font-medium leading-snug transition-colors group-hover:text-[var(--accent)]"
|
|
style={{ color: "var(--ink)" }}
|
|
>
|
|
{p.title}
|
|
</span>
|
|
<span className="mt-1 flex items-center gap-2.5 meta text-[11.5px]">
|
|
<span className="inline-flex items-center gap-1">
|
|
<MessageCircle size={11.5} /> {p.comment_count}
|
|
</span>
|
|
<span className="inline-flex items-center gap-1">
|
|
<Heart size={11.5} /> {p.like_count}
|
|
</span>
|
|
</span>
|
|
</span>
|
|
</Link>
|
|
</li>
|
|
))}
|
|
</ol>
|
|
)}
|
|
</section>
|
|
);
|
|
}
|
|
|
|
/* ---------- 5. 本板活跃 ---------- */
|
|
|
|
function BoardActive({ users }: { users: ActiveUser[] }) {
|
|
return (
|
|
<section className="j13-module" aria-label="本板活跃成员">
|
|
<p className="j13-module-title mb-2">
|
|
<Users size={15} style={{ color: "var(--accent)" }} /> 本板活跃
|
|
<span
|
|
className="ml-auto font-normal normal-case tracking-normal text-[11px]"
|
|
style={{ color: "var(--ink-3)" }}
|
|
>
|
|
本周
|
|
</span>
|
|
</p>
|
|
{users.length === 0 ? (
|
|
<p className="meta px-1.5 py-2">本周本板还没有人互动</p>
|
|
) : (
|
|
<ul className="space-y-0.5">
|
|
{users.map((u) => {
|
|
const name = u.nickname || u.username;
|
|
return (
|
|
<li key={u.id}>
|
|
<Link href={`/u/${u.id}`} className="j13-raillink" data-tip={name}>
|
|
<Avatar name={name} src={u.avatar || undefined} size={30} />
|
|
<span className="flex-1 min-w-0">
|
|
<span
|
|
className="block truncate text-[13px] font-medium leading-tight"
|
|
style={{ color: "var(--ink)" }}
|
|
>
|
|
{name}
|
|
</span>
|
|
<span className="mt-px flex items-center gap-2.5 text-[11px] meta">
|
|
<span className="inline-flex items-center gap-1">
|
|
<PenLine size={10.5} /> 发帖 {u.post_count}
|
|
</span>
|
|
<span className="inline-flex items-center gap-1">
|
|
<MessageCircle size={10.5} /> 回复 {u.comment_count}
|
|
</span>
|
|
</span>
|
|
</span>
|
|
<span
|
|
className="shrink-0 text-[13px] tabular-nums"
|
|
style={{ color: "var(--accent)" }}
|
|
aria-label={`本周共 ${u.post_count + u.comment_count} 次互动`}
|
|
>
|
|
{u.post_count + u.comment_count}
|
|
</span>
|
|
</Link>
|
|
</li>
|
|
);
|
|
})}
|
|
</ul>
|
|
)}
|
|
</section>
|
|
);
|
|
}
|
|
|
|
/* ---------- 装配 ---------- */
|
|
|
|
type BoardPanelsProps = {
|
|
board: BoardWithCount;
|
|
moderators: BoardModerator[];
|
|
stats: BoardSidebarStats;
|
|
hot: PostListItem[];
|
|
activeUsers: ActiveUser[];
|
|
checkin?: CheckinStatus | null;
|
|
};
|
|
|
|
function BoardPanelsBody(props: BoardPanelsProps) {
|
|
return (
|
|
<>
|
|
<Moderators items={props.moderators} />
|
|
<BoardIntro board={props.board} />
|
|
<BoardPulse stats={props.stats} checkin={props.checkin} />
|
|
<BoardHot posts={props.hot} />
|
|
<BoardActive users={props.activeUsers} />
|
|
</>
|
|
);
|
|
}
|
|
|
|
export function BoardPanels(props: BoardPanelsProps) {
|
|
return (
|
|
<aside className="hidden w-[280px] shrink-0 lg:block xl:w-[300px]" aria-label="板块面板">
|
|
<div className="sticky top-24 space-y-4">
|
|
<BoardPanelsBody {...props} />
|
|
</div>
|
|
</aside>
|
|
);
|
|
}
|
|
|
|
export function BoardPanelsMobile(props: BoardPanelsProps) {
|
|
return (
|
|
<div className="space-y-4 lg:hidden" aria-label="板块面板">
|
|
<BoardPanelsBody {...props} />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
/** 板块不存在时的简卡 */
|
|
export function BoardMissingPanel() {
|
|
return (
|
|
<aside className="hidden w-[280px] shrink-0 lg:block xl:w-[300px]" aria-label="板块面板">
|
|
<div className="sticky top-24">
|
|
<section className="j13-module">
|
|
<p className="j13-module-title mb-2">
|
|
<LayoutGrid size={15} style={{ color: "var(--ink-3)" }} /> 板块
|
|
</p>
|
|
<p className="meta">板块不存在或已删除</p>
|
|
<Link href="/" className="btn btn-line btn-sm mt-3 w-full justify-center">
|
|
返回全部讨论
|
|
</Link>
|
|
</section>
|
|
</div>
|
|
</aside>
|
|
);
|
|
}
|