/** * Google Analytics 4 工具函数 * 提供标准化的事件跟踪和页面访问监控 */ // 扩展全局Window接口 declare global { interface Window { gtag: (...args: any[]) => void; dataLayer: any[]; } } /** * GA4事件参数类型定义 */ export interface GAEventParameters { event_category?: string; event_label?: string; value?: number; custom_parameters?: Record; } /** * 页面访问参数类型定义 */ export interface GAPageViewParameters { page_title?: string; page_location?: string; custom_parameters?: Record; } /** * 规范化/序列化事件参数,避免 [object Object] */ const normalizeEventParams = ( params: Record ): Record => { const result: Record = {}; const assignPrimitive = (key: string, value: any) => { if (value === undefined) return; if ( typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean' ) { result[key] = value; } else if (value === null) { result[key] = 'null'; } else { try { result[key] = JSON.stringify(value); } catch (_) { result[key] = String(value); } } }; for (const [key, value] of Object.entries(params || {})) { if (key === 'custom_parameters' && value && typeof value === 'object') { for (const [ck, cv] of Object.entries(value)) { assignPrimitive(ck, cv); } continue; } assignPrimitive(key, value); } return result; }; /** * 检查GA是否可用 */ export const isGAAvailable = (): boolean => { return typeof window !== 'undefined' && typeof window.gtag === 'function' && process.env.NEXT_PUBLIC_GA_ENABLED === 'true'; }; /** * 获取GA测量ID */ export const getGAMeasurementId = (): string => { return process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID || 'G-4BDXV6TWF4'; }; /** * 跟踪自定义事件 * @param eventName - 事件名称 * @param parameters - 事件参数 */ export const trackEvent = ( eventName: string, parameters?: GAEventParameters ): void => { if (!isGAAvailable()) { console.warn('Google Analytics not available'); return; } try { const eventParamsRaw = { event_category: parameters?.event_category || 'general', event_label: parameters?.event_label, value: parameters?.value, ...parameters?.custom_parameters, }; const eventParams = normalizeEventParams(eventParamsRaw); window.gtag('event', eventName, eventParams); // 开发环境下打印日志 if (process.env.NODE_ENV === 'development') { console.log('GA Event:', eventName, eventParams); } } catch (error) { console.error('Error tracking GA event:', error); } }; /** * 跟踪页面访问 * @param pagePath - 页面路径 * @param pageTitle - 页面标题 * @param parameters - 额外参数 */ export const trackPageView = ( pagePath: string, pageTitle?: string, parameters?: GAPageViewParameters ): void => { if (!isGAAvailable()) { console.warn('Google Analytics not available'); return; } try { const pageParamsRaw = { page_path: pagePath, page_title: pageTitle, page_location: parameters?.page_location || window.location.href, ...parameters?.custom_parameters, }; const pageParams = normalizeEventParams(pageParamsRaw); window.gtag('config', getGAMeasurementId(), pageParams); // 开发环境下打印日志 if (process.env.NODE_ENV === 'development') { console.log('GA Page View:', pagePath, pageParams); } } catch (error) { console.error('Error tracking GA page view:', error); } }; /** * 跟踪用户注册事件 * @param method - 注册方式 (email, google, etc.) */ export const trackUserRegistration = (method: string): void => { trackEvent('user_registration', { event_category: 'user', event_label: method, custom_parameters: { registration_method: method, }, }); }; /** * 跟踪用户登录事件 * @param method - 登录方式 (email, google, etc.) */ export const trackUserLogin = (method: string): void => { trackEvent('user_login', { event_category: 'user', event_label: method, custom_parameters: { login_method: method, }, }); }; /** * 跟踪视频创建开始事件 * @param templateType - 模板类型 * @param aspectRatio - 视频比例 */ export const trackVideoCreationStart = ( templateType: string, aspectRatio?: string ): void => { trackEvent('video_creation_start', { event_category: 'video', event_label: templateType, custom_parameters: { template_type: templateType, aspect_ratio: aspectRatio, }, }); }; /** * 跟踪视频生成完成事件 * @param duration - 视频时长 * @param templateType - 模板类型 */ export const trackVideoGenerationComplete = ( duration: number, templateType: string ): void => { trackEvent('video_generation_complete', { event_category: 'video', value: duration, custom_parameters: { template_type: templateType, video_duration: duration, }, }); }; /** * 跟踪支付事件 * @param paymentType - 支付类型 (subscription, token) * @param amount - 支付金额 * @param currency - 货币类型 */ export const trackPayment = ( paymentType: string, amount: number, currency: string = 'USD' ): void => { trackEvent('purchase', { event_category: 'ecommerce', value: amount, custom_parameters: { payment_type: paymentType, currency: currency, }, }); }; /** * 跟踪模板选择事件 * @param templateId - 模板ID * @param templateName - 模板名称 */ export const trackTemplateSelection = ( templateId: string, templateName: string ): void => { trackEvent('template_selection', { event_category: 'template', event_label: templateName, custom_parameters: { template_id: templateId, template_name: templateName, }, }); }; /** * 跟踪功能使用事件 * @param featureName - 功能名称 * @param action - 操作类型 */ export const trackFeatureUsage = ( featureName: string, action: string ): void => { trackEvent('feature_usage', { event_category: 'feature', event_label: featureName, custom_parameters: { feature_name: featureName, action: action, }, }); }; /** * 跟踪错误事件 * @param errorType - 错误类型 * @param errorMessage - 错误信息 * @param errorLocation - 错误位置 */ export const trackError = ( errorType: string, errorMessage: string, errorLocation?: string ): void => { trackEvent('error', { event_category: 'error', event_label: errorType, custom_parameters: { error_type: errorType, error_message: errorMessage, error_location: errorLocation, }, }); }; /** * 跟踪用户属性 * @param userId - 用户ID * @param userProperties - 用户属性 */ export const setUserProperties = ( userId: string, userProperties: Record ): void => { if (!isGAAvailable()) { console.warn('Google Analytics not available'); return; } try { // GA4 推荐:通过 config 设置 user_id,通过 set user_properties 设置用户属性 window.gtag('config', getGAMeasurementId(), { user_id: userId, }); window.gtag('set', 'user_properties', normalizeEventParams(userProperties)); } catch (error) { console.error('Error setting user properties:', error); } };