机器人进真人房配置

This commit is contained in:
zhx 2026-06-23 14:05:17 +08:00
parent 17251552d9
commit 05412e477a
8 changed files with 83 additions and 31 deletions

View File

@ -5500,6 +5500,12 @@
"type": "object", "type": "object",
"required": ["countryCode", "maxRoomCount"], "required": ["countryCode", "maxRoomCount"],
"properties": { "properties": {
"allowedOwnerIds": {
"items": {
"type": "string"
},
"type": "array"
},
"countryCode": { "countryCode": {
"type": "string" "type": "string"
}, },

View File

@ -70,6 +70,7 @@ function emptyHumanConfigForm() {
function emptyHumanCountryRule(countryOptions = [], existingRules = []) { function emptyHumanCountryRule(countryOptions = [], existingRules = []) {
return { return {
allowedOwnerIds: "",
countryCode: firstAvailableCountryCode(countryOptions, existingRules), countryCode: firstAvailableCountryCode(countryOptions, existingRules),
maxRoomCount: "1", maxRoomCount: "1",
}; };
@ -405,6 +406,7 @@ function humanConfigToForm(config = null) {
candidateRoomMaxOnline: String(config.candidateRoomMaxOnline ?? defaults.candidateRoomMaxOnline), candidateRoomMaxOnline: String(config.candidateRoomMaxOnline ?? defaults.candidateRoomMaxOnline),
countryLimitEnabled: Boolean(config.countryLimitEnabled), countryLimitEnabled: Boolean(config.countryLimitEnabled),
countryRules: (config.countryRules || []).map((rule) => ({ countryRules: (config.countryRules || []).map((rule) => ({
allowedOwnerIds: (rule.allowedOwnerIds || []).map(String).join(","),
countryCode: String(rule.countryCode || "").toUpperCase(), countryCode: String(rule.countryCode || "").toUpperCase(),
maxRoomCount: String(rule.maxRoomCount ?? "1"), maxRoomCount: String(rule.maxRoomCount ?? "1"),
})), })),

View File

@ -7,6 +7,7 @@ import MenuItem from "@mui/material/MenuItem";
import TextField from "@mui/material/TextField"; import TextField from "@mui/material/TextField";
import Tooltip from "@mui/material/Tooltip"; import Tooltip from "@mui/material/Tooltip";
import { Button } from "@/shared/ui/Button.jsx"; import { Button } from "@/shared/ui/Button.jsx";
import { IconButton } from "@/shared/ui/IconButton.jsx";
import { import {
AdminFormAmountField, AdminFormAmountField,
AdminFormDialog, AdminFormDialog,
@ -591,15 +592,6 @@ function HumanConfigDialog({ page }) {
> >
{page.humanConfigForm.countryLimitEnabled ? <HumanCountryRules page={page} /> : null} {page.humanConfigForm.countryLimitEnabled ? <HumanCountryRules page={page} /> : null}
</AdminFormSection> </AdminFormSection>
<section className={styles.robotAutoPoolNotice}>
<div className={styles.primaryText}>机器人池自动获取</div>
<div className={styles.meta}>
{page.humanConfigForm.countryLimitEnabled
? "国家配置启用后,只会读取已配置国家的机器人,并限制每个国家同时进入的真人房数量。"
: "系统会读取所有启用的全站机器人,并按机器人账号国家进入同国家真人房间。"}
</div>
</section>
</AdminFormDialog> </AdminFormDialog>
); );
} }
@ -657,13 +649,27 @@ function HumanCountryRules({ page }) {
value={rule.maxRoomCount} value={rule.maxRoomCount}
onChange={(value) => page.updateHumanCountryRule(index, { maxRoomCount: value })} onChange={(value) => page.updateHumanCountryRule(index, { maxRoomCount: value })}
/> />
<Button <TextField
startIcon={<DeleteOutlineOutlined fontSize="small" />} className={styles.countryRuleOwners}
variant="secondary" label="房主ID列表"
onClick={() => page.removeHumanCountryRule(index)} placeholder="输入房主ID逗号分隔"
> size="small"
删除 value={rule.allowedOwnerIds || ""}
</Button> onChange={(event) =>
page.updateHumanCountryRule(index, { allowedOwnerIds: event.target.value })
}
/>
<Tooltip arrow title="删除">
<span className={styles.countryRuleRemove}>
<IconButton
label="删除国家规则"
tone="danger"
onClick={() => page.removeHumanCountryRule(index)}
>
<DeleteOutlineOutlined fontSize="small" />
</IconButton>
</span>
</Tooltip>
</div> </div>
))} ))}
{rows.length === 0 ? <div className={styles.meta}>当前无数据</div> : null} {rows.length === 0 ? <div className={styles.meta}>当前无数据</div> : null}

View File

@ -293,15 +293,6 @@
gap: var(--space-2); gap: var(--space-2);
} }
.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);
}
.countryRulesSection { .countryRulesSection {
display: grid; display: grid;
gap: var(--space-3); gap: var(--space-3);
@ -321,14 +312,32 @@
.countryRuleRows { .countryRuleRows {
display: grid; display: grid;
gap: var(--space-2); gap: var(--space-3);
} }
.countryRuleRow { .countryRuleRow {
display: grid; display: grid;
grid-template-columns: minmax(220px, 1fr) minmax(132px, 0.42fr) auto; grid-template-columns: minmax(0, 1fr) minmax(128px, 160px) auto;
align-items: center; align-items: start;
gap: var(--space-2); gap: var(--space-3);
}
.countryRuleRow > :global(.MuiTextField-root),
.countryRuleRow > :global(.MuiFormControl-root) {
width: 100%;
}
.countryRuleOwners {
grid-column: 1 / -1;
grid-row: 2;
}
.countryRuleRemove {
display: inline-flex;
grid-column: 3;
grid-row: 1;
align-self: center;
justify-content: flex-end;
} }
@media (max-width: 720px) { @media (max-width: 720px) {
@ -342,6 +351,17 @@
.countryRuleRow { .countryRuleRow {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
.countryRuleOwners {
grid-column: auto;
grid-row: auto;
}
.countryRuleRemove {
grid-column: auto;
grid-row: auto;
justify-content: flex-start;
}
} }
.rowActions { .rowActions {

View File

@ -41,7 +41,7 @@ describe("human room robot config schema", () => {
const payload = parseForm(humanRoomRobotConfigSchema, { const payload = parseForm(humanRoomRobotConfigSchema, {
candidateRoomMaxOnline: "5", candidateRoomMaxOnline: "5",
countryLimitEnabled: true, countryLimitEnabled: true,
countryRules: [{ countryCode: "sa", maxRoomCount: "2" }], countryRules: [{ allowedOwnerIds: "1001, 1002,,1001", countryCode: "sa", maxRoomCount: "2" }],
enabled: true, enabled: true,
giftIds: ["84"], giftIds: ["84"],
luckyComboMax: "3", luckyComboMax: "3",
@ -68,7 +68,7 @@ describe("human room robot config schema", () => {
expect(payload.normalGiftIntervalMaxMs).toBe(20000); expect(payload.normalGiftIntervalMaxMs).toBe(20000);
expect(payload.giftIds).toEqual(["84"]); expect(payload.giftIds).toEqual(["84"]);
expect(payload.countryLimitEnabled).toBe(true); expect(payload.countryLimitEnabled).toBe(true);
expect(payload.countryRules).toEqual([{ countryCode: "SA", maxRoomCount: 2 }]); expect(payload.countryRules).toEqual([{ allowedOwnerIds: ["1001", "1002"], countryCode: "SA", maxRoomCount: 2 }]);
}); });
test("rejects duplicated country rules", () => { test("rejects duplicated country rules", () => {

View File

@ -12,6 +12,21 @@ const countryCodeSchema = z
.trim() .trim()
.transform((value) => value.toUpperCase()) .transform((value) => value.toUpperCase())
.refine((value) => /^[A-Z]{2,3}$/.test(value), "国家码不正确"); .refine((value) => /^[A-Z]{2,3}$/.test(value), "国家码不正确");
const commaSeparatedTextListSchema = z
.union([z.string(), z.array(z.union([z.string(), z.number()]))])
.optional()
.default("")
.transform((value) => {
const raw = Array.isArray(value) ? value.join(",") : value;
return [
...new Set(
String(raw || "")
.split(",")
.map((item) => item.trim())
.filter(Boolean),
),
];
});
const pinTypeSchema = z const pinTypeSchema = z
.string() .string()
.trim() .trim()
@ -146,6 +161,7 @@ export const humanRoomRobotConfigSchema = z
countryRules: z countryRules: z
.array( .array(
z.object({ z.object({
allowedOwnerIds: commaSeparatedTextListSchema,
countryCode: countryCodeSchema, countryCode: countryCodeSchema,
maxRoomCount: z.coerce.number().int().min(1, "国家进房数量不正确"), maxRoomCount: z.coerce.number().int().min(1, "国家进房数量不正确"),
}), }),

View File

@ -3350,6 +3350,7 @@ export interface components {
userId: string; userId: string;
}; };
HumanRoomRobotCountryRule: { HumanRoomRobotCountryRule: {
allowedOwnerIds?: string[];
countryCode: string; countryCode: string;
maxRoomCount: number; maxRoomCount: number;
}; };

View File

@ -1061,6 +1061,7 @@ export interface HumanRoomRobotConfigDto {
} }
export interface HumanRoomRobotCountryRuleDto { export interface HumanRoomRobotCountryRuleDto {
allowedOwnerIds?: string[];
countryCode: string; countryCode: string;
maxRoomCount: number; maxRoomCount: number;
} }