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