import Add from "@mui/icons-material/Add"; import CodeOutlined from "@mui/icons-material/CodeOutlined"; import DeleteOutlineOutlined from "@mui/icons-material/DeleteOutlineOutlined"; import EditOutlined from "@mui/icons-material/EditOutlined"; import SettingsOutlined from "@mui/icons-material/SettingsOutlined"; import SportsEsportsOutlined from "@mui/icons-material/SportsEsportsOutlined"; import SyncOutlined from "@mui/icons-material/SyncOutlined"; import { AdminSwitch } from "@/shared/ui/AdminSwitch.jsx"; import Checkbox from "@mui/material/Checkbox"; import MenuItem from "@mui/material/MenuItem"; import TextField from "@mui/material/TextField"; import { AdminFormDialog, AdminFormFieldGrid, AdminFormSection } from "@/shared/ui/AdminFormDialog.jsx"; import { Button } from "@/shared/ui/Button.jsx"; import { DataState } from "@/shared/ui/DataState.jsx"; import { DataTable } from "@/shared/ui/DataTable.jsx"; import { JsonEditorField } from "@/shared/ui/JsonEditorField.jsx"; import { AdminActionIconButton, AdminListBody, AdminListPage, AdminListToolbar, AdminRowActions, } from "@/shared/ui/AdminListLayout.jsx"; import { createOptionsColumnFilter } from "@/shared/ui/tableFilters.js"; import { UploadField } from "@/shared/ui/UploadField.jsx"; import { formatMillis } from "@/shared/utils/time.js"; import { useGamesPage } from "@/features/games/hooks/useGamesPage.js"; import styles from "@/features/games/games.module.css"; const statusOptions = [ ["", "全部状态"], ["active", "Active"], ["maintenance", "Maintenance"], ["disabled", "Disabled"], ]; const orientationOptions = [ ["portrait", "Portrait"], ["landscape", "Landscape"], ]; const launchModeOptions = [ ["full_screen", "全屏"], ["half_screen", "半屏"], ["three_quarter_screen", "3/4屏"], ]; // 这里的值必须和 game-service 的 adapter_type 白名单一致;新增厂商先后端登记,再放到后台可选项。 const adapterTypeOptions = [ ["yomi_v4", "小游 Yomi V4"], ["leadercc_v1", "灵仙 LeaderCC V1"], ["zeeone_v1", "ZeeOne V1"], ["baishun_v1", "百顺 BAISHUN V1"], ["vivagames_v1", "VIVAGAMES V1"], ["reyou_v1", "热游 Reyou V1"], ]; const baseColumns = [ { key: "game", label: "游戏", width: "minmax(290px, 1.5fr)", render: (game) => , }, { key: "platform", label: "平台", width: "minmax(160px, 0.75fr)", render: (game) => (
{game.platformCode} {game.providerGameId}
), }, { key: "launch", label: "启动", width: "minmax(150px, 0.75fr)", render: (game) => (
{launchModeLabel(game.launchMode)} {game.orientation} · 安全高 {formatNumber(game.safeHeight)}
), }, { key: "coin", label: "最低金币", width: "minmax(116px, 0.55fr)", render: (game) => formatNumber(game.minCoin), }, { key: "status", label: "状态", width: "minmax(120px, 0.6fr)", }, { key: "updatedAt", label: "更新时间", width: "minmax(170px, 0.9fr)", render: (game) => formatMillis(game.updatedAtMs || game.createdAtMs), }, { key: "actions", label: "操作", width: "minmax(112px, 0.5fr)", }, ]; export function GameListPage() { const page = useGamesPage(); const items = page.data.items || []; const platformFilterOptions = [["", "全部平台"], ...page.platformOptions]; const columns = baseColumns.map((column) => { if (column.key === "platform") { return { ...column, filter: createOptionsColumnFilter({ options: platformFilterOptions, placeholder: "搜索平台", value: page.platformCode, onChange: page.setPlatformCode, }), }; } if (column.key === "status") { return { ...column, filter: createOptionsColumnFilter({ options: statusOptions, placeholder: "搜索状态", value: page.status, onChange: page.setStatus, }), render: (game) => , }; } if (column.key === "actions") { return { ...column, render: (game) => , }; } return column; }); return ( {page.abilities.canUpdate ? ( ) : null} {page.abilities.canUpdate ? ( ) : null} {page.abilities.canUpdate ? ( ) : null} {page.abilities.canCreate ? ( ) : null} } /> game.gameId} /> ); } function GameIdentity({ game }) { return (
{game.iconUrl ? ( ) : ( )}
{game.gameName || game.gameId} {game.gameId}
); } function GameStatusSwitch({ page, game }) { const disabled = !page.abilities.canStatus || page.loadingAction === `status-${game.gameId}`; return ( page.changeGameStatus(game, event.target.checked)} /> ); } function GameRowActions({ page, game }) { return ( page.openEditGame(game)} > page.deleteGame(game)} > ); } function GameFormDialog({ form, loading, mode, open, page, onClose, onSubmit }) { const disabled = mode === "edit" ? !page.abilities.canUpdate : !page.abilities.canCreate; return ( page.setGameForm({ ...form, gameId: event.target.value })} /> 0} value={form.platformCode} onChange={(event) => page.setGameForm({ ...form, platformCode: event.target.value })} > {page.platformOptions.map(([value, label]) => ( {label || value} ))} page.setGameForm({ ...form, providerGameId: event.target.value })} /> page.setGameForm({ ...form, gameName: event.target.value })} /> page.setGameForm({ ...form, category: event.target.value })} /> page.setGameForm({ ...form, launchMode: event.target.value })} > {launchModeOptions.map(([value, label]) => ( {label} ))} page.setGameForm({ ...form, orientation: event.target.value })} > {orientationOptions.map(([value, label]) => ( {label} ))} page.setGameForm({ ...form, safeHeight: event.target.value })} /> page.setGameForm({ ...form, gameUrl: event.target.value })} /> page.setGameForm({ ...form, minCoin: event.target.value })} /> page.setGameForm({ ...form, sortOrder: event.target.value })} /> page.setGameForm({ ...form, status: checked ? "active" : form.status === "disabled" ? "disabled" : "maintenance", }) } /> page.setGameForm({ ...form, iconUrl })} /> page.setGameForm({ ...form, coverUrl })} /> page.setGameForm({ ...form, tagsText: event.target.value })} /> ); } function BridgeScriptDialog({ form, loading, open, page, onClose, onSubmit }) { const disabled = !page.abilities.canUpdate; const submitDisabled = disabled || loading || !form.bridgeScriptUrl.trim() || !form.bridgeScriptVersion.trim(); return ( page.setBridgeScriptForm({ ...form, bridgeScriptVersion: event.target.value }) } /> page.setBridgeScriptForm({ ...form, bridgeScriptUrl: event.target.value })} /> ); } function PlatformFormDialog({ form, loading, mode, open, page, onClose, onSubmit }) { const isLeaderCC = form.adapterType === "leadercc_v1"; return ( page.setPlatformForm({ ...form, platformCode: event.target.value })} /> page.setPlatformForm({ ...form, platformName: event.target.value })} /> page.setPlatformForm({ ...form, status: checked ? "active" : form.status === "disabled" ? "disabled" : "maintenance", }) } /> page.setPlatformForm({ ...form, sortOrder: event.target.value })} /> {/* 适配器决定服务端启动参数、签名/解密和错误码映射。 */} page.setPlatformForm({ ...form, adapterType: event.target.value })} > {adapterTypeOptions.map(([value, label]) => ( {label} ))} {!isLeaderCC ? ( page.setPlatformForm({ ...form, apiBaseUrl: event.target.value })} /> ) : null} {/* 后台直接回显当前厂商 key;清空保存时后端保持旧密钥,输入新值才轮换。 */} page.setPlatformForm({ ...form, callbackSecret: event.target.value })} /> {!isLeaderCC ? ( page.setPlatformForm({ ...form, callbackIpWhitelistText: event.target.value }) } /> ) : null} {/* 厂商差异字段放 JSON:如 uid_mode、default_lang、token_ttl_seconds、game_level_uid。 */} page.setPlatformForm({ ...form, adapterConfigJson })} /> ); } function PlatformManageDialog({ open, page, onClose }) { const platforms = page.platformData.items || []; return ( { event.preventDefault(); onClose(); }} >
{platforms.map((platform) => (
{platform.platformName || platform.platformCode} {platform.platformCode} / {platform.adapterType || "未配置"} {platform.apiBaseUrl ? {platform.apiBaseUrl} : null} 密钥:{platform.callbackSecret || "未配置"}
{platform.callbackSecretSet ? "密钥已配置" : "未配置密钥"} page.openEditPlatform(platform)} > page.fetchPlatformGames(platform)} >
))}
); } function ProviderGameListDialog({ open, page, onClose }) { const games = page.syncCandidates || []; const selected = new Set(page.selectedSyncGameIds || []); return ( {page.allSyncGamesSelected ? "取消全选" : "全选"} } >
{games.length > 0 ? ( games.map((game) => (
page.toggleSyncGame(game.providerGameId)} />
{game.providerGameId} 默认下架 {game.launchUrl ? ( {game.launchUrl} ) : null}
page.importSingleSyncGame(game)} >
)) ) : ( 暂无可添加游戏 )}
); } function SwitchOnlyField({ checked, checkedLabel, disabled, label, onChange, uncheckedLabel }) { return ( onChange(event.target.checked, event)} /> ); } function formatNumber(value) { return Number(value || 0).toLocaleString(); } function launchModeLabel(value) { return launchModeOptions.find(([mode]) => mode === value)?.[1] || "全屏"; }