import AddOutlined from "@mui/icons-material/AddOutlined"; import DeleteOutlineOutlined from "@mui/icons-material/DeleteOutlineOutlined"; import RefreshOutlined from "@mui/icons-material/RefreshOutlined"; import Avatar from "@mui/material/Avatar"; import MenuItem from "@mui/material/MenuItem"; import Switch from "@mui/material/Switch"; import TextField from "@mui/material/TextField"; import { useCallback, useEffect, useMemo, useState } from "react"; import { PERMISSIONS } from "@/app/permissions"; import { useAuth } from "@/app/auth/AuthProvider.jsx"; import { deleteDiceRobot, generateDiceRobots, listDiceRobots, setDiceRobotStatus } from "@/features/games/api"; import { useCountryOptions } from "@/features/host-org/hooks/useCountryOptions.js"; import styles from "@/features/games/games.module.css"; import { AdminActionIconButton, AdminFilterResetButton, AdminFilterSelect, AdminListBody, AdminListPage, AdminListToolbar, AdminRowActions, } from "@/shared/ui/AdminListLayout.jsx"; import { AdminFormDialog, AdminFormFieldGrid } from "@/shared/ui/AdminFormDialog.jsx"; import { useConfirm } from "@/shared/ui/ConfirmProvider.jsx"; import { DataTable } from "@/shared/ui/DataTable.jsx"; import { PageSkeleton } from "@/shared/ui/PageSkeleton.jsx"; import { useToast } from "@/shared/ui/ToastProvider.jsx"; import { formatMillis } from "@/shared/utils/time.js"; const defaultGenerateForm = { count: 20, country: "SA", nicknameLanguage: "arabic", }; const maxGenerateRobotCount = 1000; export function GameRobotPage() { const { can } = useAuth(); const confirm = useConfirm(); const { showToast } = useToast(); const canCreate = can(PERMISSIONS.gameCreate); const canUpdate = can(PERMISSIONS.gameUpdate); const canDelete = can(PERMISSIONS.gameDelete) || canUpdate; const { countryOptions, loadingCountries } = useCountryOptions(); const [items, setItems] = useState([]); const [status, setStatus] = useState(""); const [nextCursor, setNextCursor] = useState(""); const [loading, setLoading] = useState(true); const [loadingAction, setLoadingAction] = useState(""); const [dialogOpen, setDialogOpen] = useState(false); const [generateForm, setGenerateForm] = useState(defaultGenerateForm); const load = useCallback( async (cursor = "", append = false) => { setLoading(!append); try { const result = await listDiceRobots({ status, cursor, pageSize: 50 }); setItems((current) => (append ? [...current, ...(result.items || [])] : result.items || [])); setNextCursor(result.nextCursor || ""); } catch (err) { showToast(err.message || "加载全站机器人失败", "error"); } finally { setLoading(false); } }, [showToast, status], ); useEffect(() => { load("", false); }, [load]); useEffect(() => { setGenerateForm((current) => { if (countryOptions.length === 0) { return current; } const currentCountryEnabled = countryOptions.some((option) => option.value === current.country); if (current.country && currentCountryEnabled) { return current; } // 国家主数据由后端控制启用状态;默认优先沿用历史 SA,否则取当前启用列表第一项,避免提交空国家被 user-service 拒绝。 const defaultCountry = countryOptions.find((option) => option.value === "SA")?.value || countryOptions[0].value; return { ...current, country: defaultCountry }; }); }, [countryOptions]); const changeStatus = useCallback( async (robot, checked) => { setLoadingAction(`status-${robot.userId}`); try { await setDiceRobotStatus(robot.userId, checked ? "active" : "disabled"); await load("", false); showToast("机器人状态已更新", "success"); } catch (err) { showToast(err.message || "更新机器人状态失败", "error"); } finally { setLoadingAction(""); } }, [load, showToast], ); const removeRobot = useCallback( async (robot) => { const ok = await confirm({ confirmText: "删除", message: `${robot.nickname || robot.userId} 会从全站机器人列表移除,历史用户资料和对局记录保留。`, title: "删除全站机器人", tone: "danger", }); if (!ok) { return; } setLoadingAction(`delete-${robot.userId}`); try { await deleteDiceRobot(robot.userId); await load("", false); showToast("全站机器人已删除", "success"); } catch (err) { showToast(err.message || "删除全站机器人失败", "error"); } finally { setLoadingAction(""); } }, [confirm, load, showToast], ); const columns = useMemo( () => [ { key: "user", label: "机器人用户", width: "minmax(220px, 1fr)", render: (robot) => (
{robotAvatarText(robot)}
{robot.nickname || robot.userId} {robot.shortId ? `短ID ${robot.shortId}` : "短ID -"}
), }, { key: "status", label: "状态", width: "120px", render: (robot) => ( changeStatus(robot, event.target.checked)} /> ), }, { key: "created", label: "创建时间", width: "minmax(170px, 1fr)", render: (robot) => (robot.createdAtMs ? formatMillis(robot.createdAtMs) : "-"), }, { key: "updated", label: "更新时间", width: "minmax(170px, 1fr)", render: (robot) => (robot.updatedAtMs ? formatMillis(robot.updatedAtMs) : "-"), }, { key: "actions", label: "操作", width: "96px", render: (robot) => ( removeRobot(robot)} > ), }, ], [canDelete, canUpdate, changeStatus, loadingAction, removeRobot], ); const submitGenerate = async (event) => { event.preventDefault(); setLoadingAction("generate"); try { const enabledCountry = countryOptions.some((option) => option.value === generateForm.country) ? generateForm.country : countryOptions[0]?.value || generateForm.country || "SA"; const result = await generateDiceRobots({ country: enabledCountry, count: Number(generateForm.count || 0), nicknameLanguage: generateForm.nicknameLanguage || "arabic", }); await load("", false); showToast(`已批量创建 ${result.created || 0} 个机器人`, "success"); setDialogOpen(false); } catch (err) { showToast(err.message || "批量创建全站机器人失败", "error"); } finally { setLoadingAction(""); } }; if (loading) { return ; } return ( load("", false)}> setDialogOpen(true)} > } filters={ <> setStatus(event.target.value)} > 全部状态 启用 停用 setStatus("")} /> } /> load(nextCursor, true), }} items={items} minWidth="760px" rowKey={(robot) => robot.userId} /> setDialogOpen(false)} onSubmit={submitGenerate} /> ); } function GenerateDialog({ countryOptions, form, loading, loadingCountries, open, setForm, onClose, onSubmit }) { const selectCountryOptions = countryOptions.length > 0 ? countryOptions : [{ label: "SA", value: "SA" }]; const selectedCountry = selectCountryOptions.some((country) => country.value === form.country) ? form.country : selectCountryOptions[0]?.value || "SA"; return ( setForm({ ...form, count: event.target.value })} /> setForm({ ...form, nicknameLanguage: event.target.value })} > 阿拉伯语账号 英语账号 setForm({ ...form, country: event.target.value })} > {selectCountryOptions.map((country) => ( {country.label} ))} ); } function robotAvatarText(robot) { const value = String(robot.nickname || robot.shortId || robot.userId || "?").trim(); return value ? value.slice(0, 1).toUpperCase() : "?"; }