当前位置: 首页 > news >正文

Vue3打造高效前端埋点系统

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Vue版前端埋点上报系统</title><script src="https://unpkg.com/vue@3/dist/vue.global.js"></script><style>:root {--primary-color: #3498db;--secondary-color: #2ecc71;--danger-color: #e74c3c;--warning-color: #f39c12;--dark-color: #2c3e50;--light-color: #ecf0f1;--border-radius: 6px;--box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);}* {margin: 0;padding: 0;box-sizing: border-box;font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;}body {background-color: #f5f7fa;color: #333;line-height: 1.6;}.container {max-width: 1400px;margin: 0 auto;padding: 20px;}header {background: white;padding: 20px;border-radius: var(--border-radius);box-shadow: var(--box-shadow);margin-bottom: 20px;display: flex;justify-content: space-between;align-items: center;}h1 {color: var(--dark-color);font-size: 1.8rem;}.status-bar {display: flex;gap: 15px;}.status-item {display: flex;flex-direction: column;align-items: center;}.status-value {font-size: 1.5rem;font-weight: bold;color: var(--primary-color);}.status-label {font-size: 0.8rem;color: #7f8c8d;}.dashboard {display: grid;grid-template-columns: 1fr 1fr;gap: 20px;margin-bottom: 20px;}@media (max-width: 768px) {.dashboard {grid-template-columns: 1fr;}}.card {background: white;border-radius: var(--border-radius);box-shadow: var(--box-shadow);padding: 20px;}.card-header {display: flex;justify-content: space-between;align-items: center;margin-bottom: 15px;padding-bottom: 10px;border-bottom: 1px solid #eee;}.card-title {font-size: 1.2rem;font-weight: 600;color: var(--dark-color);}.card-actions {display: flex;gap: 10px;}button {padding: 8px 15px;border: none;border-radius: var(--border-radius);cursor: pointer;font-weight: 600;transition: all 0.3s ease;font-size: 0.9rem;}.btn-primary {background: var(--primary-color);color: white;}.btn-success {background: var(--secondary-color);color: white;}.btn-danger {background: var(--danger-color);color: white;}.btn-warning {background: var(--warning-color);color: white;}button:hover {opacity: 0.9;transform: translateY(-2px);}button:disabled {background: #bdc3c7;cursor: not-allowed;transform: none;}.stats-grid {display: grid;grid-template-columns: repeat(4, 1fr);gap: 15px;}@media (max-width: 992px) {.stats-grid {grid-template-columns: repeat(2, 1fr);}}@media (max-width: 576px) {.stats-grid {grid-template-columns: 1fr;}}.stat-card {background: #f8f9fa;padding: 15px;border-radius: var(--border-radius);text-align: center;border-left: 4px solid var(--primary-color);}.stat-value {font-size: 1.8rem;font-weight: bold;margin-bottom: 5px;}.stat-label {font-size: 0.9rem;color: #7f8c8d;}.chart-container {height: 250px;margin-top: 10px;}.events-list {max-height: 300px;overflow-y: auto;}.event-item {padding: 12px 15px;border-bottom: 1px solid #eee;display: flex;justify-content: space-between;align-items: center;}.event-item:last-child {border-bottom: none;}.event-type {display: inline-block;padding: 3px 8px;border-radius: 20px;font-size: 0.8rem;font-weight: 600;}.event-type.click {background: rgba(52, 152, 219, 0.2);color: var(--primary-color);}.event-type.pageview {background: rgba(46, 204, 113, 0.2);color: var(--secondary-color);}.event-type.error {background: rgba(231, 76, 60, 0.2);color: var(--danger-color);}.event-type.performance {background: rgba(243, 156, 18, 0.2);color: var(--warning-color);}.event-time {font-size: 0.8rem;color: #7f8c8d;}.config-section {margin-top: 20px;}.config-grid {display: grid;grid-template-columns: repeat(2, 1fr);gap: 15px;}@media (max-width: 768px) {.config-grid {grid-template-columns: 1fr;}}.config-item {margin-bottom: 15px;}label {display: block;margin-bottom: 5px;font-weight: 600;font-size: 0.9rem;}input, select, textarea {width: 100%;padding: 10px;border: 1px solid #ddd;border-radius: var(--border-radius);font-size: 0.9rem;}.checkbox-group {display: flex;gap: 15px;flex-wrap: wrap;}.checkbox-item {display: flex;align-items: center;gap: 5px;}.logs-container {margin-top: 20px;}.logs-header {display: flex;justify-content: space-between;align-items: center;margin-bottom: 10px;}.log-filters {display: flex;gap: 10px;align-items: center;}.logs {background: #1a1a1a;color: #e0e0e0;padding: 15px;border-radius: var(--border-radius);font-family: 'Consolas', 'Monaco', 'Courier New', monospace;font-size: 0.85rem;max-height: 400px;overflow-y: auto;line-height: 1.4;}.log-entry {margin-bottom: 10px;padding: 8px;border-radius: 4px;background: #252525;}.log-entry:hover {background: #2d2d2d;}.log-time {color: #6a9955;}.log-level-info {color: #4fc1ff;}.log-level-warn {color: #ffcc02;}.log-level-error {color: #f44747;}.log-level-debug {color: #b5cea8;}.log-details {margin-top: 5px;padding-left: 15px;border-left: 2px solid #3c3c3c;}.log-detail-item {margin-bottom: 3px;}.log-detail-key {color: #9cdcfe;}.log-detail-value {color: #ce9178;}.log-json {color: #ce9178;white-space: pre-wrap;margin-top: 5px;}footer {text-align: center;margin-top: 30px;padding: 20px;color: #7f8c8d;font-size: 0.9rem;}.tabs {display: flex;border-bottom: 1px solid #ddd;margin-bottom: 20px;}.tab {padding: 10px 20px;cursor: pointer;border-bottom: 3px solid transparent;}.tab.active {border-bottom-color: var(--primary-color);font-weight: 600;color: var(--primary-color);}.tab-content {display: none;}.tab-content.active {display: block;}.log-entry.expanded .log-details {display: block;}.log-entry:not(.expanded) .log-details {display: none;}.expand-toggle {cursor: pointer;color: #6a9955;font-size: 0.8rem;margin-left: 10px;}.log-actions {display: flex;gap: 5px;margin-top: 5px;}.log-action-btn {background: transparent;color: #6a9955;padding: 2px 6px;font-size: 0.7rem;border: 1px solid #6a9955;}.log-action-btn:hover {background: #6a9955;color: white;}.demo-area {margin-top: 20px;padding: 15px;background: #f8f9fa;border-radius: var(--border-radius);}.demo-buttons {display: flex;gap: 10px;flex-wrap: wrap;}.demo-element {margin-top: 15px;padding: 10px;border: 1px dashed #ccc;border-radius: var(--border-radius);}.user-info {background: #e8f4fd;padding: 10px;border-radius: var(--border-radius);margin-bottom: 15px;}.user-info h3 {margin-bottom: 8px;color: var(--primary-color);}.user-info-item {display: flex;margin-bottom: 5px;}.user-info-label {font-weight: 600;min-width: 120px;}</style>
</head>
<body><div id="app"><div class="container"><header><h1>Vue版前端埋点上报系统</h1><div class="status-bar"><div class="status-item"><div class="status-value">{{ stats.totalEvents }}</div><div class="status-label">总事件数</div></div><div class="status-item"><div class="status-value">{{ successRate }}%</div><div class="status-label">上报成功率</div></div><div class="status-item"><div class="status-value">{{ activeTime }}s</div><div class="status-label">页面活跃时间</div></div></div></header><div class="tabs"><div class="tab" :class="{ active: activeTab === 'dashboard' }" @click="activeTab = 'dashboard'">数据看板</div><div class="tab" :class="{ active: activeTab === 'configuration' }" @click="activeTab = 'configuration'">配置管理</div><div class="tab" :class="{ active: activeTab === 'logs' }" @click="activeTab = 'logs'">详细日志</div><div class="tab" :class="{ active: activeTab === 'demo' }" @click="activeTab = 'demo'">演示区域</div></div><!-- 数据看板 --><div class="tab-content" :class="{ active: activeTab === 'dashboard' }"><div class="dashboard"><div class="card"><div class="card-header"><div class="card-title">事件统计</div><div class="card-actions"><button class="btn-primary" @click="refreshStats">刷新</button></div></div><div class="stats-grid"><div class="stat-card"><div class="stat-value">{{ stats.clickEvents }}</div><div class="stat-label">点击事件</div></div><div class="stat-card"><div class="stat-value">{{ stats.pageviewEvents }}</div><div class="stat-label">页面浏览</div></div><div class="stat-card"><div class="stat-value">{{ stats.errorEvents }}</div><div class="stat-label">错误事件</div></div><div class="stat-card"><div class="stat-value">{{ stats.performanceEvents }}</div><div class="stat-label">性能指标</div></div></div></div><div class="card"><div class="card-header"><div class="card-title">实时事件流</div><div class="card-actions"><button class="btn-danger" @click="clearEvents">清空</button></div></div><div class="events-list"><div class="event-item" v-for="event in recentEvents" :key="event.id"><div><span class="event-type" :class="event.type">{{ event.type }}</span><span>{{ getEventDescription(event) }}</span></div><div class="event-time">{{ formatTime(event.timestamp) }}</div></div></div></div></div><div class="card"><div class="card-header"><div class="card-title">事件趋势</div><div class="card-actions"><select v-model="timeRange"><option value="1h">最近1小时</option><option value="6h">最近6小时</option><option value="24h">最近24小时</option><option value="7d">最近7天</option></select></div></div><div class="chart-container"><div style="display: flex; height: 100%; align-items: center; justify-content: center; color: #7f8c8d;">事件趋势图表区域 ({{ timeRange }})</div></div></div></div><!-- 配置管理 --><div class="tab-content" :class="{ active: activeTab === 'configuration' }"><div class="card"><div class="card-header"><div class="card-title">埋点配置</div></div><div class="config-section"><div class="config-grid"><div class="config-item"><label for="report-url">上报地址</label><input type="text" id="report-url" v-model="config.reportUrl"></div><div class="config-item"><label for="report-interval">上报间隔(ms)</label><input type="number" id="report-interval" v-model.number="config.reportInterval" min="1000"></div><div class="config-item"><label for="sample-rate">采样率(%)</label><input type="number" id="sample-rate" v-model.number="config.sampleRate" min="1" max="100"></div><div class="config-item"><label for="max-retry">最大重试次数</label><input type="number" id="max-retry" v-model.number="config.maxRetry" min="0" max="10"></div></div><div class="config-item"><label>启用的事件类型</label><div class="checkbox-group"><div class="checkbox-item" v-for="(enabled, type) in config.enabledEvents" :key="type"><input type="checkbox" :id="`track-${type}`" v-model="config.enabledEvents[type]"><label :for="`track-${type}`">{{ getEventTypeLabel(type) }}</label></div></div></div><div class="config-item"><label>日志级别</label><div class="checkbox-group"><div class="checkbox-item" v-for="(enabled, level) in config.logLevels" :key="level"><input type="checkbox" :id="`log-${level}`" v-model="config.logLevels[level]"><label :for="`log-${level}`">{{ level.toUpperCase() }}</label></div></div></div><div class="config-item"><label for="custom-events">自定义事件</label><textarea id="custom-events" rows="4" v-model="customEventsText" placeholder='格式: {"event1": "自定义事件1", "event2": "自定义事件2"}'></textarea></div><div style="margin-top: 20px; display: flex; gap: 10px;"><button class="btn-success" @click="saveConfig">保存配置</button><button class="btn-primary" @click="resetConfig">重置默认</button><button class="btn-warning" @click="testReport">测试上报</button><button class="btn-primary" @click="simulateError">模拟错误</button></div></div></div></div><!-- 详细日志 --><div class="tab-content" :class="{ active: activeTab === 'logs' }"><div class="card"><div class="card-header"><div class="card-title">详细日志</div><div class="card-actions"><button class="btn-danger" @click="clearLogs">清空日志</button><button class="btn-primary" @click="exportLogs">导出日志</button></div></div><div class="logs-container"><div class="logs-header"><div>系统操作日志</div><div class="log-filters"><label>筛选:</label><select v-model="logLevelFilter"><option value="all">所有级别</option><option value="debug">DEBUG</option><option value="info">INFO</option><option value="warn">WARN</option><option value="error">ERROR</option></select><input type="text" v-model="logSearchText" placeholder="搜索日志内容..."></div></div><div class="logs"><div class="log-entry" v-for="log in filteredLogs" :key="log.id" :class="{ expanded: expandedLogs.includes(log.id) }"><div><span class="log-time">[{{ formatTime(log.timestamp, true) }}]</span><span :class="`log-level-${log.level}`">[{{ log.level.toUpperCase() }}]</span><span>{{ log.message }}</span><span class="expand-toggle" v-if="log.details" @click="toggleLogDetails(log.id)">{{ expandedLogs.includes(log.id) ? '[收起]' : '[展开]' }}</span></div><div class="log-details" v-if="log.details"><div class="log-detail-item" v-for="(value, key) in log.details" :key="key"><span class="log-detail-key">{{ key }}:</span><span class="log-detail-value" v-if="typeof value !== 'object'">{{ value }}</span><div class="log-json" v-else>{{ JSON.stringify(value, null, 2) }}</div></div><div class="log-actions"><button class="log-action-btn" @click="copyLogDetails(log.id)">复制详情</button><button class="log-action-btn" @click="createIssue(log.id)">创建Issue</button></div></div></div></div></div></div></div><!-- 演示区域 --><div class="tab-content" :class="{ active: activeTab === 'demo' }"><div class="card"><div class="card-header"><div class="card-title">用户信息与设备指纹</div></div><div class="user-info"><h3>当前用户信息</h3><div class="user-info-item"><span class="user-info-label">用户代理:</span><span>{{ userInfo.userAgent }}</span></div><div class="user-info-item"><span class="user-info-label">设备指纹:</span><span>{{ userInfo.fingerprint }}</span></div><div class="user-info-item"><span class="user-info-label">会话ID:</span><span>{{ userInfo.sessionId }}</span></div><div class="user-info-item"><span class="user-info-label">屏幕分辨率:</span><span>{{ userInfo.screenWidth }} x {{ userInfo.screenHeight }}</span></div><div class="user-info-item"><span class="user-info-label">语言:</span><span>{{ userInfo.language }}</span></div><div class="user-info-item"><span class="user-info-label">时区:</span><span>{{ userInfo.timezone }}</span></div></div></div><div class="card demo-area"><div class="card-header"><div class="card-title">点击事件演示区域</div></div><p>点击下面的按钮和元素,查看详细的日志记录:</p><div class="demo-buttons"><button class="btn-primary" @click="trackDemoClick('primary-button')">主要按钮</button><button class="btn-success" @click="trackDemoClick('success-button')">成功按钮</button><button class="btn-warning" @click="trackDemoClick('warning-button')">警告按钮</button><button class="btn-danger" @click="trackDemoClick('danger-button')">危险按钮</button></div><div class="demo-element" @click="trackDemoClick('demo-div')">点击这个DIV区域 (ID: demo-div)</div><div class="demo-element" @click="trackDemoClick('demo-paragraph')"><p>点击这个段落 (ID: demo-paragraph)</p><span>包含多个子元素的区域</span></div><div class="demo-buttons"><button class="btn-primary" @click="generateCustomEvent">生成自定义事件</button><button class="btn-warning" @click="simulateNetworkError">模拟网络错误</button></div></div></div><footer><p>Vue版前端埋点上报系统 | 用于收集和分析用户行为数据</p></footer></div></div><script>const { createApp, ref, reactive, computed, onMounted, onUnmounted, watch } = Vue;createApp({setup() {// 响应式数据const activeTab = ref('dashboard');const timeRange = ref('24h');const logLevelFilter = ref('all');const logSearchText = ref('');const expandedLogs = ref([]);const customEventsText = ref('');const activeTime = ref(0);// 用户信息const userInfo = reactive({userAgent: navigator.userAgent,fingerprint: '',sessionId: '',screenWidth: screen.width,screenHeight: screen.height,language: navigator.language,timezone: Intl.DateTimeFormat().resolvedOptions().timeZone});// 配置const config = reactive({reportUrl: 'https://api.example.com/track',reportInterval: 5000,sampleRate: 100,maxRetry: 3,enabledEvents: {clicks: true,pageviews: true,errors: true,performance: true,userBehavior: false},logLevels: {debug: true,info: true,warn: true,error: true},customEvents: {}});// 事件和日志数据const events = ref([]);const logs = ref([]);// 统计信息const stats = reactive({totalEvents: 0,clickEvents: 0,pageviewEvents: 0,errorEvents: 0,performanceEvents: 0,successfulReports: 0,failedReports: 0});// 计算属性const successRate = computed(() => {const totalReports = stats.successfulReports + stats.failedReports;return totalReports > 0 ? Math.round((stats.successfulReports / totalReports) * 100) : 100;});const recentEvents = computed(() => {return events.value.slice(-10).reverse();});const filteredLogs = computed(() => {return logs.value.filter(log => {// 级别筛选if (logLevelFilter.value !== 'all' && log.level !== logLevelFilter.value) {return false;}// 搜索筛选if (logSearchText.value && !log.message.toLowerCase().includes(logSearchText.value.toLowerCase()) && !JSON.stringify(log.details || {}).toLowerCase().includes(logSearchText.value.toLowerCase())) {return false;}return true;}).slice(-100).reverse();});// 方法const generateId = () => {return Math.random().toString(36).substring(2) + Date.now().toString(36);};const getSessionId = () => {let sessionId = sessionStorage.getItem('trackingSessionId');if (!sessionId) {sessionId = generateId();sessionStorage.setItem('trackingSessionId', sessionId);}return sessionId;};const generateFingerprint = () => {const components = [navigator.userAgent,navigator.language,screen.width + 'x' + screen.height,new Date().getTimezoneOffset(),!!navigator.cookieEnabled,!!window.localStorage,!!window.sessionStorage];return components.join('|');};const formatTime = (timestamp, withMs = false) => {const date = new Date(timestamp);if (withMs) {return `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}:${date.getSeconds().toString().padStart(2, '0')}.${date.getMilliseconds().toString().padStart(3, '0')}`;}return date.toLocaleTimeString();};const getEventDescription = (event) => {switch(event.type) {case 'click':return `点击了 ${event.data.element} 元素 (ID: ${event.data.id || '无'})`;case 'pageview':return `浏览了 ${event.data.title}`;case 'error':return `错误: ${event.data.message}`;case 'performance':return '性能指标记录';case 'test':return '测试事件';case 'custom':return `自定义事件: ${event.data.name}`;default:return '未知事件';}};const getEventTypeLabel = (type) => {const labels = {clicks: '点击事件',pageviews: '页面浏览',errors: '错误事件',performance: '性能指标',userBehavior: '用户行为'};return labels[type] || type;};const log = (message, level = 'info', details = null) => {// 检查日志级别是否启用if (!config.logLevels[level]) return;const logEntry = {id: generateId(),timestamp: new Date(),level,message,details};logs.value.push(logEntry);};const trackEvent = (type, data) => {const event = {id: generateId(),type,timestamp: new Date().toISOString(),data,userAgent: userInfo.userAgent,url: window.location.href,viewport: {width: window.innerWidth,height: window.innerHeight},sessionId: userInfo.sessionId,fingerprint: userInfo.fingerprint};events.value.push(event);updateStats(type);// 记录详细日志if (type === 'click') {log(`用户点击了元素: ${data.element} (ID: ${data.id || '无'})`, 'info', {element: data.element,elementId: data.id,className: data.className,textContent: data.text,coordinates: { x: data.x, y: data.y },pageCoordinates: { pageX: data.pageX, pageY: data.pageY },userInfo: {sessionId: userInfo.sessionId,fingerprint: userInfo.fingerprint,userAgent: userInfo.userAgent},timestamp: event.timestamp});}};const updateStats = (type) => {stats.totalEvents++;switch(type) {case 'click':stats.clickEvents++;break;case 'pageview':stats.pageviewEvents++;break;case 'error':stats.errorEvents++;break;case 'performance':stats.performanceEvents++;break;}};const trackDemoClick = (elementId) => {if (!config.enabledEvents.clicks) return;const eventData = {element: 'BUTTON',id: elementId,className: 'demo-button',text: `点击了${elementId}`,x: 0, // 在实际应用中应从事件对象获取y: 0,pageX: 0,pageY: 0};trackEvent('click', eventData);};const generateCustomEvent = () => {const eventData = {name: 'custom_demo_event',value: Math.random(),timestamp: new Date().toISOString()};trackEvent('custom', eventData);log('自定义事件已生成', 'debug', eventData);};const simulateNetworkError = () => {try {// 模拟网络错误throw new Error('模拟网络请求失败: 404 Not Found');} catch (error) {trackEvent('error', {message: error.message,type: 'NetworkError',url: config.reportUrl});log('网络错误模拟', 'error', {message: error.message,stack: error.stack,type: 'NetworkError',url: config.reportUrl});}};const testReport = () => {trackEvent('test', { message: '测试事件', testId: Date.now() });log('测试事件已发送', 'debug', { testId: Date.now() });};const simulateError = () => {try {// 故意创建一个错误undefinedFunction();} catch (error) {log('模拟错误已触发', 'error', {message: error.message,stack: error.stack,type: 'SimulatedError',userInfo: {sessionId: userInfo.sessionId,fingerprint: userInfo.fingerprint}});}};const refreshStats = () => {// 统计信息已经是响应式的,无需额外操作log('统计数据已刷新', 'debug');};const clearEvents = () => {events.value = [];log('事件列表已清空', 'info');};const clearLogs = () => {logs.value = [];};const exportLogs = () => {const logData = logs.value.map(log => ({timestamp: log.timestamp.toISOString(),level: log.level,message: log.message,details: log.details}));const blob = new Blob([JSON.stringify(logData, null, 2)], { type: 'application/json' });const url = URL.createObjectURL(blob);const a = document.createElement('a');a.href = url;a.download = `tracking-logs-${new Date().toISOString().slice(0, 10)}.json`;a.click();URL.revokeObjectURL(url);log('日志已导出为JSON文件', 'info');};const toggleLogDetails = (logId) => {const index = expandedLogs.value.indexOf(logId);if (index > -1) {expandedLogs.value.splice(index, 1);} else {expandedLogs.value.push(logId);}};const copyLogDetails = (logId) => {const log = logs.value.find(l => l.id === logId);if (log) {const text = `时间: ${log.timestamp.toISOString()}\n级别: ${log.level}\n消息: ${log.message}\n详情: ${JSON.stringify(log.details, null, 2)}`;navigator.clipboard.writeText(text).then(() => {log('日志详情已复制到剪贴板', 'info');});}};const createIssue = (logId) => {const log = logs.value.find(l => l.id === logId);if (log) {alert(`将为日志 ${logId} 创建Issue\n\n时间: ${log.timestamp.toISOString()}\n级别: ${log.level}\n消息: ${log.message}`);log('创建Issue功能已触发', 'info', { logId, log });}};const saveConfig = () => {try {if (customEventsText.value.trim()) {config.customEvents = JSON.parse(customEventsText.value);}} catch (e) {log('自定义事件配置格式错误', 'error', { error: e.message });}localStorage.setItem('trackingConfig', JSON.stringify(config));log('配置已保存', 'info', { config: { ...config } });};const resetConfig = () => {Object.assign(config, {reportUrl: 'https://api.example.com/track',reportInterval: 5000,sampleRate: 100,maxRetry: 3,enabledEvents: {clicks: true,pageviews: true,errors: true,performance: true,userBehavior: false},logLevels: {debug: true,info: true,warn: true,error: true},customEvents: {}});customEventsText.value = '';log('配置已重置为默认值', 'info');};const loadConfig = () => {const savedConfig = localStorage.getItem('trackingConfig');if (savedConfig) {Object.assign(config, JSON.parse(savedConfig));}};const startActiveTimeCounter = () => {setInterval(() => {activeTime.value++;}, 1000);};const trackPageView = () => {if (!config.enabledEvents.pageviews) return;const perfData = performance.timing ? {navigationStart: performance.timing.navigationStart,loadEventEnd: performance.timing.loadEventEnd,domComplete: performance.timing.domComplete,domInteractive: performance.timing.domInteractive} : {};trackEvent('pageview', {referrer: document.referrer,title: document.title,...perfData});log('页面浏览事件已记录', 'info', {title: document.title,url: window.location.href,referrer: document.referrer,performance: perfData,userInfo: {sessionId: userInfo.sessionId,fingerprint: userInfo.fingerprint}});};// 初始化onMounted(() => {// 设置用户信息userInfo.sessionId = getSessionId();userInfo.fingerprint = generateFingerprint();// 加载配置loadConfig();// 开始活跃时间计数器startActiveTimeCounter();// 记录初始页面浏览trackPageView();// 添加全局错误监听window.addEventListener('error', (e) => {if (config.enabledEvents.errors) {trackEvent('error', {message: e.message,filename: e.filename,lineno: e.lineno,colno: e.colno,error: e.error?.toString()});log('JavaScript错误已捕获', 'error', {message: e.message,filename: e.filename,lineno: e.lineno,colno: e.colno,stack: e.error?.stack,userInfo: {sessionId: userInfo.sessionId,fingerprint: userInfo.fingerprint}});}});// 添加全局点击监听(捕获更详细的点击信息)document.addEventListener('click', (e) => {if (config.enabledEvents.clicks) {const target = e.target;const eventData = {element: target.tagName,id: target.id || '',className: target.className || '',text: target.textContent?.substring(0, 100) || '',x: e.clientX,y: e.clientY,pageX: e.pageX,pageY: e.pageY,target: {tagName: target.tagName,id: target.id,className: target.className,nodeName: target.nodeName}};trackEvent('click', eventData);}});log('Vue版埋点系统已初始化完成', 'info', {userInfo: { ...userInfo },config: { ...config },timestamp: new Date().toISOString()});});return {activeTab,timeRange,logLevelFilter,logSearchText,expandedLogs,customEventsText,activeTime,userInfo,config,events,logs,stats,successRate,recentEvents,filteredLogs,formatTime,getEventDescription,getEventTypeLabel,trackDemoClick,generateCustomEvent,simulateNetworkError,testReport,simulateError,refreshStats,clearEvents,clearLogs,exportLogs,toggleLogDetails,copyLogDetails,createIssue,saveConfig,resetConfig};}}).mount('#app');</script>
</body>
</html>
http://www.dtcms.com/a/464804.html

相关文章:

  • 框架--Maven
  • 【Java集合】
  • 停止Conda开机自动运行方法
  • 湘潭市高新建设局施工报建网站wordpress 宕机
  • 复杂结构数据挖掘(二)关联规则挖掘 Association rule mining
  • Windows 上安装 PostgreSQL
  • 基于JETSON/x86+FPGA+AI的5G远程驾驶座舱时延验证方案
  • 支持向量机(SVM)完全解读
  • 单片机学习日记
  • 重庆网站制作多少钱app设计开发哪家好
  • AI大模型学习(17)python-flask AI大模型和图片处理工具的从一张图到多平台适配的简单方法
  • 如何通过 7 种解决方案将文件从PC无线传输到Android
  • Word 为每一页设置不同页边距(VBA 宏)
  • wordpiece、unigram、sentencepiece基本原理
  • css word-spacing属性
  • 使用 python-docx 库操作 word 文档(2):在word文档中插入各种内容
  • 中企动力销售工作内容白城网站seo
  • 从0死磕全栈之Next.js 企业级 `next.config.js` 配置详解:打造高性能、安全、可维护的中大型项目
  • 在JavaScript中,const和var的区别
  • 【SDR课堂第36讲】RFSOC PS软件开发入门指南(一)
  • 学做网站中国设计网站导航
  • [嵌入式系统-84]:NPU/TPU/LPU有指令集吗?
  • 光伏安全协议-安全责任协议书8篇
  • Java 单元测试全攻略:JUnit 生命周期、覆盖率提升、自动化框架与 Mock 技术
  • SaaS多租户数据隔离实战:MyBatis拦截器实现行级安全方案
  • 【深入理解计算机网络08】网络层之IPv4
  • 网站的标签wordpress 导航栏居中
  • 解决电脑提示“0xc000007b错误”的简单指南
  • 【STM32项目开源】基于STM32的智能家居安防系统
  • 网络营销方式思维导图aso优化榜单