feat: 可配置站点 URL 风格

- 新增 urlStyle 工具与测试:postPath/userPath/sitePagePath 统一生成链接
- 管理端新增 UrlStylePanel 设置面板,settings 服务与 handler 适配
- 全站路由链接与 JSON-LD/sitemap 切换为统一 URL 生成
- middleware 与相关组件适配
This commit is contained in:
2026-09-27 04:01:42 +08:00
parent 1d862a0dd3
commit 7a44c1f2cf
64 changed files with 1183 additions and 200 deletions

View File

@@ -12,7 +12,9 @@ import {
} from "lucide-react";
import { apiChatUnreadSummary, type User } from "@/lib/api";
import { useAllowMessages } from "@/components/MessagesPolicyProvider";
import { useUrlStyle } from "@/components/SiteBrandProvider";
import { onDocumentVisible } from "@/lib/visibilityReconcile";
import { parseEntityPath, userPath, leaderboardPath } from "@/lib/urlStyle";
import { realtime, RT_CHAT_UNREAD } from "@/lib/realtime";
import type { RtChatUnreadData } from "@/lib/realtime";
@@ -30,6 +32,8 @@ export default function MobileTabBar({
}) {
const pathname = usePathname();
const allowMessages = useAllowMessages();
const urlStyle = useUrlStyle();
const lbHref = leaderboardPath(urlStyle);
const [chatUnread, setChatUnread] = useState(initialChatUnread);
// 登录态经 router.refresh() 下发新 props 时校正徽标
@@ -96,10 +100,10 @@ export default function MobileTabBar({
active={isActive((p) => p === "/")}
/>
<TabLink
href="/leaderboard"
href={lbHref}
icon={<Trophy size={20} />}
label="排行榜"
active={isActive((p) => p.startsWith("/leaderboard"))}
active={!!pathname && (pathname === lbHref || pathname.startsWith(`${lbHref}/`))}
/>
<TabLink
href={user ? "/compose" : "/login"}
@@ -118,11 +122,18 @@ export default function MobileTabBar({
/>
)}
<TabLink
href={user ? `/u/${user.id}` : "/login"}
href={user ? userPath(urlStyle, user.id) : "/login"}
icon={<UserIcon size={20} />}
label="我的"
active={
!!user && isActive((p) => p === `/u/${user.id}` || p.startsWith(`/u/${user.id}/`))
!!user &&
isActive((p) => {
const parsed = parseEntityPath(p);
return (
(parsed?.kind === "user" && parsed.id === user.id) ||
p.startsWith(`${userPath(urlStyle, user.id)}/`)
);
})
}
/>
</div>