import type { MouseEvent as ReactMouseEvent } from 'react'; import { v4 as uuidv4 } from 'uuid'; import { reportUserEvent, type UserEventPayload } from '@/utils/api'; import { getUserId } from '@/utils/storage'; // 路由 path → page_id 映射,需与 .cursorrules 中的命名约定保持一致 const PATH_TO_PAGE_ID: Record = { '/welcome': 'welcome', '/begin': 'begin', '/login-register': 'login_register', '/loading': 'loading', '/naming': 'naming', '/lumi-naming': 'lumi_naming', '/chatbot': 'chatbot', '/tutorial-1': 'tutorial_1', '/tutorial-2': 'tutorial_2', '/tutorial-3': 'tutorial_3', '/tutorial-4': 'tutorial_4', '/tutorial-5': 'tutorial_5', '/tutorial-6': 'tutorial_6', '/tutorial-7': 'tutorial_7', '/tutorial-8': 'tutorial_8', '/tutorial-9': 'tutorial_9', '/tutorial-10': 'tutorial_10', '/level5-welcome': 'level5_welcome', '/level34-welcome': 'level34_welcome', '/first-login': 'first_login', '/verification-code': 'verification_code', '/verification-input': 'verification_input', '/login-verification-code': 'login_verification_code', '/login-verification-input': 'login_verification_input', '/first-login-reward': 'first_login_reward', '/share-success': 'share_success', '/email-login': 'email_login', '/phone-login': 'phone_login', '/profile': 'profile', '/error': 'error', }; const getPageIdFromPath = (path: string): string | null => { if (!path) return null; const clean = path.split('?')[0]; return PATH_TO_PAGE_ID[clean] ?? null; }; const getPlatform = (): UserEventPayload['platform'] => 'web'; const getTimezoneOffsetMinutes = (): number => -new Date().getTimezoneOffset(); const getAppVersion = (): string | undefined => import.meta.env.VITE_APP_VERSION; const SESSION_ID_KEY = 'lyrria_session_id'; function getOrCreateSessionId(): string { try { let sid = sessionStorage.getItem(SESSION_ID_KEY); if (!sid) { sid = uuidv4(); sessionStorage.setItem(SESSION_ID_KEY, sid); } return sid; } catch { return uuidv4(); } } export const trackPageEnter = (path: string): void => { const userId = getUserId(); const pageId = getPageIdFromPath(path); if (!userId || !pageId) { if (import.meta.env.DEV) { console.log('[tracking] trackPageEnter 跳过:', { path, hasUserId: !!userId, pageId }); } return; } const event: UserEventPayload = { event_id: uuidv4(), user_id: userId, event_name: `page.${pageId}__enter`, event_label: `进入页面:${pageId}`, event_time: new Date().toISOString(), session_id: getOrCreateSessionId(), platform: getPlatform(), app_version: getAppVersion(), timezone_offset_minutes: getTimezoneOffsetMinutes(), properties: { page: pageId, path, }, }; reportUserEvent(event); }; export const trackButtonClick = ( pageId: string, buttonId: string, options?: { label?: string; properties?: Record; }, ): void => { const userId = getUserId(); if (!userId) { if (import.meta.env.DEV) { console.log('[tracking] trackButtonClick 跳过: 未登录', { pageId, buttonId }); } return; } const eventName = `button.${pageId}.${buttonId}__click`; const event: UserEventPayload = { event_id: uuidv4(), user_id: userId, event_name: eventName, event_label: options?.label ?? `按钮点击:${pageId}.${buttonId}`, event_time: new Date().toISOString(), session_id: getOrCreateSessionId(), platform: getPlatform(), app_version: getAppVersion(), timezone_offset_minutes: getTimezoneOffsetMinutes(), properties: { page: pageId, button_id: buttonId, ...(options?.properties ?? {}), }, }; reportUserEvent(event); }; /** 包装点击回调:先上报 button 埋点再执行原 onClick(用于原生 button/div 等) */ export function wrapTrackedClick( pageId: string, buttonId: string, onClick?: (e?: ReactMouseEvent) => void, options?: { label?: string; properties?: Record }, ): (e?: ReactMouseEvent) => void { return (e?: ReactMouseEvent) => { trackButtonClick(pageId, buttonId, options); onClick?.(e); }; }