公开页补齐自引用 Canonical 与按类型的结构化数据,图片补上说明文字。

后台设置和内容编辑改成可导航布局;部署用访客 origin 生成 Canonical,避免落到容器地址。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-20 00:11:24 +08:00
parent 2b3d122e56
commit 40bbad2c82
51 changed files with 3042 additions and 1318 deletions

View File

@@ -10,11 +10,12 @@ export type FeedSort = "latest" | "new" | "recommended";
const TAB_KEYS: FeedSort[] = ["latest", "new", "recommended"];
// 在指定板块内切换排序时保留 ?board=
function tabHref(key: FeedSort, boardId: number) {
// 在指定板块内切换排序时保留 ?board=;默认排序不写入 URL
function tabHref(key: FeedSort, boardId: number, allowComments: boolean) {
const params = new URLSearchParams();
if (boardId > 0) params.set("board", String(boardId));
if (key !== "latest") params.set("sort", key);
const def = allowComments ? "latest" : "new";
if (key !== def) params.set("sort", key);
const qs = params.toString();
return qs ? `/?${qs}` : "/";
}
@@ -31,7 +32,7 @@ export default function FeedTabs({ current, boardId = 0 }: { current: FeedSort;
e.preventDefault();
const next = (i + (e.key === "ArrowRight" ? 1 : keys.length - 1)) % keys.length;
tabRefs.current[next]?.focus();
router.push(tabHref(keys[next], boardId));
router.push(tabHref(keys[next], boardId, allowComments));
};
return (
@@ -44,7 +45,7 @@ export default function FeedTabs({ current, boardId = 0 }: { current: FeedSort;
ref={(el) => {
tabRefs.current[i] = el;
}}
href={tabHref(key, boardId)}
href={tabHref(key, boardId, allowComments)}
role="tab"
className="j13-tab inline-flex items-center gap-1"
aria-selected={current === key}

View File

@@ -2,8 +2,9 @@
import Link from "next/link";
import { useSearchParams } from "next/navigation";
import { PenSquare, Home, LayoutGrid } from "lucide-react";
import { PenSquare, Home, LayoutGrid, ShieldCheck } from "lucide-react";
import type { BoardWithCount, User } from "@/lib/api";
import { isStaff } from "@/lib/roles";
// 首页壳内板块切换:仅替换中栏帖子流,不离开工作台
function boardHref(id: number) {
@@ -11,9 +12,10 @@ function boardHref(id: number) {
}
// 桌面左栏:整块模块,与右栏 j13-module 对齐
export function WorkspaceNav({ boards }: { boards: BoardWithCount[]; user?: User | null }) {
export function WorkspaceNav({ boards, user }: { boards: BoardWithCount[]; user?: User | null }) {
const params = useSearchParams();
const activeBoard = Number(params.get("board")) || 0;
const showAdmin = !!user && isStaff(user.role);
return (
// sticky 必须挂在 flex 子项上:父级 items-start 会把内层缩成内容高,失去粘滞行程
@@ -68,6 +70,21 @@ export function WorkspaceNav({ boards }: { boards: BoardWithCount[]; user?: User
</Link>
))}
</div>
{showAdmin ? (
<div className="shrink-0 mt-1.5 pt-1.5" style={{ borderTop: "1px solid var(--line)" }}>
<Link href="/admin" className="j13-bcard">
<span
className="j13-bcard-ico"
style={{ background: "var(--accent-soft)", color: "var(--accent)" }}
aria-hidden="true"
>
<ShieldCheck size={15} />
</span>
<span className="j13-bcard-name flex-1 truncate">管理面板</span>
</Link>
</div>
) : null}
</section>
</nav>
);