feat(自动建群): 添加内容库选择功能并重构表单为分步式
添加内容库选择组件及相关类型定义 重构自动建群表单为分步式操作流程 新增基础设置、设备选择和内容选择三个步骤组件 完善表单验证逻辑和步骤导航功能
This commit is contained in:
@@ -0,0 +1,291 @@
|
||||
import React, { useImperativeHandle, forwardRef } from "react";
|
||||
import { Input, Button, Card, Switch, Form, InputNumber } from "antd";
|
||||
import { TextArea } from "antd-mobile";
|
||||
|
||||
interface BasicSettingsProps {
|
||||
defaultValues?: {
|
||||
name: string;
|
||||
startTime: string;
|
||||
endTime: string;
|
||||
groupSizeMin: number;
|
||||
groupSizeMax: number;
|
||||
maxGroupsPerDay: number;
|
||||
groupNameTemplate: string;
|
||||
groupDescription: string;
|
||||
status: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface BasicSettingsRef {
|
||||
validate: () => Promise<boolean>;
|
||||
getValues: () => any;
|
||||
}
|
||||
|
||||
const BasicSettings = forwardRef<BasicSettingsRef, BasicSettingsProps>(
|
||||
({
|
||||
defaultValues = {
|
||||
name: "",
|
||||
startTime: "06:00",
|
||||
endTime: "23:59",
|
||||
groupSizeMin: 20,
|
||||
groupSizeMax: 50,
|
||||
maxGroupsPerDay: 10,
|
||||
groupNameTemplate: "",
|
||||
groupDescription: "",
|
||||
status: 1,
|
||||
},
|
||||
}, ref) => {
|
||||
const [form] = Form.useForm();
|
||||
|
||||
// 暴露方法给父组件
|
||||
useImperativeHandle(ref, () => ({
|
||||
validate: async () => {
|
||||
try {
|
||||
await form.validateFields();
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.log("BasicSettings 表单验证失败:", error);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
getValues: () => {
|
||||
return form.getFieldsValue();
|
||||
},
|
||||
}));
|
||||
|
||||
return (
|
||||
<div style={{ marginBottom: 24 }}>
|
||||
<Card>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
initialValues={defaultValues}
|
||||
onValuesChange={(changedValues, allValues) => {
|
||||
// 可以在这里处理表单值变化
|
||||
}}
|
||||
>
|
||||
{/* 任务名称 */}
|
||||
<Form.Item
|
||||
label="任务名称"
|
||||
name="name"
|
||||
rules={[
|
||||
{ required: true, message: "请输入任务名称" },
|
||||
{ min: 2, max: 50, message: "任务名称长度在2-50个字符之间" },
|
||||
]}
|
||||
>
|
||||
<Input placeholder="请输入任务名称" />
|
||||
</Form.Item>
|
||||
|
||||
{/* 允许建群的时间段 */}
|
||||
<Form.Item label="允许建群的时间段">
|
||||
<div style={{ display: "flex", gap: 8, alignItems: "center" }}>
|
||||
<Form.Item
|
||||
name="startTime"
|
||||
noStyle
|
||||
rules={[{ required: true, message: "请选择开始时间" }]}
|
||||
>
|
||||
<Input type="time" style={{ width: 120 }} />
|
||||
</Form.Item>
|
||||
<span style={{ color: "#888" }}>至</span>
|
||||
<Form.Item
|
||||
name="endTime"
|
||||
noStyle
|
||||
rules={[{ required: true, message: "请选择结束时间" }]}
|
||||
>
|
||||
<Input type="time" style={{ width: 120 }} />
|
||||
</Form.Item>
|
||||
</div>
|
||||
</Form.Item>
|
||||
|
||||
{/* 每日最大建群数 */}
|
||||
<Form.Item
|
||||
label="每日最大建群数"
|
||||
name="maxGroupsPerDay"
|
||||
rules={[
|
||||
{ required: true, message: "请输入每日最大建群数" },
|
||||
{
|
||||
type: "number",
|
||||
min: 1,
|
||||
max: 100,
|
||||
message: "每日最大建群数在1-100之间",
|
||||
},
|
||||
]}
|
||||
>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
|
||||
<Button
|
||||
htmlType="button"
|
||||
onClick={() => {
|
||||
const currentValue = form.getFieldValue("maxGroupsPerDay") || 1;
|
||||
const newValue = Math.max(1, currentValue - 1);
|
||||
form.setFieldValue("maxGroupsPerDay", newValue);
|
||||
}}
|
||||
>
|
||||
-
|
||||
</Button>
|
||||
<InputNumber
|
||||
min={1}
|
||||
max={100}
|
||||
placeholder="请输入最大建群数"
|
||||
step={1}
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
<Button
|
||||
htmlType="button"
|
||||
onClick={() => {
|
||||
const currentValue = form.getFieldValue("maxGroupsPerDay") || 1;
|
||||
const newValue = Math.min(100, currentValue + 1);
|
||||
form.setFieldValue("maxGroupsPerDay", newValue);
|
||||
}}
|
||||
>
|
||||
+
|
||||
</Button>
|
||||
</div>
|
||||
</Form.Item>
|
||||
|
||||
{/* 群组最小人数 */}
|
||||
<Form.Item
|
||||
label="群组最小人数"
|
||||
name="groupSizeMin"
|
||||
rules={[
|
||||
{ required: true, message: "请输入群组最小人数" },
|
||||
{
|
||||
type: "number",
|
||||
min: 1,
|
||||
max: 500,
|
||||
message: "群组最小人数在1-500之间",
|
||||
},
|
||||
]}
|
||||
>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
|
||||
<Button
|
||||
htmlType="button"
|
||||
onClick={() => {
|
||||
const currentValue = form.getFieldValue("groupSizeMin") || 1;
|
||||
const maxValue = form.getFieldValue("groupSizeMax") || 500;
|
||||
const newValue = Math.max(1, currentValue - 1);
|
||||
form.setFieldValue("groupSizeMin", Math.min(newValue, maxValue));
|
||||
}}
|
||||
>
|
||||
-
|
||||
</Button>
|
||||
<InputNumber
|
||||
min={1}
|
||||
max={500}
|
||||
placeholder="请输入最小人数"
|
||||
step={1}
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
<Button
|
||||
htmlType="button"
|
||||
onClick={() => {
|
||||
const currentValue = form.getFieldValue("groupSizeMin") || 1;
|
||||
const newValue = Math.min(500, currentValue + 1);
|
||||
form.setFieldValue("groupSizeMin", newValue);
|
||||
}}
|
||||
>
|
||||
+
|
||||
</Button>
|
||||
</div>
|
||||
</Form.Item>
|
||||
|
||||
{/* 群组最大人数 */}
|
||||
<Form.Item
|
||||
label="群组最大人数"
|
||||
name="groupSizeMax"
|
||||
rules={[
|
||||
{ required: true, message: "请输入群组最大人数" },
|
||||
{
|
||||
type: "number",
|
||||
min: 1,
|
||||
max: 500,
|
||||
message: "群组最大人数在1-500之间",
|
||||
},
|
||||
]}
|
||||
>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
|
||||
<Button
|
||||
htmlType="button"
|
||||
onClick={() => {
|
||||
const currentValue = form.getFieldValue("groupSizeMax") || 1;
|
||||
const newValue = Math.max(1, currentValue - 1);
|
||||
form.setFieldValue("groupSizeMax", newValue);
|
||||
}}
|
||||
>
|
||||
-
|
||||
</Button>
|
||||
<InputNumber
|
||||
min={1}
|
||||
max={500}
|
||||
placeholder="请输入最大人数"
|
||||
step={1}
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
<Button
|
||||
htmlType="button"
|
||||
onClick={() => {
|
||||
const currentValue = form.getFieldValue("groupSizeMax") || 1;
|
||||
const minValue = form.getFieldValue("groupSizeMin") || 1;
|
||||
const newValue = Math.min(500, currentValue + 1);
|
||||
form.setFieldValue("groupSizeMax", Math.max(newValue, minValue));
|
||||
}}
|
||||
>
|
||||
+
|
||||
</Button>
|
||||
</div>
|
||||
</Form.Item>
|
||||
|
||||
{/* 群名称模板 */}
|
||||
<Form.Item
|
||||
label="群名称模板"
|
||||
name="groupNameTemplate"
|
||||
rules={[
|
||||
{ required: true, message: "请输入群名称模板" },
|
||||
{ min: 2, max: 100, message: "群名称模板长度在2-100个字符之间" },
|
||||
]}
|
||||
>
|
||||
<Input placeholder="请输入群名称模板" />
|
||||
</Form.Item>
|
||||
|
||||
{/* 群描述 */}
|
||||
<Form.Item
|
||||
label="群描述"
|
||||
name="groupDescription"
|
||||
rules={[{ max: 200, message: "群描述不能超过200个字符" }]}
|
||||
>
|
||||
<TextArea
|
||||
placeholder="请输入群描述"
|
||||
rows={3}
|
||||
maxLength={200}
|
||||
showCount
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
{/* 是否启用 */}
|
||||
<Form.Item
|
||||
label="是否启用"
|
||||
name="status"
|
||||
valuePropName="checked"
|
||||
getValueFromEvent={(checked) => checked ? 1 : 0}
|
||||
getValueProps={(value) => ({ checked: value === 1 })}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<span>状态</span>
|
||||
<Switch />
|
||||
</div>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
BasicSettings.displayName = "BasicSettings";
|
||||
|
||||
export default BasicSettings;
|
||||
@@ -0,0 +1,95 @@
|
||||
import React, { useImperativeHandle, forwardRef } from "react";
|
||||
import { Form, Card } from "antd";
|
||||
import ContentSelection from "@/components/ContentSelection";
|
||||
import { ContentItem } from "@/components/ContentSelection/data";
|
||||
|
||||
interface ContentSelectorProps {
|
||||
selectedContent: ContentItem[];
|
||||
onNext: (data: {
|
||||
contentGroups: string[];
|
||||
contentGroupsOptions: ContentItem[];
|
||||
}) => void;
|
||||
}
|
||||
|
||||
export interface ContentSelectorRef {
|
||||
validate: () => Promise<boolean>;
|
||||
getValues: () => any;
|
||||
}
|
||||
|
||||
const ContentSelector = forwardRef<ContentSelectorRef, ContentSelectorProps>(
|
||||
({ selectedContent, onNext }, ref) => {
|
||||
const [form] = Form.useForm();
|
||||
|
||||
// 暴露方法给父组件
|
||||
useImperativeHandle(ref, () => ({
|
||||
validate: async () => {
|
||||
try {
|
||||
await form.validateFields();
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.log("ContentSelector 表单验证失败:", error);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
getValues: () => {
|
||||
return form.getFieldsValue();
|
||||
},
|
||||
}));
|
||||
|
||||
// 处理选择变化
|
||||
const handleContentChange = (contentGroupsOptions: ContentItem[]) => {
|
||||
const contentGroups = contentGroupsOptions.map(c => c.id.toString());
|
||||
form.setFieldValue("contentGroups", contentGroups);
|
||||
onNext({
|
||||
contentGroups,
|
||||
contentGroupsOptions,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ marginBottom: 24 }}>
|
||||
<Card>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
initialValues={{
|
||||
contentGroups: selectedContent.map(c => c.id.toString()),
|
||||
}}
|
||||
>
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<h2 style={{ margin: 0, fontSize: 18, fontWeight: 600 }}>
|
||||
选择内容库
|
||||
</h2>
|
||||
<p style={{ margin: "8px 0 0 0", color: "#666", fontSize: 14 }}>
|
||||
请选择要用于建群的内容库
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Form.Item
|
||||
name="contentGroups"
|
||||
rules={[
|
||||
{ required: true, message: "请选择至少一个内容库" },
|
||||
{ type: "array", min: 1, message: "请选择至少一个内容库" },
|
||||
{ type: "array", max: 20, message: "最多只能选择20个内容库" },
|
||||
]}
|
||||
>
|
||||
<ContentSelection
|
||||
selectedOptions={selectedContent}
|
||||
onSelect={handleContentChange}
|
||||
placeholder="选择内容库"
|
||||
showInput={true}
|
||||
showSelectedList={true}
|
||||
readonly={false}
|
||||
selectedListMaxHeight={320}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
ContentSelector.displayName = "ContentSelector";
|
||||
|
||||
export default ContentSelector;
|
||||
@@ -0,0 +1,95 @@
|
||||
import React, { useImperativeHandle, forwardRef } from "react";
|
||||
import { Form, Card } from "antd";
|
||||
import DeviceSelection from "@/components/DeviceSelection";
|
||||
import { DeviceSelectionItem } from "@/components/DeviceSelection/data";
|
||||
|
||||
interface DeviceSelectorProps {
|
||||
selectedDevices: DeviceSelectionItem[];
|
||||
onNext: (data: {
|
||||
deveiceGroups: string[];
|
||||
deveiceGroupsOptions: DeviceSelectionItem[];
|
||||
}) => void;
|
||||
}
|
||||
|
||||
export interface DeviceSelectorRef {
|
||||
validate: () => Promise<boolean>;
|
||||
getValues: () => any;
|
||||
}
|
||||
|
||||
const DeviceSelector = forwardRef<DeviceSelectorRef, DeviceSelectorProps>(
|
||||
({ selectedDevices, onNext }, ref) => {
|
||||
const [form] = Form.useForm();
|
||||
|
||||
// 暴露方法给父组件
|
||||
useImperativeHandle(ref, () => ({
|
||||
validate: async () => {
|
||||
try {
|
||||
await form.validateFields();
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.log("DeviceSelector 表单验证失败:", error);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
getValues: () => {
|
||||
return form.getFieldsValue();
|
||||
},
|
||||
}));
|
||||
|
||||
// 设备选择
|
||||
const handleDeviceSelect = (
|
||||
deveiceGroupsOptions: DeviceSelectionItem[],
|
||||
) => {
|
||||
const deveiceGroups = deveiceGroupsOptions.map(item => item.id);
|
||||
form.setFieldValue("deveiceGroups", deveiceGroups);
|
||||
// 通知父组件数据变化
|
||||
onNext({
|
||||
deveiceGroups: deveiceGroups.map(id => String(id)),
|
||||
deveiceGroupsOptions,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
initialValues={{
|
||||
deveiceGroups: selectedDevices.map(item => item.id),
|
||||
}}
|
||||
>
|
||||
<div style={{ marginBottom: 20 }}>
|
||||
<h2 style={{ margin: 0, fontSize: 18, fontWeight: 600 }}>
|
||||
选择设备组
|
||||
</h2>
|
||||
<p style={{ margin: "8px 0 0 0", color: "#666", fontSize: 14 }}>
|
||||
请选择要用于建群的设备组
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Form.Item
|
||||
name="deveiceGroups"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
type: "array",
|
||||
min: 1,
|
||||
message: "请选择至少一个设备组",
|
||||
},
|
||||
{ type: "array", max: 20, message: "最多只能选择20个设备组" },
|
||||
]}
|
||||
>
|
||||
<DeviceSelection
|
||||
selectedOptions={selectedDevices}
|
||||
onSelect={handleDeviceSelect}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Card>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
DeviceSelector.displayName = "DeviceSelector";
|
||||
|
||||
export default DeviceSelector;
|
||||
@@ -1,21 +1,34 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { Form, Toast, TextArea } from "antd-mobile";
|
||||
import { Input, InputNumber, Button, Switch } from "antd";
|
||||
import { Toast } from "antd-mobile";
|
||||
import { Button } from "antd";
|
||||
import Layout from "@/components/Layout/Layout";
|
||||
import style from "./index.module.scss";
|
||||
import { createAutoGroup, updateAutoGroup, getAutoGroupDetail } from "./api";
|
||||
import { AutoGroupFormData } from "./types";
|
||||
import DeviceSelection from "@/components/DeviceSelection/index";
|
||||
import { AutoGroupFormData, StepItem } from "./types";
|
||||
import StepIndicator from "@/components/StepIndicator";
|
||||
import BasicSettings, { BasicSettingsRef } from "./components/BasicSettings";
|
||||
import DeviceSelector, { DeviceSelectorRef } from "./components/DeviceSelector";
|
||||
import ContentSelector, {
|
||||
ContentSelectorRef,
|
||||
} from "./components/ContentSelector";
|
||||
import NavCommon from "@/components/NavCommon/index";
|
||||
import dayjs from "dayjs";
|
||||
import { DeviceSelectionItem } from "@/components/DeviceSelection/data";
|
||||
import { ContentItem } from "@/components/ContentSelection/data";
|
||||
|
||||
const steps: StepItem[] = [
|
||||
{ id: 1, title: "步骤 1", subtitle: "基础设置" },
|
||||
{ id: 2, title: "步骤 2", subtitle: "选择设备" },
|
||||
{ id: 3, title: "步骤 3", subtitle: "选择内容库" },
|
||||
];
|
||||
|
||||
const defaultForm: AutoGroupFormData = {
|
||||
name: "",
|
||||
type: 4,
|
||||
deveiceGroups: [], // 设备组
|
||||
deveiceGroupsOptions: [], // 设备组选项
|
||||
contentGroups: [], // 内容库
|
||||
contentGroupsOptions: [], // 内容库选项
|
||||
startTime: dayjs().format("HH:mm"), // 开始时间 (HH:mm)
|
||||
endTime: dayjs().add(1, "hour").format("HH:mm"), // 结束时间 (HH:mm)
|
||||
groupSizeMin: 20, // 群组最小人数
|
||||
@@ -30,17 +43,32 @@ const AutoGroupForm: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
const { id } = useParams();
|
||||
const isEdit = Boolean(id);
|
||||
const [form, setForm] = useState<AutoGroupFormData>(defaultForm);
|
||||
const [currentStep, setCurrentStep] = useState(1);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [formData, setFormData] = useState<AutoGroupFormData>(defaultForm);
|
||||
const [deviceGroupsOptions, setDeviceGroupsOptions] = useState<
|
||||
DeviceSelectionItem[]
|
||||
>([]);
|
||||
const [contentGroupsOptions, setContentGroupsOptions] = useState<
|
||||
ContentItem[]
|
||||
>([]);
|
||||
|
||||
// 创建子组件的ref
|
||||
const basicSettingsRef = useRef<BasicSettingsRef>(null);
|
||||
const deviceSelectorRef = useRef<DeviceSelectorRef>(null);
|
||||
const contentSelectorRef = useRef<ContentSelectorRef>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!id) return;
|
||||
// 这里应请求详情接口,回填表单,演示用mock
|
||||
getAutoGroupDetail(id).then(res => {
|
||||
setForm({
|
||||
const updatedForm = {
|
||||
...defaultForm,
|
||||
name: res.name,
|
||||
deveiceGroups: res.config.deveiceGroups || [],
|
||||
deveiceGroupsOptions: res.config.deveiceGroupsOptions || [],
|
||||
contentGroups: res.config.contentGroups || [],
|
||||
contentGroupsOptions: res.config.contentGroupsOptions || [],
|
||||
startTime: res.config.startTime,
|
||||
endTime: res.config.endTime,
|
||||
groupSizeMin: res.config.groupSizeMin,
|
||||
@@ -51,19 +79,65 @@ const AutoGroupForm: React.FC = () => {
|
||||
status: res.status,
|
||||
type: res.type,
|
||||
id: res.id,
|
||||
});
|
||||
console.log(form);
|
||||
};
|
||||
setFormData(updatedForm);
|
||||
setDeviceGroupsOptions(res.config.deveiceGroupsOptions || []);
|
||||
setContentGroupsOptions(res.config.contentGroupsOptions || []);
|
||||
});
|
||||
}, [id]);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
const handleBasicSettingsChange = (values: Partial<AutoGroupFormData>) => {
|
||||
setFormData(prev => ({ ...prev, ...values }));
|
||||
};
|
||||
|
||||
// 设备组选择
|
||||
const handleDevicesChange = (data: {
|
||||
deveiceGroups: string[];
|
||||
deveiceGroupsOptions: DeviceSelectionItem[];
|
||||
}) => {
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
deveiceGroups: data.deveiceGroups,
|
||||
}));
|
||||
setDeviceGroupsOptions(data.deveiceGroupsOptions);
|
||||
};
|
||||
|
||||
// 内容库选择
|
||||
const handleContentChange = (data: {
|
||||
contentGroups: string[];
|
||||
contentGroupsOptions: ContentItem[];
|
||||
}) => {
|
||||
setFormData(prev => ({ ...prev, contentGroups: data.contentGroups }));
|
||||
setContentGroupsOptions(data.contentGroupsOptions);
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!formData.name.trim()) {
|
||||
Toast.show({ content: "请输入任务名称" });
|
||||
return;
|
||||
}
|
||||
if (formData.deveiceGroups.length === 0) {
|
||||
Toast.show({ content: "请选择至少一个设备组" });
|
||||
return;
|
||||
}
|
||||
if (formData.contentGroups.length === 0) {
|
||||
Toast.show({ content: "请选择至少一个内容库" });
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
const submitData = {
|
||||
...formData,
|
||||
deveiceGroupsOptions: deviceGroupsOptions,
|
||||
contentGroupsOptions: contentGroupsOptions,
|
||||
};
|
||||
|
||||
if (isEdit) {
|
||||
await updateAutoGroup(form);
|
||||
await updateAutoGroup(submitData);
|
||||
Toast.show({ content: "编辑成功" });
|
||||
} else {
|
||||
await createAutoGroup(form);
|
||||
await createAutoGroup(submitData);
|
||||
Toast.show({ content: "创建成功" });
|
||||
}
|
||||
navigate("/workspace/auto-group");
|
||||
@@ -74,17 +148,113 @@ const AutoGroupForm: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const setTaskName = (val: string) => {
|
||||
setForm((f: any) => ({ ...f, name: val }));
|
||||
const handlePrevious = () => {
|
||||
if (currentStep > 1) {
|
||||
setCurrentStep(currentStep - 1);
|
||||
}
|
||||
};
|
||||
const setDeviceGroups = (val: DeviceSelectionItem[]) => {
|
||||
console.log(val);
|
||||
setForm((f: any) => ({
|
||||
...f,
|
||||
deveiceGroups: val.map(item => item.id),
|
||||
deveiceGroupsOptions: val,
|
||||
}));
|
||||
|
||||
const handleNext = async () => {
|
||||
if (currentStep < 3) {
|
||||
try {
|
||||
let isValid = false;
|
||||
|
||||
switch (currentStep) {
|
||||
case 1:
|
||||
// 调用 BasicSettings 的表单校验
|
||||
isValid = (await basicSettingsRef.current?.validate()) || false;
|
||||
if (isValid) {
|
||||
const values = basicSettingsRef.current?.getValues();
|
||||
if (values) {
|
||||
handleBasicSettingsChange(values);
|
||||
}
|
||||
setCurrentStep(2);
|
||||
}
|
||||
break;
|
||||
|
||||
case 2:
|
||||
// 调用 DeviceSelector 的表单校验
|
||||
isValid = (await deviceSelectorRef.current?.validate()) || false;
|
||||
if (isValid) {
|
||||
setCurrentStep(3);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
setCurrentStep(currentStep + 1);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("表单验证失败:", error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const renderCurrentStep = () => {
|
||||
switch (currentStep) {
|
||||
case 1:
|
||||
return (
|
||||
<BasicSettings
|
||||
ref={basicSettingsRef}
|
||||
defaultValues={{
|
||||
name: formData.name,
|
||||
startTime: formData.startTime,
|
||||
endTime: formData.endTime,
|
||||
groupSizeMin: formData.groupSizeMin,
|
||||
groupSizeMax: formData.groupSizeMax,
|
||||
maxGroupsPerDay: formData.maxGroupsPerDay,
|
||||
groupNameTemplate: formData.groupNameTemplate,
|
||||
groupDescription: formData.groupDescription,
|
||||
status: formData.status,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
case 2:
|
||||
return (
|
||||
<DeviceSelector
|
||||
ref={deviceSelectorRef}
|
||||
selectedDevices={deviceGroupsOptions}
|
||||
onNext={handleDevicesChange}
|
||||
/>
|
||||
);
|
||||
case 3:
|
||||
return (
|
||||
<ContentSelector
|
||||
ref={contentSelectorRef}
|
||||
selectedContent={contentGroupsOptions}
|
||||
onNext={handleContentChange}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const renderFooter = () => {
|
||||
return (
|
||||
<div className="footer-btn-group">
|
||||
{currentStep > 1 && (
|
||||
<Button size="large" onClick={handlePrevious}>
|
||||
上一步
|
||||
</Button>
|
||||
)}
|
||||
{currentStep === 3 ? (
|
||||
<Button
|
||||
size="large"
|
||||
type="primary"
|
||||
loading={loading}
|
||||
onClick={handleSave}
|
||||
>
|
||||
{isEdit ? "保存修改" : "创建任务"}
|
||||
</Button>
|
||||
) : (
|
||||
<Button size="large" type="primary" onClick={handleNext}>
|
||||
下一步
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Layout
|
||||
header={
|
||||
@@ -93,207 +263,13 @@ const AutoGroupForm: React.FC = () => {
|
||||
backFn={() => navigate(-1)}
|
||||
/>
|
||||
}
|
||||
footer={renderFooter()}
|
||||
>
|
||||
<div className={style.autoGroupForm}>
|
||||
<Form
|
||||
layout="vertical"
|
||||
footer={
|
||||
<Button
|
||||
block
|
||||
type="primary"
|
||||
loading={loading}
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
{isEdit ? "保存修改" : "创建任务"}
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<Form.Item label="任务名称" required>
|
||||
<Input
|
||||
value={form.name}
|
||||
onChange={val => setTaskName(val.target.value)}
|
||||
placeholder="请输入任务名称"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label="设备组" required>
|
||||
<DeviceSelection
|
||||
selectedOptions={form.deveiceGroupsOptions}
|
||||
onSelect={setDeviceGroups}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label="每日最大建群数" name="maxGroupsPerDay" required>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
|
||||
<Button
|
||||
htmlType="button"
|
||||
onClick={() => {
|
||||
const newValue = Math.max(1, (form.maxGroupsPerDay || 1) - 1);
|
||||
setForm((f: any) => ({ ...f, maxGroupsPerDay: newValue }));
|
||||
}}
|
||||
>
|
||||
-
|
||||
</Button>
|
||||
<InputNumber
|
||||
min={1}
|
||||
max={100}
|
||||
value={form.maxGroupsPerDay}
|
||||
onChange={val =>
|
||||
setForm((f: any) => ({ ...f, maxGroupsPerDay: val || 1 }))
|
||||
}
|
||||
placeholder="请输入最大建群数"
|
||||
step={1}
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
<Button
|
||||
htmlType="button"
|
||||
onClick={() => {
|
||||
const newValue = Math.min(
|
||||
100,
|
||||
(form.maxGroupsPerDay || 1) + 1,
|
||||
);
|
||||
setForm((f: any) => ({ ...f, maxGroupsPerDay: newValue }));
|
||||
}}
|
||||
>
|
||||
+
|
||||
</Button>
|
||||
</div>
|
||||
</Form.Item>
|
||||
<Form.Item label="开始时间" required>
|
||||
<Input
|
||||
type="time"
|
||||
style={{ width: 120 }}
|
||||
value={form.startTime || ""}
|
||||
onChange={e => {
|
||||
setForm((f: any) => ({ ...f, startTime: e.target.value }));
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label="结束时间" required>
|
||||
<Input
|
||||
type="time"
|
||||
style={{ width: 120 }}
|
||||
value={form.endTime || ""}
|
||||
onChange={e => {
|
||||
setForm((f: any) => ({ ...f, endTime: e.target.value }));
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label="群组最小人数" name="groupSizeMin" required>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
|
||||
<Button
|
||||
htmlType="button"
|
||||
onClick={() => {
|
||||
const newValue = Math.max(1, (form.groupSizeMin || 1) - 1);
|
||||
setForm((f: any) => ({ ...f, groupSizeMin: newValue }));
|
||||
}}
|
||||
>
|
||||
-
|
||||
</Button>
|
||||
<InputNumber
|
||||
min={1}
|
||||
max={500}
|
||||
value={form.groupSizeMin}
|
||||
onChange={val => {
|
||||
const newValue = val || 1;
|
||||
setForm((f: any) => ({
|
||||
...f,
|
||||
groupSizeMin: Math.min(newValue, f.groupSizeMax),
|
||||
}));
|
||||
}}
|
||||
placeholder="请输入最小人数"
|
||||
step={1}
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
<Button
|
||||
htmlType="button"
|
||||
onClick={() => {
|
||||
const newValue = Math.min(500, (form.groupSizeMin || 1) + 1);
|
||||
setForm((f: any) => ({ ...f, groupSizeMin: newValue }));
|
||||
}}
|
||||
>
|
||||
+
|
||||
</Button>
|
||||
</div>
|
||||
</Form.Item>
|
||||
<Form.Item label="群组最大人数" name="groupSizeMax" required>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
|
||||
<Button
|
||||
htmlType="button"
|
||||
onClick={() => {
|
||||
const newValue = Math.max(1, (form.groupSizeMax || 1) - 1);
|
||||
setForm((f: any) => ({ ...f, groupSizeMax: newValue }));
|
||||
}}
|
||||
>
|
||||
-
|
||||
</Button>
|
||||
<InputNumber
|
||||
min={1}
|
||||
max={500}
|
||||
value={form.groupSizeMax}
|
||||
onChange={val => {
|
||||
const newValue = val || 1;
|
||||
setForm((f: any) => ({
|
||||
...f,
|
||||
groupSizeMax: Math.max(newValue, f.groupSizeMin),
|
||||
}));
|
||||
}}
|
||||
placeholder="请输入最大人数"
|
||||
step={1}
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
<Button
|
||||
htmlType="button"
|
||||
onClick={() => {
|
||||
const newValue = Math.min(500, (form.groupSizeMax || 1) + 1);
|
||||
setForm((f: any) => ({ ...f, groupSizeMax: newValue }));
|
||||
}}
|
||||
>
|
||||
+
|
||||
</Button>
|
||||
</div>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="群名称模板" required>
|
||||
<Input
|
||||
value={form.groupNameTemplate}
|
||||
onChange={val =>
|
||||
setForm((f: any) => ({
|
||||
...f,
|
||||
groupNameTemplate: val.target.value,
|
||||
}))
|
||||
}
|
||||
placeholder="请输入群名称模板"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label="群描述" name="groupDescription">
|
||||
<TextArea
|
||||
value={form.groupDescription}
|
||||
onChange={val =>
|
||||
setForm((f: any) => ({ ...f, groupDescription: val }))
|
||||
}
|
||||
placeholder="请输入群描述"
|
||||
rows={3}
|
||||
maxLength={100}
|
||||
showCount
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label="是否开启" name="status">
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<span>状态</span>
|
||||
<Switch
|
||||
checked={form.status === 1}
|
||||
onChange={checked =>
|
||||
setForm((f: any) => ({ ...f, status: checked ? 1 : 0 }))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
<div style={{ padding: 12 }}>
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<StepIndicator currentStep={currentStep} steps={steps} />
|
||||
</div>
|
||||
<div>{renderCurrentStep()}</div>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { DeviceSelectionItem } from "@/components/DeviceSelection/data";
|
||||
import { ContentItem } from "@/components/ContentSelection/data";
|
||||
|
||||
// 自动建群表单数据类型定义
|
||||
export interface AutoGroupFormData {
|
||||
id?: string; // 任务ID
|
||||
@@ -6,6 +8,8 @@ export interface AutoGroupFormData {
|
||||
name: string; // 任务名称
|
||||
deveiceGroups: string[]; // 设备组
|
||||
deveiceGroupsOptions: DeviceSelectionItem[]; // 设备组选项
|
||||
contentGroups: string[]; // 内容库
|
||||
contentGroupsOptions: ContentItem[]; // 内容库选项
|
||||
startTime: string; // 开始时间 (YYYY-MM-DD HH:mm:ss)
|
||||
endTime: string; // 结束时间 (YYYY-MM-DD HH:mm:ss)
|
||||
groupSizeMin: number; // 群组最小人数
|
||||
@@ -17,6 +21,13 @@ export interface AutoGroupFormData {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
// 步骤定义
|
||||
export interface StepItem {
|
||||
id: number;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
}
|
||||
|
||||
// 表单验证规则
|
||||
export const formValidationRules = {
|
||||
name: [
|
||||
@@ -27,6 +38,10 @@ export const formValidationRules = {
|
||||
{ required: true, message: "请选择设备组" },
|
||||
{ type: "array", min: 1, message: "至少选择一个设备组" },
|
||||
],
|
||||
contentGroups: [
|
||||
{ required: true, message: "请选择内容库" },
|
||||
{ type: "array", min: 1, message: "至少选择一个内容库" },
|
||||
],
|
||||
startTime: [{ required: true, message: "请选择开始时间" }],
|
||||
endTime: [{ required: true, message: "请选择结束时间" }],
|
||||
groupSizeMin: [
|
||||
|
||||
Reference in New Issue
Block a user