From c3909d63da76198c2710524d3d7462d301e5aa91 Mon Sep 17 00:00:00 2001 From: CodeST <694468528@qq.com> Date: Tue, 6 Jan 2026 19:25:34 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=9F=8B=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CustomKeyboard/KeyboardViewController.m | 68 +++++ .../View/Function/KBFunctionTagListView.m | 18 +- CustomKeyboard/View/KBFunctionView.m | 21 ++ KBMaiPointEventTable.md | 148 +++++++++++ KBMaiPointEventTable.xlsx | Bin 0 -> 22106 bytes Shared/KBMaiPointReporter.h | 26 +- Shared/KBMaiPointReporter.m | 244 ++++++++++++++++-- keyBoard/Class/Guard/V/KBGuideTopCell.m | 10 + keyBoard/Class/Home/V/HomeHeadView.m | 5 + keyBoard/Class/Home/VC/HomeMainVC.m | 12 +- keyBoard/Class/Login/VC/KBEmailLoginVC.m | 5 + keyBoard/Class/Login/VC/KBEmailRegistVC.m | 6 + keyBoard/Class/Login/VC/KBForgetPwdNewPwdVC.m | 6 + keyBoard/Class/Login/VC/KBForgetPwdVC.m | 5 + keyBoard/Class/Login/VC/KBForgetVerPwdVC.m | 5 + keyBoard/Class/Login/VC/KBLoginVC.m | 20 ++ keyBoard/Class/Login/VC/KBRegistVerEmailVC.m | 5 + keyBoard/Class/Me/VC/KBFeedBackVC.m | 6 + keyBoard/Class/Me/VC/KBMyKeyBoardVC.m | 5 + keyBoard/Class/Me/VC/KBPersonInfoVC.m | 14 +- keyBoard/Class/Me/VC/MySkinVC.m | 24 ++ keyBoard/Class/Me/VC/MyVC.m | 18 ++ keyBoard/Class/Pay/VC/KBJfPay.m | 21 ++ keyBoard/Class/Pay/VC/KBVipPay.m | 31 +++ keyBoard/Class/Search/VC/KBSearchResultVC.m | 7 + keyBoard/Class/Search/VC/KBSearchVC.m | 33 +++ keyBoard/Class/Shop/VC/KBShopItemVC.m | 10 + keyBoard/Class/Shop/VC/KBShopVC.m | 10 + keyBoard/Class/Shop/VC/KBSkinDetailVC.m | 22 ++ keyBoard/VC/KBPermissionViewController.m | 18 +- 30 files changed, 784 insertions(+), 39 deletions(-) create mode 100644 KBMaiPointEventTable.md create mode 100644 KBMaiPointEventTable.xlsx diff --git a/CustomKeyboard/KeyboardViewController.m b/CustomKeyboard/KeyboardViewController.m index 74a30bf..20b6069 100644 --- a/CustomKeyboard/KeyboardViewController.m +++ b/CustomKeyboard/KeyboardViewController.m @@ -277,7 +277,16 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center, self.keyBoardMainView.hidden = show; if (show) { + [[KBMaiPointReporter sharedReporter] reportPageExposureWithEventName:@"enter_keyboard_function_panel" + pageId:@"keyboard_function_panel" + extra:nil + completion:nil]; [self hideSubscriptionPanel]; + } else { + [[KBMaiPointReporter sharedReporter] reportPageExposureWithEventName:@"enter_keyboard_main_panel" + pageId:@"keyboard_main_panel" + extra:nil + completion:nil]; } // 可选:把当前显示的视图置顶,避免层级遮挡 @@ -291,6 +300,10 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center, /// 显示/隐藏设置页(高度与 keyBoardMainView 一致),右侧滑入/滑出 - (void)showSettingView:(BOOL)show { if (show) { + [[KBMaiPointReporter sharedReporter] reportPageExposureWithEventName:@"enter_keyboard_settings" + pageId:@"keyboard_settings" + extra:nil + completion:nil]; // if (!self.settingView) { self.settingView = [[KBSettingView alloc] init]; self.settingView.hidden = YES; @@ -342,6 +355,10 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center, BOOL ok = [KBHostAppLauncher openHostAppURL:scheme fromResponder:self.view]; return; } + [[KBMaiPointReporter sharedReporter] reportPageExposureWithEventName:@"enter_keyboard_subscription_panel" + pageId:@"keyboard_subscription_panel" + extra:nil + completion:nil]; [self showFunctionPanel:NO]; KBKeyboardSubscriptionView *panel = self.subscriptionView; if (!panel.superview) { @@ -427,6 +444,12 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center, } - (void)keyBoardMainView:(KBKeyBoardMainView *)keyBoardMainView didTapToolActionAtIndex:(NSInteger)index { + NSDictionary *extra = @{@"index": @(index)}; + [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_keyboard_toolbar_action" + pageId:@"keyboard_main_panel" + elementId:@"toolbar_action" + extra:extra + completion:nil]; if (index == 0) { [self showFunctionPanel:YES]; [self kb_clearCurrentWord]; @@ -436,6 +459,11 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center, } - (void)keyBoardMainViewDidTapSettings:(KBKeyBoardMainView *)keyBoardMainView { + [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_keyboard_settings_btn" + pageId:@"keyboard_main_panel" + elementId:@"settings_btn" + extra:nil + completion:nil]; [self showSettingView:YES]; } @@ -448,16 +476,32 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center, } - (void)keyBoardMainViewDidTapUndo:(KBKeyBoardMainView *)keyBoardMainView { + [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_keyboard_undo_btn" + pageId:@"keyboard_main_panel" + elementId:@"undo_btn" + extra:nil + completion:nil]; [[KBBackspaceUndoManager shared] performUndoFromResponder:self.view]; [self kb_scheduleContextRefreshResetSuppression:YES]; } - (void)keyBoardMainViewDidTapEmojiSearch:(KBKeyBoardMainView *)keyBoardMainView { + [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_keyboard_emoji_search_btn" + pageId:@"keyboard_main_panel" + elementId:@"emoji_search_btn" + extra:nil + completion:nil]; [KBHUD showInfo:KBLocalized(@"Search coming soon")]; } - (void)keyBoardMainView:(KBKeyBoardMainView *)keyBoardMainView didSelectSuggestion:(NSString *)suggestion { if (suggestion.length == 0) { return; } + NSDictionary *extra = @{@"suggestion_len": @(suggestion.length)}; + [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_keyboard_suggestion_item" + pageId:@"keyboard_main_panel" + elementId:@"suggestion_item" + extra:extra + completion:nil]; [[KBBackspaceUndoManager shared] registerNonClearAction]; NSString *current = self.currentWord ?: @""; if (current.length > 0) { @@ -481,6 +525,11 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center, } } - (void)functionView:(KBFunctionView *_Nullable)functionView didRightTapToolActionAtIndex:(NSInteger)index{ + [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_keyboard_function_right_action" + pageId:@"keyboard_function_panel" + elementId:@"right_action" + extra:@{@"action": @"login_or_recharge"} + completion:nil]; if (!KBAuthManager.shared.isLoggedIn) { NSString *schemeStr = [NSString stringWithFormat:@"%@://login?src=keyboard", KB_APP_SCHEME]; NSURL *scheme = [NSURL URLWithString:schemeStr]; @@ -510,10 +559,24 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center, #pragma mark - KBKeyboardSubscriptionViewDelegate - (void)subscriptionViewDidTapClose:(KBKeyboardSubscriptionView *)view { + [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_keyboard_subscription_close_btn" + pageId:@"keyboard_subscription_panel" + elementId:@"close_btn" + extra:nil + completion:nil]; [self hideSubscriptionPanel]; } - (void)subscriptionView:(KBKeyboardSubscriptionView *)view didTapPurchaseForProduct:(KBKeyboardSubscriptionProduct *)product { + NSMutableDictionary *extra = [NSMutableDictionary dictionary]; + if ([product.productId isKindOfClass:NSString.class] && product.productId.length > 0) { + extra[@"product_id"] = product.productId; + } + [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_keyboard_subscription_product_btn" + pageId:@"keyboard_subscription_panel" + elementId:@"product_btn" + extra:extra.copy + completion:nil]; [self hideSubscriptionPanel]; [self kb_openRechargeForProduct:product]; } @@ -588,6 +651,11 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center, } - (void)onTapSettingsBack { + [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_keyboard_settings_back_btn" + pageId:@"keyboard_settings" + elementId:@"back_btn" + extra:nil + completion:nil]; [self showSettingView:NO]; } diff --git a/CustomKeyboard/View/Function/KBFunctionTagListView.m b/CustomKeyboard/View/Function/KBFunctionTagListView.m index c389ac2..95c3a09 100644 --- a/CustomKeyboard/View/Function/KBFunctionTagListView.m +++ b/CustomKeyboard/View/Function/KBFunctionTagListView.m @@ -4,6 +4,7 @@ #import "KBFunctionTagListView.h" #import "KBFunctionTagCell.h" +#import "KBMaiPointReporter.h" static NSString * const kKBFunctionTagCellId2 = @"KBFunctionTagCellId2"; static CGFloat const kKBItemSpace = 4; @@ -66,8 +67,23 @@ static CGFloat const kKBItemSpace = 4; - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section { return kKBItemSpace; } - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { + KBTagItemModel *model = (indexPath.item < self.items.count) ? self.items[indexPath.item] : [KBTagItemModel new]; + NSInteger personaId = 0; + if ([model isKindOfClass:KBTagItemModel.class]) { + personaId = model.characterId > 0 ? model.characterId : model.tagId; + } + NSMutableDictionary *extra = [NSMutableDictionary dictionary]; + extra[@"index"] = @(indexPath.item); + extra[@"id"] = @(personaId); + if ([model.characterName isKindOfClass:NSString.class] && model.characterName.length > 0) { + extra[@"name"] = model.characterName; + } + [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_keyboard_function_tag_item" + pageId:@"keyboard_function_panel" + elementId:@"renshe_item" + extra:extra.copy + completion:nil]; if ([self.delegate respondsToSelector:@selector(tagListView:didSelectIndex:title:)]) { - KBTagItemModel *model = (indexPath.item < self.items.count) ? self.items[indexPath.item] : [KBTagItemModel new]; [self.delegate tagListView:self didSelectIndex:indexPath.item title:model.characterName]; } } diff --git a/CustomKeyboard/View/KBFunctionView.m b/CustomKeyboard/View/KBFunctionView.m index 8b6fa66..8faf6b1 100644 --- a/CustomKeyboard/View/KBFunctionView.m +++ b/CustomKeyboard/View/KBFunctionView.m @@ -640,6 +640,7 @@ static void KBULDarwinCallback(CFNotificationCenterRef center, void *observer, C // 用户点击功能标签:优先 UL 拉起主App,失败再 Scheme;两次都失败则提示开启完全访问。 // 若已开启“完全访问”,则直接在键盘侧创建 KBStreamTextView,并在其右上角提供删除按钮关闭。 - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { + // 点击上报已下沉到 KBFunctionTagListView(保证能拿到人设 id/name) // 权限全部打开(键盘已启用 + 完全访问)。在扩展进程中仅需判断“完全访问”。 if ([[KBFullAccessManager shared] hasFullAccess]) { KBTagItemModel *selModel = self.modelArray[indexPath.item]; @@ -682,6 +683,11 @@ static void KBULDarwinCallback(CFNotificationCenterRef center, void *observer, C #pragma mark - Button Actions - (void)onTapPaste { + [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_keyboard_function_paste_btn" + pageId:@"keyboard_function_panel" + elementId:@"paste_btn" + extra:nil + completion:nil]; // 用户点击“粘贴”时才读取剪贴板: // - iOS16+ 会在跨 App 首次读取时自动弹出系统权限弹窗; // - iOS15 及以下不会弹窗,直接返回内容; @@ -776,6 +782,11 @@ static void KBULDarwinCallback(CFNotificationCenterRef center, void *observer, C - (void)onTapDelete { NSLog(@"点击:删除"); + [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_keyboard_function_delete_btn" + pageId:@"keyboard_function_panel" + elementId:@"delete_btn" + extra:nil + completion:nil]; UIInputViewController *ivc = KBFindInputViewController(self); id proxy = ivc.textDocumentProxy; [[KBInputBufferManager shared] refreshFromProxyIfPossible:proxy]; @@ -786,11 +797,21 @@ static void KBULDarwinCallback(CFNotificationCenterRef center, void *observer, C } - (void)onTapClear { NSLog(@"点击:清空"); + [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_keyboard_function_clear_btn" + pageId:@"keyboard_function_panel" + elementId:@"clear_btn" + extra:nil + completion:nil]; [self.backspaceHandler performClearAction]; } - (void)onTapSend { NSLog(@"点击:发送"); + [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_keyboard_function_send_btn" + pageId:@"keyboard_function_panel" + elementId:@"send_btn" + extra:nil + completion:nil]; [[KBBackspaceUndoManager shared] registerNonClearAction]; // 发送:插入换行。大多数聊天类 App 会把回车视为“发送” UIInputViewController *ivc = KBFindInputViewController(self); diff --git a/KBMaiPointEventTable.md b/KBMaiPointEventTable.md new file mode 100644 index 0000000..84801ff --- /dev/null +++ b/KBMaiPointEventTable.md @@ -0,0 +1,148 @@ +# KBMaiPoint 埋点事件表(统一口径:iOS / Android / 后端) + +## 统一约定(全端一致) + +### 1)事件类型(event_type) +- 页面曝光:`page_exposure` +- 点击事件:`click` + +> iOS 侧可映射为:`KBMaiPointGenericReportTypePage / KBMaiPointGenericReportTypeClick` + +### 2)事件名称(event_name) +- 统一使用 `lower_snake_case`,不绑定任何端的类名/资源名 +- 页面曝光统一前缀:`enter_` +- 点击事件统一前缀:`click_` + +### 3)事件参数(value / params) +- **所有事件都固定带**:`token`(`NSString`,有就传真实值;没有就传空字符串 `""`) +- 建议额外固定带:`page_id`(页面/区域统一ID) +- 点击类事件建议固定带:`element_id`(控件/入口统一ID) +- 列表/集合类点击建议带:`index`(`NSInteger`)与业务 `id`(如 `theme_id` / `product_id`) + +参数示例(最小): +```json +{ "token": "", "page_id": "shop", "element_id": "search_btn" } +``` + +--- + +## A. 主工程(keyBoard) + +### A1)页面曝光(触发:VC 的 `viewDidAppear`) + +| 注释 | 事件类型 | 事件名称 | page_id | iOS 对应页面 | Android 对应页面 | 触发时机 | 事件参数(示例) | +|---|---|---|---|---|---|---|---| +| 进入首页 | page_exposure | enter_home_main | home_main | HomeMainVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"home_main" }` | +| 进入首页Tab容器 | page_exposure | enter_home | home | HomeVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"home" }` | +| 进入热门页 | page_exposure | enter_home_hot | home_hot | HomeHotVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"home_hot" }` | +| 进入排行榜页 | page_exposure | enter_home_rank | home_rank | HomeRankVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"home_rank" }` | +| 进入排行榜内容页 | page_exposure | enter_home_rank_content | home_rank_content | HomeRankContentVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"home_rank_content" }` | +| 进入首页底部弹层 | page_exposure | enter_home_sheet | home_sheet | HomeSheetVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"home_sheet" }` | +| 进入社区页 | page_exposure | enter_community | community | KBCommunityVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"community" }` | +| 进入搜索页 | page_exposure | enter_search | search | KBSearchVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"search" }` | +| 进入搜索结果页 | page_exposure | enter_search_result | search_result | KBSearchResultVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"search_result" }` | +| 进入商店页 | page_exposure | enter_shop | shop | KBShopVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"shop" }` | +| 进入商店分类列表页 | page_exposure | enter_shop_item_list | shop_item_list | KBShopItemVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"shop_item_list" }` | +| 进入皮肤详情页 | page_exposure | enter_skin_detail | skin_detail | KBSkinDetailVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"skin_detail", "theme_id":"" }` | +| 进入我的页 | page_exposure | enter_my | my | MyVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"my" }` | +| 进入我的皮肤页 | page_exposure | enter_my_skin | my_skin | MySkinVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"my_skin" }` | +| 进入我的键盘配置页 | page_exposure | enter_my_keyboard | my_keyboard | KBMyKeyBoardVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"my_keyboard" }` | +| 进入个人信息页 | page_exposure | enter_person_info | person_info | KBPersonInfoVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"person_info" }` | +| 进入反馈页 | page_exposure | enter_feedback | feedback | KBFeedBackVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"feedback" }` | +| 进入公告页 | page_exposure | enter_notice | notice | KBNoticeVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"notice" }` | +| 进入消费记录页 | page_exposure | enter_consumption_record | consumption_record | KBConsumptionRecordVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"consumption_record" }` | +| 进入VIP购买页 | page_exposure | enter_vip_pay | vip_pay | KBVipPay | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"vip_pay" }` | +| 进入积分充值页 | page_exposure | enter_points_recharge | points_recharge | KBJfPay | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"points_recharge" }` | +| 进入登录页 | page_exposure | enter_login | login | KBLoginVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"login" }` | +| 进入邮箱登录页 | page_exposure | enter_login_email | login_email | KBEmailLoginVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"login_email" }` | +| 进入邮箱注册页 | page_exposure | enter_register_email | register_email | KBEmailRegistVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"register_email" }` | +| 进入注册验证码页 | page_exposure | enter_register_verify_email | register_verify_email | KBRegistVerEmailVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"register_verify_email" }` | +| 进入忘记密码页 | page_exposure | enter_forgot_password_email | forgot_password_email | KBForgetPwdVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"forgot_password_email" }` | +| 进入忘记密码验证码页 | page_exposure | enter_forgot_password_verify | forgot_password_verify | KBForgetVerPwdVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"forgot_password_verify" }` | +| 进入忘记密码新密码页 | page_exposure | enter_forgot_password_newpwd | forgot_password_newpwd | KBForgetPwdNewPwdVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"forgot_password_newpwd" }` | +| 进入键盘权限引导页(App内) | page_exposure | enter_keyboard_permission_guide | keyboard_permission_guide | KBPermissionViewController | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"keyboard_permission_guide" }` | +| 进入首次引导页 | page_exposure | enter_guide | guide | KBGuideVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"guide" }` | +| 进入性别选择页 | page_exposure | enter_sex_select | sex_select | KBSexSelVC | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"sex_select" }` | +| 进入WebView页 | page_exposure | enter_webview | webview | KBWebViewViewController | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"webview", "url":"" }` | + +> 测试/工具页(建议仅 DEBUG 或按需接入):`KBTestVC / KBLangTestVC / KBSkinCenterVC / ViewController / LoginViewController / KBLoginSheetViewController`。 + +### A2)点击事件(按钮/列表/入口) + +| 注释 | 事件类型 | 事件名称 | page_id | element_id | iOS 对应控件/方法 | Android 对应控件 | 触发时机 | 事件参数(示例) | +|---|---|---|---|---|---|---|---|---| +| 首页点击“购买会员” | click | click_home_buy_vip_btn | home_main | buy_vip_btn | HomeHeadView `onTapBuyAction` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"home_main", "element_id":"buy_vip_btn" }` | +| 首页点击“权限悬浮按钮” | click | click_home_permission_float_btn | home_main | permission_float_btn | HomeMainVC `keyPermissButton.clickDragViewBlock` | Android 自定义 | 点击悬浮按钮 | `{ "token":"", "page_id":"home_main", "element_id":"permission_float_btn" }` | +| 权限引导页点击“去设置” | click | click_permission_open_settings_btn | keyboard_permission_guide | open_settings_btn | KBPermissionViewController `openSettings` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"keyboard_permission_guide", "element_id":"open_settings_btn" }` | +| 权限引导页点击“关闭” | click | click_permission_close_btn | keyboard_permission_guide | close_btn | KBPermissionViewController `closeButtonAction` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"keyboard_permission_guide", "element_id":"close_btn" }` | +| 商店页点击“搜索” | click | click_shop_search_btn | shop | search_btn | KBShopVC `searchBtnAction` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"shop", "element_id":"search_btn" }` | +| 商店页点击“我的皮肤” | click | click_shop_my_skin_btn | shop | my_skin_btn | KBShopVC `skinBtnAction` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"shop", "element_id":"my_skin_btn" }` | +| 商店列表点击皮肤卡片 | click | click_shop_theme_card | shop_item_list | theme_card | KBShopItemVC `didSelectItemAtIndexPath` | Android 自定义 | didSelect | `{ "token":"", "page_id":"shop_item_list", "element_id":"theme_card", "theme_id":"", "index":0 }` | +| 皮肤详情点击“下载/购买” | click | click_skin_download_btn | skin_detail | download_btn | KBSkinDetailVC `handleDownloadAction` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"skin_detail", "element_id":"download_btn", "theme_id":"", "purchased":0 }` | +| 皮肤详情点击“推荐皮肤” | click | click_skin_recommend_card | skin_detail | recommend_card | KBSkinDetailVC `didSelectItemAtIndexPath` | Android 自定义 | didSelect | `{ "token":"", "page_id":"skin_detail", "element_id":"recommend_card", "from_theme_id":"", "to_theme_id":"", "index":0 }` | +| 搜索栏点击搜索 | click | click_search_submit | search | search_submit | KBSearchBarView `onSearch` | Android 自定义 | 点击搜索 | `{ "token":"", "page_id":"search", "element_id":"search_submit", "keyword_len":0 }` | +| 搜索页点击历史词条 | click | click_search_history_item | search | history_item | KBSearchVC `didSelectItemAtIndexPath` | Android 自定义 | didSelect | `{ "token":"", "page_id":"search", "element_id":"history_item", "index":0 }` | +| 搜索页点击“展开更多历史” | click | click_search_history_more | search | history_more | KBSearchVC `didSelectItemAtIndexPath` | Android 自定义 | didSelect | `{ "token":"", "page_id":"search", "element_id":"history_more" }` | +| 搜索页点击“清空历史” | click | click_search_clear_history | search | clear_history | KBSearchVC `clearHistory`(header trash) | Android 自定义 | 点击垃圾桶 | `{ "token":"", "page_id":"search", "element_id":"clear_history" }` | +| 搜索页点击推荐皮肤 | click | click_search_recommend_theme | search | recommend_theme_card | KBSearchVC `didSelectItemAtIndexPath` | Android 自定义 | didSelect | `{ "token":"", "page_id":"search", "element_id":"recommend_theme_card", "theme_id":"", "index":0 }` | +| 搜索结果页点击皮肤 | click | click_search_result_theme | search_result | result_theme_card | KBSearchResultVC `didSelectItemAtIndexPath` | Android 自定义 | didSelect | `{ "token":"", "page_id":"search_result", "element_id":"result_theme_card", "theme_id":"", "index":0 }` | +| 我的页点击菜单项 | click | click_my_menu_item | my | menu_item | MyVC `didSelectRowAtIndexPath` | Android 自定义 | didSelect | `{ "token":"", "page_id":"my", "element_id":"menu_item", "item_id":"", "item_title":"" }` | +| 我的页点击“邀请”成功复制 | click | click_my_invite_copy | my | invite_copy | MyVC(邀请分支) | Android 自定义 | 复制时机 | `{ "token":"", "page_id":"my", "element_id":"invite_copy" }` | +| 反馈页点击提交 | click | click_feedback_commit_btn | feedback | commit_btn | KBFeedBackVC `onTapCommit` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"feedback", "element_id":"commit_btn", "content_len":0 }` | +| 个人信息点击更换头像 | click | click_person_avatar_edit | person_info | avatar_edit | KBPersonInfoVC `onTapAvatarEdit` | Android 自定义 | tapGesture | `{ "token":"", "page_id":"person_info", "element_id":"avatar_edit" }` | +| 个人信息点击退出登录 | click | click_person_logout_btn | person_info | logout_btn | KBPersonInfoVC `onTapLogout` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"person_info", "element_id":"logout_btn" }` | +| 我的键盘页点击保存 | click | click_my_keyboard_save_btn | my_keyboard | save_btn | KBMyKeyBoardVC `onSave` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"my_keyboard", "element_id":"save_btn" }` | +| 我的皮肤页点击编辑/取消 | click | click_my_skin_toggle_edit | my_skin | toggle_edit | MySkinVC `onToggleEdit` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"my_skin", "element_id":"toggle_edit", "editing":0 }` | +| 我的皮肤页点击删除 | click | click_my_skin_delete_btn | my_skin | delete_btn | MySkinVC `onDelete` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"my_skin", "element_id":"delete_btn", "selected_count":0 }` | +| 我的皮肤页点击皮肤(进入详情) | click | click_my_skin_theme_card | my_skin | theme_card | MySkinVC `didSelectItemAtIndexPath` | Android 自定义 | didSelect | `{ "token":"", "page_id":"my_skin", "element_id":"theme_card", "theme_id":"", "index":0 }` | +| 登录页点击 Apple 登录 | click | click_login_apple_btn | login | apple_btn | KBLoginVC `onTapAppleLogin` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"login", "element_id":"apple_btn" }` | +| 登录页点击邮箱登录 | click | click_login_email_btn | login | email_btn | KBLoginVC `onTapEmailLogin` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"login", "element_id":"email_btn" }` | +| 登录页点击注册 | click | click_login_signup_btn | login | signup_btn | KBLoginVC `onTapSignUp` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"login", "element_id":"signup_btn" }` | +| 登录页点击忘记密码 | click | click_login_forgot_btn | login | forgot_btn | KBLoginVC `onTapForgotPassword` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"login", "element_id":"forgot_btn" }` | +| 邮箱登录页点击提交 | click | click_login_email_submit_btn | login_email | submit_btn | KBEmailLoginVC `onTapSubmit` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"login_email", "element_id":"submit_btn" }` | +| 邮箱注册页点击提交 | click | click_register_email_submit_btn | register_email | submit_btn | KBEmailRegistVC `onTapSubmit` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"register_email", "element_id":"submit_btn" }` | +| 注册验证码页点击确认 | click | click_register_verify_confirm_btn | register_verify_email | confirm_btn | KBRegistVerEmailVC `onTapConfirm` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"register_verify_email", "element_id":"confirm_btn" }` | +| 忘记密码(邮箱)点击下一步 | click | click_forgot_email_next_btn | forgot_password_email | next_btn | KBForgetPwdVC `onTapNext` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"forgot_password_email", "element_id":"next_btn" }` | +| 忘记密码(验证码)点击下一步 | click | click_forgot_verify_next_btn | forgot_password_verify | next_btn | KBForgetVerPwdVC `onTapNext` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"forgot_password_verify", "element_id":"next_btn" }` | +| 忘记密码(新密码)点击下一步 | click | click_forgot_newpwd_next_btn | forgot_password_newpwd | next_btn | KBForgetPwdNewPwdVC `onTapNext` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"forgot_password_newpwd", "element_id":"next_btn" }` | +| VIP页选择套餐 | click | click_vip_select_plan | vip_pay | plan_item | KBVipPay `didSelectItemAtIndexPath` | Android 自定义 | didSelect | `{ "token":"", "page_id":"vip_pay", "element_id":"plan_item", "product_id":"", "index":0 }` | +| VIP页点击支付 | click | click_vip_pay_btn | vip_pay | pay_btn | KBVipPay `onTapPayButton` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"vip_pay", "element_id":"pay_btn", "product_id":"" }` | +| VIP页点击恢复购买 | click | click_vip_restore_btn | vip_pay | restore_btn | KBVipPay `onTapRestoreButton` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"vip_pay", "element_id":"restore_btn" }` | +| VIP页点击关闭 | click | click_vip_close_btn | vip_pay | close_btn | KBVipPay `onTapClose` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"vip_pay", "element_id":"close_btn" }` | +| 积分充值页选择商品 | click | click_points_select_product | points_recharge | product_item | KBJfPay `didSelectItemAtIndexPath` | Android 自定义 | didSelect | `{ "token":"", "page_id":"points_recharge", "element_id":"product_item", "product_id":"", "index":0 }` | +| 积分充值页点击充值 | click | click_points_pay_btn | points_recharge | pay_btn | KBJfPay `onTapPayButton` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"points_recharge", "element_id":"pay_btn", "product_id":"" }` | +| 引导页点击复制示例1 | click | click_guide_copy_example_1 | guide | copy_example_1 | KBGuideTopCell `kb_onTapQ1` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"guide", "element_id":"copy_example_1" }` | +| 引导页点击复制示例2 | click | click_guide_copy_example_2 | guide | copy_example_2 | KBGuideTopCell `kb_onTapQ2` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"guide", "element_id":"copy_example_2" }` | + +--- + +## B. 键盘扩展(CustomKeyboard) + +### B1)页面曝光(触发:显示/切换时机) + +| 注释 | 事件类型 | 事件名称 | page_id | iOS 对应页面/视图 | Android 对应页面 | 触发时机 | 事件参数(示例) | +|---|---|---|---|---|---|---|---| +| 键盘首次显示 | page_exposure | enter_keyboard | keyboard | KeyboardViewController | Android 自定义 | viewDidAppear | `{ "token":"", "page_id":"keyboard" }` | +| 打开功能面板 | page_exposure | enter_keyboard_function_panel | keyboard_function_panel | KBFunctionView | Android 自定义 | showFunctionPanel:YES | `{ "token":"", "page_id":"keyboard_function_panel" }` | +| 关闭功能面板(回到主键盘) | page_exposure | enter_keyboard_main_panel | keyboard_main_panel | KBKeyBoardMainView | Android 自定义 | showFunctionPanel:NO | `{ "token":"", "page_id":"keyboard_main_panel" }` | +| 打开设置页 | page_exposure | enter_keyboard_settings | keyboard_settings | KBSettingView | Android 自定义 | showSettingView:YES | `{ "token":"", "page_id":"keyboard_settings" }` | +| 打开订阅/充值面板 | page_exposure | enter_keyboard_subscription_panel | keyboard_subscription_panel | KBKeyboardSubscriptionView | Android 自定义 | showSubscriptionPanel | `{ "token":"", "page_id":"keyboard_subscription_panel" }` | + +### B2)点击事件(键盘工具栏 / 功能面板 / 订阅面板) + +| 注释 | 事件类型 | 事件名称 | page_id | element_id | iOS 对应控件/方法 | Android 对应控件 | 触发时机 | 事件参数(示例) | +|---|---|---|---|---|---|---|---|---| +| 点击键盘顶部工具栏(index=0 打开功能面板) | click | click_keyboard_toolbar_action | keyboard_main_panel | toolbar_action | KBKeyBoardMainViewDelegate `didTapToolActionAtIndex:` | Android 自定义 | 点击工具栏 | `{ "token":"", "page_id":"keyboard_main_panel", "element_id":"toolbar_action", "index":0 }` | +| 点击键盘设置按钮 | click | click_keyboard_settings_btn | keyboard_main_panel | settings_btn | `keyBoardMainViewDidTapSettings:` | Android 自定义 | 点击设置 | `{ "token":"", "page_id":"keyboard_main_panel", "element_id":"settings_btn" }` | +| 点击设置页返回 | click | click_keyboard_settings_back_btn | keyboard_settings | back_btn | KeyboardViewController `onTapSettingsBack` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"keyboard_settings", "element_id":"back_btn" }` | +| 点击撤销删除 | click | click_keyboard_undo_btn | keyboard_main_panel | undo_btn | `keyBoardMainViewDidTapUndo:` | Android 自定义 | 点击撤销 | `{ "token":"", "page_id":"keyboard_main_panel", "element_id":"undo_btn" }` | +| 点击表情面板搜索 | click | click_keyboard_emoji_search_btn | keyboard_main_panel | emoji_search_btn | `keyBoardMainViewDidTapEmojiSearch:` | Android 自定义 | 点击搜索 | `{ "token":"", "page_id":"keyboard_main_panel", "element_id":"emoji_search_btn" }` | +| 点击联想词条 | click | click_keyboard_suggestion_item | keyboard_main_panel | suggestion_item | `didSelectSuggestion:` | Android 自定义 | 点击候选 | `{ "token":"", "page_id":"keyboard_main_panel", "element_id":"suggestion_item", "index":0 }` | +| 功能面板点击“粘贴” | click | click_keyboard_function_paste_btn | keyboard_function_panel | paste_btn | KBFunctionView `onTapPaste` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"keyboard_function_panel", "element_id":"paste_btn" }` | +| 功能面板点击“删除” | click | click_keyboard_function_delete_btn | keyboard_function_panel | delete_btn | KBFunctionView `onTapDelete` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"keyboard_function_panel", "element_id":"delete_btn" }` | +| 功能面板点击“清空” | click | click_keyboard_function_clear_btn | keyboard_function_panel | clear_btn | KBFunctionView `onTapClear` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"keyboard_function_panel", "element_id":"clear_btn" }` | +| 功能面板点击“发送” | click | click_keyboard_function_send_btn | keyboard_function_panel | send_btn | KBFunctionView `onTapSend` | Android 自定义 | touchUpInside | `{ "token":"", "page_id":"keyboard_function_panel", "element_id":"send_btn" }` | +| 功能面板点击“人设/标签”条目 | click | click_keyboard_function_tag_item | keyboard_function_panel | renshe_item | KBFunctionTagListView `didSelectItemAtIndexPath` | Android 自定义 | didSelect | `{ "token":"", "page_id":"keyboard_function_panel", "element_id":"renshe_item", "index":0, "id":456, "name":"" }` | +| 功能面板右侧点击“登录/充值”入口(未登录走登录) | click | click_keyboard_function_right_action | keyboard_function_panel | right_action | KeyboardViewController `didRightTapToolActionAtIndex:` | Android 自定义 | 点击右侧入口 | `{ "token":"", "page_id":"keyboard_function_panel", "element_id":"right_action", "action":"login_or_recharge" }` | +| 订阅面板点击关闭 | click | click_keyboard_subscription_close_btn | keyboard_subscription_panel | close_btn | `subscriptionViewDidTapClose:` | Android 自定义 | 点击关闭 | `{ "token":"", "page_id":"keyboard_subscription_panel", "element_id":"close_btn" }` | +| 订阅面板点击购买某商品 | click | click_keyboard_subscription_product_btn | keyboard_subscription_panel | product_btn | `didTapPurchaseForProduct:` | Android 自定义 | 点击购买 | `{ "token":"", "page_id":"keyboard_subscription_panel", "element_id":"product_btn", "product_id":"", "index":0 }` | diff --git a/KBMaiPointEventTable.xlsx b/KBMaiPointEventTable.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..785e13bc548758471ffad2f205a1d909da07af54 GIT binary patch literal 22106 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 HF3#?M7%qo8 literal 0 HcmV?d00001 diff --git a/Shared/KBMaiPointReporter.h b/Shared/KBMaiPointReporter.h index 8b3ec19..2d886db 100644 --- a/Shared/KBMaiPointReporter.h +++ b/Shared/KBMaiPointReporter.h @@ -6,7 +6,7 @@ #import #ifndef KB_MAI_POINT_BASE_URL -#define KB_MAI_POINT_BASE_URL @"http://192.168.2.188:35310/api" +#define KB_MAI_POINT_BASE_URL @"http://192.168.2.21:35310/api" #endif #ifndef KB_MAI_POINT_PATH_NEW_ACCOUNT @@ -20,6 +20,8 @@ NS_ASSUME_NONNULL_BEGIN extern NSString * const KBMaiPointErrorDomain; +extern NSString * const KBMaiPointEventTypePageExposure; +extern NSString * const KBMaiPointEventTypeClick; typedef void (^KBMaiPointReportCompletion)(BOOL success, NSError * _Nullable error); @@ -39,6 +41,28 @@ typedef NS_ENUM(NSInteger, KBMaiPointGenericReportType) { + (instancetype)sharedReporter; +/// 统一埋点:POST /genericData +/// - eventType: 建议取值 `page_exposure` / `click` +/// - eventName: 统一事件名(如 enter_xxx / click_xxx) +/// - value: 事件参数字典(内部会自动注入 token;无 token 时为 @"") +- (void)reportEventType:(NSString *)eventType + eventName:(NSString *)eventName + value:(nullable NSDictionary *)value + completion:(KBMaiPointReportCompletion _Nullable)completion; + +/// 页面曝光快捷方法:内部会补齐 page_id +- (void)reportPageExposureWithEventName:(NSString *)eventName + pageId:(NSString *)pageId + extra:(nullable NSDictionary *)extra + completion:(KBMaiPointReportCompletion _Nullable)completion; + +/// 点击快捷方法:内部会补齐 page_id / element_id +- (void)reportClickWithEventName:(NSString *)eventName + pageId:(NSString *)pageId + elementId:(NSString *)elementId + extra:(nullable NSDictionary *)extra + completion:(KBMaiPointReportCompletion _Nullable)completion; + /// POST /newAccount with type + account. - (void)reportNewAccountWithType:(NSString *)type account:(nullable NSString *)account diff --git a/Shared/KBMaiPointReporter.m b/Shared/KBMaiPointReporter.m index 427541e..2f29c62 100644 --- a/Shared/KBMaiPointReporter.m +++ b/Shared/KBMaiPointReporter.m @@ -5,8 +5,15 @@ #import "KBMaiPointReporter.h" #import "KBLog.h" +#import "KBAuthManager.h" +#if __has_include() +#import +#import +#endif NSString * const KBMaiPointErrorDomain = @"KBMaiPointErrorDomain"; +NSString * const KBMaiPointEventTypePageExposure = @"page_exposure"; +NSString * const KBMaiPointEventTypeClick = @"click"; #if DEBUG static void KBMaiPoint_DebugLogURL(NSURLRequest *request) { @@ -47,12 +54,91 @@ static void KBMaiPoint_DebugLogError(NSURLResponse *response, NSError *error) { return [value stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] ?: @""; } +- (NSString *)kb_currentTokenOrEmpty { + NSString *t = [KBAuthManager shared].current.accessToken; + return [self kb_trimmedStringOrEmpty:t]; +} + +- (void)reportEventType:(NSString *)eventType + eventName:(NSString *)eventName + value:(NSDictionary * _Nullable)value + completion:(KBMaiPointReportCompletion _Nullable)completion { + NSString *trimmedType = [self kb_trimmedStringOrEmpty:eventType]; + NSString *trimmedName = [self kb_trimmedStringOrEmpty:eventName]; + if (trimmedType.length == 0 || trimmedName.length == 0) { + NSError *error = [NSError errorWithDomain:KBMaiPointErrorDomain + code:-1 + userInfo:@{NSLocalizedDescriptionKey: @"Invalid parameter"}]; + if (completion) { + dispatch_async(dispatch_get_main_queue(), ^{ + completion(NO, error); + }); + } + return; + } + + NSMutableDictionary *val = [NSMutableDictionary dictionary]; + if ([value isKindOfClass:[NSDictionary class]] && value.count > 0) { + [val addEntriesFromDictionary:value]; + } + if (![val[@"token"] isKindOfClass:NSString.class]) { + val[@"token"] = [self kb_currentTokenOrEmpty]; + } else { + // 若外部传了 token,也做一次兜底(nil -> @"" / trim) + val[@"token"] = [self kb_trimmedStringOrEmpty:val[@"token"]]; + } + + NSDictionary *params = @{ + // 字段兼容:后端若用 eventId 统计,也能直接用 eventName + @"eventType": trimmedType, + @"eventName": trimmedName, + @"eventId": trimmedName, + @"value": val.copy + }; + [self postPath:KB_MAI_POINT_PATH_GENERIC_DATA parameters:params completion:completion]; +} + +- (void)reportPageExposureWithEventName:(NSString *)eventName + pageId:(NSString *)pageId + extra:(NSDictionary * _Nullable)extra + completion:(KBMaiPointReportCompletion _Nullable)completion { + NSString *pid = [self kb_trimmedStringOrEmpty:pageId]; + NSMutableDictionary *val = [NSMutableDictionary dictionary]; + if (pid.length > 0) { + val[@"page_id"] = pid; + } + if ([extra isKindOfClass:[NSDictionary class]] && extra.count > 0) { + [val addEntriesFromDictionary:extra]; + } + [self reportEventType:KBMaiPointEventTypePageExposure eventName:eventName value:val completion:completion]; +} + +- (void)reportClickWithEventName:(NSString *)eventName + pageId:(NSString *)pageId + elementId:(NSString *)elementId + extra:(NSDictionary * _Nullable)extra + completion:(KBMaiPointReportCompletion _Nullable)completion { + NSString *pid = [self kb_trimmedStringOrEmpty:pageId]; + NSString *eid = [self kb_trimmedStringOrEmpty:elementId]; + NSMutableDictionary *val = [NSMutableDictionary dictionary]; + if (pid.length > 0) { + val[@"page_id"] = pid; + } + if (eid.length > 0) { + val[@"element_id"] = eid; + } + if ([extra isKindOfClass:[NSDictionary class]] && extra.count > 0) { + [val addEntriesFromDictionary:extra]; + } + [self reportEventType:KBMaiPointEventTypeClick eventName:eventName value:val completion:completion]; +} + - (void)reportNewAccountWithType:(NSString *)type account:(NSString * _Nullable)account completion:(KBMaiPointReportCompletion _Nullable)completion { NSString *trimmedType = [self kb_trimmedStringOrEmpty:type]; NSString *trimmedAccount = [self kb_trimmedStringOrEmpty:account]; - if (trimmedType.length == 0 || trimmedAccount.length == 0) { + if (trimmedType.length == 0) { NSError *error = [NSError errorWithDomain:KBMaiPointErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Invalid parameter"}]; @@ -66,7 +152,8 @@ static void KBMaiPoint_DebugLogError(NSURLResponse *response, NSError *error) { NSDictionary *params = @{ @"type": trimmedType, - @"account": trimmedAccount + @"account": trimmedAccount ?: @"", + @"token": [self kb_currentTokenOrEmpty] }; [self postPath:KB_MAI_POINT_PATH_NEW_ACCOUNT parameters:params completion:completion]; } @@ -83,27 +170,20 @@ static void KBMaiPoint_DebugLogError(NSURLResponse *response, NSError *error) { - (void)reportGenericDataWithEventType:(KBMaiPointGenericReportType)eventType account:(nullable NSString *)account completion:(KBMaiPointReportCompletion _Nullable)completion{ -// if ([KBUserSessionManager shared].isLoggedIn == false) { -// return; -// } - NSString *trimmedAccount = [self kb_trimmedStringOrEmpty:account]; - if (trimmedAccount.length == 0) { - NSError *error = [NSError errorWithDomain:KBMaiPointErrorDomain - code:-1 - userInfo:@{NSLocalizedDescriptionKey: @"Invalid parameter"}]; - if (completion) { - dispatch_async(dispatch_get_main_queue(), ^{ - completion(NO, error); - }); - } - return; + // 兼容旧接口:没有 eventName 时给一个默认值,避免调用方崩溃 + NSString *typeStr = @"unknown"; + switch (eventType) { + case KBMaiPointGenericReportTypeClick: typeStr = KBMaiPointEventTypeClick; break; + case KBMaiPointGenericReportTypeExposure: typeStr = @"exposure"; break; + case KBMaiPointGenericReportTypePage: typeStr = KBMaiPointEventTypePageExposure; break; + default: break; } - - NSDictionary *params = @{ - @"eventId": @"123", - @"account": account - }; - [self postPath:KB_MAI_POINT_PATH_GENERIC_DATA parameters:params completion:completion]; + NSMutableDictionary *val = [NSMutableDictionary dictionary]; + NSString *trimmedAccount = [self kb_trimmedStringOrEmpty:account]; + if (trimmedAccount.length > 0) { + val[@"account"] = trimmedAccount; + } + [self reportEventType:typeStr eventName:@"generic_event" value:val completion:completion]; } - (void)postPath:(NSString *)path @@ -197,3 +277,123 @@ static void KBMaiPoint_DebugLogError(NSURLResponse *response, NSError *error) { } @end + +#if __has_include() + +// ============================ +// 自动页面曝光(viewDidAppear) +// 说明:仅对“在表里登记过的 VC”生效;其它 VC 不处理。 +// ============================ + +static NSDictionary *KBMaiPoint_PageExposureMap(void) { + static NSDictionary *m; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + m = @{ + // 主工程 + @"HomeMainVC": @{@"event_name": @"enter_home_main", @"page_id": @"home_main"}, + @"HomeVC": @{@"event_name": @"enter_home", @"page_id": @"home"}, + @"HomeHotVC": @{@"event_name": @"enter_home_hot", @"page_id": @"home_hot"}, + @"HomeRankVC": @{@"event_name": @"enter_home_rank", @"page_id": @"home_rank"}, + @"HomeRankContentVC": @{@"event_name": @"enter_home_rank_content", @"page_id": @"home_rank_content"}, + @"HomeSheetVC": @{@"event_name": @"enter_home_sheet", @"page_id": @"home_sheet"}, + @"KBCommunityVC": @{@"event_name": @"enter_community", @"page_id": @"community"}, + @"KBSearchVC": @{@"event_name": @"enter_search", @"page_id": @"search"}, + @"KBSearchResultVC": @{@"event_name": @"enter_search_result", @"page_id": @"search_result"}, + @"KBShopVC": @{@"event_name": @"enter_shop", @"page_id": @"shop"}, + @"KBShopItemVC": @{@"event_name": @"enter_shop_item_list", @"page_id": @"shop_item_list"}, + @"KBSkinDetailVC": @{@"event_name": @"enter_skin_detail", @"page_id": @"skin_detail"}, + @"MyVC": @{@"event_name": @"enter_my", @"page_id": @"my"}, + @"MySkinVC": @{@"event_name": @"enter_my_skin", @"page_id": @"my_skin"}, + @"KBMyKeyBoardVC": @{@"event_name": @"enter_my_keyboard", @"page_id": @"my_keyboard"}, + @"KBPersonInfoVC": @{@"event_name": @"enter_person_info", @"page_id": @"person_info"}, + @"KBFeedBackVC": @{@"event_name": @"enter_feedback", @"page_id": @"feedback"}, + @"KBNoticeVC": @{@"event_name": @"enter_notice", @"page_id": @"notice"}, + @"KBConsumptionRecordVC": @{@"event_name": @"enter_consumption_record", @"page_id": @"consumption_record"}, + @"KBVipPay": @{@"event_name": @"enter_vip_pay", @"page_id": @"vip_pay"}, + @"KBJfPay": @{@"event_name": @"enter_points_recharge", @"page_id": @"points_recharge"}, + @"KBLoginVC": @{@"event_name": @"enter_login", @"page_id": @"login"}, + @"KBEmailLoginVC": @{@"event_name": @"enter_login_email", @"page_id": @"login_email"}, + @"KBEmailRegistVC": @{@"event_name": @"enter_register_email", @"page_id": @"register_email"}, + @"KBRegistVerEmailVC": @{@"event_name": @"enter_register_verify_email", @"page_id": @"register_verify_email"}, + @"KBForgetPwdVC": @{@"event_name": @"enter_forgot_password_email", @"page_id": @"forgot_password_email"}, + @"KBForgetVerPwdVC": @{@"event_name": @"enter_forgot_password_verify", @"page_id": @"forgot_password_verify"}, + @"KBForgetPwdNewPwdVC": @{@"event_name": @"enter_forgot_password_newpwd", @"page_id": @"forgot_password_newpwd"}, + @"KBPermissionViewController": @{@"event_name": @"enter_keyboard_permission_guide", @"page_id": @"keyboard_permission_guide"}, + @"KBGuideVC": @{@"event_name": @"enter_guide", @"page_id": @"guide"}, + @"KBSexSelVC": @{@"event_name": @"enter_sex_select", @"page_id": @"sex_select"}, + @"KBWebViewViewController": @{@"event_name": @"enter_webview", @"page_id": @"webview"}, + + // 键盘扩展 + @"KeyboardViewController": @{@"event_name": @"enter_keyboard", @"page_id": @"keyboard"}, + }; + }); + return m; +} + +static inline void KBMaiPoint_SwizzleInstanceMethod(Class cls, SEL originalSel, SEL swizzledSel) { + Method original = class_getInstanceMethod(cls, originalSel); + Method swizzled = class_getInstanceMethod(cls, swizzledSel); + if (!original || !swizzled) return; + BOOL added = class_addMethod(cls, + originalSel, + method_getImplementation(swizzled), + method_getTypeEncoding(swizzled)); + if (added) { + class_replaceMethod(cls, + swizzledSel, + method_getImplementation(original), + method_getTypeEncoding(original)); + } else { + method_exchangeImplementations(original, swizzled); + } +} + +@interface UIViewController (KBMaiPointAutoReport) +@end + +@implementation UIViewController (KBMaiPointAutoReport) + ++ (void)load { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + KBMaiPoint_SwizzleInstanceMethod(self, @selector(viewDidAppear:), @selector(kb_maipoint_viewDidAppear:)); + }); +} + +- (void)kb_maipoint_viewDidAppear:(BOOL)animated { + [self kb_maipoint_viewDidAppear:animated]; + + NSString *clsName = NSStringFromClass(self.class); + NSDictionary *cfg = KBMaiPoint_PageExposureMap()[clsName]; + if (![cfg isKindOfClass:NSDictionary.class]) { return; } + + NSString *eventName = cfg[@"event_name"]; + NSString *pageId = cfg[@"page_id"]; + if (![eventName isKindOfClass:NSString.class] || ![pageId isKindOfClass:NSString.class]) { return; } + + // 少数页面带额外参数(尽量不取敏感信息) + NSMutableDictionary *extra = [NSMutableDictionary dictionary]; + if ([clsName isEqualToString:@"KBSkinDetailVC"]) { + id themeId = nil; + @try { themeId = [self valueForKey:@"themeId"]; } @catch (__unused NSException *e) { themeId = nil; } + if ([themeId isKindOfClass:NSString.class] && ((NSString *)themeId).length > 0) { + extra[@"theme_id"] = themeId; + } + } else if ([clsName isEqualToString:@"KBWebViewViewController"]) { + id url = nil; + @try { url = [self valueForKey:@"url"]; } @catch (__unused NSException *e) { url = nil; } + if ([url isKindOfClass:NSString.class] && ((NSString *)url).length > 0) { + extra[@"url"] = url; + } + } + + [[KBMaiPointReporter sharedReporter] reportPageExposureWithEventName:eventName + pageId:pageId + extra:(extra.count > 0 ? extra.copy : nil) + completion:nil]; +} + +@end + +#endif diff --git a/keyBoard/Class/Guard/V/KBGuideTopCell.m b/keyBoard/Class/Guard/V/KBGuideTopCell.m index 4b0e28d..46c83f1 100644 --- a/keyBoard/Class/Guard/V/KBGuideTopCell.m +++ b/keyBoard/Class/Guard/V/KBGuideTopCell.m @@ -181,11 +181,21 @@ /// 点击第一条示例文案 - (void)kb_onTapQ1 { + [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_guide_copy_example_1" + pageId:@"guide" + elementId:@"copy_example_1" + extra:nil + completion:nil]; [self kb_copyTextToPasteboard:[self.q1Button titleForState:UIControlStateNormal]]; } /// 点击第二条示例文案 - (void)kb_onTapQ2 { + [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_guide_copy_example_2" + pageId:@"guide" + elementId:@"copy_example_2" + extra:nil + completion:nil]; [self kb_copyTextToPasteboard:[self.q2Button titleForState:UIControlStateNormal]]; } diff --git a/keyBoard/Class/Home/V/HomeHeadView.m b/keyBoard/Class/Home/V/HomeHeadView.m index 9f5e896..e9e7759 100644 --- a/keyBoard/Class/Home/V/HomeHeadView.m +++ b/keyBoard/Class/Home/V/HomeHeadView.m @@ -164,6 +164,11 @@ #pragma mark - Actions - (void)onTapBuyAction { // if (self.onTapBuy) { self.onTapBuy(); } + [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_home_buy_vip_btn" + pageId:@"home_main" + elementId:@"buy_vip_btn" + extra:nil + completion:nil]; // 未登录时先跳到登录页;登录后才允许进入会员购买页 if (![KBUserSessionManager shared].isLoggedIn) { [[KBUserSessionManager shared] goLoginVC]; diff --git a/keyBoard/Class/Home/VC/HomeMainVC.m b/keyBoard/Class/Home/VC/HomeMainVC.m index 9aacc63..387de89 100644 --- a/keyBoard/Class/Home/VC/HomeMainVC.m +++ b/keyBoard/Class/Home/VC/HomeMainVC.m @@ -48,15 +48,15 @@ KBWeakSelf self.keyPermissButton.clickDragViewBlock = ^(WMDragView *dragView){ + [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_home_permission_float_btn" + pageId:@"home_main" + elementId:@"permission_float_btn" + extra:nil + completion:nil]; KBGuideVC *vc = [KBGuideVC new]; [weakSelf.navigationController pushViewController:vc animated:true]; }; - - - [[KBMaiPointReporter sharedReporter] reportGenericDataWithEventType:KBMaiPointGenericReportTypePage account:@"123" completion:^(BOOL success, NSError * _Nullable error) { - NSLog(@"==="); - }]; - + // 测试groups // NSUserDefaults *sharedDefaults = [[NSUserDefaults alloc] initWithSuiteName:AppGroup]; // // 写入一个简单字符串 diff --git a/keyBoard/Class/Login/VC/KBEmailLoginVC.m b/keyBoard/Class/Login/VC/KBEmailLoginVC.m index be003d8..841e315 100644 --- a/keyBoard/Class/Login/VC/KBEmailLoginVC.m +++ b/keyBoard/Class/Login/VC/KBEmailLoginVC.m @@ -226,6 +226,11 @@ KBLOG(@"KBEmailLoginVC onTapSubmit, email=%@, pwdLen=%zd", self.emailTextField.text, self.passwordTextField.text.length); + [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_login_email_submit_btn" + pageId:@"login_email" + elementId:@"submit_btn" + extra:nil + completion:nil]; NSString *email = self.emailTextField.text ? self.emailTextField.text : @""; NSString *password = self.passwordTextField.text ? self.passwordTextField.text : @""; KBWeakSelf; diff --git a/keyBoard/Class/Login/VC/KBEmailRegistVC.m b/keyBoard/Class/Login/VC/KBEmailRegistVC.m index b121ad4..d7726e4 100644 --- a/keyBoard/Class/Login/VC/KBEmailRegistVC.m +++ b/keyBoard/Class/Login/VC/KBEmailRegistVC.m @@ -265,6 +265,12 @@ NSString *pwd = self.passwordTextField.text ?: @""; NSString *repeat = self.repeatPasswordTextField.text ?: @""; + [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_register_email_submit_btn" + pageId:@"register_email" + elementId:@"submit_btn" + extra:nil + completion:nil]; + if (email.length == 0 || pwd.length == 0 || repeat.length == 0) { [KBHUD showInfo:KBLocalized(@"Please complete all fields")]; return; diff --git a/keyBoard/Class/Login/VC/KBForgetPwdNewPwdVC.m b/keyBoard/Class/Login/VC/KBForgetPwdNewPwdVC.m index 8994c6b..cd379b1 100644 --- a/keyBoard/Class/Login/VC/KBForgetPwdNewPwdVC.m +++ b/keyBoard/Class/Login/VC/KBForgetPwdNewPwdVC.m @@ -113,6 +113,12 @@ - (void)onTapNext { NSString *pwd = self.passwordTextField.text ?: @""; NSString *repeat = self.repeatPasswordTextField.text ?: @""; + + [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_forgot_newpwd_next_btn" + pageId:@"forgot_password_newpwd" + elementId:@"next_btn" + extra:nil + completion:nil]; if (pwd.length == 0 || repeat.length == 0) { [KBHUD showInfo:KBLocalized(@"Please complete all fields")]; diff --git a/keyBoard/Class/Login/VC/KBForgetPwdVC.m b/keyBoard/Class/Login/VC/KBForgetPwdVC.m index 3105530..971c6a4 100644 --- a/keyBoard/Class/Login/VC/KBForgetPwdVC.m +++ b/keyBoard/Class/Login/VC/KBForgetPwdVC.m @@ -66,6 +66,11 @@ - (void)onTapNext { NSString *email = [self.emailTextField.text ?: @"" stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]]; + [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_forgot_email_next_btn" + pageId:@"forgot_password_email" + elementId:@"next_btn" + extra:nil + completion:nil]; if (email.length == 0) { [KBHUD showInfo:KBLocalized(@"Enter Email Address")]; return; diff --git a/keyBoard/Class/Login/VC/KBForgetVerPwdVC.m b/keyBoard/Class/Login/VC/KBForgetVerPwdVC.m index e4b61f2..9dd1aee 100644 --- a/keyBoard/Class/Login/VC/KBForgetVerPwdVC.m +++ b/keyBoard/Class/Login/VC/KBForgetVerPwdVC.m @@ -76,6 +76,11 @@ - (void)onTapNext { NSString *code = self.codeInputView.textValue ?: @""; + [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_forgot_verify_next_btn" + pageId:@"forgot_password_verify" + elementId:@"next_btn" + extra:nil + completion:nil]; if (code.length == 0) { [KBHUD showInfo:KBLocalized(@"Enter Email Verification Code")]; return; diff --git a/keyBoard/Class/Login/VC/KBLoginVC.m b/keyBoard/Class/Login/VC/KBLoginVC.m index 2b93580..e591f85 100644 --- a/keyBoard/Class/Login/VC/KBLoginVC.m +++ b/keyBoard/Class/Login/VC/KBLoginVC.m @@ -169,6 +169,11 @@ - (void)onTapAppleLogin { KBLOG(@"onTapAppleLogin"); + [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_login_apple_btn" + pageId:@"login" + elementId:@"apple_btn" + extra:nil + completion:nil]; [[KBLoginVM shared] signInWithAppleFromViewController:KB_CURRENT_NAV completion:^(BOOL success, NSError * _Nullable error) { if (success) { [KBHUD showInfo:KBLocalized(@"Signed in successfully")]; @@ -190,6 +195,11 @@ - (void)onTapEmailLogin { // 后续接入邮箱登录逻辑 KBLOG(@"onTapEmailLogin"); + [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_login_email_btn" + pageId:@"login" + elementId:@"email_btn" + extra:nil + completion:nil]; KBEmailLoginVC *vc = [[KBEmailLoginVC alloc] init]; UINavigationController *nav = KB_CURRENT_NAV; if ([nav isKindOfClass:[BaseNavigationController class]]) { @@ -207,6 +217,11 @@ - (void)onTapSignUp { // 打开注册页 KBLOG(@"onTapSignUp"); + [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_login_signup_btn" + pageId:@"login" + elementId:@"signup_btn" + extra:nil + completion:nil]; KBEmailRegistVC *vc = [[KBEmailRegistVC alloc] init]; UINavigationController *nav = KB_CURRENT_NAV; if ([nav isKindOfClass:[BaseNavigationController class]]) { @@ -219,6 +234,11 @@ - (void)onTapForgotPassword { // 打开忘记密码页 KBLOG(@"onTapForgotPassword"); + [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_login_forgot_btn" + pageId:@"login" + elementId:@"forgot_btn" + extra:nil + completion:nil]; KBForgetPwdVC *vc = [[KBForgetPwdVC alloc] init]; UINavigationController *nav = KB_CURRENT_NAV; if ([nav isKindOfClass:[BaseNavigationController class]]) { diff --git a/keyBoard/Class/Login/VC/KBRegistVerEmailVC.m b/keyBoard/Class/Login/VC/KBRegistVerEmailVC.m index 9772155..b9ec71b 100644 --- a/keyBoard/Class/Login/VC/KBRegistVerEmailVC.m +++ b/keyBoard/Class/Login/VC/KBRegistVerEmailVC.m @@ -89,6 +89,11 @@ - (void)onTapConfirm { NSString *code = self.codeInputView.textValue ?: @""; + [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_register_verify_confirm_btn" + pageId:@"register_verify_email" + elementId:@"confirm_btn" + extra:nil + completion:nil]; if (code.length == 0) { [KBHUD showInfo:KBLocalized(@"Enter Email Verification Code")]; return; diff --git a/keyBoard/Class/Me/VC/KBFeedBackVC.m b/keyBoard/Class/Me/VC/KBFeedBackVC.m index 7922985..38435da 100644 --- a/keyBoard/Class/Me/VC/KBFeedBackVC.m +++ b/keyBoard/Class/Me/VC/KBFeedBackVC.m @@ -90,6 +90,12 @@ [KBHUD showInfo:KBLocalized(@"Please Enter The Content")]; return; } + NSDictionary *extra = @{@"content_len": @(content.length)}; + [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_feedback_commit_btn" + pageId:@"feedback" + elementId:@"commit_btn" + extra:extra + completion:nil]; __weak typeof(self) weakSelf = self; [self.viewModel submitFeedbackWithContent:content completion:^(BOOL success, NSError * _Nullable error) { if (!success) { return; } diff --git a/keyBoard/Class/Me/VC/KBMyKeyBoardVC.m b/keyBoard/Class/Me/VC/KBMyKeyBoardVC.m index a1c2bda..7506031 100644 --- a/keyBoard/Class/Me/VC/KBMyKeyBoardVC.m +++ b/keyBoard/Class/Me/VC/KBMyKeyBoardVC.m @@ -257,6 +257,11 @@ static NSString * const kKBMyKeyboardCellId = @"kKBMyKeyboardCellId"; - (void)onSave { // 点击底部保存按钮时,调用更新用户人设排序接口 + [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_my_keyboard_save_btn" + pageId:@"my_keyboard" + elementId:@"save_btn" + extra:nil + completion:nil]; [self kb_updateUserCharacterSortWithShowHUD:YES]; } diff --git a/keyBoard/Class/Me/VC/KBPersonInfoVC.m b/keyBoard/Class/Me/VC/KBPersonInfoVC.m index d0f5038..003aaba 100644 --- a/keyBoard/Class/Me/VC/KBPersonInfoVC.m +++ b/keyBoard/Class/Me/VC/KBPersonInfoVC.m @@ -257,9 +257,21 @@ #pragma mark - Actions -- (void)onTapAvatarEdit { [self presentImagePicker]; } +- (void)onTapAvatarEdit { + [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_person_avatar_edit" + pageId:@"person_info" + elementId:@"avatar_edit" + extra:nil + completion:nil]; + [self presentImagePicker]; +} - (void)onTapLogout { + [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_person_logout_btn" + pageId:@"person_info" + elementId:@"logout_btn" + extra:nil + completion:nil]; [self.myVM logout]; } diff --git a/keyBoard/Class/Me/VC/MySkinVC.m b/keyBoard/Class/Me/VC/MySkinVC.m index 5ad823c..872d5ab 100644 --- a/keyBoard/Class/Me/VC/MySkinVC.m +++ b/keyBoard/Class/Me/VC/MySkinVC.m @@ -111,6 +111,13 @@ static NSString * const kMySkinCellId = @"kMySkinCellId"; - (void)onToggleEdit { self.editingMode = !self.editingMode; + NSDictionary *extra = @{@"editing": @(self.isEditingMode)}; + [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_my_skin_toggle_edit" + pageId:@"my_skin" + elementId:@"toggle_edit" + extra:extra + completion:nil]; + // 更新顶部按钮 [self.kb_rightButton setTitle:(self.isEditingMode ? KBLocalized(@"Cancel") : KBLocalized(@"Editor")) forState:UIControlStateNormal]; @@ -154,6 +161,13 @@ static NSString * const kMySkinCellId = @"kMySkinCellId"; NSArray *selectedIndexPaths = [[self.collectionView indexPathsForSelectedItems] sortedArrayUsingSelector:@selector(compare:)]; if (selectedIndexPaths.count == 0) return; + NSDictionary *preExtra = @{@"selected_count": @(selectedIndexPaths.count)}; + [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_my_skin_delete_btn" + pageId:@"my_skin" + elementId:@"delete_btn" + extra:preExtra + completion:nil]; + NSMutableArray *themeIds = [NSMutableArray arrayWithCapacity:selectedIndexPaths.count]; for (NSIndexPath *ip in selectedIndexPaths) { if (ip.item >= self.data.count) { continue; } @@ -245,6 +259,16 @@ static NSString * const kMySkinCellId = @"kMySkinCellId"; // 非编辑态:可在此进入详情,当前示例不处理 KBMyTheme *theme = self.data[indexPath.item]; [collectionView deselectItemAtIndexPath:indexPath animated:YES]; + NSMutableDictionary *extra = [NSMutableDictionary dictionary]; + if ([theme.themeId isKindOfClass:NSString.class] && theme.themeId.length > 0) { + extra[@"theme_id"] = theme.themeId; + } + extra[@"index"] = @(indexPath.item); + [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_my_skin_theme_card" + pageId:@"my_skin" + elementId:@"theme_card" + extra:extra.copy + completion:nil]; KBSkinDetailVC *vc = [[KBSkinDetailVC alloc] init]; vc.themeId = theme.themeId; [self.navigationController pushViewController:vc animated:true]; diff --git a/keyBoard/Class/Me/VC/MyVC.m b/keyBoard/Class/Me/VC/MyVC.m index baa45dd..988317e 100644 --- a/keyBoard/Class/Me/VC/MyVC.m +++ b/keyBoard/Class/Me/VC/MyVC.m @@ -120,6 +120,19 @@ [tableView deselectRowAtIndexPath:indexPath animated:YES]; NSDictionary *info = self.data[indexPath.section][indexPath.row]; NSString *itemID = info[@"id"]; + NSMutableDictionary *extra = [NSMutableDictionary dictionary]; + if ([itemID isKindOfClass:NSString.class] && itemID.length > 0) { + extra[@"item_id"] = itemID; + } + NSString *title = info[@"title"]; + if ([title isKindOfClass:NSString.class] && title.length > 0) { + extra[@"item_title"] = title; + } + [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_my_menu_item" + pageId:@"my" + elementId:@"menu_item" + extra:extra.copy + completion:nil]; if ([itemID isEqualToString:@"1"]) { [self.navigationController pushViewController:[KBNoticeVC new] animated:true]; @@ -159,6 +172,11 @@ popover.permittedArrowDirections = 0; } UIPasteboard.generalPasteboard.string = textToCopy; + [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_my_invite_copy" + pageId:@"my" + elementId:@"invite_copy" + extra:nil + completion:nil]; [self presentViewController:activityVC animated:YES completion:nil]; }); }]; diff --git a/keyBoard/Class/Pay/VC/KBJfPay.m b/keyBoard/Class/Pay/VC/KBJfPay.m index bd42600..611709e 100644 --- a/keyBoard/Class/Pay/VC/KBJfPay.m +++ b/keyBoard/Class/Pay/VC/KBJfPay.m @@ -202,6 +202,18 @@ static NSString * const kKBJfPayCellId = @"kKBJfPayCellId"; NSInteger old = self.selectedIndex; self.selectedIndex = indexPath.item; + KBPayProductModel *item = (indexPath.item < self.data.count) ? self.data[indexPath.item] : nil; + NSMutableDictionary *extra = [NSMutableDictionary dictionary]; + extra[@"index"] = @(indexPath.item); + if ([item.productId isKindOfClass:NSString.class] && item.productId.length > 0) { + extra[@"product_id"] = item.productId; + } + [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_points_select_product" + pageId:@"points_recharge" + elementId:@"product_item" + extra:extra.copy + completion:nil]; + KBJfPayCell *newCell = (KBJfPayCell *)[collectionView cellForItemAtIndexPath:indexPath]; [newCell applySelected:YES animated:YES]; if (old >= 0 && old < self.data.count) { @@ -319,6 +331,15 @@ static NSString * const kKBJfPayCellId = @"kKBJfPayCellId"; [KBHUD showInfo:KBLocalized(@"Please select a product")]; return; } + NSMutableDictionary *extra = [NSMutableDictionary dictionary]; + if ([selectedItem.productId isKindOfClass:NSString.class] && selectedItem.productId.length > 0) { + extra[@"product_id"] = selectedItem.productId; + } + [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_points_pay_btn" + pageId:@"points_recharge" + elementId:@"pay_btn" + extra:extra.copy + completion:nil]; NSString *productId = selectedItem.productId; if (productId.length == 0) { [KBHUD showInfo:KBLocalized(@"Product unavailable")]; diff --git a/keyBoard/Class/Pay/VC/KBVipPay.m b/keyBoard/Class/Pay/VC/KBVipPay.m index b43f65c..9d91517 100644 --- a/keyBoard/Class/Pay/VC/KBVipPay.m +++ b/keyBoard/Class/Pay/VC/KBVipPay.m @@ -293,6 +293,11 @@ static NSString * const kKBVipReviewListCellId = @"kKBVipReviewListCellId"; #pragma mark - Action - (void)onTapClose{ + [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_vip_close_btn" + pageId:@"vip_pay" + elementId:@"close_btn" + extra:nil + completion:nil]; [self.navigationController popViewControllerAnimated:true]; } @@ -303,6 +308,15 @@ static NSString * const kKBVipReviewListCellId = @"kKBVipReviewListCellId"; [KBHUD showInfo:KBLocalized(@"Please select a product")]; return; } + NSMutableDictionary *extra = [NSMutableDictionary dictionary]; + if ([plan.productId isKindOfClass:NSString.class] && plan.productId.length > 0) { + extra[@"product_id"] = plan.productId; + } + [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_vip_pay_btn" + pageId:@"vip_pay" + elementId:@"pay_btn" + extra:extra.copy + completion:nil]; NSString *productId = plan.productId; if (productId.length == 0) { [KBHUD showInfo:KBLocalized(@"Product unavailable")]; @@ -326,6 +340,11 @@ static NSString * const kKBVipReviewListCellId = @"kKBVipReviewListCellId"; } - (void)onTapRestoreButton { + [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_vip_restore_btn" + pageId:@"vip_pay" + elementId:@"restore_btn" + extra:nil + completion:nil]; [KBHUD show]; __weak typeof(self) weakSelf = self; [[KBStoreKitBridge shared] restorePurchasesWithCompletion:^(BOOL success, NSString * _Nullable message) { @@ -386,6 +405,18 @@ static NSString * const kKBVipReviewListCellId = @"kKBVipReviewListCellId"; if (self.selectedIndex == indexPath.item) { return; } NSInteger old = self.selectedIndex; self.selectedIndex = indexPath.item; + + KBPayProductModel *plan = (indexPath.item < self.plans.count) ? self.plans[indexPath.item] : nil; + NSMutableDictionary *extra = [NSMutableDictionary dictionary]; + extra[@"index"] = @(indexPath.item); + if ([plan.productId isKindOfClass:NSString.class] && plan.productId.length > 0) { + extra[@"product_id"] = plan.productId; + } + [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_vip_select_plan" + pageId:@"vip_pay" + elementId:@"plan_item" + extra:extra.copy + completion:nil]; KBVipSubscribeCell *newCell = (KBVipSubscribeCell *)[collectionView cellForItemAtIndexPath:indexPath]; [newCell applySelected:YES animated:YES]; diff --git a/keyBoard/Class/Search/VC/KBSearchResultVC.m b/keyBoard/Class/Search/VC/KBSearchResultVC.m index d85738a..ddcabc5 100644 --- a/keyBoard/Class/Search/VC/KBSearchResultVC.m +++ b/keyBoard/Class/Search/VC/KBSearchResultVC.m @@ -120,6 +120,13 @@ static NSString * const kResultCellId = @"KBSkinCardCell"; KBSearchThemeModel *model = self.resultItems[indexPath.item]; NSString *themeId = [model.themeId stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; if (themeId.length == 0) { return; } + NSDictionary *extra = @{@"theme_id": themeId, + @"index": @(indexPath.item)}; + [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_search_result_theme" + pageId:@"search_result" + elementId:@"result_theme_card" + extra:extra + completion:nil]; KBSkinDetailVC *vc = [[KBSkinDetailVC alloc] init]; vc.themeId = themeId; [self.navigationController pushViewController:vc animated:YES]; diff --git a/keyBoard/Class/Search/VC/KBSearchVC.m b/keyBoard/Class/Search/VC/KBSearchVC.m index 87f5f40..e456a4a 100644 --- a/keyBoard/Class/Search/VC/KBSearchVC.m +++ b/keyBoard/Class/Search/VC/KBSearchVC.m @@ -244,6 +244,11 @@ typedef NS_ENUM(NSInteger, KBSearchSection) { /// 清空历史 - (void)clearHistory { + [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_search_clear_history" + pageId:@"search" + elementId:@"clear_history" + extra:nil + completion:nil]; [self.historyWords removeAllObjects]; self.historyExpanded = NO; [self saveHistoryWordsToLocal:self.historyWords]; @@ -369,10 +374,22 @@ typedef NS_ENUM(NSInteger, KBSearchSection) { NSArray *list = [self currentDisplayHistory]; NSString *kw = list[indexPath.item]; if ([kw isEqualToString:kMoreToken]) { + [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_search_history_more" + pageId:@"search" + elementId:@"history_more" + extra:nil + completion:nil]; // 展开所有历史 self.historyExpanded = YES; [self.collectionView reloadData]; } else { + NSDictionary *extra = @{@"index": @(indexPath.item), + @"keyword_len": @(kw.length)}; + [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_search_history_item" + pageId:@"search" + elementId:@"history_item" + extra:extra + completion:nil]; [self.searchBarView updateKeyword:kw]; [self performSearch:kw]; [self openResultForKeyword:kw]; @@ -381,6 +398,16 @@ typedef NS_ENUM(NSInteger, KBSearchSection) { } if (indexPath.section == KBSearchSectionRecommend) { KBShopThemeModel *model = self.recommendedThemes[indexPath.item]; + NSMutableDictionary *extra = [NSMutableDictionary dictionary]; + extra[@"index"] = @(indexPath.item); + if ([model.themeId isKindOfClass:NSString.class] && model.themeId.length > 0) { + extra[@"theme_id"] = model.themeId; + } + [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_search_recommend_theme" + pageId:@"search" + elementId:@"recommend_theme_card" + extra:extra.copy + completion:nil]; [self openDetailForThemeId:model.themeId ?: @""]; } } @@ -394,6 +421,12 @@ typedef NS_ENUM(NSInteger, KBSearchSection) { // _searchBarView.placeholder = @"Themes"; KBWeakSelf _searchBarView.onSearch = ^(NSString * _Nonnull keyword) { + NSDictionary *extra = @{@"keyword_len": @(keyword.length)}; + [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_search_submit" + pageId:@"search" + elementId:@"search_submit" + extra:extra + completion:nil]; // 置顶到历史 + 打开结果页 [weakSelf performSearch:keyword]; [weakSelf openResultForKeyword:keyword]; diff --git a/keyBoard/Class/Shop/VC/KBShopItemVC.m b/keyBoard/Class/Shop/VC/KBShopItemVC.m index 90610f0..e6840fb 100644 --- a/keyBoard/Class/Shop/VC/KBShopItemVC.m +++ b/keyBoard/Class/Shop/VC/KBShopItemVC.m @@ -173,6 +173,16 @@ - (void)kb_handleShopTapAtIndexPath:(NSIndexPath *)indexPath { KBShopThemeModel *selTheme = (indexPath.item < self.dataSource.count) ? self.dataSource[indexPath.item] : nil; + NSMutableDictionary *extra = [NSMutableDictionary dictionary]; + if ([selTheme.themeId isKindOfClass:NSString.class] && selTheme.themeId.length > 0) { + extra[@"theme_id"] = selTheme.themeId; + } + extra[@"index"] = @(indexPath.item); + [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_shop_theme_card" + pageId:@"shop_item_list" + elementId:@"theme_card" + extra:extra.copy + completion:nil]; KBSkinDetailVC *vc = [[KBSkinDetailVC alloc] init]; vc.themeId = selTheme.themeId; [self.navigationController pushViewController:vc animated:true]; diff --git a/keyBoard/Class/Shop/VC/KBShopVC.m b/keyBoard/Class/Shop/VC/KBShopVC.m index 88e23d7..b379c21 100644 --- a/keyBoard/Class/Shop/VC/KBShopVC.m +++ b/keyBoard/Class/Shop/VC/KBShopVC.m @@ -244,11 +244,21 @@ static const CGFloat JXheightForHeaderInSection = 50; #pragma mark - action - (void)searchBtnAction{ + [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_shop_search_btn" + pageId:@"shop" + elementId:@"search_btn" + extra:nil + completion:nil]; KBSearchVC *vc = [[KBSearchVC alloc] init]; [self.navigationController pushViewController:vc animated:true]; } - (void)skinBtnAction{ + [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_shop_my_skin_btn" + pageId:@"shop" + elementId:@"my_skin_btn" + extra:nil + completion:nil]; MySkinVC *vc = [[MySkinVC alloc] init]; [self.navigationController pushViewController:vc animated:true]; } diff --git a/keyBoard/Class/Shop/VC/KBSkinDetailVC.m b/keyBoard/Class/Shop/VC/KBSkinDetailVC.m index 7c37042..6f8e88e 100644 --- a/keyBoard/Class/Shop/VC/KBSkinDetailVC.m +++ b/keyBoard/Class/Shop/VC/KBSkinDetailVC.m @@ -130,6 +130,18 @@ typedef NS_ENUM(NSInteger, KBSkinDetailSection) { if (nextThemeId.length == 0) { return; } if ([nextThemeId isEqualToString:self.themeId ?: @""]) { return; } + NSMutableDictionary *extra = [NSMutableDictionary dictionary]; + if ([self.themeId isKindOfClass:NSString.class] && self.themeId.length > 0) { + extra[@"from_theme_id"] = self.themeId; + } + extra[@"to_theme_id"] = nextThemeId; + extra[@"index"] = @(indexPath.item); + [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_skin_recommend_card" + pageId:@"skin_detail" + elementId:@"recommend_card" + extra:extra.copy + completion:nil]; + // 不跳转新页面:直接切换 themeId,重新请求详情并刷新当前页面 self.themeId = nextThemeId; self.detailModel = nil; @@ -242,6 +254,16 @@ typedef NS_ENUM(NSInteger, KBSkinDetailSection) { [KBHUD showInfo:KBLocalized(@"正在加载主题详情")]; return; } + + NSMutableDictionary *extra = [NSMutableDictionary dictionary]; + extra[@"theme_id"] = self.themeId ?: @""; + extra[@"purchased"] = @(self.detailModel.isPurchased); + [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_skin_download_btn" + pageId:@"skin_detail" + elementId:@"download_btn" + extra:extra.copy + completion:nil]; + NSLog(@"🧩[SkinDetail] action themeId=%@ purchased=%d", self.themeId, self.detailModel.isPurchased); if (self.detailModel.isPurchased) { [self requestDownload]; diff --git a/keyBoard/VC/KBPermissionViewController.m b/keyBoard/VC/KBPermissionViewController.m index 380543a..9e44830 100644 --- a/keyBoard/VC/KBPermissionViewController.m +++ b/keyBoard/VC/KBPermissionViewController.m @@ -193,7 +193,11 @@ static void *KBPermPlayerPresentationSizeContext = &KBPermPlayerPresentationSize } - (void)openSettings { - [self report]; + [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_permission_open_settings_btn" + pageId:@"keyboard_permission_guide" + elementId:@"open_settings_btn" + extra:nil + completion:nil]; NSURL *url = [NSURL URLWithString:UIApplicationOpenSettingsURLString]; UIApplication *app = [UIApplication sharedApplication]; if ([app canOpenURL:url]) { @@ -206,6 +210,11 @@ static void *KBPermPlayerPresentationSizeContext = &KBPermPlayerPresentationSize } - (void)closeButtonAction{ + [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_permission_close_btn" + pageId:@"keyboard_permission_guide" + elementId:@"close_btn" + extra:nil + completion:nil]; [self.navigationController popViewControllerAnimated:true]; } @@ -286,13 +295,6 @@ static void *KBPermPlayerPresentationSizeContext = &KBPermPlayerPresentationSize [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; } -#pragma mark - network -- (void)report{ - [[KBMaiPointReporter sharedReporter] reportNewAccountWithType:@"键盘申请授权" account:nil completion:^(BOOL success, NSError * _Nullable error) { - - }]; -} - #pragma mark - Lazy Subviews - (UIButton *)backButton {