FEAT => 本次更新项目为:
This commit is contained in:
@@ -27,7 +27,10 @@ const PowerCenter: React.FC = () => {
|
||||
</div>
|
||||
<div className={styles.categoryInfo}>
|
||||
<h2 className={styles.categoryTitle}>{category.title}</h2>
|
||||
<span className={styles.categoryCount}>
|
||||
<span
|
||||
className={styles.categoryCount}
|
||||
style={{ backgroundColor: category.color, color: "#ffffff" }}
|
||||
>
|
||||
{category.count}个功能
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
.container {
|
||||
padding: 24px;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.header {
|
||||
margin-bottom: 24px;
|
||||
|
||||
h1 {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #262626;
|
||||
margin: 0 0 8px 0;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 14px;
|
||||
color: #8c8c8c;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 300px;
|
||||
background: #fafafa;
|
||||
border: 1px dashed #d9d9d9;
|
||||
border-radius: 6px;
|
||||
|
||||
p {
|
||||
font-size: 16px;
|
||||
color: #8c8c8c;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
import React from "react";
|
||||
import PowerNavigation from "@/components/PowerNavtion";
|
||||
import styles from "./index.module.scss";
|
||||
|
||||
const MomentsMarketing: React.FC = () => {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<PowerNavigation
|
||||
title="朋友圈营销"
|
||||
subtitle="AI智能生成朋友圈内容,提升品牌曝光度"
|
||||
showBackButton={true}
|
||||
backButtonText="返回功能中心"
|
||||
/>
|
||||
<div className={styles.content}>
|
||||
{/* 功能内容待开发 */}
|
||||
<div className={styles.placeholder}>
|
||||
<p>朋友圈营销功能正在开发中...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MomentsMarketing;
|
||||
@@ -1,215 +0,0 @@
|
||||
.header {
|
||||
margin-bottom: 24px;
|
||||
|
||||
h1 {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #262626;
|
||||
margin: 0 0 8px 0;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 14px;
|
||||
color: #8c8c8c;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.stepsContainer {
|
||||
padding: 24px;
|
||||
background: #fff;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.steps {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.stepContent {
|
||||
margin: 15px;
|
||||
}
|
||||
|
||||
.stepCard {
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
border-radius: 8px;
|
||||
|
||||
:global(.ant-card-head) {
|
||||
background: #f8f9fa;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
|
||||
.ant-card-head-title {
|
||||
font-weight: 600;
|
||||
color: #495057;
|
||||
|
||||
.anticon {
|
||||
margin-right: 8px;
|
||||
color: #1890ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ageRange {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.ant-input-number {
|
||||
width: 80px;
|
||||
}
|
||||
}
|
||||
|
||||
.estimatedCount {
|
||||
margin-top: 24px;
|
||||
padding: 16px;
|
||||
background: #e6f7ff;
|
||||
border: 1px solid #91d5ff;
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
.anticon {
|
||||
color: #1890ff;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 14px;
|
||||
color: #262626;
|
||||
|
||||
strong {
|
||||
color: #1890ff;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.variableList {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
|
||||
.ant-tag {
|
||||
margin: 0;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.messagePreview {
|
||||
margin-top: 24px;
|
||||
|
||||
h4 {
|
||||
margin-bottom: 12px;
|
||||
color: #262626;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.previewContent {
|
||||
padding: 16px;
|
||||
background: #f5f5f5;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 6px;
|
||||
min-height: 80px;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
color: #262626;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.summary {
|
||||
h4 {
|
||||
margin-bottom: 16px;
|
||||
color: #262626;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.summaryGrid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.summaryItem {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 12px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 6px;
|
||||
|
||||
.anticon {
|
||||
color: #1890ff;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 14px;
|
||||
color: #262626;
|
||||
}
|
||||
}
|
||||
|
||||
.stepActions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 24px 0;
|
||||
background: #fff;
|
||||
|
||||
.ant-btn {
|
||||
min-width: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
// 响应式设计
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.stepsContainer {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.summaryGrid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.ageRange {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
|
||||
span {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 表单样式优化
|
||||
:global {
|
||||
.ant-form-item-label > label {
|
||||
font-weight: 500;
|
||||
color: #262626;
|
||||
}
|
||||
|
||||
.ant-checkbox-group {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px 16px;
|
||||
}
|
||||
|
||||
.ant-upload-list-picture-card .ant-upload-list-item {
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.ant-steps-item-title {
|
||||
font-weight: 500 !important;
|
||||
}
|
||||
}
|
||||
@@ -1,726 +0,0 @@
|
||||
import React, { useState, useEffect, useCallback } from "react";
|
||||
import Layout from "@/components/Layout/LayoutFiexd";
|
||||
import {
|
||||
Card,
|
||||
Button,
|
||||
Steps,
|
||||
Form,
|
||||
Input,
|
||||
Select,
|
||||
Checkbox,
|
||||
Radio,
|
||||
DatePicker,
|
||||
TimePicker,
|
||||
InputNumber,
|
||||
Upload,
|
||||
Tag,
|
||||
Divider,
|
||||
Space,
|
||||
message,
|
||||
} from "antd";
|
||||
import {
|
||||
UserOutlined,
|
||||
MessageOutlined,
|
||||
SendOutlined,
|
||||
PlusOutlined,
|
||||
TagsOutlined,
|
||||
ClockCircleOutlined,
|
||||
TeamOutlined,
|
||||
AimOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import dayjs from "dayjs";
|
||||
import type { UploadFile } from "antd";
|
||||
import PowerNavigation from "@/components/PowerNavtion";
|
||||
import styles from "./index.module.scss";
|
||||
|
||||
const { Step } = Steps;
|
||||
const { TextArea } = Input;
|
||||
const { Option } = Select;
|
||||
|
||||
interface CustomerFilter {
|
||||
tags: string[];
|
||||
regions: string[];
|
||||
ageRange: [number, number];
|
||||
gender: "all" | "male" | "female";
|
||||
lastContactTime: "all" | "7days" | "30days" | "90days" | "180days";
|
||||
purchaseHistory: "all" | "purchased" | "no-purchase" | "high-value";
|
||||
}
|
||||
|
||||
interface MessageContent {
|
||||
type: "text" | "image" | "mixed";
|
||||
text: string;
|
||||
images: UploadFile[];
|
||||
variables: string[];
|
||||
}
|
||||
|
||||
interface SendSettings {
|
||||
sendMode: "immediate" | "scheduled";
|
||||
scheduledTime?: string;
|
||||
sendInterval: number;
|
||||
maxPerDay: number;
|
||||
timeRange: [string, string];
|
||||
}
|
||||
|
||||
const PrecisionSend: React.FC = () => {
|
||||
const [currentStep, setCurrentStep] = useState(0);
|
||||
const [form] = Form.useForm();
|
||||
const [customerFilter, setCustomerFilter] = useState<CustomerFilter>({
|
||||
tags: [],
|
||||
regions: [],
|
||||
ageRange: [18, 65],
|
||||
gender: "all",
|
||||
lastContactTime: "all",
|
||||
purchaseHistory: "all",
|
||||
});
|
||||
const [messageContent, setMessageContent] = useState<MessageContent>({
|
||||
type: "text",
|
||||
text: "",
|
||||
images: [],
|
||||
variables: [],
|
||||
});
|
||||
const [sendSettings, setSendSettings] = useState<SendSettings>({
|
||||
sendMode: "immediate",
|
||||
sendInterval: 5,
|
||||
maxPerDay: 100,
|
||||
timeRange: ["09:00", "18:00"],
|
||||
});
|
||||
const [estimatedCount, setEstimatedCount] = useState(0);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const customerTags = [
|
||||
"高价值客户",
|
||||
"潜在客户",
|
||||
"活跃用户",
|
||||
"沉默用户",
|
||||
"VIP客户",
|
||||
"新客户",
|
||||
"老客户",
|
||||
"流失客户",
|
||||
];
|
||||
|
||||
const regions = [
|
||||
"北京",
|
||||
"上海",
|
||||
"广州",
|
||||
"深圳",
|
||||
"杭州",
|
||||
"南京",
|
||||
"成都",
|
||||
"武汉",
|
||||
"西安",
|
||||
"重庆",
|
||||
"天津",
|
||||
"苏州",
|
||||
];
|
||||
|
||||
const messageVariables = [
|
||||
"{客户姓名}",
|
||||
"{公司名称}",
|
||||
"{联系人}",
|
||||
"{产品名称}",
|
||||
"{优惠金额}",
|
||||
"{到期时间}",
|
||||
"{客服电话}",
|
||||
"{官网地址}",
|
||||
];
|
||||
|
||||
const handleNext = () => {
|
||||
if (currentStep < 2) {
|
||||
setCurrentStep(currentStep + 1);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePrev = () => {
|
||||
if (currentStep > 0) {
|
||||
setCurrentStep(currentStep - 1);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
// 验证必填项
|
||||
if (!messageContent.text && messageContent.images.length === 0) {
|
||||
message.error("请输入消息内容或上传图片");
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
sendSettings.sendMode === "scheduled" &&
|
||||
!sendSettings.scheduledTime
|
||||
) {
|
||||
message.error("请选择发送时间");
|
||||
return;
|
||||
}
|
||||
|
||||
if (estimatedCount === 0) {
|
||||
message.error("没有符合条件的客户,请调整筛选条件");
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
const hide = message.loading("正在创建发送任务...", 0);
|
||||
|
||||
// 模拟API调用
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
|
||||
hide();
|
||||
message.success(
|
||||
`发送任务已创建成功!预计发送给 ${estimatedCount} 位客户`,
|
||||
);
|
||||
|
||||
// 重置表单
|
||||
setCurrentStep(0);
|
||||
form.resetFields();
|
||||
setCustomerFilter({
|
||||
tags: [],
|
||||
regions: [],
|
||||
ageRange: [18, 65],
|
||||
gender: "all",
|
||||
lastContactTime: "all",
|
||||
purchaseHistory: "all",
|
||||
});
|
||||
setMessageContent({
|
||||
type: "text",
|
||||
text: "",
|
||||
images: [],
|
||||
variables: [],
|
||||
});
|
||||
setSendSettings({
|
||||
sendMode: "immediate",
|
||||
sendInterval: 5,
|
||||
maxPerDay: 100,
|
||||
timeRange: ["09:00", "18:00"],
|
||||
});
|
||||
} catch (error) {
|
||||
message.error("提交失败,请检查网络连接后重试");
|
||||
console.error("Submit error:", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 计算预估客户数量
|
||||
const calculateEstimatedCount = useCallback(() => {
|
||||
let baseCount = 1000; // 假设基础客户数
|
||||
|
||||
// 根据筛选条件调整数量
|
||||
if (customerFilter.tags.length > 0) {
|
||||
baseCount = Math.floor(
|
||||
baseCount * (0.9 - customerFilter.tags.length * 0.1),
|
||||
);
|
||||
}
|
||||
if (customerFilter.regions.length > 0) {
|
||||
baseCount = Math.floor(
|
||||
baseCount * (0.95 - customerFilter.regions.length * 0.05),
|
||||
);
|
||||
}
|
||||
if (customerFilter.gender !== "all") {
|
||||
baseCount = Math.floor(baseCount * 0.5);
|
||||
}
|
||||
if (customerFilter.purchaseHistory !== "all") {
|
||||
baseCount = Math.floor(baseCount * 0.6);
|
||||
}
|
||||
|
||||
setEstimatedCount(Math.max(baseCount, 0));
|
||||
}, [customerFilter]);
|
||||
|
||||
useEffect(() => {
|
||||
calculateEstimatedCount();
|
||||
}, [calculateEstimatedCount]);
|
||||
|
||||
const renderStepContent = () => {
|
||||
switch (currentStep) {
|
||||
case 0:
|
||||
return (
|
||||
<Card
|
||||
title={
|
||||
<>
|
||||
<UserOutlined /> 客户筛选
|
||||
</>
|
||||
}
|
||||
className={styles.stepCard}
|
||||
>
|
||||
<Form form={form} layout="vertical">
|
||||
<Form.Item label="客户标签" name="tags">
|
||||
<Checkbox.Group
|
||||
options={customerTags.map(tag => ({
|
||||
label: tag,
|
||||
value: tag,
|
||||
}))}
|
||||
value={customerFilter.tags}
|
||||
onChange={values =>
|
||||
setCustomerFilter({
|
||||
...customerFilter,
|
||||
tags: values as string[],
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="地区筛选" name="regions">
|
||||
<Select
|
||||
mode="multiple"
|
||||
placeholder="选择目标地区"
|
||||
value={customerFilter.regions}
|
||||
onChange={values =>
|
||||
setCustomerFilter({ ...customerFilter, regions: values })
|
||||
}
|
||||
>
|
||||
{regions.map(region => (
|
||||
<Option key={region} value={region}>
|
||||
{region}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="年龄范围" name="ageRange">
|
||||
<div className={styles.ageRange}>
|
||||
<InputNumber
|
||||
min={18}
|
||||
max={100}
|
||||
value={customerFilter.ageRange[0]}
|
||||
onChange={value =>
|
||||
setCustomerFilter({
|
||||
...customerFilter,
|
||||
ageRange: [value || 18, customerFilter.ageRange[1]],
|
||||
})
|
||||
}
|
||||
/>
|
||||
<span style={{ margin: "0 8px" }}>-</span>
|
||||
<InputNumber
|
||||
min={18}
|
||||
max={100}
|
||||
value={customerFilter.ageRange[1]}
|
||||
onChange={value =>
|
||||
setCustomerFilter({
|
||||
...customerFilter,
|
||||
ageRange: [customerFilter.ageRange[0], value || 65],
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="性别" name="gender">
|
||||
<Radio.Group
|
||||
value={customerFilter.gender}
|
||||
onChange={e =>
|
||||
setCustomerFilter({
|
||||
...customerFilter,
|
||||
gender: e.target.value,
|
||||
})
|
||||
}
|
||||
>
|
||||
<Radio value="all">不限</Radio>
|
||||
<Radio value="male">男性</Radio>
|
||||
<Radio value="female">女性</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="最后联系时间" name="lastContactTime">
|
||||
<Select
|
||||
value={customerFilter.lastContactTime}
|
||||
onChange={value =>
|
||||
setCustomerFilter({
|
||||
...customerFilter,
|
||||
lastContactTime: value,
|
||||
})
|
||||
}
|
||||
>
|
||||
<Option value="all">不限</Option>
|
||||
<Option value="7days">7天内</Option>
|
||||
<Option value="30days">30天内</Option>
|
||||
<Option value="90days">90天内</Option>
|
||||
<Option value="180days">180天内</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="购买历史" name="purchaseHistory">
|
||||
<Select
|
||||
value={customerFilter.purchaseHistory}
|
||||
onChange={value =>
|
||||
setCustomerFilter({
|
||||
...customerFilter,
|
||||
purchaseHistory: value,
|
||||
})
|
||||
}
|
||||
>
|
||||
<Option value="all">不限</Option>
|
||||
<Option value="purchased">有购买记录</Option>
|
||||
<Option value="no-purchase">无购买记录</Option>
|
||||
<Option value="high-value">高价值客户</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
|
||||
<div className={styles.estimatedCount}>
|
||||
<TeamOutlined />
|
||||
<span>
|
||||
预估触达客户:<strong>{estimatedCount}</strong> 人
|
||||
</span>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
|
||||
case 1:
|
||||
return (
|
||||
<Card
|
||||
title={
|
||||
<>
|
||||
<MessageOutlined /> 消息内容
|
||||
</>
|
||||
}
|
||||
className={styles.stepCard}
|
||||
>
|
||||
<Form form={form} layout="vertical">
|
||||
<Form.Item label="消息类型" name="messageType">
|
||||
<Radio.Group
|
||||
value={messageContent.type}
|
||||
onChange={e =>
|
||||
setMessageContent({
|
||||
...messageContent,
|
||||
type: e.target.value,
|
||||
})
|
||||
}
|
||||
>
|
||||
<Radio value="text">纯文本</Radio>
|
||||
<Radio value="image">图片</Radio>
|
||||
<Radio value="mixed">图文混合</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
|
||||
{(messageContent.type === "text" ||
|
||||
messageContent.type === "mixed") && (
|
||||
<Form.Item label="消息文本" name="messageContent">
|
||||
<TextArea
|
||||
rows={6}
|
||||
placeholder="请输入消息内容,支持变量如 {客户姓名}、{产品名称} 等"
|
||||
value={messageContent.text}
|
||||
onChange={e =>
|
||||
setMessageContent({
|
||||
...messageContent,
|
||||
text: e.target.value,
|
||||
})
|
||||
}
|
||||
showCount
|
||||
maxLength={500}
|
||||
/>
|
||||
</Form.Item>
|
||||
)}
|
||||
|
||||
{(messageContent.type === "image" ||
|
||||
messageContent.type === "mixed") && (
|
||||
<Form.Item label="上传图片" name="images">
|
||||
<Upload
|
||||
listType="picture-card"
|
||||
fileList={messageContent.images}
|
||||
onChange={({ fileList }) =>
|
||||
setMessageContent({ ...messageContent, images: fileList })
|
||||
}
|
||||
beforeUpload={file => {
|
||||
const isImage = file.type?.startsWith("image/");
|
||||
if (!isImage) {
|
||||
message.error("只能上传图片文件!");
|
||||
}
|
||||
const isLt2M = file.size / 1024 / 1024 < 2;
|
||||
if (!isLt2M) {
|
||||
message.error("图片大小不能超过 2MB!");
|
||||
}
|
||||
return false; // 阻止自动上传
|
||||
}}
|
||||
accept="image/*"
|
||||
>
|
||||
{messageContent.images.length < 9 && (
|
||||
<div>
|
||||
<PlusOutlined />
|
||||
<div style={{ marginTop: 8 }}>上传图片</div>
|
||||
</div>
|
||||
)}
|
||||
</Upload>
|
||||
</Form.Item>
|
||||
)}
|
||||
|
||||
<Form.Item label="可用变量">
|
||||
<div className={styles.variableList}>
|
||||
{messageVariables.map(variable => (
|
||||
<Tag
|
||||
key={variable}
|
||||
icon={<TagsOutlined />}
|
||||
color="blue"
|
||||
style={{ cursor: "pointer", margin: "4px" }}
|
||||
onClick={() => {
|
||||
const newText = messageContent.text + variable;
|
||||
setMessageContent({ ...messageContent, text: newText });
|
||||
}}
|
||||
>
|
||||
{variable}
|
||||
</Tag>
|
||||
))}
|
||||
</div>
|
||||
</Form.Item>
|
||||
|
||||
<Divider />
|
||||
<div className={styles.previewArea}>
|
||||
<h4>消息预览</h4>
|
||||
<div className={styles.messagePreview}>
|
||||
{messageContent.text && (
|
||||
<div className={styles.textPreview}>
|
||||
{messageContent.text}
|
||||
</div>
|
||||
)}
|
||||
{messageContent.images.length > 0 && (
|
||||
<div className={styles.imagePreview}>
|
||||
{messageContent.images.map((image, index) => (
|
||||
<img
|
||||
key={index}
|
||||
src={image.url || image.thumbUrl}
|
||||
alt={`preview-${index}`}
|
||||
style={{
|
||||
width: 60,
|
||||
height: 60,
|
||||
objectFit: "cover",
|
||||
margin: 4,
|
||||
borderRadius: 4,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
</Card>
|
||||
);
|
||||
|
||||
case 2:
|
||||
return (
|
||||
<Card
|
||||
title={
|
||||
<>
|
||||
<SendOutlined /> 发送设置
|
||||
</>
|
||||
}
|
||||
className={styles.stepCard}
|
||||
>
|
||||
<Form form={form} layout="vertical">
|
||||
<Form.Item label="发送模式" name="sendMode">
|
||||
<Radio.Group
|
||||
value={sendSettings.sendMode}
|
||||
onChange={e =>
|
||||
setSendSettings({
|
||||
...sendSettings,
|
||||
sendMode: e.target.value,
|
||||
})
|
||||
}
|
||||
>
|
||||
<Radio value="immediate">立即发送</Radio>
|
||||
<Radio value="scheduled">定时发送</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
|
||||
{sendSettings.sendMode === "scheduled" && (
|
||||
<Form.Item label="定时时间" name="scheduledTime">
|
||||
<DatePicker
|
||||
showTime
|
||||
placeholder="选择发送时间"
|
||||
onChange={(date, dateString) =>
|
||||
setSendSettings({
|
||||
...sendSettings,
|
||||
scheduledTime: dateString as string,
|
||||
})
|
||||
}
|
||||
disabledDate={current =>
|
||||
current && current < dayjs().endOf("day")
|
||||
}
|
||||
/>
|
||||
</Form.Item>
|
||||
)}
|
||||
|
||||
<Form.Item label="发送间隔(秒)" name="sendInterval">
|
||||
<InputNumber
|
||||
min={1}
|
||||
max={60}
|
||||
value={sendSettings.sendInterval}
|
||||
onChange={value =>
|
||||
setSendSettings({
|
||||
...sendSettings,
|
||||
sendInterval: value || 5,
|
||||
})
|
||||
}
|
||||
addonAfter="秒"
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="每日最大发送数" name="maxPerDay">
|
||||
<InputNumber
|
||||
min={1}
|
||||
max={1000}
|
||||
value={sendSettings.maxPerDay}
|
||||
onChange={value =>
|
||||
setSendSettings({
|
||||
...sendSettings,
|
||||
maxPerDay: value || 100,
|
||||
})
|
||||
}
|
||||
addonAfter="条"
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="发送时间段" name="timeRange">
|
||||
<TimePicker.RangePicker
|
||||
format="HH:mm"
|
||||
value={
|
||||
sendSettings.timeRange.map(time =>
|
||||
time ? dayjs(time, "HH:mm") : null,
|
||||
) as any
|
||||
}
|
||||
onChange={(times, timeStrings) =>
|
||||
setSendSettings({
|
||||
...sendSettings,
|
||||
timeRange: timeStrings as [string, string],
|
||||
})
|
||||
}
|
||||
placeholder={["开始时间", "结束时间"]}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="高级设置">
|
||||
<Space direction="vertical">
|
||||
<Checkbox>启用发送跟踪 (统计阅读率、回复率等)</Checkbox>
|
||||
<Checkbox>启用自动回复 (客户回复时自动响应)</Checkbox>
|
||||
<Checkbox>避开休息时间 (自动跳过非工作时间)</Checkbox>
|
||||
</Space>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
|
||||
<Divider />
|
||||
|
||||
<div className={styles.summary}>
|
||||
<h4>
|
||||
<AimOutlined style={{ marginRight: 8 }} />
|
||||
发送摘要
|
||||
</h4>
|
||||
<div className={styles.summaryGrid}>
|
||||
<div className={styles.summaryItem}>
|
||||
<TeamOutlined />
|
||||
<span>
|
||||
目标客户:<strong>{estimatedCount}</strong> 人
|
||||
</span>
|
||||
</div>
|
||||
<div className={styles.summaryItem}>
|
||||
<ClockCircleOutlined />
|
||||
<span>
|
||||
发送模式:
|
||||
<strong>
|
||||
{sendSettings.sendMode === "immediate"
|
||||
? "立即发送"
|
||||
: `定时发送 (${sendSettings.scheduledTime || "未设置"})`}
|
||||
</strong>
|
||||
</span>
|
||||
</div>
|
||||
<div className={styles.summaryItem}>
|
||||
<AimOutlined />
|
||||
<span>
|
||||
发送间隔:<strong>{sendSettings.sendInterval}</strong> 秒
|
||||
</span>
|
||||
</div>
|
||||
<div className={styles.summaryItem}>
|
||||
<TagsOutlined />
|
||||
<span>
|
||||
每日限额:<strong>{sendSettings.maxPerDay}</strong> 条
|
||||
</span>
|
||||
</div>
|
||||
<div className={styles.summaryItem}>
|
||||
<ClockCircleOutlined />
|
||||
<span>
|
||||
时间段:
|
||||
<strong>
|
||||
{sendSettings.timeRange[0]} - {sendSettings.timeRange[1]}
|
||||
</strong>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Layout
|
||||
header={
|
||||
<>
|
||||
<PowerNavigation
|
||||
title="精准群发1"
|
||||
subtitle="基于客户标签和行为数据进行精准群发"
|
||||
showBackButton={true}
|
||||
backButtonText="返回功能中心"
|
||||
/>
|
||||
<div className={styles.stepsContainer}>
|
||||
<Steps current={currentStep} className={styles.steps}>
|
||||
<Step title="客户筛选" icon={<UserOutlined />} />
|
||||
<Step title="消息内容" icon={<MessageOutlined />} />
|
||||
<Step title="发送设置" icon={<SendOutlined />} />
|
||||
</Steps>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
footer={
|
||||
<div className={styles.stepActions}>
|
||||
<Space>
|
||||
{currentStep > 0 && (
|
||||
<Button size="large" onClick={handlePrev} disabled={loading}>
|
||||
上一步
|
||||
</Button>
|
||||
)}
|
||||
{currentStep < 2 ? (
|
||||
<Button
|
||||
type="primary"
|
||||
size="large"
|
||||
onClick={() => {
|
||||
// 简单验证当前步骤
|
||||
if (currentStep === 0 && estimatedCount === 0) {
|
||||
message.warning("请设置筛选条件以选择目标客户");
|
||||
return;
|
||||
}
|
||||
if (
|
||||
currentStep === 1 &&
|
||||
!messageContent.text &&
|
||||
messageContent.images.length === 0
|
||||
) {
|
||||
message.warning("请输入消息内容或上传图片");
|
||||
return;
|
||||
}
|
||||
handleNext();
|
||||
}}
|
||||
disabled={loading}
|
||||
>
|
||||
下一步
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
type="primary"
|
||||
size="large"
|
||||
onClick={handleSubmit}
|
||||
loading={loading}
|
||||
icon={<SendOutlined />}
|
||||
>
|
||||
创建群发任务
|
||||
</Button>
|
||||
)}
|
||||
</Space>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className={styles.container}>
|
||||
<div className={styles.stepContent}>{renderStepContent()}</div>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export default PrecisionSend;
|
||||
@@ -1,43 +0,0 @@
|
||||
.container {
|
||||
padding: 24px;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.header {
|
||||
margin-bottom: 24px;
|
||||
|
||||
h1 {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #262626;
|
||||
margin: 0 0 8px 0;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 14px;
|
||||
color: #8c8c8c;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 300px;
|
||||
background: #fafafa;
|
||||
border: 1px dashed #d9d9d9;
|
||||
border-radius: 6px;
|
||||
|
||||
p {
|
||||
font-size: 16px;
|
||||
color: #8c8c8c;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
import React from "react";
|
||||
import PowerNavigation from "@/components/PowerNavtion";
|
||||
import styles from "./index.module.scss";
|
||||
|
||||
const SopSend: React.FC = () => {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<PowerNavigation
|
||||
title="SOP群发"
|
||||
subtitle="使用触客宝SOP标准化流程进行批量消息发送"
|
||||
showBackButton={true}
|
||||
backButtonText="返回功能中心"
|
||||
/>
|
||||
<div className={styles.content}>
|
||||
{/* 功能内容待开发 */}
|
||||
<div className={styles.placeholder}>
|
||||
<p>SOP群发功能正在开发中...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SopSend;
|
||||
@@ -1,43 +0,0 @@
|
||||
.container {
|
||||
padding: 24px;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.header {
|
||||
margin-bottom: 24px;
|
||||
|
||||
h1 {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #262626;
|
||||
margin: 0 0 8px 0;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 14px;
|
||||
color: #8c8c8c;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 300px;
|
||||
background: #fafafa;
|
||||
border: 1px dashed #d9d9d9;
|
||||
border-radius: 6px;
|
||||
|
||||
p {
|
||||
font-size: 16px;
|
||||
color: #8c8c8c;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
import React from "react";
|
||||
import PowerNavigation from "@/components/PowerNavtion";
|
||||
import styles from "./index.module.scss";
|
||||
|
||||
const TagManagement: React.FC = () => {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<PowerNavigation
|
||||
title="标签管理"
|
||||
subtitle="智能客户标签分类,精准用户画像分析"
|
||||
showBackButton={true}
|
||||
backButtonText="返回功能中心"
|
||||
/>
|
||||
<div className={styles.content}>
|
||||
{/* 功能内容待开发 */}
|
||||
<div className={styles.placeholder}>
|
||||
<p>标签管理功能正在开发中...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TagManagement;
|
||||
@@ -2,10 +2,6 @@ import CkboxPage from "@/pages/pc/ckbox";
|
||||
import WeChatPage from "@/pages/pc/ckbox/weChat";
|
||||
import Dashboard from "@/pages/pc/ckbox/dashboard";
|
||||
import PowerCenter from "@/pages/pc/ckbox/powerCenter";
|
||||
import PrecisionSend from "@/pages/pc/ckbox/powerCenter/precision-send";
|
||||
import SopSend from "@/pages/pc/ckbox/powerCenter/sop-send";
|
||||
import MomentsMarketing from "@/pages/pc/ckbox/powerCenter/moments-marketing";
|
||||
import TagManagement from "@/pages/pc/ckbox/powerCenter/tag-management";
|
||||
import CustomerManagement from "@/pages/pc/ckbox/powerCenter/customer-management";
|
||||
import CommunicationRecord from "@/pages/pc/ckbox/powerCenter/communication-record";
|
||||
import ContentManagement from "@/pages/pc/ckbox/powerCenter/content-management";
|
||||
@@ -34,22 +30,6 @@ const ckboxRoutes = [
|
||||
path: "powerCenter",
|
||||
element: <PowerCenter />,
|
||||
},
|
||||
{
|
||||
path: "powerCenter/precision-send",
|
||||
element: <PrecisionSend />,
|
||||
},
|
||||
{
|
||||
path: "powerCenter/sop-send",
|
||||
element: <SopSend />,
|
||||
},
|
||||
{
|
||||
path: "powerCenter/moments-marketing",
|
||||
element: <MomentsMarketing />,
|
||||
},
|
||||
{
|
||||
path: "powerCenter/tag-management",
|
||||
element: <TagManagement />,
|
||||
},
|
||||
{
|
||||
path: "powerCenter/customer-management",
|
||||
element: <CustomerManagement />,
|
||||
|
||||
Reference in New Issue
Block a user