refactor(layout, i18n, admin): 优化布局结构与多语言支持

调整 AdminShell 组件的子组件顺序,提升代码可读性。更新 admin-breadcrumb 组件,简化导航标签翻译逻辑,确保多语言支持的一致性。重构 admin-language-switcher 组件,优化语言切换的用户体验,增强界面交互性。更新多语言配置,新增登录界面的副标题,提升用户体验。
This commit is contained in:
2026-05-30 17:46:27 +08:00
parent 36117144dc
commit a550c418e5
64 changed files with 3405 additions and 1378 deletions

View File

@@ -1,26 +1,21 @@
"use client";
import { useEffect, useRef, useState } from "react";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { AdminPermissionGate } from "@/components/admin/admin-permission-gate";
import { PRD_RULES_ODDS_ACCESS_ANY } from "@/lib/admin-prd";
import { ConfigDocPage } from "@/modules/config/config-doc-page";
import { ConfigSection } from "@/modules/config/config-section";
import { OddsConfigDocScreen } from "@/modules/config/doc/odds-config-doc-screen";
import { RebateConfigDocScreen } from "@/modules/config/doc/rebate-config-doc-screen";
import { useOddsConfigWorkspace } from "@/modules/config/use-odds-config-workspace";
import { RulesPageShell } from "@/modules/rules/rules-page-shell";
/** 赔率与回水:共用赔率版本线,单页上下分区。 */
/** 赔率与回水:共用赔率版本线,主栏三步骤 + 右侧配置摘要。 */
export function RulesOddsConfigScreen() {
const { t } = useTranslation("config");
const [sharedVersionId, setSharedVersionId] = useState("");
const workspace = useOddsConfigWorkspace(sharedVersionId, setSharedVersionId);
const rebateSectionRef = useRef<HTMLDivElement>(null);
const [rebateMounted, setRebateMounted] = useState(
() => typeof window !== "undefined" && window.location.hash === "#rebate",
);
useEffect(() => {
const scrollToRebate = () => {
@@ -34,44 +29,26 @@ export function RulesOddsConfigScreen() {
return () => window.removeEventListener("hashchange", scrollToRebate);
}, []);
useEffect(() => {
if (rebateMounted) {
return;
}
const node = rebateSectionRef.current;
if (!node) {
return;
}
const observer = new IntersectionObserver(
([entry]) => {
if (entry?.isIntersecting) {
setRebateMounted(true);
}
},
{ rootMargin: "240px 0px" },
);
observer.observe(node);
return () => observer.disconnect();
}, [rebateMounted]);
const rebateSection = (
<div id="rebate">
<RebateConfigDocScreen embedded mergedSection workspace={workspace} />
</div>
);
return (
<RulesPageShell>
<AdminPermissionGate requiredAny={PRD_RULES_ODDS_ACCESS_ANY}>
<ConfigDocPage title={t("nav.rulesOddsTitle")} contentClassName="space-y-8">
<ConfigSection title={t("nav.items.odds")}>
<OddsConfigDocScreen embedded workspace={workspace} />
</ConfigSection>
<ConfigSection id="rebate" title={t("nav.items.rebate")}>
<div ref={rebateSectionRef}>
{rebateMounted ? (
<RebateConfigDocScreen embedded workspace={workspace} />
) : (
<p className="text-muted-foreground py-6 text-center text-sm">
{t("rebate.lazyLoadHint", { ns: "config" })}
</p>
)}
</div>
</ConfigSection>
<ConfigDocPage
title={t("nav.rulesOddsTitle")}
description={t("nav.rulesOddsDescription")}
contentClassName="pt-2"
>
<OddsConfigDocScreen
embedded
mergedLayout
workspace={workspace}
rebateSection={rebateSection}
/>
</ConfigDocPage>
</AdminPermissionGate>
</RulesPageShell>