822 lines
34 KiB
JavaScript
822 lines
34 KiB
JavaScript
import AddOutlined from "@mui/icons-material/AddOutlined";
|
|
import BedroomParentOutlined from "@mui/icons-material/BedroomParentOutlined";
|
|
import DeleteOutlineOutlined from "@mui/icons-material/DeleteOutlineOutlined";
|
|
import TuneOutlined from "@mui/icons-material/TuneOutlined";
|
|
import Autocomplete from "@mui/material/Autocomplete";
|
|
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 { AdminFormDialog } from "@/shared/ui/AdminFormDialog.jsx";
|
|
import { AdminUserIdentity } from "@/shared/ui/AdminUserIdentity.jsx";
|
|
import { DataState } from "@/shared/ui/DataState.jsx";
|
|
import { DataTable } from "@/shared/ui/DataTable.jsx";
|
|
import { AdminSwitch } from "@/shared/ui/AdminSwitch.jsx";
|
|
import { AdminFilterResetButton } from "@/shared/ui/AdminListLayout.jsx";
|
|
import { createOptionsColumnFilter } from "@/shared/ui/tableFilters.js";
|
|
import { formatMillis } from "@/shared/utils/time.js";
|
|
import {
|
|
giftId,
|
|
giftOptionLabel,
|
|
robotLocationLabel,
|
|
robotOptionLabel,
|
|
useRobotRoomsPage,
|
|
} from "@/features/rooms/hooks/useRobotRoomsPage.js";
|
|
import styles from "@/features/rooms/rooms.module.css";
|
|
|
|
const statusOptions = [
|
|
{ label: "运行中", value: "active" },
|
|
{ label: "已停止", value: "stopped" },
|
|
{ label: "全部状态", value: "all" },
|
|
];
|
|
|
|
const columns = [
|
|
{
|
|
key: "room",
|
|
label: "房间信息",
|
|
width: "minmax(280px, 1.4fr)",
|
|
render: (room) => <RobotRoomIdentity room={room} />,
|
|
},
|
|
{
|
|
key: "owner",
|
|
label: "房主机器人",
|
|
width: "minmax(220px, 1fr)",
|
|
render: (room) => <RobotIdentity user={room.owner} fallbackId={room.ownerRobotUserId} />,
|
|
},
|
|
{
|
|
key: "robots",
|
|
label: "机器人",
|
|
width: "minmax(150px, 0.6fr)",
|
|
render: (room) => (
|
|
<RuleStack
|
|
rows={[
|
|
`活跃 ${Number(room.activeRobotCount || 0).toLocaleString("zh-CN")}`,
|
|
`候选 ${Number(room.robotUserIds?.length || room.robots?.length || 0).toLocaleString("zh-CN")}`,
|
|
]}
|
|
/>
|
|
),
|
|
},
|
|
{
|
|
key: "normalRule",
|
|
label: "普通礼物",
|
|
width: "minmax(190px, 0.8fr)",
|
|
render: (room) => (
|
|
<RuleStack
|
|
rows={[
|
|
`${room.giftRule?.giftIds?.length || 0} 个礼物`,
|
|
`${formatSeconds(room.giftRule?.normalGiftIntervalMs)} / 次`,
|
|
]}
|
|
/>
|
|
),
|
|
},
|
|
{
|
|
key: "luckyRule",
|
|
label: "幸运礼物",
|
|
width: "minmax(220px, 0.9fr)",
|
|
render: (room) => (
|
|
<RuleStack
|
|
rows={[
|
|
`${room.giftRule?.luckyGiftIds?.length || 0} 个礼物`,
|
|
`${room.giftRule?.luckyComboMin || 0}-${room.giftRule?.luckyComboMax || 0} 连击`,
|
|
`${formatSeconds(room.giftRule?.luckyPauseMinMs)}-${formatSeconds(room.giftRule?.luckyPauseMaxMs)} 间隔`,
|
|
]}
|
|
/>
|
|
),
|
|
},
|
|
{
|
|
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",
|
|
label: "创建时间",
|
|
width: "minmax(170px, 0.8fr)",
|
|
render: (room) => formatMillis(room.createdAtMs),
|
|
},
|
|
];
|
|
|
|
export function RoomRobotPage() {
|
|
const page = useRobotRoomsPage();
|
|
const items = page.data.items || [];
|
|
const total = page.data.total || 0;
|
|
const tableColumns = [
|
|
...columns.map((column) =>
|
|
column.key === "createdAt"
|
|
? {
|
|
...column,
|
|
filter: createOptionsColumnFilter({
|
|
options: statusOptions,
|
|
placeholder: "房间状态",
|
|
value: page.status,
|
|
onChange: page.changeStatus,
|
|
}),
|
|
}
|
|
: column,
|
|
),
|
|
{
|
|
key: "status",
|
|
label: "状态",
|
|
width: "minmax(112px, 0.5fr)",
|
|
render: (room) => <RobotRoomStatus page={page} room={room} />,
|
|
},
|
|
];
|
|
|
|
return (
|
|
<section className={styles.root}>
|
|
<div className={styles.contentPanel}>
|
|
<div className={styles.toolbar}>
|
|
<section className={styles.filters}>
|
|
<AdminFilterResetButton disabled={page.status === "active"} onClick={page.resetFilters} />
|
|
</section>
|
|
{page.abilities.canRobotCreate ? (
|
|
<div className={styles.toolbarActions}>
|
|
<Button
|
|
startIcon={<TuneOutlined fontSize="small" />}
|
|
variant="secondary"
|
|
onClick={page.openHumanConfig}
|
|
>
|
|
真人房间机器人配置
|
|
</Button>
|
|
<Button
|
|
startIcon={<AddOutlined fontSize="small" />}
|
|
variant="primary"
|
|
onClick={page.openCreate}
|
|
>
|
|
增加机器人房间
|
|
</Button>
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
<HumanConfigPanel page={page} />
|
|
|
|
<DataState error={page.error} loading={page.loading} onRetry={page.reload}>
|
|
<div className={styles.listBlock}>
|
|
<DataTable
|
|
columns={tableColumns}
|
|
items={items}
|
|
minWidth="1320px"
|
|
rowKey={(room) => room.roomId}
|
|
pagination={
|
|
total > 0
|
|
? {
|
|
page: page.page,
|
|
pageSize: page.data.pageSize || 50,
|
|
total,
|
|
onPageChange: page.setPage,
|
|
}
|
|
: undefined
|
|
}
|
|
/>
|
|
</div>
|
|
</DataState>
|
|
</div>
|
|
|
|
<AdminFormDialog
|
|
loading={page.loadingAction === "create"}
|
|
open={page.activeAction === "create"}
|
|
size="wide"
|
|
submitDisabled={!page.abilities.canRobotCreate || page.loadingAction === "create"}
|
|
title="增加机器人房间"
|
|
onClose={page.closeAction}
|
|
onSubmit={page.submitCreate}
|
|
>
|
|
<Autocomplete
|
|
getOptionLabel={robotOptionLabel}
|
|
isOptionEqualToValue={(option, value) => option.userId === value.userId}
|
|
loading={page.availableRobotsLoading}
|
|
noOptionsText="当前无数据"
|
|
options={page.availableRobots}
|
|
renderInput={(params) => (
|
|
<TextField
|
|
{...params}
|
|
error={Boolean(page.availableRobotsError)}
|
|
helperText={page.availableRobotsError}
|
|
label="房主机器人"
|
|
required
|
|
/>
|
|
)}
|
|
renderOption={(props, option) => (
|
|
<li {...props} key={option.userId}>
|
|
<OwnerRobotOption
|
|
countryLabels={page.robotCountryLabels}
|
|
loadingLocation={page.loadingRobotLocationOptions}
|
|
option={option}
|
|
regionLabels={page.robotRegionLabels}
|
|
/>
|
|
</li>
|
|
)}
|
|
size="small"
|
|
value={page.selectedOwnerRobot}
|
|
onChange={(_, option) => page.selectOwnerRobot(option)}
|
|
/>
|
|
<Autocomplete
|
|
multiple
|
|
disableCloseOnSelect
|
|
getOptionLabel={robotOptionLabel}
|
|
isOptionEqualToValue={(option, value) => option.userId === value.userId}
|
|
loading={page.availableRobotsLoading}
|
|
noOptionsText="当前无数据"
|
|
options={page.candidateRobotOptions}
|
|
renderInput={(params) => (
|
|
<TextField
|
|
{...params}
|
|
error={Boolean(page.availableRobotsError)}
|
|
helperText={page.availableRobotsError}
|
|
label={<RequiredLabel>候选机器人</RequiredLabel>}
|
|
/>
|
|
)}
|
|
renderOption={(props, option) => (
|
|
<li {...props} key={option.userId}>
|
|
<RobotIdentity openInAppUserDetail={false} user={option.user} fallbackId={option.userId} />
|
|
</li>
|
|
)}
|
|
size="small"
|
|
value={page.selectedCandidateRobots}
|
|
onChange={(_, options) => page.selectCandidateRobots(options)}
|
|
/>
|
|
<div className={styles.formGrid}>
|
|
<TextField
|
|
label="最少进房人数"
|
|
required
|
|
type="number"
|
|
value={page.form.minRobotCount}
|
|
onChange={(event) => page.setForm({ ...page.form, minRobotCount: event.target.value })}
|
|
/>
|
|
<TextField
|
|
label="最多进房人数"
|
|
required
|
|
type="number"
|
|
value={page.form.maxRobotCount}
|
|
onChange={(event) => page.setForm({ ...page.form, maxRobotCount: event.target.value })}
|
|
/>
|
|
</div>
|
|
<Autocomplete
|
|
multiple
|
|
disableCloseOnSelect
|
|
getOptionLabel={giftOptionLabel}
|
|
isOptionEqualToValue={(option, value) => giftId(option) === giftId(value)}
|
|
loading={page.giftOptionsLoading}
|
|
noOptionsText="当前无数据"
|
|
options={page.giftOptions}
|
|
renderInput={(params) => (
|
|
<TextField
|
|
{...params}
|
|
error={Boolean(page.giftOptionsError)}
|
|
helperText={page.giftOptionsError}
|
|
label={<RequiredLabel>礼物合集</RequiredLabel>}
|
|
/>
|
|
)}
|
|
size="small"
|
|
value={page.selectedGiftOptions}
|
|
onChange={(_, options) => page.selectGiftIds("giftIds", options)}
|
|
/>
|
|
<TextField
|
|
label="普通礼物频次(毫秒)"
|
|
required
|
|
type="number"
|
|
value={page.form.normalGiftIntervalMs}
|
|
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
|
|
multiple
|
|
disableCloseOnSelect
|
|
getOptionLabel={giftOptionLabel}
|
|
isOptionEqualToValue={(option, value) => giftId(option) === giftId(value)}
|
|
loading={page.giftOptionsLoading}
|
|
noOptionsText="当前无数据"
|
|
options={page.giftOptions}
|
|
renderInput={(params) => (
|
|
<TextField
|
|
{...params}
|
|
error={Boolean(page.giftOptionsError)}
|
|
helperText={page.giftOptionsError}
|
|
label={<RequiredLabel>幸运礼物合集</RequiredLabel>}
|
|
/>
|
|
)}
|
|
size="small"
|
|
value={page.selectedLuckyGiftOptions}
|
|
onChange={(_, options) => page.selectGiftIds("luckyGiftIds", options)}
|
|
/>
|
|
<div className={styles.formGrid}>
|
|
<TextField
|
|
label="最少幸运连击"
|
|
required
|
|
type="number"
|
|
value={page.form.luckyComboMin}
|
|
onChange={(event) => page.setForm({ ...page.form, luckyComboMin: event.target.value })}
|
|
/>
|
|
<TextField
|
|
label="最多幸运连击"
|
|
required
|
|
type="number"
|
|
value={page.form.luckyComboMax}
|
|
onChange={(event) => page.setForm({ ...page.form, luckyComboMax: event.target.value })}
|
|
/>
|
|
<TextField
|
|
label="最短幸运间隔(毫秒)"
|
|
required
|
|
type="number"
|
|
value={page.form.luckyPauseMinMs}
|
|
onChange={(event) => page.setForm({ ...page.form, luckyPauseMinMs: event.target.value })}
|
|
/>
|
|
<TextField
|
|
label="最长幸运间隔(毫秒)"
|
|
required
|
|
type="number"
|
|
value={page.form.luckyPauseMaxMs}
|
|
onChange={(event) => page.setForm({ ...page.form, luckyPauseMaxMs: event.target.value })}
|
|
/>
|
|
</div>
|
|
</AdminFormDialog>
|
|
<HumanConfigDialog page={page} />
|
|
</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 ? (
|
|
<div className={styles.humanConfigActions}>
|
|
<Button
|
|
loading={page.humanConfigLoading}
|
|
startIcon={<TuneOutlined fontSize="small" />}
|
|
variant="secondary"
|
|
onClick={page.openHumanCountryConfig}
|
|
>
|
|
{config.countryLimitEnabled ? "国家配置" : "国家启用"}
|
|
</Button>
|
|
<Button
|
|
loading={page.humanConfigLoading}
|
|
startIcon={<TuneOutlined fontSize="small" />}
|
|
variant="secondary"
|
|
onClick={page.openHumanConfig}
|
|
>
|
|
配置
|
|
</Button>
|
|
</div>
|
|
) : 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>
|
|
<label className={styles.switchField}>
|
|
<AdminSwitch
|
|
checked={Boolean(page.humanConfigForm.countryLimitEnabled)}
|
|
checkedLabel="启用"
|
|
label="启用国家配置"
|
|
uncheckedLabel="关闭"
|
|
onChange={(_, checked) => page.setHumanCountryLimitEnabled(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)}
|
|
/>
|
|
{page.humanConfigForm.countryLimitEnabled ? <HumanCountryRules page={page} /> : null}
|
|
<section className={styles.robotAutoPoolNotice}>
|
|
<div className={styles.primaryText}>机器人池自动获取</div>
|
|
<div className={styles.meta}>
|
|
{page.humanConfigForm.countryLimitEnabled
|
|
? "国家配置启用后,只会读取已配置国家的机器人,并限制每个国家同时进入的真人房数量。"
|
|
: "系统会读取所有启用的全站机器人,并按机器人账号国家进入同国家真人房间。"}
|
|
</div>
|
|
</section>
|
|
</AdminFormDialog>
|
|
);
|
|
}
|
|
|
|
function HumanCountryRules({ page }) {
|
|
const rows = page.humanConfigForm.countryRules || [];
|
|
return (
|
|
<section className={styles.countryRulesSection}>
|
|
<div className={styles.countryRulesHeader}>
|
|
<div className={styles.primaryText}>国家进房配置</div>
|
|
<Button
|
|
startIcon={<AddOutlined fontSize="small" />}
|
|
variant="secondary"
|
|
onClick={page.addHumanCountryRule}
|
|
>
|
|
添加国家
|
|
</Button>
|
|
</div>
|
|
<div className={styles.countryRuleRows}>
|
|
{rows.map((rule, index) => (
|
|
<div className={styles.countryRuleRow} key={`${rule.countryCode || "country"}-${index}`}>
|
|
<TextField
|
|
label="国家"
|
|
required
|
|
select
|
|
size="small"
|
|
value={rule.countryCode || ""}
|
|
onChange={(event) =>
|
|
page.updateHumanCountryRule(index, { countryCode: event.target.value })
|
|
}
|
|
>
|
|
{countrySelectOptions(page.countryOptions, rule.countryCode).map((option) => (
|
|
<MenuItem key={option.value} value={option.value}>
|
|
{option.label}
|
|
</MenuItem>
|
|
))}
|
|
</TextField>
|
|
<TextField
|
|
label="进房数量"
|
|
required
|
|
size="small"
|
|
type="number"
|
|
value={rule.maxRoomCount}
|
|
onChange={(event) =>
|
|
page.updateHumanCountryRule(index, { maxRoomCount: event.target.value })
|
|
}
|
|
/>
|
|
<Button
|
|
startIcon={<DeleteOutlineOutlined fontSize="small" />}
|
|
variant="secondary"
|
|
onClick={() => page.removeHumanCountryRule(index)}
|
|
>
|
|
删除
|
|
</Button>
|
|
</div>
|
|
))}
|
|
{rows.length === 0 ? <div className={styles.meta}>当前没有国家配置</div> : null}
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|
|
|
|
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 }) {
|
|
return (
|
|
<div className={styles.identity}>
|
|
<span className={styles.cover}>
|
|
{room.coverUrl ? <img src={room.coverUrl} alt="" /> : <BedroomParentOutlined fontSize="small" />}
|
|
</span>
|
|
<div className={styles.identityText}>
|
|
<div className={styles.name}>{room.title || "-"}</div>
|
|
<div className={styles.meta}>长ID {room.roomId || "-"}</div>
|
|
<div className={styles.meta}>短ID {room.roomShortId || "-"}</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function RobotIdentity({ fallbackId = "", openInAppUserDetail = true, user = {} }) {
|
|
return (
|
|
<AdminUserIdentity
|
|
openInAppUserDetail={openInAppUserDetail}
|
|
rows={[user.displayUserId, user.userId || fallbackId]}
|
|
user={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 }) {
|
|
return (
|
|
<span>
|
|
{children} <span aria-hidden="true">*</span>
|
|
</span>
|
|
);
|
|
}
|
|
|
|
function countrySelectOptions(options = [], currentValue = "") {
|
|
const current = String(currentValue || "").toUpperCase();
|
|
const merged = options.map((option) => ({
|
|
label: option.label,
|
|
value: option.value,
|
|
}));
|
|
if (current && !merged.some((option) => option.value === current)) {
|
|
merged.unshift({ label: current, value: current });
|
|
}
|
|
if (!merged.length) {
|
|
return current ? [{ label: current, value: current }] : [{ label: "暂无可用国家", value: "" }];
|
|
}
|
|
return merged;
|
|
}
|
|
|
|
function RobotRoomStatus({ page, room }) {
|
|
const checked = room.status === "active";
|
|
return (
|
|
<div className={styles.statusCell}>
|
|
<Tooltip arrow title={checked ? "停止" : "启动"}>
|
|
<span>
|
|
<AdminSwitch
|
|
checked={checked}
|
|
checkedLabel="运行"
|
|
disabled={!page.abilities.canRobotUpdate || page.loadingAction === `status-${room.roomId}`}
|
|
label={checked ? "停止机器人房间" : "启动机器人房间"}
|
|
uncheckedLabel="停止"
|
|
onChange={() => page.setRoomStatus(room)}
|
|
/>
|
|
</span>
|
|
</Tooltip>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function RuleStack({ rows }) {
|
|
return (
|
|
<div className={styles.stack}>
|
|
{rows.map((row) => (
|
|
<span className={styles.meta} key={row}>
|
|
{row}
|
|
</span>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function formatSeconds(ms) {
|
|
const seconds = Math.round(Number(ms || 0) / 1000);
|
|
return `${seconds.toLocaleString("zh-CN")}秒`;
|
|
}
|
|
|
|
function formatDurationRange(minMs, maxMs) {
|
|
return `${formatSeconds(minMs)}-${formatSeconds(maxMs)}`;
|
|
}
|