机器人
This commit is contained in:
parent
4af2c311bf
commit
9d930da859
@ -3169,6 +3169,28 @@
|
|||||||
"x-permissions": ["room-config:update"]
|
"x-permissions": ["room-config:update"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/admin/rooms/whitelist": {
|
||||||
|
"get": {
|
||||||
|
"operationId": "getRoomWhitelistConfig",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/components/responses/EmptyResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-permission": "room-whitelist:view",
|
||||||
|
"x-permissions": ["room-whitelist:view"]
|
||||||
|
},
|
||||||
|
"put": {
|
||||||
|
"operationId": "updateRoomWhitelistConfig",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/components/responses/EmptyResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-permission": "room-whitelist:update",
|
||||||
|
"x-permissions": ["room-whitelist:update"]
|
||||||
|
}
|
||||||
|
},
|
||||||
"/admin/rooms/pins": {
|
"/admin/rooms/pins": {
|
||||||
"get": {
|
"get": {
|
||||||
"operationId": "listRoomPins",
|
"operationId": "listRoomPins",
|
||||||
@ -3247,6 +3269,28 @@
|
|||||||
"x-permissions": ["room-robot:view"]
|
"x-permissions": ["room-robot:view"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/admin/rooms/robot-rooms/human-config": {
|
||||||
|
"get": {
|
||||||
|
"operationId": "getHumanRoomRobotConfig",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/components/responses/EmptyResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-permission": "room-robot:view",
|
||||||
|
"x-permissions": ["room-robot:view"]
|
||||||
|
},
|
||||||
|
"put": {
|
||||||
|
"operationId": "updateHumanRoomRobotConfig",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/components/responses/EmptyResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-permission": "room-robot:update",
|
||||||
|
"x-permissions": ["room-robot:update"]
|
||||||
|
}
|
||||||
|
},
|
||||||
"/admin/rooms/robot-rooms/{room_id}/start": {
|
"/admin/rooms/robot-rooms/{room_id}/start": {
|
||||||
"post": {
|
"post": {
|
||||||
"operationId": "startRobotRoom",
|
"operationId": "startRobotRoom",
|
||||||
|
|||||||
@ -242,6 +242,12 @@ export function DatabiApp() {
|
|||||||
))}
|
))}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section className="business-metric-grid robot-gift-metric-grid" aria-label="真人房机器人送礼指标">
|
||||||
|
{model.robotGiftKpis.map((item) => (
|
||||||
|
<MetricCard key={item.label} item={item} loading={showSkeleton} onClick={() => setTrendModalItem(item)} />
|
||||||
|
))}
|
||||||
|
</section>
|
||||||
|
|
||||||
<section className="databi-grid databi-grid--top">
|
<section className="databi-grid databi-grid--top">
|
||||||
<GlobalOverviewPanel countryBreakdown={model.countryBreakdown} loading={showSkeleton} topCountries={model.topCountries} />
|
<GlobalOverviewPanel countryBreakdown={model.countryBreakdown} loading={showSkeleton} topCountries={model.topCountries} />
|
||||||
<RevenueTrendPanel loading={showSkeleton} revenueSeries={model.revenueSeries} />
|
<RevenueTrendPanel loading={showSkeleton} revenueSeries={model.revenueSeries} />
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { act, render, screen } from "@testing-library/react";
|
import { act, render, screen, within } from "@testing-library/react";
|
||||||
import { afterEach, beforeEach, expect, test, vi } from "vitest";
|
import { afterEach, beforeEach, expect, test, vi } from "vitest";
|
||||||
import { DatabiApp } from "./DatabiApp.jsx";
|
import { DatabiApp } from "./DatabiApp.jsx";
|
||||||
import { fetchFilterOptions, fetchSelfGameStatisticsOverview, fetchStatisticsOverview } from "./api.js";
|
import { fetchFilterOptions, fetchSelfGameStatisticsOverview, fetchStatisticsOverview } from "./api.js";
|
||||||
@ -74,6 +74,29 @@ test("loads self game statistics from the big screen switch", async () => {
|
|||||||
expect(screen.getByText("创建局")).toBeTruthy();
|
expect(screen.getByText("创建局")).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("renders real-room robot gift cards in a separate row", async () => {
|
||||||
|
fetchStatisticsOverview.mockResolvedValue({
|
||||||
|
real_room_robot_avg_room_gift_coin: 300,
|
||||||
|
real_room_robot_gift_room_count: 2,
|
||||||
|
real_room_robot_lucky_gift_coin: 200,
|
||||||
|
real_room_robot_normal_gift_coin: 100,
|
||||||
|
real_room_robot_super_gift_coin: 300,
|
||||||
|
updated_at_ms: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
render(<DatabiApp />);
|
||||||
|
|
||||||
|
await flushEffects();
|
||||||
|
|
||||||
|
const businessSection = screen.getByLabelText("业务指标");
|
||||||
|
const robotGiftSection = screen.getByLabelText("真人房机器人送礼指标");
|
||||||
|
expect(within(businessSection).queryByText("真人房机器人普通礼物")).toBeNull();
|
||||||
|
expect(within(robotGiftSection).getByText("真人房机器人普通礼物")).toBeTruthy();
|
||||||
|
expect(within(robotGiftSection).getByText("真人房机器人幸运礼物")).toBeTruthy();
|
||||||
|
expect(within(robotGiftSection).getByText("真人房机器人超级幸运礼物")).toBeTruthy();
|
||||||
|
expect(within(robotGiftSection).getByText("真人房机器人房均礼物")).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
async function flushEffects() {
|
async function flushEffects() {
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
for (let index = 0; index < 10; index += 1) {
|
for (let index = 0; index < 10; index += 1) {
|
||||||
|
|||||||
@ -19,6 +19,12 @@ export function createDashboardModel(overview, { appCode, countryId, range, time
|
|||||||
const arppu = readNumber(source, "arppu_usd_minor", "arppuUsdMinor");
|
const arppu = readNumber(source, "arppu_usd_minor", "arppuUsdMinor");
|
||||||
const giftCoinSpent = readNumber(source, "gift_coin_spent", "giftCoinSpent");
|
const giftCoinSpent = readNumber(source, "gift_coin_spent", "giftCoinSpent");
|
||||||
const coinSellerTransferCoin = readNumber(source, "coin_seller_transfer_coin", "coinSellerTransferCoin");
|
const coinSellerTransferCoin = readNumber(source, "coin_seller_transfer_coin", "coinSellerTransferCoin");
|
||||||
|
const realRoomRobotNormalGiftCoin = readNumber(source, "real_room_robot_normal_gift_coin", "realRoomRobotNormalGiftCoin");
|
||||||
|
const realRoomRobotLuckyGiftCoin = readNumber(source, "real_room_robot_lucky_gift_coin", "realRoomRobotLuckyGiftCoin");
|
||||||
|
const realRoomRobotSuperGiftCoin = readNumber(source, "real_room_robot_super_gift_coin", "realRoomRobotSuperGiftCoin");
|
||||||
|
const realRoomRobotTotalGiftCoin = readNumber(source, "real_room_robot_total_gift_coin", "realRoomRobotTotalGiftCoin") || realRoomRobotNormalGiftCoin + realRoomRobotLuckyGiftCoin + realRoomRobotSuperGiftCoin;
|
||||||
|
const realRoomRobotGiftRoomCount = readNumber(source, "real_room_robot_gift_room_count", "realRoomRobotGiftRoomCount");
|
||||||
|
const realRoomRobotAvgRoomGiftCoin = readNumber(source, "real_room_robot_avg_room_gift_coin", "realRoomRobotAvgRoomGiftCoin") || Math.round(ratio(realRoomRobotTotalGiftCoin, realRoomRobotGiftRoomCount));
|
||||||
const luckyGiftPools = normalizeLuckyGiftPools(source);
|
const luckyGiftPools = normalizeLuckyGiftPools(source);
|
||||||
const luckyGiftPoolTotals = summarizeLuckyGiftPools(luckyGiftPools);
|
const luckyGiftPoolTotals = summarizeLuckyGiftPools(luckyGiftPools);
|
||||||
const luckyGiftTurnover = luckyGiftPools.length ? luckyGiftPoolTotals.turnover : readNumber(source, "room_lucky_gift_turnover", "roomLuckyGiftTurnover", "lucky_gift_turnover", "luckyGiftTurnover");
|
const luckyGiftTurnover = luckyGiftPools.length ? luckyGiftPoolTotals.turnover : readNumber(source, "room_lucky_gift_turnover", "roomLuckyGiftTurnover", "lucky_gift_turnover", "luckyGiftTurnover");
|
||||||
@ -100,6 +106,12 @@ export function createDashboardModel(overview, { appCode, countryId, range, time
|
|||||||
luckyGiftPools,
|
luckyGiftPools,
|
||||||
payoutDistribution,
|
payoutDistribution,
|
||||||
revenueSeries,
|
revenueSeries,
|
||||||
|
robotGiftKpis: [
|
||||||
|
coinMetric("真人房机器人普通礼物", realRoomRobotNormalGiftCoin, "", "当前筛选", { trend: metricTrend(dailySeries, "real_room_robot_normal_gift_coin", "真人房机器人普通礼物", "coin") }),
|
||||||
|
coinMetric("真人房机器人幸运礼物", realRoomRobotLuckyGiftCoin, "", "当前筛选", { trend: metricTrend(dailySeries, "real_room_robot_lucky_gift_coin", "真人房机器人幸运礼物", "coin") }),
|
||||||
|
coinMetric("真人房机器人超级幸运礼物", realRoomRobotSuperGiftCoin, "", "当前筛选", { trend: metricTrend(dailySeries, "real_room_robot_super_gift_coin", "真人房机器人超级幸运礼物", "coin") }),
|
||||||
|
coinMetric("真人房机器人房均礼物", realRoomRobotAvgRoomGiftCoin, "", `${formatNumber(realRoomRobotGiftRoomCount)} 个房间`, { trend: metricTrend(dailySeries, "real_room_robot_avg_room_gift_coin", "真人房机器人房均礼物", "coin") })
|
||||||
|
],
|
||||||
roomMetrics: [
|
roomMetrics: [
|
||||||
{ caption: captionWithDelta(comparisonCaption, readDelta(source, "gift_coin_spent_delta_rate", "giftCoinSpentDeltaRate")), label: "礼物消费(充值)", value: formatWholeMoney(source.gift_coin_spent) },
|
{ caption: captionWithDelta(comparisonCaption, readDelta(source, "gift_coin_spent_delta_rate", "giftCoinSpentDeltaRate")), label: "礼物消费(充值)", value: formatWholeMoney(source.gift_coin_spent) },
|
||||||
{ caption: captionWithDelta(comparisonCaption, readDelta(source, "room_lucky_gift_turnover_delta_rate", "lucky_gift_turnover_delta_rate", "roomLuckyGiftTurnoverDeltaRate", "luckyGiftTurnoverDeltaRate")), label: "幸运礼物流水", value: formatWholeMoney(source.room_lucky_gift_turnover ?? source.lucky_gift_turnover) }
|
{ caption: captionWithDelta(comparisonCaption, readDelta(source, "room_lucky_gift_turnover_delta_rate", "lucky_gift_turnover_delta_rate", "roomLuckyGiftTurnoverDeltaRate", "luckyGiftTurnoverDeltaRate")), label: "幸运礼物流水", value: formatWholeMoney(source.room_lucky_gift_turnover ?? source.lucky_gift_turnover) }
|
||||||
|
|||||||
@ -67,6 +67,22 @@ test("exposes new user KPI and removes visitor stage from funnel", () => {
|
|||||||
expect(model.funnel.map((item) => item.name)).toEqual(["活跃用户", "付费用户", "充值用户"]);
|
expect(model.funnel.map((item) => item.name)).toEqual(["活跃用户", "付费用户", "充值用户"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("adds real-room robot gift metrics to a separate card row", () => {
|
||||||
|
const model = createDashboardModel({
|
||||||
|
real_room_robot_gift_room_count: 2,
|
||||||
|
real_room_robot_lucky_gift_coin: 200,
|
||||||
|
real_room_robot_normal_gift_coin: 100,
|
||||||
|
real_room_robot_super_gift_coin: 300
|
||||||
|
}, { appCode: "lalu", countryId: 0, range: { end: "2026-06-04", start: "2026-06-04" } });
|
||||||
|
|
||||||
|
expect(metricValue(model, "真人房机器人普通礼物")).toBe("100");
|
||||||
|
expect(metricValue(model, "真人房机器人幸运礼物")).toBe("200");
|
||||||
|
expect(metricValue(model, "真人房机器人超级幸运礼物")).toBe("300");
|
||||||
|
expect(metricValue(model, "真人房机器人房均礼物")).toBe("300");
|
||||||
|
expect(model.businessKpis.map((item) => item.label)).not.toContain("真人房机器人房均礼物");
|
||||||
|
expect(model.robotGiftKpis.find((item) => item.label === "真人房机器人房均礼物")?.caption).toBe("2 个房间");
|
||||||
|
});
|
||||||
|
|
||||||
test("uses active users as payer and recharge conversion denominator", () => {
|
test("uses active users as payer and recharge conversion denominator", () => {
|
||||||
const model = createDashboardModel({
|
const model = createDashboardModel({
|
||||||
active_users: 80,
|
active_users: 80,
|
||||||
@ -92,5 +108,5 @@ test("uses active users as payer and recharge conversion denominator", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
function metricValue(model, label) {
|
function metricValue(model, label) {
|
||||||
return [...model.kpis, ...model.businessKpis].find((item) => item.label === label)?.value;
|
return [...model.kpis, ...model.businessKpis, ...model.robotGiftKpis].find((item) => item.label === label)?.value;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -141,6 +141,7 @@ export const fallbackNavigation = [
|
|||||||
routeNavItem("room-list", { icon: BedroomParentOutlined }),
|
routeNavItem("room-list", { icon: BedroomParentOutlined }),
|
||||||
routeNavItem("room-pins", { icon: PushPinOutlined }),
|
routeNavItem("room-pins", { icon: PushPinOutlined }),
|
||||||
routeNavItem("room-config", { icon: SettingsApplicationsOutlined }),
|
routeNavItem("room-config", { icon: SettingsApplicationsOutlined }),
|
||||||
|
routeNavItem("room-whitelist", { icon: ShieldOutlined }),
|
||||||
routeNavItem("room-robots", { icon: GroupsOutlined }),
|
routeNavItem("room-robots", { icon: GroupsOutlined }),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@ -99,6 +99,8 @@ export const PERMISSIONS = {
|
|||||||
roomPinCancel: "room-pin:cancel",
|
roomPinCancel: "room-pin:cancel",
|
||||||
roomConfigView: "room-config:view",
|
roomConfigView: "room-config:view",
|
||||||
roomConfigUpdate: "room-config:update",
|
roomConfigUpdate: "room-config:update",
|
||||||
|
roomWhitelistView: "room-whitelist:view",
|
||||||
|
roomWhitelistUpdate: "room-whitelist:update",
|
||||||
roomRobotView: "room-robot:view",
|
roomRobotView: "room-robot:view",
|
||||||
roomRobotCreate: "room-robot:create",
|
roomRobotCreate: "room-robot:create",
|
||||||
roomRobotUpdate: "room-robot:update",
|
roomRobotUpdate: "room-robot:update",
|
||||||
@ -191,6 +193,7 @@ export const MENU_CODES = {
|
|||||||
roomList: "room-list",
|
roomList: "room-list",
|
||||||
roomPins: "room-pins",
|
roomPins: "room-pins",
|
||||||
roomConfig: "room-config",
|
roomConfig: "room-config",
|
||||||
|
roomWhitelist: "room-whitelist",
|
||||||
roomRobots: "room-robots",
|
roomRobots: "room-robots",
|
||||||
appConfig: "app-config",
|
appConfig: "app-config",
|
||||||
appConfigH5: "app-config-h5",
|
appConfigH5: "app-config-h5",
|
||||||
|
|||||||
@ -6,6 +6,7 @@ export const APP_BANNER_DISPLAY_SCOPE_OPTIONS = [
|
|||||||
{ label: "首页", value: "home" },
|
{ label: "首页", value: "home" },
|
||||||
{ label: "房间内", value: "room" },
|
{ label: "房间内", value: "room" },
|
||||||
{ label: "充值页", value: "recharge" },
|
{ label: "充值页", value: "recharge" },
|
||||||
|
{ label: "我的页", value: "me" },
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export type AppBannerDisplayScope = (typeof APP_BANNER_DISPLAY_SCOPE_OPTIONS)[number]["value"];
|
export type AppBannerDisplayScope = (typeof APP_BANNER_DISPLAY_SCOPE_OPTIONS)[number]["value"];
|
||||||
|
|||||||
@ -213,7 +213,7 @@ function formFromBanner(item) {
|
|||||||
|
|
||||||
function normalizeDisplayScopes(value) {
|
function normalizeDisplayScopes(value) {
|
||||||
const values = Array.isArray(value) ? value : String(value || "").split(",");
|
const values = Array.isArray(value) ? value : String(value || "").split(",");
|
||||||
const known = ["home", "room", "recharge"];
|
const known = ["home", "room", "recharge", "me"];
|
||||||
const seen = new Set(values.map((item) => String(item || "").trim()).filter(Boolean));
|
const seen = new Set(values.map((item) => String(item || "").trim()).filter(Boolean));
|
||||||
const normalized = known.filter((scope) => seen.has(scope));
|
const normalized = known.filter((scope) => seen.has(scope));
|
||||||
return normalized.length ? normalized : ["home"];
|
return normalized.length ? normalized : ["home"];
|
||||||
|
|||||||
@ -107,11 +107,11 @@ describe("app config form schema", () => {
|
|||||||
test("accepts multiple banner display scopes", () => {
|
test("accepts multiple banner display scopes", () => {
|
||||||
const payload = parseForm(appBannerSchema, {
|
const payload = parseForm(appBannerSchema, {
|
||||||
...validBannerForm,
|
...validBannerForm,
|
||||||
displayScopes: ["home", "room", "recharge"],
|
displayScopes: ["home", "room", "recharge", "me"],
|
||||||
roomSmallImageUrl: "https://media.haiyihy.com/banner/room-small.png",
|
roomSmallImageUrl: "https://media.haiyihy.com/banner/room-small.png",
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(payload.displayScopes).toEqual(["home", "room", "recharge"]);
|
expect(payload.displayScopes).toEqual(["home", "room", "recharge", "me"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("requires room small image for room banner", () => {
|
test("requires room small image for room banner", () => {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { validatePublicAppJumpParam } from "@/shared/app-jump/appJumpConfig.js";
|
import { validatePublicAppJumpParam } from "@/shared/app-jump/appJumpConfig.js";
|
||||||
|
|
||||||
const appBannerDisplayScopes = ["home", "room", "recharge"] as const;
|
const appBannerDisplayScopes = ["home", "room", "recharge", "me"] as const;
|
||||||
|
|
||||||
const requiredH5LinkURLSchema = z
|
const requiredH5LinkURLSchema = z
|
||||||
.string()
|
.string()
|
||||||
|
|||||||
@ -6,10 +6,14 @@ import type {
|
|||||||
PageQuery,
|
PageQuery,
|
||||||
RoomConfigDto,
|
RoomConfigDto,
|
||||||
RoomConfigPayload,
|
RoomConfigPayload,
|
||||||
|
RoomWhitelistConfigDto,
|
||||||
|
RoomWhitelistPayload,
|
||||||
RoomDto,
|
RoomDto,
|
||||||
RoomPinDto,
|
RoomPinDto,
|
||||||
RoomPinPayload,
|
RoomPinPayload,
|
||||||
AvailableRoomRobotDto,
|
AvailableRoomRobotDto,
|
||||||
|
HumanRoomRobotConfigDto,
|
||||||
|
HumanRoomRobotConfigPayload,
|
||||||
RobotRoomDto,
|
RobotRoomDto,
|
||||||
RobotRoomPayload,
|
RobotRoomPayload,
|
||||||
RoomUpdatePayload,
|
RoomUpdatePayload,
|
||||||
@ -35,7 +39,7 @@ export function listRoomPins(query: PageQuery = {}): Promise<ApiPage<RoomPinDto>
|
|||||||
const endpoint = API_ENDPOINTS.listRoomPins;
|
const endpoint = API_ENDPOINTS.listRoomPins;
|
||||||
return apiRequest<ApiPage<RoomPinDto>>(apiEndpointPath(API_OPERATIONS.listRoomPins), {
|
return apiRequest<ApiPage<RoomPinDto>>(apiEndpointPath(API_OPERATIONS.listRoomPins), {
|
||||||
method: endpoint.method,
|
method: endpoint.method,
|
||||||
query
|
query,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,14 +47,14 @@ export function createRoomPin(payload: RoomPinPayload): Promise<RoomPinDto> {
|
|||||||
const endpoint = API_ENDPOINTS.createRoomPin;
|
const endpoint = API_ENDPOINTS.createRoomPin;
|
||||||
return apiRequest<RoomPinDto, RoomPinPayload>(apiEndpointPath(API_OPERATIONS.createRoomPin), {
|
return apiRequest<RoomPinDto, RoomPinPayload>(apiEndpointPath(API_OPERATIONS.createRoomPin), {
|
||||||
body: payload,
|
body: payload,
|
||||||
method: endpoint.method
|
method: endpoint.method,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function cancelRoomPin(pinId: EntityId): Promise<RoomPinDto> {
|
export function cancelRoomPin(pinId: EntityId): Promise<RoomPinDto> {
|
||||||
const endpoint = API_ENDPOINTS.cancelRoomPin;
|
const endpoint = API_ENDPOINTS.cancelRoomPin;
|
||||||
return apiRequest<RoomPinDto>(apiEndpointPath(API_OPERATIONS.cancelRoomPin, { pin_id: pinId }), {
|
return apiRequest<RoomPinDto>(apiEndpointPath(API_OPERATIONS.cancelRoomPin, { pin_id: pinId }), {
|
||||||
method: endpoint.method
|
method: endpoint.method,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,6 +80,24 @@ export function updateRoomConfig(payload: RoomConfigPayload): Promise<RoomConfig
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getRoomWhitelistConfig(): Promise<RoomWhitelistConfigDto> {
|
||||||
|
const endpoint = API_ENDPOINTS.getRoomWhitelistConfig;
|
||||||
|
return apiRequest<RoomWhitelistConfigDto>(apiEndpointPath(API_OPERATIONS.getRoomWhitelistConfig), {
|
||||||
|
method: endpoint.method,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateRoomWhitelistConfig(payload: RoomWhitelistPayload): Promise<RoomWhitelistConfigDto> {
|
||||||
|
const endpoint = API_ENDPOINTS.updateRoomWhitelistConfig;
|
||||||
|
return apiRequest<RoomWhitelistConfigDto, RoomWhitelistPayload>(
|
||||||
|
apiEndpointPath(API_OPERATIONS.updateRoomWhitelistConfig),
|
||||||
|
{
|
||||||
|
body: payload,
|
||||||
|
method: endpoint.method,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function listRobotRooms(query: PageQuery = {}): Promise<ApiPage<RobotRoomDto>> {
|
export function listRobotRooms(query: PageQuery = {}): Promise<ApiPage<RobotRoomDto>> {
|
||||||
const endpoint = API_ENDPOINTS.listRobotRooms;
|
const endpoint = API_ENDPOINTS.listRobotRooms;
|
||||||
return apiRequest<ApiPage<RobotRoomDto>>(apiEndpointPath(API_OPERATIONS.listRobotRooms), {
|
return apiRequest<ApiPage<RobotRoomDto>>(apiEndpointPath(API_OPERATIONS.listRobotRooms), {
|
||||||
@ -84,6 +106,24 @@ export function listRobotRooms(query: PageQuery = {}): Promise<ApiPage<RobotRoom
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getHumanRoomRobotConfig(): Promise<HumanRoomRobotConfigDto> {
|
||||||
|
const endpoint = API_ENDPOINTS.getHumanRoomRobotConfig;
|
||||||
|
return apiRequest<HumanRoomRobotConfigDto>(apiEndpointPath(API_OPERATIONS.getHumanRoomRobotConfig), {
|
||||||
|
method: endpoint.method,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateHumanRoomRobotConfig(payload: HumanRoomRobotConfigPayload): Promise<HumanRoomRobotConfigDto> {
|
||||||
|
const endpoint = API_ENDPOINTS.updateHumanRoomRobotConfig;
|
||||||
|
return apiRequest<HumanRoomRobotConfigDto, HumanRoomRobotConfigPayload>(
|
||||||
|
apiEndpointPath(API_OPERATIONS.updateHumanRoomRobotConfig),
|
||||||
|
{
|
||||||
|
body: payload,
|
||||||
|
method: endpoint.method,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function listAvailableRoomRobots(): Promise<AvailableRoomRobotDto[]> {
|
export function listAvailableRoomRobots(): Promise<AvailableRoomRobotDto[]> {
|
||||||
const endpoint = API_ENDPOINTS.listAvailableRoomRobots;
|
const endpoint = API_ENDPOINTS.listAvailableRoomRobots;
|
||||||
return apiRequest<AvailableRoomRobotDto[]>(apiEndpointPath(API_OPERATIONS.listAvailableRoomRobots), {
|
return apiRequest<AvailableRoomRobotDto[]>(apiEndpointPath(API_OPERATIONS.listAvailableRoomRobots), {
|
||||||
|
|||||||
@ -1,15 +1,19 @@
|
|||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
|
import { useCountryOptions } from "@/features/host-org/hooks/useCountryOptions.js";
|
||||||
import { listGifts } from "@/features/resources/api";
|
import { listGifts } from "@/features/resources/api";
|
||||||
import {
|
import {
|
||||||
createRobotRoom,
|
createRobotRoom,
|
||||||
|
getHumanRoomRobotConfig,
|
||||||
listAvailableRoomRobots,
|
listAvailableRoomRobots,
|
||||||
listRobotRooms,
|
listRobotRooms,
|
||||||
startRobotRoom,
|
startRobotRoom,
|
||||||
stopRobotRoom,
|
stopRobotRoom,
|
||||||
|
updateHumanRoomRobotConfig,
|
||||||
} from "@/features/rooms/api";
|
} from "@/features/rooms/api";
|
||||||
import { useRoomAbilities } from "@/features/rooms/permissions.js";
|
import { useRoomAbilities } from "@/features/rooms/permissions.js";
|
||||||
import { robotRoomCreateSchema } from "@/features/rooms/schema";
|
import { humanRoomRobotConfigSchema, robotRoomCreateSchema } from "@/features/rooms/schema";
|
||||||
import { parseForm } from "@/shared/forms/validation";
|
import { parseForm } from "@/shared/forms/validation";
|
||||||
|
import { useRegionOptions } from "@/shared/hooks/useRegionOptions.js";
|
||||||
import { usePaginatedQuery } from "@/shared/hooks/usePaginatedQuery.js";
|
import { usePaginatedQuery } from "@/shared/hooks/usePaginatedQuery.js";
|
||||||
import { useToast } from "@/shared/ui/ToastProvider.jsx";
|
import { useToast } from "@/shared/ui/ToastProvider.jsx";
|
||||||
|
|
||||||
@ -25,22 +29,55 @@ function emptyForm() {
|
|||||||
luckyGiftIds: [],
|
luckyGiftIds: [],
|
||||||
luckyPauseMaxMs: "20000",
|
luckyPauseMaxMs: "20000",
|
||||||
luckyPauseMinMs: "5000",
|
luckyPauseMinMs: "5000",
|
||||||
|
maxGiftSenders: "1",
|
||||||
maxRobotCount: "8",
|
maxRobotCount: "8",
|
||||||
minRobotCount: "6",
|
minRobotCount: "6",
|
||||||
normalGiftIntervalMs: "10000",
|
normalGiftIntervalMs: "10000",
|
||||||
ownerRobotUserId: "",
|
ownerRobotUserId: "",
|
||||||
|
robotReplaceMaxMs: "60000",
|
||||||
|
robotReplaceMinMs: "0",
|
||||||
|
robotStayMaxMs: "600000",
|
||||||
|
robotStayMinMs: "180000",
|
||||||
|
seatCount: "10",
|
||||||
visibleRegionId: 0,
|
visibleRegionId: 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function emptyHumanConfigForm() {
|
||||||
|
return {
|
||||||
|
candidateRoomMaxOnline: "5",
|
||||||
|
enabled: false,
|
||||||
|
giftIds: [],
|
||||||
|
luckyComboMax: "3",
|
||||||
|
luckyComboMin: "1",
|
||||||
|
luckyGiftIds: [],
|
||||||
|
luckyPauseMaxMs: "20000",
|
||||||
|
luckyPauseMinMs: "5000",
|
||||||
|
maxGiftSenders: "1",
|
||||||
|
normalGiftIntervalMaxMs: "20000",
|
||||||
|
normalGiftIntervalMinMs: "1000",
|
||||||
|
robotReplaceMaxMs: "180000",
|
||||||
|
robotReplaceMinMs: "60000",
|
||||||
|
robotStayMaxMs: "600000",
|
||||||
|
robotStayMinMs: "60000",
|
||||||
|
roomTargetMaxOnline: "10",
|
||||||
|
roomTargetMinOnline: "8",
|
||||||
|
superLuckyGiftIds: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function useRobotRoomsPage() {
|
export function useRobotRoomsPage() {
|
||||||
const abilities = useRoomAbilities();
|
const abilities = useRoomAbilities();
|
||||||
|
const { countryOptions, loadingCountries } = useCountryOptions();
|
||||||
|
const { loadingRegions, regionOptions } = useRegionOptions();
|
||||||
const { showToast } = useToast();
|
const { showToast } = useToast();
|
||||||
const [activeAction, setActiveAction] = useState("");
|
const [activeAction, setActiveAction] = useState("");
|
||||||
const [availableRobotsState, setAvailableRobotsState] = useState({ error: "", loading: false, options: [] });
|
const [availableRobotsState, setAvailableRobotsState] = useState({ error: "", loading: false, options: [] });
|
||||||
const [candidatesTouched, setCandidatesTouched] = useState(false);
|
const [candidatesTouched, setCandidatesTouched] = useState(false);
|
||||||
const [form, setForm] = useState(emptyForm);
|
const [form, setForm] = useState(emptyForm);
|
||||||
const [giftOptionsState, setGiftOptionsState] = useState({ error: "", loading: false, options: [] });
|
const [giftOptionsState, setGiftOptionsState] = useState({ error: "", loading: false, options: [] });
|
||||||
|
const [humanConfigState, setHumanConfigState] = useState({ config: null, error: "", loading: false });
|
||||||
|
const [humanConfigForm, setHumanConfigForm] = useState(emptyHumanConfigForm);
|
||||||
const [loadingAction, setLoadingAction] = useState("");
|
const [loadingAction, setLoadingAction] = useState("");
|
||||||
const [page, setPage] = useState(1);
|
const [page, setPage] = useState(1);
|
||||||
const [status, setStatus] = useState("active");
|
const [status, setStatus] = useState("active");
|
||||||
@ -79,12 +116,29 @@ export function useRobotRoomsPage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
const loadHumanConfig = async () => {
|
||||||
if (activeAction !== "create") {
|
setHumanConfigState((current) => ({ ...current, error: "", loading: true }));
|
||||||
return;
|
try {
|
||||||
|
const config = await getHumanRoomRobotConfig();
|
||||||
|
setHumanConfigState({ config, error: "", loading: false });
|
||||||
|
setHumanConfigForm(humanConfigToForm(config));
|
||||||
|
} catch (err) {
|
||||||
|
setHumanConfigState({ config: null, error: err.message || "加载真人房间机器人配置失败", loading: false });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadHumanConfig();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (activeAction === "create") {
|
||||||
|
loadAvailableRobots();
|
||||||
|
loadGiftOptions();
|
||||||
|
}
|
||||||
|
if (activeAction === "human-config") {
|
||||||
|
loadGiftOptions();
|
||||||
}
|
}
|
||||||
loadAvailableRobots();
|
|
||||||
loadGiftOptions();
|
|
||||||
}, [activeAction]);
|
}, [activeAction]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -115,6 +169,11 @@ export function useRobotRoomsPage() {
|
|||||||
setGiftOptionsState({ error: "", loading: false, options: [] });
|
setGiftOptionsState({ error: "", loading: false, options: [] });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const openHumanConfig = () => {
|
||||||
|
setActiveAction("human-config");
|
||||||
|
setHumanConfigForm(humanConfigToForm(humanConfigState.config));
|
||||||
|
};
|
||||||
|
|
||||||
const closeAction = () => {
|
const closeAction = () => {
|
||||||
setActiveAction("");
|
setActiveAction("");
|
||||||
};
|
};
|
||||||
@ -149,7 +208,9 @@ export function useRobotRoomsPage() {
|
|||||||
setForm((current) => ({
|
setForm((current) => ({
|
||||||
...current,
|
...current,
|
||||||
ownerRobotUserId,
|
ownerRobotUserId,
|
||||||
candidateRobotUserIds: (current.candidateRobotUserIds || []).filter((userId) => String(userId) !== String(ownerRobotUserId)),
|
candidateRobotUserIds: (current.candidateRobotUserIds || []).filter(
|
||||||
|
(userId) => String(userId) !== String(ownerRobotUserId),
|
||||||
|
),
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -167,6 +228,21 @@ export function useRobotRoomsPage() {
|
|||||||
setForm((current) => ({ ...current, [key]: gifts.map(giftId).filter(Boolean) }));
|
setForm((current) => ({ ...current, [key]: gifts.map(giftId).filter(Boolean) }));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const selectHumanGiftIds = (key, gifts) => {
|
||||||
|
setHumanConfigForm((current) => ({ ...current, [key]: gifts.map(giftId).filter(Boolean) }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const submitHumanConfig = async (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
await runAction("human-config", "真人房间机器人配置已保存", async () => {
|
||||||
|
const payload = parseForm(humanRoomRobotConfigSchema, humanConfigForm);
|
||||||
|
const config = await updateHumanRoomRobotConfig(payload);
|
||||||
|
setHumanConfigState({ config, error: "", loading: false });
|
||||||
|
setHumanConfigForm(humanConfigToForm(config));
|
||||||
|
closeAction();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const runAction = async (action, successMessage, submitter) => {
|
const runAction = async (action, successMessage, submitter) => {
|
||||||
setLoadingAction(action);
|
setLoadingAction(action);
|
||||||
try {
|
try {
|
||||||
@ -191,6 +267,14 @@ export function useRobotRoomsPage() {
|
|||||||
() => availableRobotsState.options.filter((item) => String(item.userId) !== String(form.ownerRobotUserId)),
|
() => availableRobotsState.options.filter((item) => String(item.userId) !== String(form.ownerRobotUserId)),
|
||||||
[availableRobotsState.options, form.ownerRobotUserId],
|
[availableRobotsState.options, form.ownerRobotUserId],
|
||||||
);
|
);
|
||||||
|
const countryLabels = useMemo(
|
||||||
|
() => Object.fromEntries(countryOptions.map((option) => [option.value, option.label])),
|
||||||
|
[countryOptions],
|
||||||
|
);
|
||||||
|
const regionLabels = useMemo(
|
||||||
|
() => Object.fromEntries(regionOptions.map((option) => [option.value, option.label])),
|
||||||
|
[regionOptions],
|
||||||
|
);
|
||||||
const selectedGiftOptions = useMemo(() => {
|
const selectedGiftOptions = useMemo(() => {
|
||||||
const selected = new Set((form.giftIds || []).map(String));
|
const selected = new Set((form.giftIds || []).map(String));
|
||||||
return giftOptionsState.options.filter((item) => selected.has(giftId(item)));
|
return giftOptionsState.options.filter((item) => selected.has(giftId(item)));
|
||||||
@ -199,6 +283,25 @@ export function useRobotRoomsPage() {
|
|||||||
const selected = new Set((form.luckyGiftIds || []).map(String));
|
const selected = new Set((form.luckyGiftIds || []).map(String));
|
||||||
return giftOptionsState.options.filter((item) => selected.has(giftId(item)));
|
return giftOptionsState.options.filter((item) => selected.has(giftId(item)));
|
||||||
}, [form.luckyGiftIds, giftOptionsState.options]);
|
}, [form.luckyGiftIds, giftOptionsState.options]);
|
||||||
|
const selectedHumanGiftOptions = useMemo(
|
||||||
|
() => ({
|
||||||
|
giftIds: giftOptionsState.options.filter((item) =>
|
||||||
|
new Set((humanConfigForm.giftIds || []).map(String)).has(giftId(item)),
|
||||||
|
),
|
||||||
|
luckyGiftIds: giftOptionsState.options.filter((item) =>
|
||||||
|
new Set((humanConfigForm.luckyGiftIds || []).map(String)).has(giftId(item)),
|
||||||
|
),
|
||||||
|
superLuckyGiftIds: giftOptionsState.options.filter((item) =>
|
||||||
|
new Set((humanConfigForm.superLuckyGiftIds || []).map(String)).has(giftId(item)),
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
[
|
||||||
|
giftOptionsState.options,
|
||||||
|
humanConfigForm.giftIds,
|
||||||
|
humanConfigForm.luckyGiftIds,
|
||||||
|
humanConfigForm.superLuckyGiftIds,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
abilities,
|
abilities,
|
||||||
@ -209,30 +312,79 @@ export function useRobotRoomsPage() {
|
|||||||
changeStatus,
|
changeStatus,
|
||||||
closeAction,
|
closeAction,
|
||||||
candidateRobotOptions,
|
candidateRobotOptions,
|
||||||
|
countryOptions,
|
||||||
data,
|
data,
|
||||||
error,
|
error,
|
||||||
form,
|
form,
|
||||||
giftOptions: giftOptionsState.options,
|
giftOptions: giftOptionsState.options,
|
||||||
giftOptionsError: giftOptionsState.error,
|
giftOptionsError: giftOptionsState.error,
|
||||||
giftOptionsLoading: giftOptionsState.loading,
|
giftOptionsLoading: giftOptionsState.loading,
|
||||||
|
humanConfig: humanConfigState.config,
|
||||||
|
humanConfigError: humanConfigState.error,
|
||||||
|
humanConfigForm,
|
||||||
|
humanConfigLoading: humanConfigState.loading,
|
||||||
loading,
|
loading,
|
||||||
loadingAction,
|
loadingAction,
|
||||||
|
loadingRobotLocationOptions: loadingCountries || loadingRegions,
|
||||||
openCreate,
|
openCreate,
|
||||||
|
openHumanConfig,
|
||||||
page,
|
page,
|
||||||
reload,
|
reload,
|
||||||
resetFilters,
|
resetFilters,
|
||||||
selectCandidateRobots,
|
selectCandidateRobots,
|
||||||
selectGiftIds,
|
selectGiftIds,
|
||||||
|
selectHumanGiftIds,
|
||||||
selectOwnerRobot,
|
selectOwnerRobot,
|
||||||
selectedCandidateRobots,
|
selectedCandidateRobots,
|
||||||
selectedGiftOptions,
|
selectedGiftOptions,
|
||||||
selectedLuckyGiftOptions,
|
selectedLuckyGiftOptions,
|
||||||
|
selectedHumanGiftOptions,
|
||||||
selectedOwnerRobot,
|
selectedOwnerRobot,
|
||||||
|
robotCountryLabels: countryLabels,
|
||||||
|
robotRegionLabels: regionLabels,
|
||||||
setForm,
|
setForm,
|
||||||
|
setHumanConfigForm,
|
||||||
setPage,
|
setPage,
|
||||||
setRoomStatus,
|
setRoomStatus,
|
||||||
status,
|
status,
|
||||||
submitCreate,
|
submitCreate,
|
||||||
|
submitHumanConfig,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function humanConfigToForm(config = null) {
|
||||||
|
const defaults = emptyHumanConfigForm();
|
||||||
|
if (!config) {
|
||||||
|
return defaults;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...defaults,
|
||||||
|
candidateRoomMaxOnline: String(config.candidateRoomMaxOnline ?? defaults.candidateRoomMaxOnline),
|
||||||
|
enabled: Boolean(config.enabled),
|
||||||
|
giftIds: (config.giftIds || []).map(String),
|
||||||
|
luckyComboMax: String(config.luckyComboMax ?? defaults.luckyComboMax),
|
||||||
|
luckyComboMin: String(config.luckyComboMin ?? defaults.luckyComboMin),
|
||||||
|
luckyGiftIds: (config.luckyGiftIds || []).map(String),
|
||||||
|
luckyPauseMaxMs: String(config.luckyPauseMaxMs ?? defaults.luckyPauseMaxMs),
|
||||||
|
luckyPauseMinMs: String(config.luckyPauseMinMs ?? defaults.luckyPauseMinMs),
|
||||||
|
maxGiftSenders: String(config.maxGiftSenders ?? defaults.maxGiftSenders),
|
||||||
|
normalGiftIntervalMaxMs: String(
|
||||||
|
config.normalGiftIntervalMaxMs ?? config.normalGiftIntervalMs ?? defaults.normalGiftIntervalMaxMs,
|
||||||
|
),
|
||||||
|
normalGiftIntervalMinMs: String(
|
||||||
|
config.normalGiftIntervalMinMs ?? config.normalGiftIntervalMs ?? defaults.normalGiftIntervalMinMs,
|
||||||
|
),
|
||||||
|
robotReplaceMaxMs: String(config.robotReplaceMaxMs ?? defaults.robotReplaceMaxMs),
|
||||||
|
robotReplaceMinMs: String(config.robotReplaceMinMs ?? defaults.robotReplaceMinMs),
|
||||||
|
robotStayMaxMs: String(config.robotStayMaxMs ?? defaults.robotStayMaxMs),
|
||||||
|
robotStayMinMs: String(config.robotStayMinMs ?? defaults.robotStayMinMs),
|
||||||
|
roomTargetMaxOnline: String(
|
||||||
|
config.roomTargetMaxOnline ?? config.roomFullStopOnline ?? defaults.roomTargetMaxOnline,
|
||||||
|
),
|
||||||
|
roomTargetMinOnline: String(
|
||||||
|
config.roomTargetMinOnline ?? config.roomFullStopOnline ?? defaults.roomTargetMinOnline,
|
||||||
|
),
|
||||||
|
superLuckyGiftIds: (config.superLuckyGiftIds || []).map(String),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -244,6 +396,18 @@ export function robotOptionLabel(robot) {
|
|||||||
return [user.username, user.displayUserId, robot.userId].filter(Boolean).join(" / ");
|
return [user.username, user.displayUserId, robot.userId].filter(Boolean).join(" / ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function robotLocationLabel(robot, countryLabels = {}, regionLabels = {}) {
|
||||||
|
const user = robot?.user || {};
|
||||||
|
const countryCode = String(user.countryCode || "")
|
||||||
|
.trim()
|
||||||
|
.toUpperCase();
|
||||||
|
const regionId = user.visibleRegionId || "";
|
||||||
|
const countryLabel = countryCode ? countryLabels[countryCode] || countryCode : "国家 -";
|
||||||
|
const regionKey = regionId ? String(regionId) : "";
|
||||||
|
const regionLabel = regionKey ? regionLabels[regionKey] || `区域 ${regionKey}` : "区域 -";
|
||||||
|
return `${countryLabel} / ${regionLabel}`;
|
||||||
|
}
|
||||||
|
|
||||||
export function giftId(gift) {
|
export function giftId(gift) {
|
||||||
return String(gift?.giftId || "");
|
return String(gift?.giftId || "");
|
||||||
}
|
}
|
||||||
|
|||||||
25
src/features/rooms/hooks/useRobotRoomsPage.test.js
Normal file
25
src/features/rooms/hooks/useRobotRoomsPage.test.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { robotLocationLabel } from "@/features/rooms/hooks/useRobotRoomsPage.js";
|
||||||
|
|
||||||
|
describe("robotLocationLabel", () => {
|
||||||
|
it("shows robot country and region labels", () => {
|
||||||
|
const label = robotLocationLabel(
|
||||||
|
{
|
||||||
|
user: {
|
||||||
|
countryCode: "ae",
|
||||||
|
visibleRegionId: 686,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ AE: "🇦🇪 · 阿联酋 · AE" },
|
||||||
|
{ 686: "中东 · ME · 686" },
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(label).toBe("🇦🇪 · 阿联酋 · AE / 中东 · ME · 686");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("falls back to raw country and region ids", () => {
|
||||||
|
const label = robotLocationLabel({ user: { countryCode: "sa", visibleRegionId: 7 } }, {}, {});
|
||||||
|
|
||||||
|
expect(label).toBe("SA / 区域 7");
|
||||||
|
});
|
||||||
|
});
|
||||||
76
src/features/rooms/hooks/useRoomWhitelistPage.js
Normal file
76
src/features/rooms/hooks/useRoomWhitelistPage.js
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
|
import { getRoomWhitelistConfig, updateRoomWhitelistConfig } from "@/features/rooms/api";
|
||||||
|
import { useRoomAbilities } from "@/features/rooms/permissions.js";
|
||||||
|
import { roomWhitelistSchema } from "@/features/rooms/schema";
|
||||||
|
import { parseForm } from "@/shared/forms/validation";
|
||||||
|
import { useAdminQuery } from "@/shared/hooks/useAdminQuery.js";
|
||||||
|
import { useToast } from "@/shared/ui/ToastProvider.jsx";
|
||||||
|
|
||||||
|
const emptyData = {
|
||||||
|
updatedAtMs: 0,
|
||||||
|
userIds: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
export function useRoomWhitelistPage() {
|
||||||
|
const abilities = useRoomAbilities();
|
||||||
|
const { showToast } = useToast();
|
||||||
|
const [text, setText] = useState("");
|
||||||
|
const [loadingAction, setLoadingAction] = useState("");
|
||||||
|
const queryFn = useCallback(() => getRoomWhitelistConfig(), []);
|
||||||
|
const {
|
||||||
|
data = emptyData,
|
||||||
|
error,
|
||||||
|
loading,
|
||||||
|
reload,
|
||||||
|
} = useAdminQuery(queryFn, {
|
||||||
|
errorMessage: "加载房间白名单失败",
|
||||||
|
initialData: emptyData,
|
||||||
|
queryKey: ["rooms", "whitelist"],
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setText(normalizeUserIds(data?.userIds).join("\n"));
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
|
const userIds = useMemo(() => normalizeUserIds(splitUserIds(text)), [text]);
|
||||||
|
|
||||||
|
const submitWhitelist = async (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
setLoadingAction("save");
|
||||||
|
try {
|
||||||
|
const payload = parseForm(roomWhitelistSchema, { userIds });
|
||||||
|
const saved = await updateRoomWhitelistConfig(payload);
|
||||||
|
setText(normalizeUserIds(saved.userIds).join("\n"));
|
||||||
|
await reload();
|
||||||
|
showToast("房间白名单已保存", "success");
|
||||||
|
} catch (err) {
|
||||||
|
showToast(err.message || "保存失败", "error");
|
||||||
|
} finally {
|
||||||
|
setLoadingAction("");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
abilities,
|
||||||
|
data,
|
||||||
|
error,
|
||||||
|
loading,
|
||||||
|
loadingAction,
|
||||||
|
reload,
|
||||||
|
setText,
|
||||||
|
submitWhitelist,
|
||||||
|
text,
|
||||||
|
userIds,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function splitUserIds(value) {
|
||||||
|
return String(value || "")
|
||||||
|
.split(/[\s,,;;]+/)
|
||||||
|
.map((item) => item.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeUserIds(values) {
|
||||||
|
return Array.isArray(values) ? [...new Set(values.map((value) => String(value).trim()).filter(Boolean))] : [];
|
||||||
|
}
|
||||||
@ -1,5 +1,6 @@
|
|||||||
import AddOutlined from "@mui/icons-material/AddOutlined";
|
import AddOutlined from "@mui/icons-material/AddOutlined";
|
||||||
import BedroomParentOutlined from "@mui/icons-material/BedroomParentOutlined";
|
import BedroomParentOutlined from "@mui/icons-material/BedroomParentOutlined";
|
||||||
|
import TuneOutlined from "@mui/icons-material/TuneOutlined";
|
||||||
import Autocomplete from "@mui/material/Autocomplete";
|
import Autocomplete from "@mui/material/Autocomplete";
|
||||||
import TextField from "@mui/material/TextField";
|
import TextField from "@mui/material/TextField";
|
||||||
import Tooltip from "@mui/material/Tooltip";
|
import Tooltip from "@mui/material/Tooltip";
|
||||||
@ -15,6 +16,7 @@ import { formatMillis } from "@/shared/utils/time.js";
|
|||||||
import {
|
import {
|
||||||
giftId,
|
giftId,
|
||||||
giftOptionLabel,
|
giftOptionLabel,
|
||||||
|
robotLocationLabel,
|
||||||
robotOptionLabel,
|
robotOptionLabel,
|
||||||
useRobotRoomsPage,
|
useRobotRoomsPage,
|
||||||
} from "@/features/rooms/hooks/useRobotRoomsPage.js";
|
} from "@/features/rooms/hooks/useRobotRoomsPage.js";
|
||||||
@ -41,9 +43,16 @@ const columns = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "robots",
|
key: "robots",
|
||||||
label: "进房机器人",
|
label: "机器人",
|
||||||
width: "minmax(120px, 0.5fr)",
|
width: "minmax(150px, 0.6fr)",
|
||||||
render: (room) => Number(room.robotUserIds?.length || room.robots?.length || 0).toLocaleString("zh-CN"),
|
render: (room) => (
|
||||||
|
<RuleStack
|
||||||
|
rows={[
|
||||||
|
`活跃 ${Number(room.activeRobotCount || 0).toLocaleString("zh-CN")}`,
|
||||||
|
`候选 ${Number(room.robotUserIds?.length || room.robots?.length || 0).toLocaleString("zh-CN")}`,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "normalRule",
|
key: "normalRule",
|
||||||
@ -72,6 +81,20 @@ const columns = [
|
|||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: "behaviorRule",
|
||||||
|
label: "行为",
|
||||||
|
width: "minmax(220px, 0.9fr)",
|
||||||
|
render: (room) => (
|
||||||
|
<RuleStack
|
||||||
|
rows={[
|
||||||
|
`${formatDurationRange(room.giftRule?.robotStayMinMs, room.giftRule?.robotStayMaxMs)} 停留`,
|
||||||
|
`${formatDurationRange(room.giftRule?.robotReplaceMinMs, room.giftRule?.robotReplaceMaxMs)} 补位`,
|
||||||
|
`最多 ${Number(room.giftRule?.maxGiftSenders || 0).toLocaleString("zh-CN")} 个送礼`,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: "createdAt",
|
key: "createdAt",
|
||||||
label: "创建时间",
|
label: "创建时间",
|
||||||
@ -115,12 +138,24 @@ export function RoomRobotPage() {
|
|||||||
</section>
|
</section>
|
||||||
{page.abilities.canRobotCreate ? (
|
{page.abilities.canRobotCreate ? (
|
||||||
<div className={styles.toolbarActions}>
|
<div className={styles.toolbarActions}>
|
||||||
<Button startIcon={<AddOutlined fontSize="small" />} variant="primary" onClick={page.openCreate}>
|
<Button
|
||||||
|
startIcon={<TuneOutlined fontSize="small" />}
|
||||||
|
variant="secondary"
|
||||||
|
onClick={page.openHumanConfig}
|
||||||
|
>
|
||||||
|
真人房间机器人配置
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
startIcon={<AddOutlined fontSize="small" />}
|
||||||
|
variant="primary"
|
||||||
|
onClick={page.openCreate}
|
||||||
|
>
|
||||||
增加机器人房间
|
增加机器人房间
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
<HumanConfigPanel page={page} />
|
||||||
|
|
||||||
<DataState error={page.error} loading={page.loading} onRetry={page.reload}>
|
<DataState error={page.error} loading={page.loading} onRetry={page.reload}>
|
||||||
<div className={styles.listBlock}>
|
<div className={styles.listBlock}>
|
||||||
@ -170,7 +205,12 @@ export function RoomRobotPage() {
|
|||||||
)}
|
)}
|
||||||
renderOption={(props, option) => (
|
renderOption={(props, option) => (
|
||||||
<li {...props} key={option.userId}>
|
<li {...props} key={option.userId}>
|
||||||
<RobotIdentity openInAppUserDetail={false} user={option.user} fallbackId={option.userId} />
|
<OwnerRobotOption
|
||||||
|
countryLabels={page.robotCountryLabels}
|
||||||
|
loadingLocation={page.loadingRobotLocationOptions}
|
||||||
|
option={option}
|
||||||
|
regionLabels={page.robotRegionLabels}
|
||||||
|
/>
|
||||||
</li>
|
</li>
|
||||||
)}
|
)}
|
||||||
size="small"
|
size="small"
|
||||||
@ -245,6 +285,43 @@ export function RoomRobotPage() {
|
|||||||
value={page.form.normalGiftIntervalMs}
|
value={page.form.normalGiftIntervalMs}
|
||||||
onChange={(event) => page.setForm({ ...page.form, normalGiftIntervalMs: event.target.value })}
|
onChange={(event) => page.setForm({ ...page.form, normalGiftIntervalMs: event.target.value })}
|
||||||
/>
|
/>
|
||||||
|
<div className={styles.formGrid}>
|
||||||
|
<TextField
|
||||||
|
label="最短停留(毫秒)"
|
||||||
|
required
|
||||||
|
type="number"
|
||||||
|
value={page.form.robotStayMinMs}
|
||||||
|
onChange={(event) => page.setForm({ ...page.form, robotStayMinMs: event.target.value })}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="最长停留(毫秒)"
|
||||||
|
required
|
||||||
|
type="number"
|
||||||
|
value={page.form.robotStayMaxMs}
|
||||||
|
onChange={(event) => page.setForm({ ...page.form, robotStayMaxMs: event.target.value })}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="最短补位(毫秒)"
|
||||||
|
required
|
||||||
|
type="number"
|
||||||
|
value={page.form.robotReplaceMinMs}
|
||||||
|
onChange={(event) => page.setForm({ ...page.form, robotReplaceMinMs: event.target.value })}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="最长补位(毫秒)"
|
||||||
|
required
|
||||||
|
type="number"
|
||||||
|
value={page.form.robotReplaceMaxMs}
|
||||||
|
onChange={(event) => page.setForm({ ...page.form, robotReplaceMaxMs: event.target.value })}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="同时送礼上限"
|
||||||
|
required
|
||||||
|
type="number"
|
||||||
|
value={page.form.maxGiftSenders}
|
||||||
|
onChange={(event) => page.setForm({ ...page.form, maxGiftSenders: event.target.value })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<Autocomplete
|
<Autocomplete
|
||||||
multiple
|
multiple
|
||||||
disableCloseOnSelect
|
disableCloseOnSelect
|
||||||
@ -296,10 +373,271 @@ export function RoomRobotPage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</AdminFormDialog>
|
</AdminFormDialog>
|
||||||
|
<HumanConfigDialog page={page} />
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function HumanConfigPanel({ page }) {
|
||||||
|
const config = page.humanConfig || {};
|
||||||
|
return (
|
||||||
|
<section className={styles.robotConfigPanel}>
|
||||||
|
<div>
|
||||||
|
<div className={styles.primaryText}>真人房间机器人</div>
|
||||||
|
<div className={styles.meta}>
|
||||||
|
{page.humanConfigLoading
|
||||||
|
? "配置加载中"
|
||||||
|
: page.humanConfigError || (config.enabled ? "已启用" : "未启用")}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<RuleStack
|
||||||
|
rows={[
|
||||||
|
`低于 ${Number(config.candidateRoomMaxOnline || 5).toLocaleString("zh-CN")} 人进房`,
|
||||||
|
`目标 ${Number(config.roomTargetMinOnline || config.roomFullStopOnline || 8).toLocaleString("zh-CN")}-${Number(config.roomTargetMaxOnline || config.roomFullStopOnline || 10).toLocaleString("zh-CN")} 人`,
|
||||||
|
`${formatDurationRange(config.robotStayMinMs || 60000, config.robotStayMaxMs || 600000)} 停留`,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<RuleStack
|
||||||
|
rows={[
|
||||||
|
"自动使用全站机器人",
|
||||||
|
`${formatDurationRange(
|
||||||
|
config.normalGiftIntervalMinMs || config.normalGiftIntervalMs || 10000,
|
||||||
|
config.normalGiftIntervalMaxMs || config.normalGiftIntervalMs || 10000,
|
||||||
|
)} / 次`,
|
||||||
|
`${Number(config.maxGiftSenders || 1).toLocaleString("zh-CN")} 个机器人送礼`,
|
||||||
|
`${config.giftIds?.length || 0} 个普通礼物`,
|
||||||
|
`${(config.luckyGiftIds?.length || 0) + (config.superLuckyGiftIds?.length || 0)} 个幸运礼物`,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
{page.abilities.canRobotUpdate ? (
|
||||||
|
<Button
|
||||||
|
loading={page.humanConfigLoading}
|
||||||
|
startIcon={<TuneOutlined fontSize="small" />}
|
||||||
|
variant="secondary"
|
||||||
|
onClick={page.openHumanConfig}
|
||||||
|
>
|
||||||
|
配置
|
||||||
|
</Button>
|
||||||
|
) : null}
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function HumanConfigDialog({ page }) {
|
||||||
|
return (
|
||||||
|
<AdminFormDialog
|
||||||
|
loading={page.loadingAction === "human-config"}
|
||||||
|
open={page.activeAction === "human-config"}
|
||||||
|
size="wide"
|
||||||
|
submitDisabled={!page.abilities.canRobotUpdate || page.loadingAction === "human-config"}
|
||||||
|
title="真人房间机器人配置"
|
||||||
|
onClose={page.closeAction}
|
||||||
|
onSubmit={page.submitHumanConfig}
|
||||||
|
>
|
||||||
|
<div className={styles.formGrid}>
|
||||||
|
<label className={styles.switchField}>
|
||||||
|
<AdminSwitch
|
||||||
|
checked={Boolean(page.humanConfigForm.enabled)}
|
||||||
|
checkedLabel="启用"
|
||||||
|
label="启用真人房间机器人"
|
||||||
|
uncheckedLabel="关闭"
|
||||||
|
onChange={(_, checked) =>
|
||||||
|
page.setHumanConfigForm({ ...page.humanConfigForm, enabled: checked })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<TextField
|
||||||
|
label="真人房间低于人数"
|
||||||
|
required
|
||||||
|
type="number"
|
||||||
|
value={page.humanConfigForm.candidateRoomMaxOnline}
|
||||||
|
onChange={(event) =>
|
||||||
|
page.setHumanConfigForm({
|
||||||
|
...page.humanConfigForm,
|
||||||
|
candidateRoomMaxOnline: event.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="目标人数下限"
|
||||||
|
required
|
||||||
|
type="number"
|
||||||
|
value={page.humanConfigForm.roomTargetMinOnline}
|
||||||
|
onChange={(event) =>
|
||||||
|
page.setHumanConfigForm({ ...page.humanConfigForm, roomTargetMinOnline: event.target.value })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="目标人数上限"
|
||||||
|
required
|
||||||
|
type="number"
|
||||||
|
value={page.humanConfigForm.roomTargetMaxOnline}
|
||||||
|
onChange={(event) =>
|
||||||
|
page.setHumanConfigForm({ ...page.humanConfigForm, roomTargetMaxOnline: event.target.value })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={styles.formGrid}>
|
||||||
|
<TextField
|
||||||
|
label="最短送礼间隔(毫秒)"
|
||||||
|
required
|
||||||
|
type="number"
|
||||||
|
value={page.humanConfigForm.normalGiftIntervalMinMs}
|
||||||
|
onChange={(event) =>
|
||||||
|
page.setHumanConfigForm({
|
||||||
|
...page.humanConfigForm,
|
||||||
|
normalGiftIntervalMinMs: event.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="最长送礼间隔(毫秒)"
|
||||||
|
required
|
||||||
|
type="number"
|
||||||
|
value={page.humanConfigForm.normalGiftIntervalMaxMs}
|
||||||
|
onChange={(event) =>
|
||||||
|
page.setHumanConfigForm({
|
||||||
|
...page.humanConfigForm,
|
||||||
|
normalGiftIntervalMaxMs: event.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="最短停留(毫秒)"
|
||||||
|
required
|
||||||
|
type="number"
|
||||||
|
value={page.humanConfigForm.robotStayMinMs}
|
||||||
|
onChange={(event) =>
|
||||||
|
page.setHumanConfigForm({ ...page.humanConfigForm, robotStayMinMs: event.target.value })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="最长停留(毫秒)"
|
||||||
|
required
|
||||||
|
type="number"
|
||||||
|
value={page.humanConfigForm.robotStayMaxMs}
|
||||||
|
onChange={(event) =>
|
||||||
|
page.setHumanConfigForm({ ...page.humanConfigForm, robotStayMaxMs: event.target.value })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="最短补位(毫秒)"
|
||||||
|
required
|
||||||
|
type="number"
|
||||||
|
value={page.humanConfigForm.robotReplaceMinMs}
|
||||||
|
onChange={(event) =>
|
||||||
|
page.setHumanConfigForm({ ...page.humanConfigForm, robotReplaceMinMs: event.target.value })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="最长补位(毫秒)"
|
||||||
|
required
|
||||||
|
type="number"
|
||||||
|
value={page.humanConfigForm.robotReplaceMaxMs}
|
||||||
|
onChange={(event) =>
|
||||||
|
page.setHumanConfigForm({ ...page.humanConfigForm, robotReplaceMaxMs: event.target.value })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="房间送礼机器人数"
|
||||||
|
required
|
||||||
|
type="number"
|
||||||
|
value={page.humanConfigForm.maxGiftSenders}
|
||||||
|
onChange={(event) =>
|
||||||
|
page.setHumanConfigForm({ ...page.humanConfigForm, maxGiftSenders: event.target.value })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<GiftAutocomplete
|
||||||
|
label="普通礼物合集"
|
||||||
|
loading={page.giftOptionsLoading}
|
||||||
|
options={page.giftOptions}
|
||||||
|
required
|
||||||
|
value={page.selectedHumanGiftOptions.giftIds}
|
||||||
|
onChange={(options) => page.selectHumanGiftIds("giftIds", options)}
|
||||||
|
/>
|
||||||
|
<div className={styles.formGrid}>
|
||||||
|
<TextField
|
||||||
|
label="最少幸运连击"
|
||||||
|
required
|
||||||
|
type="number"
|
||||||
|
value={page.humanConfigForm.luckyComboMin}
|
||||||
|
onChange={(event) =>
|
||||||
|
page.setHumanConfigForm({ ...page.humanConfigForm, luckyComboMin: event.target.value })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="最多幸运连击"
|
||||||
|
required
|
||||||
|
type="number"
|
||||||
|
value={page.humanConfigForm.luckyComboMax}
|
||||||
|
onChange={(event) =>
|
||||||
|
page.setHumanConfigForm({ ...page.humanConfigForm, luckyComboMax: event.target.value })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="最短幸运间隔(毫秒)"
|
||||||
|
required
|
||||||
|
type="number"
|
||||||
|
value={page.humanConfigForm.luckyPauseMinMs}
|
||||||
|
onChange={(event) =>
|
||||||
|
page.setHumanConfigForm({ ...page.humanConfigForm, luckyPauseMinMs: event.target.value })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="最长幸运间隔(毫秒)"
|
||||||
|
required
|
||||||
|
type="number"
|
||||||
|
value={page.humanConfigForm.luckyPauseMaxMs}
|
||||||
|
onChange={(event) =>
|
||||||
|
page.setHumanConfigForm({ ...page.humanConfigForm, luckyPauseMaxMs: event.target.value })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<GiftAutocomplete
|
||||||
|
label="幸运礼物合集"
|
||||||
|
loading={page.giftOptionsLoading}
|
||||||
|
options={page.giftOptions}
|
||||||
|
value={page.selectedHumanGiftOptions.luckyGiftIds}
|
||||||
|
onChange={(options) => page.selectHumanGiftIds("luckyGiftIds", options)}
|
||||||
|
/>
|
||||||
|
<GiftAutocomplete
|
||||||
|
label="超级幸运礼物合集"
|
||||||
|
loading={page.giftOptionsLoading}
|
||||||
|
options={page.giftOptions}
|
||||||
|
value={page.selectedHumanGiftOptions.superLuckyGiftIds}
|
||||||
|
onChange={(options) => page.selectHumanGiftIds("superLuckyGiftIds", options)}
|
||||||
|
/>
|
||||||
|
<section className={styles.robotAutoPoolNotice}>
|
||||||
|
<div className={styles.primaryText}>机器人池自动获取</div>
|
||||||
|
<div className={styles.meta}>
|
||||||
|
系统会读取所有启用的全站机器人,并按机器人账号国家进入同国家真人房间。
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</AdminFormDialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function GiftAutocomplete({ label, loading, onChange, options, required = false, value }) {
|
||||||
|
return (
|
||||||
|
<Autocomplete
|
||||||
|
multiple
|
||||||
|
disableCloseOnSelect
|
||||||
|
getOptionLabel={giftOptionLabel}
|
||||||
|
isOptionEqualToValue={(option, selected) => giftId(option) === giftId(selected)}
|
||||||
|
loading={loading}
|
||||||
|
noOptionsText="当前无数据"
|
||||||
|
options={options}
|
||||||
|
renderInput={(params) => (
|
||||||
|
<TextField {...params} label={required ? <RequiredLabel>{label}</RequiredLabel> : label} />
|
||||||
|
)}
|
||||||
|
size="small"
|
||||||
|
value={value}
|
||||||
|
onChange={(_, selected) => onChange(selected)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function RobotRoomIdentity({ room }) {
|
function RobotRoomIdentity({ room }) {
|
||||||
return (
|
return (
|
||||||
<div className={styles.identity}>
|
<div className={styles.identity}>
|
||||||
@ -325,6 +663,17 @@ function RobotIdentity({ fallbackId = "", openInAppUserDetail = true, user = {}
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function OwnerRobotOption({ countryLabels, loadingLocation, option, regionLabels }) {
|
||||||
|
return (
|
||||||
|
<div className={styles.ownerRobotOption}>
|
||||||
|
<RobotIdentity openInAppUserDetail={false} user={option.user} fallbackId={option.userId} />
|
||||||
|
<span className={styles.ownerRobotLocation}>
|
||||||
|
{loadingLocation ? "国家/区域加载中" : robotLocationLabel(option, countryLabels, regionLabels)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function RequiredLabel({ children }) {
|
function RequiredLabel({ children }) {
|
||||||
return (
|
return (
|
||||||
<span>
|
<span>
|
||||||
@ -369,3 +718,7 @@ function formatSeconds(ms) {
|
|||||||
const seconds = Math.round(Number(ms || 0) / 1000);
|
const seconds = Math.round(Number(ms || 0) / 1000);
|
||||||
return `${seconds.toLocaleString("zh-CN")}秒`;
|
return `${seconds.toLocaleString("zh-CN")}秒`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatDurationRange(minMs, maxMs) {
|
||||||
|
return `${formatSeconds(minMs)}-${formatSeconds(maxMs)}`;
|
||||||
|
}
|
||||||
|
|||||||
62
src/features/rooms/pages/RoomWhitelistPage.jsx
Normal file
62
src/features/rooms/pages/RoomWhitelistPage.jsx
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import GroupsOutlined from "@mui/icons-material/GroupsOutlined";
|
||||||
|
import SaveOutlined from "@mui/icons-material/SaveOutlined";
|
||||||
|
import TextField from "@mui/material/TextField";
|
||||||
|
import { useRoomWhitelistPage } from "@/features/rooms/hooks/useRoomWhitelistPage.js";
|
||||||
|
import styles from "@/features/rooms/rooms.module.css";
|
||||||
|
import { AdminActionIconButton, AdminListBody, AdminListPage, AdminListToolbar } from "@/shared/ui/AdminListLayout.jsx";
|
||||||
|
import { DataState } from "@/shared/ui/DataState.jsx";
|
||||||
|
import { TimeText } from "@/shared/ui/TimeText.jsx";
|
||||||
|
|
||||||
|
export function RoomWhitelistPage() {
|
||||||
|
const page = useRoomWhitelistPage();
|
||||||
|
const canSave = page.abilities.canUpdateWhitelist && page.loadingAction !== "save";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AdminListPage>
|
||||||
|
<AdminListToolbar
|
||||||
|
actions={
|
||||||
|
page.abilities.canUpdateWhitelist ? (
|
||||||
|
<AdminActionIconButton
|
||||||
|
disabled={!canSave}
|
||||||
|
form="room-whitelist-form"
|
||||||
|
label="保存"
|
||||||
|
primary
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
<SaveOutlined fontSize="small" />
|
||||||
|
</AdminActionIconButton>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<DataState error={page.error} loading={page.loading} onRetry={page.reload}>
|
||||||
|
<AdminListBody>
|
||||||
|
<form id="room-whitelist-form" className={styles.configForm} onSubmit={page.submitWhitelist}>
|
||||||
|
<section className={styles.configSection}>
|
||||||
|
<div className={styles.configHeader}>
|
||||||
|
<span className={styles.configIcon}>
|
||||||
|
<GroupsOutlined fontSize="small" />
|
||||||
|
</span>
|
||||||
|
<div className={styles.configTitle}>
|
||||||
|
<h2>用户 ID</h2>
|
||||||
|
<span>
|
||||||
|
{page.data?.updatedAtMs ? <TimeText value={page.data.updatedAtMs} /> : "未配置"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
disabled={!page.abilities.canUpdateWhitelist}
|
||||||
|
fullWidth
|
||||||
|
label="用户 ID"
|
||||||
|
minRows={12}
|
||||||
|
multiline
|
||||||
|
value={page.text}
|
||||||
|
onChange={(event) => page.setText(event.target.value)}
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
</form>
|
||||||
|
</AdminListBody>
|
||||||
|
</DataState>
|
||||||
|
</AdminListPage>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -7,6 +7,8 @@ export function useRoomAbilities() {
|
|||||||
return {
|
return {
|
||||||
canUpdateConfig: can(PERMISSIONS.roomConfigUpdate),
|
canUpdateConfig: can(PERMISSIONS.roomConfigUpdate),
|
||||||
canViewConfig: can(PERMISSIONS.roomConfigView),
|
canViewConfig: can(PERMISSIONS.roomConfigView),
|
||||||
|
canUpdateWhitelist: can(PERMISSIONS.roomWhitelistUpdate),
|
||||||
|
canViewWhitelist: can(PERMISSIONS.roomWhitelistView),
|
||||||
canDelete: can(PERMISSIONS.roomDelete),
|
canDelete: can(PERMISSIONS.roomDelete),
|
||||||
canPinCancel: can(PERMISSIONS.roomPinCancel),
|
canPinCancel: can(PERMISSIONS.roomPinCancel),
|
||||||
canPinCreate: can(PERMISSIONS.roomPinCreate),
|
canPinCreate: can(PERMISSIONS.roomPinCreate),
|
||||||
|
|||||||
@ -48,10 +48,20 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.toolbarActions {
|
.toolbarActions {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--space-2);
|
gap: var(--space-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.robotConfigPanel {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(180px, 1fr) minmax(220px, 1fr) minmax(220px, 1fr) auto;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-4);
|
||||||
|
padding: var(--space-3) var(--space-5);
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
background: var(--bg-card-strong);
|
||||||
}
|
}
|
||||||
|
|
||||||
.formGrid {
|
.formGrid {
|
||||||
@ -64,6 +74,11 @@
|
|||||||
.formGrid {
|
.formGrid {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.robotConfigPanel {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.search {
|
.search {
|
||||||
@ -182,6 +197,41 @@
|
|||||||
font-size: var(--admin-font-size);
|
font-size: var(--admin-font-size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ownerRobotOption {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: var(--space-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ownerRobotOption > :first-child {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ownerRobotLocation {
|
||||||
|
flex: 0 1 320px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: var(--admin-font-size);
|
||||||
|
line-height: 1.35;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 720px) {
|
||||||
|
.ownerRobotOption {
|
||||||
|
align-items: flex-start;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ownerRobotLocation {
|
||||||
|
flex-basis: auto;
|
||||||
|
padding-left: 48px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.sortHeader {
|
.sortHeader {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
@ -223,6 +273,21 @@
|
|||||||
font-weight: 650;
|
font-weight: 650;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.switchField {
|
||||||
|
display: inline-flex;
|
||||||
|
min-height: var(--control-height);
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.robotAutoPoolNotice {
|
||||||
|
display: grid;
|
||||||
|
gap: var(--space-3);
|
||||||
|
padding: var(--space-3);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius-control);
|
||||||
|
background: var(--bg-card-strong);
|
||||||
|
}
|
||||||
|
|
||||||
.rowActions {
|
.rowActions {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@ -25,6 +25,14 @@ export const roomRoutes = [
|
|||||||
path: "/rooms/config",
|
path: "/rooms/config",
|
||||||
permission: PERMISSIONS.roomConfigView,
|
permission: PERMISSIONS.roomConfigView,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: "房间白名单",
|
||||||
|
loader: () => import("./pages/RoomWhitelistPage.jsx").then((module) => module.RoomWhitelistPage),
|
||||||
|
menuCode: MENU_CODES.roomWhitelist,
|
||||||
|
pageKey: "room-whitelist",
|
||||||
|
path: "/rooms/whitelist",
|
||||||
|
permission: PERMISSIONS.roomWhitelistView,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: "机器人房间",
|
label: "机器人房间",
|
||||||
loader: () => import("./pages/RoomRobotPage.jsx").then((module) => module.RoomRobotPage),
|
loader: () => import("./pages/RoomRobotPage.jsx").then((module) => module.RoomRobotPage),
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { describe, expect, test } from "vitest";
|
import { describe, expect, test } from "vitest";
|
||||||
|
|
||||||
import { parseForm } from "@/shared/forms/validation";
|
import { parseForm } from "@/shared/forms/validation";
|
||||||
import { robotRoomCreateSchema } from "./schema";
|
import { humanRoomRobotConfigSchema, robotRoomCreateSchema, roomWhitelistSchema } from "./schema";
|
||||||
|
|
||||||
describe("robot room form schema", () => {
|
describe("robot room form schema", () => {
|
||||||
test("preserves int64 robot user ids as strings", () => {
|
test("preserves int64 robot user ids as strings", () => {
|
||||||
@ -13,14 +13,92 @@ describe("robot room form schema", () => {
|
|||||||
luckyGiftIds: ["28"],
|
luckyGiftIds: ["28"],
|
||||||
luckyPauseMaxMs: "20000",
|
luckyPauseMaxMs: "20000",
|
||||||
luckyPauseMinMs: "5000",
|
luckyPauseMinMs: "5000",
|
||||||
|
maxGiftSenders: "2",
|
||||||
maxRobotCount: "8",
|
maxRobotCount: "8",
|
||||||
minRobotCount: "2",
|
minRobotCount: "2",
|
||||||
normalGiftIntervalMs: "10000",
|
normalGiftIntervalMs: "10000",
|
||||||
ownerRobotUserId: "325379237278126080",
|
ownerRobotUserId: "325379237278126080",
|
||||||
|
robotReplaceMaxMs: "60000",
|
||||||
|
robotReplaceMinMs: "0",
|
||||||
|
robotStayMaxMs: "600000",
|
||||||
|
robotStayMinMs: "180000",
|
||||||
|
seatCount: "10",
|
||||||
visibleRegionId: 0,
|
visibleRegionId: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(payload.ownerRobotUserId).toBe("325379237278126080");
|
expect(payload.ownerRobotUserId).toBe("325379237278126080");
|
||||||
expect(payload.candidateRobotUserIds).toEqual(["325455441691676672"]);
|
expect(payload.candidateRobotUserIds).toEqual(["325455441691676672"]);
|
||||||
|
expect(payload.robotStayMinMs).toBe(180000);
|
||||||
|
expect(payload.robotStayMaxMs).toBe(600000);
|
||||||
|
expect(payload.robotReplaceMinMs).toBe(0);
|
||||||
|
expect(payload.robotReplaceMaxMs).toBe(60000);
|
||||||
|
expect(payload.maxGiftSenders).toBe(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("human room robot config schema", () => {
|
||||||
|
test("preserves robot ids and parses enabled config", () => {
|
||||||
|
const payload = parseForm(humanRoomRobotConfigSchema, {
|
||||||
|
candidateRoomMaxOnline: "5",
|
||||||
|
enabled: true,
|
||||||
|
giftIds: ["84"],
|
||||||
|
luckyComboMax: "3",
|
||||||
|
luckyComboMin: "1",
|
||||||
|
luckyGiftIds: ["28"],
|
||||||
|
luckyPauseMaxMs: "20000",
|
||||||
|
luckyPauseMinMs: "5000",
|
||||||
|
maxGiftSenders: "1",
|
||||||
|
normalGiftIntervalMaxMs: "20000",
|
||||||
|
normalGiftIntervalMinMs: "1000",
|
||||||
|
robotReplaceMaxMs: "180000",
|
||||||
|
robotReplaceMinMs: "60000",
|
||||||
|
robotStayMaxMs: "600000",
|
||||||
|
robotStayMinMs: "60000",
|
||||||
|
roomTargetMaxOnline: "10",
|
||||||
|
roomTargetMinOnline: "8",
|
||||||
|
superLuckyGiftIds: ["99"],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(payload.roomTargetMinOnline).toBe(8);
|
||||||
|
expect(payload.roomTargetMaxOnline).toBe(10);
|
||||||
|
expect(payload.roomFullStopOnline).toBe(10);
|
||||||
|
expect(payload.normalGiftIntervalMinMs).toBe(1000);
|
||||||
|
expect(payload.normalGiftIntervalMaxMs).toBe(20000);
|
||||||
|
expect(payload.giftIds).toEqual(["84"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("requires gift pools when enabled", () => {
|
||||||
|
expect(() =>
|
||||||
|
parseForm(humanRoomRobotConfigSchema, {
|
||||||
|
candidateRoomMaxOnline: "5",
|
||||||
|
enabled: true,
|
||||||
|
giftIds: [],
|
||||||
|
luckyComboMax: "3",
|
||||||
|
luckyComboMin: "1",
|
||||||
|
luckyGiftIds: [],
|
||||||
|
luckyPauseMaxMs: "20000",
|
||||||
|
luckyPauseMinMs: "5000",
|
||||||
|
maxGiftSenders: "1",
|
||||||
|
normalGiftIntervalMaxMs: "20000",
|
||||||
|
normalGiftIntervalMinMs: "1000",
|
||||||
|
robotReplaceMaxMs: "180000",
|
||||||
|
robotReplaceMinMs: "60000",
|
||||||
|
robotStayMaxMs: "600000",
|
||||||
|
robotStayMinMs: "60000",
|
||||||
|
roomTargetMaxOnline: "10",
|
||||||
|
roomTargetMinOnline: "8",
|
||||||
|
superLuckyGiftIds: [],
|
||||||
|
}),
|
||||||
|
).toThrow("启用后请选择普通礼物合集");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("room whitelist schema", () => {
|
||||||
|
test("keeps long user ids as strings and removes duplicates", () => {
|
||||||
|
const payload = parseForm(roomWhitelistSchema, {
|
||||||
|
userIds: ["325379237278126080", "325379237278126080", "1001"],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(payload.userIds).toEqual(["1001", "325379237278126080"]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -2,10 +2,11 @@ import { z } from "zod";
|
|||||||
|
|
||||||
const optionalText = (max: number, message: string) => z.string().trim().max(max, message).optional().default("");
|
const optionalText = (max: number, message: string) => z.string().trim().max(max, message).optional().default("");
|
||||||
const roomSeatCandidates = [10, 15, 20, 25, 30] as const;
|
const roomSeatCandidates = [10, 15, 20, 25, 30] as const;
|
||||||
const robotUserIdSchema = (message: string) => z
|
const robotUserIdSchema = (message: string) =>
|
||||||
.union([z.string(), z.number()])
|
z
|
||||||
.transform((value) => String(value).trim())
|
.union([z.string(), z.number()])
|
||||||
.refine((value) => /^[1-9]\d*$/.test(value), message);
|
.transform((value) => String(value).trim())
|
||||||
|
.refine((value) => /^[1-9]\d*$/.test(value), message);
|
||||||
const pinTypeSchema = z
|
const pinTypeSchema = z
|
||||||
.string()
|
.string()
|
||||||
.trim()
|
.trim()
|
||||||
@ -61,6 +62,20 @@ export const roomConfigSchema = z
|
|||||||
path: ["defaultSeatCount"],
|
path: ["defaultSeatCount"],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const roomWhitelistSchema = z.object({
|
||||||
|
userIds: z
|
||||||
|
.array(
|
||||||
|
z
|
||||||
|
.union([z.string(), z.number()])
|
||||||
|
.transform((value) => String(value).trim())
|
||||||
|
.refine((value) => /^[1-9]\d*$/.test(value), "用户 ID 不正确"),
|
||||||
|
)
|
||||||
|
.default([])
|
||||||
|
.transform((values) =>
|
||||||
|
[...new Set(values)].sort((left, right) => left.localeCompare(right, "en", { numeric: true })),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
export const roomPinCreateSchema = z
|
export const roomPinCreateSchema = z
|
||||||
.object({
|
.object({
|
||||||
expiresAtMs: dateTimeMsSchema,
|
expiresAtMs: dateTimeMsSchema,
|
||||||
@ -83,10 +98,19 @@ export const robotRoomCreateSchema = z
|
|||||||
luckyGiftIds: z.array(z.string().trim().min(1)).min(1, "请选择幸运礼物合集"),
|
luckyGiftIds: z.array(z.string().trim().min(1)).min(1, "请选择幸运礼物合集"),
|
||||||
luckyPauseMaxMs: z.coerce.number().int().min(1000, "幸运礼物间隔范围不正确"),
|
luckyPauseMaxMs: z.coerce.number().int().min(1000, "幸运礼物间隔范围不正确"),
|
||||||
luckyPauseMinMs: z.coerce.number().int().min(1000, "幸运礼物间隔范围不正确"),
|
luckyPauseMinMs: z.coerce.number().int().min(1000, "幸运礼物间隔范围不正确"),
|
||||||
|
maxGiftSenders: z.coerce.number().int().min(1, "同时送礼机器人数量不正确"),
|
||||||
maxRobotCount: z.coerce.number().int().min(1, "机器人数量范围不正确"),
|
maxRobotCount: z.coerce.number().int().min(1, "机器人数量范围不正确"),
|
||||||
minRobotCount: z.coerce.number().int().min(1, "机器人数量范围不正确"),
|
minRobotCount: z.coerce.number().int().min(1, "机器人数量范围不正确"),
|
||||||
normalGiftIntervalMs: z.coerce.number().int().min(1000, "普通礼物频次不正确"),
|
normalGiftIntervalMs: z.coerce.number().int().min(1000, "普通礼物频次不正确"),
|
||||||
ownerRobotUserId: robotUserIdSchema("请选择房主机器人"),
|
ownerRobotUserId: robotUserIdSchema("请选择房主机器人"),
|
||||||
|
robotReplaceMaxMs: z.coerce.number().int().min(0, "机器人补位时间范围不正确"),
|
||||||
|
robotReplaceMinMs: z.coerce.number().int().min(0, "机器人补位时间范围不正确"),
|
||||||
|
robotStayMaxMs: z.coerce.number().int().min(1000, "机器人停留时间范围不正确"),
|
||||||
|
robotStayMinMs: z.coerce.number().int().min(1000, "机器人停留时间范围不正确"),
|
||||||
|
seatCount: z.coerce
|
||||||
|
.number()
|
||||||
|
.int()
|
||||||
|
.refine((value) => value === 10 || value === 15 || value === 20, "请选择麦位数"),
|
||||||
visibleRegionId: z.coerce.number().int().min(0, "区域 ID 不正确").default(0),
|
visibleRegionId: z.coerce.number().int().min(0, "区域 ID 不正确").default(0),
|
||||||
})
|
})
|
||||||
.refine((value) => value.maxRobotCount >= value.minRobotCount, {
|
.refine((value) => value.maxRobotCount >= value.minRobotCount, {
|
||||||
@ -100,10 +124,81 @@ export const robotRoomCreateSchema = z
|
|||||||
.refine((value) => value.luckyPauseMaxMs >= value.luckyPauseMinMs, {
|
.refine((value) => value.luckyPauseMaxMs >= value.luckyPauseMinMs, {
|
||||||
message: "幸运礼物间隔范围不正确",
|
message: "幸运礼物间隔范围不正确",
|
||||||
path: ["luckyPauseMaxMs"],
|
path: ["luckyPauseMaxMs"],
|
||||||
|
})
|
||||||
|
.refine((value) => value.robotStayMaxMs >= value.robotStayMinMs, {
|
||||||
|
message: "机器人停留时间范围不正确",
|
||||||
|
path: ["robotStayMaxMs"],
|
||||||
|
})
|
||||||
|
.refine((value) => value.robotReplaceMaxMs >= value.robotReplaceMinMs, {
|
||||||
|
message: "机器人补位时间范围不正确",
|
||||||
|
path: ["robotReplaceMaxMs"],
|
||||||
|
});
|
||||||
|
|
||||||
|
export const humanRoomRobotConfigSchema = z
|
||||||
|
.object({
|
||||||
|
candidateRoomMaxOnline: z.coerce.number().int().min(1, "真人房间筛选人数不正确"),
|
||||||
|
enabled: z.coerce.boolean().default(false),
|
||||||
|
giftIds: z.array(z.string().trim().min(1)).default([]),
|
||||||
|
luckyComboMax: z.coerce.number().int().min(1, "幸运礼物连击范围不正确"),
|
||||||
|
luckyComboMin: z.coerce.number().int().min(1, "幸运礼物连击范围不正确"),
|
||||||
|
luckyGiftIds: z.array(z.string().trim().min(1)).default([]),
|
||||||
|
luckyPauseMaxMs: z.coerce.number().int().min(1000, "幸运礼物间隔范围不正确"),
|
||||||
|
luckyPauseMinMs: z.coerce.number().int().min(1000, "幸运礼物间隔范围不正确"),
|
||||||
|
maxGiftSenders: z.coerce.number().int().min(1, "房间送礼机器人数量不正确"),
|
||||||
|
normalGiftIntervalMs: z.coerce.number().int().min(1000, "普通礼物频次不正确").optional(),
|
||||||
|
normalGiftIntervalMaxMs: z.coerce.number().int().min(1000, "普通礼物间隔范围不正确"),
|
||||||
|
normalGiftIntervalMinMs: z.coerce.number().int().min(1000, "普通礼物间隔范围不正确"),
|
||||||
|
robotReplaceMaxMs: z.coerce.number().int().min(0, "机器人补位时间范围不正确"),
|
||||||
|
robotReplaceMinMs: z.coerce.number().int().min(0, "机器人补位时间范围不正确"),
|
||||||
|
robotStayMaxMs: z.coerce.number().int().min(1000, "机器人停留时间范围不正确"),
|
||||||
|
robotStayMinMs: z.coerce.number().int().min(1000, "机器人停留时间范围不正确"),
|
||||||
|
roomFullStopOnline: z.coerce.number().int().min(1, "真人房间停止人数不正确").optional(),
|
||||||
|
roomTargetMaxOnline: z.coerce.number().int().min(1, "房间目标人数范围不正确"),
|
||||||
|
roomTargetMinOnline: z.coerce.number().int().min(1, "房间目标人数范围不正确"),
|
||||||
|
superLuckyGiftIds: z.array(z.string().trim().min(1)).default([]),
|
||||||
|
})
|
||||||
|
.transform((value) => ({ ...value, roomFullStopOnline: value.roomTargetMaxOnline }))
|
||||||
|
.refine((value) => value.roomTargetMaxOnline >= value.roomTargetMinOnline, {
|
||||||
|
message: "房间目标人数范围不正确",
|
||||||
|
path: ["roomTargetMaxOnline"],
|
||||||
|
})
|
||||||
|
.refine((value) => value.roomTargetMaxOnline >= value.candidateRoomMaxOnline, {
|
||||||
|
message: "目标人数上限不能小于筛选人数",
|
||||||
|
path: ["roomTargetMaxOnline"],
|
||||||
|
})
|
||||||
|
.refine((value) => value.luckyComboMax >= value.luckyComboMin, {
|
||||||
|
message: "幸运礼物连击范围不正确",
|
||||||
|
path: ["luckyComboMax"],
|
||||||
|
})
|
||||||
|
.refine((value) => value.luckyPauseMaxMs >= value.luckyPauseMinMs, {
|
||||||
|
message: "幸运礼物间隔范围不正确",
|
||||||
|
path: ["luckyPauseMaxMs"],
|
||||||
|
})
|
||||||
|
.refine((value) => value.normalGiftIntervalMaxMs >= value.normalGiftIntervalMinMs, {
|
||||||
|
message: "普通礼物间隔范围不正确",
|
||||||
|
path: ["normalGiftIntervalMaxMs"],
|
||||||
|
})
|
||||||
|
.refine((value) => value.robotStayMaxMs >= value.robotStayMinMs, {
|
||||||
|
message: "机器人停留时间范围不正确",
|
||||||
|
path: ["robotStayMaxMs"],
|
||||||
|
})
|
||||||
|
.refine((value) => value.robotReplaceMaxMs >= value.robotReplaceMinMs, {
|
||||||
|
message: "机器人补位时间范围不正确",
|
||||||
|
path: ["robotReplaceMaxMs"],
|
||||||
|
})
|
||||||
|
.refine((value) => !value.enabled || value.giftIds.length > 0, {
|
||||||
|
message: "启用后请选择普通礼物合集",
|
||||||
|
path: ["giftIds"],
|
||||||
|
})
|
||||||
|
.refine((value) => !value.enabled || value.luckyGiftIds.length + value.superLuckyGiftIds.length > 0, {
|
||||||
|
message: "启用后请选择幸运礼物或超级幸运礼物合集",
|
||||||
|
path: ["luckyGiftIds"],
|
||||||
});
|
});
|
||||||
|
|
||||||
export type RoomUpdateForm = z.infer<typeof roomUpdateSchema>;
|
export type RoomUpdateForm = z.infer<typeof roomUpdateSchema>;
|
||||||
export type RoomStatusForm = z.infer<typeof roomStatusSchema>;
|
export type RoomStatusForm = z.infer<typeof roomStatusSchema>;
|
||||||
export type RoomConfigForm = z.infer<typeof roomConfigSchema>;
|
export type RoomConfigForm = z.infer<typeof roomConfigSchema>;
|
||||||
|
export type RoomWhitelistForm = z.infer<typeof roomWhitelistSchema>;
|
||||||
export type RoomPinCreateForm = z.infer<typeof roomPinCreateSchema>;
|
export type RoomPinCreateForm = z.infer<typeof roomPinCreateSchema>;
|
||||||
export type RobotRoomCreateForm = z.infer<typeof robotRoomCreateSchema>;
|
export type RobotRoomCreateForm = z.infer<typeof robotRoomCreateSchema>;
|
||||||
|
export type HumanRoomRobotConfigForm = z.infer<typeof humanRoomRobotConfigSchema>;
|
||||||
|
|||||||
@ -106,6 +106,7 @@ export const API_OPERATIONS = {
|
|||||||
getCPConfig: "getCPConfig",
|
getCPConfig: "getCPConfig",
|
||||||
getCumulativeRechargeRewardConfig: "getCumulativeRechargeRewardConfig",
|
getCumulativeRechargeRewardConfig: "getCumulativeRechargeRewardConfig",
|
||||||
getFirstRechargeRewardConfig: "getFirstRechargeRewardConfig",
|
getFirstRechargeRewardConfig: "getFirstRechargeRewardConfig",
|
||||||
|
getHumanRoomRobotConfig: "getHumanRoomRobotConfig",
|
||||||
getInviteActivityRewardConfig: "getInviteActivityRewardConfig",
|
getInviteActivityRewardConfig: "getInviteActivityRewardConfig",
|
||||||
getLuckyGiftConfig: "getLuckyGiftConfig",
|
getLuckyGiftConfig: "getLuckyGiftConfig",
|
||||||
getLuckyGiftDrawSummary: "getLuckyGiftDrawSummary",
|
getLuckyGiftDrawSummary: "getLuckyGiftDrawSummary",
|
||||||
@ -121,6 +122,7 @@ export const API_OPERATIONS = {
|
|||||||
getRoomRpsChallenge: "getRoomRpsChallenge",
|
getRoomRpsChallenge: "getRoomRpsChallenge",
|
||||||
getRoomRpsConfig: "getRoomRpsConfig",
|
getRoomRpsConfig: "getRoomRpsConfig",
|
||||||
getRoomTurnoverRewardConfig: "getRoomTurnoverRewardConfig",
|
getRoomTurnoverRewardConfig: "getRoomTurnoverRewardConfig",
|
||||||
|
getRoomWhitelistConfig: "getRoomWhitelistConfig",
|
||||||
getSevenDayCheckInConfig: "getSevenDayCheckInConfig",
|
getSevenDayCheckInConfig: "getSevenDayCheckInConfig",
|
||||||
getUser: "getUser",
|
getUser: "getUser",
|
||||||
getWeeklyStarCycle: "getWeeklyStarCycle",
|
getWeeklyStarCycle: "getWeeklyStarCycle",
|
||||||
@ -243,6 +245,7 @@ export const API_OPERATIONS = {
|
|||||||
updateH5Link: "updateH5Link",
|
updateH5Link: "updateH5Link",
|
||||||
updateH5Links: "updateH5Links",
|
updateH5Links: "updateH5Links",
|
||||||
updateHostAgencyPolicy: "updateHostAgencyPolicy",
|
updateHostAgencyPolicy: "updateHostAgencyPolicy",
|
||||||
|
updateHumanRoomRobotConfig: "updateHumanRoomRobotConfig",
|
||||||
updateInviteActivityRewardConfig: "updateInviteActivityRewardConfig",
|
updateInviteActivityRewardConfig: "updateInviteActivityRewardConfig",
|
||||||
updateMenu: "updateMenu",
|
updateMenu: "updateMenu",
|
||||||
updateMenuVisible: "updateMenuVisible",
|
updateMenuVisible: "updateMenuVisible",
|
||||||
@ -263,6 +266,7 @@ export const API_OPERATIONS = {
|
|||||||
updateRoomRocketConfig: "updateRoomRocketConfig",
|
updateRoomRocketConfig: "updateRoomRocketConfig",
|
||||||
updateRoomRpsConfig: "updateRoomRpsConfig",
|
updateRoomRpsConfig: "updateRoomRpsConfig",
|
||||||
updateRoomTurnoverRewardConfig: "updateRoomTurnoverRewardConfig",
|
updateRoomTurnoverRewardConfig: "updateRoomTurnoverRewardConfig",
|
||||||
|
updateRoomWhitelistConfig: "updateRoomWhitelistConfig",
|
||||||
updateSevenDayCheckInConfig: "updateSevenDayCheckInConfig",
|
updateSevenDayCheckInConfig: "updateSevenDayCheckInConfig",
|
||||||
updateSplashScreen: "updateSplashScreen",
|
updateSplashScreen: "updateSplashScreen",
|
||||||
updateTaskDefinition: "updateTaskDefinition",
|
updateTaskDefinition: "updateTaskDefinition",
|
||||||
@ -932,6 +936,13 @@ export const API_ENDPOINTS: Record<ApiOperationId, ApiEndpoint> = {
|
|||||||
permission: "first-recharge-reward:view",
|
permission: "first-recharge-reward:view",
|
||||||
permissions: ["first-recharge-reward:view"]
|
permissions: ["first-recharge-reward:view"]
|
||||||
},
|
},
|
||||||
|
getHumanRoomRobotConfig: {
|
||||||
|
method: "GET",
|
||||||
|
operationId: API_OPERATIONS.getHumanRoomRobotConfig,
|
||||||
|
path: "/v1/admin/rooms/robot-rooms/human-config",
|
||||||
|
permission: "room-robot:view",
|
||||||
|
permissions: ["room-robot:view"]
|
||||||
|
},
|
||||||
getInviteActivityRewardConfig: {
|
getInviteActivityRewardConfig: {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
operationId: API_OPERATIONS.getInviteActivityRewardConfig,
|
operationId: API_OPERATIONS.getInviteActivityRewardConfig,
|
||||||
@ -1037,6 +1048,13 @@ export const API_ENDPOINTS: Record<ApiOperationId, ApiEndpoint> = {
|
|||||||
permission: "room-turnover-reward:view",
|
permission: "room-turnover-reward:view",
|
||||||
permissions: ["room-turnover-reward:view"]
|
permissions: ["room-turnover-reward:view"]
|
||||||
},
|
},
|
||||||
|
getRoomWhitelistConfig: {
|
||||||
|
method: "GET",
|
||||||
|
operationId: API_OPERATIONS.getRoomWhitelistConfig,
|
||||||
|
path: "/v1/admin/rooms/whitelist",
|
||||||
|
permission: "room-whitelist:view",
|
||||||
|
permissions: ["room-whitelist:view"]
|
||||||
|
},
|
||||||
getSevenDayCheckInConfig: {
|
getSevenDayCheckInConfig: {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
operationId: API_OPERATIONS.getSevenDayCheckInConfig,
|
operationId: API_OPERATIONS.getSevenDayCheckInConfig,
|
||||||
@ -1877,6 +1895,13 @@ export const API_ENDPOINTS: Record<ApiOperationId, ApiEndpoint> = {
|
|||||||
permission: "host-agency-policy:update",
|
permission: "host-agency-policy:update",
|
||||||
permissions: ["host-agency-policy:update"]
|
permissions: ["host-agency-policy:update"]
|
||||||
},
|
},
|
||||||
|
updateHumanRoomRobotConfig: {
|
||||||
|
method: "PUT",
|
||||||
|
operationId: API_OPERATIONS.updateHumanRoomRobotConfig,
|
||||||
|
path: "/v1/admin/rooms/robot-rooms/human-config",
|
||||||
|
permission: "room-robot:update",
|
||||||
|
permissions: ["room-robot:update"]
|
||||||
|
},
|
||||||
updateInviteActivityRewardConfig: {
|
updateInviteActivityRewardConfig: {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
operationId: API_OPERATIONS.updateInviteActivityRewardConfig,
|
operationId: API_OPERATIONS.updateInviteActivityRewardConfig,
|
||||||
@ -2017,6 +2042,13 @@ export const API_ENDPOINTS: Record<ApiOperationId, ApiEndpoint> = {
|
|||||||
permission: "room-turnover-reward:update",
|
permission: "room-turnover-reward:update",
|
||||||
permissions: ["room-turnover-reward:update"]
|
permissions: ["room-turnover-reward:update"]
|
||||||
},
|
},
|
||||||
|
updateRoomWhitelistConfig: {
|
||||||
|
method: "PUT",
|
||||||
|
operationId: API_OPERATIONS.updateRoomWhitelistConfig,
|
||||||
|
path: "/v1/admin/rooms/whitelist",
|
||||||
|
permission: "room-whitelist:update",
|
||||||
|
permissions: ["room-whitelist:update"]
|
||||||
|
},
|
||||||
updateSevenDayCheckInConfig: {
|
updateSevenDayCheckInConfig: {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
operationId: API_OPERATIONS.updateSevenDayCheckInConfig,
|
operationId: API_OPERATIONS.updateSevenDayCheckInConfig,
|
||||||
|
|||||||
80
src/shared/api/generated/schema.d.ts
vendored
80
src/shared/api/generated/schema.d.ts
vendored
@ -2164,6 +2164,22 @@ export interface paths {
|
|||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
|
"/admin/rooms/whitelist": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
get: operations["getRoomWhitelistConfig"];
|
||||||
|
put: operations["updateRoomWhitelistConfig"];
|
||||||
|
post?: never;
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
"/admin/rooms/pins": {
|
"/admin/rooms/pins": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
@ -2228,6 +2244,22 @@ export interface paths {
|
|||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
|
"/admin/rooms/robot-rooms/human-config": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
get: operations["getHumanRoomRobotConfig"];
|
||||||
|
put: operations["updateHumanRoomRobotConfig"];
|
||||||
|
post?: never;
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
"/admin/rooms/robot-rooms/{room_id}/start": {
|
"/admin/rooms/robot-rooms/{room_id}/start": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
@ -6104,6 +6136,30 @@ export interface operations {
|
|||||||
200: components["responses"]["EmptyResponse"];
|
200: components["responses"]["EmptyResponse"];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
getRoomWhitelistConfig: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
200: components["responses"]["EmptyResponse"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
updateRoomWhitelistConfig: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
200: components["responses"]["EmptyResponse"];
|
||||||
|
};
|
||||||
|
};
|
||||||
listRoomPins: {
|
listRoomPins: {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
@ -6178,6 +6234,30 @@ export interface operations {
|
|||||||
200: components["responses"]["EmptyResponse"];
|
200: components["responses"]["EmptyResponse"];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
getHumanRoomRobotConfig: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
200: components["responses"]["EmptyResponse"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
updateHumanRoomRobotConfig: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
200: components["responses"]["EmptyResponse"];
|
||||||
|
};
|
||||||
|
};
|
||||||
startRobotRoom: {
|
startRobotRoom: {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
|
|||||||
@ -945,7 +945,14 @@ export interface RobotGiftRuleDto {
|
|||||||
luckyComboMin?: number;
|
luckyComboMin?: number;
|
||||||
luckyPauseMaxMs?: number;
|
luckyPauseMaxMs?: number;
|
||||||
luckyPauseMinMs?: number;
|
luckyPauseMinMs?: number;
|
||||||
|
maxGiftSenders?: number;
|
||||||
normalGiftIntervalMs?: number;
|
normalGiftIntervalMs?: number;
|
||||||
|
normalGiftIntervalMinMs?: number;
|
||||||
|
normalGiftIntervalMaxMs?: number;
|
||||||
|
robotReplaceMaxMs?: number;
|
||||||
|
robotReplaceMinMs?: number;
|
||||||
|
robotStayMaxMs?: number;
|
||||||
|
robotStayMinMs?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RobotRoomDto {
|
export interface RobotRoomDto {
|
||||||
@ -959,6 +966,8 @@ export interface RobotRoomDto {
|
|||||||
regionName?: string;
|
regionName?: string;
|
||||||
robots?: RoomOwnerDto[];
|
robots?: RoomOwnerDto[];
|
||||||
robotUserIds?: string[];
|
robotUserIds?: string[];
|
||||||
|
activeRobotCount?: number;
|
||||||
|
seatCount?: number;
|
||||||
roomId: string;
|
roomId: string;
|
||||||
roomShortId?: string;
|
roomShortId?: string;
|
||||||
status?: string;
|
status?: string;
|
||||||
@ -984,22 +993,78 @@ export interface RobotRoomPayload {
|
|||||||
luckyGiftIds: string[];
|
luckyGiftIds: string[];
|
||||||
luckyPauseMaxMs: number;
|
luckyPauseMaxMs: number;
|
||||||
luckyPauseMinMs: number;
|
luckyPauseMinMs: number;
|
||||||
|
maxGiftSenders: number;
|
||||||
maxRobotCount: number;
|
maxRobotCount: number;
|
||||||
minRobotCount: number;
|
minRobotCount: number;
|
||||||
normalGiftIntervalMs: number;
|
normalGiftIntervalMs: number;
|
||||||
ownerRobotUserId: EntityId;
|
ownerRobotUserId: EntityId;
|
||||||
|
robotReplaceMaxMs: number;
|
||||||
|
robotReplaceMinMs: number;
|
||||||
|
robotStayMaxMs: number;
|
||||||
|
robotStayMinMs: number;
|
||||||
|
seatCount: number;
|
||||||
roomAvatar?: string;
|
roomAvatar?: string;
|
||||||
roomName?: string;
|
roomName?: string;
|
||||||
visibleRegionId?: EntityId;
|
visibleRegionId?: EntityId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface HumanRoomRobotConfigDto {
|
||||||
|
candidateRoomMaxOnline?: number;
|
||||||
|
enabled?: boolean;
|
||||||
|
giftIds?: string[];
|
||||||
|
luckyComboMax?: number;
|
||||||
|
luckyComboMin?: number;
|
||||||
|
luckyGiftIds?: string[];
|
||||||
|
luckyPauseMaxMs?: number;
|
||||||
|
luckyPauseMinMs?: number;
|
||||||
|
maxGiftSenders?: number;
|
||||||
|
normalGiftIntervalMs?: number;
|
||||||
|
normalGiftIntervalMaxMs?: number;
|
||||||
|
normalGiftIntervalMinMs?: number;
|
||||||
|
robotReplaceMaxMs?: number;
|
||||||
|
robotReplaceMinMs?: number;
|
||||||
|
robotStayMaxMs?: number;
|
||||||
|
robotStayMinMs?: number;
|
||||||
|
roomFullStopOnline?: number;
|
||||||
|
roomTargetMaxOnline?: number;
|
||||||
|
roomTargetMinOnline?: number;
|
||||||
|
superLuckyGiftIds?: string[];
|
||||||
|
updatedAtMs?: number;
|
||||||
|
updatedByAdminId?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HumanRoomRobotConfigPayload {
|
||||||
|
candidateRoomMaxOnline: number;
|
||||||
|
enabled: boolean;
|
||||||
|
giftIds: string[];
|
||||||
|
luckyComboMax: number;
|
||||||
|
luckyComboMin: number;
|
||||||
|
luckyGiftIds: string[];
|
||||||
|
luckyPauseMaxMs: number;
|
||||||
|
luckyPauseMinMs: number;
|
||||||
|
maxGiftSenders: number;
|
||||||
|
normalGiftIntervalMs: number;
|
||||||
|
normalGiftIntervalMinMs: number;
|
||||||
|
normalGiftIntervalMaxMs: number;
|
||||||
|
roomTargetMaxOnline: number;
|
||||||
|
roomTargetMinOnline: number;
|
||||||
|
robotReplaceMaxMs: number;
|
||||||
|
robotReplaceMinMs: number;
|
||||||
|
robotStayMaxMs: number;
|
||||||
|
robotStayMinMs: number;
|
||||||
|
roomFullStopOnline: number;
|
||||||
|
superLuckyGiftIds: string[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface RoomOwnerDto {
|
export interface RoomOwnerDto {
|
||||||
avatar?: string;
|
avatar?: string;
|
||||||
|
countryCode?: string;
|
||||||
displayUserId?: string;
|
displayUserId?: string;
|
||||||
prettyDisplayUserId?: string;
|
prettyDisplayUserId?: string;
|
||||||
prettyId?: string;
|
prettyId?: string;
|
||||||
userId?: string;
|
userId?: string;
|
||||||
username?: string;
|
username?: string;
|
||||||
|
visibleRegionId?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RoomUpdatePayload {
|
export interface RoomUpdatePayload {
|
||||||
@ -1024,6 +1089,15 @@ export interface RoomConfigPayload {
|
|||||||
defaultSeatCount: number;
|
defaultSeatCount: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface RoomWhitelistConfigDto {
|
||||||
|
updatedAtMs?: number;
|
||||||
|
userIds: EntityId[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RoomWhitelistPayload {
|
||||||
|
userIds: EntityId[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface UploadResultDto {
|
export interface UploadResultDto {
|
||||||
content_type?: string;
|
content_type?: string;
|
||||||
contentType?: string;
|
contentType?: string;
|
||||||
@ -1087,8 +1161,8 @@ export interface AppBannerDto {
|
|||||||
coverUrl: string;
|
coverUrl: string;
|
||||||
createdAtMs?: number;
|
createdAtMs?: number;
|
||||||
description?: string;
|
description?: string;
|
||||||
displayScope?: "home" | "room" | "recharge" | string;
|
displayScope?: "home" | "room" | "recharge" | "me" | string;
|
||||||
displayScopes?: Array<"home" | "room" | "recharge" | string>;
|
displayScopes?: Array<"home" | "room" | "recharge" | "me" | string>;
|
||||||
endsAtMs?: number;
|
endsAtMs?: number;
|
||||||
id: number;
|
id: number;
|
||||||
param?: string;
|
param?: string;
|
||||||
@ -1106,8 +1180,8 @@ export interface AppBannerPayload {
|
|||||||
countryCode?: string;
|
countryCode?: string;
|
||||||
coverUrl: string;
|
coverUrl: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
displayScope?: "home" | "room" | "recharge";
|
displayScope?: "home" | "room" | "recharge" | "me";
|
||||||
displayScopes?: Array<"home" | "room" | "recharge">;
|
displayScopes?: Array<"home" | "room" | "recharge" | "me">;
|
||||||
endsAtMs?: number;
|
endsAtMs?: number;
|
||||||
param?: string;
|
param?: string;
|
||||||
platform: "android" | "ios";
|
platform: "android" | "ios";
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user