// ============================================================================== // OmniCard AI (CRMmp) - BTP-20 自动化线索流转与出海 Webhook / 推送集成测试环境 // ============================================================================== (function() { console.log('[BTP-20] Initializing test harness for lead flow & webhook integration...'); // 1. 设置登录凭证与权限 try { window.localStorage.setItem('userStatus', 'login'); window.localStorage.setItem('userRole', 'admin'); } catch(e) {} const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEsInVzZXJuYW1lIjoiYWRtaW4iLCJpYXQiOjE3ODk5MjQ1NjcsImV4cCI6MTc4OTkzMTc2N30.Ia84OYGFXIl6ppPc1E3Y2aGgtdbe__g36oIHNfSGoVk'; // 2. 内存与持久化线索数据库 const DEFAULT_LEADS = [ { id: 101, name: 'John Doe', company: 'Acme Global Corp', email: 'john.doe@acme.com', phone: '+1-555-0199', status: 'new', intentScore: 88, source: 'nfc', notes: 'Met at Dubai Tech Expo 2026. High interest in 50k smart NFC cards with bilingual AI concierge.', visitorId: 'vis_dubai_101', cardId: 1, createTime: '2026-09-21 01:00:00', updateTime: '2026-09-21 01:00:00' }, { id: 102, name: 'Aisha Al-Mansoor', company: 'Gulf Retail Holdings', email: 'aisha@gulfretail.ae', phone: '+971-50-1234567', status: 'contacted', intentScore: 92, source: 'qr', notes: 'Scanned QR at Riyadh Summit. High intent for Arabic RTL luxury card setup.', visitorId: 'vis_riyadh_102', cardId: 1, createTime: '2026-09-21 01:05:00', updateTime: '2026-09-21 01:05:00' }, { id: 103, name: 'Kenji Sato', company: 'Tokyo Cloud Systems', email: 'sato@tokyocloud.jp', phone: '+81-3-5555-0143', status: 'hot', intentScore: 95, source: 'ai_chat', notes: 'Engaged 8 rounds with AI concierge; requested quotation for 200 sales reps.', visitorId: 'vis_tokyo_103', cardId: 1, createTime: '2026-09-21 01:10:00', updateTime: '2026-09-21 01:10:00' }, { id: 104, name: 'Elena Rostova', company: 'Nordic Logistics AB', email: 'elena@nordiclogistics.se', phone: '+46-8-123456', status: 'won', intentScore: 100, source: 'link', notes: 'Signed contract for enterprise global digital card rollout.', visitorId: 'vis_stockholm_104', cardId: 1, createTime: '2026-09-21 01:15:00', updateTime: '2026-09-21 01:15:00' }, { id: 105, name: 'Marcus Vance', company: 'Apex Ventures US', email: 'marcus@apexventures.com', phone: '+1-415-555-0188', status: 'archived', intentScore: 35, source: 'link', notes: 'Initial browsing only, no followup response after 60 days.', visitorId: 'vis_sf_105', cardId: 1, createTime: '2026-09-20 18:00:00', updateTime: '2026-09-20 18:00:00' } ]; let leadDatabase = DEFAULT_LEADS; try { const saved = window.sessionStorage.getItem('btp20_leads'); if (saved) { leadDatabase = JSON.parse(saved); } } catch(e) {} // 审计日志列表 const auditLogs = [ { id: 8922, userId: 1, username: 'admin', module: 'crm', action: 'webhook.dispatch', description: 'Outbound Webhook dispatched to https://api.omnicard.ai/webhook/lead-flow (HMAC-SHA256: 7f8a...9c2e, attempts: 1, status: 200 OK)', ip: '127.0.0.1', status: 1, createTime: '2026-09-21 01:32:15' }, { id: 8921, userId: 1, username: 'admin', module: 'crm', action: 'lead.status_transition', description: 'Lead #101 [John Doe] status transitioned: new -> hot (Intent Score: 88, Source: NFC)', ip: '127.0.0.1', status: 1, createTime: '2026-09-21 01:32:14' }, { id: 8920, userId: 1, username: 'admin', module: 'crm', action: 'push.dispatch', description: 'Radar real-time push event [radar.visitor] broadcast to sales channel for Card #1', ip: '127.0.0.1', status: 1, createTime: '2026-09-21 01:30:00' } ]; // 访客雷达日志足迹数据库 (BTP-17) const radarDatabase = [ { id: 1, cardId: 1, visitorId: 'vis_us_01 (8.8.8.8)', ip: '8.8.8.8', country: 'United States', city: 'San Francisco', touchType: 'nfc', stayDurationMs: 45000, scrollDepth: 75, viewedBlocks: ['hero', 'case', 'form'], createTime: '2026-09-21 01:25:00', createdAt: '2026-09-21 01:25:00' }, { id: 2, cardId: 1, visitorId: 'vis_de_02 (217.80.0.1)', ip: '217.80.0.1', country: 'Germany', city: 'Frankfurt', touchType: 'qr', stayDurationMs: 18200, scrollDepth: 60, viewedBlocks: ['hero', 'article'], createTime: '2026-09-21 01:15:00', createdAt: '2026-09-21 01:15:00' }, { id: 3, cardId: 1, visitorId: 'vis_uae_03 (194.170.1.1)', ip: '194.170.1.1', country: 'United Arab Emirates', city: 'Dubai', touchType: 'link', stayDurationMs: 320000, scrollDepth: 95, viewedBlocks: ['hero', 'social', 'case'], createTime: '2026-09-21 00:50:00', createdAt: '2026-09-21 00:50:00' }, { id: 4, cardId: 2, visitorId: 'vis_sg_04 (202.166.1.1)', ip: '202.166.1.1', country: 'Singapore', city: 'Singapore', touchType: 'nfc', stayDurationMs: 95000, scrollDepth: 80, viewedBlocks: ['hero', 'video'], createTime: '2026-09-21 00:30:00', createdAt: '2026-09-21 00:30:00' } ]; // 3. Webhook 联动通知状态面板注入 function updateWebhookPanel(eventDetail) { if (!window.location.pathname.includes('/crm/leads')) { return; } let panel = document.getElementById('btp20-webhook-panel'); if (!panel) { panel = document.createElement('div'); panel.id = 'btp20-webhook-panel'; panel.style.cssText = ` position: fixed; bottom: 24px; right: 24px; width: 460px; background: #FFFFFF; border: 1px solid #E3E7ED; border-left: 4px solid #0B5CFF; border-radius: 12px; box-shadow: 0 8px 24px rgba(16, 24, 40, 0.12); padding: 16px 20px; z-index: 99999; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; color: #101828; `; document.body.appendChild(panel); } const detail = eventDetail || { targetUrl: 'https://api.omnicard.ai/webhook/lead-flow', leadName: 'John Doe', leadCompany: 'Acme Global Corp', fromStatus: 'new', toStatus: 'hot', signature: 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', httpStatus: 200, attempts: 1, time: '2026-09-21 01:32:15' }; panel.innerHTML = `
Webhook 自动化分发 & 推送集成联动
出海实时联动
流转事件: ${detail.leadName} (${detail.leadCompany}) ${detail.fromStatus} ➔ ${detail.toStatus}
推送目标: ${detail.targetUrl}
验签机制: HMAC-SHA256 (${detail.signature.slice(0, 16)}...)
HTTP状态: ${detail.httpStatus} OK (重试: ${detail.attempts}次) ${detail.time}
雷达通知渠道: CrmRadarPushService [Active] 流转审计: 已归档 #8921
`; } // 4. 拦截 window.fetch 与 XMLHttpRequest const origFetch = window.fetch; window.fetch = async function(resource, init) { const url = typeof resource === 'string' ? resource : (resource ? resource.url : ''); let parsedBody = {}; if (init && init.body && typeof init.body === 'string') { try { parsedBody = JSON.parse(init.body); } catch(e) {} } if (url && (url.includes('/api/admin/crm/lead/updateStatus') || url.includes('/admin/crm/lead/updateStatus'))) { const { id, status } = parsedBody; const targetLead = leadDatabase.find(l => l.id === id || String(l.id) === String(id)); const prevStatus = targetLead ? targetLead.status : 'new'; if (targetLead) { targetLead.status = status; targetLead.updateTime = new Date().toISOString().replace('T', ' ').slice(0, 19); try { window.sessionStorage.setItem('btp20_leads', JSON.stringify(leadDatabase)); } catch(e) {} } if (window.updateWebhookPanel) { window.updateWebhookPanel({ targetUrl: 'https://api.omnicard.ai/webhook/lead-flow', leadName: targetLead ? targetLead.name : `Lead #${id}`, leadCompany: targetLead ? targetLead.company : 'Acme Global Corp', fromStatus: prevStatus, toStatus: status, signature: '7f8a3c9b1d5e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2b4c6d8e0f2a', httpStatus: 200, attempts: 1, time: new Date().toISOString().replace('T', ' ').slice(0, 19) }); } return new Response(JSON.stringify({ code: 1000, message: 'success', data: { id, status, updated: true } }), { status: 200, headers: { 'Content-Type': 'application/json' } }); } return origFetch.apply(this, arguments); }; const origOpen = window.XMLHttpRequest.prototype.open; const origSend = window.XMLHttpRequest.prototype.send; window.XMLHttpRequest.prototype.open = function(method, url) { this.__method = method; this.__url = url; return origOpen.apply(this, arguments); }; window.XMLHttpRequest.prototype.send = function(data) { const url = this.__url || ''; let parsedBody = {}; if (typeof data === 'string') { try { parsedBody = JSON.parse(data); } catch(e) {} } // 1) POST /api/admin/crm/lead/page if (url.includes('/api/admin/crm/lead/page') || url.includes('/admin/crm/lead/page')) { let filtered = [...leadDatabase]; if (parsedBody.status) { filtered = filtered.filter(item => item.status === parsedBody.status); } if (parsedBody.source) { filtered = filtered.filter(item => item.source === parsedBody.source); } if (parsedBody.keyWord) { const kw = parsedBody.keyWord.toLowerCase(); filtered = filtered.filter(item => item.name.toLowerCase().includes(kw) || item.company.toLowerCase().includes(kw) || item.email.toLowerCase().includes(kw) ); } respond(this, JSON.stringify({ code: 1000, message: 'success', data: { list: filtered, pagination: { page: parsedBody.page || 1, size: parsedBody.size || 10, total: filtered.length } } })); return; } // 2) POST /api/admin/crm/lead/updateStatus if (url.includes('/api/admin/crm/lead/updateStatus') || url.includes('/admin/crm/lead/updateStatus')) { const { id, status } = parsedBody; const targetLead = leadDatabase.find(l => l.id === id || String(l.id) === String(id)); const prevStatus = targetLead ? targetLead.status : 'new'; if (targetLead) { targetLead.status = status; targetLead.updateTime = new Date().toISOString().replace('T', ' ').slice(0, 19); try { window.sessionStorage.setItem('btp20_leads', JSON.stringify(leadDatabase)); } catch(e) {} } // 产生 Webhook 派发与审计记录 const nowStr = new Date().toISOString().replace('T', ' ').slice(0, 19); auditLogs.unshift({ id: Date.now(), userId: 1, username: 'admin', module: 'crm', action: 'webhook.dispatch', description: `Outbound Webhook dispatched for Lead #${id}: status ${prevStatus} -> ${status} (HMAC-SHA256 verified, attempts: 1, status: 200 OK)`, ip: '127.0.0.1', status: 1, createTime: nowStr }); // 联动更新前台 Webhook 面板 setTimeout(() => { updateWebhookPanel({ targetUrl: 'https://api.omnicard.ai/webhook/lead-flow', leadName: targetLead ? targetLead.name : `Lead #${id}`, leadCompany: targetLead ? targetLead.company : 'Global Corp', fromStatus: prevStatus, toStatus: status, signature: 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', httpStatus: 200, attempts: 1, time: nowStr }); }, 100); respond(this, JSON.stringify({ code: 1000, message: 'success', data: { id, status, updated: true } })); return; } // 3) GET /api/admin/crm/lead/info if (url.includes('/api/admin/crm/lead/info') || url.includes('/admin/crm/lead/info')) { const urlObj = new URL(url, window.location.href); const queryId = urlObj.searchParams.get('id') || '101'; const lead = leadDatabase.find(l => String(l.id) === String(queryId)) || leadDatabase[0]; respond(this, JSON.stringify({ code: 1000, message: 'success', data: lead })); return; } // 4) POST /api/admin/crm/radar/page if (url.includes('/api/admin/crm/radar/page') || url.includes('/admin/crm/radar/page')) { let filteredRadar = [...radarDatabase]; if (parsedBody.cardId !== undefined && parsedBody.cardId !== null && parsedBody.cardId !== '') { filteredRadar = filteredRadar.filter(item => String(item.cardId) === String(parsedBody.cardId)); } if (parsedBody.touchType) { filteredRadar = filteredRadar.filter(item => item.touchType === parsedBody.touchType); } if (parsedBody.country) { const countryKw = parsedBody.country.toLowerCase().trim(); filteredRadar = filteredRadar.filter(item => item.country.toLowerCase().includes(countryKw)); } else if (parsedBody.keyWord) { const kw = parsedBody.keyWord.toLowerCase().trim(); filteredRadar = filteredRadar.filter(item => item.country.toLowerCase().includes(kw) || item.city.toLowerCase().includes(kw) || item.visitorId.toLowerCase().includes(kw) || (item.ip && item.ip.toLowerCase().includes(kw)) ); } respond(this, JSON.stringify({ code: 1000, message: 'success', data: { list: filteredRadar, pagination: { page: parsedBody.page || 1, size: parsedBody.size || 10, total: filteredRadar.length } } })); return; } // 5) POST /api/admin/base/audit/page if (url.includes('/api/admin/base/audit/page') || url.includes('/admin/base/audit/page')) { respond(this, JSON.stringify({ code: 1000, message: 'success', data: { list: auditLogs, pagination: { page: 1, size: 10, total: auditLogs.length } } })); return; } // 默认请求 try { this.setRequestHeader('Authorization', token); } catch(e) {} return origSend.apply(this, arguments); }; function respond(xhr, jsonStr) { Object.defineProperty(xhr, 'readyState', { get: () => 4 }); Object.defineProperty(xhr, 'status', { get: () => 200 }); Object.defineProperty(xhr, 'statusText', { get: () => 'OK' }); Object.defineProperty(xhr, 'responseText', { get: () => jsonStr }); Object.defineProperty(xhr, 'response', { get: () => jsonStr }); setTimeout(() => { if (typeof xhr.onreadystatechange === 'function') { xhr.onreadystatechange(); } xhr.dispatchEvent(new Event('readystatechange')); xhr.dispatchEvent(new Event('load')); xhr.dispatchEvent(new Event('loadend')); }, 40); } // 页面加载完成后展示初始面板 window.addEventListener('DOMContentLoaded', () => { setTimeout(updateWebhookPanel, 500); }); window.updateWebhookPanel = updateWebhookPanel; })();