feat: 添加货币管理功能,更新国际化支持,移除报表相关代码

This commit is contained in:
2026-05-21 16:24:56 +08:00
parent 6ecbaf5fb4
commit 055c613a6d
87 changed files with 1615 additions and 1319 deletions

View File

@@ -36,7 +36,9 @@ import {
TableHeader,
TableRow,
} from "@/components/ui/table";
import { useAdminCurrencyCatalog } from "@/hooks/use-admin-currency-catalog";
import { useAdminDateTimeFormatter } from "@/hooks/use-admin-datetime-formatter";
import { formatAdminMinorUnits } from "@/lib/money";
import { LotteryApiBizError } from "@/types/api/errors";
import type {
AdminPlayerWalletsData,
@@ -44,11 +46,6 @@ import type {
AdminWalletTxnListData,
} from "@/types/api/admin-wallet";
function formatMinorUnits(minor: number, currencyCode: string): string {
const major = minor / 100;
return `${major.toFixed(2)} ${currencyCode}`;
}
/** 长单号/流水号:单行截断;点击复制全文,悬停可看全文 */
function CellMonoId({
value,
@@ -227,12 +224,13 @@ function canManuallyProcessTransferOrder(row: {
export function TransferOrdersPanel(): React.ReactElement {
const { t } = useTranslation(["wallet", "common"]);
useAdminCurrencyCatalog();
const formatTs = useAdminDateTimeFormatter();
const [data, setData] = useState<AdminTransferOrderListData | null>(null);
const [loading, setLoading] = useState(true);
const [err, setErr] = useState<string | null>(null);
const [page, setPage] = useState(1);
const [perPage, setPerPage] = useState(20);
const [perPage, setPerPage] = useState(10);
const [draft, setDraft] = useState<TransferFilters>(emptyTransferFilters);
const [applied, setApplied] = useState<TransferFilters>(emptyTransferFilters);
const [actionLoading, setActionLoading] = useState<Set<string>>(new Set());
@@ -475,7 +473,7 @@ export function TransferOrdersPanel(): React.ReactElement {
</TableCell>
<TableCell>{row.direction}</TableCell>
<TableCell className="tabular-nums">
{formatMinorUnits(row.amount, row.currency_code)}
{formatAdminMinorUnits(row.amount, row.currency_code)}
</TableCell>
<TableCell>
<Badge variant={statusBadgeVariant(row.status)}>{statusLabelT(row.status, t)}</Badge>
@@ -552,7 +550,7 @@ export function WalletTxnsPanel(): React.ReactElement {
const [loading, setLoading] = useState(true);
const [err, setErr] = useState<string | null>(null);
const [page, setPage] = useState(1);
const [perPage, setPerPage] = useState(20);
const [perPage, setPerPage] = useState(10);
const [draft, setDraft] = useState<TxnFilters>(emptyTxnFilters);
const [applied, setApplied] = useState<TxnFilters>(emptyTxnFilters);
@@ -836,6 +834,7 @@ export function WalletTxnsPanel(): React.ReactElement {
export function PlayerWalletPanel(): React.ReactElement {
const { t } = useTranslation(["wallet", "common"]);
useAdminCurrencyCatalog();
const [playerId, setPlayerId] = useState("");
const [result, setResult] = useState<AdminPlayerWalletsData | null>(null);
const [err, setErr] = useState<string | null>(null);
@@ -918,7 +917,7 @@ export function PlayerWalletPanel(): React.ReactElement {
<TableCell>{w.currency_code}</TableCell>
<TableCell className="font-mono tabular-nums">{w.balance}</TableCell>
<TableCell className="tabular-nums">
{formatMinorUnits(w.available_balance, w.currency_code)}
{formatAdminMinorUnits(w.available_balance, w.currency_code)}
</TableCell>
</TableRow>
))

View File

@@ -4,8 +4,8 @@ import Link from "next/link";
import { usePathname } from "next/navigation";
import { useTranslation } from "react-i18next";
import { cn } from "@/lib/utils";
import { adminHasAnyPermission } from "@/lib/admin-permissions";
import { cn } from "@/lib/utils";
import { useAdminProfile } from "@/stores/admin-session";
const RECONCILE_PERMS = [
@@ -28,27 +28,38 @@ export function WalletSubnav(): React.ReactElement {
return (
<nav
aria-label={t("subnavLabel")}
className="mb-6 flex flex-wrap gap-2 border-b border-border pb-3"
className="flex w-full flex-wrap items-end gap-1 border-b border-border/60 px-1"
>
{tabs.map((tab) => {
const allowed = adminHasAnyPermission(perms, [...tab.requiredAny]);
const active = pathname === tab.href || pathname.startsWith(`${tab.href}/`);
const className = cn(
"rounded-lg px-3 py-1.5 text-sm font-medium transition-colors",
active
? "bg-primary text-primary-foreground"
: "bg-muted/60 text-foreground hover:bg-muted",
!allowed && "cursor-not-allowed opacity-45",
);
if (!allowed) {
return (
<span key={tab.href} className={className} title={t("noPermission")}>
<span
key={tab.href}
className={cn(
"border-b-2 border-transparent px-4 py-3 text-sm font-medium text-muted-foreground/45",
"cursor-not-allowed",
)}
title={t("noPermission")}
>
{t(tab.label)}
</span>
);
}
return (
<Link key={tab.href} href={tab.href} className={className}>
<Link
key={tab.href}
href={tab.href}
className={cn(
"border-b-2 px-4 py-3 text-sm font-medium transition-colors",
active
? "border-primary text-primary"
: "border-transparent text-muted-foreground hover:border-border/80 hover:text-foreground",
)}
>
{t(tab.label)}
</Link>
);