Просмотр исходного кода

Merge remote-tracking branch 'origin/master'

YPZ 9 часов назад
Родитель
Сommit
ad2f66772a

+ 6 - 1
ui/smarttrade-platform/src/api/marketing/index.js

@@ -9,6 +9,11 @@ export const getSopById = (id) => get(`/router/rest/api/sop/${id}`)
 export const addMarketing = (data = {}) => post('/router/rest/marketing/add', data)
 export const editMarketing = (data = {}) => post('/router/rest/marketing/edit', data)
 export const marketingAnalysis = () => get('/router/rest/marketing/dashboard/stats')
-export const getMarketingCustomer = (data = {}) => post('/router/rest/marketing/campaign/sop-detail/customers', data)
+export const getOpenCustomer = (data = {}) => post('/router/rest/marketEmails/open-list', data)
+export const setProtectCustomer = (data = {}) => post('/router/rest/marketEmails/protect', data)
+export const setSuspendCustomer = (data = {}) => post('/router/rest/marketEmails/suspend', data)
+export const getReplyCustomer = (data = {}) => get('/router/rest/marketEmails/reply-list', data)
+export const getInterventionCustomer = (data = {}) => get('/router/rest/marketEmails/intervention-list', data)
+export const getEmailList = (data = {}) => get('/router/rest/marketEmails/management-detail', data)
 export const getTradeCustomer = (data = {}) => post('/router/rest/customerTrade/list', data)
 export const saveGenerateEmail = (data = {}) => post('/router/rest/sopDetail/edit', data)

+ 6 - 5
ui/smarttrade-platform/src/components/addMarketing.vue

@@ -64,6 +64,7 @@
                                 <el-option value="promote" label="产品推广"></el-option>
                                 <el-option value="seasonal" label="节日营销"></el-option>
                                 <el-option value="exhibition" label="展会跟进"></el-option>
+                                <el-option value="other" label="其他"></el-option>
                             </el-select>
                         </div>
                         <div class="form-group">
@@ -756,7 +757,7 @@
         },
         goals: { sent: 500, openRate: 40, replyRate: 10, opportunities: 15 },
         channels: ['email'],
-        emailSender: 'fan@storlead.com', emailSendTime: 'optimal',
+        emailSender: '', emailSendTime: 'optimal',
         trackOpen: true, trackClick: true, trackReply: true,
         whatsappAccount: '业务账号1', whatsappInterval: '60', whatsappTemplate: '',
         linkedinAction: 'message', linkedinLimit: '30',
@@ -776,7 +777,7 @@
     const populateCampaignForm = (data = {}) => {
         const defaults = getDefaultCampaign()
         const campaign = data || {}
-        const typeMap = { 1: 'prospect', 2: 'nurture', 3: 'reactivate', 4: 'promote', 5: 'seasonal', 6: 'exhibition' }
+        const typeMap = { 1: 'prospect', 2: 'nurture', 3: 'reactivate', 4: 'promote', 5: 'seasonal', 6: 'exhibition', 7: 'other' }
         const priorityMap = { 1: 'urgent', 2: 'high', 3: 'medium', 4: 'low' }
         const channelConfig = Number(campaign.channelConfiguration || 0)
         const channels = []
@@ -859,7 +860,7 @@
     }
 
     const sopTemplates = ref([])
-    const getTypeLabel = (type) => ({ prospect: '潜客开发', nurture: '客户培育', reactivate: '客户唤醒', promote: '产品推广', seasonal: '节日营销', exhibition: '展会跟进' }[type] || type)
+    const getTypeLabel = (type) => ({ prospect: '潜客开发', nurture: '客户培育', reactivate: '客户唤醒', promote: '产品推广', seasonal: '节日营销', exhibition: '展会跟进', other: '其他' }[type] || type)
     const getPriorityLabel = (priority) => ({ urgent: '紧急', high: '高优先', medium: '中等', low: '低优先' }[priority] || priority)
     const getOwnerName = (owner) => ({ sales1: '张经理', sales2: '李主管', sales3: '王小明' }[owner] || '')
     const getChannelStyle = (id) => {
@@ -896,7 +897,7 @@
             return
         }
         try {
-            const typeMap = { prospect: 1, nurture: 2, reactivate: 3, promote: 4, seasonal: 5, exhibition: 6 }
+            const typeMap = { prospect: 1, nurture: 2, reactivate: 3, promote: 4, seasonal: 5, exhibition: 6, other: 7 }
             const priorityMap = { urgent: 1, high: 2, medium: 3, low: 4 }
             const ownerMap = { sales1: 1, sales2: 2, sales3: 3 }
             const selectedOwners = Array.isArray(newCampaign.value.owner)
@@ -980,7 +981,7 @@
     }
     const applySuggestion = (type) => {
         if (type === 'name') {
-            const typeNames = { prospect: '潜客开发', nurture: '客户培育', reactivate: '客户唤醒', promote: '产品推广', seasonal: '节日营销', exhibition: '展会跟进' }
+            const typeNames = { prospect: '潜客开发', nurture: '客户培育', reactivate: '客户唤醒', promote: '产品推广', seasonal: '节日营销', exhibition: '展会跟进', other: '其他' }
             const regions = ['中东', '欧洲', '北美', '东南亚']
             const region = regions[Math.floor(Math.random() * regions.length)]
             newCampaign.value.name = `2026春季${region}${typeNames[newCampaign.value.type] || '营销'}活动`

+ 0 - 1
ui/smarttrade-platform/src/components/layout/Sidebar.vue

@@ -41,7 +41,6 @@
         <router-link to="/customers" class="nav-item" :class="{ active: $route.path === '/customers' }">
           <i class="fas fa-users nav-icon"></i>
           <span class="nav-text">客户池</span>
-          <span class="nav-badge">847</span>
         </router-link>
         <!-- <router-link to="/opportunities" class="nav-item" :class="{ active: $route.path === '/opportunities' }">
           <i class="fas fa-briefcase nav-icon"></i>

+ 4 - 0
ui/smarttrade-platform/src/components/marketing/emailList.vue

@@ -79,6 +79,7 @@
 <script setup>
 import { computed, ref } from 'vue'
 import { useRouter } from 'vue-router'
+import { getEmailList } from '@/api/marketing'
 
 const router = useRouter()
 const showEmailListModal = ref(false)
@@ -149,6 +150,9 @@ const open = (campaign, list = null) => {
   if (Array.isArray(list)) {
     emailList.value = list
   }
+  getEmailList({ id: campaign.id }).then((res) => {
+    console.log('res :>> ', res);
+  })
   showEmailListModal.value = true
 }
 

+ 96 - 0
ui/smarttrade-platform/src/components/marketing/interventionCustomers.vue

@@ -0,0 +1,96 @@
+<template>
+    <div v-if="showInterventionModal" class="modal-overlay" @click.self="showInterventionModal = false">
+      <div class="modal detail-modal">
+        <div class="modal-header">
+          <div class="modal-title">
+            <i class="fas fa-hand-paper"></i> 人工干预
+          </div>
+          <button class="modal-close" @click="showInterventionModal = false"><i class="fas fa-times"></i></button>
+        </div>
+        <div class="modal-body">
+          <div class="tabs-section">
+            <div class="tab-item" :class="{ 'active': tabType == 'protect' }" @click="changeTab('protect')">
+              <i class="fas fa-shield-alt"></i> 客户保护白名单
+            </div>
+            <div class="tab-item" :class="{ 'active': tabType == 'suspend' }" @click="changeTab('suspend')">
+              <i class="fas fa-hand-paper"></i> 营销暂停
+            </div>
+          </div>
+
+          <!-- Protected Customers List -->
+          <div class="detail-section">
+            <div class="protected-customers">
+              <div v-for="customer in protectedCustomersList" :key="customer.id" class="protected-customer-item">
+                <div class="customer-header">
+                  <div class="customer-info">
+                    <div class="customer-name">
+                      {{ customer.name }}
+                      <span class="protection-level" :class="customer.protectionLevel">
+                        {{ customer.protectionLevel }}保护
+                      </span>
+                    </div>
+                    <div class="customer-email">{{ customer.email }}</div>
+                  </div>
+                </div>
+                <div class="customer-details">
+                  <span class="detail">保护原因: {{ customer.reason }}</span>
+                  <span class="detail">保护时间: {{ customer.protectedSince }}</span>
+                </div>
+                <div class="customer-actions">
+                  <button class="btn btn-secondary btn-sm" @click="unprotectCustomer(customer)">
+                    <i class="fas fa-times"></i> 移除保护
+                  </button>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+        <div class="modal-footer">
+          <button class="btn btn-secondary" @click="showInterventionModal = false">关闭</button>
+          <button class="btn btn-primary" @click="showInterventionModal = false">
+            <i class="fas fa-download"></i> 导出报告
+          </button>
+        </div>
+      </div>
+    </div>
+</template>
+
+<script setup>
+    import { ref } from 'vue';
+    import { getInterventionCustomer, setProtectCustomer, setSuspendCustomer } from '@/api/marketing'
+
+    const showInterventionModal = ref(false);
+    // 客户保护白名单 - 防止AI营销影响
+    const protectedCustomersList = ref([
+        { id: 1, name: '阿里巴巴国际', email: 'contact@alibaba-intl.com', protectionLevel: '高', reason: '高价值客户', annualValue: '¥500K+', status: 'active', protectedSince: '2026-03-20', assignedSalesRep: '张三' },
+        { id: 2, name: '迪拜电子商务', email: 'info@dubai-ecom.ae', protectionLevel: '高', reason: '高价值客户', annualValue: '¥800K+', status: 'active', protectedSince: '2026-03-18', assignedSalesRep: '李四' },
+        { id: 3, name: '德国工业集团', email: 'procurement@de-industry.de', protectionLevel: '高', reason: '高价值客户', annualValue: '¥1.2M+', status: 'active', protectedSince: '2026-03-15', assignedSalesRep: '王五' },
+        { id: 4, name: '沙特阿美贸易', email: 'trade@aramco-sa.com', protectionLevel: '中', reason: '活跃客户', annualValue: '¥200K', status: 'active', protectedSince: '2026-03-22', assignedSalesRep: '赵六' },
+        { id: 5, name: '英国分销商', email: 'sales@uk-dist.co.uk', protectionLevel: '中', reason: '活跃客户', annualValue: '¥300K', status: 'active', protectedSince: '2026-03-21', assignedSalesRep: '孙七' }
+    ])
+    const tabType = ref('protect')
+
+    const marketingData = ref()
+    const open = (data) => {
+        showInterventionModal.value = true;
+        marketingData.value = data
+        getData()
+    }
+
+    const getData = () => {
+        getInterventionCustomer({ id: marketingData.value.id, tabType: tabType.value }).then(res => {
+            console.log('res :>> ', res);
+        })
+    }
+
+    const changeTab = (type) => {
+        tabType.value = type
+        getData()
+    }
+
+    defineExpose({ open });
+</script>
+
+<style scoped>
+    @import '../../assets/styles/marketing.css';
+</style>

+ 33 - 7
ui/smarttrade-platform/src/components/marketing/openedCustomers.vue

@@ -3,7 +3,7 @@
       <div class="modal detail-modal">
         <div class="modal-header">
           <div class="modal-title">
-            <i class="fas fa-envelope-open"></i> 打开邮件的客户 ({{ selectedOpenedCampaign?.openedCustomers }})
+            <i class="fas fa-envelope-open"></i> 打开邮件的客户 ({{ openedCustomers.length }})
           </div>
           <button class="modal-close" @click="showOpenedCustomersModal = false"><i class="fas fa-times"></i></button>
         </div>
@@ -24,13 +24,13 @@
                       <i class="fas fa-shield-alt"></i> 已保护
                     </span>
                   </div>
-                  <div class="customer-email">{{ customer.email }}</div>
+                  <div class="customer-email">{{ customer.recipient }}</div>
                 </div>
               </div>
               <div class="customer-details">
                 <div class="detail-item">
                   <span class="label">打开时间:</span>
-                  <span class="value">{{ customer.openTime }}</span>
+                  <span class="value">{{ customer.firstOpenTime }}</span>
                 </div>
                 <div class="detail-item">
                   <span class="label">点击次数:</span>
@@ -41,13 +41,13 @@
                 <button v-if="!customer.hasProtect" class="btn btn-secondary btn-sm" @click="protectCustomer(customer)" title="添加到保护白名单">
                   <i class="fas fa-shield-alt"></i> 保护
                 </button>
-                <button v-else class="btn btn-secondary btn-sm" @click="unprotectCustomer(customer)" title="从保护白名单中移除">
+                <button v-else class="btn btn-secondary btn-sm" @click="protectCustomer(customer)" title="从保护白名单中移除">
                   <i class="fas fa-times"></i> 取消保护
                 </button>
                 <button class="btn btn-secondary btn-sm" @click="pauseCampaignForCustomer(customer)" v-if="!customer.hasSuspend" title="暂停向该客户发送邮件">
                   <i class="fas fa-pause"></i> 暂停
                 </button>
-                <button class="btn btn-secondary btn-sm" @click="resumeCampaignForCustomer(customer)" v-else title="恢复向该客户发送邮件">
+                <button class="btn btn-secondary btn-sm" @click="pauseCampaignForCustomer(customer)" v-else title="恢复向该客户发送邮件">
                   <i class="fas fa-play"></i> 恢复
                 </button>
               </div>
@@ -66,7 +66,7 @@
 
 <script setup>
     import { ref } from 'vue'
-    import { getMarketingCustomer } from '@/api/marketing'
+    import { getOpenCustomer, setProtectCustomer, setSuspendCustomer } from '@/api/marketing'
 
     const showOpenedCustomersModal = ref(false)
     const selectedOpenedCampaign = ref()
@@ -84,12 +84,38 @@
 
     const open = (data) => {
         selectedOpenedCampaign.value = data
-        getMarketingCustomer({ marketingCampaignId: data.id, pageIndex: 1, pageSize: 10000 }).then(res => {
+        getOpenCustomer({ marketingCampaignId: data.id, pageIndex: 1, pageSize: 10000 }).then(res => {
             openedCustomers.value = res.result.records || []
         })
         showOpenedCustomersModal.value = true
     }
 
+    const protectCustomer = (customer) => {
+      let params = {
+        id: customer.customerId,
+        hasProtect: customer.hasProtect ? 0 : 1
+      }
+      setProtectCustomer(params).then(res => {
+        customer.hasProtect = !customer.hasProtect
+        ElMessage.success('操作成功')
+      }).catch(err => {
+        ElMessage.error('操作失败')
+      })
+    }
+
+    const pauseCampaignForCustomer = (customer) => {
+      let params = {
+        id: customer.customerId,
+        hasSuspend: customer.hasSuspend ? 0 : 1
+      }
+      setSuspendCustomer(params).then(res => {
+        customer.hasSuspend = !customer.hasSuspend
+        ElMessage.success('操作成功')
+      }).catch(err => {
+        ElMessage.error('操作失败')
+      })
+    }
+
     defineExpose({
         open
     })

+ 105 - 0
ui/smarttrade-platform/src/components/marketing/replyCustomers.vue

@@ -0,0 +1,105 @@
+<template>
+    <div v-if="showReplyReviewModal" class="modal-overlay" @click.self="showReplyReviewModal = false">
+      <div class="modal detail-modal">
+        <div class="modal-header">
+          <div class="modal-title">
+            <i class="fas fa-reply"></i> 回复邮件的客户 ({{ repliedCustomers.length }}个)
+          </div>
+          <button class="modal-close" @click="showReplyReviewModal = false"><i class="fas fa-times"></i></button>
+        </div>
+        <div class="modal-body">
+          <div class="review-stats">
+            <div class="stat-box">
+              <span class="stat-label">全部</span>
+              <span class="stat-value">{{ repliedCustomers.filter(r => r.status === 'pending-review').length }}</span>
+            </div>
+            <div class="stat-box">
+              <span class="stat-label">已保护</span>
+              <span class="stat-value">{{ repliedCustomers.filter(r => r.requiresHumanReview).length }}</span>
+            </div>
+            <div class="stat-box">
+              <span class="stat-label">营销暂停</span>
+              <span class="stat-value">{{ repliedCustomers.filter(r => r.priority === '高').length }}</span>
+            </div>
+          </div>
+          <div class="replies-list">
+            <div v-for="reply in repliedCustomers" :key="reply.id" class="reply-item" :class="[reply.status, { 'requires-review': reply.requiresHumanReview }]">
+              <div class="reply-header">
+                <div class="reply-info">
+                  <div class="reply-name">
+                    {{ reply.name }}
+                  </div>
+                  <div class="reply-email">{{ reply.email }}</div>
+                </div>
+                <div class="reply-badges">
+                  <span class="priority-badge" :class="reply.priority">
+                    {{ reply.priority }}优先级
+                  </span>
+                  <span class="value-badge" :class="reply.value">{{ reply.value }}</span>
+                </div>
+              </div>
+              <div class="reply-time">回复时间: {{ reply.replyTime }}</div>
+              <div class="reply-content">
+                <p>{{ reply.replyContent }}</p>
+              </div>
+              <div class="reply-review-notes" v-if="reply.reviewNotes">
+                <i class="fas fa-lightbulb"></i>
+                <span>{{ reply.reviewNotes }}</span>
+              </div>
+              <div class="reply-actions">
+                <button v-if="!reply.protected" class="btn btn-secondary btn-sm" @click="protectCustomer(reply)" title="添加到保护白名单">
+                  <i class="fas fa-shield-alt"></i> 保护
+                </button>
+                <button v-else class="btn btn-secondary btn-sm" @click="unprotectCustomer(reply)" title="从保护白名单中移除">
+                  <i class="fas fa-times"></i> 取消保护
+                </button>
+                <button class="btn btn-secondary btn-sm" @click="pauseCampaignForCustomer(reply)" v-if="reply.interventionStatus !== 'paused'" title="暂停向该客户发送邮件">
+                  <i class="fas fa-pause"></i> 暂停
+                </button>
+                <button class="btn btn-secondary btn-sm" @click="resumeCampaignForCustomer(reply)" v-else title="恢复向该客户发送邮件">
+                  <i class="fas fa-play"></i> 恢复
+                </button>
+              </div>
+            </div>
+          </div>
+        </div>
+        <div class="modal-footer">
+          <button class="btn btn-secondary" @click="showReplyReviewModal = false">关闭</button>
+          <button class="btn btn-primary" @click="showReplyReviewModal = false">
+            <i class="fas fa-download"></i> 导出回复
+          </button>
+        </div>
+      </div>
+    </div>
+</template>
+
+<script setup>
+    import { ref } from 'vue';
+    import { getReplyCustomer, setProtectCustomer, setSuspendCustomer } from '@/api/marketing'
+
+    const showReplyReviewModal = ref(false);
+    // 回复邮件的客户列表 - 需要人工审核和介入
+    const repliedCustomers = ref([
+        { id: 1, name: '中东贸易公司', email: 'buyer@middle-east-trade.com', replyTime: '2026-03-23 11:45', replyContent: '感谢您的邮件,我们对您的产品很感兴趣,能否提供详细的产品规格和价格?', status: 'pending-review', sentiment: '正面', priority: '高', value: '高价值', annualPurchase: '¥600K+', lastOrder: '2026-02-10', requiresHumanReview: true, reviewNotes: '需要销售人员立即跟进,客户明确表示感兴趣', assignedTo: '', reviewedBy: '', reviewTime: '' },
+        { id: 2, name: '欧洲数据中心', email: 'procurement@eu-datacenter.eu', replyTime: '2026-03-23 10:20', replyContent: '我们正在评估新的存储解决方案,您能否安排一次技术演示?', status: 'pending-review', sentiment: '正面', priority: '高', value: '高价值', annualPurchase: '¥1M+', lastOrder: '2026-03-05', requiresHumanReview: true, reviewNotes: '需要技术团队配合,安排产品演示', assignedTo: '', reviewedBy: '', reviewTime: '' },
+        { id: 3, name: '北美分销', email: 'sales@na-dist.com', replyTime: '2026-03-22 16:30', replyContent: '价格太高了,能否提供折扣?', status: 'pending-review', sentiment: '中立', priority: '中', value: '中价值', annualPurchase: '¥250K', lastOrder: '2026-01-15', requiresHumanReview: true, reviewNotes: '需要销售经理评估折扣空间', assignedTo: '', reviewedBy: '', reviewTime: '' },
+        { id: 4, name: '东南亚进口商', email: 'import@sea-import.com', replyTime: '2026-03-22 14:15', replyContent: '我们有兴趣,但需要了解更多关于技术支持的信息。', status: 'pending-review', sentiment: '正面', priority: '中', value: '中价值', annualPurchase: '¥180K', lastOrder: '2026-02-01', requiresHumanReview: true, reviewNotes: '需要技术支持团队提供详细信息', assignedTo: '', reviewedBy: '', reviewTime: '' },
+        { id: 5, name: '日本电子商社', email: 'trade@japan-elec.jp', replyTime: '2026-03-21 17:00', replyContent: '暂时不需要,但会保留您的联系方式。', status: 'pending-review', sentiment: '中立', priority: '低', value: '低价值', annualPurchase: '¥80K', lastOrder: '2025-10-20', requiresHumanReview: false, reviewNotes: '可自动标记为后续跟进', assignedTo: '', reviewedBy: '', reviewTime: '' },
+        { id: 6, name: '韩国科技公司', email: 'contact@korea-tech.kr', replyTime: '2026-03-21 15:30', replyContent: '这不是我们需要的产品类型。', status: 'pending-review', sentiment: '负面', priority: '低', value: '低价值', annualPurchase: '¥40K', lastOrder: '2025-09-10', requiresHumanReview: false, reviewNotes: '可自动标记为不适配,从列表中移除', assignedTo: '', reviewedBy: '', reviewTime: '' }
+    ])
+
+    const open = (data) => {
+        showReplyReviewModal.value = true;
+        getReplyCustomer({ id: data.id }).then(res => {
+            console.log('res :>> ', res);
+        })
+    }
+
+    defineExpose({
+        open
+    })
+</script>
+
+<style scoped>
+    @import '../../assets/styles/marketing.css';
+</style>

+ 14 - 222
ui/smarttrade-platform/src/views/MarketingView.vue

@@ -6,12 +6,12 @@
         <p class="header-desc">多渠道自动化营销,AI 智能生成个性化内容</p>
       </div>
       <div class="header-actions">
-        <button class="btn btn-secondary" @click="showHistoryModal = true">
+        <!-- <button class="btn btn-secondary" @click="showHistoryModal = true">
           <i class="fas fa-history"></i> 历史活动
         </button>
         <button class="btn btn-secondary" @click="showTemplatesModal = true">
           <i class="fas fa-file-alt"></i> 模板库
-        </button>
+        </button> -->
         <button class="btn btn-primary" @click="openCreateModal">
           <i class="fas fa-plus"></i> 创建营销活动
         </button>
@@ -118,11 +118,11 @@
         </div>
         <div class="card-actions" style="justify-content: space-between;" @click.stop>
           <div style="display: flex;gap: 8px">
-            <button class="btn btn-secondary btn-sm" @click.stop="openOpenedCustomers(campaign)" :title="`打开邮件: ${campaign.openedCustomers}个客户`">
-              <i class="fas fa-envelope-open"></i> {{ campaign.openedCustomers }}
+            <button class="btn btn-secondary btn-sm" @click.stop="openOpenedCustomers(campaign)" title="打开邮件客户">
+              <i class="fas fa-envelope-open"></i>
             </button>
-            <button class="btn btn-secondary btn-sm" @click.stop="openReplyReview(campaign)" :title="`回复邮件: ${campaign.repliedCustomers}个客户`">
-              <i class="fas fa-reply"></i> {{ campaign.repliedCustomers }}
+            <button class="btn btn-secondary btn-sm" @click.stop="openReplyReview(campaign)" title="回复邮件客户">
+              <i class="fas fa-reply"></i>
             </button>
             <button class="btn btn-secondary btn-sm" @click="openInterventionModal(campaign)" title="人工干预">
               <i class="fas fa-hand-paper"></i>
@@ -714,178 +714,11 @@
     <OpenedCustomers ref="openedCustomersRef" />
 
     <!-- Reply Review Modal -->
-    <div v-if="showReplyReviewModal" class="modal-overlay" @click.self="showReplyReviewModal = false">
-      <div class="modal detail-modal">
-        <div class="modal-header">
-          <div class="modal-title">
-            <i class="fas fa-reply"></i> 回复邮件的客户 ({{ repliedCustomers.length }}个)
-          </div>
-          <button class="modal-close" @click="showReplyReviewModal = false"><i class="fas fa-times"></i></button>
-        </div>
-        <div class="modal-body">
-          <div class="review-stats">
-            <div class="stat-box">
-              <span class="stat-label">全部</span>
-              <span class="stat-value">{{ repliedCustomers.filter(r => r.status === 'pending-review').length }}</span>
-            </div>
-            <div class="stat-box">
-              <span class="stat-label">已保护</span>
-              <span class="stat-value">{{ repliedCustomers.filter(r => r.requiresHumanReview).length }}</span>
-            </div>
-            <div class="stat-box">
-              <span class="stat-label">营销暂停</span>
-              <span class="stat-value">{{ repliedCustomers.filter(r => r.priority === '高').length }}</span>
-            </div>
-          </div>
-          <div class="replies-list">
-            <div v-for="reply in repliedCustomers" :key="reply.id" class="reply-item" :class="[reply.status, { 'requires-review': reply.requiresHumanReview }]">
-              <div class="reply-header">
-                <div class="reply-info">
-                  <div class="reply-name">
-                    {{ reply.name }}
-                  </div>
-                  <div class="reply-email">{{ reply.email }}</div>
-                </div>
-                <div class="reply-badges">
-                  <span class="priority-badge" :class="reply.priority">
-                    {{ reply.priority }}优先级
-                  </span>
-                  <span class="value-badge" :class="reply.value">{{ reply.value }}</span>
-                </div>
-              </div>
-              <div class="reply-time">回复时间: {{ reply.replyTime }}</div>
-              <div class="reply-content">
-                <p>{{ reply.replyContent }}</p>
-              </div>
-              <div class="reply-review-notes" v-if="reply.reviewNotes">
-                <i class="fas fa-lightbulb"></i>
-                <span>{{ reply.reviewNotes }}</span>
-              </div>
-              <div class="reply-actions">
-                <button v-if="!reply.protected" class="btn btn-secondary btn-sm" @click="protectCustomer(reply)" title="添加到保护白名单">
-                  <i class="fas fa-shield-alt"></i> 保护
-                </button>
-                <button v-else class="btn btn-secondary btn-sm" @click="unprotectCustomer(reply)" title="从保护白名单中移除">
-                  <i class="fas fa-times"></i> 取消保护
-                </button>
-                <button class="btn btn-secondary btn-sm" @click="pauseCampaignForCustomer(reply)" v-if="reply.interventionStatus !== 'paused'" title="暂停向该客户发送邮件">
-                  <i class="fas fa-pause"></i> 暂停
-                </button>
-                <button class="btn btn-secondary btn-sm" @click="resumeCampaignForCustomer(reply)" v-else title="恢复向该客户发送邮件">
-                  <i class="fas fa-play"></i> 恢复
-                </button>
-              </div>
-            </div>
-          </div>
-        </div>
-        <div class="modal-footer">
-          <button class="btn btn-secondary" @click="showReplyReviewModal = false">关闭</button>
-          <button class="btn btn-primary" @click="showReplyReviewModal = false">
-            <i class="fas fa-download"></i> 导出回复
-          </button>
-        </div>
-      </div>
-    </div>
+    <ReplyCustomers ref="replyCustomersRef" />
 
     <!-- Intervention Modal -->
-    <div v-if="showInterventionModal" class="modal-overlay" @click.self="showInterventionModal = false">
-      <div class="modal detail-modal">
-        <div class="modal-header">
-          <div class="modal-title">
-            <i class="fas fa-hand-paper"></i> 人工干预
-          </div>
-          <button class="modal-close" @click="showInterventionModal = false"><i class="fas fa-times"></i></button>
-        </div>
-        <div class="modal-body" v-if="selectedIntervention">
-          <div class="tabs-section">
-            <div class="tab-item active" @click="selectedIntervention.activeTab = 'protected'">
-              <i class="fas fa-shield-alt"></i> 客户保护白名单
-            </div>
-            <div class="tab-item" @click="selectedIntervention.activeTab = 'history'">
-              <i class="fas fa-hand-paper"></i> 营销暂停
-            </div>
-          </div>
+    <InterventionCustomers ref="interventionCustomersRef" />
 
-          <!-- Protected Customers List -->
-          <div v-if="!selectedIntervention.activeTab || selectedIntervention.activeTab === 'protected'" class="detail-section">
-            <div class="protected-customers">
-              <div v-for="customer in protectedCustomersList" :key="customer.id" class="protected-customer-item">
-                <div class="customer-header">
-                  <div class="customer-info">
-                    <div class="customer-name">
-                      {{ customer.name }}
-                      <span class="protection-level" :class="customer.protectionLevel">
-                        {{ customer.protectionLevel }}保护
-                      </span>
-                    </div>
-                    <div class="customer-email">{{ customer.email }}</div>
-                  </div>
-                </div>
-                <div class="customer-details">
-                  <span class="detail">保护原因: {{ customer.reason }}</span>
-                  <span class="detail">保护时间: {{ customer.protectedSince }}</span>
-                </div>
-                <div class="customer-actions">
-                  <button class="btn btn-secondary btn-sm" @click="unprotectCustomer(customer)">
-                    <i class="fas fa-times"></i> 移除保护
-                  </button>
-                </div>
-              </div>
-            </div>
-          </div>
-
-          <!-- Intervention History -->
-          <div v-if="selectedIntervention.activeTab === 'history'" class="detail-section">
-            <h5>干预历史记录</h5>
-            <div class="interventions-list">
-              <div v-for="intervention in interventions.filter(i => i.campaignId === selectedIntervention.id)" :key="intervention.id" class="intervention-item">
-                <div class="intervention-header">
-                  <span class="intervention-type">{{ intervention.type }}</span>
-                  <span class="intervention-status" :class="intervention.status">{{ intervention.status === 'active' ? '进行中' : '已完成' }}</span>
-                </div>
-                <div class="intervention-info">
-                  <p class="description">{{ intervention.description }}</p>
-                  <div class="meta">
-                    <span>原因: {{ intervention.reason }}</span>
-                    <span>影响: {{ intervention.affectedCount }}个客户</span>
-                    <span>创建: {{ intervention.createdTime }}</span>
-                    <span>创建人: {{ intervention.createdBy }}</span>
-                  </div>
-                </div>
-              </div>
-            </div>
-          </div>
-
-          <!-- Current Interventions -->
-          <div class="detail-section">
-            <h5>现有干预</h5>
-            <div class="interventions-list">
-              <div v-for="intervention in interventions.filter(i => i.campaignId === selectedIntervention.id)" :key="intervention.id" class="intervention-item">
-                <div class="intervention-header">
-                  <span class="intervention-type">{{ intervention.type }}</span>
-                  <span class="intervention-status" :class="intervention.status">{{ intervention.status === 'active' ? '进行中' : '已完成' }}</span>
-                </div>
-                <div class="intervention-info">
-                  <p class="description">{{ intervention.description }}</p>
-                  <div class="meta">
-                    <span>原因: {{ intervention.reason }}</span>
-                    <span>影响: {{ intervention.affectedCount }}个客户</span>
-                    <span>创建: {{ intervention.createdTime }}</span>
-                    <span>创建人: {{ intervention.createdBy }}</span>
-                  </div>
-                </div>
-              </div>
-            </div>
-          </div>
-        </div>
-        <div class="modal-footer">
-          <button class="btn btn-secondary" @click="showInterventionModal = false">关闭</button>
-          <button class="btn btn-primary" @click="showInterventionModal = false">
-            <i class="fas fa-download"></i> 导出报告
-          </button>
-        </div>
-      </div>
-    </div>
     <transition name="toast">
       <div v-if="showToast" class="toast" :class="toastType">
         <i :class="toastType === 'success' ? 'fas fa-check-circle' : 'fas fa-info-circle'"></i>
@@ -901,6 +734,8 @@ import { ElMessageBox } from 'element-plus'
 import AddMarketing from '../components/addMarketing.vue'
 import { getMarketingList, getSopById, marketingAnalysis } from '../api/marketing'
 import OpenedCustomers from '../components/marketing/openedCustomers.vue'
+import ReplyCustomers from '../components/marketing/replyCustomers.vue'
+import InterventionCustomers from '../components/marketing/interventionCustomers.vue'
 import AiGenerator from '../components/marketing/aiGenerator.vue'
 import EmailList from '../components/marketing/emailList.vue'
 import MarketingDetail from '../components/marketing/marketingDetail.vue'
@@ -975,33 +810,6 @@ const tabs = ref()
 
 const filteredMarketingList = computed(() => filterTab.value === 'all' ? marketingList.value : marketingList.value.filter(c => c.status === filterTab.value))
 
-// 回复邮件的客户列表 - 需要人工审核和介入
-const repliedCustomers = ref([
-  { id: 1, name: '中东贸易公司', email: 'buyer@middle-east-trade.com', replyTime: '2026-03-23 11:45', replyContent: '感谢您的邮件,我们对您的产品很感兴趣,能否提供详细的产品规格和价格?', status: 'pending-review', sentiment: '正面', priority: '高', value: '高价值', annualPurchase: '¥600K+', lastOrder: '2026-02-10', requiresHumanReview: true, reviewNotes: '需要销售人员立即跟进,客户明确表示感兴趣', assignedTo: '', reviewedBy: '', reviewTime: '' },
-  { id: 2, name: '欧洲数据中心', email: 'procurement@eu-datacenter.eu', replyTime: '2026-03-23 10:20', replyContent: '我们正在评估新的存储解决方案,您能否安排一次技术演示?', status: 'pending-review', sentiment: '正面', priority: '高', value: '高价值', annualPurchase: '¥1M+', lastOrder: '2026-03-05', requiresHumanReview: true, reviewNotes: '需要技术团队配合,安排产品演示', assignedTo: '', reviewedBy: '', reviewTime: '' },
-  { id: 3, name: '北美分销', email: 'sales@na-dist.com', replyTime: '2026-03-22 16:30', replyContent: '价格太高了,能否提供折扣?', status: 'pending-review', sentiment: '中立', priority: '中', value: '中价值', annualPurchase: '¥250K', lastOrder: '2026-01-15', requiresHumanReview: true, reviewNotes: '需要销售经理评估折扣空间', assignedTo: '', reviewedBy: '', reviewTime: '' },
-  { id: 4, name: '东南亚进口商', email: 'import@sea-import.com', replyTime: '2026-03-22 14:15', replyContent: '我们有兴趣,但需要了解更多关于技术支持的信息。', status: 'pending-review', sentiment: '正面', priority: '中', value: '中价值', annualPurchase: '¥180K', lastOrder: '2026-02-01', requiresHumanReview: true, reviewNotes: '需要技术支持团队提供详细信息', assignedTo: '', reviewedBy: '', reviewTime: '' },
-  { id: 5, name: '日本电子商社', email: 'trade@japan-elec.jp', replyTime: '2026-03-21 17:00', replyContent: '暂时不需要,但会保留您的联系方式。', status: 'pending-review', sentiment: '中立', priority: '低', value: '低价值', annualPurchase: '¥80K', lastOrder: '2025-10-20', requiresHumanReview: false, reviewNotes: '可自动标记为后续跟进', assignedTo: '', reviewedBy: '', reviewTime: '' },
-  { id: 6, name: '韩国科技公司', email: 'contact@korea-tech.kr', replyTime: '2026-03-21 15:30', replyContent: '这不是我们需要的产品类型。', status: 'pending-review', sentiment: '负面', priority: '低', value: '低价值', annualPurchase: '¥40K', lastOrder: '2025-09-10', requiresHumanReview: false, reviewNotes: '可自动标记为不适配,从列表中移除', assignedTo: '', reviewedBy: '', reviewTime: '' }
-])
-
-// 人工干预列表 - 保护有效客户
-const interventions = ref([
-  { id: 1, campaignId: 1, type: '客户保护', reason: '高价值客户,需要人工跟进', affectedCount: 12, status: 'active', createdTime: '2026-03-23 12:00', createdBy: '销售经理', description: '将高价值客户从自动营销中移除,由销售人员进行专属跟进' },
-  { id: 2, campaignId: 2, type: '审核干预', reason: '回复邮件需要人工审核', affectedCount: 6, status: 'active', createdTime: '2026-03-23 11:30', createdBy: '营销主管', description: '所有回复邮件必须经过人工审核后才能进行后续跟进' },
-  { id: 3, campaignId: 3, type: '内容调整', reason: '提高邮件个性化程度', affectedCount: 89, status: 'active', createdTime: '2026-03-22 10:00', createdBy: '营销主管', description: '根据客户历史数据调整邮件内容,提高相关性' },
-  { id: 4, campaignId: 1, type: '发送暂停', reason: '等待销售人员反馈', affectedCount: 45, status: 'active', createdTime: '2026-03-21 14:30', createdBy: '销售经理', description: '暂停向该客户群体发送邮件,等待销售团队的跟进反馈' }
-])
-
-// 客户保护白名单 - 防止AI营销影响
-const protectedCustomersList = ref([
-  { id: 1, name: '阿里巴巴国际', email: 'contact@alibaba-intl.com', protectionLevel: '高', reason: '高价值客户', annualValue: '¥500K+', status: 'active', protectedSince: '2026-03-20', assignedSalesRep: '张三' },
-  { id: 2, name: '迪拜电子商务', email: 'info@dubai-ecom.ae', protectionLevel: '高', reason: '高价值客户', annualValue: '¥800K+', status: 'active', protectedSince: '2026-03-18', assignedSalesRep: '李四' },
-  { id: 3, name: '德国工业集团', email: 'procurement@de-industry.de', protectionLevel: '高', reason: '高价值客户', annualValue: '¥1.2M+', status: 'active', protectedSince: '2026-03-15', assignedSalesRep: '王五' },
-  { id: 4, name: '沙特阿美贸易', email: 'trade@aramco-sa.com', protectionLevel: '中', reason: '活跃客户', annualValue: '¥200K', status: 'active', protectedSince: '2026-03-22', assignedSalesRep: '赵六' },
-  { id: 5, name: '英国分销商', email: 'sales@uk-dist.co.uk', protectionLevel: '中', reason: '活跃客户', annualValue: '¥300K', status: 'active', protectedSince: '2026-03-21', assignedSalesRep: '孙七' }
-])
-
 const showCustomerPreview = ref(false)
 const showSubjectSuggestions = ref(false)
 
@@ -1253,14 +1061,14 @@ const openOpenedCustomers = (campaign) => {
   openedCustomersRef.value.open(campaign)
 }
 
+const replyCustomersRef = ref()
 const openReplyReview = (campaign) => {
-  selectedReplyReview.value = campaign
-  showReplyReviewModal.value = true
+  replyCustomersRef.value.open(campaign)
 }
 
+const interventionCustomersRef = ref()
 const openInterventionModal = (campaign) => {
-  selectedIntervention.value = campaign
-  showInterventionModal.value = true
+  interventionCustomersRef.value.open(campaign)
 }
 
 const approveReply = (reply) => {
@@ -1300,22 +1108,6 @@ const assignReplyToSales = (reply, salesPerson) => {
   toast(`已将 ${reply.name} 分配给 ${salesPerson}`, 'success')
 }
 
-const createIntervention = (type, reason) => {
-  const newIntervention = {
-    id: interventions.value.length + 1,
-    campaignId: selectedIntervention.value.id,
-    type: type,
-    reason: reason,
-    affectedCount: selectedIntervention.value.stats.sent,
-    status: 'active',
-    createdTime: new Date().toLocaleString('zh-CN'),
-    createdBy: '当前用户'
-  }
-  interventions.value.push(newIntervention)
-  toast(`已创建人工干预: ${type}`, 'success')
-  showInterventionModal.value = false
-}
-
 const contactCustomer = (customer) => {
   toast(`正在联系 ${customer.name}...`, 'success')
 }