feat: 增加管理端多语言与风控/报表/奖池操作能力

This commit is contained in:
2026-05-18 15:08:34 +08:00
parent afffa4e508
commit 49a4caf01e
31 changed files with 918 additions and 115 deletions

View File

@@ -2,7 +2,11 @@
import { useCallback, useEffect, useState } from "react";
import { getAdminJackpotPools, putAdminJackpotPool } from "@/api/admin-jackpot";
import {
getAdminJackpotPools,
postAdminJackpotManualBurst,
putAdminJackpotPool,
} from "@/api/admin-jackpot";
import { ModuleScaffold } from "@/components/admin/module-scaffold";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
@@ -27,7 +31,10 @@ type Draft = {
payout_rate: string;
force_trigger_draw_gap: string;
min_bet_amount: string;
combo_trigger_play_codes: string;
status: string;
manual_burst_draw_id: string;
manual_burst_amount: string;
};
function toDraft(p: AdminJackpotPoolRow): Draft {
@@ -38,7 +45,10 @@ function toDraft(p: AdminJackpotPoolRow): Draft {
payout_rate: String(p.payout_rate),
force_trigger_draw_gap: String(p.force_trigger_draw_gap),
min_bet_amount: String(p.min_bet_amount),
combo_trigger_play_codes: p.combo_trigger_play_codes.join(","),
status: String(p.status),
manual_burst_draw_id: "",
manual_burst_amount: "",
};
}
@@ -47,6 +57,7 @@ export function JackpotPoolsConsole() {
const [drafts, setDrafts] = useState<Record<number, Draft>>({});
const [loading, setLoading] = useState(true);
const [savingId, setSavingId] = useState<number | null>(null);
const [burstingId, setBurstingId] = useState<number | null>(null);
const load = useCallback(async () => {
setLoading(true);
@@ -90,6 +101,10 @@ export function JackpotPoolsConsole() {
payout_rate: Number(d.payout_rate),
force_trigger_draw_gap: Number.parseInt(d.force_trigger_draw_gap, 10),
min_bet_amount: Number.parseInt(d.min_bet_amount, 10),
combo_trigger_play_codes: d.combo_trigger_play_codes
.split(",")
.map((v) => v.trim().toLowerCase())
.filter(Boolean),
status: Number.parseInt(d.status, 10),
});
toast.success("已保存");
@@ -101,6 +116,34 @@ export function JackpotPoolsConsole() {
}
};
const manualBurst = async (p: AdminJackpotPoolRow) => {
const d = drafts[p.id];
if (!d) return;
const drawId = Number.parseInt(d.manual_burst_draw_id, 10);
if (!Number.isFinite(drawId) || drawId <= 0) {
toast.error("请填写有效的期号 ID");
return;
}
const amount = d.manual_burst_amount.trim()
? Number.parseInt(d.manual_burst_amount, 10)
: undefined;
setBurstingId(p.id);
try {
await postAdminJackpotManualBurst(p.id, {
draw_id: drawId,
amount: amount !== undefined && Number.isFinite(amount) ? amount : undefined,
});
toast.success("已手动触发爆池");
await load();
} catch (e) {
toast.error(e instanceof LotteryApiBizError ? e.message : "手动爆池失败");
} finally {
setBurstingId(null);
}
};
return (
<ModuleScaffold>
<Card>
@@ -177,6 +220,16 @@ export function JackpotPoolsConsole() {
onChange={(e) => updateDraft(p.id, { min_bet_amount: e.target.value })}
/>
</div>
<div className="space-y-1.5">
<Label htmlFor={`combo-${p.id}`}></Label>
<Input
id={`combo-${p.id}`}
className="font-mono"
value={d.combo_trigger_play_codes}
placeholder="straight,ibox"
onChange={(e) => updateDraft(p.id, { combo_trigger_play_codes: e.target.value })}
/>
</div>
<div className="space-y-1.5">
<Label></Label>
<Select
@@ -198,6 +251,36 @@ export function JackpotPoolsConsole() {
{savingId === p.id ? "保存中…" : "保存"}
</Button>
</div>
<div className="rounded-md border border-amber-200 bg-amber-50 p-3">
<div className="grid gap-3 sm:grid-cols-[1fr_1fr_auto] sm:items-end">
<div className="space-y-1.5">
<Label htmlFor={`burst-draw-${p.id}`}> ID</Label>
<Input
id={`burst-draw-${p.id}`}
className="font-mono"
value={d.manual_burst_draw_id}
onChange={(e) => updateDraft(p.id, { manual_burst_draw_id: e.target.value })}
/>
</div>
<div className="space-y-1.5">
<Label htmlFor={`burst-amount-${p.id}`}></Label>
<Input
id={`burst-amount-${p.id}`}
className="font-mono"
value={d.manual_burst_amount}
onChange={(e) => updateDraft(p.id, { manual_burst_amount: e.target.value })}
/>
</div>
<Button
type="button"
variant="destructive"
disabled={burstingId === p.id}
onClick={() => void manualBurst(p)}
>
{burstingId === p.id ? "处理中…" : "手动爆池"}
</Button>
</div>
</div>
</div>
);
})}