diff --git a/contracts/admin-openapi.json b/contracts/admin-openapi.json index a75a035..1b77c52 100644 --- a/contracts/admin-openapi.json +++ b/contracts/admin-openapi.json @@ -3138,6 +3138,84 @@ "x-permissions": ["room-pin:cancel"] } }, + "/admin/rooms/robot-rooms": { + "get": { + "operationId": "listRobotRooms", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "x-permission": "room-robot:view", + "x-permissions": ["room-robot:view"] + }, + "post": { + "operationId": "createRobotRoom", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "x-permission": "room-robot:create", + "x-permissions": ["room-robot:create"] + } + }, + "/admin/rooms/robot-rooms/available-robots": { + "get": { + "operationId": "listAvailableRoomRobots", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "x-permission": "room-robot:view", + "x-permissions": ["room-robot:view"] + } + }, + "/admin/rooms/robot-rooms/{room_id}/start": { + "post": { + "operationId": "startRobotRoom", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "parameters": [ + { + "in": "path", + "name": "room_id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "x-permission": "room-robot:update", + "x-permissions": ["room-robot:update"] + } + }, + "/admin/rooms/robot-rooms/{room_id}/stop": { + "post": { + "operationId": "stopRobotRoom", + "responses": { + "200": { + "$ref": "#/components/responses/EmptyResponse" + } + }, + "parameters": [ + { + "in": "path", + "name": "room_id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "x-permission": "room-robot:update", + "x-permissions": ["room-robot:update"] + } + }, "/admin/users/level-config": { "get": { "operationId": "listLevelConfig", diff --git a/src/app/navigation/menu.js b/src/app/navigation/menu.js index bbcbd99..e92b68e 100644 --- a/src/app/navigation/menu.js +++ b/src/app/navigation/menu.js @@ -141,6 +141,7 @@ export const fallbackNavigation = [ routeNavItem("room-list", { icon: BedroomParentOutlined }), routeNavItem("room-pins", { icon: PushPinOutlined }), routeNavItem("room-config", { icon: SettingsApplicationsOutlined }), + routeNavItem("room-robots", { icon: GroupsOutlined }), ], }, { diff --git a/src/app/permissions.ts b/src/app/permissions.ts index 1f6f0bd..9b43b66 100644 --- a/src/app/permissions.ts +++ b/src/app/permissions.ts @@ -97,6 +97,9 @@ export const PERMISSIONS = { roomPinCancel: "room-pin:cancel", roomConfigView: "room-config:view", roomConfigUpdate: "room-config:update", + roomRobotView: "room-robot:view", + roomRobotCreate: "room-robot:create", + roomRobotUpdate: "room-robot:update", appConfigView: "app-config:view", appConfigUpdate: "app-config:update", appVersionView: "app-version:view", @@ -183,6 +186,7 @@ export const MENU_CODES = { roomList: "room-list", roomPins: "room-pins", roomConfig: "room-config", + roomRobots: "room-robots", appConfig: "app-config", appConfigH5: "app-config-h5", appConfigBanners: "app-config-banners", diff --git a/src/features/rooms/api.ts b/src/features/rooms/api.ts index dff2dfb..a4793e2 100644 --- a/src/features/rooms/api.ts +++ b/src/features/rooms/api.ts @@ -9,6 +9,9 @@ import type { RoomDto, RoomPinDto, RoomPinPayload, + AvailableRoomRobotDto, + RobotRoomDto, + RobotRoomPayload, RoomUpdatePayload, } from "@/shared/api/types"; @@ -72,3 +75,40 @@ export function updateRoomConfig(payload: RoomConfigPayload): Promise> { + const endpoint = API_ENDPOINTS.listRobotRooms; + return apiRequest>(apiEndpointPath(API_OPERATIONS.listRobotRooms), { + method: endpoint.method, + query, + }); +} + +export function listAvailableRoomRobots(): Promise { + const endpoint = API_ENDPOINTS.listAvailableRoomRobots; + return apiRequest(apiEndpointPath(API_OPERATIONS.listAvailableRoomRobots), { + method: endpoint.method, + }); +} + +export function createRobotRoom(payload: RobotRoomPayload): Promise { + const endpoint = API_ENDPOINTS.createRobotRoom; + return apiRequest(apiEndpointPath(API_OPERATIONS.createRobotRoom), { + body: payload, + method: endpoint.method, + }); +} + +export function startRobotRoom(roomId: EntityId): Promise { + const endpoint = API_ENDPOINTS.startRobotRoom; + return apiRequest(apiEndpointPath(API_OPERATIONS.startRobotRoom, { room_id: roomId }), { + method: endpoint.method, + }); +} + +export function stopRobotRoom(roomId: EntityId): Promise { + const endpoint = API_ENDPOINTS.stopRobotRoom; + return apiRequest(apiEndpointPath(API_OPERATIONS.stopRobotRoom, { room_id: roomId }), { + method: endpoint.method, + }); +} diff --git a/src/features/rooms/hooks/useRobotRoomsPage.js b/src/features/rooms/hooks/useRobotRoomsPage.js new file mode 100644 index 0000000..e4cf6da --- /dev/null +++ b/src/features/rooms/hooks/useRobotRoomsPage.js @@ -0,0 +1,242 @@ +import { useEffect, useMemo, useState } from "react"; +import { listGifts } from "@/features/resources/api"; +import { + createRobotRoom, + listAvailableRoomRobots, + listRobotRooms, + startRobotRoom, + stopRobotRoom, +} from "@/features/rooms/api"; +import { useRoomAbilities } from "@/features/rooms/permissions.js"; +import { robotRoomCreateSchema } from "@/features/rooms/schema"; +import { parseForm } from "@/shared/forms/validation"; +import { usePaginatedQuery } from "@/shared/hooks/usePaginatedQuery.js"; +import { useToast } from "@/shared/ui/ToastProvider.jsx"; + +const pageSize = 50; +const emptyData = { items: [], page: 1, pageSize, total: 0 }; + +function emptyForm() { + return { + candidateRobotUserIds: [], + giftIds: [], + luckyComboMax: "10000", + luckyComboMin: "100", + luckyGiftIds: [], + luckyPauseMaxMs: "20000", + luckyPauseMinMs: "5000", + maxRobotCount: "8", + minRobotCount: "6", + normalGiftIntervalMs: "10000", + ownerRobotUserId: "", + roomName: "", + visibleRegionId: 0, + }; +} + +export function useRobotRoomsPage() { + const abilities = useRoomAbilities(); + const { showToast } = useToast(); + const [activeAction, setActiveAction] = useState(""); + const [availableRobotsState, setAvailableRobotsState] = useState({ error: "", loading: false, options: [] }); + const [candidatesTouched, setCandidatesTouched] = useState(false); + const [form, setForm] = useState(emptyForm); + const [giftOptionsState, setGiftOptionsState] = useState({ error: "", loading: false, options: [] }); + const [loadingAction, setLoadingAction] = useState(""); + const [page, setPage] = useState(1); + const [status, setStatus] = useState("active"); + const filters = useMemo(() => ({ status }), [status]); + const { + data = emptyData, + error, + loading, + reload, + } = usePaginatedQuery({ + errorMessage: "加载机器人房间列表失败", + fetcher: listRobotRooms, + filters, + page, + pageSize, + queryKey: ["robot-rooms", filters, page], + }); + + const loadAvailableRobots = async () => { + setAvailableRobotsState((current) => ({ ...current, error: "", loading: true })); + try { + const options = await listAvailableRoomRobots(); + setAvailableRobotsState({ error: "", loading: false, options: options || [] }); + } catch (err) { + setAvailableRobotsState({ error: err.message || "加载可用机器人失败", loading: false, options: [] }); + } + }; + + const loadGiftOptions = async () => { + setGiftOptionsState((current) => ({ ...current, error: "", loading: true })); + try { + const result = await listGifts({ page: 1, page_size: 200, status: "active" }); + setGiftOptionsState({ error: "", loading: false, options: result?.items || [] }); + } catch (err) { + setGiftOptionsState({ error: err.message || "加载礼物列表失败", loading: false, options: [] }); + } + }; + + useEffect(() => { + if (activeAction !== "create") { + return; + } + loadAvailableRobots(); + loadGiftOptions(); + }, [activeAction]); + + useEffect(() => { + if (activeAction !== "create" || candidatesTouched) { + return; + } + const ids = availableRobotsState.options.map((item) => item.userId).filter(Boolean); + if (ids.length > 0) { + setForm((current) => ({ ...current, candidateRobotUserIds: ids })); + } + }, [activeAction, availableRobotsState.options, candidatesTouched]); + + const changeStatus = (value) => { + setStatus(value || "active"); + setPage(1); + }; + + const resetFilters = () => { + setStatus("active"); + setPage(1); + }; + + const openCreate = () => { + setActiveAction("create"); + setCandidatesTouched(false); + setForm(emptyForm()); + setAvailableRobotsState({ error: "", loading: false, options: [] }); + setGiftOptionsState({ error: "", loading: false, options: [] }); + }; + + const closeAction = () => { + setActiveAction(""); + }; + + const submitCreate = async (event) => { + event.preventDefault(); + await runAction("create", "机器人房间已创建", async () => { + const payload = parseForm(robotRoomCreateSchema, form); + await createRobotRoom(payload); + closeAction(); + await reload(); + }); + }; + + const setRoomStatus = async (room) => { + if (!room?.roomId || !abilities.canRobotUpdate) { + return; + } + const nextActive = room.status !== "active"; + await runAction(`status-${room.roomId}`, nextActive ? "机器人房间已启动" : "机器人房间已停止", async () => { + if (nextActive) { + await startRobotRoom(room.roomId); + } else { + await stopRobotRoom(room.roomId); + } + await reload(); + }); + }; + + const selectOwnerRobot = (robot) => { + setForm((current) => ({ ...current, ownerRobotUserId: robot?.userId || "" })); + }; + + const selectCandidateRobots = (robots) => { + setCandidatesTouched(true); + setForm((current) => ({ ...current, candidateRobotUserIds: robots.map((robot) => robot.userId).filter(Boolean) })); + }; + + const selectGiftIds = (key, gifts) => { + setForm((current) => ({ ...current, [key]: gifts.map(giftId).filter(Boolean) })); + }; + + const runAction = async (action, successMessage, submitter) => { + setLoadingAction(action); + try { + await submitter(); + showToast(successMessage, "success"); + } catch (err) { + showToast(err.message || "操作失败", "error"); + } finally { + setLoadingAction(""); + } + }; + + const selectedOwnerRobot = useMemo( + () => availableRobotsState.options.find((item) => item.userId === String(form.ownerRobotUserId)) || null, + [availableRobotsState.options, form.ownerRobotUserId], + ); + const selectedCandidateRobots = useMemo(() => { + const selected = new Set((form.candidateRobotUserIds || []).map(String)); + return availableRobotsState.options.filter((item) => selected.has(String(item.userId))); + }, [availableRobotsState.options, form.candidateRobotUserIds]); + const selectedGiftOptions = useMemo(() => { + const selected = new Set((form.giftIds || []).map(String)); + return giftOptionsState.options.filter((item) => selected.has(giftId(item))); + }, [form.giftIds, giftOptionsState.options]); + const selectedLuckyGiftOptions = useMemo(() => { + const selected = new Set((form.luckyGiftIds || []).map(String)); + return giftOptionsState.options.filter((item) => selected.has(giftId(item))); + }, [form.luckyGiftIds, giftOptionsState.options]); + + return { + abilities, + activeAction, + availableRobotsError: availableRobotsState.error, + availableRobotsLoading: availableRobotsState.loading, + availableRobots: availableRobotsState.options, + changeStatus, + closeAction, + data, + error, + form, + giftOptions: giftOptionsState.options, + giftOptionsError: giftOptionsState.error, + giftOptionsLoading: giftOptionsState.loading, + loading, + loadingAction, + openCreate, + page, + reload, + resetFilters, + selectCandidateRobots, + selectGiftIds, + selectOwnerRobot, + selectedCandidateRobots, + selectedGiftOptions, + selectedLuckyGiftOptions, + selectedOwnerRobot, + setForm, + setPage, + setRoomStatus, + status, + submitCreate, + }; +} + +export function robotOptionLabel(robot) { + if (!robot) { + return ""; + } + const user = robot.user || {}; + return [user.username, user.displayUserId, robot.userId].filter(Boolean).join(" / "); +} + +export function giftId(gift) { + return String(gift?.giftId || ""); +} + +export function giftOptionLabel(gift) { + if (!gift) { + return ""; + } + return [gift.name, gift.giftId].filter(Boolean).join(" / "); +} diff --git a/src/features/rooms/pages/RoomRobotPage.jsx b/src/features/rooms/pages/RoomRobotPage.jsx new file mode 100644 index 0000000..3901e21 --- /dev/null +++ b/src/features/rooms/pages/RoomRobotPage.jsx @@ -0,0 +1,365 @@ +import AddOutlined from "@mui/icons-material/AddOutlined"; +import BedroomParentOutlined from "@mui/icons-material/BedroomParentOutlined"; +import Autocomplete from "@mui/material/Autocomplete"; +import TextField from "@mui/material/TextField"; +import Tooltip from "@mui/material/Tooltip"; +import { Button } from "@/shared/ui/Button.jsx"; +import { AdminFormDialog } from "@/shared/ui/AdminFormDialog.jsx"; +import { AdminUserIdentity } from "@/shared/ui/AdminUserIdentity.jsx"; +import { DataState } from "@/shared/ui/DataState.jsx"; +import { DataTable } from "@/shared/ui/DataTable.jsx"; +import { AdminSwitch } from "@/shared/ui/AdminSwitch.jsx"; +import { AdminFilterResetButton } from "@/shared/ui/AdminListLayout.jsx"; +import { createOptionsColumnFilter } from "@/shared/ui/tableFilters.js"; +import { formatMillis } from "@/shared/utils/time.js"; +import { + giftId, + giftOptionLabel, + robotOptionLabel, + useRobotRoomsPage, +} from "@/features/rooms/hooks/useRobotRoomsPage.js"; +import styles from "@/features/rooms/rooms.module.css"; + +const statusOptions = [ + { label: "运行中", value: "active" }, + { label: "已停止", value: "stopped" }, + { label: "全部状态", value: "all" }, +]; + +const columns = [ + { + key: "room", + label: "房间信息", + width: "minmax(280px, 1.4fr)", + render: (room) => , + }, + { + key: "owner", + label: "房主机器人", + width: "minmax(220px, 1fr)", + render: (room) => , + }, + { + key: "robots", + label: "进房机器人", + width: "minmax(120px, 0.5fr)", + render: (room) => Number(room.robotUserIds?.length || room.robots?.length || 0).toLocaleString("zh-CN"), + }, + { + key: "normalRule", + label: "普通礼物", + width: "minmax(190px, 0.8fr)", + render: (room) => ( + + ), + }, + { + key: "luckyRule", + label: "幸运礼物", + width: "minmax(220px, 0.9fr)", + render: (room) => ( + + ), + }, + { + key: "createdAt", + label: "创建时间", + width: "minmax(170px, 0.8fr)", + render: (room) => formatMillis(room.createdAtMs), + }, +]; + +export function RoomRobotPage() { + const page = useRobotRoomsPage(); + const items = page.data.items || []; + const total = page.data.total || 0; + const tableColumns = [ + ...columns.map((column) => + column.key === "createdAt" + ? { + ...column, + filter: createOptionsColumnFilter({ + options: statusOptions, + placeholder: "房间状态", + value: page.status, + onChange: page.changeStatus, + }), + } + : column, + ), + { + key: "status", + label: "状态", + width: "minmax(112px, 0.5fr)", + render: (room) => , + }, + ]; + + return ( +
+
+
+
+ +
+ {page.abilities.canRobotCreate ? ( +
+ +
+ ) : null} +
+ + +
+ room.roomId} + pagination={ + total > 0 + ? { + page: page.page, + pageSize: page.data.pageSize || 50, + total, + onPageChange: page.setPage, + } + : undefined + } + /> +
+
+
+ + + page.setForm({ ...page.form, roomName: event.target.value })} + /> + option.userId === value.userId} + loading={page.availableRobotsLoading} + noOptionsText="当前无数据" + options={page.availableRobots} + renderInput={(params) => ( + + )} + renderOption={(props, option) => ( +
  • + +
  • + )} + size="small" + value={page.selectedOwnerRobot} + onChange={(_, option) => page.selectOwnerRobot(option)} + /> + option.userId === value.userId} + loading={page.availableRobotsLoading} + noOptionsText="当前无数据" + options={page.availableRobots} + renderInput={(params) => ( + + )} + renderOption={(props, option) => ( +
  • + +
  • + )} + size="small" + value={page.selectedCandidateRobots} + onChange={(_, options) => page.selectCandidateRobots(options)} + /> +
    + page.setForm({ ...page.form, minRobotCount: event.target.value })} + /> + page.setForm({ ...page.form, maxRobotCount: event.target.value })} + /> +
    + giftId(option) === giftId(value)} + loading={page.giftOptionsLoading} + noOptionsText="当前无数据" + options={page.giftOptions} + renderInput={(params) => ( + + )} + size="small" + value={page.selectedGiftOptions} + onChange={(_, options) => page.selectGiftIds("giftIds", options)} + /> + page.setForm({ ...page.form, normalGiftIntervalMs: event.target.value })} + /> + giftId(option) === giftId(value)} + loading={page.giftOptionsLoading} + noOptionsText="当前无数据" + options={page.giftOptions} + renderInput={(params) => ( + + )} + size="small" + value={page.selectedLuckyGiftOptions} + onChange={(_, options) => page.selectGiftIds("luckyGiftIds", options)} + /> +
    + page.setForm({ ...page.form, luckyComboMin: event.target.value })} + /> + page.setForm({ ...page.form, luckyComboMax: event.target.value })} + /> + page.setForm({ ...page.form, luckyPauseMinMs: event.target.value })} + /> + page.setForm({ ...page.form, luckyPauseMaxMs: event.target.value })} + /> +
    +
    +
    + ); +} + +function RobotRoomIdentity({ room }) { + return ( +
    + + {room.coverUrl ? : } + +
    +
    {room.title || "-"}
    +
    长ID {room.roomId || "-"}
    +
    短ID {room.roomShortId || "-"}
    +
    +
    + ); +} + +function RobotIdentity({ user = {}, fallbackId = "" }) { + return ; +} + +function RobotRoomStatus({ page, room }) { + const checked = room.status === "active"; + return ( +
    + + + page.setRoomStatus(room)} + /> + + +
    + ); +} + +function RuleStack({ rows }) { + return ( +
    + {rows.map((row) => ( + + {row} + + ))} +
    + ); +} + +function formatSeconds(ms) { + const seconds = Math.round(Number(ms || 0) / 1000); + return `${seconds.toLocaleString("zh-CN")}秒`; +} diff --git a/src/features/rooms/permissions.js b/src/features/rooms/permissions.js index dd8e33c..52f57ff 100644 --- a/src/features/rooms/permissions.js +++ b/src/features/rooms/permissions.js @@ -11,6 +11,9 @@ export function useRoomAbilities() { canPinCancel: can(PERMISSIONS.roomPinCancel), canPinCreate: can(PERMISSIONS.roomPinCreate), canPinView: can(PERMISSIONS.roomPinView), + canRobotCreate: can(PERMISSIONS.roomRobotCreate), + canRobotUpdate: can(PERMISSIONS.roomRobotUpdate), + canRobotView: can(PERMISSIONS.roomRobotView), canUpdate: can(PERMISSIONS.roomUpdate), canUpload: can(PERMISSIONS.uploadCreate), canView: can(PERMISSIONS.roomView), diff --git a/src/features/rooms/rooms.module.css b/src/features/rooms/rooms.module.css index 9e76cc9..7cfb07f 100644 --- a/src/features/rooms/rooms.module.css +++ b/src/features/rooms/rooms.module.css @@ -54,6 +54,18 @@ gap: var(--space-2); } +.formGrid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: var(--space-3); +} + +@media (max-width: 720px) { + .formGrid { + grid-template-columns: 1fr; + } +} + .search { flex: 0 1 320px; } diff --git a/src/features/rooms/routes.js b/src/features/rooms/routes.js index e296822..75f4fc1 100644 --- a/src/features/rooms/routes.js +++ b/src/features/rooms/routes.js @@ -25,4 +25,12 @@ export const roomRoutes = [ path: "/rooms/config", permission: PERMISSIONS.roomConfigView, }, + { + label: "机器人房间", + loader: () => import("./pages/RoomRobotPage.jsx").then((module) => module.RoomRobotPage), + menuCode: MENU_CODES.roomRobots, + pageKey: "room-robots", + path: "/rooms/robots", + permission: PERMISSIONS.roomRobotView, + }, ]; diff --git a/src/features/rooms/schema.ts b/src/features/rooms/schema.ts index 42562e6..71d8ecb 100644 --- a/src/features/rooms/schema.ts +++ b/src/features/rooms/schema.ts @@ -70,7 +70,37 @@ export const roomPinCreateSchema = z path: ["expiresAtMs"], }); +export const robotRoomCreateSchema = z + .object({ + candidateRobotUserIds: z.array(z.coerce.number().int().positive()).min(1, "请选择候选机器人"), + giftIds: z.array(z.string().trim().min(1)).min(1, "请选择普通礼物合集"), + luckyComboMax: z.coerce.number().int().min(1, "幸运礼物连击范围不正确"), + luckyComboMin: z.coerce.number().int().min(1, "幸运礼物连击范围不正确"), + luckyGiftIds: z.array(z.string().trim().min(1)).min(1, "请选择幸运礼物合集"), + luckyPauseMaxMs: z.coerce.number().int().min(1000, "幸运礼物间隔范围不正确"), + luckyPauseMinMs: z.coerce.number().int().min(1000, "幸运礼物间隔范围不正确"), + maxRobotCount: z.coerce.number().int().min(1, "机器人数量范围不正确"), + minRobotCount: z.coerce.number().int().min(1, "机器人数量范围不正确"), + normalGiftIntervalMs: z.coerce.number().int().min(1000, "普通礼物频次不正确"), + ownerRobotUserId: z.coerce.number().int().positive("请选择房主机器人"), + roomName: z.string().trim().max(128, "房间名称不能超过 128 个字符").optional().default(""), + visibleRegionId: z.coerce.number().int().min(0, "区域 ID 不正确").default(0), + }) + .refine((value) => value.maxRobotCount >= value.minRobotCount, { + message: "机器人数量范围不正确", + path: ["maxRobotCount"], + }) + .refine((value) => value.luckyComboMax >= value.luckyComboMin, { + message: "幸运礼物连击范围不正确", + path: ["luckyComboMax"], + }) + .refine((value) => value.luckyPauseMaxMs >= value.luckyPauseMinMs, { + message: "幸运礼物间隔范围不正确", + path: ["luckyPauseMaxMs"], + }); + export type RoomUpdateForm = z.infer; export type RoomStatusForm = z.infer; export type RoomConfigForm = z.infer; export type RoomPinCreateForm = z.infer; +export type RobotRoomCreateForm = z.infer; diff --git a/src/shared/api/generated/endpoints.ts b/src/shared/api/generated/endpoints.ts index 2db6b5f..7040a6c 100644 --- a/src/shared/api/generated/endpoints.ts +++ b/src/shared/api/generated/endpoints.ts @@ -52,6 +52,7 @@ export const API_OPERATIONS = { createRegion: "createRegion", createResource: "createResource", createResourceGroup: "createResourceGroup", + createRobotRoom: "createRobotRoom", createRole: "createRole", createRoomPin: "createRoomPin", createSplashScreen: "createSplashScreen", @@ -128,6 +129,7 @@ export const API_OPERATIONS = { listAgencies: "listAgencies", listApps: "listApps", listAppVersions: "listAppVersions", + listAvailableRoomRobots: "listAvailableRoomRobots", listBanners: "listBanners", listBDLeaders: "listBDLeaders", listBDs: "listBDs", @@ -172,6 +174,7 @@ export const API_OPERATIONS = { listResourceGroups: "listResourceGroups", listResources: "listResources", listResourceShopItems: "listResourceShopItems", + listRobotRooms: "listRobotRooms", listRoles: "listRoles", listRoomPins: "listRoomPins", listRoomRpsChallenges: "listRoomRpsChallenges", @@ -218,6 +221,8 @@ export const API_OPERATIONS = { setThirdPartyPaymentMethodStatus: "setThirdPartyPaymentMethodStatus", setWeeklyStarCycleStatus: "setWeeklyStarCycleStatus", sortMenus: "sortMenus", + startRobotRoom: "startRobotRoom", + stopRobotRoom: "stopRobotRoom", syncPermissions: "syncPermissions", syncThirdPartyPaymentRates: "syncThirdPartyPaymentRates", updateAchievementDefinition: "updateAchievementDefinition", @@ -546,6 +551,13 @@ export const API_ENDPOINTS: Record = { permission: "resource-group:create", permissions: ["resource-group:create"] }, + createRobotRoom: { + method: "POST", + operationId: API_OPERATIONS.createRobotRoom, + path: "/v1/admin/rooms/robot-rooms", + permission: "room-robot:create", + permissions: ["room-robot:create"] + }, createRole: { method: "POST", operationId: API_OPERATIONS.createRole, @@ -1076,6 +1088,13 @@ export const API_ENDPOINTS: Record = { permission: "app-version:view", permissions: ["app-version:view"] }, + listAvailableRoomRobots: { + method: "GET", + operationId: API_OPERATIONS.listAvailableRoomRobots, + path: "/v1/admin/rooms/robot-rooms/available-robots", + permission: "room-robot:view", + permissions: ["room-robot:view"] + }, listBanners: { method: "GET", operationId: API_OPERATIONS.listBanners, @@ -1384,6 +1403,13 @@ export const API_ENDPOINTS: Record = { permission: "resource-shop:view", permissions: ["resource-shop:view"] }, + listRobotRooms: { + method: "GET", + operationId: API_OPERATIONS.listRobotRooms, + path: "/v1/admin/rooms/robot-rooms", + permission: "room-robot:view", + permissions: ["room-robot:view"] + }, listRoles: { method: "GET", operationId: API_OPERATIONS.listRoles, @@ -1694,6 +1720,20 @@ export const API_ENDPOINTS: Record = { permission: "menu:sort", permissions: ["menu:sort"] }, + startRobotRoom: { + method: "POST", + operationId: API_OPERATIONS.startRobotRoom, + path: "/v1/admin/rooms/robot-rooms/{room_id}/start", + permission: "room-robot:update", + permissions: ["room-robot:update"] + }, + stopRobotRoom: { + method: "POST", + operationId: API_OPERATIONS.stopRobotRoom, + path: "/v1/admin/rooms/robot-rooms/{room_id}/stop", + permission: "room-robot:update", + permissions: ["room-robot:update"] + }, syncPermissions: { method: "POST", operationId: API_OPERATIONS.syncPermissions, diff --git a/src/shared/api/generated/schema.d.ts b/src/shared/api/generated/schema.d.ts index 9ffd83a..d811b82 100644 --- a/src/shared/api/generated/schema.d.ts +++ b/src/shared/api/generated/schema.d.ts @@ -2164,6 +2164,70 @@ export interface paths { patch?: never; trace?: never; }; + "/admin/rooms/robot-rooms": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["listRobotRooms"]; + put?: never; + post: operations["createRobotRoom"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/admin/rooms/robot-rooms/available-robots": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["listAvailableRoomRobots"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/admin/rooms/robot-rooms/{room_id}/start": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["startRobotRoom"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/admin/rooms/robot-rooms/{room_id}/stop": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["stopRobotRoom"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/admin/users/level-config": { parameters: { query?: never; @@ -5977,6 +6041,70 @@ export interface operations { 200: components["responses"]["EmptyResponse"]; }; }; + listRobotRooms: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + createRobotRoom: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + listAvailableRoomRobots: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + startRobotRoom: { + parameters: { + query?: never; + header?: never; + path: { + room_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; + stopRobotRoom: { + parameters: { + query?: never; + header?: never; + path: { + room_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: components["responses"]["EmptyResponse"]; + }; + }; listLevelConfig: { parameters: { query?: never; diff --git a/src/shared/api/types.ts b/src/shared/api/types.ts index 7028f40..7457ecb 100644 --- a/src/shared/api/types.ts +++ b/src/shared/api/types.ts @@ -938,6 +938,61 @@ export interface RoomPinPayload { weight: number; } +export interface RobotGiftRuleDto { + giftIds?: string[]; + luckyGiftIds?: string[]; + luckyComboMax?: number; + luckyComboMin?: number; + luckyPauseMaxMs?: number; + luckyPauseMinMs?: number; + normalGiftIntervalMs?: number; +} + +export interface RobotRoomDto { + appCode?: string; + coverUrl?: string; + createdAtMs?: number; + createdByAdminId?: number; + giftRule?: RobotGiftRuleDto; + owner?: RoomOwnerDto; + ownerRobotUserId?: string; + regionName?: string; + robots?: RoomOwnerDto[]; + robotUserIds?: string[]; + roomId: string; + roomShortId?: string; + status?: string; + title?: string; + updatedAtMs?: number; + visibleRegionId?: number; +} + +export interface AvailableRoomRobotDto { + lastUsedAtMs?: number; + roomScene?: string; + status?: string; + usedCount?: number; + user?: RoomOwnerDto; + userId: string; +} + +export interface RobotRoomPayload { + candidateRobotUserIds: EntityId[]; + giftIds: string[]; + luckyComboMax: number; + luckyComboMin: number; + luckyGiftIds: string[]; + luckyPauseMaxMs: number; + luckyPauseMinMs: number; + maxRobotCount: number; + minRobotCount: number; + normalGiftIntervalMs: number; + ownerRobotUserId: EntityId; + roomAvatar?: string; + roomName?: string; + visibleRegionId?: EntityId; +} + export interface RoomOwnerDto { avatar?: string; displayUserId?: string;