import { useEffect, useState } from "react"; import { App, Button, Form, Input, InputNumber, Modal, Popconfirm, Select, Space } from "antd"; import type { ColumnsType } from "antd/es/table"; import { Ban, Copy, Eye, KeyRound, RefreshCw, Search, TicketCheck } from "lucide-react"; import { PaginationBar } from "@/components/layout/workspace-page"; import { formatCredits } from "@/constant/credits"; import { useDebouncedValue } from "@/hooks/use-debounced-value"; import { createAdminRedeemBatch, disableAdminRedeemBatch, disableAdminRedeemCode, listAdminRedeemBatchCodes, listAdminRedeemBatches, type AdminRedeemCode, type RedeemBatch } from "@/services/api/wallet"; import { AdminDataTable, AdminExportButton, AdminFilterChip, AdminRowActions, AdminStatusBadge, AdminTableEmpty, type AdminStatusTone } from "./admin-ui"; type RedeemFormValues = { amount: number; count: number; note?: string; expiresAt?: string }; export default function RedemptionCodesPanel() { const { message } = App.useApp(); const [batches, setBatches] = useState([]); const [generatedCodes, setGeneratedCodes] = useState([]); const [selectedBatch, setSelectedBatch] = useState(null); const [loading, setLoading] = useState(true); const [creating, setCreating] = useState(false); const [keyword, setKeyword] = useState(""); const debouncedKeyword = useDebouncedValue(keyword); const [validity, setValidity] = useState<"all" | "active" | "expired">("all"); const [page, setPage] = useState(1); const [pageSize, setPageSize] = useState(20); const [total, setTotal] = useState(0); const [form] = Form.useForm(); const reload = async (targetPage = page, targetPageSize = pageSize) => { setLoading(true); try { const result = await listAdminRedeemBatches({ keyword: debouncedKeyword || undefined, validity: validity === "all" ? undefined : validity, page: targetPage, limit: targetPageSize }); setBatches(result.batches); setTotal(result.total); } catch (error) { message.error(error instanceof Error ? error.message : "读取兑换码批次失败"); } finally { setLoading(false); } }; useEffect(() => { form.setFieldsValue({ amount: 10, count: 10 }); }, [form]); useEffect(() => { void reload(page, pageSize); }, [debouncedKeyword, validity, page, pageSize]); const createBatch = async () => { const values = await form.validateFields(); setCreating(true); try { const result = await createAdminRedeemBatch({ amountMicrocredits: Math.round(values.amount * 1_000_000), count: values.count, note: values.note?.trim(), expiresAt: values.expiresAt ? new Date(values.expiresAt).toISOString() : undefined, }); setGeneratedCodes(result.codes); const createdBatch: RedeemBatch = { ...result.batch, availableCount: result.batch.count, redeemedCount: 0, disabledCount: 0, expiredCount: 0 }; setBatches((current) => [createdBatch, ...current.filter((item) => item.id !== createdBatch.id)].slice(0, pageSize)); setTotal((current) => current + 1); setPage(1); message.success(`已生成 ${result.codes.length} 个兑换码`); } catch (error) { const detail = error instanceof Error ? error.message : "生成兑换码失败"; message.error(detail.toLowerCase().includes("timeout") ? "生成超过 30 秒,后台可能仍已完成;请刷新批次列表确认,兑换码可从批次明细重新查看。" : detail); } finally { setCreating(false); } }; const columns: ColumnsType = [ { title: "创建时间", dataIndex: "createdAt", width: 180, render: formatTime }, { title: "单码积分", dataIndex: "amountMicrocredits", width: 130, align: "right", render: (value) => {formatCredits(value)} }, { title: "数量", dataIndex: "count", width: 100, align: "right", render: (value) => {value} }, { title: "核销状态", width: 180, render: (_, batch) => (
{batch.redeemedCount ?? 0}/{batch.count} 已核销 {(batch.expiredCount ?? 0) > 0 ? : null}
), }, { title: "有效期", dataIndex: "expiresAt", width: 180, render: (value) => (value ? formatTime(value) : ) }, { title: "批次备注", dataIndex: "note", render: (value) => value || 未填写 }, { title: "操作", width: 210, render: (_, batch) => , onClick: () => setSelectedBatch(batch) }} actions={[{ key: "disable", label: "禁用批次", icon: , danger: true, disabled: (batch.availableCount ?? 0) <= 0, confirm: { title: "禁用该批次的可用兑换码?", description: "已核销和已过期记录不会变更。", okText: "确认禁用" }, onClick: async () => { try { const result = await disableAdminRedeemBatch(batch.id); message.success(`已禁用 ${result.disabledCount} 个兑换码`); await reload(); } catch (error) { message.error(error instanceof Error ? error.message : "禁用批次失败"); } } }]} />, }, ]; return (

生成兑换码批次

} value={keyword} placeholder="搜索批次备注、积分或数量" onChange={(event) => { setKeyword(event.target.value); setPage(1); }} />} toolbarActiveFilters={<>{keyword ? { setKeyword(""); setPage(1); }} /> : null}{validity !== "all" ? { setValidity("all"); setPage(1); }} /> : null}} toolbarActive={Boolean(keyword || validity !== "all")} toolbarFilters={