forked from 77media/video-flow
335 lines
8.1 KiB
TypeScript
335 lines
8.1 KiB
TypeScript
/**
|
||
* Google Analytics 4 工具函数
|
||
* 提供标准化的事件跟踪和页面访问监控
|
||
*/
|
||
|
||
import { isGAAvailable as checkGAAvailable, gaMeasurementId } from '@/lib/env';
|
||
|
||
// 扩展全局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<string, any>;
|
||
}
|
||
|
||
/**
|
||
* 页面访问参数类型定义
|
||
*/
|
||
export interface GAPageViewParameters {
|
||
page_title?: string;
|
||
page_location?: string;
|
||
custom_parameters?: Record<string, any>;
|
||
}
|
||
|
||
/**
|
||
* 规范化/序列化事件参数,避免 [object Object]
|
||
*/
|
||
const normalizeEventParams = (
|
||
params: Record<string, any>
|
||
): Record<string, string | number | boolean> => {
|
||
const result: Record<string, string | number | boolean> = {};
|
||
|
||
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 checkGAAvailable();
|
||
};
|
||
|
||
/**
|
||
* 获取GA测量ID
|
||
*/
|
||
export const getGAMeasurementId = (): string => {
|
||
return gaMeasurementId;
|
||
};
|
||
|
||
/**
|
||
* 跟踪自定义事件
|
||
* @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 (typeof window !== 'undefined' && window.location.hostname === 'localhost') {
|
||
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 (typeof window !== 'undefined' && window.location.hostname === 'localhost') {
|
||
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<string, any>
|
||
): 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);
|
||
}
|
||
};
|
||
|
||
|
||
|