refactor: 移除冗余注释和描述,优化管理员模块的代码结构
This commit is contained in:
@@ -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()}>
|
||||
刷新
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export const auditModuleMeta = {
|
||||
segment: "audit",
|
||||
export const auditLogsModuleMeta = {
|
||||
segment: "audit-logs",
|
||||
title: "审计日志",
|
||||
description: "运营留痕查询(权限范围由后端按角色过滤)。",
|
||||
description: "",
|
||||
} as const;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export const authModuleMeta = {
|
||||
segment: "auth",
|
||||
segment: "login",
|
||||
title: "登录",
|
||||
description: "账号、密码与图形验证码登录;对接 Laravel Sanctum。",
|
||||
description: "",
|
||||
} as const;
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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.5:分类与玩法切换编辑五档赔率;odds_value = 赔率乘数 × 10000(NPR 100 基准展示)。
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/** 对齐界面文档 §5.5:头 / 二 / 三 / 特别 / 安慰(含 starter / consolation 映射)。 */
|
||||
/** 奖项档位顺序(含 starter / consolation)。 */
|
||||
|
||||
export const PRIZE_SCOPE_ORDER = [
|
||||
"first",
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export const dashboardModuleMeta = {
|
||||
segment: "dashboard",
|
||||
title: "仪表盘",
|
||||
description: "仪表盘:大厅当前期、投注/派彩/风控占用与快捷入口。",
|
||||
description: "",
|
||||
} as const;
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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 ? (
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 ? (
|
||||
|
||||
@@ -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 与各列实际高度不一致;移动端单列自上而下 */}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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 ? (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export const jackpotModuleMeta = {
|
||||
title: "Jackpot",
|
||||
description: "奖池配置与蓄水 / 派彩记录",
|
||||
title: "奖池",
|
||||
description: "",
|
||||
} as const;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export const playersModuleMeta = {
|
||||
segment: "players",
|
||||
title: "玩家查询",
|
||||
description: "按玩家 ID 查钱包等(PRD 15.3);列表/冻结待管理端用户 API。",
|
||||
title: "玩家",
|
||||
description: "",
|
||||
} as const;
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export const reconcileModuleMeta = {
|
||||
segment: "reconcile",
|
||||
title: "对账",
|
||||
description: "钱包对账任务列表、创建与明细(PRD §8 钱包对账)。",
|
||||
description: "",
|
||||
} as const;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export const reportsModuleMeta = {
|
||||
segment: "reports",
|
||||
title: "报表导出",
|
||||
description: "创建异步导出任务并查看历史记录(PRD §8 报表权限)。",
|
||||
title: "报表",
|
||||
description: "",
|
||||
} as const;
|
||||
|
||||
@@ -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()}>
|
||||
刷新
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export const riskModuleMeta = {
|
||||
segment: "risk",
|
||||
title: "风控",
|
||||
description: "规则、告警与黑名单(占位)。",
|
||||
description: "",
|
||||
} as const;
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export const serviceDeskModuleMeta = {
|
||||
segment: "service_desk",
|
||||
title: "客服 / 财务",
|
||||
description: "PRD §15.4 能力入口:注单、流水、转账失败、期号收支、报表。",
|
||||
segment: "service-desk",
|
||||
title: "服务台",
|
||||
description: "",
|
||||
} as const;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export const settingsModuleMeta = {
|
||||
segment: "settings",
|
||||
title: "系统设置",
|
||||
description: "运营参数与权限(占位)。",
|
||||
description: "",
|
||||
} as const;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export const settlementModuleMeta = {
|
||||
title: "结算批次",
|
||||
description: "按期查看结算批次与注单结算明细",
|
||||
title: "结算",
|
||||
description: "",
|
||||
} as const;
|
||||
|
||||
@@ -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 ? (
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export const ticketsModuleMeta = {
|
||||
segment: "tickets",
|
||||
title: "玩家注单",
|
||||
description: "PRD §15.4:按玩家查注单(管理端 ticket-items API)。",
|
||||
title: "注单",
|
||||
description: "",
|
||||
} as const;
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export const walletModuleMeta = {
|
||||
segment: "wallet",
|
||||
title: "钱包流水与对账",
|
||||
description:""
|
||||
description: "",
|
||||
} as const;
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user