| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145 |
- 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<string, string> = {
- '/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',
- '/lumi-outfits': 'lumi_outfits',
- '/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<string, unknown>;
- },
- ): 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<HTMLElement>) => void,
- options?: { label?: string; properties?: Record<string, unknown> },
- ): (e?: ReactMouseEvent<HTMLElement>) => void {
- return (e?: ReactMouseEvent<HTMLElement>) => {
- trackButtonClick(pageId, buttonId, options);
- onClick?.(e);
- };
- }
|