chore: 去掉未上线前的旧兼容垫片,板块流收口到首页
鉴权只认 cookie、SEO 只走 Next、sort 与推荐对齐;删除 Bearer、Go sitemap、post_type 回填等冗余路径。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -20,7 +20,6 @@ import {
|
||||
ChevronDown,
|
||||
ShieldCheck,
|
||||
MessageSquare,
|
||||
LayoutGrid,
|
||||
} from "lucide-react";
|
||||
import { apiLogout, apiMe, apiChatUnreadSummary, type User } from "@/lib/api";
|
||||
import { hasAuthCookieHint } from "@/lib/cookies";
|
||||
@@ -87,8 +86,8 @@ export default function Header({
|
||||
});
|
||||
}), [router]);
|
||||
|
||||
// 挂载后静默校正一次(仅一次):覆盖 SSR 期间后端不可用、旧 Path refresh
|
||||
// 半登录等边缘情况。正常 F5 下结果与 SSR 一致,不产生可见闪动;
|
||||
// 挂载后静默校正一次(仅一次):覆盖 SSR 期间后端不可用等边缘情况。
|
||||
// 正常 F5 下结果与 SSR 一致,不产生可见闪动;
|
||||
// 无可读 CSRF cookie 说明必然是游客,跳过请求
|
||||
useEffect(() => {
|
||||
if (!hasAuthCookieHint()) return;
|
||||
@@ -238,13 +237,6 @@ export default function Header({
|
||||
<NavPill href="/" active={pathname === "/"} icon={<Home size={15} />}>
|
||||
首页
|
||||
</NavPill>
|
||||
<NavPill
|
||||
href="/boards"
|
||||
active={pathname === "/boards" || pathname?.startsWith("/board/") === true}
|
||||
icon={<LayoutGrid size={15} />}
|
||||
>
|
||||
板块
|
||||
</NavPill>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
@@ -378,12 +370,6 @@ export default function Header({
|
||||
</form>
|
||||
<div className="flex flex-col">
|
||||
<MobileLink href="/" active={pathname === "/"}>首页</MobileLink>
|
||||
<MobileLink
|
||||
href="/boards"
|
||||
active={pathname === "/boards" || pathname?.startsWith("/board/") === true}
|
||||
>
|
||||
板块
|
||||
</MobileLink>
|
||||
{user && <MobileLink href="/compose">发帖</MobileLink>}
|
||||
{user && (
|
||||
<MobileLink href="/chat" active={pathname?.startsWith("/chat") ?? false}>
|
||||
|
||||
@@ -91,7 +91,7 @@ export default function PostRow({
|
||||
}`}
|
||||
>
|
||||
{boardHref && (
|
||||
<Link href={`/board/${post.board.id}`} className="j13-board-tag">
|
||||
<Link href={`/?board=${post.board.id}`} className="j13-board-tag">
|
||||
{post.board.name}
|
||||
</Link>
|
||||
)}
|
||||
|
||||
@@ -26,7 +26,7 @@ export default function ThemeSync({ initialAccent }: { initialAccent: string })
|
||||
|
||||
useEffect(() => {
|
||||
// 外观设置页存在"未保存的本地实时预览",该页面内暂停同步,
|
||||
// 避免轮询结果覆盖管理员正在预览的颜色;离开页面后 effect 重跑立即对齐
|
||||
// 避免 HTTP 校准覆盖管理员正在预览的颜色;离开页面后 effect 重跑立即对齐
|
||||
if (pathname?.startsWith("/admin/appearance")) return;
|
||||
|
||||
let cancelled = false;
|
||||
|
||||
307
frontend/components/home/BoardPanels.tsx
Normal file
307
frontend/components/home/BoardPanels.tsx
Normal file
@@ -0,0 +1,307 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import Link from "next/link";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { PenSquare, Home, ChevronRight } from "lucide-react";
|
||||
import { PenSquare, Home } from "lucide-react";
|
||||
import type { BoardWithCount, User } from "@/lib/api";
|
||||
|
||||
// 首页壳内板块切换:仅替换中栏帖子流,不离开工作台
|
||||
@@ -34,26 +34,14 @@ export function WorkspaceNav({ boards }: { boards: BoardWithCount[]; user?: User
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* 板块目录:标题可点击进入板块汇总列表页(/boards);
|
||||
下方板块卡片点击不离开工作台,仅在中栏筛选该板块帖子流 */}
|
||||
{/* 板块目录:纯分区标题;下方卡片仅在中栏筛选该板帖子流 */}
|
||||
<div>
|
||||
<Link
|
||||
href="/boards"
|
||||
className="group px-1 mb-2 flex items-center justify-between text-[11.5px] font-medium tracking-[0.08em] uppercase text-[var(--ink-3)] hover:text-[var(--accent)] transition-colors"
|
||||
aria-label="进入板块目录列表页"
|
||||
>
|
||||
<span className="flex items-center">
|
||||
<span>板块目录</span>
|
||||
<ChevronRight
|
||||
size={12}
|
||||
className="ml-0.5 opacity-0 -translate-x-0.5 group-hover:opacity-100 group-hover:translate-x-0 transition-all"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</span>
|
||||
<div className="px-1 mb-2 flex items-center justify-between text-[11.5px] font-medium tracking-[0.08em] uppercase text-[var(--ink-3)]">
|
||||
<span>板块目录</span>
|
||||
{boards.length > 0 && (
|
||||
<span className="tracking-normal tabular-nums">{boards.length} 个</span>
|
||||
)}
|
||||
</Link>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
{boards.map((b, i) => (
|
||||
<Link
|
||||
|
||||
Reference in New Issue
Block a user