|
|
@@ -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')
|
|
|
}
|