refactor: 合并多语言支持的显示名称字段,优化奖池手动爆发功能的返回数据结构,增强管理端权限控制

This commit is contained in:
2026-05-25 14:31:24 +08:00
parent 7d01e5c47e
commit ddedef824e
101 changed files with 3033 additions and 641 deletions

View File

@@ -1,6 +1,7 @@
"use client";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useConfirmAction } from "@/hooks/use-confirm-action";
import { useExportLabels } from "@/hooks/use-export-labels";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
@@ -9,6 +10,8 @@ import {
deleteAdminPlayer,
getAdminPlayers,
postAdminPlayer,
postAdminPlayerFreeze,
postAdminPlayerUnfreeze,
putAdminPlayer,
} from "@/api/admin-player";
import { AdminListPaginationFooter } from "@/components/admin/admin-list-pagination-footer";
@@ -27,6 +30,7 @@ import {
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { adminHasAnyPermission } from "@/lib/admin-permissions";
import { PRD_PLAYER_FREEZE_MANAGE, PRD_USERS_MANAGE } from "@/lib/admin-prd";
import { useAdminProfile } from "@/stores/admin-session";
import {
Select,
@@ -63,10 +67,12 @@ const PLAYER_STATUS_OPTIONS = [
export function PlayersConsole(): React.ReactElement {
const { t } = useTranslation(["players", "common"]);
const { request: requestConfirm, ConfirmDialog } = useConfirmAction();
const exportLabels = useExportLabels("players");
const profile = useAdminProfile();
useAdminCurrencyCatalog();
const canManagePlayers = adminHasAnyPermission(profile?.permissions, ["prd.users.manage"]);
const canManagePlayers = adminHasAnyPermission(profile?.permissions, [PRD_USERS_MANAGE]);
const canFreezePlayers = adminHasAnyPermission(profile?.permissions, [PRD_PLAYER_FREEZE_MANAGE]);
const [page, setPage] = useState(1);
const [perPage, setPerPage] = useState(10);
const [keyword, setKeyword] = useState("");
@@ -91,6 +97,7 @@ export function PlayersConsole(): React.ReactElement {
const [deleteTarget, setDeleteTarget] = useState<AdminPlayerRow | null>(null);
const [deleteBusy, setDeleteBusy] = useState(false);
const [freezeBusyId, setFreezeBusyId] = useState<number | null>(null);
const editingPlayer = useMemo(
() => items.find((p) => p.id === editingAccountId) ?? null,
@@ -226,6 +233,28 @@ export function PlayersConsole(): React.ReactElement {
}
}
async function toggleFreeze(row: AdminPlayerRow, freeze: boolean): Promise<void> {
setFreezeBusyId(row.id);
try {
const updated = freeze
? await postAdminPlayerFreeze(row.id)
: await postAdminPlayerUnfreeze(row.id);
setItems((prev) => prev.map((r) => (r.id === updated.id ? updated : r)));
const name = updated.username ?? updated.site_player_id;
toast.success(freeze ? t("freezeSuccess", { name }) : t("unfreezeSuccess", { name }));
} catch (e) {
const msg =
e instanceof LotteryApiBizError
? e.message
: freeze
? t("freezeFailed")
: t("unfreezeFailed");
toast.error(msg);
} finally {
setFreezeBusyId(null);
}
}
async function confirmDelete(): Promise<void> {
if (!deleteTarget) return;
setDeleteBusy(true);
@@ -364,26 +393,66 @@ export function PlayersConsole(): React.ReactElement {
: "—"}
</TableCell>
<TableCell>
{canManagePlayers ? (
{canManagePlayers || canFreezePlayers ? (
<div className="flex flex-wrap gap-1">
<Button
type="button"
size="sm"
variant={
accountOpen && editingAccountId === row.id ? "secondary" : "outline"
}
onClick={() => openEditAccount(row)}
>
{t("edit")}
</Button>
<Button
type="button"
size="sm"
variant="destructive"
onClick={() => setDeleteTarget(row)}
>
{t("delete")}
</Button>
{canFreezePlayers && row.status === 0 ? (
<Button
type="button"
size="sm"
variant="outline"
disabled={freezeBusyId === row.id}
onClick={() => {
const name = row.username ?? row.site_player_id;
requestConfirm({
title: t("confirmFreezeTitle"),
description: t("confirmFreezeDescription", { name }),
onConfirm: () => toggleFreeze(row, true),
});
}}
>
{freezeBusyId === row.id ? t("saving") : t("freeze")}
</Button>
) : null}
{canFreezePlayers && row.status === 1 ? (
<Button
type="button"
size="sm"
variant="outline"
disabled={freezeBusyId === row.id}
onClick={() => {
const name = row.username ?? row.site_player_id;
requestConfirm({
title: t("confirmUnfreezeTitle"),
description: t("confirmUnfreezeDescription", { name }),
onConfirm: () => toggleFreeze(row, false),
});
}}
>
{freezeBusyId === row.id ? t("saving") : t("unfreeze")}
</Button>
) : null}
{canManagePlayers ? (
<>
<Button
type="button"
size="sm"
variant={
accountOpen && editingAccountId === row.id ? "secondary" : "outline"
}
onClick={() => openEditAccount(row)}
>
{t("edit")}
</Button>
<Button
type="button"
size="sm"
variant="destructive"
onClick={() => setDeleteTarget(row)}
>
{t("delete")}
</Button>
</>
) : null}
</div>
) : (
<span className="text-xs text-muted-foreground"></span>
@@ -554,6 +623,7 @@ export function PlayersConsole(): React.ReactElement {
</div>
</DialogContent>
</Dialog>
<ConfirmDialog />
</div>
);
}