import BlockOutlined from "@mui/icons-material/BlockOutlined"; import EditOutlined from "@mui/icons-material/EditOutlined"; import FileDownloadOutlined from "@mui/icons-material/FileDownloadOutlined"; import KeyboardArrowDownOutlined from "@mui/icons-material/KeyboardArrowDownOutlined"; import KeyboardArrowUpOutlined from "@mui/icons-material/KeyboardArrowUpOutlined"; import LockOpenOutlined from "@mui/icons-material/LockOpenOutlined"; import PasswordOutlined from "@mui/icons-material/PasswordOutlined"; import RestartAltOutlined from "@mui/icons-material/RestartAltOutlined"; import UnfoldMoreOutlined from "@mui/icons-material/UnfoldMoreOutlined"; import Autocomplete from "@mui/material/Autocomplete"; import Checkbox from "@mui/material/Checkbox"; import FormControlLabel from "@mui/material/FormControlLabel"; import MenuItem from "@mui/material/MenuItem"; import TextField from "@mui/material/TextField"; import Tooltip from "@mui/material/Tooltip"; import { AdminFormDialog, AdminFormSection } from "@/shared/ui/AdminFormDialog.jsx"; import { AdminUserIdentity } from "@/shared/ui/AdminUserIdentity.jsx"; import { Button } from "@/shared/ui/Button.jsx"; import { DataState } from "@/shared/ui/DataState.jsx"; import { DataTable } from "@/shared/ui/DataTable.jsx"; import { IconButton } from "@/shared/ui/IconButton.jsx"; import { SideDrawer } from "@/shared/ui/SideDrawer.jsx"; import { createOptionsColumnFilter, createUserIdentityColumnFilter } from "@/shared/ui/tableFilters.js"; import { TimeRangeFilter } from "@/shared/ui/TimeRangeFilter.jsx"; import { UploadField } from "@/shared/ui/UploadField.jsx"; import { formatMillis } from "@/shared/utils/time.js"; import { appUserStatusFilters, appUserStatusLabels, genderOptions } from "@/features/app-users/constants.js"; import { BanCountdown, GenderValue, LevelValue, OperatorValue, RolesValue, } from "@/features/app-users/components/AppUserValues.jsx"; import { useAppUsersPage } from "@/features/app-users/hooks/useAppUsersPage.js"; import { CoinLedgerTable } from "@/features/operations/components/CoinLedgerTable.jsx"; import styles from "@/features/app-users/app-users.module.css"; export function AppUserListPage() { const page = useAppUsersPage(); const items = page.data.items || []; const total = page.data.total || 0; const selectableItems = items.filter((user) => !isBannedUser(user)); const allChecked = selectableItems.length > 0 && selectableItems.every((user) => page.selectedUserIds.includes(user.userId)); const selectedOnPageCount = selectableItems.filter((user) => page.selectedUserIds.includes(user.userId)).length; const someChecked = selectedOnPageCount > 0 && !allChecked; const toggleAll = (checked) => { if (!checked) { page.setSelectedUserIds((current) => current.filter((userId) => !selectableItems.some((user) => user.userId === userId)), ); return; } page.setSelectedUserIds((current) => Array.from(new Set([...current, ...selectableItems.map((user) => user.userId)])), ); }; const tableColumns = [ { key: "select", className: styles.selectColumn, header: ( toggleAll(event.target.checked)} /> ), resizable: false, width: "64px", render: (user) => { const banned = isBannedUser(user); return ( { page.setSelectedUserIds((current) => event.target.checked ? Array.from(new Set([...current, user.userId])) : current.filter((userId) => userId !== user.userId), ); }} /> ); }, }, { key: "identity", label: "用户", width: "minmax(240px, 1.5fr)", filter: createUserIdentityColumnFilter({ value: page.userFilter, onChange: page.changeUserFilter, }), render: (user) => , }, { key: "gender", label: "性别", width: "minmax(72px, 0.4fr)", render: (user) => , }, { key: "age", label: "年龄", width: "minmax(72px, 0.4fr)", render: (user) => (user.age === undefined || user.age === null ? "-" : `${user.age} 岁`), }, { key: "wealthLevel", label: "富豪等级", width: "minmax(118px, 0.7fr)", render: (user) => , }, { key: "charmLevel", label: "魅力等级", width: "minmax(118px, 0.7fr)", render: (user) => , }, { key: "gameLevel", label: "游戏等级", width: "minmax(96px, 0.55fr)", render: (user) => , }, { key: "roles", label: "身份", width: "minmax(150px, 0.9fr)", render: (user) => , }, { key: "vip", label: "VIP", width: "minmax(80px, 0.45fr)", render: (user) => , }, { key: "location", label: "国家 / 区域", width: "minmax(170px, 1fr)", filter: createOptionsColumnFilter({ emptyLabel: "全部国家 / 区域", loading: page.loadingCountries || page.loadingRegions, options: page.locationFilterOptions, placeholder: "搜索国家或区域", value: page.locationFilterValue, onChange: page.changeLocationFilter, }), render: (user) => , }, { key: "coin", header: ( ), label: "金币", width: "minmax(90px, 0.6fr)", render: (user) => , }, { key: "status", label: "状态", width: "minmax(92px, 0.6fr)", filter: createOptionsColumnFilter({ options: appUserStatusFilters, placeholder: "搜索状态", value: page.status, onChange: page.changeStatus, }), render: (user) => (
), }, { key: "lastLoginAtMs", label: "最新成功登录", width: "minmax(160px, 0.9fr)", render: (user) => formatMillis(user.lastLoginAtMs), }, { key: "lastOperator", label: "最新操作人", width: "minmax(142px, 0.8fr)", render: (user) => , }, { key: "lastOperatedAtMs", label: "最新操作时间", width: "minmax(160px, 0.9fr)", render: (user) => formatMillis(user.lastOperatedAtMs), }, { key: "time", header: ( ), label: "创建 / 活跃", width: "minmax(180px, 1fr)", render: (user) => (
{formatMillis(user.createdAtMs)} {formatMillis(user.lastActiveAtMs)}
), }, { key: "actions", label: "操作", width: "minmax(148px, 0.8fr)", render: (user) => , }, ]; return (
{page.abilities.canStatus ? ( 已选 {page.selectedUserIds.length} 个 ) : null}
{page.abilities.canExport ? ( ) : null} {page.abilities.canStatus ? ( ) : null}
0 ? { page: page.page, pageSize: page.data.pageSize || 50, total, onPageChange: page.setPage, } : undefined } rowKey={(user) => user.userId} />
event.preventDefault()} > page.setEditForm({ ...page.editForm, username: event.target.value })} /> page.setEditForm({ ...page.editForm, avatar })} /> page.setEditForm({ ...page.editForm, gender: event.target.value })} > {genderOptions.map(([value, label]) => ( {label} ))} {page.loadingAction === "levels" ? "保存中" : "保存等级"} } title="等级调整" > page.setBanForm({ ...page.banForm, mode: event.target.value })} > 永久 指定截止时间 {page.banForm.mode === "timed" ? ( page.setBanForm({ ...page.banForm, expiresAtLocal: event.target.value })} /> ) : null} page.setBanForm({ ...page.banForm, reason: event.target.value })} /> option.label || ""} isOptionEqualToValue={(option, value) => option.value === value.value} loading={page.loadingCountries} options={page.countryOptions} renderInput={(params) => } size="small" value={page.countryOptions.find((option) => option.value === page.countryForm.country) || null} onChange={(_, option) => page.setCountryForm({ country: option?.value || "" })} /> page.setPasswordForm({ ...page.passwordForm, password: event.target.value })} /> {page.activeUser ? ( ) : null}
); } function CoinValue({ page, user }) { const value = formatNumber(user.coin); if (!page.abilities.canCoinLedger) { return value; } return ( ); } function LevelAdjustmentFields({ page }) { const tracks = [ ["wealth", "富豪等级"], ["charm", "魅力等级"], ]; const updateTrack = (track, patch) => { page.setLevelForm((current) => ({ ...current, [track]: { ...current[track], ...patch }, })); }; return (
{tracks.map(([track, label]) => { const field = page.levelForm[track]; const current = page.activeUser?.levels?.[track]; return (
updateTrack(track, { enabled: event.target.checked })} /> } label={`${label}(真实 Lv${Number(current?.realLevel || 0)},展示 Lv${Number(current?.displayLevel || 0)})`} />
updateTrack(track, { level: event.target.value })} /> updateTrack(track, { durationDays: event.target.value })} />
); })} page.setLevelForm((current) => ({ ...current, reason: event.target.value }))} />
); } function VipValue({ vip }) { const level = Number(vip?.level || 0); if (level <= 0 || !vip?.active) { return VIP 0; } return VIP {level}; } function UserLocation({ page, user }) { const countryLabel = formatCountry(user); const regionLabel = user.regionName || (user.regionId ? `区域 ${user.regionId}` : "-"); return (
{page.abilities.canUpdate ? ( ) : ( {countryLabel} )} {regionLabel}
); } function UserActions({ page, user }) { const banned = user.status === "banned" || user.status === "disabled"; return (
{page.abilities.canUpdate || page.abilities.canLevel ? ( page.openEdit(user)} > ) : null} {page.abilities.canStatus ? ( page.toggleBan(user)} > {banned ? : } ) : null} {page.abilities.canPassword ? ( page.openPassword(user)}> ) : null}
); } function UserStatus({ status }) { const tone = status === "active" ? "running" : "danger"; return ( {appUserStatusLabels[status] || status || "-"} ); } function SortHeader({ field, label, onClick, sortBy, sortDirection }) { const active = sortBy === field; const nextDirection = active && sortDirection === "desc" ? "asc" : "desc"; const Icon = !active ? UnfoldMoreOutlined : sortDirection === "asc" ? KeyboardArrowUpOutlined : KeyboardArrowDownOutlined; return ( ); } function ActionModal({ children, disabled, loading, onClose, onSubmit, open, sectionTitle = "基本信息", title }) { return ( {children} ); } function formatNumber(value) { return Number(value || 0).toLocaleString("zh-CN"); } function userLedgerTitle(user) { if (!user) { return "用户"; } return user.username || user.displayUserId || user.prettyDisplayUserId || user.userId || "用户"; } function formatCountry(user) { return user.countryDisplayName || user.countryName || user.country || "-"; } function isBannedUser(user) { return user.status === "banned" || user.status === "disabled"; }