feat: 扩展开奖与结算管理,支持手动操作、导出和版本展示
This commit is contained in:
@@ -2,8 +2,16 @@
|
||||
|
||||
import Link from "next/link";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import { getAdminSettlementBatch, getAdminSettlementBatchDetails } from "@/api/admin-settlement";
|
||||
import {
|
||||
downloadAdminSettlementBatchExport,
|
||||
getAdminSettlementBatch,
|
||||
getAdminSettlementBatchDetails,
|
||||
postAdminApproveSettlementBatch,
|
||||
postAdminPayoutSettlementBatch,
|
||||
postAdminRejectSettlementBatch,
|
||||
} 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";
|
||||
@@ -37,6 +45,7 @@ export function SettlementBatchDetailsConsole({ batchId }: Props) {
|
||||
const [err, setErr] = useState<string | null>(null);
|
||||
const [page, setPage] = useState(1);
|
||||
const [perPage, setPerPage] = useState(25);
|
||||
const [acting, setActing] = useState<string | null>(null);
|
||||
|
||||
const load = useCallback(async () => {
|
||||
setLoading(true);
|
||||
@@ -57,6 +66,38 @@ export function SettlementBatchDetailsConsole({ batchId }: Props) {
|
||||
}
|
||||
}, [batchId, page, perPage]);
|
||||
|
||||
async function runAction(label: string, action: () => Promise<unknown>): Promise<void> {
|
||||
setActing(label);
|
||||
try {
|
||||
await action();
|
||||
toast.success(`${label}成功`);
|
||||
await load();
|
||||
} catch (e) {
|
||||
toast.error(e instanceof LotteryApiBizError ? e.message : `${label}失败`);
|
||||
} finally {
|
||||
setActing(null);
|
||||
}
|
||||
}
|
||||
|
||||
async function exportCsv(): Promise<void> {
|
||||
setActing("导出");
|
||||
try {
|
||||
const blob = await downloadAdminSettlementBatchExport(batchId);
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = `settlement-${batchId}.csv`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
URL.revokeObjectURL(url);
|
||||
} catch (e) {
|
||||
toast.error(e instanceof LotteryApiBizError ? e.message : "导出失败");
|
||||
} finally {
|
||||
setActing(null);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const t = window.setTimeout(() => void load(), 0);
|
||||
return () => window.clearTimeout(t);
|
||||
@@ -98,6 +139,10 @@ export function SettlementBatchDetailsConsole({ batchId }: Props) {
|
||||
<span className="text-muted-foreground">结算状态</span>{" "}
|
||||
<span className="font-mono">{summary.status}</span>
|
||||
</p>
|
||||
<p>
|
||||
<span className="text-muted-foreground">审核状态</span>{" "}
|
||||
<span className="font-mono">{summary.review_status ?? "—"}</span>
|
||||
</p>
|
||||
<p>
|
||||
<span className="text-muted-foreground">注单数</span>{" "}
|
||||
<span className="tabular-nums">{summary.total_ticket_count}</span>
|
||||
@@ -122,6 +167,37 @@ export function SettlementBatchDetailsConsole({ batchId }: Props) {
|
||||
<p>
|
||||
<span className="text-muted-foreground">结束</span> {formatDt(summary.finished_at)}
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-2 sm:col-span-2">
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="outline"
|
||||
disabled={acting !== null || summary.status !== "pending_review"}
|
||||
onClick={() => void runAction("审核通过", () => postAdminApproveSettlementBatch(batchId))}
|
||||
>
|
||||
审核通过
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="outline"
|
||||
disabled={acting !== null || summary.status !== "pending_review"}
|
||||
onClick={() => void runAction("驳回", () => postAdminRejectSettlementBatch(batchId))}
|
||||
>
|
||||
驳回
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
disabled={acting !== null || summary.status !== "approved"}
|
||||
onClick={() => void runAction("执行派彩", () => postAdminPayoutSettlementBatch(batchId))}
|
||||
>
|
||||
执行派彩
|
||||
</Button>
|
||||
<Button type="button" size="sm" variant="secondary" disabled={acting !== null} onClick={() => void exportCsv()}>
|
||||
导出结算报表
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : loading ? (
|
||||
|
||||
@@ -2,8 +2,15 @@
|
||||
|
||||
import Link from "next/link";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import { getAdminSettlementBatches } from "@/api/admin-settlement";
|
||||
import {
|
||||
downloadAdminSettlementBatchExport,
|
||||
getAdminSettlementBatches,
|
||||
postAdminApproveSettlementBatch,
|
||||
postAdminPayoutSettlementBatch,
|
||||
postAdminRejectSettlementBatch,
|
||||
} 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";
|
||||
@@ -52,6 +59,7 @@ export function SettlementBatchesConsole() {
|
||||
const [appliedStatus, setAppliedStatus] = useState(STATUS_ALL);
|
||||
const [page, setPage] = useState(1);
|
||||
const [perPage, setPerPage] = useState(20);
|
||||
const [actingId, setActingId] = useState<number | null>(null);
|
||||
|
||||
const load = useCallback(async () => {
|
||||
setLoading(true);
|
||||
@@ -86,6 +94,38 @@ export function SettlementBatchesConsole() {
|
||||
setPage(1);
|
||||
};
|
||||
|
||||
async function runBatchAction(batchId: number, label: string, action: () => Promise<unknown>): Promise<void> {
|
||||
setActingId(batchId);
|
||||
try {
|
||||
await action();
|
||||
toast.success(`${label}成功`);
|
||||
await load();
|
||||
} catch (e) {
|
||||
toast.error(e instanceof LotteryApiBizError ? e.message : `${label}失败`);
|
||||
} finally {
|
||||
setActingId(null);
|
||||
}
|
||||
}
|
||||
|
||||
async function exportBatch(batchId: number): Promise<void> {
|
||||
setActingId(batchId);
|
||||
try {
|
||||
const blob = await downloadAdminSettlementBatchExport(batchId);
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = `settlement-${batchId}.csv`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
URL.revokeObjectURL(url);
|
||||
} catch (e) {
|
||||
toast.error(e instanceof LotteryApiBizError ? e.message : "导出失败");
|
||||
} finally {
|
||||
setActingId(null);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<ModuleScaffold>
|
||||
<div className="mb-6">
|
||||
@@ -142,6 +182,7 @@ export function SettlementBatchesConsole() {
|
||||
<TableHead>ID</TableHead>
|
||||
<TableHead>期号</TableHead>
|
||||
<TableHead>版本</TableHead>
|
||||
<TableHead>审核状态</TableHead>
|
||||
<TableHead>状态</TableHead>
|
||||
<TableHead className="text-right">注单数</TableHead>
|
||||
<TableHead className="text-right">中奖笔数</TableHead>
|
||||
@@ -157,6 +198,9 @@ export function SettlementBatchesConsole() {
|
||||
<TableCell className="font-mono text-xs">{row.id}</TableCell>
|
||||
<TableCell className="font-mono text-sm">{row.draw_no ?? "—"}</TableCell>
|
||||
<TableCell className="font-mono text-xs">v{row.settle_version}</TableCell>
|
||||
<TableCell className="text-xs text-muted-foreground">
|
||||
{row.review_status ?? "—"}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<span
|
||||
className={cn(
|
||||
@@ -181,12 +225,49 @@ export function SettlementBatchesConsole() {
|
||||
{formatDt(row.finished_at ?? row.started_at)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Link
|
||||
href={`/admin/settlement-batches/${row.id}/details`}
|
||||
className={cn(buttonVariants({ variant: "link", size: "sm" }), "px-0")}
|
||||
>
|
||||
明细
|
||||
</Link>
|
||||
<div className="flex flex-wrap justify-end gap-1.5">
|
||||
<Link
|
||||
href={`/admin/settlement-batches/${row.id}/details`}
|
||||
className={cn(buttonVariants({ variant: "link", size: "sm" }), "px-0")}
|
||||
>
|
||||
明细
|
||||
</Link>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="outline"
|
||||
disabled={actingId !== null || row.status !== "pending_review"}
|
||||
onClick={() => void runBatchAction(row.id, "审核通过", () => postAdminApproveSettlementBatch(row.id))}
|
||||
>
|
||||
通过
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="outline"
|
||||
disabled={actingId !== null || row.status !== "pending_review"}
|
||||
onClick={() => void runBatchAction(row.id, "驳回", () => postAdminRejectSettlementBatch(row.id))}
|
||||
>
|
||||
驳回
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
disabled={actingId !== null || row.status !== "approved"}
|
||||
onClick={() => void runBatchAction(row.id, "执行派彩", () => postAdminPayoutSettlementBatch(row.id))}
|
||||
>
|
||||
派彩
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
disabled={actingId !== null}
|
||||
onClick={() => void exportBatch(row.id)}
|
||||
>
|
||||
导出
|
||||
</Button>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user