refactor: 移除冗余注释和描述,优化管理员模块的代码结构

This commit is contained in:
2026-05-11 17:21:12 +08:00
parent 217ed7c02f
commit 76e318be8f
57 changed files with 163 additions and 382 deletions

View File

@@ -5,7 +5,7 @@ import { useCallback, useEffect, useState } from "react";
import { getAdminAuditLogs } from "@/api/admin-audit";
import { AdminListPaginationFooter } from "@/components/admin/admin-list-pagination-footer";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
@@ -67,10 +67,6 @@ export function AuditLogsConsole(): React.ReactElement {
<CardHeader className="flex flex-row flex-wrap items-end justify-between gap-4">
<div>
<CardTitle></CardTitle>
<CardDescription>
<code className="rounded bg-muted px-1">GET /api/v1/admin/audit-logs</code>{" "}
RBAC
</CardDescription>
</div>
<Button type="button" variant="secondary" size="sm" onClick={() => void load()}>

View File

@@ -1,5 +1,5 @@
export const auditModuleMeta = {
segment: "audit",
export const auditLogsModuleMeta = {
segment: "audit-logs",
title: "审计日志",
description: "运营留痕查询(权限范围由后端按角色过滤)。",
description: "",
} as const;

View File

@@ -1,5 +1,5 @@
export const authModuleMeta = {
segment: "auth",
segment: "login",
title: "登录",
description: "账号、密码与图形验证码登录;对接 Laravel Sanctum。",
description: "",
} as const;

View File

@@ -7,7 +7,7 @@ import {
getPlayConfigVersions,
getRiskCapVersions,
} from "@/api/admin-config";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import {
Table,
TableBody,
@@ -111,9 +111,6 @@ export function ConfigVersionsConsole() {
<Card>
<CardHeader className="space-y-1">
<CardTitle className="text-lg"></CardTitle>
<CardDescription>
线
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
{error ? <p className="text-sm text-destructive">{error}</p> : null}

View File

@@ -12,7 +12,7 @@ import {
putOddsItems,
} from "@/api/admin-config";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import {
Dialog,
DialogContent,
@@ -346,9 +346,6 @@ export function OddsConfigDocScreen() {
<Card>
<CardHeader className="space-y-1">
<CardTitle className="text-lg"></CardTitle>
<CardDescription>
§5.5odds_value = × 10000NPR 100
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<div className="flex flex-wrap gap-2">

View File

@@ -13,7 +13,7 @@ import {
putPlayConfigItems,
} from "@/api/admin-config";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Checkbox } from "@/components/ui/checkbox";
import {
Dialog,
@@ -332,9 +332,6 @@ export function PlayConfigDocScreen() {
<Card>
<CardHeader className="space-y-1">
<CardTitle className="text-lg"></CardTitle>
<CardDescription>
§5.4稿
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex flex-wrap items-center gap-2">

View File

@@ -1,4 +1,4 @@
/** 对齐界面文档 §5.5:头 / 二 / 三 / 特别 / 安慰(含 starter / consolation 映射)。 */
/** 奖项档位顺序(含 starter / consolation。 */
export const PRIZE_SCOPE_ORDER = [
"first",

View File

@@ -12,7 +12,7 @@ import {
putOddsItems,
} from "@/api/admin-config";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Checkbox } from "@/components/ui/checkbox";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
@@ -246,9 +246,6 @@ export function RebateConfigDocScreen() {
<Card>
<CardHeader className="space-y-1">
<CardTitle className="text-lg"> / </CardTitle>
<CardDescription>
§5.6 2D / 3D / 4D rebate_rate odds_versions
</CardDescription>
</CardHeader>
<CardContent className="space-y-6 max-w-xl">
<div className="flex flex-wrap gap-2">

View File

@@ -11,7 +11,7 @@ import {
putRiskCapItems,
} from "@/api/admin-config";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import {
Dialog,
DialogContent,
@@ -294,9 +294,6 @@ export function RiskCapDocScreen() {
</span>
) : null}
</CardTitle>
<CardDescription>
§5.7
</CardDescription>
</CardHeader>
<CardContent className="space-y-8">
<div className="flex flex-wrap items-end gap-4">

View File

@@ -1,14 +1,29 @@
export const configHubMeta = {
title: "运营配置",
description: "玩法、赔率、回水、风控封顶(界面文档 §5.4§5.7",
};
title: "配置中心",
description: "",
} as const;
/** @deprecated 路由重定向至 `/admin/config/plays` */
export const configPlaySwitchesMeta = { title: "玩法开关 · 运营配置" };
/** @deprecated 路由重定向至 `/admin/config/plays` */
export const configPlayLimitsMeta = { title: "最小/最大下注 · 运营配置" };
export const configPlayConfigMeta = { title: "玩法配置 · 运营配置" };
export const configOddsMeta = { title: "赔率配置 · 运营配置" };
export const configRebateMeta = { title: "佣金 / 回水 · 运营配置" };
export const configRiskCapMeta = { title: "风控封顶 · 运营配置" };
export const configVersionsMeta = { title: "配置版本历史 · 运营配置" };
export const configPlayConfigMeta = {
title: "玩法配置",
description: "",
} as const;
export const configOddsMeta = {
title: "赔率配置",
description: "",
} as const;
export const configRebateMeta = {
title: "佣金 / 回水",
description: "",
} as const;
export const configRiskCapMeta = {
title: "风控封顶",
description: "",
} as const;
export const configVersionsMeta = {
title: "配置版本历史",
description: "",
} as const;

View File

@@ -14,7 +14,6 @@ import {
RefreshCw,
ScrollText,
Shield,
ShieldCheck,
Ticket,
TrendingUp,
Wallet,
@@ -23,7 +22,7 @@ import {
import { getAdminDashboard } from "@/api/admin-dashboard";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Button, buttonVariants } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Skeleton } from "@/components/ui/skeleton";
import { cn } from "@/lib/utils";
import { LotteryApiBizError } from "@/types/api/errors";
@@ -31,7 +30,7 @@ import type { AdminDrawFinanceSummaryData } from "@/types/api/admin-draw-finance
import type { AdminRiskPoolRow } from "@/types/api/admin-risk";
import type { DrawCurrentSnapshot } from "@/types/api/public-draw";
type HotPlayTab = "4D" | "3D" | "2D";
type HotPlayTab = "4D" | "3D" | "2D" | "特别";
type SoldOutBuckets = {
d4: number;
@@ -67,22 +66,28 @@ function formatSignedMoneyMinor(minor: number, currencyCode: string | null): str
return `${s}${formatMoneyMinor(Math.abs(minor), currencyCode)}`;
}
/** 与后端 {@see AdminDashboardSnapshotBuilder::soldOutBucketKey} 维度对齐 */
function poolPlayCategory(normalizedNumber: string): HotPlayTab | "other" {
const raw = normalizedNumber.trim();
if (/[A-Za-z]/.test(raw)) {
return "other";
}
const digits = raw.replace(/\D/g, "");
const len = digits.length > 0 ? digits.length : raw.length;
if (len >= 4) {
const digitLen = digits.length;
const hasLetter = /[A-Za-z]/.test(raw);
if (hasLetter && digitLen < 3) {
return "特别";
}
if (digitLen >= 4) {
return "4D";
}
if (len === 3) {
if (digitLen === 3) {
return "3D";
}
if (len === 2) {
if (digitLen === 2) {
return "2D";
}
if (hasLetter) {
return "特别";
}
return "other";
}
@@ -132,7 +137,7 @@ function HotBarChart({ rows }: { rows: AdminRiskPoolRow[] }): ReactElement {
return (
<div className="flex h-52 flex-col">
<div className="relative flex flex-1 items-end gap-1.5 border-b border-slate-200/90 pb-0.5 pl-7">
<div className="relative flex h-[168px] min-h-[168px] w-full items-stretch gap-1.5 border-b border-slate-200/90 pb-0.5 pl-7">
<span
className="pointer-events-none absolute bottom-6 left-0 top-2 w-6 rotate-180 text-[10px] leading-tight text-muted-foreground [writing-mode:vertical-rl]"
aria-hidden
@@ -144,15 +149,22 @@ function HotBarChart({ rows }: { rows: AdminRiskPoolRow[] }): ReactElement {
) : (
rows.map((row) => {
const u = row.usage_ratio ?? 0;
const h = Math.max(6, (u / maxU) * 100);
const h = Math.max(8, (u / maxU) * 100);
return (
<div key={row.normalized_number} className="flex min-w-0 flex-1 flex-col items-center gap-1">
<div
className="w-full max-w-[2.25rem] rounded-t-sm bg-[#c41e3a]/90 shadow-sm transition-all"
style={{ height: `${h}%` }}
title={`${row.normalized_number}: ${(u * 100).toFixed(1)}%`}
/>
<span className="truncate font-mono text-[10px] text-[#1a365d]">{row.normalized_number}</span>
<div
key={row.normalized_number}
className="flex min-h-0 min-w-0 flex-1 flex-col items-stretch gap-1"
>
<div className="flex min-h-0 flex-1 flex-col justify-end">
<div
className="mx-auto w-full max-w-[2.25rem] rounded-t-sm bg-[#c41e3a]/90 shadow-sm transition-all"
style={{ height: `${h}%`, minHeight: 6 }}
title={`${row.normalized_number}: ${(u * 100).toFixed(1)}%`}
/>
</div>
<span className="truncate text-center font-mono text-[10px] text-[#1a365d]">
{row.normalized_number.trim()}
</span>
</div>
);
})
@@ -345,7 +357,6 @@ export function DashboardConsole(): ReactElement {
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<div>
<h1 className="text-2xl font-bold tracking-tight text-[#1a365d]"></h1>
<p className="text-sm text-muted-foreground"> · </p>
</div>
<div className="flex items-center gap-2">
<span className="text-sm text-muted-foreground">{todayLabel}</span>
@@ -372,7 +383,7 @@ export function DashboardConsole(): ReactElement {
{notice && !error ? (
<Alert className="border-sky-200 bg-sky-50 dark:border-sky-900/50 dark:bg-sky-950/30">
<AlertTitle></AlertTitle>
<AlertTitle></AlertTitle>
<AlertDescription>{notice}</AlertDescription>
</Alert>
) : null}
@@ -541,11 +552,10 @@ export function DashboardConsole(): ReactElement {
<CardHeader className="flex flex-row flex-wrap items-end justify-between gap-2 space-y-0 pb-2">
<div>
<CardTitle className="text-base font-semibold text-[#1a365d]"> Top 10</CardTitle>
<CardDescription> 100 </CardDescription>
</div>
<div className="flex items-center gap-3">
<div role="tablist" aria-label="玩法维度" className="flex gap-1 border-b border-transparent">
{(["4D", "3D", "2D"] as const).map((tab) => (
{(["4D", "3D", "2D", "特别"] as const).map((tab) => (
<button
key={tab}
type="button"
@@ -586,7 +596,6 @@ export function DashboardConsole(): ReactElement {
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<div>
<CardTitle className="text-base font-semibold text-[#1a365d]"></CardTitle>
<CardDescription></CardDescription>
</div>
{drawId != null ? (
<Link
@@ -621,7 +630,6 @@ export function DashboardConsole(): ReactElement {
<p className="mt-1 text-4xl font-bold tabular-nums text-[#c41e3a]">
{pendingReview ?? "—"}
</p>
<p className="text-xs text-muted-foreground"></p>
</div>
</div>
{drawId != null ? (
@@ -646,7 +654,6 @@ export function DashboardConsole(): ReactElement {
<p className="mt-1 text-4xl font-bold tabular-nums text-[#c41e3a]">
{abnormalTransferTotal ?? "—"}
</p>
<p className="text-xs text-muted-foreground"></p>
</div>
</div>
<Link
@@ -679,10 +686,6 @@ export function DashboardConsole(): ReactElement {
</CardContent>
</Card>
<footer className="flex items-center justify-center gap-2 text-xs text-muted-foreground">
<ShieldCheck className="size-4 text-[#c41e3a]" aria-hidden />
<span> · 访</span>
</footer>
</div>
);
}

View File

@@ -1,5 +1,5 @@
export const dashboardModuleMeta = {
segment: "dashboard",
title: "仪表盘",
description: "仪表盘:大厅当前期、投注/派彩/风控占用与快捷入口。",
description: "",
} as const;

View File

@@ -5,7 +5,7 @@ import { useCallback, useEffect, useState } from "react";
import { getAdminDraw } from "@/api/admin-draws";
import { buttonVariants } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Label } from "@/components/ui/label";
import { Separator } from "@/components/ui/separator";
import { useAdminDateTimeFormatter } from "@/hooks/use-admin-datetime-formatter";
@@ -70,10 +70,6 @@ export function DrawDetailConsole({ drawId }: { drawId: string }) {
<Card>
<CardHeader>
<CardTitle className="text-lg"></CardTitle>
<CardDescription>
<code className="rounded bg-muted px-1 py-0.5 text-xs">status</code>{" "}
tick DB
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex flex-wrap items-center gap-2">
@@ -103,7 +99,6 @@ export function DrawDetailConsole({ drawId }: { drawId: string }) {
<Card>
<CardHeader>
<CardTitle className="text-base"></CardTitle>
<CardDescription> / </CardDescription>
</CardHeader>
<CardContent className="flex flex-col gap-4">
<div className="flex flex-wrap gap-6 text-sm">

View File

@@ -5,7 +5,7 @@ import { useCallback, useEffect, useState } from "react";
import { getAdminDrawFinanceSummary } from "@/api/admin-draws";
import { Button, buttonVariants } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import {
Table,
TableBody,
@@ -18,7 +18,6 @@ import { cn } from "@/lib/utils";
import { LotteryApiBizError } from "@/types/api/errors";
import type { AdminDrawFinanceSummaryData } from "@/types/api/admin-draw-finance";
/** PRD §15.4:单期投注/派彩与结算批次(`GET …/draws/{id}/finance-summary` */
export function DrawFinanceConsole({ drawId }: { drawId: string }): React.ReactElement {
const idNum = Number(drawId);
const [data, setData] = useState<AdminDrawFinanceSummaryData | null>(null);
@@ -57,16 +56,11 @@ export function DrawFinanceConsole({ drawId }: { drawId: string }): React.ReactE
return <p className="text-destructive text-sm">{err ?? "无数据"}</p>;
}
const cur = data.currency_code ?? "—";
return (
<div className="space-y-6">
<Card>
<CardHeader>
<CardTitle className="text-lg"></CardTitle>
<CardDescription>
{cur} = + Jackpot
</CardDescription>
</CardHeader>
<CardContent className="grid gap-3 text-sm sm:grid-cols-2 lg:grid-cols-3">
<div>
@@ -120,7 +114,6 @@ export function DrawFinanceConsole({ drawId }: { drawId: string }): React.ReactE
<Card>
<CardHeader>
<CardTitle className="text-base"></CardTitle>
<CardDescription> `settlement_batches` </CardDescription>
</CardHeader>
<CardContent>
{data.settlement_batches.length === 0 ? (

View File

@@ -1,17 +1,2 @@
/**
* 后台开奖域与 PRD 阶段 3 · §11.4 / §11.7 验收对照(路径以 `API_V1_PREFIX=/api/v1` 为前缀)。
*
* - 期号列表:`GET …/admin/draws`
* - 期号详情 / 状态:`GET …/admin/draws/{draw}`
* - 开奖结果批次(含待审核、已发布):`GET …/admin/draws/{draw}/result-batches`
* - 发布:`POST …/admin/draws/{draw}/result-batches/{batch}/publish`
*/
export const DRAW_ADMIN_API_PRD_LINES = [
"GET /api/v1/admin/draws",
"GET /api/v1/admin/draws/{draw}",
"GET /api/v1/admin/draws/{draw}/result-batches",
"POST /api/v1/admin/draws/{draw}/result-batches/{batch}/publish",
] as const;
/** 具备其一即可执行发布、进入发布页(与 Laravel `prd.draw_result.manage` 一致) */
/** 开奖结果发布权限 slug */
export const PRD_DRAW_RESULT_MANAGE = "prd.draw_result.manage" as const;

View File

@@ -7,7 +7,7 @@ import { toast } from "sonner";
import { getAdminDrawResultBatches, postAdminPublishResultBatch } from "@/api/admin-draws";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Button, buttonVariants } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
import {
Table,
TableBody,
@@ -112,38 +112,24 @@ export function DrawPublishConsole({ drawId, batchId }: { drawId: string; batchI
<Card>
<CardHeader>
<CardTitle className="text-lg"></CardTitle>
<CardDescription>
<span className="font-mono font-medium">{data.draw_no}</span> · v
{batch.result_version}{" "}
<span className="rounded bg-muted px-1 py-0.5 font-mono text-xs">{batch.status}</span>
· {" "}
<code className="rounded bg-muted px-1 text-xs">
POST /result-batches/{batch.id}/publish
</code>
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
{!canManageDraw ? (
<Alert variant="destructive">
<AlertTitle></AlertTitle>
<AlertDescription>
<code className="rounded bg-background/80 px-1">{PRD_DRAW_RESULT_MANAGE}</code>{" "}
</AlertDescription>
<AlertDescription></AlertDescription>
</Alert>
) : null}
{!canPublish && canManageDraw ? (
<Alert>
<AlertTitle></AlertTitle>
<AlertDescription>
pending_review {batch.status}
</AlertDescription>
<AlertDescription>{batch.status}</AlertDescription>
</Alert>
) : null}
{canPublish ? (
<Alert>
<AlertTitle></AlertTitle>
<AlertDescription></AlertDescription>
<AlertDescription></AlertDescription>
</Alert>
) : null}

View File

@@ -5,7 +5,7 @@ import { useCallback, useEffect, useState } from "react";
import { getAdminDrawResultBatches } from "@/api/admin-draws";
import { buttonVariants } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import {
Table,
TableBody,
@@ -74,8 +74,7 @@ export function DrawResultsConsole({ drawId }: { drawId: string }) {
<div>
<h2 className="text-lg font-semibold"></h2>
<p className="text-sm text-muted-foreground">
{data.draw_no} · <DrawStatusBadge status={data.draw_status} /> ·
{data.draw_no} · <DrawStatusBadge status={data.draw_status} />
</p>
</div>
<Link
@@ -89,7 +88,7 @@ export function DrawResultsConsole({ drawId }: { drawId: string }) {
{published.length === 0 ? (
<Card>
<CardContent className="py-8 text-center text-sm text-muted-foreground">
RNG
</CardContent>
</Card>
) : (
@@ -104,9 +103,9 @@ function BatchTable({ batch }: { batch: AdminDrawBatchRow }) {
<Card>
<CardHeader className="pb-2">
<CardTitle className="text-base"> v{batch.result_version}</CardTitle>
<CardDescription className="font-mono text-xs">
<p className="font-mono text-xs text-muted-foreground">
RNG {batch.rng_seed_hash ?? "—"} · {batch.confirmed_at ?? "—"}
</CardDescription>
</p>
</CardHeader>
<CardContent className="overflow-x-auto pt-0">
<Table>

View File

@@ -5,7 +5,7 @@ import { useCallback, useEffect, useMemo, useState } from "react";
import { getAdminDrawResultBatches } from "@/api/admin-draws";
import { buttonVariants } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import {
Table,
TableBody,
@@ -74,13 +74,9 @@ export function DrawReviewConsole({ drawId }: { drawId: string }) {
<Card>
<CardHeader>
<CardTitle className="text-lg"></CardTitle>
<CardDescription>
RNG {" "}
<code className="rounded bg-muted px-1 text-xs">{PRD_DRAW_RESULT_MANAGE}</code>{" "}
{" "}
<code className="rounded bg-muted px-1 text-xs">POST /result-batches//publish</code>
DB <DrawStatusBadge status={data.draw_status} />
</CardDescription>
<p className="flex flex-wrap items-center gap-2 text-sm text-muted-foreground">
<DrawStatusBadge status={data.draw_status} />
</p>
</CardHeader>
<CardContent>
{pending.length === 0 ? (

View File

@@ -5,7 +5,7 @@ import { useCallback, useEffect, useState } from "react";
import { getAdminDraws } from "@/api/admin-draws";
import { Button, buttonVariants } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { AdminListPaginationFooter } from "@/components/admin/admin-list-pagination-footer";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
@@ -29,7 +29,6 @@ import { cn } from "@/lib/utils";
import { LotteryApiBizError } from "@/types/api/errors";
import type { AdminDrawListData, AdminDrawListItem } from "@/types/api/admin-draws";
import { DRAW_ADMIN_API_PRD_LINES } from "./draw-prd";
import { DrawStatusBadge } from "./draw-status-badge";
/** 下拉「不限」;请求时不传 status */
@@ -104,14 +103,6 @@ export function DrawsIndexConsole() {
<Card>
<CardHeader className="space-y-1">
<CardTitle className="text-lg"></CardTitle>
<CardDescription className="space-y-2">
<span>
3 · §11.4
</span>
<span className="block font-mono text-[11px] leading-relaxed text-muted-foreground">
{DRAW_ADMIN_API_PRD_LINES.join(" · ")}
</span>
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
{/* Grid桌面端标签一行 / 控件一行,避免 flex+items-end 与各列实际高度不一致;移动端单列自上而下 */}

View File

@@ -1,6 +1,5 @@
export const drawsModuleMeta = {
segment: "draws",
title: "开奖",
description:
"PRD 阶段3 §11.4:期号列表 / 状态 / 开奖结果 / 审核队列 / 发布POST …/result-batches/…/publish路由 `/admin/draws/[id]/publish/[batchId]` 与接口动词对齐。",
description: "",
} as const;

View File

@@ -5,7 +5,7 @@ import { useCallback, useEffect, useState } from "react";
import { getAdminJackpotPools, putAdminJackpotPool } from "@/api/admin-jackpot";
import { ModuleScaffold } from "@/components/admin/module-scaffold";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
@@ -106,7 +106,6 @@ export function JackpotPoolsConsole() {
<Card>
<CardHeader>
<CardTitle className="text-base">Jackpot </CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent className="space-y-8">
{loading ? <p className="text-muted-foreground text-sm"></p> : null}

View File

@@ -6,7 +6,7 @@ import { getAdminJackpotContributions, getAdminJackpotPayoutLogs } from "@/api/a
import { AdminListPaginationFooter } from "@/components/admin/admin-list-pagination-footer";
import { ModuleScaffold } from "@/components/admin/module-scaffold";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
@@ -97,7 +97,6 @@ export function JackpotRecordsConsole() {
<Card className="mb-6">
<CardHeader className="pb-3">
<CardTitle className="text-base"></CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent className="flex flex-col gap-3 sm:flex-row sm:items-end">
<div className="flex max-w-xs flex-1 flex-col gap-1.5">
@@ -121,7 +120,6 @@ export function JackpotRecordsConsole() {
<Card className="mb-8">
<CardHeader>
<CardTitle className="text-base">Jackpot </CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
{loadingP && !payouts ? (
@@ -177,7 +175,6 @@ export function JackpotRecordsConsole() {
<Card>
<CardHeader>
<CardTitle className="text-base">Jackpot </CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
{loadingC && !contribs ? (

View File

@@ -1,4 +1,4 @@
export const jackpotModuleMeta = {
title: "Jackpot",
description: "奖池配置与蓄水 / 派彩记录",
title: "奖池",
description: "",
} as const;

View File

@@ -1,5 +1,5 @@
export const playersModuleMeta = {
segment: "players",
title: "玩家查询",
description: "按玩家 ID 查钱包等PRD 15.3);列表/冻结待管理端用户 API。",
title: "玩家",
description: "",
} as const;

View File

@@ -1,23 +1,15 @@
"use client";
import { Card, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Card, CardHeader, CardTitle } from "@/components/ui/card";
import { PlayerWalletPanel } from "@/modules/wallet/wallet-console";
/** PRD 15.3:玩家查询(当前对接 `GET .../players/{id}/wallets`)。 */
export function PlayersConsole(): React.ReactElement {
return (
<div className="flex w-full max-w-none flex-col gap-6">
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription>
{" "}
<code className="rounded bg-muted px-1">
prd.users.manage | view_finance | view_cs
</code>{" "}
/ API
</CardDescription>
</CardHeader>
</Card>
<PlayerWalletPanel />

View File

@@ -1,5 +1,5 @@
export const reconcileModuleMeta = {
segment: "reconcile",
title: "对账",
description: "钱包对账任务列表、创建与明细PRD §8 钱包对账)。",
description: "",
} as const;

View File

@@ -11,7 +11,7 @@ import {
import { AdminListPaginationFooter } from "@/components/admin/admin-list-pagination-footer";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
@@ -143,10 +143,6 @@ export function ReconcileConsole(): React.ReactElement {
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription>
<code className="rounded bg-muted px-1">prd.wallet_reconcile.manage</code>
</CardDescription>
</CardHeader>
<CardContent className="grid max-w-3xl gap-4">
<div className="grid gap-1.5">
@@ -193,16 +189,13 @@ export function ReconcileConsole(): React.ReactElement {
</CardContent>
</Card>
) : (
<p className="text-muted-foreground text-sm">
·
</p>
<p className="text-muted-foreground text-sm"></p>
)}
<Card>
<CardHeader className="flex flex-row flex-wrap items-end justify-between gap-4">
<div>
<CardTitle></CardTitle>
<CardDescription></CardDescription>
</div>
<Button type="button" variant="secondary" size="sm" onClick={() => void loadJobs()}>
@@ -290,13 +283,16 @@ export function ReconcileConsole(): React.ReactElement {
<Card>
<CardHeader>
<CardTitle> #{selectedId} </CardTitle>
<CardDescription>
{itemsLoading ? "加载中…" : items?.job_no ?? ""}
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
{itemsLoading && !items ? (
<p className="text-sm text-muted-foreground"></p>
) : null}
{items ? (
<>
{items.job_no ? (
<p className="font-mono text-sm text-muted-foreground">{items.job_no}</p>
) : null}
<div className="rounded-md border">
<Table>
<TableHeader>

View File

@@ -1,5 +1,5 @@
export const reportsModuleMeta = {
segment: "reports",
title: "报表导出",
description: "创建异步导出任务并查看历史记录PRD §8 报表权限)。",
title: "报表",
description: "",
} as const;

View File

@@ -7,7 +7,7 @@ import { getAdminReportJobs, postAdminReportJob } from "@/api/admin-reports";
import { AdminListPaginationFooter } from "@/components/admin/admin-list-pagination-footer";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Label } from "@/components/ui/label";
import {
Select,
@@ -105,11 +105,6 @@ export function ReportsConsole(): React.ReactElement {
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription>
<code className="rounded bg-muted px-1">POST /api/v1/admin/report-jobs</code>
<code className="rounded bg-muted px-1">output_path</code>{" "}
</CardDescription>
</CardHeader>
<CardContent className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
<div className="grid gap-1.5">
@@ -177,7 +172,6 @@ export function ReportsConsole(): React.ReactElement {
<CardHeader className="flex flex-row flex-wrap items-end justify-between gap-4">
<div>
<CardTitle></CardTitle>
<CardDescription></CardDescription>
</div>
<Button type="button" variant="secondary" size="sm" onClick={() => void load()}>

View File

@@ -1,5 +1,5 @@
export const riskModuleMeta = {
segment: "risk",
title: "风控",
description: "规则、告警与黑名单(占位)。",
description: "",
} as const;

View File

@@ -48,10 +48,6 @@ export function RiskDrawHeader({ drawId }: { drawId: number }) {
<DrawStatusBadge status={draw.status} />
<span className="text-xs opacity-80">{draw.hall_preview_status}</span>
</p>
<p className="text-xs text-muted-foreground">
`risk_pools` /
`risk_pool_lock_logs`
</p>
</div>
);
}

View File

@@ -5,7 +5,7 @@ import { useCallback, useEffect, useState } from "react";
import { getAdminDraws } from "@/api/admin-draws";
import { buttonVariants } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import {
Table,
TableBody,
@@ -52,9 +52,6 @@ export function RiskIndexConsole() {
<Card>
<CardHeader>
<CardTitle className="text-lg"></CardTitle>
<CardDescription>
§13.4
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
{error ? <p className="text-sm text-destructive">{error}</p> : null}

View File

@@ -5,7 +5,7 @@ import { useCallback, useEffect, useState } from "react";
import { getAdminRiskPoolLockLogs } from "@/api/admin-risk";
import { AdminListPaginationFooter } from "@/components/admin/admin-list-pagination-footer";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
@@ -77,9 +77,6 @@ export function RiskLockLogsConsole({ drawId }: { drawId: number }) {
<Card>
<CardHeader>
<CardTitle className="text-lg"></CardTitle>
<CardDescription>
/ `risk_pool_lock_logs`
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid max-w-full gap-3 sm:grid-cols-[minmax(0,8rem)_minmax(0,10rem)_auto] sm:items-end">

View File

@@ -6,7 +6,7 @@ import { useCallback, useEffect, useState } from "react";
import { getAdminRiskPoolDetail } from "@/api/admin-risk";
import { AdminListPaginationFooter } from "@/components/admin/admin-list-pagination-footer";
import { buttonVariants } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import {
Table,
TableBody,
@@ -62,9 +62,9 @@ export function RiskPoolDetailConsole({
<Card className="border-destructive/40">
<CardHeader>
<CardTitle className="text-lg"></CardTitle>
<CardDescription>{error}</CardDescription>
</CardHeader>
<CardContent>
<CardContent className="space-y-2">
<p className="text-sm text-destructive">{error}</p>
<Link
href={`/admin/risk/draws/${drawId}/pools`}
className={cn(buttonVariants({ variant: "outline", size: "sm" }))}
@@ -102,9 +102,7 @@ export function RiskPoolDetailConsole({
<CardTitle className="text-lg">
<span className="font-mono">{pool.normalized_number}</span>
</CardTitle>
<CardDescription>
{data.draw_no} · `risk_pools` 0 §6.4
</CardDescription>
<p className="text-sm text-muted-foreground"> {data.draw_no}</p>
</CardHeader>
<CardContent className="grid gap-3 sm:grid-cols-2 lg:grid-cols-4">
<div className="rounded-lg border bg-muted/40 p-3">
@@ -139,7 +137,6 @@ export function RiskPoolDetailConsole({
<Card>
<CardHeader>
<CardTitle className="text-base"> / </CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="overflow-x-auto rounded-md border">

View File

@@ -6,7 +6,7 @@ import { useCallback, useEffect, useState } from "react";
import { getAdminRiskPools } from "@/api/admin-risk";
import { AdminListPaginationFooter } from "@/components/admin/admin-list-pagination-footer";
import { buttonVariants } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Label } from "@/components/ui/label";
import {
Select,
@@ -39,7 +39,6 @@ const SORT_OPTIONS: { value: "usage_desc" | "locked_desc" | "remaining_asc" | "n
type RiskPoolsConsoleProps = {
drawId: number;
title: string;
description: string;
soldOutOnly: boolean;
defaultSort: "usage_desc" | "locked_desc" | "remaining_asc" | "number_asc";
allowSortChange?: boolean;
@@ -48,7 +47,6 @@ type RiskPoolsConsoleProps = {
export function RiskPoolsConsole({
drawId,
title,
description,
soldOutOnly,
defaultSort,
allowSortChange = false,
@@ -91,7 +89,6 @@ export function RiskPoolsConsole({
<Card>
<CardHeader className="space-y-2">
<CardTitle className="text-lg">{title}</CardTitle>
<CardDescription>{description}</CardDescription>
{allowSortChange ? (
<div className="flex max-w-xs flex-col gap-2 sm:flex-row sm:items-end">
<div className="space-y-1.5">

View File

@@ -1,5 +1,5 @@
export const serviceDeskModuleMeta = {
segment: "service_desk",
title: "客服 / 财务",
description: "PRD §15.4 能力入口:注单、流水、转账失败、期号收支、报表。",
segment: "service-desk",
title: "服务台",
description: "",
} as const;

View File

@@ -1,58 +1,29 @@
"use client";
import type { ReactElement } from "react";
import Link from "next/link";
import { buttonVariants } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { cn } from "@/lib/utils";
const items: { title: string; desc: string; href: string; hint: string }[] = [
{
title: "玩家注单",
desc: "按玩家 ID 查注单、失败原因、中奖金额。",
href: "/admin/tickets",
hint: "GET …/players/{id}/ticket-items",
},
{
title: "钱包流水",
desc: "按玩家、单号、日期筛选流水。",
href: "/admin/wallet/transactions",
hint: "GET …/wallet/transactions",
},
{
title: "转账单与失败原因",
desc: "筛选 status=failed 或异常单,查看 fail_reason 列。",
href: "/admin/wallet/transfer-orders",
hint: "GET …/wallet/transfer-orders",
},
{
title: "期号列表 → 期号收支",
desc: "开奖 → 点期号 →「期号收支」看当期投注/派彩与结算批次。",
href: "/admin/draws",
hint: "GET …/draws/{id}/finance-summary",
},
{
title: "报表导出",
desc: "异步导出任务(如钱包流水日报)。",
href: "/admin/reports",
hint: "POST …/report-jobs",
},
const items: { title: string; href: string }[] = [
{ title: "玩家注单", href: "/admin/tickets" },
{ title: "钱包流水", href: "/admin/wallet/transactions" },
{ title: "转账单", href: "/admin/wallet/transfer-orders" },
{ title: "期号列表", href: "/admin/draws" },
{ title: "报表导出", href: "/admin/reports" },
];
/** PRD §15.4 客服/财务能力入口聚合 */
export function ServiceDeskConsole(): React.ReactElement {
export function ServiceDeskConsole(): ReactElement {
return (
<div className="grid gap-6 md:grid-cols-2">
{items.map((it) => (
<Card key={it.href}>
<CardHeader>
<CardTitle className="text-base">{it.title}</CardTitle>
<CardDescription>{it.desc}</CardDescription>
</CardHeader>
<CardContent className="flex flex-col gap-3">
<code className="block rounded-md bg-muted px-2 py-1.5 font-mono text-[11px] text-muted-foreground">
{it.hint}
</code>
<CardContent>
<Link href={it.href} className={cn(buttonVariants({ size: "sm" }), "w-fit")}>
</Link>

View File

@@ -1,5 +1,5 @@
export const settingsModuleMeta = {
segment: "settings",
title: "系统设置",
description: "运营参数与权限(占位)。",
description: "",
} as const;

View File

@@ -1,4 +1,4 @@
export const settlementModuleMeta = {
title: "结算批次",
description: "按期查看结算批次与注单结算明细",
title: "结算",
description: "",
} as const;

View File

@@ -7,7 +7,7 @@ import { getAdminSettlementBatch, getAdminSettlementBatchDetails } from "@/api/a
import { AdminListPaginationFooter } from "@/components/admin/admin-list-pagination-footer";
import { ModuleScaffold } from "@/components/admin/module-scaffold";
import { Button, buttonVariants } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import {
Table,
TableBody,
@@ -74,9 +74,9 @@ export function SettlementBatchDetailsConsole({ batchId }: Props) {
<Card className="border-destructive/40">
<CardHeader>
<CardTitle className="text-base"></CardTitle>
<CardDescription>{err}</CardDescription>
</CardHeader>
<CardContent>
<CardContent className="space-y-2">
<p className="text-sm text-destructive">{err}</p>
<Button type="button" size="sm" variant="secondary" onClick={() => void load()}>
</Button>
@@ -88,10 +88,10 @@ export function SettlementBatchDetailsConsole({ batchId }: Props) {
<Card className="mb-6">
<CardHeader>
<CardTitle className="font-mono text-base"> #{summary.id}</CardTitle>
<CardDescription>
<p className="text-sm text-muted-foreground">
{summary.draw_no ?? "—"} · {summary.draw_status ?? "—"} · v
{summary.result_batch_version ?? "—"}
</CardDescription>
</p>
</CardHeader>
<CardContent className="grid gap-2 text-sm sm:grid-cols-2">
<p>
@@ -131,7 +131,6 @@ export function SettlementBatchDetailsConsole({ batchId }: Props) {
<Card>
<CardHeader>
<CardTitle className="text-base"></CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
{details ? (

View File

@@ -7,7 +7,7 @@ import { getAdminSettlementBatches } from "@/api/admin-settlement";
import { AdminListPaginationFooter } from "@/components/admin/admin-list-pagination-footer";
import { ModuleScaffold } from "@/components/admin/module-scaffold";
import { Button, buttonVariants } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
@@ -90,12 +90,10 @@ export function SettlementBatchesConsole() {
<ModuleScaffold>
<div className="mb-6">
<h1 className="text-lg font-semibold tracking-tight">{settlementModuleMeta.title}</h1>
<p className="text-muted-foreground mt-1 text-sm">{settlementModuleMeta.description}</p>
</div>
<Card className="mb-6">
<CardHeader className="pb-3">
<CardTitle className="text-base"></CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent className="flex flex-col gap-4 sm:flex-row sm:flex-wrap sm:items-end">
<div className="flex min-w-[12rem] flex-1 flex-col gap-1.5">
@@ -132,7 +130,6 @@ export function SettlementBatchesConsole() {
<Card>
<CardHeader>
<CardTitle className="text-base"></CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
{error ? <p className="text-destructive text-sm">{error}</p> : null}

View File

@@ -1,5 +1,5 @@
export const ticketsModuleMeta = {
segment: "tickets",
title: "玩家注单",
description: "PRD §15.4:按玩家查注单(管理端 ticket-items API",
title: "注单",
description: "",
} as const;

View File

@@ -5,7 +5,7 @@ import { useCallback, useEffect, useState } from "react";
import { getAdminPlayerTicketItems } from "@/api/admin-player-tickets";
import { AdminListPaginationFooter } from "@/components/admin/admin-list-pagination-footer";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
@@ -19,7 +19,6 @@ import {
import { LotteryApiBizError } from "@/types/api/errors";
import type { AdminPlayerTicketItemsData } from "@/types/api/admin-player-tickets";
/** PRD §15.4:按玩家主键查注单(`GET …/admin/players/{id}/ticket-items` */
export function PlayerTicketsConsole(): React.ReactElement {
const [playerIdDraft, setPlayerIdDraft] = useState("");
const [drawNoDraft, setDrawNoDraft] = useState("");
@@ -77,10 +76,6 @@ export function PlayerTicketsConsole(): React.ReactElement {
<Card className="w-full max-w-none">
<CardHeader>
<CardTitle></CardTitle>
<CardDescription>
<code className="rounded bg-muted px-1 text-xs">prd.users.view_cs | view_finance | manage</code>
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex flex-wrap items-end gap-3">

View File

@@ -1,5 +1,5 @@
export const walletModuleMeta = {
segment: "wallet",
title: "钱包流水与对账",
description:""
description: "",
} as const;

View File

@@ -13,7 +13,7 @@ import { AdminDateRangeField } from "@/components/admin/admin-date-range-field";
import { AdminListPaginationFooter } from "@/components/admin/admin-list-pagination-footer";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Checkbox } from "@/components/ui/checkbox";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
@@ -243,11 +243,6 @@ export function TransferOrdersPanel(): React.ReactElement {
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription>
§5.11 {" "}
<code className="rounded bg-muted px-1">player_id</code> ID PRD §15.4{" "}
<code className="rounded bg-muted px-1">status=failed</code>
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
@@ -510,10 +505,6 @@ export function WalletTxnsPanel(): React.ReactElement {
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription>
§5.11 {" "}
<code className="rounded bg-muted px-1">pending_reconcile</code>
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
@@ -771,10 +762,6 @@ export function PlayerWalletPanel(): React.ReactElement {
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription>
<code className="rounded bg-muted px-1">players.id</code>{" "}
§5.12
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex flex-wrap items-end gap-2">