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

@@ -51,34 +51,16 @@ import type {
OddsVersionDetail,
} from "@/types/api/admin-config";
import { ConfigWorkflowSection } from "@/modules/config/config-workflow-section";
import {
inferRebatePercentFromDimension,
rateToPercentUi,
} from "@/modules/config/doc/odds-rebate-rates";
import { PRIZE_SCOPE_ORDER } from "@/modules/config/doc/prize-scopes";
const SETTLEMENT_GROUP = "settlement";
const APPLY_REBATE_TO_PAYOUT_KEY = "settlement.apply_rebate_to_payout";
function rateToPercentUi(rateStr: string): string {
const n = Number.parseFloat(rateStr);
if (!Number.isFinite(n)) {
return "0.00";
}
return (Math.round(n * 10000) / 100).toFixed(2);
}
function inferPercentFrom(dim: 2 | 3 | 4, rows: OddsItemRow[], typeList: AdminPlayTypeRow[]): string {
const codes = typeList
.filter((t) => (t.dimension ?? 2) === dim)
.map((t) => t.play_code)
.sort((a, b) => a.localeCompare(b));
const scope = PRIZE_SCOPE_ORDER[0];
for (const code of codes) {
const hit = rows.find((r) => r.play_code === code && r.prize_scope === scope);
if (hit) {
return rateToPercentUi(String(hit.rebate_rate));
}
}
return "0";
}
function dimensionDistinctPrimaryScopePercents(
dim: 2 | 3 | 4,
rows: OddsItemRow[],
@@ -101,6 +83,8 @@ function dimensionDistinctPrimaryScopePercents(
type RebateConfigDocScreenProps = {
embedded?: boolean;
/** 合并页第 3 步卡片 */
mergedSection?: boolean;
workspace?: OddsConfigWorkspace;
versionId?: string;
onVersionIdChange?: (id: string) => void;
@@ -108,6 +92,7 @@ type RebateConfigDocScreenProps = {
export function RebateConfigDocScreen({
embedded = false,
mergedSection = false,
workspace,
versionId: controlledVersionId,
onVersionIdChange,
@@ -205,9 +190,9 @@ export function RebateConfigDocScreen({
if (!workspace) {
return;
}
setP2(inferPercentFrom(2, workspace.draftRows, workspace.types));
setP3(inferPercentFrom(3, workspace.draftRows, workspace.types));
setP4(inferPercentFrom(4, workspace.draftRows, workspace.types));
setP2(inferRebatePercentFromDimension(2, workspace.draftRows, workspace.types));
setP3(inferRebatePercentFromDimension(3, workspace.draftRows, workspace.types));
setP4(inferRebatePercentFromDimension(4, workspace.draftRows, workspace.types));
}, [workspace?.draftRows, workspace?.types, workspace]);
async function handleWinEnjoyChange(checked: boolean): Promise<void> {
@@ -236,9 +221,9 @@ export function RebateConfigDocScreen({
const rows = d.items.map((it) => ({ ...it }));
setDetail(d);
setDraftRows(rows);
setP2(inferPercentFrom(2, rows, typeList));
setP3(inferPercentFrom(3, rows, typeList));
setP4(inferPercentFrom(4, rows, typeList));
setP2(inferRebatePercentFromDimension(2, rows, typeList));
setP3(inferRebatePercentFromDimension(3, rows, typeList));
setP4(inferRebatePercentFromDimension(4, rows, typeList));
} catch (e) {
toast.error(e instanceof LotteryApiBizError ? e.message : t("errors.loadFailed", { ns: "common" }));
setDetail(null);
@@ -357,9 +342,9 @@ export function RebateConfigDocScreen({
setDetail(d);
setDraftRows(rows);
}
setP2(inferPercentFrom(2, rows, resolvedTypes));
setP3(inferPercentFrom(3, rows, resolvedTypes));
setP4(inferPercentFrom(4, rows, resolvedTypes));
setP2(inferRebatePercentFromDimension(2, rows, resolvedTypes));
setP3(inferRebatePercentFromDimension(3, rows, resolvedTypes));
setP4(inferRebatePercentFromDimension(4, rows, resolvedTypes));
toast.success(t("versionActions.saveDraft", { ns: "config" }));
void (workspace?.refreshList() ?? refreshList());
} catch (e) {
@@ -383,9 +368,9 @@ export function RebateConfigDocScreen({
setDetail(d);
setDraftRows(rows);
}
setP2(inferPercentFrom(2, rows, resolvedTypes));
setP3(inferPercentFrom(3, rows, resolvedTypes));
setP4(inferPercentFrom(4, rows, resolvedTypes));
setP2(inferRebatePercentFromDimension(2, rows, resolvedTypes));
setP3(inferRebatePercentFromDimension(3, rows, resolvedTypes));
setP4(inferRebatePercentFromDimension(4, rows, resolvedTypes));
toast.success(t("rebate.publishSuccess", { ns: "config" }));
void (workspace?.refreshList() ?? refreshList());
setSelectedId(String(d.id));
@@ -414,9 +399,9 @@ export function RebateConfigDocScreen({
setDetail(d);
setDraftRows(rows);
}
setP2(inferPercentFrom(2, rows, resolvedTypes));
setP3(inferPercentFrom(3, rows, resolvedTypes));
setP4(inferPercentFrom(4, rows, resolvedTypes));
setP2(inferRebatePercentFromDimension(2, rows, resolvedTypes));
setP3(inferRebatePercentFromDimension(3, rows, resolvedTypes));
setP4(inferRebatePercentFromDimension(4, rows, resolvedTypes));
} catch (e) {
toast.error(e instanceof LotteryApiBizError ? e.message : t("rebate.createDraftFailed", { ns: "config" }));
} finally {
@@ -457,9 +442,9 @@ export function RebateConfigDocScreen({
setDetail(d);
setDraftRows(rows);
}
setP2(inferPercentFrom(2, rows, resolvedTypes));
setP3(inferPercentFrom(3, rows, resolvedTypes));
setP4(inferPercentFrom(4, rows, resolvedTypes));
setP2(inferRebatePercentFromDimension(2, rows, resolvedTypes));
setP3(inferRebatePercentFromDimension(3, rows, resolvedTypes));
setP4(inferRebatePercentFromDimension(4, rows, resolvedTypes));
setRollbackOpen(false);
setRollbackTarget(null);
} catch (e) {
@@ -658,6 +643,23 @@ export function RebateConfigDocScreen({
</Dialog>
);
if (embedded && mergedSection) {
return (
<>
<ConfigWorkflowSection
step={3}
title={t("nav.items.rebate", { ns: "config" })}
description={t("rebate.sectionHint", { ns: "config" })}
contentClassName="space-y-5"
>
{fieldsBlock}
</ConfigWorkflowSection>
{rollbackDialog}
<ConfirmDialog />
</>
);
}
if (embedded) {
return (
<div className="space-y-4">