Vue3项目埋点方案完整实现,拿来就能用

摘要:做网站或者APP时,我们经常需要知道用户怎么用我们的产品。比如用户点了哪些按钮?在页面停留多久?有没有遇到错误?这些信息很重要,能帮我们改进产品。今天我就手把手教你给Vue3项目做一个完整的埋点方案。

做网站或者APP时,我们经常需要知道用户怎么用我们的产品。比如用户点了哪些按钮?在页面停留多久?有没有遇到错误?这些信息很重要,能帮我们改进产品。今天我就手把手教你给Vue3项目做一个完整的埋点方案。

这个方案要实现三个主要功能:

  1. 记录用户点击了什么

  2. 计算用户在页面的停留时间

  3. 收集程序错误信息


为什么选择Vue3?

  • Vue3性能更好

  • Composition API用起来更灵活

  • TypeScript支持更完善


需要安装什么?

npm install vue-router@4

我们不用第三方埋点库,自己用JavaScript实现,这样可以减少代码体积。


第一步:定义数据类型

先确定要收集哪些数据。新建一个文件 types/tracking.ts:

// 点击事件
export interface ClickEvent {
  type: 'click'
  element: string      // 点击的元素标识
  page: string         // 页面路径
  timestamp: number    // 时间戳
}

// 页面浏览事件
export interface PageViewEvent {
  type: 'pageview'
  page: string         // 页面名称
  duration: number     // 停留时间(毫秒)
  timestamp: number
}

// 错误事件  
export interface ErrorEvent {
  type: 'error'
  message: string      // 错误信息
  stack?: string       // 错误堆栈
  page: string         // 发生错误的页面
  timestamp: number
}

// 所有事件类型
export type TrackingEvent = ClickEvent | PageViewEvent | ErrorEvent


第二步:实现核心追踪类

这个类负责收集和发送数据。新建 utils/tracker.ts:

import type { TrackingEvent } from '@/types/tracking'

class Tracker {
  private queue: TrackingEvent[] = []  // 事件队列
  private maxRetry = 3                 // 最大重试次数
  private batchSize = 10               // 批量发送大小

  // 发送数据到服务器
  private async sendToServer(events: TrackingEvent[]): Promise<void> {
    try {
      const response = await fetch('/api/track', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ events }),
      })
      
      if (!response.ok) {
        throw new Error(`请求失败,状态码: ${response.status}`)
      }
    } catch (error) {
      console.warn('埋点发送失败:', error)
      throw error
    }
  }

  // 添加事件
  track(event: TrackingEvent): void {
    this.queue.push(event)
    
    // 达到批量大小就发送
    if (this.queue.length >= this.batchSize) {
      this.flush()
    }
  }

  // 强制发送所有事件
  async flush(): Promise<void> {
    if (this.queue.length === 0) return

    const events = [...this.queue]
    this.queue = []

    for (let attempt = 1; attempt <= this.maxRetry; attempt++) {
      try {
        await this.sendToServer(events)
        break  // 成功就退出
      } catch (error) {
        if (attempt === this.maxRetry) {
          console.error('埋点发送最终失败:', error)
          // 可以保存到localStorage,下次重试
          this.saveToLocalStorage(events)
        }
      }
    }
  }

  // 保存到本地存储(备用)
  private saveToLocalStorage(events: TrackingEvent[]): void {
    const stored = localStorage.getItem('pending_events') || '[]'
    const pending = JSON.parse(stored)
    const updated = [...pending, ...events]
    localStorage.setItem('pending_events', JSON.stringify(updated.slice(-100))) // 最多存100条
  }
}

// 创建单例
export const tracker = new Tracker()


第三步:点击事件追踪指令

新建 directives/trackClick.ts:

import { tracker } from '@/utils/tracker'

export const trackClick = {
  mounted(el: HTMLElement, binding: any) {
    const trackData = binding.value
    
    const handleClick = () => {
      const event = {
        type: 'click' as const,
        element: trackData.element || el.tagName,
        page: window.location.pathname,
        timestamp: Date.now()
      }
      
      tracker.track(event)
    }
    
    el.addEventListener('click', handleClick)
    
    // 保存引用,方便卸载
    el._trackClickHandler = handleClick
  },
  
  unmounted(el: HTMLElement) {
    if (el._trackClickHandler) {
      el.removeEventListener('click', el._trackClickHandler)
      delete el._trackClickHandler
    }
  }
}


第四步:页面停留时间统计

新建 composables/usePageTrack.ts:

import { onMounted, onUnmounted } from 'vue'
import { tracker } from '@/utils/tracker'

export function usePageTrack(pageName: string) {
  let startTime = 0
  
  onMounted(() => {
    startTime = Date.now()
  })
  
  onUnmounted(() => {
    const endTime = Date.now()
    const duration = endTime - startTime
    
    // 停留时间太短可能是误操作,过滤掉
    if (duration < 1000) return
    
    const event = {
      type: 'pageview' as const,
      page: pageName,
      duration,
      timestamp: endTime
    }
    
    tracker.track(event)
  })
}


第五步:全局错误捕获

新建 utils/errorHandler.ts:

import { tracker } from './tracker'
import type { App } from 'vue'

export function setupErrorTracking(app: App): void {
  // Vue错误
  app.config.errorHandler = (err, vm, info) => {
    const errorEvent = {
      type: 'error' as const,
      message: err instanceof Error ? err.message : String(err),
      stack: err instanceof Error ? err.stack : undefined,
      page: window.location.pathname,
      timestamp: Date.now(),
      component: info
    }
    
    tracker.track(errorEvent)
    
    // 开发环境也打印到控制台
    if (process.env.NODE_ENV !== 'production') {
      console.error('Vue错误:', err, info)
    }
  }
  
  // JavaScript错误
  window.addEventListener('error', (event) => {
    const errorEvent = {
      type: 'error' as const,
      message: event.message,
      stack: event.error?.stack,
      page: window.location.pathname,
      timestamp: Date.now()
    }
    
    tracker.track(errorEvent)
  })
  
  // Promise错误
  window.addEventListener('unhandledrejection', (event) => {
    const errorEvent = {
      type: 'error' as const,
      message: event.reason?.message || '未处理的Promise错误',
      stack: event.reason?.stack,
      page: window.location.pathname,
      timestamp: Date.now()
    }
    
    tracker.track(errorEvent)
  })
}


第六步:在Vue3中集成

修改 main.ts:

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import { trackClick } from './directives/trackClick'
import { setupErrorTracking } from './utils/errorHandler'

const app = createApp(App)

// 注册全局指令
app.directive('track-click', trackClick)

// 设置错误追踪
setupErrorTracking(app)

// 定时发送埋点数据(30秒一次)
setInterval(() => {
  import('@/utils/tracker').then(({ tracker }) => {
    tracker.flush()
  })
}, 30000)

app.use(router)
app.mount('#app')


第七步:路由配置

修改 router/index.ts:

import { createRouter, createWebHistory } from 'vue-router'

const routes = [
  {
    path: '/',
    name: 'Home',
    component: () => import('@/views/Home.vue'),
    meta: { 
      pageName: '首页'
    }
  },
  {
    path: '/about',
    name: 'About', 
    component: () => import('@/views/About.vue'),
    meta: {
      pageName: '关于页面'
    }
  }
]

const router = createRouter({
  history: createWebHistory(),
  routes
})

// 路由切换时记录
let pageStartTime = 0

router.beforeEach((to, from) => {
  pageStartTime = Date.now()
})

router.afterEach((to) => {
  const duration = Date.now() - pageStartTime
  
  // 只记录有页面名称的路由
  if (to.meta.pageName && duration > 1000) {
    import('@/utils/tracker').then(({ tracker }) => {
      tracker.track({
        type: 'pageview',
        page: to.meta.pageName as string,
        duration,
        timestamp: Date.now()
      })
    })
  }
})

export default router


第八步:在组件中使用

<!-- ProductList.vue -->
<template>
  <div>
    <h1>产品列表</h1>
    
    <!-- 按钮点击追踪 -->
    <button 
      v-track-click="{ element: 'filter-button' }"
      @click="handleFilter"
    >
      筛选
    </button>
    
    <!-- 列表项点击追踪 -->
    <div 
      v-for="product in products" 
      :key="product.id"
      class="product-item"
      v-track-click="{ element: `product-${product.id}` }"
      @click="viewProduct(product)"
    >
      {{ product.name }}
    </div>
  </div>
</template>

<script setup lang="ts">
import { usePageTrack } from '@/composables/usePageTrack'

// 自动记录页面停留时间
usePageTrack('产品列表页')

const products = [
  { id: 1, name: '产品A' },
  { id: 2, name: '产品B' }
]

const viewProduct = (product: any) => {
  // 处理产品查看
  console.log('查看产品:', product.name)
}
</script>


额外功能:性能监控

如果你还想监控页面性能,可以添加这个:

// utils/performanceTracker.ts
import { tracker } from './tracker'

export function trackPerformance(): void {
  // 页面加载完成时
  window.addEventListener('load', () => {
    const timing = performance.timing
    
    const loadTime = timing.loadEventEnd - timing.navigationStart
    
    tracker.track({
      type: 'performance',
      metric: 'page_load',
      value: loadTime,
      timestamp: Date.now(),
      page: window.location.pathname
    })
  })
}


优化建议

  1. 减少请求次数:批量发送数据,不要每个事件都单独请求

  2. 失败重试:网络不好时自动重试

  3. 数据采样:用户量很大时,可以只收集部分用户的数据

  4. 隐私保护:不要收集姓名、电话等个人信息

  5. 开关配置:提供开关让用户可以关闭数据收集


环境配置

创建配置文件 config/tracking.ts:

export const trackingConfig = {
  // 生产环境才发送真实数据
  enabled: import.meta.env.PROD,
  
  // 采样率:10%的用户
  samplingRate: 0.1,
  
  // 批量大小
  batchSize: 20,
  
  // 自动发送间隔(毫秒)
  flushInterval: 30000,
  
  // 是否开启调试
  debug: import.meta.env.DEV
}


总结

这个埋点方案包含了基本的数据收集功能,可以直接用到你的Vue3项目中。你可以根据自己的需求修改,比如添加更多的追踪类型,或者调整发送策略。

关键点记住:

  • 点击追踪用自定义指令

  • 页面停留时间用生命周期钩子

  • 错误捕获要全局设置

  • 数据批量发送,减少请求

做好埋点后,你就能清楚地知道用户怎么用你的产品,为后续优化提供数据支持。

本文内容仅供个人学习、研究或参考使用,不构成任何形式的决策建议、专业指导或法律依据。未经授权,禁止任何单位或个人以商业售卖、虚假宣传、侵权传播等非学习研究目的使用本文内容。如需分享或转载,请保留原文来源信息,不得篡改、删减内容或侵犯相关权益。感谢您的理解与支持!

链接: https://shenqiku.cn/article/FLY_13255