FEAT => 本次更新项目为:
选择社群构建完成
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
import React, { useState } from "react";
|
||||
import { Input, Button, Card, Switch } from "antd";
|
||||
import { MinusOutlined, PlusOutlined } from "@ant-design/icons";
|
||||
import React, { useImperativeHandle, forwardRef } from "react";
|
||||
import { Input, Button, Card, Switch, Form, InputNumber } from "antd";
|
||||
|
||||
interface BasicSettingsProps {
|
||||
defaultValues?: {
|
||||
@@ -18,194 +17,199 @@ interface BasicSettingsProps {
|
||||
loading?: boolean;
|
||||
}
|
||||
|
||||
const BasicSettings: React.FC<BasicSettingsProps> = ({
|
||||
defaultValues = {
|
||||
name: "",
|
||||
pushTimeStart: "06:00",
|
||||
pushTimeEnd: "23:59",
|
||||
dailyPushCount: 20,
|
||||
pushOrder: "latest",
|
||||
isLoopPush: false,
|
||||
isImmediatePush: false,
|
||||
isEnabled: false,
|
||||
},
|
||||
onNext,
|
||||
onSave,
|
||||
loading = false,
|
||||
}) => {
|
||||
const [values, setValues] = useState(defaultValues);
|
||||
export interface BasicSettingsRef {
|
||||
validate: () => Promise<boolean>;
|
||||
getValues: () => any;
|
||||
}
|
||||
|
||||
const handleChange = (field: string, value: any) => {
|
||||
setValues(prev => ({ ...prev, [field]: value }));
|
||||
};
|
||||
const BasicSettings = forwardRef<BasicSettingsRef, BasicSettingsProps>(
|
||||
(
|
||||
{
|
||||
defaultValues = {
|
||||
name: "",
|
||||
pushTimeStart: "06:00",
|
||||
pushTimeEnd: "23:59",
|
||||
dailyPushCount: 20,
|
||||
pushOrder: "latest",
|
||||
isLoopPush: false,
|
||||
isImmediatePush: false,
|
||||
isEnabled: false,
|
||||
},
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const handleCountChange = (increment: boolean) => {
|
||||
setValues(prev => ({
|
||||
...prev,
|
||||
dailyPushCount: increment
|
||||
? prev.dailyPushCount + 1
|
||||
: Math.max(1, prev.dailyPushCount - 1),
|
||||
// 暴露方法给父组件
|
||||
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>
|
||||
<div>
|
||||
{/* 任务名称 */}
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<span style={{ color: "red", marginRight: 4 }}>*</span>任务名称:
|
||||
<Input
|
||||
value={values.name}
|
||||
onChange={e => handleChange("name", e.target.value)}
|
||||
placeholder="请输入任务名称"
|
||||
style={{ marginTop: 4 }}
|
||||
/>
|
||||
</div>
|
||||
{/* 允许推送的时间段 */}
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<span>允许推送的时间段:</span>
|
||||
<div style={{ display: "flex", gap: 8, marginTop: 4 }}>
|
||||
<Input
|
||||
type="time"
|
||||
value={values.pushTimeStart}
|
||||
onChange={e => handleChange("pushTimeStart", e.target.value)}
|
||||
style={{ width: 120 }}
|
||||
/>
|
||||
<span style={{ color: "#888" }}>至</span>
|
||||
<Input
|
||||
type="time"
|
||||
value={values.pushTimeEnd}
|
||||
onChange={e => handleChange("pushTimeEnd", e.target.value)}
|
||||
style={{ width: 120 }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/* 每日推送 */}
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<span>每日推送:</span>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: 8,
|
||||
marginTop: 4,
|
||||
}}
|
||||
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个字符之间" },
|
||||
]}
|
||||
>
|
||||
<Button
|
||||
icon={<MinusOutlined />}
|
||||
onClick={() => handleCountChange(false)}
|
||||
disabled={loading}
|
||||
/>
|
||||
<Input
|
||||
type="number"
|
||||
value={values.dailyPushCount}
|
||||
onChange={e =>
|
||||
handleChange(
|
||||
"dailyPushCount",
|
||||
Number.parseInt(e.target.value) || 1,
|
||||
)
|
||||
}
|
||||
style={{ width: 80, textAlign: "center" }}
|
||||
<Input placeholder="请输入任务名称" />
|
||||
</Form.Item>
|
||||
|
||||
{/* 允许推送的时间段 */}
|
||||
<Form.Item label="允许推送的时间段">
|
||||
<div style={{ display: "flex", gap: 8, alignItems: "center" }}>
|
||||
<Form.Item
|
||||
name="pushTimeStart"
|
||||
noStyle
|
||||
rules={[{ required: true, message: "请选择开始时间" }]}
|
||||
>
|
||||
<Input type="time" style={{ width: 120 }} />
|
||||
</Form.Item>
|
||||
<span style={{ color: "#888" }}>至</span>
|
||||
<Form.Item
|
||||
name="pushTimeEnd"
|
||||
noStyle
|
||||
rules={[{ required: true, message: "请选择结束时间" }]}
|
||||
>
|
||||
<Input type="time" style={{ width: 120 }} />
|
||||
</Form.Item>
|
||||
</div>
|
||||
</Form.Item>
|
||||
|
||||
{/* 每日推送 */}
|
||||
<Form.Item
|
||||
label="每日推送"
|
||||
name="dailyPushCount"
|
||||
rules={[
|
||||
{ required: true, message: "请输入每日推送数量" },
|
||||
{
|
||||
type: "number",
|
||||
min: 1,
|
||||
max: 100,
|
||||
message: "每日推送数量在1-100之间",
|
||||
},
|
||||
]}
|
||||
>
|
||||
<InputNumber
|
||||
min={1}
|
||||
disabled={loading}
|
||||
max={100}
|
||||
style={{ width: 120 }}
|
||||
addonAfter="条内容"
|
||||
/>
|
||||
<Button
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => handleCountChange(true)}
|
||||
disabled={loading}
|
||||
/>
|
||||
<span style={{ color: "#888" }}>条内容</span>
|
||||
</div>
|
||||
</div>
|
||||
{/* 推送顺序 */}
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<span>推送顺序:</span>
|
||||
<div style={{ display: "flex" }}>
|
||||
<Button
|
||||
type={values.pushOrder === "earliest" ? "primary" : "default"}
|
||||
onClick={() => handleChange("pushOrder", "earliest")}
|
||||
disabled={loading}
|
||||
style={{ borderRadius: "6px 0 0 6px" }}
|
||||
>
|
||||
按最早
|
||||
</Button>
|
||||
<Button
|
||||
type={values.pushOrder === "latest" ? "primary" : "default"}
|
||||
onClick={() => handleChange("pushOrder", "latest")}
|
||||
disabled={loading}
|
||||
style={{ borderRadius: "0 6px 6px 0", marginLeft: -1 }}
|
||||
>
|
||||
按最新
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{/* 是否循环推送 */}
|
||||
<div
|
||||
style={{
|
||||
marginBottom: 16,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<span>是否循环推送:</span>
|
||||
<Switch
|
||||
checked={values.isLoopPush}
|
||||
onChange={checked => handleChange("isLoopPush", checked)}
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
{/* 是否立即推送 */}
|
||||
<div
|
||||
style={{
|
||||
marginBottom: 16,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<span>是否立即推送:</span>
|
||||
<Switch
|
||||
checked={values.isImmediatePush}
|
||||
onChange={checked => handleChange("isImmediatePush", checked)}
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
{values.isImmediatePush && (
|
||||
<div
|
||||
style={{
|
||||
background: "#fffbe6",
|
||||
border: "1px solid #ffe58f",
|
||||
borderRadius: 4,
|
||||
padding: 8,
|
||||
color: "#ad8b00",
|
||||
marginBottom: 16,
|
||||
}}
|
||||
</Form.Item>
|
||||
|
||||
{/* 推送顺序 */}
|
||||
<Form.Item
|
||||
label="推送顺序"
|
||||
name="pushOrder"
|
||||
rules={[{ required: true, message: "请选择推送顺序" }]}
|
||||
>
|
||||
如果启用,系统会把内容库里所有的内容按顺序推送到指定的社群
|
||||
</div>
|
||||
)}
|
||||
{/* 是否启用 */}
|
||||
<div
|
||||
style={{
|
||||
marginBottom: 16,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<span>是否启用:</span>
|
||||
<Switch
|
||||
checked={values.isEnabled}
|
||||
onChange={checked => handleChange("isEnabled", checked)}
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
<div style={{ display: "flex" }}>
|
||||
<Button
|
||||
type="default"
|
||||
style={{ borderRadius: "6px 0 0 6px" }}
|
||||
onClick={() => form.setFieldValue("pushOrder", "earliest")}
|
||||
className={
|
||||
form.getFieldValue("pushOrder") === "earliest"
|
||||
? "ant-btn-primary"
|
||||
: ""
|
||||
}
|
||||
>
|
||||
按最早
|
||||
</Button>
|
||||
<Button
|
||||
type="default"
|
||||
style={{ borderRadius: "0 6px 6px 0", marginLeft: -1 }}
|
||||
onClick={() => form.setFieldValue("pushOrder", "latest")}
|
||||
className={
|
||||
form.getFieldValue("pushOrder") === "latest"
|
||||
? "ant-btn-primary"
|
||||
: ""
|
||||
}
|
||||
>
|
||||
按最新
|
||||
</Button>
|
||||
</div>
|
||||
</Form.Item>
|
||||
|
||||
{/* 是否循环推送 */}
|
||||
<Form.Item
|
||||
label="是否循环推送"
|
||||
name="isLoopPush"
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
|
||||
{/* 是否立即推送 */}
|
||||
<Form.Item
|
||||
label="是否立即推送"
|
||||
name="isImmediatePush"
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
|
||||
{/* 是否启用 */}
|
||||
<Form.Item
|
||||
label="是否启用"
|
||||
name="isEnabled"
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
|
||||
{/* 立即推送提示 */}
|
||||
<Form.Item noStyle shouldUpdate>
|
||||
{() => {
|
||||
const isImmediatePush = form.getFieldValue("isImmediatePush");
|
||||
return isImmediatePush ? (
|
||||
<div
|
||||
style={{
|
||||
background: "#fffbe6",
|
||||
border: "1px solid #ffe58f",
|
||||
borderRadius: 4,
|
||||
padding: 8,
|
||||
color: "#ad8b00",
|
||||
marginBottom: 16,
|
||||
}}
|
||||
>
|
||||
如果启用,系统会把内容库里所有的内容按顺序推送到指定的社群
|
||||
</div>
|
||||
) : null;
|
||||
}}
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
BasicSettings.displayName = "BasicSettings";
|
||||
|
||||
export default BasicSettings;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useState } from "react";
|
||||
import React, { useImperativeHandle, forwardRef } from "react";
|
||||
import { Form, Card } from "antd";
|
||||
import ContentLibrarySelection from "@/components/ContentLibrarySelection";
|
||||
|
||||
interface ContentLibrary {
|
||||
@@ -19,60 +20,116 @@ interface ContentSelectorProps {
|
||||
loading?: boolean;
|
||||
}
|
||||
|
||||
const ContentSelector: React.FC<ContentSelectorProps> = ({
|
||||
selectedLibraries,
|
||||
onLibrariesChange,
|
||||
onPrevious,
|
||||
onNext,
|
||||
onSave,
|
||||
loading = false,
|
||||
}) => {
|
||||
// 将 ContentLibrary[] 转换为 string[] 用于 ContentLibrarySelection
|
||||
const selectedLibraryIds = selectedLibraries.map(lib => lib.id);
|
||||
export interface ContentSelectorRef {
|
||||
validate: () => Promise<boolean>;
|
||||
getValues: () => any;
|
||||
}
|
||||
|
||||
// 处理选择变化
|
||||
const handleLibrariesChange = (libraryIds: string[]) => {
|
||||
// 这里需要根据选中的ID重新构建ContentLibrary对象
|
||||
// 由于ContentLibrarySelection只返回ID,我们需要从原始数据中获取完整信息
|
||||
// 暂时使用简化的处理方式
|
||||
const newSelectedLibraries = libraryIds.map(id => ({
|
||||
id,
|
||||
name: `内容库 ${id}`, // 这里应该从API获取完整信息
|
||||
targets: [], // 这里应该从API获取完整信息
|
||||
const ContentSelector = forwardRef<ContentSelectorRef, ContentSelectorProps>(
|
||||
(
|
||||
{
|
||||
selectedLibraries,
|
||||
onLibrariesChange,
|
||||
onPrevious,
|
||||
onNext,
|
||||
onSave,
|
||||
loading = false,
|
||||
},
|
||||
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();
|
||||
},
|
||||
}));
|
||||
onLibrariesChange(newSelectedLibraries);
|
||||
};
|
||||
|
||||
// 处理选择详情变化
|
||||
const handleSelectDetail = (libraries: any[]) => {
|
||||
// 将API返回的数据转换为ContentLibrary格式
|
||||
const convertedLibraries = libraries.map(lib => ({
|
||||
id: lib.id,
|
||||
name: lib.name,
|
||||
targets: [], // 这里需要根据实际情况获取targets数据
|
||||
}));
|
||||
onLibrariesChange(convertedLibraries);
|
||||
};
|
||||
// 将 ContentLibrary[] 转换为 string[] 用于 ContentLibrarySelection
|
||||
const selectedLibraryIds = selectedLibraries.map(lib => lib.id);
|
||||
|
||||
return (
|
||||
<div style={{ marginBottom: 24 }}>
|
||||
<div>
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<span>选择内容库:</span>
|
||||
</div>
|
||||
<ContentLibrarySelection
|
||||
selectedLibraries={selectedLibraryIds}
|
||||
onSelect={handleLibrariesChange}
|
||||
onSelectDetail={handleSelectDetail}
|
||||
placeholder="选择内容库"
|
||||
showInput={true}
|
||||
showSelectedList={true}
|
||||
readonly={loading}
|
||||
selectedListMaxHeight={320}
|
||||
/>
|
||||
// 处理选择变化
|
||||
const handleLibrariesChange = (libraryIds: string[]) => {
|
||||
// 这里需要根据选中的ID重新构建ContentLibrary对象
|
||||
// 由于ContentLibrarySelection只返回ID,我们需要从原始数据中获取完整信息
|
||||
// 暂时使用简化的处理方式
|
||||
const newSelectedLibraries = libraryIds.map(id => ({
|
||||
id,
|
||||
name: `内容库 ${id}`, // 这里应该从API获取完整信息
|
||||
targets: [], // 这里应该从API获取完整信息
|
||||
}));
|
||||
onLibrariesChange(newSelectedLibraries);
|
||||
form.setFieldValue("contentLibraries", libraryIds);
|
||||
};
|
||||
|
||||
// 处理选择详情变化
|
||||
const handleSelectDetail = (libraries: any[]) => {
|
||||
// 将API返回的数据转换为ContentLibrary格式
|
||||
const convertedLibraries = libraries.map(lib => ({
|
||||
id: lib.id,
|
||||
name: lib.name,
|
||||
targets: [], // 这里需要根据实际情况获取targets数据
|
||||
}));
|
||||
onLibrariesChange(convertedLibraries);
|
||||
form.setFieldValue(
|
||||
"contentLibraries",
|
||||
libraries.map(lib => lib.id),
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ marginBottom: 24 }}>
|
||||
<Card>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
initialValues={{ contentLibraries: selectedLibraryIds }}
|
||||
>
|
||||
<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="contentLibraries"
|
||||
rules={[
|
||||
{ required: true, message: "请选择至少一个内容库" },
|
||||
{ type: "array", min: 1, message: "请选择至少一个内容库" },
|
||||
{ type: "array", max: 20, message: "最多只能选择20个内容库" },
|
||||
]}
|
||||
>
|
||||
<ContentLibrarySelection
|
||||
selectedLibraries={selectedLibraryIds}
|
||||
onSelect={handleLibrariesChange}
|
||||
onSelectDetail={handleSelectDetail}
|
||||
placeholder="选择内容库"
|
||||
showInput={true}
|
||||
showSelectedList={true}
|
||||
readonly={loading}
|
||||
selectedListMaxHeight={320}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
ContentSelector.displayName = "ContentSelector";
|
||||
|
||||
export default ContentSelector;
|
||||
|
||||
@@ -1,59 +1,95 @@
|
||||
import React, { useState } from "react";
|
||||
import React, { useImperativeHandle, forwardRef } from "react";
|
||||
import { Form, Card } from "antd";
|
||||
import GroupSelection from "@/components/GroupSelection";
|
||||
import { GroupSelectionItem } from "@/components/GroupSelection/data";
|
||||
|
||||
interface GroupSelectorProps {
|
||||
selectedGroups: string[];
|
||||
onGroupsChange: (groups: string[]) => void;
|
||||
selectedGroups: GroupSelectionItem[];
|
||||
onPrevious: () => void;
|
||||
onNext: () => void;
|
||||
onSave: () => void;
|
||||
loading?: boolean;
|
||||
onNext: (data: {
|
||||
wechatGroups: string[];
|
||||
wechatGroupsOptions: GroupSelectionItem[];
|
||||
}) => void;
|
||||
}
|
||||
|
||||
const GroupSelector: React.FC<GroupSelectorProps> = ({
|
||||
selectedGroups,
|
||||
onGroupsChange,
|
||||
onPrevious,
|
||||
onNext,
|
||||
onSave,
|
||||
loading = false,
|
||||
}) => {
|
||||
// 将string[]转换为GroupSelectionItem[]
|
||||
const selectedGroupItems: GroupSelectionItem[] = selectedGroups.map(id => ({
|
||||
id,
|
||||
name: `群组 ${id}`,
|
||||
avatar: "",
|
||||
chatroomId: id,
|
||||
}));
|
||||
export interface GroupSelectorRef {
|
||||
validate: () => Promise<boolean>;
|
||||
getValues: () => any;
|
||||
}
|
||||
|
||||
const handleGroupSelect = (groupItems: GroupSelectionItem[]) => {
|
||||
// 将GroupSelectionItem[]转换回string[]
|
||||
const groupIds = groupItems.map(item => item.id);
|
||||
onGroupsChange(groupIds);
|
||||
};
|
||||
const GroupSelector = forwardRef<GroupSelectorRef, GroupSelectorProps>(
|
||||
({ selectedGroups, onNext }, ref) => {
|
||||
const [form] = Form.useForm();
|
||||
|
||||
return (
|
||||
<div style={{ padding: 20 }}>
|
||||
<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>
|
||||
// 暴露方法给父组件
|
||||
useImperativeHandle(ref, () => ({
|
||||
validate: async () => {
|
||||
try {
|
||||
form.setFieldsValue({
|
||||
wechatGroups: selectedGroups.map(item => item.id),
|
||||
});
|
||||
await form.validateFields();
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.log("GroupSelector 表单验证失败:", error);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
getValues: () => {
|
||||
return form.getFieldsValue();
|
||||
},
|
||||
}));
|
||||
|
||||
<GroupSelection
|
||||
selectedGroups={selectedGroupItems}
|
||||
onSelect={handleGroupSelect}
|
||||
placeholder="选择要推送的群组"
|
||||
readonly={false}
|
||||
showSelectedList={true}
|
||||
selectedListMaxHeight={300}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
// 群组选择
|
||||
const handleGroupSelect = (wechatGroupsOptions: GroupSelectionItem[]) => {
|
||||
const wechatGroups = wechatGroupsOptions.map(item => item.id);
|
||||
form.setFieldValue("wechatGroups", wechatGroups);
|
||||
onNext({ wechatGroups, wechatGroupsOptions });
|
||||
};
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
initialValues={{ groups: selectedGroups }}
|
||||
>
|
||||
<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="wechatGroups"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
type: "array",
|
||||
min: 1,
|
||||
message: "请选择至少一个群组",
|
||||
},
|
||||
{ type: "array", max: 50, message: "最多只能选择50个群组" },
|
||||
]}
|
||||
>
|
||||
<GroupSelection
|
||||
selectedGroups={selectedGroups}
|
||||
onSelect={handleGroupSelect}
|
||||
placeholder="选择要推送的群组"
|
||||
readonly={false}
|
||||
showSelectedList={true}
|
||||
selectedListMaxHeight={300}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Card>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
GroupSelector.displayName = "GroupSelector";
|
||||
|
||||
export default GroupSelector;
|
||||
|
||||
@@ -27,6 +27,7 @@ export interface FormData {
|
||||
isLoopPush: boolean;
|
||||
isImmediatePush: boolean;
|
||||
isEnabled: boolean;
|
||||
groups: WechatGroup[];
|
||||
contentLibraries: ContentLibrary[];
|
||||
wechatGroups: string[];
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { Button } from "antd";
|
||||
import { createGroupPushTask } from "./index.api";
|
||||
import Layout from "@/components/Layout/Layout";
|
||||
import StepIndicator from "@/components/StepIndicator";
|
||||
import BasicSettings from "./components/BasicSettings";
|
||||
import GroupSelector from "./components/GroupSelector";
|
||||
import ContentSelector from "./components/ContentSelector";
|
||||
import BasicSettings, { BasicSettingsRef } from "./components/BasicSettings";
|
||||
import GroupSelector, { GroupSelectorRef } from "./components/GroupSelector";
|
||||
import ContentSelector, {
|
||||
ContentSelectorRef,
|
||||
} from "./components/ContentSelector";
|
||||
import type { ContentLibrary, FormData } from "./index.data";
|
||||
import NavCommon from "@/components/NavCommon";
|
||||
import { GroupSelectionItem } from "@/components/GroupSelection/data";
|
||||
const steps = [
|
||||
{ id: 1, title: "步骤 1", subtitle: "基础设置" },
|
||||
{ id: 2, title: "步骤 2", subtitle: "选择社群" },
|
||||
@@ -21,6 +24,9 @@ const NewGroupPush: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
const [currentStep, setCurrentStep] = useState(1);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [wechatGroupsOptions, setWechatGroupsOptions] = useState<
|
||||
GroupSelectionItem[]
|
||||
>([]);
|
||||
const [formData, setFormData] = useState<FormData>({
|
||||
name: "",
|
||||
pushTimeStart: "06:00",
|
||||
@@ -30,11 +36,16 @@ const NewGroupPush: React.FC = () => {
|
||||
isLoopPush: false,
|
||||
isImmediatePush: false,
|
||||
isEnabled: false,
|
||||
groups: [],
|
||||
wechatGroups: [],
|
||||
contentLibraries: [],
|
||||
});
|
||||
const [isEditMode, setIsEditMode] = useState(false);
|
||||
|
||||
// 创建子组件的ref
|
||||
const basicSettingsRef = useRef<BasicSettingsRef>(null);
|
||||
const groupSelectorRef = useRef<GroupSelectorRef>(null);
|
||||
const contentSelectorRef = useRef<ContentSelectorRef>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!id) return;
|
||||
setIsEditMode(true);
|
||||
@@ -44,19 +55,16 @@ const NewGroupPush: React.FC = () => {
|
||||
setFormData(prev => ({ ...prev, ...values }));
|
||||
};
|
||||
|
||||
const handleGroupsChange = (groups: string[]) => {
|
||||
// 将string[]转换为WechatGroup[]
|
||||
const convertedGroups = groups.map(id => ({
|
||||
id,
|
||||
name: `群组 ${id}`,
|
||||
avatar: "",
|
||||
serviceAccount: {
|
||||
id: "",
|
||||
name: "",
|
||||
avatar: "",
|
||||
},
|
||||
//群组选择
|
||||
const handleGroupsChange = (data: {
|
||||
wechatGroups: string[];
|
||||
wechatGroupsOptions: GroupSelectionItem[];
|
||||
}) => {
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
wechatGroups: data.wechatGroups,
|
||||
}));
|
||||
setFormData(prev => ({ ...prev, groups: convertedGroups }));
|
||||
setWechatGroupsOptions(data.wechatGroupsOptions);
|
||||
};
|
||||
|
||||
const handleLibrariesChange = (contentLibraries: ContentLibrary[]) => {
|
||||
@@ -119,64 +127,66 @@ const NewGroupPush: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleNext = () => {
|
||||
const handleNext = async () => {
|
||||
if (currentStep < 4) {
|
||||
setCurrentStep(currentStep + 1);
|
||||
}
|
||||
};
|
||||
try {
|
||||
let isValid = false;
|
||||
|
||||
const canGoNext = () => {
|
||||
switch (currentStep) {
|
||||
case 1: {
|
||||
return formData.name.trim() !== "";
|
||||
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:
|
||||
// 调用 GroupSelector 的表单校验
|
||||
isValid = (await groupSelectorRef.current?.validate()) || false;
|
||||
if (isValid) {
|
||||
setCurrentStep(3);
|
||||
}
|
||||
break;
|
||||
|
||||
case 3:
|
||||
// 调用 ContentSelector 的表单校验
|
||||
isValid = (await contentSelectorRef.current?.validate()) || false;
|
||||
if (isValid) {
|
||||
setCurrentStep(4);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
setCurrentStep(currentStep + 1);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("表单验证失败:", error);
|
||||
}
|
||||
case 2: {
|
||||
// 选择社群:检查是否选择了群组
|
||||
const groupsValid =
|
||||
formData.groups.length > 0 && formData.groups.length <= 50; // 添加上限检查
|
||||
return groupsValid;
|
||||
}
|
||||
case 3: {
|
||||
// 选择内容库:检查是否选择了内容库
|
||||
const librariesValid =
|
||||
formData.contentLibraries.length > 0 &&
|
||||
formData.contentLibraries.length <= 20; // 添加上限检查
|
||||
return librariesValid;
|
||||
}
|
||||
case 4: {
|
||||
// 京东联盟:可以进入下一步(保存)
|
||||
// 这里可以添加京东联盟相关的验证逻辑
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const renderFooter = () => {
|
||||
if (currentStep === 4) {
|
||||
return (
|
||||
<div className="footer-btn-group">
|
||||
<Button size="large" onClick={handlePrevious}>
|
||||
上一步
|
||||
</Button>
|
||||
<Button type="primary" size="large" onClick={handleSave}>
|
||||
保存
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="footer-btn-group">
|
||||
{currentStep > 1 && (
|
||||
<Button size="large" type="primary" onClick={handlePrevious}>
|
||||
<Button size="large" onClick={handlePrevious}>
|
||||
上一步
|
||||
</Button>
|
||||
)}
|
||||
<Button size="large" type="primary" onClick={handleNext}>
|
||||
下一步
|
||||
</Button>
|
||||
{currentStep === 4 ? (
|
||||
<Button size="large" type="primary" onClick={handleSave}>
|
||||
保存
|
||||
</Button>
|
||||
) : (
|
||||
<Button size="large" type="primary" onClick={handleNext}>
|
||||
下一步
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -186,11 +196,14 @@ const NewGroupPush: React.FC = () => {
|
||||
header={<NavCommon title={isEditMode ? "编辑任务" : "新建任务"} />}
|
||||
footer={renderFooter()}
|
||||
>
|
||||
<div style={{ maxWidth: 600, margin: "0 auto", padding: 16 }}>
|
||||
<StepIndicator currentStep={currentStep} steps={steps} />
|
||||
<div style={{ marginTop: 32 }}>
|
||||
<div style={{ padding: 12 }}>
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<StepIndicator currentStep={currentStep} steps={steps} />
|
||||
</div>
|
||||
<div>
|
||||
{currentStep === 1 && (
|
||||
<BasicSettings
|
||||
ref={basicSettingsRef}
|
||||
defaultValues={{
|
||||
name: formData.name,
|
||||
pushTimeStart: formData.pushTimeStart,
|
||||
@@ -208,16 +221,15 @@ const NewGroupPush: React.FC = () => {
|
||||
)}
|
||||
{currentStep === 2 && (
|
||||
<GroupSelector
|
||||
selectedGroups={formData.groups.map(g => g.id)}
|
||||
onGroupsChange={handleGroupsChange}
|
||||
ref={groupSelectorRef}
|
||||
selectedGroups={wechatGroupsOptions}
|
||||
onPrevious={() => setCurrentStep(1)}
|
||||
onNext={() => setCurrentStep(3)}
|
||||
onSave={handleSave}
|
||||
loading={loading}
|
||||
onNext={handleGroupsChange}
|
||||
/>
|
||||
)}
|
||||
{currentStep === 3 && (
|
||||
<ContentSelector
|
||||
ref={contentSelectorRef}
|
||||
selectedLibraries={formData.contentLibraries}
|
||||
onLibrariesChange={handleLibrariesChange}
|
||||
onPrevious={() => setCurrentStep(2)}
|
||||
|
||||
Reference in New Issue
Block a user