机器人进真人房配置

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",
"required": ["countryCode", "maxRoomCount"],
"properties": {
"allowedOwnerIds": {
"items": {
"type": "string"
},
"type": "array"
},
"countryCode": {
"type": "string"
},

View File

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

View File

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

View File

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

View File

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

View File

@ -12,6 +12,21 @@ const countryCodeSchema = z
.trim()
.transform((value) => value.toUpperCase())
.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
.string()
.trim()
@ -146,6 +161,7 @@ export const humanRoomRobotConfigSchema = z
countryRules: z
.array(
z.object({
allowedOwnerIds: commaSeparatedTextListSchema,
countryCode: countryCodeSchema,
maxRoomCount: z.coerce.number().int().min(1, "国家进房数量不正确"),
}),

View File

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

View File

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