feat: 重构管理端列表与风控/结算导航,新增表格导出和结算审核确认

This commit is contained in:
2026-05-19 17:06:56 +08:00
parent a1fb163f1b
commit 37b13278ef
47 changed files with 1255 additions and 524 deletions

View File

@@ -33,6 +33,12 @@ import {
PRD_PAYOUT_REVIEW,
} from "./draw-prd";
function drawStatusText(status: string, t: (key: string) => string): string {
const key = `statusOptions.${status}`;
const translated = t(key);
return translated === key ? status : translated;
}
function Field({ label, children }: { label: string; children: React.ReactNode }) {
return (
<div className="grid gap-1 sm:grid-cols-[10rem_1fr] sm:items-start">
@@ -114,12 +120,16 @@ export function DrawDetailConsole({ drawId }: { drawId: string }) {
<CardTitle className="text-xl">{data.draw_no}</CardTitle>
<p className="mt-1 text-sm text-muted-foreground">{t("drawDetail")}</p>
</div>
<div className="flex flex-wrap items-center gap-2">
<DrawStatusBadge status={data.status} label={data.status} />
<div className="flex flex-col items-end gap-1 text-right">
<DrawStatusBadge
status={data.hall_preview_status}
label={t("hallPreviewStatus", { status: data.hall_preview_status })}
status={data.status}
label={drawStatusText(data.status, t)}
/>
<p className="text-sm text-muted-foreground">
{t("hallPreviewStatus", {
status: drawStatusText(data.hall_preview_status, t),
})}
</p>
</div>
</div>
</CardHeader>

View File

@@ -7,6 +7,7 @@ import { useTranslation } from "react-i18next";
import { getAdminDrawFinanceSummary } from "@/api/admin-draws";
import { postAdminRunDrawSettlement } from "@/api/admin-settlement";
import { Button, buttonVariants } from "@/components/ui/button";
import { AdminTableExportButton } from "@/components/admin/admin-table-export-button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import {
Table,
@@ -156,7 +157,14 @@ export function DrawFinanceConsole({ drawId }: { drawId: string }): React.ReactE
<p className="text-muted-foreground text-sm">{t("noSettlementBatches")}</p>
) : (
<div className="overflow-x-auto rounded-md border">
<Table>
<div className="admin-table-toolbar">
<AdminTableExportButton
tableId={`draw-finance-table-${drawId}`}
filename={`期号收支-${data.draw_no}`}
sheetName="期号收支"
/>
</div>
<Table id={`draw-finance-table-${drawId}`}>
<TableHeader>
<TableRow>
<TableHead className="w-20">ID</TableHead>

View File

@@ -12,6 +12,10 @@ const segments = [
{ suffix: "/results", key: "results", label: "subnav.results" },
{ suffix: "/finance", key: "finance", label: "subnav.finance" },
{ suffix: "/review", key: "review", label: "subnav.review" },
{ suffix: "/risk/occupancy", key: "riskOccupancy", label: "subnav.riskOccupancy" },
{ suffix: "/risk/hot", key: "riskHot", label: "subnav.riskHot" },
{ suffix: "/risk/sold-out", key: "riskSoldOut", label: "subnav.riskSoldOut" },
{ suffix: "/risk/pools", key: "riskPools", label: "subnav.riskPools" },
] as const;
function isReviewTabActive(pathname: string, base: string): boolean {

View File

@@ -7,6 +7,7 @@ import { toast } from "sonner";
import { getAdminDraws, postAdminGenerateDrawPlan } from "@/api/admin-draws";
import { Button, buttonVariants } from "@/components/ui/button";
import { AdminTableExportButton } from "@/components/admin/admin-table-export-button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { AdminListPaginationFooter } from "@/components/admin/admin-list-pagination-footer";
import { Input } from "@/components/ui/input";
@@ -140,60 +141,66 @@ export function DrawsIndexConsole() {
}, [load]);
return (
<Card>
<CardHeader className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<CardTitle className="text-lg">{t("statusListTitle")}</CardTitle>
<Card className="admin-list-card">
<CardHeader className="admin-list-header flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<CardTitle className="admin-list-title">{t("statusListTitle")}</CardTitle>
{canManageDraw ? (
<Button type="button" onClick={() => void generatePlan()} disabled={generating}>
{generating ? t("generating") : t("generatePlan")}
</Button>
) : null}
</CardHeader>
<CardContent className="space-y-4">
{/* Grid桌面端标签一行 / 控件一行,避免 flex+items-end 与各列实际高度不一致;移动端单列自上而下 */}
<div
className="grid max-w-full gap-x-6 gap-y-3 sm:grid-cols-[minmax(0,12rem)_minmax(0,11rem)_auto] sm:gap-y-1.5"
>
<Label htmlFor="draw-filter-no">
{t("drawNo")}
</Label>
<Input
id="draw-filter-no"
placeholder={t("fuzzyDrawNo")}
value={draftDrawNo}
className="w-full min-w-0 sm:w-full"
onChange={(e) => setDraftDrawNo(e.target.value)}
/>
<Label htmlFor="draw-filter-status">
{t("status")}
</Label>
<div className="min-w-0">
<Select
modal={false}
value={
draftStatus === "" ||
!DRAW_STATUS_OPTIONS.some((o) => o.value === draftStatus)
? DRAW_FILTER_ALL
: draftStatus
}
onValueChange={(v) =>
setDraftStatus(v == null || v === DRAW_FILTER_ALL ? "" : String(v))
}
>
<SelectTrigger id="draw-filter-status" className="h-8 w-full min-w-0 sm:w-44">
<SelectValue>{drawStatusTriggerLabel}</SelectValue>
</SelectTrigger>
<SelectContent align="start" sideOffset={6}>
<SelectItem value={DRAW_FILTER_ALL}>{t("statusOptions.all")}</SelectItem>
{DRAW_STATUS_OPTIONS.map((o) => (
<SelectItem key={o.value} value={o.value}>
{t(o.label)}
</SelectItem>
))}
</SelectContent>
</Select>
<CardContent className="admin-list-content">
<div className="admin-list-toolbar">
<div className="admin-list-field xl:min-w-0">
<Label htmlFor="draw-filter-no" className="sm:w-10 sm:shrink-0">
{t("drawNo")}
</Label>
<Input
id="draw-filter-no"
placeholder={t("fuzzyDrawNo")}
value={draftDrawNo}
className="w-full sm:w-[16rem] xl:w-[20rem]"
onChange={(e) => setDraftDrawNo(e.target.value)}
/>
</div>
<div className="[grid-area:act] flex flex-wrap gap-2">
<div className="admin-list-field">
<Label htmlFor="draw-filter-status" className="sm:w-10 sm:shrink-0">
{t("status")}
</Label>
<div className="min-w-0">
<Select
modal={false}
value={
draftStatus === "" ||
!DRAW_STATUS_OPTIONS.some((o) => o.value === draftStatus)
? DRAW_FILTER_ALL
: draftStatus
}
onValueChange={(v) =>
setDraftStatus(v == null || v === DRAW_FILTER_ALL ? "" : String(v))
}
>
<SelectTrigger id="draw-filter-status" className="h-8 w-full sm:w-44">
<SelectValue>{drawStatusTriggerLabel}</SelectValue>
</SelectTrigger>
<SelectContent align="start" sideOffset={6}>
<SelectItem value={DRAW_FILTER_ALL}>{t("statusOptions.all")}</SelectItem>
{DRAW_STATUS_OPTIONS.map((o) => (
<SelectItem key={o.value} value={o.value}>
{t(o.label)}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
<div className="admin-list-actions">
<AdminTableExportButton
tableId="draws-index-table"
filename="期号列表"
sheetName="期号列表"
/>
<Button
type="button"
onClick={() => {
@@ -224,8 +231,8 @@ export function DrawsIndexConsole() {
<p className="text-sm text-destructive">{error}</p>
) : null}
<div className="rounded-lg border border-border">
<Table>
<div className="admin-table-shell">
<Table id="draws-index-table">
<TableHeader>
<TableRow>
<TableHead>{t("drawNo")}</TableHead>