From d95e07285c86624a1f197f6029d844c1b80b344a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Sat, 13 Sep 2025 18:04:25 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E7=B2=BE=E5=87=86=E7=BE=A4=E5=8F=91):=20?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E4=B8=89=E6=AD=A5=E8=A1=A8=E5=8D=95=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E5=8F=8A=E5=93=8D=E5=BA=94=E5=BC=8F=E8=AE=BE=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加客户筛选、消息内容和发送设置三个步骤的表单功能 包含预估客户数计算、消息预览和发送摘要展示 优化样式并添加移动端响应式设计 --- .../precision-send/index.module.scss | 202 ++++- .../powerCenter/precision-send/index.tsx | 705 +++++++++++++++++- 2 files changed, 894 insertions(+), 13 deletions(-) diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/precision-send/index.module.scss b/Touchkebao/src/pages/pc/ckbox/powerCenter/precision-send/index.module.scss index bf1ac1e6..e881b178 100644 --- a/Touchkebao/src/pages/pc/ckbox/powerCenter/precision-send/index.module.scss +++ b/Touchkebao/src/pages/pc/ckbox/powerCenter/precision-send/index.module.scss @@ -23,21 +23,205 @@ } .content { - min-height: 400px; + min-height: 600px; } -.placeholder { +.stepsContainer { + margin-bottom: 32px; + padding: 24px; + background: #fafafa; + border-radius: 8px; +} + +.steps { + max-width: 600px; + margin: 0 auto; +} + +.stepContent { + margin-bottom: 24px; +} + +.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; - justify-content: center; - height: 300px; - background: #fafafa; - border: 1px dashed #d9d9d9; + + .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; - p { + .anticon { + color: #1890ff; font-size: 16px; - color: #8c8c8c; - margin: 0; + } + + span { + font-size: 14px; + color: #262626; + } +} + +.stepActions { + display: flex; + justify-content: center; + padding: 24px 0; + border-top: 1px solid #f0f0f0; + + .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; } } \ No newline at end of file diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/precision-send/index.tsx b/Touchkebao/src/pages/pc/ckbox/powerCenter/precision-send/index.tsx index c350739a..dbd2cbfe 100644 --- a/Touchkebao/src/pages/pc/ckbox/powerCenter/precision-send/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/powerCenter/precision-send/index.tsx @@ -1,17 +1,714 @@ -import React from "react"; +import React, { useState, useEffect, useCallback } from "react"; +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 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({ + tags: [], + regions: [], + ageRange: [18, 65], + gender: "all", + lastContactTime: "all", + purchaseHistory: "all", + }); + const [messageContent, setMessageContent] = useState({ + type: "text", + text: "", + images: [], + variables: [], + }); + const [sendSettings, setSendSettings] = useState({ + 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 ( + + 客户筛选 + + } + className={styles.stepCard} + > +
+ + ({ + label: tag, + value: tag, + }))} + value={customerFilter.tags} + onChange={values => + setCustomerFilter({ + ...customerFilter, + tags: values as string[], + }) + } + /> + + + + + + + +
+ + setCustomerFilter({ + ...customerFilter, + ageRange: [value || 18, customerFilter.ageRange[1]], + }) + } + /> + - + + setCustomerFilter({ + ...customerFilter, + ageRange: [customerFilter.ageRange[0], value || 65], + }) + } + /> +
+
+ + + + setCustomerFilter({ + ...customerFilter, + gender: e.target.value, + }) + } + > + 不限 + 男性 + 女性 + + + + + + + + + + +
+ +
+ + + 预估触达客户:{estimatedCount} 人 + +
+
+ ); + + case 1: + return ( + + 消息内容 + + } + className={styles.stepCard} + > +
+ + + setMessageContent({ + ...messageContent, + type: e.target.value, + }) + } + > + 纯文本 + 图片 + 图文混合 + + + + {(messageContent.type === "text" || + messageContent.type === "mixed") && ( + +