|
@@ -142,219 +142,7 @@
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<!-- Campaign Detail Modal -->
|
|
<!-- Campaign Detail Modal -->
|
|
|
- <div v-if="showCampaignDetailModal" class="modal-overlay" @click.self="showCampaignDetailModal = false">
|
|
|
|
|
- <div class="modal campaign-detail-modal">
|
|
|
|
|
- <div class="modal-header">
|
|
|
|
|
- <div class="modal-title">
|
|
|
|
|
- <i class="fas fa-chart-bar"></i> 活动详情
|
|
|
|
|
- <span class="detail-status" :class="selectedCampaign?.status">{{ getStatusLabel(selectedCampaign?.status) }}</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- <button class="modal-close" @click="showCampaignDetailModal = false"><i class="fas fa-times"></i></button>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="modal-body" v-if="selectedCampaign">
|
|
|
|
|
- <!-- 基本信息 -->
|
|
|
|
|
- <div class="detail-section">
|
|
|
|
|
- <div class="section-header">
|
|
|
|
|
- <h4><i class="fas fa-info-circle"></i> 基本信息</h4>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="detail-grid">
|
|
|
|
|
- <div class="detail-item">
|
|
|
|
|
- <label>活动名称</label>
|
|
|
|
|
- <span>{{ selectedCampaign.name }}</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="detail-item">
|
|
|
|
|
- <label>活动类型</label>
|
|
|
|
|
- <span>{{ getTypeLabel(selectedCampaign.type) }}</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="detail-item">
|
|
|
|
|
- <label>优先级</label>
|
|
|
|
|
- <span>{{ getPriorityLabel(selectedCampaign.priority) }}</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="detail-item">
|
|
|
|
|
- <label>负责人</label>
|
|
|
|
|
- <span>{{ selectedCampaign.owner || '张经理' }}</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="detail-item">
|
|
|
|
|
- <label>创建时间</label>
|
|
|
|
|
- <span>{{ selectedCampaign.createTime }}</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="detail-item">
|
|
|
|
|
- <label>活动周期</label>
|
|
|
|
|
- <span>{{ selectedCampaign.beginData }} 至 {{ selectedCampaign.endDate }}</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="detail-item full-width">
|
|
|
|
|
- <label>活动描述</label>
|
|
|
|
|
- <span>{{ selectedCampaign.description }}</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 核心数据 -->
|
|
|
|
|
- <div class="detail-section">
|
|
|
|
|
- <div class="section-header">
|
|
|
|
|
- <h4><i class="fas fa-chart-line"></i> 核心数据</h4>
|
|
|
|
|
- <div class="progress-info">达成 {{ selectedCampaign.progressPercent || 0 }}%</div>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="detail-progress-bar">
|
|
|
|
|
- <div class="progress-fill" :style="{ width: (selectedCampaign.progressPercent || 0) + '%' }"></div>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="metrics-grid">
|
|
|
|
|
- <div class="metric-card">
|
|
|
|
|
- <div class="metric-icon blue"><i class="fas fa-paper-plane"></i></div>
|
|
|
|
|
- <div class="metric-content">
|
|
|
|
|
- <div class="metric-value">{{ formatNumber(selectedCampaign.sendVolume) }}</div>
|
|
|
|
|
- <div class="metric-label">已发送</div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="metric-card">
|
|
|
|
|
- <div class="metric-icon green"><i class="fas fa-envelope-open"></i></div>
|
|
|
|
|
- <div class="metric-content">
|
|
|
|
|
- <div class="metric-value">{{ Math.round(selectedCampaign.sendVolume * selectedCampaign.openRate / 100) }}</div>
|
|
|
|
|
- <div class="metric-label">打开数 ({{ selectedCampaign.openRate }}%)</div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="metric-card">
|
|
|
|
|
- <div class="metric-icon purple"><i class="fas fa-reply"></i></div>
|
|
|
|
|
- <div class="metric-content">
|
|
|
|
|
- <div class="metric-value">{{ Math.round(selectedCampaign.sendVolume * selectedCampaign.replyRate / 100) }}</div>
|
|
|
|
|
- <div class="metric-label">回复数 ({{ selectedCampaign.replyRate }}%)</div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 触达渠道 -->
|
|
|
|
|
- <div class="detail-section">
|
|
|
|
|
- <div class="section-header">
|
|
|
|
|
- <h4><i class="fas fa-bullhorn"></i> 触达渠道</h4>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="channel-detail-list">
|
|
|
|
|
- <div class="channel-detail-item">
|
|
|
|
|
- <div class="channel-icon-wrapper" :style="getChannelStyleByName('邮件')">
|
|
|
|
|
- <i :class="getChannelIcon('邮件')"></i>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="channel-detail-info">
|
|
|
|
|
- <div class="channel-detail-name">邮件</div>
|
|
|
|
|
- <div class="channel-detail-stats">
|
|
|
|
|
- <span>发送: {{ Math.round(selectedCampaign.sendVolume) }}</span>
|
|
|
|
|
- <span>打开率: {{ selectedCampaign.openRate }}%</span>
|
|
|
|
|
- <span>回复率: {{ selectedCampaign.replyRate }}%</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="channel-status">
|
|
|
|
|
- <span class="status-dot active"></span> 运行中
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 目标客户 -->
|
|
|
|
|
- <div class="detail-section">
|
|
|
|
|
- <div class="section-header">
|
|
|
|
|
- <h4><i class="fas fa-users"></i> 目标客户</h4>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="target-info">
|
|
|
|
|
- <div class="target-stat">
|
|
|
|
|
- <div class="target-value">{{ selectedCampaign.targetCount || 847 }}</div>
|
|
|
|
|
- <div class="target-label">目标客户数</div>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="target-stat">
|
|
|
|
|
- <div class="target-value">{{ selectedCampaign.reachedCount || 623 }}</div>
|
|
|
|
|
- <div class="target-label">已触达</div>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="target-stat">
|
|
|
|
|
- <div class="target-value">{{ selectedCampaign.pendingCount || 224 }}</div>
|
|
|
|
|
- <div class="target-label">待触达</div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <!-- SOP 序列 -->
|
|
|
|
|
- <div class="detail-section">
|
|
|
|
|
- <div class="section-header">
|
|
|
|
|
- <h4><i class="fas fa-robot"></i> SOP 自动化序列</h4>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="sop-detail">
|
|
|
|
|
- <div class="sop-header-info">
|
|
|
|
|
- <span class="sop-name">{{ sopDetails.name }}</span>
|
|
|
|
|
- <span class="sop-steps">{{ sopDetails.details.length }} 步序列</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="sop-timeline-detail">
|
|
|
|
|
- <div class="sop-step-item pending" v-for="item in sopDetails.details" :key="item.id">
|
|
|
|
|
- <div class="step-marker">{{ item.step }}</div>
|
|
|
|
|
- <div class="step-content">
|
|
|
|
|
- <div class="step-title">{{ item.name }}</div>
|
|
|
|
|
- <div class="step-meta">第{{ item.days }}天 · 邮件 · 待执行</div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 最近互动 -->
|
|
|
|
|
- <div class="detail-section">
|
|
|
|
|
- <div class="section-header">
|
|
|
|
|
- <h4><i class="fas fa-comments"></i> 最近互动</h4>
|
|
|
|
|
- <button class="btn-link" @click="showAllInteractions">查看全部</button>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="interactions-list">
|
|
|
|
|
- <div class="interaction-item">
|
|
|
|
|
- <div class="interaction-avatar">M</div>
|
|
|
|
|
- <div class="interaction-content">
|
|
|
|
|
- <div class="interaction-header">
|
|
|
|
|
- <span class="interaction-name">Mohammed Al-Rashid</span>
|
|
|
|
|
- <span class="interaction-time">10分钟前</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="interaction-text">回复了您的邮件,询问价格和交期</div>
|
|
|
|
|
- <div class="interaction-tags">
|
|
|
|
|
- <span class="tag reply">回复</span>
|
|
|
|
|
- <span class="tag interested">高意向</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="interaction-item">
|
|
|
|
|
- <div class="interaction-avatar">A</div>
|
|
|
|
|
- <div class="interaction-content">
|
|
|
|
|
- <div class="interaction-header">
|
|
|
|
|
- <span class="interaction-name">Ahmed Hassan</span>
|
|
|
|
|
- <span class="interaction-time">1小时前</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="interaction-text">打开了您的邮件</div>
|
|
|
|
|
- <div class="interaction-tags">
|
|
|
|
|
- <span class="tag open">打开</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="interaction-item">
|
|
|
|
|
- <div class="interaction-avatar">S</div>
|
|
|
|
|
- <div class="interaction-content">
|
|
|
|
|
- <div class="interaction-header">
|
|
|
|
|
- <span class="interaction-name">Sarah Williams</span>
|
|
|
|
|
- <span class="interaction-time">3小时前</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="interaction-text">点击了邮件中的产品链接</div>
|
|
|
|
|
- <div class="interaction-tags">
|
|
|
|
|
- <span class="tag click">点击</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="modal-footer">
|
|
|
|
|
- <button class="btn btn-secondary" @click="duplicateCampaign(selectedCampaign)">
|
|
|
|
|
- <i class="fas fa-copy"></i> 复制活动
|
|
|
|
|
- </button>
|
|
|
|
|
- <button class="btn btn-secondary" @click="exportCampaignReport(selectedCampaign)">
|
|
|
|
|
- <i class="fas fa-download"></i> 导出报告
|
|
|
|
|
- </button>
|
|
|
|
|
- <button class="btn btn-secondary" @click="showCampaignDetailModal = false">关闭</button>
|
|
|
|
|
- <button class="btn btn-primary" @click="editCampaign(selectedCampaign)">
|
|
|
|
|
- <i class="fas fa-edit"></i> 编辑活动
|
|
|
|
|
- </button>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ <MarketingDetail ref="marketingDetailRef" />
|
|
|
|
|
|
|
|
<!-- All Interactions Modal -->
|
|
<!-- All Interactions Modal -->
|
|
|
<div v-if="showInteractionsModal" class="modal-overlay" @click.self="showInteractionsModal = false">
|
|
<div v-if="showInteractionsModal" class="modal-overlay" @click.self="showInteractionsModal = false">
|
|
@@ -1115,6 +903,7 @@ import { getMarketingList, getSopById, marketingAnalysis } from '../api/marketin
|
|
|
import OpenedCustomers from '../components/marketing/openedCustomers.vue'
|
|
import OpenedCustomers from '../components/marketing/openedCustomers.vue'
|
|
|
import AiGenerator from '../components/marketing/aiGenerator.vue'
|
|
import AiGenerator from '../components/marketing/aiGenerator.vue'
|
|
|
import EmailList from '../components/marketing/emailList.vue'
|
|
import EmailList from '../components/marketing/emailList.vue'
|
|
|
|
|
+import MarketingDetail from '../components/marketing/marketingDetail.vue'
|
|
|
|
|
|
|
|
const filterTab = ref('all')
|
|
const filterTab = ref('all')
|
|
|
|
|
|
|
@@ -1124,7 +913,7 @@ const showHistoryDetailModal = ref(false)
|
|
|
const selectedHistoryCampaign = ref(null)
|
|
const selectedHistoryCampaign = ref(null)
|
|
|
const showTemplatePreview = ref(false)
|
|
const showTemplatePreview = ref(false)
|
|
|
const showCreateTemplateModal = ref(false)
|
|
const showCreateTemplateModal = ref(false)
|
|
|
-const showCampaignDetailModal = ref(false)
|
|
|
|
|
|
|
+
|
|
|
const showInteractionsModal = ref(false)
|
|
const showInteractionsModal = ref(false)
|
|
|
const selectedCampaignInteractions = ref([])
|
|
const selectedCampaignInteractions = ref([])
|
|
|
const selectedCampaign = ref(null)
|
|
const selectedCampaign = ref(null)
|
|
@@ -1285,8 +1074,6 @@ const formatNumber = (n) => n >= 1000 ? (n / 1000).toFixed(1) + 'K' : n.toLocale
|
|
|
const getStatusLabel = (s) => ({ 0: '未开始', 1: '进行中', 2: '已暂停', 3: '已完成' }[s] || s)
|
|
const getStatusLabel = (s) => ({ 0: '未开始', 1: '进行中', 2: '已暂停', 3: '已完成' }[s] || s)
|
|
|
const getChannelIcon = (c) => ({ '邮件': 'fas fa-envelope', 'WhatsApp': 'fab fa-whatsapp', 'LinkedIn': 'fab fa-linkedin' }[c] || 'fas fa-comment')
|
|
const getChannelIcon = (c) => ({ '邮件': 'fas fa-envelope', 'WhatsApp': 'fab fa-whatsapp', 'LinkedIn': 'fab fa-linkedin' }[c] || 'fas fa-comment')
|
|
|
|
|
|
|
|
-const getTypeLabel = (type) => ({ 1: '潜客开发', 2: '客户培养', 3: '客户唤醒', 4: '产品推广', 5: '节日营销', 6: '展会跟进' }[type] || type)
|
|
|
|
|
-const getPriorityLabel = (priority) => ({ 1: '紧急', 2: '高优先', 3: '中等', 4: '低优先' }[priority] || priority)
|
|
|
|
|
const getRegionLabel = (region) => ({ 'middle-east': '中东', 'europe': '欧洲', 'north-america': '北美', 'southeast-asia': '东南亚', 'africa': '非洲', 'south-america': '南美' }[region] || '全部地区')
|
|
const getRegionLabel = (region) => ({ 'middle-east': '中东', 'europe': '欧洲', 'north-america': '北美', 'southeast-asia': '东南亚', 'africa': '非洲', 'south-america': '南美' }[region] || '全部地区')
|
|
|
const getIndustryLabel = (industry) => ({ 'datacenter': '数据中心', 'consumer': '消费电子', 'distributor': '电子分销', 'retail': '零售', 'manufacturing': '制造业' }[industry] || '全部行业')
|
|
const getIndustryLabel = (industry) => ({ 'datacenter': '数据中心', 'consumer': '消费电子', 'distributor': '电子分销', 'retail': '零售', 'manufacturing': '制造业' }[industry] || '全部行业')
|
|
|
const getChannelName = (id) => ({ email: '邮件营销', whatsapp: 'WhatsApp', linkedin: 'LinkedIn', wechat: '微信企业号' }[id] || id)
|
|
const getChannelName = (id) => ({ email: '邮件营销', whatsapp: 'WhatsApp', linkedin: 'LinkedIn', wechat: '微信企业号' }[id] || id)
|
|
@@ -1331,15 +1118,10 @@ const copyEmail = () => {
|
|
|
const useTemplate = (tpl) => { showTemplatesModal.value = false; toast(`已应用模板: ${tpl.subject}`, 'success') }
|
|
const useTemplate = (tpl) => { showTemplatesModal.value = false; toast(`已应用模板: ${tpl.subject}`, 'success') }
|
|
|
const toggleCampaign = (c) => { c.status = c.status === 'paused' ? 'active' : 'paused'; toast(c.status === 'paused' ? '已暂停' : '已恢复', 'success') }
|
|
const toggleCampaign = (c) => { c.status = c.status === 'paused' ? 'active' : 'paused'; toast(c.status === 'paused' ? '已暂停' : '已恢复', 'success') }
|
|
|
|
|
|
|
|
-const sopDetails = ref()
|
|
|
|
|
|
|
+const marketingDetailRef = ref()
|
|
|
const showCampaignDetail = (campaign) => {
|
|
const showCampaignDetail = (campaign) => {
|
|
|
selectedCampaign.value = campaign
|
|
selectedCampaign.value = campaign
|
|
|
- showCampaignDetailModal.value = true
|
|
|
|
|
- getSopById(campaign.sopId).then(res => {
|
|
|
|
|
- if(res.code == 200){
|
|
|
|
|
- sopDetails.value = res.result
|
|
|
|
|
- }
|
|
|
|
|
- })
|
|
|
|
|
|
|
+ marketingDetailRef.value.open(campaign)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const showAllInteractions = () => {
|
|
const showAllInteractions = () => {
|
|
@@ -1584,12 +1366,6 @@ const resumeCampaignForCustomer = (customer) => {
|
|
|
toast(`已恢复向 ${customer.name} 发送营销邮件`, 'success')
|
|
toast(`已恢复向 ${customer.name} 发送营销邮件`, 'success')
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// 活动详情方法
|
|
|
|
|
-const openCampaignDetailModal = (campaign) => {
|
|
|
|
|
- selectedCampaign.value = campaign
|
|
|
|
|
- showCampaignDetailModal.value = true
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
const getChannelStyleByName = (ch) => {
|
|
const getChannelStyleByName = (ch) => {
|
|
|
const styles = {
|
|
const styles = {
|
|
|
'邮件': { background: 'rgba(99, 102, 241, 0.2)', color: 'var(--primary)' },
|
|
'邮件': { background: 'rgba(99, 102, 241, 0.2)', color: 'var(--primary)' },
|
|
@@ -1603,11 +1379,6 @@ const getChannelStyleByName = (ch) => {
|
|
|
const exportCampaignReport = (campaign) => {
|
|
const exportCampaignReport = (campaign) => {
|
|
|
toast(`正在导出"${campaign.name}"报告`, 'success')
|
|
toast(`正在导出"${campaign.name}"报告`, 'success')
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
-const editCampaign = (campaign) => {
|
|
|
|
|
- showCampaignDetailModal.value = false
|
|
|
|
|
- addMarketingRef.value.open(campaign)
|
|
|
|
|
-}
|
|
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|
|
|
<style scoped>
|
|
<style scoped>
|