611 lines
20 KiB
TypeScript
611 lines
20 KiB
TypeScript
import React, { useState } from "react";
|
||
import { Form, Input, Button, Tabs, Modal, Alert, Upload, message } from "antd";
|
||
import {
|
||
PlusOutlined,
|
||
CloseOutlined,
|
||
UploadOutlined,
|
||
ClockCircleOutlined,
|
||
MessageOutlined,
|
||
PictureOutlined,
|
||
VideoCameraOutlined,
|
||
FileOutlined,
|
||
AppstoreOutlined,
|
||
LinkOutlined,
|
||
TeamOutlined,
|
||
} from "@ant-design/icons";
|
||
import Layout from "@/components/Layout/Layout";
|
||
|
||
interface MessageContent {
|
||
id: string;
|
||
type: "text" | "image" | "video" | "file" | "miniprogram" | "link" | "group";
|
||
content: string;
|
||
sendInterval?: number;
|
||
intervalUnit?: "seconds" | "minutes";
|
||
scheduledTime?: {
|
||
hour: number;
|
||
minute: number;
|
||
second: number;
|
||
};
|
||
title?: string;
|
||
description?: string;
|
||
address?: string;
|
||
coverImage?: string;
|
||
groupId?: string;
|
||
linkUrl?: string;
|
||
}
|
||
|
||
interface DayPlan {
|
||
day: number;
|
||
messages: MessageContent[];
|
||
}
|
||
|
||
interface MessageSettingsProps {
|
||
formData: any;
|
||
onChange: (data: any) => void;
|
||
onNext: () => void;
|
||
onPrev: () => void;
|
||
}
|
||
|
||
// 消息类型配置
|
||
const messageTypes = [
|
||
{ id: "text", icon: MessageOutlined, label: "文本" },
|
||
{ id: "image", icon: PictureOutlined, label: "图片" },
|
||
{ id: "video", icon: VideoCameraOutlined, label: "视频" },
|
||
{ id: "file", icon: FileOutlined, label: "文件" },
|
||
{ id: "miniprogram", icon: AppstoreOutlined, label: "小程序" },
|
||
{ id: "link", icon: LinkOutlined, label: "链接" },
|
||
{ id: "group", icon: TeamOutlined, label: "邀请入群" },
|
||
];
|
||
|
||
// 模拟群组数据
|
||
const mockGroups = [
|
||
{ id: "1", name: "产品交流群1", memberCount: 156 },
|
||
{ id: "2", name: "产品交流群2", memberCount: 234 },
|
||
{ id: "3", name: "产品交流群3", memberCount: 89 },
|
||
];
|
||
|
||
const MessageSettings: React.FC<MessageSettingsProps> = ({
|
||
formData,
|
||
onChange,
|
||
onNext,
|
||
onPrev,
|
||
}) => {
|
||
const [dayPlans, setDayPlans] = useState<DayPlan[]>([
|
||
{
|
||
day: 0,
|
||
messages: [
|
||
{
|
||
id: "1",
|
||
type: "text",
|
||
content: "",
|
||
sendInterval: 5,
|
||
intervalUnit: "seconds",
|
||
},
|
||
],
|
||
},
|
||
]);
|
||
const [isAddDayPlanOpen, setIsAddDayPlanOpen] = useState(false);
|
||
const [isGroupSelectOpen, setIsGroupSelectOpen] = useState(false);
|
||
const [selectedGroupId, setSelectedGroupId] = useState("");
|
||
|
||
// 添加新消息
|
||
const handleAddMessage = (dayIndex: number, type = "text") => {
|
||
const updatedPlans = [...dayPlans];
|
||
const newMessage: MessageContent = {
|
||
id: Date.now().toString(),
|
||
type: type as MessageContent["type"],
|
||
content: "",
|
||
};
|
||
|
||
if (dayPlans[dayIndex].day === 0) {
|
||
newMessage.sendInterval = 5;
|
||
newMessage.intervalUnit = "seconds";
|
||
} else {
|
||
newMessage.scheduledTime = {
|
||
hour: 9,
|
||
minute: 0,
|
||
second: 0,
|
||
};
|
||
}
|
||
|
||
updatedPlans[dayIndex].messages.push(newMessage);
|
||
setDayPlans(updatedPlans);
|
||
onChange({ ...formData, messagePlans: updatedPlans });
|
||
};
|
||
|
||
// 更新消息内容
|
||
const handleUpdateMessage = (
|
||
dayIndex: number,
|
||
messageIndex: number,
|
||
updates: Partial<MessageContent>
|
||
) => {
|
||
const updatedPlans = [...dayPlans];
|
||
updatedPlans[dayIndex].messages[messageIndex] = {
|
||
...updatedPlans[dayIndex].messages[messageIndex],
|
||
...updates,
|
||
};
|
||
setDayPlans(updatedPlans);
|
||
onChange({ ...formData, messagePlans: updatedPlans });
|
||
};
|
||
|
||
// 删除消息
|
||
const handleRemoveMessage = (dayIndex: number, messageIndex: number) => {
|
||
const updatedPlans = [...dayPlans];
|
||
updatedPlans[dayIndex].messages.splice(messageIndex, 1);
|
||
setDayPlans(updatedPlans);
|
||
onChange({ ...formData, messagePlans: updatedPlans });
|
||
};
|
||
|
||
// 切换时间单位
|
||
const toggleIntervalUnit = (dayIndex: number, messageIndex: number) => {
|
||
const message = dayPlans[dayIndex].messages[messageIndex];
|
||
const newUnit = message.intervalUnit === "minutes" ? "seconds" : "minutes";
|
||
handleUpdateMessage(dayIndex, messageIndex, { intervalUnit: newUnit });
|
||
};
|
||
|
||
// 添加新的天数计划
|
||
const handleAddDayPlan = () => {
|
||
const newDay = dayPlans.length;
|
||
setDayPlans([
|
||
...dayPlans,
|
||
{
|
||
day: newDay,
|
||
messages: [
|
||
{
|
||
id: Date.now().toString(),
|
||
type: "text",
|
||
content: "",
|
||
scheduledTime: {
|
||
hour: 9,
|
||
minute: 0,
|
||
second: 0,
|
||
},
|
||
},
|
||
],
|
||
},
|
||
]);
|
||
setIsAddDayPlanOpen(false);
|
||
message.success(`已添加第${newDay}天的消息计划`);
|
||
};
|
||
|
||
// 选择群组
|
||
const handleSelectGroup = (groupId: string) => {
|
||
setSelectedGroupId(groupId);
|
||
setIsGroupSelectOpen(false);
|
||
message.success(
|
||
`已选择群组:${mockGroups.find((g) => g.id === groupId)?.name}`
|
||
);
|
||
};
|
||
|
||
// 处理文件上传
|
||
const handleFileUpload = (
|
||
dayIndex: number,
|
||
messageIndex: number,
|
||
type: "image" | "video" | "file"
|
||
) => {
|
||
message.success(
|
||
`${
|
||
type === "image" ? "图片" : type === "video" ? "视频" : "文件"
|
||
}上传成功`
|
||
);
|
||
};
|
||
|
||
const items = dayPlans.map((plan, dayIndex) => ({
|
||
key: plan.day.toString(),
|
||
label: plan.day === 0 ? "即时消息" : `第${plan.day}天`,
|
||
children: (
|
||
<div className="space-y-4">
|
||
{plan.messages.map((message, messageIndex) => (
|
||
<div key={message.id} className="space-y-4 p-4 bg-gray-50 rounded-lg">
|
||
<div className="flex items-center justify-between">
|
||
<div className="flex items-center space-x-2">
|
||
{plan.day === 0 ? (
|
||
<>
|
||
<div className="w-10">间隔</div>
|
||
<div className="w-40">
|
||
<Input
|
||
type="number"
|
||
value={String(message.sendInterval)}
|
||
onChange={(e) =>
|
||
handleUpdateMessage(dayIndex, messageIndex, {
|
||
sendInterval: Number(e.target.value),
|
||
})
|
||
}
|
||
/>
|
||
</div>
|
||
<Button
|
||
onClick={() => toggleIntervalUnit(dayIndex, messageIndex)}
|
||
className="flex items-center space-x-1"
|
||
>
|
||
<ClockCircleOutlined className="h-3 w-3" />
|
||
<span>
|
||
{message.intervalUnit === "minutes" ? "分钟" : "秒"}
|
||
</span>
|
||
</Button>
|
||
</>
|
||
) : (
|
||
<>
|
||
<div className="font-medium">发送时间</div>
|
||
<div className="flex items-center space-x-1">
|
||
<Input
|
||
type="number"
|
||
min={0}
|
||
max={23}
|
||
value={String(message.scheduledTime?.hour || 0)}
|
||
onChange={(e) =>
|
||
handleUpdateMessage(dayIndex, messageIndex, {
|
||
scheduledTime: {
|
||
...(message.scheduledTime || {
|
||
hour: 0,
|
||
minute: 0,
|
||
second: 0,
|
||
}),
|
||
hour: Number(e.target.value),
|
||
},
|
||
})
|
||
}
|
||
className="w-16"
|
||
/>
|
||
<span>:</span>
|
||
<Input
|
||
type="number"
|
||
min={0}
|
||
max={59}
|
||
value={String(message.scheduledTime?.minute || 0)}
|
||
onChange={(e) =>
|
||
handleUpdateMessage(dayIndex, messageIndex, {
|
||
scheduledTime: {
|
||
...(message.scheduledTime || {
|
||
hour: 0,
|
||
minute: 0,
|
||
second: 0,
|
||
}),
|
||
minute: Number(e.target.value),
|
||
},
|
||
})
|
||
}
|
||
className="w-16"
|
||
/>
|
||
<span>:</span>
|
||
<Input
|
||
type="number"
|
||
min={0}
|
||
max={59}
|
||
value={String(message.scheduledTime?.second || 0)}
|
||
onChange={(e) =>
|
||
handleUpdateMessage(dayIndex, messageIndex, {
|
||
scheduledTime: {
|
||
...(message.scheduledTime || {
|
||
hour: 0,
|
||
minute: 0,
|
||
second: 0,
|
||
}),
|
||
second: Number(e.target.value),
|
||
},
|
||
})
|
||
}
|
||
className="w-16"
|
||
/>
|
||
</div>
|
||
</>
|
||
)}
|
||
</div>
|
||
<Button
|
||
onClick={() => handleRemoveMessage(dayIndex, messageIndex)}
|
||
>
|
||
<CloseOutlined className="h-4 w-4" />
|
||
</Button>
|
||
</div>
|
||
|
||
<div className="flex items-center space-x-2 bg-white p-2 rounded-lg">
|
||
{messageTypes.map((type) => (
|
||
<Button
|
||
key={type.id}
|
||
type={message.type === type.id ? "primary" : "default"}
|
||
onClick={() =>
|
||
handleUpdateMessage(dayIndex, messageIndex, {
|
||
type: type.id as any,
|
||
})
|
||
}
|
||
className="flex flex-col items-center p-2 h-auto"
|
||
>
|
||
<type.icon className="h-4 w-4" />
|
||
</Button>
|
||
))}
|
||
</div>
|
||
|
||
{message.type === "text" && (
|
||
<Input.TextArea
|
||
value={message.content}
|
||
onChange={(e) =>
|
||
handleUpdateMessage(dayIndex, messageIndex, {
|
||
content: e.target.value,
|
||
})
|
||
}
|
||
placeholder="请输入消息内容"
|
||
className="min-h-[100px]"
|
||
/>
|
||
)}
|
||
|
||
{message.type === "miniprogram" && (
|
||
<div className="space-y-4">
|
||
<div className="space-y-2">
|
||
<div className="font-medium">
|
||
标题<span className="text-red-500">*</span>
|
||
</div>
|
||
<Input
|
||
value={message.title}
|
||
onChange={(e) =>
|
||
handleUpdateMessage(dayIndex, messageIndex, {
|
||
title: e.target.value,
|
||
})
|
||
}
|
||
placeholder="请输入小程序标题"
|
||
/>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<div className="font-medium">描述</div>
|
||
<Input
|
||
value={message.description}
|
||
onChange={(e) =>
|
||
handleUpdateMessage(dayIndex, messageIndex, {
|
||
description: e.target.value,
|
||
})
|
||
}
|
||
placeholder="请输入小程序描述"
|
||
/>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<div className="font-medium">
|
||
链接<span className="text-red-500">*</span>
|
||
</div>
|
||
<Input
|
||
value={message.address}
|
||
onChange={(e) =>
|
||
handleUpdateMessage(dayIndex, messageIndex, {
|
||
address: e.target.value,
|
||
})
|
||
}
|
||
placeholder="请输入小程序路径"
|
||
/>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<div className="font-medium">
|
||
封面<span className="text-red-500">*</span>
|
||
</div>
|
||
<div className="border-2 border-dashed rounded-lg p-4 text-center">
|
||
{message.coverImage ? (
|
||
<div className="relative">
|
||
<img
|
||
src={message.coverImage || "/placeholder.svg"}
|
||
alt="封面"
|
||
className="max-w-[200px] mx-auto rounded-lg"
|
||
/>
|
||
<Button
|
||
onClick={() =>
|
||
handleUpdateMessage(dayIndex, messageIndex, {
|
||
coverImage: undefined,
|
||
})
|
||
}
|
||
>
|
||
<CloseOutlined className="h-4 w-4" />
|
||
</Button>
|
||
</div>
|
||
) : (
|
||
<Button
|
||
onClick={() =>
|
||
handleFileUpload(dayIndex, messageIndex, "image")
|
||
}
|
||
>
|
||
<UploadOutlined className="h-4 w-4 mr-2" />
|
||
上传封面
|
||
</Button>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{message.type === "link" && (
|
||
<div className="space-y-4">
|
||
<div className="space-y-2">
|
||
<div className="font-medium">
|
||
标题<span className="text-red-500">*</span>
|
||
</div>
|
||
<Input
|
||
value={message.title}
|
||
onChange={(e) =>
|
||
handleUpdateMessage(dayIndex, messageIndex, {
|
||
title: e.target.value,
|
||
})
|
||
}
|
||
placeholder="请输入链接标题"
|
||
/>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<div className="font-medium">描述</div>
|
||
<Input
|
||
value={message.description}
|
||
onChange={(e) =>
|
||
handleUpdateMessage(dayIndex, messageIndex, {
|
||
description: e.target.value,
|
||
})
|
||
}
|
||
placeholder="请输入链接描述"
|
||
/>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<div className="font-medium">
|
||
链接<span className="text-red-500">*</span>
|
||
</div>
|
||
<Input
|
||
value={message.linkUrl}
|
||
onChange={(e) =>
|
||
handleUpdateMessage(dayIndex, messageIndex, {
|
||
linkUrl: e.target.value,
|
||
})
|
||
}
|
||
placeholder="请输入链接地址"
|
||
/>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<div className="font-medium">
|
||
封面<span className="text-red-500">*</span>
|
||
</div>
|
||
<div className="border-2 border-dashed rounded-lg p-4 text-center">
|
||
{message.coverImage ? (
|
||
<div className="relative">
|
||
<img
|
||
src={message.coverImage || "/placeholder.svg"}
|
||
alt="封面"
|
||
className="max-w-[200px] mx-auto rounded-lg"
|
||
/>
|
||
<Button
|
||
onClick={() =>
|
||
handleUpdateMessage(dayIndex, messageIndex, {
|
||
coverImage: undefined,
|
||
})
|
||
}
|
||
>
|
||
<CloseOutlined className="h-4 w-4" />
|
||
</Button>
|
||
</div>
|
||
) : (
|
||
<Button
|
||
onClick={() =>
|
||
handleFileUpload(dayIndex, messageIndex, "image")
|
||
}
|
||
>
|
||
<UploadOutlined className="h-4 w-4 mr-2" />
|
||
上传封面
|
||
</Button>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{message.type === "group" && (
|
||
<div className="space-y-2">
|
||
<div className="font-medium">
|
||
选择群聊<span className="text-red-500">*</span>
|
||
</div>
|
||
<Button onClick={() => setIsGroupSelectOpen(true)}>
|
||
{selectedGroupId
|
||
? mockGroups.find((g) => g.id === selectedGroupId)?.name
|
||
: "选择邀请入的群"}
|
||
</Button>
|
||
</div>
|
||
)}
|
||
|
||
{(message.type === "image" ||
|
||
message.type === "video" ||
|
||
message.type === "file") && (
|
||
<div className="border-2 border-dashed rounded-lg p-4 text-center">
|
||
<Button
|
||
onClick={() =>
|
||
handleFileUpload(
|
||
dayIndex,
|
||
messageIndex,
|
||
message.type as any
|
||
)
|
||
}
|
||
>
|
||
<UploadOutlined className="h-4 w-4 mr-2" />
|
||
上传
|
||
{message.type === "image"
|
||
? "图片"
|
||
: message.type === "video"
|
||
? "视频"
|
||
: "文件"}
|
||
</Button>
|
||
</div>
|
||
)}
|
||
</div>
|
||
))}
|
||
|
||
<Button onClick={() => handleAddMessage(dayIndex)} className="w-full">
|
||
<PlusOutlined className="w-4 h-4 mr-2" />
|
||
添加消息
|
||
</Button>
|
||
</div>
|
||
),
|
||
}));
|
||
|
||
return (
|
||
<>
|
||
<Layout
|
||
footer={
|
||
<div className="p-4 border-t bg-white">
|
||
<div className="flex justify-between">
|
||
<Button onClick={onPrev}>上一步</Button>
|
||
<Button type="primary" onClick={onNext}>
|
||
下一步
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
}
|
||
>
|
||
<div className="p-4 space-y-6">
|
||
<div className="flex items-center justify-between">
|
||
<h2 className="text-lg font-semibold">消息设置</h2>
|
||
<Button onClick={() => setIsAddDayPlanOpen(true)}>
|
||
<PlusOutlined className="h-4 w-4" />
|
||
</Button>
|
||
</div>
|
||
|
||
<Tabs defaultActiveKey="0" items={items} />
|
||
</div>
|
||
</Layout>
|
||
|
||
{/* 添加天数计划弹窗 */}
|
||
<Modal
|
||
title="添加消息计划"
|
||
open={isAddDayPlanOpen}
|
||
onCancel={() => setIsAddDayPlanOpen(false)}
|
||
onOk={() => {
|
||
handleAddDayPlan();
|
||
setIsAddDayPlanOpen(false);
|
||
}}
|
||
>
|
||
<p className="text-sm text-gray-500 mb-4">选择要添加的消息计划类型</p>
|
||
<Button onClick={handleAddDayPlan} className="w-full">
|
||
添加第 {dayPlans.length} 天计划
|
||
</Button>
|
||
</Modal>
|
||
|
||
{/* 选择群聊弹窗 */}
|
||
<Modal
|
||
title="选择群聊"
|
||
open={isGroupSelectOpen}
|
||
onCancel={() => setIsGroupSelectOpen(false)}
|
||
onOk={() => {
|
||
handleSelectGroup(selectedGroupId);
|
||
setIsGroupSelectOpen(false);
|
||
}}
|
||
>
|
||
<div className="space-y-2">
|
||
{mockGroups.map((group) => (
|
||
<div
|
||
key={group.id}
|
||
className={`p-4 rounded-lg cursor-pointer hover:bg-gray-100 ${
|
||
selectedGroupId === group.id
|
||
? "bg-blue-50 border border-blue-200"
|
||
: ""
|
||
}`}
|
||
onClick={() => handleSelectGroup(group.id)}
|
||
>
|
||
<div className="font-medium">{group.name}</div>
|
||
<div className="text-sm text-gray-500">
|
||
成员数:{group.memberCount}
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</Modal>
|
||
</>
|
||
);
|
||
};
|
||
|
||
export default MessageSettings;
|