更新 index.html 和 manifest.json 中的資源引用版本,並在新計劃頁面中調整海報模板的屬性名稱,將 preview 改為 url,以統一資料結構。

This commit is contained in:
超级老白兔
2025-08-12 15:31:27 +08:00
parent 0baf472473
commit 88a007de7c
9 changed files with 631 additions and 501 deletions

View File

@@ -32,7 +32,7 @@
"name": "vendor"
},
"index.html": {
"file": "assets/index-CeSKt0aC.js",
"file": "assets/index-PKCtfAad.js",
"name": "index",
"src": "index.html",
"isEntry": true,

View File

@@ -11,7 +11,7 @@
</style>
<!-- 引入 uni-app web-view SDK必须 -->
<script type="text/javascript" src="./websdk.js"></script>
<script type="module" crossorigin src="/assets/index-CeSKt0aC.js"></script>
<script type="module" crossorigin src="/assets/index-PKCtfAad.js"></script>
<link rel="modulepreload" crossorigin href="/assets/vendor-2vc8h_ct.js">
<link rel="modulepreload" crossorigin href="/assets/ui-DE3rfvO3.js">
<link rel="modulepreload" crossorigin href="/assets/utils-BEiZ4iZ8.js">

View File

@@ -137,12 +137,47 @@ const MainImgUpload: React.FC<MainImgUploadProps> = ({
// 预览图片
const handlePreview = (url: string) => {
const img = new Image();
// 使用自定义全屏预览,确保不受父级容器限制
const modal = document.createElement("div");
modal.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.9);
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
`;
const img = document.createElement("img");
img.src = url;
const newWindow = window.open();
if (newWindow) {
newWindow.document.write(img.outerHTML);
}
img.style.cssText = `
max-width: 90vw;
max-height: 90vh;
object-fit: contain;
border-radius: 8px;
`;
const closeModal = () => {
document.body.removeChild(modal);
};
modal.addEventListener("click", closeModal);
modal.appendChild(img);
document.body.appendChild(modal);
// 添加键盘事件监听
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === "Escape") {
closeModal();
document.removeEventListener("keydown", handleKeyDown);
}
};
document.addEventListener("keydown", handleKeyDown);
};
// 格式化文件大小
@@ -244,7 +279,20 @@ const MainImgUpload: React.FC<MainImgUploadProps> = ({
/>
</div>
</div>
<div className={style.mainImgPreview}>
<div
className={style.mainImgPreview}
onClick={e => {
// 阻止事件冒泡,防止触发删除操作
e.stopPropagation();
// 点击图片预览区域时,触发文件选择
const uploadInput = document.querySelector(
'input[type="file"]',
) as HTMLInputElement;
if (uploadInput) {
uploadInput.click();
}
}}
>
<img
src={file.url}
alt={file.name}
@@ -257,7 +305,10 @@ const MainImgUpload: React.FC<MainImgUploadProps> = ({
type="text"
size="small"
icon={<EyeOutlined />}
onClick={() => handlePreview(file.url || "")}
onClick={e => {
e.stopPropagation();
handlePreview(file.url || "");
}}
className={style.previewBtn}
/>
)}
@@ -265,7 +316,10 @@ const MainImgUpload: React.FC<MainImgUploadProps> = ({
type="text"
size="small"
icon={<DeleteOutlined />}
onClick={() => handleRemove()}
onClick={e => {
e.stopPropagation();
handleRemove();
}}
className={style.deleteBtn}
/>
</div>

View File

@@ -0,0 +1,54 @@
// 步骤定义 - 只保留三个步骤
import { DeviceSelectionItem } from "@/components/DeviceSelection/data";
import { GroupSelectionItem } from "@/components/GroupSelection/data";
export const steps = [
{ id: 1, title: "步骤一", subtitle: "基础设置" },
{ id: 2, title: "步骤二", subtitle: "好友申请设置" },
{ id: 3, title: "步骤三", subtitle: "消息设置" },
];
// 类型定义
export interface FormData {
name: string;
scenario: number;
status: number;
sceneId: string | number;
remarkType: string;
greeting: string;
addInterval: number;
startTime: string;
endTime: string;
enabled: boolean;
remarkFormat: string;
addFriendInterval: number;
posters: any[]; // 后续可替换为具体Poster类型
device: string[];
customTags: string[];
deveiceGroups: string[];
deveiceGroupsOptions: DeviceSelectionItem[];
wechatGroups: string[];
wechatGroupsOptions: GroupSelectionItem[];
messagePlans: any[];
}
export const defFormData: FormData = {
name: "",
scenario: 1,
status: 0,
sceneId: "",
remarkType: "phone",
greeting: "你好,请通过",
addInterval: 1,
startTime: "09:00",
endTime: "18:00",
enabled: true,
remarkFormat: "",
addFriendInterval: 1,
posters: [],
device: [],
customTags: [],
messagePlans: [],
deveiceGroups: [],
deveiceGroupsOptions: [],
wechatGroups: [],
wechatGroupsOptions: [],
};

View File

@@ -8,62 +8,19 @@ import FriendRequestSettings from "./steps/FriendRequestSettings";
import MessageSettings from "./steps/MessageSettings";
import Layout from "@/components/Layout/Layout";
import StepIndicator from "@/components/StepIndicator";
import { DeviceSelectionItem } from "@/components/DeviceSelection/data";
import {
getScenarioTypes,
createPlan,
getPlanDetail,
updatePlan,
} from "./index.api";
// 步骤定义 - 只保留三个步骤
const steps = [
{ id: 1, title: "步骤一", subtitle: "基础设置" },
{ id: 2, title: "步骤二", subtitle: "好友申请设置" },
{ id: 3, title: "步骤三", subtitle: "消息设置" },
];
// 类型定义
interface FormData {
name: string;
scenario: number;
posters: any[]; // 后续可替换为具体Poster类型
device: string[];
customTags: string[];
remarkType: string;
greeting: string;
addInterval: number;
startTime: string;
endTime: string;
enabled: boolean;
sceneId: string | number;
remarkFormat: string;
addFriendInterval: number;
deveiceGroups: string[];
deveiceGroupsOptions: DeviceSelectionItem[];
}
import { FormData, defFormData, steps } from "./index.data";
export default function NewPlan() {
const router = useNavigate();
const [currentStep, setCurrentStep] = useState(1);
const [formData, setFormData] = useState<FormData>({
name: "",
scenario: 1,
posters: [],
device: [],
customTags: [],
remarkType: "phone",
greeting: "你好,请通过",
addInterval: 1,
startTime: "09:00",
endTime: "18:00",
enabled: true,
sceneId: "",
remarkFormat: "",
addFriendInterval: 1,
deveiceGroups: [],
deveiceGroupsOptions: [],
});
const [formData, setFormData] = useState<FormData>(defFormData);
const [sceneList, setSceneList] = useState<any[]>([]);
const [sceneLoading, setSceneLoading] = useState(true);
@@ -110,6 +67,10 @@ export default function NewPlan() {
tips: detail.tips ?? "",
deveiceGroups: detail.deveiceGroups ?? [],
deveiceGroupsOptions: detail.deveiceGroupsOptions ?? [],
wechatGroups: detail.wechatGroups ?? [],
wechatGroupsOptions: detail.wechatGroupsOptions ?? [],
status: detail.status ?? 0,
messagePlans: detail.messagePlans ?? [],
}));
} else {
if (scenarioId) {

View File

@@ -11,6 +11,7 @@ import styles from "./base.module.scss";
import { posterTemplates } from "./base.data";
import GroupSelection from "@/components/GroupSelection";
import FileUpload from "@/components/Upload/FileUpload";
import { GroupSelectionItem } from "@/components/GroupSelection/data";
interface BasicSettingsProps {
isEdit: boolean;
@@ -66,14 +67,6 @@ const BasicSettings: React.FC<BasicSettingsProps> = ({
questionExtraction: formData.phoneSettings?.questionExtraction ?? true,
});
// 群设置相关状态
const [weixinqunName, setWeixinqunName] = useState(
formData.weixinqunName || "",
);
const [weixinqunNotice, setWeixinqunNotice] = useState(
formData.weixinqunNotice || "",
);
// 新增:自定义海报相关状态
const [customPosters, setCustomPosters] = useState<Material[]>([]);
const [previewUrl, setPreviewUrl] = useState<string | null>(null);
@@ -187,6 +180,13 @@ const BasicSettings: React.FC<BasicSettingsProps> = ({
const openPoster =
formData.scenario !== 1 ? { display: "none" } : { display: "block" };
const handleWechatGroupSelect = (groups: GroupSelectionItem[]) => {
onChange({
...formData,
wechatGroups: groups.map(v => v.id),
wechatGroupsOptions: groups,
});
};
return (
<div className={styles["basic-container"]}>
{/* 场景选择区块 */}
@@ -420,10 +420,8 @@ const BasicSettings: React.FC<BasicSettingsProps> = ({
<div className={styles["basic-group-selection"]}>
<div className={styles["basic-label"]}></div>
<GroupSelection
selectedOptions={formData.groupSelected || []}
onSelect={groups =>
onChange({ ...formData, groupSelected: groups })
}
selectedOptions={formData.wechatGroupsOptions || []}
onSelect={handleWechatGroupSelect}
placeholder="请选择微信群"
className={styles["basic-group-selector"]}
/>

View File

@@ -0,0 +1,336 @@
import React from "react";
import { Input, Button } from "antd";
import { CloseOutlined, ClockCircleOutlined } from "@ant-design/icons";
import styles from "./messages.module.scss";
// 导入Upload组件
import ImageUpload from "@/components/Upload/ImageUpload/ImageUpload";
import VideoUpload from "@/components/Upload/VideoUpload";
import FileUpload from "@/components/Upload/FileUpload";
import MainImgUpload from "@/components/Upload/MainImgUpload";
// 导入GroupSelection组件
import GroupSelection from "@/components/GroupSelection";
import { GroupSelectionItem } from "@/components/GroupSelection/data";
import { MessageContentItem, messageTypes } from "./base.data";
interface MessageCardProps {
message: MessageContentItem;
dayIndex: number;
messageIndex: number;
planDay: number;
onUpdateMessage: (
dayIndex: number,
messageIndex: number,
updates: Partial<MessageContentItem>,
) => void;
onRemoveMessage: (dayIndex: number, messageIndex: number) => void;
onToggleIntervalUnit: (dayIndex: number, messageIndex: number) => void;
}
const MessageCard: React.FC<MessageCardProps> = ({
message,
dayIndex,
messageIndex,
planDay,
onUpdateMessage,
onRemoveMessage,
onToggleIntervalUnit,
}) => {
return (
<div className={styles["messages-message-card"]}>
<div className={styles["messages-message-header"]}>
{/* 时间/间隔设置 */}
<div className={styles["messages-message-header-content"]}>
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
{planDay === 0 ? (
<>
<span style={{ minWidth: 36 }}></span>
<Input
type="number"
value={String(message.sendInterval || 5)}
onChange={e =>
onUpdateMessage(dayIndex, messageIndex, {
sendInterval: Number(e.target.value),
})
}
style={{ width: 60 }}
/>
<Button
size="small"
onClick={() => onToggleIntervalUnit(dayIndex, messageIndex)}
>
<ClockCircleOutlined />
{message.intervalUnit === "minutes" ? "分钟" : "秒"}
</Button>
</>
) : (
<>
<span style={{ minWidth: 60 }}></span>
<Input
type="number"
min={0}
max={23}
value={String(message.scheduledTime?.hour || 9)}
onChange={e =>
onUpdateMessage(dayIndex, messageIndex, {
scheduledTime: {
...(message.scheduledTime || {
hour: 9,
minute: 0,
second: 0,
}),
hour: Number(e.target.value),
},
})
}
style={{ width: 40 }}
/>
<span>:</span>
<Input
type="number"
min={0}
max={59}
value={String(message.scheduledTime?.minute || 0)}
onChange={e =>
onUpdateMessage(dayIndex, messageIndex, {
scheduledTime: {
...(message.scheduledTime || {
hour: 9,
minute: 0,
second: 0,
}),
minute: Number(e.target.value),
},
})
}
style={{ width: 40 }}
/>
<span>:</span>
<Input
type="number"
min={0}
max={59}
value={String(message.scheduledTime?.second || 0)}
onChange={e =>
onUpdateMessage(dayIndex, messageIndex, {
scheduledTime: {
...(message.scheduledTime || {
hour: 9,
minute: 0,
second: 0,
}),
second: Number(e.target.value),
},
})
}
style={{ width: 40 }}
/>
</>
)}
</div>
<button
className={styles["messages-message-remove-btn"]}
onClick={() => onRemoveMessage(dayIndex, messageIndex)}
title="删除"
>
<CloseOutlined />
</button>
</div>
{/* 类型切换按钮 */}
<div className={styles["messages-message-type-btns"]}>
{messageTypes.map(type => (
<Button
key={type.id}
type={message.type === type.id ? "primary" : "default"}
onClick={() =>
onUpdateMessage(dayIndex, messageIndex, {
type: type.id as any,
})
}
className={styles["messages-message-type-btn"]}
>
<type.icon />
</Button>
))}
</div>
</div>
<div className={styles["messages-message-content"]}>
{/* 文本消息 */}
{message.type === "text" && (
<Input.TextArea
value={message.content}
onChange={e =>
onUpdateMessage(dayIndex, messageIndex, {
content: e.target.value,
})
}
placeholder="请输入消息内容"
autoSize={{ minRows: 3, maxRows: 6 }}
/>
)}
{/* 小程序消息 */}
{message.type === "miniprogram" && (
<>
<Input
value={message.title}
onChange={e =>
onUpdateMessage(dayIndex, messageIndex, {
title: e.target.value,
})
}
placeholder="请输入小程序标题"
style={{ marginBottom: 8 }}
/>
<Input
value={message.description}
onChange={e =>
onUpdateMessage(dayIndex, messageIndex, {
description: e.target.value,
})
}
placeholder="请输入小程序描述"
style={{ marginBottom: 8 }}
/>
<Input
value={message.address}
onChange={e =>
onUpdateMessage(dayIndex, messageIndex, {
address: e.target.value,
})
}
placeholder="请输入小程序路径"
style={{ marginBottom: 8 }}
/>
<div style={{ marginBottom: 8 }}>
<MainImgUpload
value={message.content || ""}
onChange={url =>
onUpdateMessage(dayIndex, messageIndex, {
content: url,
})
}
maxSize={5}
showPreview={true}
/>
</div>
</>
)}
{/* 链接消息 */}
{message.type === "link" && (
<>
<Input
value={message.title}
onChange={e =>
onUpdateMessage(dayIndex, messageIndex, {
title: e.target.value,
})
}
placeholder="请输入链接标题"
style={{ marginBottom: 8 }}
/>
<Input
value={message.description}
onChange={e =>
onUpdateMessage(dayIndex, messageIndex, {
description: e.target.value,
})
}
placeholder="请输入链接描述"
style={{ marginBottom: 8 }}
/>
<Input
value={message.linkUrl}
onChange={e =>
onUpdateMessage(dayIndex, messageIndex, {
linkUrl: e.target.value,
})
}
placeholder="请输入链接地址"
style={{ marginBottom: 8 }}
/>
<div style={{ marginBottom: 8 }}>
<MainImgUpload
value={message.coverImage || ""}
onChange={url =>
onUpdateMessage(dayIndex, messageIndex, {
coverImage: url,
})
}
maxSize={5}
showPreview={true}
/>
</div>
</>
)}
{/* 群邀请消息 */}
{message.type === "group" && (
<div style={{ marginBottom: 8 }}>
<GroupSelection
selectedOptions={message.groupOptions || []}
onSelect={(groups: GroupSelectionItem[]) => {
onUpdateMessage(dayIndex, messageIndex, {
groupIds: groups.map(v => v.id),
groupOptions: groups,
});
}}
placeholder="选择邀请入的群"
showSelectedList={true}
selectedListMaxHeight={200}
/>
</div>
)}
{/* 图片消息 */}
{message.type === "image" && (
<div style={{ marginBottom: 8 }}>
<ImageUpload
value={message.content ? [message.content] : []}
onChange={urls =>
onUpdateMessage(dayIndex, messageIndex, {
content: urls[0] || "",
})
}
count={1}
accept="image/*"
/>
</div>
)}
{/* 视频消息 */}
{message.type === "video" && (
<div style={{ marginBottom: 8 }}>
<VideoUpload
value={message.content || ""}
onChange={url => {
const videoUrl = Array.isArray(url) ? url[0] || "" : url;
onUpdateMessage(dayIndex, messageIndex, {
content: videoUrl,
});
}}
maxSize={50}
maxCount={5}
showPreview={true}
/>
</div>
)}
{/* 文件消息 */}
{message.type === "file" && (
<div style={{ marginBottom: 8 }}>
<FileUpload
value={message.content || ""}
onChange={url => {
const fileUrl = Array.isArray(url) ? url[0] || "" : url;
onUpdateMessage(dayIndex, messageIndex, {
content: fileUrl,
});
}}
maxSize={10}
maxCount={10}
showPreview={true}
acceptTypes={["excel", "word", "ppt"]}
/>
</div>
)}
</div>
</div>
);
};
export default MessageCard;

View File

@@ -1,96 +1,52 @@
import React, { useState } from "react";
import { Input, Button, Tabs, Modal, message } from "antd";
import {
PlusOutlined,
CloseOutlined,
ClockCircleOutlined,
MessageOutlined,
PictureOutlined,
VideoCameraOutlined,
FileOutlined,
AppstoreOutlined,
LinkOutlined,
TeamOutlined,
} from "@ant-design/icons";
import { Button, Tabs, Modal, message } from "antd";
import { PlusOutlined, CloseOutlined } from "@ant-design/icons";
import styles from "./messages.module.scss";
// 导入Upload组件
import ImageUpload from "@/components/Upload/ImageUpload/ImageUpload";
import VideoUpload from "@/components/Upload/VideoUpload";
import FileUpload from "@/components/Upload/FileUpload";
import MainImgUpload from "@/components/Upload/MainImgUpload";
// 导入GroupSelection组件
import GroupSelection from "@/components/GroupSelection";
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;
groupIds?: string[]; // 改为数组以支持GroupSelection组件
linkUrl?: string;
}
interface DayPlan {
day: number;
messages: MessageContent[];
}
interface MessageSettingsProps {
formData: any;
onChange: (data: any) => 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: "邀请入群" },
];
import {
MessageContentItem,
MessageContentGroup,
MessageSettingsProps,
} from "./base.data";
import MessageCard from "./MessageCard";
const MessageSettings: React.FC<MessageSettingsProps> = ({
formData,
onChange,
}) => {
const [dayPlans, setDayPlans] = useState<DayPlan[]>([
{
day: 0,
messages: [
{
id: "1",
type: "text",
content: "",
sendInterval: 5,
intervalUnit: "seconds",
},
],
},
]);
const [isAddDayPlanOpen, setIsAddDayPlanOpen] = useState(false);
// 获取当前的消息计划,如果没有则使用默认值
const getCurrentMessagePlans = (): MessageContentGroup[] => {
if (formData.messagePlans && formData.messagePlans.length > 0) {
return formData.messagePlans;
}
return [
{
day: 0,
messages: [
{
id: "1",
type: "text",
content: "",
sendInterval: 5,
intervalUnit: "seconds",
},
],
},
];
};
// 添加新消息
const handleAddMessage = (dayIndex: number, type = "text") => {
const updatedPlans = [...dayPlans];
const newMessage: MessageContent = {
const currentPlans = getCurrentMessagePlans();
const updatedPlans = [...currentPlans];
const newMessage: MessageContentItem = {
id: Date.now().toString(),
type: type as MessageContent["type"],
type: type as MessageContentItem["type"],
content: "",
};
if (dayPlans[dayIndex].day === 0) {
if (currentPlans[dayIndex].day === 0) {
newMessage.sendInterval = 5;
newMessage.intervalUnit = "seconds";
} else {
@@ -102,7 +58,6 @@ const MessageSettings: React.FC<MessageSettingsProps> = ({
}
updatedPlans[dayIndex].messages.push(newMessage);
setDayPlans(updatedPlans);
onChange({ ...formData, messagePlans: updatedPlans });
};
@@ -110,37 +65,39 @@ const MessageSettings: React.FC<MessageSettingsProps> = ({
const handleUpdateMessage = (
dayIndex: number,
messageIndex: number,
updates: Partial<MessageContent>,
updates: Partial<MessageContentItem>,
) => {
const updatedPlans = [...dayPlans];
const currentPlans = getCurrentMessagePlans();
const updatedPlans = [...currentPlans];
updatedPlans[dayIndex].messages[messageIndex] = {
...updatedPlans[dayIndex].messages[messageIndex],
...updates,
};
setDayPlans(updatedPlans);
onChange({ ...formData, messagePlans: updatedPlans });
};
// 删除消息
const handleRemoveMessage = (dayIndex: number, messageIndex: number) => {
const updatedPlans = [...dayPlans];
const currentPlans = getCurrentMessagePlans();
const updatedPlans = [...currentPlans];
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 currentPlans = getCurrentMessagePlans();
const message = currentPlans[dayIndex].messages[messageIndex];
const newUnit = message.intervalUnit === "minutes" ? "seconds" : "minutes";
handleUpdateMessage(dayIndex, messageIndex, { intervalUnit: newUnit });
};
// 添加新的天数计划
const handleAddDayPlan = () => {
const newDay = dayPlans.length;
setDayPlans([
...dayPlans,
const currentPlans = getCurrentMessagePlans();
const newDay = currentPlans.length;
const updatedPlans = [
...currentPlans,
{
day: newDay,
messages: [
@@ -156,7 +113,8 @@ const MessageSettings: React.FC<MessageSettingsProps> = ({
},
],
},
]);
];
onChange({ ...formData, messagePlans: updatedPlans });
setIsAddDayPlanOpen(false);
message.success(`已添加第${newDay}天的消息计划`);
};
@@ -168,363 +126,82 @@ const MessageSettings: React.FC<MessageSettingsProps> = ({
return;
}
const currentPlans = getCurrentMessagePlans();
Modal.confirm({
title: "确认删除",
content: `确定要删除第${dayPlans[dayIndex].day}天的消息计划吗?`,
content: `确定要删除第${currentPlans[dayIndex].day}天的消息计划吗?`,
onOk: () => {
const updatedPlans = dayPlans.filter((_, index) => index !== dayIndex);
const updatedPlans = currentPlans.filter(
(_, index) => index !== dayIndex,
);
// 重新计算天数
const recalculatedPlans = updatedPlans.map((plan, index) => ({
...plan,
day: index,
}));
setDayPlans(recalculatedPlans);
onChange({ ...formData, messagePlans: recalculatedPlans });
message.success(`已删除第${dayPlans[dayIndex].day}天的消息计划`);
message.success(`已删除第${currentPlans[dayIndex].day}天的消息计划`);
},
});
};
const items = dayPlans.map((plan, dayIndex) => ({
key: plan.day.toString(),
label: (
<div
style={{
display: "flex",
alignItems: "center",
gap: "2px",
}}
>
<span>{plan.day === 0 ? "即时消息" : `${plan.day}`}</span>
{dayIndex > 0 && (
<Button
type="text"
size="small"
icon={<CloseOutlined />}
onClick={e => {
e.stopPropagation();
handleRemoveDayPlan(dayIndex);
}}
style={{
padding: "0 4px",
minWidth: "auto",
color: "#ff4d4f",
fontSize: "12px",
}}
title="删除此天计划"
/>
)}
</div>
),
children: (
<div className={styles["messages-day-panel"]}>
{plan.messages.map((message, messageIndex) => (
<div key={message.id} className={styles["messages-message-card"]}>
<div className={styles["messages-message-header"]}>
{/* 时间/间隔设置 */}
<div className={styles["messages-message-header-content"]}>
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
{plan.day === 0 ? (
<>
<span style={{ minWidth: 36 }}></span>
<Input
type="number"
value={String(message.sendInterval || 5)}
onChange={e =>
handleUpdateMessage(dayIndex, messageIndex, {
sendInterval: Number(e.target.value),
})
}
style={{ width: 60 }}
/>
<Button
size="small"
onClick={() =>
toggleIntervalUnit(dayIndex, messageIndex)
}
>
<ClockCircleOutlined />
{message.intervalUnit === "minutes" ? "分钟" : "秒"}
</Button>
</>
) : (
<>
<span style={{ minWidth: 60 }}></span>
<Input
type="number"
min={0}
max={23}
value={String(message.scheduledTime?.hour || 9)}
onChange={e =>
handleUpdateMessage(dayIndex, messageIndex, {
scheduledTime: {
...(message.scheduledTime || {
hour: 9,
minute: 0,
second: 0,
}),
hour: Number(e.target.value),
},
})
}
style={{ width: 40 }}
/>
<span>:</span>
<Input
type="number"
min={0}
max={59}
value={String(message.scheduledTime?.minute || 0)}
onChange={e =>
handleUpdateMessage(dayIndex, messageIndex, {
scheduledTime: {
...(message.scheduledTime || {
hour: 9,
minute: 0,
second: 0,
}),
minute: Number(e.target.value),
},
})
}
style={{ width: 40 }}
/>
<span>:</span>
<Input
type="number"
min={0}
max={59}
value={String(message.scheduledTime?.second || 0)}
onChange={e =>
handleUpdateMessage(dayIndex, messageIndex, {
scheduledTime: {
...(message.scheduledTime || {
hour: 9,
minute: 0,
second: 0,
}),
second: Number(e.target.value),
},
})
}
style={{ width: 40 }}
/>
</>
)}
</div>
<button
className={styles["messages-message-remove-btn"]}
onClick={() => handleRemoveMessage(dayIndex, messageIndex)}
title="删除"
>
<CloseOutlined />
</button>
</div>
{/* 类型切换按钮 */}
<div className={styles["messages-message-type-btns"]}>
{messageTypes.map(type => (
<Button
key={type.id}
type={message.type === type.id ? "primary" : "default"}
onClick={() =>
handleUpdateMessage(dayIndex, messageIndex, {
type: type.id as any,
})
}
className={styles["messages-message-type-btn"]}
>
<type.icon />
</Button>
))}
</div>
</div>
<div className={styles["messages-message-content"]}>
{/* 文本消息 */}
{message.type === "text" && (
<Input.TextArea
value={message.content}
onChange={e =>
handleUpdateMessage(dayIndex, messageIndex, {
content: e.target.value,
})
}
placeholder="请输入消息内容"
autoSize={{ minRows: 3, maxRows: 6 }}
/>
)}
{/* 小程序消息 */}
{message.type === "miniprogram" && (
<>
<Input
value={message.title}
onChange={e =>
handleUpdateMessage(dayIndex, messageIndex, {
title: e.target.value,
})
}
placeholder="请输入小程序标题"
style={{ marginBottom: 8 }}
/>
<Input
value={message.description}
onChange={e =>
handleUpdateMessage(dayIndex, messageIndex, {
description: e.target.value,
})
}
placeholder="请输入小程序描述"
style={{ marginBottom: 8 }}
/>
<Input
value={message.address}
onChange={e =>
handleUpdateMessage(dayIndex, messageIndex, {
address: e.target.value,
})
}
placeholder="请输入小程序路径"
style={{ marginBottom: 8 }}
/>
<div style={{ marginBottom: 8 }}>
<MainImgUpload
value={message.coverImage || ""}
onChange={url =>
handleUpdateMessage(dayIndex, messageIndex, {
coverImage: url,
})
}
maxSize={5}
showPreview={true}
/>
</div>
</>
)}
{/* 链接消息 */}
{message.type === "link" && (
<>
<Input
value={message.title}
onChange={e =>
handleUpdateMessage(dayIndex, messageIndex, {
title: e.target.value,
})
}
placeholder="请输入链接标题"
style={{ marginBottom: 8 }}
/>
<Input
value={message.description}
onChange={e =>
handleUpdateMessage(dayIndex, messageIndex, {
description: e.target.value,
})
}
placeholder="请输入链接描述"
style={{ marginBottom: 8 }}
/>
<Input
value={message.linkUrl}
onChange={e =>
handleUpdateMessage(dayIndex, messageIndex, {
linkUrl: e.target.value,
})
}
placeholder="请输入链接地址"
style={{ marginBottom: 8 }}
/>
<div style={{ marginBottom: 8 }}>
<MainImgUpload
value={message.coverImage || ""}
onChange={url =>
handleUpdateMessage(dayIndex, messageIndex, {
coverImage: url,
})
}
maxSize={5}
showPreview={true}
/>
</div>
</>
)}
{/* 群邀请消息 */}
{message.type === "group" && (
<div style={{ marginBottom: 8 }}>
<GroupSelection
selectedOptions={message.groupIds || []}
onSelect={groupIds =>
handleUpdateMessage(dayIndex, messageIndex, {
groupIds: groupIds,
})
}
placeholder="选择邀请入的群"
showSelectedList={true}
selectedListMaxHeight={200}
/>
</div>
)}
{/* 图片消息 */}
{message.type === "image" && (
<div style={{ marginBottom: 8 }}>
<ImageUpload
value={message.content ? [message.content] : []}
onChange={urls =>
handleUpdateMessage(dayIndex, messageIndex, {
content: urls[0] || "",
})
}
count={10}
accept="image/*"
/>
</div>
)}
{/* 视频消息 */}
{message.type === "video" && (
<div style={{ marginBottom: 8 }}>
<VideoUpload
value={message.content || ""}
onChange={url => {
const videoUrl = Array.isArray(url) ? url[0] || "" : url;
handleUpdateMessage(dayIndex, messageIndex, {
content: videoUrl,
});
}}
maxSize={50}
maxCount={5}
showPreview={true}
/>
</div>
)}
{/* 文件消息 */}
{message.type === "file" && (
<div style={{ marginBottom: 8 }}>
<FileUpload
value={message.content || ""}
onChange={url => {
const fileUrl = Array.isArray(url) ? url[0] || "" : url;
handleUpdateMessage(dayIndex, messageIndex, {
content: fileUrl,
});
}}
maxSize={10}
maxCount={10}
showPreview={true}
acceptTypes={["excel", "word", "ppt"]}
/>
</div>
)}
</div>
</div>
))}
<Button
onClick={() => handleAddMessage(dayIndex)}
className={styles["messages-add-message-btn"]}
const items = getCurrentMessagePlans().map(
(plan: MessageContentGroup, dayIndex: number) => ({
key: plan.day.toString(),
label: (
<div
style={{
display: "flex",
alignItems: "center",
gap: "2px",
}}
>
<PlusOutlined className="w-4 h-4 mr-2" />
</Button>
</div>
),
}));
<span>{plan.day === 0 ? "即时消息" : `${plan.day}`}</span>
{dayIndex > 0 && (
<Button
type="text"
size="small"
icon={<CloseOutlined />}
onClick={e => {
e.stopPropagation();
handleRemoveDayPlan(dayIndex);
}}
style={{
padding: "0 4px",
minWidth: "auto",
color: "#ff4d4f",
fontSize: "12px",
}}
title="删除此天计划"
/>
)}
</div>
),
children: (
<div className={styles["messages-day-panel"]}>
{plan.messages.map((message, messageIndex) => (
<MessageCard
key={message.id}
message={message}
dayIndex={dayIndex}
messageIndex={messageIndex}
planDay={plan.day}
onUpdateMessage={handleUpdateMessage}
onRemoveMessage={handleRemoveMessage}
onToggleIntervalUnit={toggleIntervalUnit}
/>
))}
<Button
onClick={() => handleAddMessage(dayIndex)}
className={styles["messages-add-message-btn"]}
>
<PlusOutlined className="w-4 h-4 mr-2" />
</Button>
</div>
),
}),
);
return (
<div className={styles["messages-container"]}>
@@ -555,7 +232,7 @@ const MessageSettings: React.FC<MessageSettingsProps> = ({
onClick={handleAddDayPlan}
className={styles["messages-modal-btn"]}
>
{dayPlans.length}
{getCurrentMessagePlans().length}
</Button>
</Modal>
</div>

View File

@@ -30,3 +30,53 @@ export const posterTemplates = [
url: "https://hebbkx1anhila5yf.public.blob.vercel-storage.com/%E7%82%B9%E5%87%BB%E6%8A%A5%E5%90%8D-Mj0nnva0BiASeDAIhNNaRRAbjPgjEj.gif",
},
];
// ========================================
import {
MessageOutlined,
PictureOutlined,
VideoCameraOutlined,
FileOutlined,
AppstoreOutlined,
LinkOutlined,
TeamOutlined,
} from "@ant-design/icons";
export interface MessageContentItem {
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;
groupIds?: string[]; // 改为数组以支持GroupSelection组件
groupOptions?: any[]; // 添加群选项数组
linkUrl?: string;
}
export interface MessageContentGroup {
day: number;
messages: MessageContentItem[];
}
export interface MessageSettingsProps {
formData: any;
onChange: (data: any) => void;
}
// 消息类型配置
export 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: "邀请入群" },
];