import EditOutlined from "@mui/icons-material/EditOutlined"; import GppBadOutlined from "@mui/icons-material/GppBadOutlined"; import TrendingUpOutlined from "@mui/icons-material/TrendingUpOutlined"; import Avatar from "@mui/material/Avatar"; import Box from "@mui/material/Box"; import Button from "@mui/material/Button"; import MenuItem from "@mui/material/MenuItem"; import Stack from "@mui/material/Stack"; import TextField from "@mui/material/TextField"; import Typography from "@mui/material/Typography"; import { useCallback, useMemo, useState } from "react"; import { useToast } from "@/shared/ui/ToastProvider.jsx"; import { banUser, grantUserVip, listUsers, updateUser, updateUserLevels } from "../api/business.js"; import { useExternalAuth } from "../auth/ExternalAuthProvider.jsx"; import { UserBanDialog, UserEditDialog, UserLevelDialog } from "../components/UserDialogs.jsx"; import { hasExternalCapability } from "../config/capabilities.js"; import { ExternalPage, ExternalPageState, PagePagination, ResponsiveDataList, StatusChip } from "../shared/PageComponents.jsx"; import { usePagedResource } from "../shared/usePagedResource.js"; export default function UsersPage() { const { session } = useExternalAuth(); const { showToast } = useToast(); const [filters, setFilters] = useState({ keyword: "", status: "" }); const [query, setQuery] = useState({ keyword: "", page: 1, status: "" }); const [dialog, setDialog] = useState({ type: "", user: null }); const [submitting, setSubmitting] = useState(false); const load = useCallback(() => listUsers({ keyword: query.keyword, page: query.page, page_size: 20, status: query.status }), [query]); const { data, error, loading, reload } = usePagedResource(load); const canEdit = hasExternalCapability(session, "user:update"); const canBan = hasExternalCapability(session, "user:ban"); const canGrantLevel = hasExternalCapability(session, "user-level:grant"); const closeDialog = () => setDialog({ type: "", user: null }); const execute = async (request, successMessage) => { setSubmitting(true); try { await request(); showToast(successMessage, "success"); closeDialog(); reload(); } catch (requestError) { showToast(requestError.message || "用户操作失败", "error"); } finally { setSubmitting(false); } }; const columns = useMemo(() => [ { key: "user", label: "用户", render: (user) => {user.username.slice(0, 1)}{user.username || "-"}ID {user.id} }, { key: "prettyId", label: "短 ID(靓号)", render: (user) => user.prettyId || "-" }, { key: "country", label: "国家/性别", render: (user) => `${user.country || "-"} / ${user.gender || "-"}` }, { key: "level", label: "等级", render: (user) => `财富 ${user.wealthLevel} / 魅力 ${user.charmLevel} / VIP ${user.vipLevel}` }, { key: "status", label: "状态", render: (user) => }, { key: "actions", label: "操作", render: (user) => {canEdit ? : null}{canGrantLevel ? : null}{canBan && !user.banned ? : null} } ], [canBan, canEdit, canGrantLevel]); return ( { event.preventDefault(); setQuery({ ...filters, page: 1 }); }}> setFilters({ ...filters, keyword: event.target.value })} /> setFilters({ ...filters, status: event.target.value })}>全部状态正常已封禁 {!loading && !error ? <> item.id} /> setQuery({ ...query, page })} /> : null} execute(() => updateUser(dialog.user.id, form), "用户信息已更新")} /> execute(() => banUser(dialog.user.id, { expires_at_ms: Number(form.days) === 0 ? 0 : Date.now() + Number(form.days) * 86400000, reason: form.reason.trim() }), "用户已封禁")} /> execute(() => form.track === "vip" ? grantUserVip(dialog.user.id, { commandId: createLevelCommandId(), durationMs: Number(form.days) * 86400000, level: Number(form.level), reason: form.reason.trim() }) : updateUserLevels(dialog.user.id, { adjustments: [{ duration_days: Number(form.days), level: Number(form.level), track: form.track }], reason: form.reason.trim() }), "用户等级已下发")} /> ); } function createLevelCommandId() { return `external-level-grant-${globalThis.crypto?.randomUUID?.() || `${Date.now()}-${Math.random().toString(16).slice(2)}`}`; }