// Analytics SDK for Frontend // 前端数据埋点 SDK interface EventProperties { [key: string]: any; } interface DeviceInfo { user_agent?: string; screen_width?: number; screen_height?: number; language?: string; timezone?: string; platform?: string; } interface MetaData { task_id?: string | number; [key: string]: any; } interface TrackEventParams { event_name: string; properties?: EventProperties; device_info?: DeviceInfo; meta_data?: MetaData; } interface AnalyticsConfig { apiUrl: string; token?: string; userId?: number | string; enableAutoTrack?: boolean; debug?: boolean; } class Analytics { private config: AnalyticsConfig; private userId: number | string | null = null; private eventQueue: TrackEventParams[] = []; private isSending: boolean = false; constructor(config: AnalyticsConfig) { this.config = { enableAutoTrack: true, debug: false, ...config, }; if (this.config.userId) { this.userId = this.config.userId; } // 自动收集设备信息 if (this.config.enableAutoTrack) { this.initAutoTrack(); } } /** * 设置用户ID */ setUserId(userId: number | string) { this.userId = userId; } /** * 获取设备信息 */ private getDeviceInfo(): DeviceInfo { return { user_agent: navigator.userAgent, screen_width: window.screen.width, screen_height: window.screen.height, language: navigator.language, timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, platform: navigator.platform, }; } /** * 记录单个事件 */ async track(params: TrackEventParams): Promise { if (!this.userId) { console.warn('Analytics: userId not set, event will not be tracked'); return; } const eventData = { user_id: this.userId, event_name: params.event_name, properties: params.properties || {}, device_info: { ...this.getDeviceInfo(), ...params.device_info, }, meta_data: { timestamp: Date.now(), ...params.meta_data, }, }; if (this.config.debug) { console.log('Analytics Track:', eventData); } try { const response = await fetch(`${this.config.apiUrl}/track`, { method: 'POST', headers: { 'Content-Type': 'application/json', ...(this.config.token && { Authorization: `Bearer ${this.config.token}` }), }, body: JSON.stringify(eventData), }); if (!response.ok) { throw new Error(`Failed to track event: ${response.statusText}`); } if (this.config.debug) { console.log('Analytics: Event tracked successfully'); } } catch (error) { console.error('Analytics: Failed to track event', error); // 失败时加入队列,稍后重试 this.eventQueue.push(params); } } /** * 批量记录事件 */ async trackBatch(events: TrackEventParams[]): Promise { if (!this.userId) { console.warn('Analytics: userId not set, events will not be tracked'); return; } const batchData = { events: events.map((params) => ({ user_id: this.userId, event_name: params.event_name, properties: params.properties || {}, device_info: { ...this.getDeviceInfo(), ...params.device_info, }, meta_data: { timestamp: Date.now(), ...params.meta_data, }, })), }; if (this.config.debug) { console.log('Analytics Track Batch:', batchData); } try { const response = await fetch(`${this.config.apiUrl}/track/batch`, { method: 'POST', headers: { 'Content-Type': 'application/json', ...(this.config.token && { Authorization: `Bearer ${this.config.token}` }), }, body: JSON.stringify(batchData), }); if (!response.ok) { throw new Error(`Failed to track batch events: ${response.statusText}`); } if (this.config.debug) { console.log('Analytics: Batch events tracked successfully'); } } catch (error) { console.error('Analytics: Failed to track batch events', error); } } /** * 页面浏览事件 */ trackPageView(pageName?: string) { this.track({ event_name: 'page_view', properties: { page_url: window.location.href, page_title: document.title, page_name: pageName || document.title, referrer: document.referrer, }, }); } /** * 点击事件 */ trackClick(elementName: string, properties?: EventProperties) { this.track({ event_name: 'click', properties: { element_name: elementName, page_url: window.location.href, ...properties, }, }); } /** * 表单提交事件 */ trackFormSubmit(formName: string, properties?: EventProperties) { this.track({ event_name: 'form_submit', properties: { form_name: formName, page_url: window.location.href, ...properties, }, }); } /** * 任务相关事件 */ trackTask(taskId: string | number, action: string, properties?: EventProperties) { this.track({ event_name: `task_${action}`, properties: { action, ...properties, }, meta_data: { task_id: taskId, }, }); } /** * 初始化自动埋点 */ private initAutoTrack() { // 页面加载完成时记录 if (document.readyState === 'complete') { this.trackPageView(); } else { window.addEventListener('load', () => this.trackPageView()); } // 页面离开前发送队列中的事件 window.addEventListener('beforeunload', () => { if (this.eventQueue.length > 0) { this.flushQueue(); } }); // 页面可见性变化 document.addEventListener('visibilitychange', () => { if (document.hidden && this.eventQueue.length > 0) { this.flushQueue(); } }); } /** * 刷新队列中的事件 */ private flushQueue() { if (this.isSending || this.eventQueue.length === 0) { return; } this.isSending = true; const eventsToSend = [...this.eventQueue]; this.eventQueue = []; this.trackBatch(eventsToSend).finally(() => { this.isSending = false; }); } /** * 手动刷新队列 */ flush() { this.flushQueue(); } } // 导出单例实例 let analyticsInstance: Analytics | null = null; export function initAnalytics(config: AnalyticsConfig): Analytics { analyticsInstance = new Analytics(config); return analyticsInstance; } export function getAnalytics(): Analytics { if (!analyticsInstance) { throw new Error('Analytics not initialized. Call initAnalytics first.'); } return analyticsInstance; } export default Analytics;