drgame-cc 1.0.18 → 1.0.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -29,8 +29,6 @@ export default class ExitWin extends cc.Component {
29
29
 
30
30
  onEnable() {
31
31
  PlatformAdapter.showWindow(this.node);
32
- PlatformAdapter.registerButton(this.btnCancel);
33
- PlatformAdapter.registerButton(this.btnExit);
34
32
  }
35
33
 
36
34
  onDisable() {
@@ -0,0 +1,162 @@
1
+ import { ActionType, Reporter } from "./ad-sdk/ad-sdk";
2
+
3
+ export enum GameAnalyticsPlatform {
4
+ XiaomiTv = "xiaomi",
5
+ Lewo = "lewo",
6
+ }
7
+
8
+ export enum AnalyticsEvent {
9
+ GameLoading = "game_loading",
10
+ StartGameButton = "start_game_button",
11
+ WatchVideoContinueButton = "watch_video_continue_button",
12
+ ReplayButton = "replay_button",
13
+ RewardVideoAd = "reward_video_ad",
14
+ }
15
+
16
+ export enum AnalyticsAction {
17
+ Exposure = "exposure",
18
+ Click = "click",
19
+ }
20
+
21
+ export interface GameAnalyticsConfig {
22
+ platform?: GameAnalyticsPlatform | string;
23
+ app_item?: string;
24
+ content_type?: string;
25
+ content_id?: string;
26
+ content_title?: string;
27
+ user_id?: string;
28
+ }
29
+
30
+ interface AnalyticsEventDefinition {
31
+ elementPosition: string;
32
+ exposureEventName?: string;
33
+ clickEventName?: string;
34
+ }
35
+
36
+ const APP_ITEM_BY_PLATFORM: { [platform: string]: string } = {
37
+ xiaomi: "25",
38
+ lewo: "29",
39
+ };
40
+
41
+ const REPORT_URL = "https://sup.daoran.tv/bi-api/api/game/lewo/batch";
42
+
43
+ export default class GameAnalytics {
44
+ private static initialized: boolean = false;
45
+ private static loadingReported: boolean = false;
46
+ private static reportedExposures: { [event: string]: boolean } = {};
47
+
48
+ private static readonly definitions: { [event: string]: AnalyticsEventDefinition } = {
49
+ game_loading: {
50
+ elementPosition: "game_loading",
51
+ exposureEventName: "game_loading_expose",
52
+ },
53
+ start_game_button: {
54
+ elementPosition: "start_game_button",
55
+ exposureEventName: "start_game_expose",
56
+ clickEventName: "start_game_click",
57
+ },
58
+ watch_video_continue_button: {
59
+ elementPosition: "watch_video_continue_button",
60
+ exposureEventName: "watch_video_continue_expose",
61
+ clickEventName: "watch_video_continue_click",
62
+ },
63
+ replay_button: {
64
+ elementPosition: "replay_button",
65
+ exposureEventName: "replay_expose",
66
+ clickEventName: "replay_click",
67
+ },
68
+ reward_video_ad: {
69
+ elementPosition: "reward_video_ad",
70
+ exposureEventName: "ad_expose",
71
+ },
72
+ };
73
+
74
+ public static init(config: GameAnalyticsConfig = {}): void {
75
+ if (this.initialized) {
76
+ if (config.user_id !== undefined) this.setUserId(config.user_id);
77
+ return;
78
+ }
79
+
80
+ try {
81
+ const platform = config.platform || GameAnalyticsPlatform.XiaomiTv;
82
+ Reporter.init({
83
+ report_url: REPORT_URL,
84
+ app_item: config.app_item || this.getAppItem(platform),
85
+ content_type: config.content_type || "独立游戏",
86
+ content_id: config.content_id || "rexueqiangshou",
87
+ content_title: config.content_title || "热血枪手",
88
+ });
89
+ this.initialized = true;
90
+ if (config.user_id !== undefined) this.setUserId(config.user_id);
91
+ } catch (error) {
92
+ console.warn("[GameAnalytics] init failed", error);
93
+ }
94
+ }
95
+
96
+ public static setUserId(userId: string): void {
97
+ this.init();
98
+ if (!this.initialized) return;
99
+ try {
100
+ Reporter.setCommonInfo({ user_id: userId || "" });
101
+ } catch (error) {
102
+ console.warn("[GameAnalytics] set user id failed", error);
103
+ }
104
+ }
105
+
106
+ public static reportLoadingOnce(): void {
107
+ if (this.loadingReported) return;
108
+ this.loadingReported = true;
109
+ this.report(AnalyticsEvent.GameLoading, AnalyticsAction.Exposure);
110
+ }
111
+
112
+ public static reportExposureOnce(event: AnalyticsEvent): void {
113
+ if (this.reportedExposures[event]) return;
114
+ this.reportedExposures[event] = true;
115
+ this.report(event, AnalyticsAction.Exposure);
116
+ }
117
+
118
+ public static report(
119
+ event: AnalyticsEvent,
120
+ action: AnalyticsAction,
121
+ adRequestId?: string
122
+ ): void {
123
+ this.init();
124
+ if (!this.initialized) return;
125
+
126
+ const definition = this.definitions[event];
127
+ const eventName = definition && action === AnalyticsAction.Exposure
128
+ ? definition.exposureEventName
129
+ : definition && definition.clickEventName;
130
+ if (!definition || !eventName) {
131
+ console.warn("[GameAnalytics] unsupported event/action", event, action);
132
+ return;
133
+ }
134
+
135
+ try {
136
+ const reportItem: any = {
137
+ element_position: definition.elementPosition,
138
+ action_type: action === AnalyticsAction.Exposure
139
+ ? ActionType.Exposure
140
+ : ActionType.Click,
141
+ event_name: eventName,
142
+ };
143
+ if (event === AnalyticsEvent.RewardVideoAd) {
144
+ reportItem.ad_request_id = adRequestId || "";
145
+ }
146
+ Reporter.report(reportItem);
147
+ } catch (error) {
148
+ console.warn("[GameAnalytics] report failed", error);
149
+ }
150
+ }
151
+
152
+ static getAppItem(platform: string): string {
153
+ const normalizedPlatform = (platform || "").toLowerCase();
154
+ const appItem = APP_ITEM_BY_PLATFORM[normalizedPlatform];
155
+ if (appItem) return appItem;
156
+ console.warn(
157
+ "[GameAnalytics] unknown platform, fallback to Xiaomi TV app_item=25",
158
+ platform
159
+ );
160
+ return APP_ITEM_BY_PLATFORM[GameAnalyticsPlatform.XiaomiTv];
161
+ }
162
+ }
@@ -2,10 +2,11 @@
2
2
  import { BtnExtra } from "./BtnExtra";
3
3
  import TvUi from "./core/TvUi";
4
4
  import { EVideoScene } from "./Define";
5
+ import GameAnalytics, { AnalyticsAction, AnalyticsEvent, GameAnalyticsPlatform } from "./GameAnalytics";
5
6
  import { showRewardVideo } from "./Info";
6
7
  import { Loader } from "./Loader";
7
8
  import ToastManager from "./toast/ToastManager";
8
-
9
+ import { Util } from "./Util";
9
10
  /** 平台适配器,名字有点长防止和Platform,Adapter冲突 */
10
11
  export class PlatformAdapter {
11
12
  private static readonly cpId = "xhbj";
@@ -13,10 +14,13 @@ export class PlatformAdapter {
13
14
  private static readonly designWidth = 1920;
14
15
  private static readonly designHeight = 1080;
15
16
  /** 初始化 */
16
- public static async init() {
17
+ public static async init(znName: string) {
18
+ var platform = GameAnalyticsPlatform.XiaomiTv;//平台由sdk处理
19
+ var appItem = GameAnalytics.getAppItem(platform);
20
+ var contentId = Util.getChineseInitials(znName);
17
21
  await ToastManager.Inst.init();
18
22
  TvUi.init({
19
- cpId: this.cpId,
23
+ cpId: contentId,
20
24
  version: this.version,
21
25
  designWidth: this.designWidth,
22
26
  designHeight: this.designHeight,
@@ -27,7 +31,7 @@ export class PlatformAdapter {
27
31
  defaultIconKey: "default"
28
32
  },
29
33
  adSdk: {
30
- platform: "xiaomi"
34
+ platform: platform
31
35
  },
32
36
  ads: {
33
37
  reward: {
@@ -56,6 +60,15 @@ export class PlatformAdapter {
56
60
  }
57
61
  }
58
62
  });
63
+
64
+ GameAnalytics.init({
65
+ platform: GameAnalyticsPlatform.XiaomiTv,
66
+ app_item: appItem,
67
+ content_type: "游戏", // 游戏(乐窝的游戏,这个字段都固定上报游戏)
68
+ content_id: contentId, // 游戏ID(具体乐窝的游戏ID)
69
+ content_title: znName, // 游戏名(具体的乐窝游戏名)
70
+ });
71
+
59
72
  }
60
73
 
61
74
  /**
@@ -72,7 +85,7 @@ export class PlatformAdapter {
72
85
  * 注册按钮事件,有BtnExtra组件会自动切换按钮图片
73
86
  * @param button 按钮组件
74
87
  */
75
- public static registerButton(button: cc.Button) {
88
+ public static registerButton(button: cc.Node) {
76
89
  var sprite = button.getComponent(cc.Sprite);
77
90
  var btnExtra = button.getComponent(BtnExtra);
78
91
  var onFocus = () => {
@@ -95,8 +108,8 @@ export class PlatformAdapter {
95
108
  */
96
109
  public static registerButtonByName(name: string, parent: cc.Node = null) {
97
110
  if (!parent) parent = cc.director.getScene();
98
- let button = this.findChildByName(parent, name).getComponent(cc.Button);
99
- if (!button) return;
111
+ let button = this.findChildByName(parent, name);
112
+
100
113
  this.registerButton(button);
101
114
  }
102
115
 
@@ -108,13 +121,12 @@ export class PlatformAdapter {
108
121
  */
109
122
  public static focusButtonByName(name: string, parent: cc.Node = null) {
110
123
  if (!parent) parent = cc.director.getScene();
111
- let button = this.findChildByName(parent, name).getComponent(cc.Button);
112
- if (!button) return;
113
- TvUi.focus.focus(button.node);
124
+ let button = this.findChildByName(parent, name);
125
+ TvUi.focus.focus(button);
114
126
  }
115
127
 
116
- public static focusButton(btn: cc.Button) {
117
- TvUi.focus.focus(btn.node);
128
+ public static focusButton(btn: cc.Node) {
129
+ TvUi.focus.focus(btn);
118
130
  }
119
131
 
120
132
  /**
@@ -143,6 +155,7 @@ export class PlatformAdapter {
143
155
  public static showWindow(window: cc.Node) {
144
156
  TvUi.focus.pushScope(window.uuid, window);
145
157
  TvUi.focus.registerButtons(window);
158
+ this.findBtnExtra(window);
146
159
  }
147
160
 
148
161
  /** 关闭界面取消注册按钮事件 */
@@ -153,8 +166,8 @@ export class PlatformAdapter {
153
166
 
154
167
  /** 刷新界面按钮事件, 用于界面按钮变化(列表,下拉)后,需要刷新按钮事件 */
155
168
  public static refreshWindow(window: cc.Node) {
156
- cc.Node.EventType.TOUCH_START
157
169
  TvUi.focus.registerButtons(window);
170
+ this.findBtnExtra(window);
158
171
  }
159
172
 
160
173
  public static async showExitWin() {
@@ -164,6 +177,18 @@ export class PlatformAdapter {
164
177
  exitWin.setPosition(cc.v2(cc.winSize.width / 2, cc.winSize.height / 2));
165
178
  }
166
179
 
180
+ private static findBtnExtra(node: cc.Node) {
181
+ for (let i = 0; i < node.children.length; i++) {
182
+ var child = node.children[i];
183
+ if (child.isValid === false || !child.activeInHierarchy) continue;
184
+ let btnExtra = child.getComponent(BtnExtra);
185
+ if (btnExtra) {
186
+ this.registerButton(child);
187
+ }
188
+ this.findBtnExtra(child);
189
+ }
190
+ }
191
+
167
192
  /** 设置启动页进度条 */
168
193
  public static setProgress(progress: number, tip: string) {
169
194
  if (window["setProgress"]) window["setProgress"](progress, tip);
@@ -173,4 +198,14 @@ export class PlatformAdapter {
173
198
  public static closeLoading() {
174
199
  if (window["closeLoading"]) window["closeLoading"]();
175
200
  }
201
+
202
+ /**
203
+ * 上报事件
204
+ * @param event 事件类型
205
+ * @param action 事件操作 曝光or点击 默认点击
206
+ * @param adRequestId 广告请求ID
207
+ */
208
+ public static report(event: AnalyticsEvent, action: AnalyticsAction = AnalyticsAction.Click, adRequestId?: string) {
209
+ GameAnalytics.report(event, action, adRequestId);
210
+ }
176
211
  }
@@ -0,0 +1,11 @@
1
+ import { pinyin } from "../node_modules/pinyin-pro/types/index";
2
+
3
+ export class Util {
4
+ public static getChineseInitials(str: string): string {
5
+ return pinyin(str, {
6
+ pattern: 'first',
7
+ toneType: 'none',
8
+ type: 'array'
9
+ }).join('').toLowerCase();
10
+ }
11
+ }
@@ -68,6 +68,25 @@ var Reporter = {
68
68
  this._commonInfo.device_id = supersetLewo.createDeviceId();
69
69
  }
70
70
 
71
+ supersetLewo.initUserInfo({
72
+ os_type: "", // 系统类型安卓
73
+ os_version: "", // 安卓版本15
74
+ app_version: "1.0.0", // APP版本号
75
+ app_item: config.app_item, // 渠道编码
76
+ app_name: config.content_title, // APP展示名称
77
+ app_project: config.content_id, // 产品内部编码
78
+ province: "", // 省份编码
79
+ city: "", // 城市编码
80
+ uid: "", // 登录用户UID,未登录填空""
81
+ user_id: "", // 会员/游客ID,无则填空""
82
+ device_id: this._commonInfo.device_id, // 设备唯一ID,没有或不传则自动生成
83
+ device_brand: "", // 手机品牌小写
84
+ device_model: "", // 手机完整型号
85
+ network_type: "", // 蜂窝流量网络
86
+ carrier: "" // 运营商
87
+ // device_id 不传,内部自动生成UUID填充,无需手动传入
88
+ });
89
+
71
90
  this._initialized = true;
72
91
  console.log('[Reporter] Initialized, reportUrl:', supersetLewo.config.reportUrl);
73
92
  },
@@ -1,35 +1,47 @@
1
1
  var supersetLewo = (function () {
2
- // ========== 私有常量与缓存(闭包内部,外部无法访问) ==========
2
+ // ========== 私有常量与缓存(闭包私有,外部不可访问) ==========
3
+ // 设备ID本地存储Key:localStorage + cookie共用
3
4
  var STORAGE_KEY = 'analytics.device_id.uuid_v4';
5
+ // 内存缓存设备UUID,避免重复读取存储
4
6
  var memoryCache = null;
7
+ // 全局缓存用户/设备公共信息,上报顶层user字段统一复用
8
+ var globalUserInfo = {};
9
+
10
+ var initInfoFlag = false;
5
11
 
6
12
  /**
7
- * 生成标准 UUID v4
8
- * 优先使用原生 crypto API,逐级降级
13
+ * 生成标准 UUID v4 设备唯一标识
14
+ * 优先级:原生crypto.randomUUID > crypto.getRandomValues > Math.random伪随机兜底
15
+ * @returns {string} 小写标准UUID v4字符串
9
16
  */
10
17
  function createUuidV4() {
18
+ // 浏览器原生高性能UUID接口
11
19
  if (globalThis.crypto?.randomUUID) {
12
20
  return globalThis.crypto.randomUUID().toLowerCase();
13
21
  }
14
-
22
+ // 支持加密随机字节,手动构造v4规范UUID
15
23
  if (globalThis.crypto?.getRandomValues) {
16
24
  var bytes = new Uint8Array(16);
17
25
  globalThis.crypto.getRandomValues(bytes);
18
- // UUID v4 版本与变种位
26
+ // UUID v4 固定版本位 0x40、变种位 0x80
19
27
  bytes[6] = (bytes[6] & 0x0f) | 0x40;
20
28
  bytes[8] = (bytes[8] & 0x3f) | 0x80;
21
-
29
+ // 字节转两位十六进制字符串
22
30
  var hex = Array.from(bytes, function (val) {
23
31
  return val.toString(16).padStart(2, '0');
24
32
  }).join('');
33
+ // 拼接标准UUID分隔格式
25
34
  return hex.slice(0, 8) + '-' + hex.slice(8, 12) + '-' +
26
35
  hex.slice(12, 16) + '-' + hex.slice(16, 20) + '-' + hex.slice(20);
27
36
  }
28
-
29
- // 最低降级:Math.random 伪随机(熵低,仅统计场景兜底)
37
+ // 最低兼容兜底:Math.random生成伪随机UUID(熵较低,仅无加密API场景使用)
30
38
  return generatePseudoUuid();
31
39
  }
32
40
 
41
+ /**
42
+ * Math.random 伪随机UUID生成器,兼容极老浏览器
43
+ * @returns {string} 伪随机UUID v4格式字符串
44
+ */
33
45
  function generatePseudoUuid() {
34
46
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
35
47
  var r = (Math.random() * 16) | 0;
@@ -39,254 +51,259 @@ var supersetLewo = (function () {
39
51
  }
40
52
 
41
53
  /**
42
- * 安全获取 Cookie(自动转义key,避免正则特殊字符 . + * 等失效)
54
+ * 安全读取Cookie,自动转义正则特殊字符,防止匹配失效
55
+ * @param {string} name cookie键名
56
+ * @returns {string|null} cookie值,无则返回null
43
57
  */
44
58
  function getCookie(name) {
45
59
  try {
60
+ // 转义 . * + ? $ 等正则特殊符号
46
61
  var escapedName = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
47
62
  var reg = new RegExp('(^| )' + escapedName + '=([^;]+)');
48
63
  var match = document.cookie.match(reg);
49
64
  return match ? match[2] : null;
50
65
  } catch (e) {
66
+ // cookie读取异常直接返回空
51
67
  return null;
52
68
  }
53
69
  }
54
70
 
55
71
  /**
56
- * 设置 Cookie:自动适配 HTTPS/HTTP,动态控制 Secure
72
+ * 设置持久化Cookie,有效期1年,自动适配HTTPS Secure标识
73
+ * @param {string} name cookie键名
74
+ * @param {string} value cookie存储值
57
75
  */
58
76
  function setCookie(name, value) {
59
77
  try {
60
78
  var isHttps = window.location.protocol === 'https:';
61
79
  var parts = [
62
80
  name + '=' + value,
63
- 'max-age=31536000',
64
- 'path=/',
65
- 'SameSite=Lax'
81
+ 'max-age=31536000', // 有效期365天
82
+ 'path=/', // 全站生效
83
+ 'SameSite=Lax' // 防跨站劫持
66
84
  ];
67
- if (isHttps) {
68
- parts.push('Secure');
69
- }
85
+ // HTTPS环境追加Secure标识
86
+ if (isHttps) parts.push('Secure');
70
87
  document.cookie = parts.join(';');
71
- } catch (e) {
72
- }
88
+ } catch (e) { }
89
+ }
90
+
91
+ /**
92
+ * 时间格式化工具,统一输出 yyyy-MM-dd HH:mm:ss
93
+ * @returns {string} 格式化后的标准时间字符串
94
+ */
95
+ function formatTime() {
96
+ var date_t = new Date();
97
+ var y_t = date_t.getFullYear();
98
+ var m_t = String(date_t.getMonth() + 1).padStart(2, '0');
99
+ var d_t = String(date_t.getDate()).padStart(2, '0');
100
+ var h_t = String(date_t.getHours()).padStart(2, '0');
101
+ var min_t = String(date_t.getMinutes()).padStart(2, '0');
102
+ var sec_t = String(date_t.getSeconds()).padStart(2, '0');
103
+ return y_t + "-" + m_t + "-" + d_t + " " + h_t + ":" + min_t + ":" + sec_t;
104
+ }
105
+
106
+ /**
107
+ * 填充单条埋点事件默认字段,缺失字段自动补空/默认行为类型,自动填充事件时间
108
+ * @param {Object} eventItem 业务传入的单条埋点原始参数
109
+ * @returns {Object} 补全默认值后的标准事件对象
110
+ */
111
+ function fillEventDefault(eventItem) {
112
+ // 事件字段默认模板,对齐后端data数组字段规范
113
+ var defaultEvent = {
114
+ element_position: "", // 产品定义元素位置编码(唯一标识)
115
+ position_name: "", // 元素位置中文名称
116
+ page_code: "", // OMS后台页面编码,无则空字符串
117
+ page_name: "", // OMS后台页面名称,无则空字符串
118
+ floor_id: "", // OMS后台楼层ID
119
+ floor_name: "", // OMS后台楼层名称
120
+ element_type: "", // 元素枚举:page/vlist/plist/res/act/link等
121
+ element_name: "", // 元素内部名称/标题
122
+ content_id: "", // 最小颗粒度内容ID(游戏/视频ID)
123
+ content_title: "", // 内容标题名称
124
+ ext_attr1: "", // 扩展属性1,存放自定义附属信息
125
+ ext_attr2: "", // 扩展属性2,存放附属ID类参数
126
+ action_type: "曝光", // 行为枚举:曝光/点击/开始播放/结束播放/分享/订购/启动/退出/切后台
127
+ action_value: "", // 行为附属值:播放时长填秒数、订购填金额(单位分)、无附属数据填空
128
+ event_time: "" // 事件发生时间,不传则自动生成当前标准时间
129
+ };
130
+ // 业务参数覆盖默认模板
131
+ var merged = Object.assign({}, defaultEvent, eventItem);
132
+ // 未传入时间自动填充当前客户端时间
133
+ if (!merged.event_time) merged.event_time = formatTime();
134
+ return merged;
73
135
  }
74
136
 
75
- // ========== 对外暴露对象 ==========
137
+ // ========== 对外暴露公共API ==========
76
138
  return {
77
- // ===================== 公共配置 =====================
139
+ // 全局埋点配置项
78
140
  config: {
79
- // 上报接口地址
80
- reportUrl: 'https://sup.daoran.tv/bi-api/api/game/lewo/batch',
81
- // reportUrl: '/bi-api/api/game/lewo/batch',
82
- // 默认请求头
83
- defaultHeaders: [
84
- // { name: 'md5', value: "GYWmhK2MfuQtDc9Cj8Fbw9hGoJwQ+f3WTgHRahD1TRiA6TpexZSORQ==" }
85
- ],
86
- defaultContentType: 'application/json;charset=UTF-8',
87
- // 默认行为类型
88
- defaultActionType: '曝光'
89
- },
90
-
91
- // ===================== 公共上报字段模板(全部改为下划线字段,对齐后端接口图片参数) =====================
92
- defaultReportItem: {
93
- user_id: '', // 用户唯一标识(需要用户登录,产生UID)
94
- device_id: '', // 设备唯一标识(设备号,如果无,则需要生成)
95
- app_item: '', // 渠道,如29,x5
96
- content_type: '游戏', // 游戏(乐窝的游戏,这个字段都固定上报游戏)
97
- content_id: '', // 游戏ID(具体乐窝的游戏ID)
98
- content_title: '', // 游戏名(具体的乐窝游戏名)
99
- element_position: '', // 元素位置(埋点范围表对应的 元素位置列)
100
- action_type: '', // 曝光/点击
101
- event_name: '', // 具体事件名(新增,友盟需要按照上面表格传值,通过元素位置和行为生成事件名字)
102
- event_time: '', // 客户端事件时间(格式建议:yyyy-MM-dd HH:mm:ss)
103
- ad_request_id: '' // 广告请求ID,仅广告相关事件需要
141
+ reportUrl: 'https://sup.daoran.tv/bi-api/api/game/lewo/batch', // 批量上报接口地址
142
+ // reportUrl: '/bi-api/api/game/lewo/batch', // 批量上报接口地址
143
+ defaultHeaders: [], // 默认全局请求头数组
144
+ defaultContentType: 'application/json;charset=UTF-8' // 请求体类型
104
145
  },
105
146
 
106
- // ===================== 工具方法 =====================
107
147
  /**
108
- * 合并默认上报字段,补全默认值
109
- * @param {Object} item 传入的下划线格式上报数据
110
- * @returns {Object} 合并后的完整上报项(下划线字段)
148
+ * 设置全局用户/设备公共信息(上报顶层user对象,全局复用)
149
+ * 页面初始化、登录、切换渠道、切换网络时调用一次即可
150
+ * @param {Object} userObj 用户设备公共参数集合
151
+ * userObj字段说明:
152
+ * os_type: 操作系统类型 android / ios 全小写
153
+ * os_version: 系统版本纯数字,如15、17.1
154
+ * app_version: APP完整版本号 7.3.0
155
+ * app_item: 渠道编码/项目编码
156
+ * app_name: APP展示名称
157
+ * app_project: 产品内部短编码
158
+ * province: 省份数字编码
159
+ * city: 城市数字编码
160
+ * uid: 登录用户全局UID,游客/未登录填空字符串
161
+ * user_id: 会员ID / cocos游客ID,无则空
162
+ * device_brand: 设备品牌全小写 huawei/xiaomi/apple
163
+ * device_model: 设备完整型号
164
+ * network_type: 网络类型 wifi / cellular(蜂窝流量)
165
+ * carrier: 运营商 移动/联通/电信/广电/未知
166
+ * device_id: 可选,不传内部自动生成UUID v4设备标识
111
167
  */
112
- mergeReportItem: function (item) {
113
- var merged = {};
114
- var defaultItem = this.defaultReportItem;
115
- // 拷贝默认值,传入值优先覆盖
116
- for (var key in defaultItem) {
117
- if (defaultItem.hasOwnProperty(key)) {
118
- merged[key] = item[key] !== undefined ? item[key] : defaultItem[key];
119
- }
168
+ initUserInfo: function (userObj) {
169
+ if (initInfoFlag) { return }
170
+ initInfoFlag = true;
171
+ // 未传入设备ID时,自动生成持久化UUID填充
172
+ if (!userObj.device_id) {
173
+ userObj.device_id = this.createDeviceId();
120
174
  }
121
- // 自动生成事件时间(未传时)
122
- if (!merged.event_time) {
123
- merged.event_time = this.handleTime();
175
+ // 合并覆盖全局缓存,保留旧字段,新参数覆盖
176
+ globalUserInfo = Object.assign({}, globalUserInfo, userObj);
177
+
178
+ try {
179
+ // 1. 异步加载 H5 专属 SDK
180
+ (function (w, d, s, q, i) {
181
+ w[q] = w[q] || [];
182
+ var f = d.getElementsByTagName(s)[0],
183
+ j = d.createElement(s);
184
+ j.async = true;
185
+ j.id = 'beacon-aplus';
186
+ // ✅ 使用 H5 网页专属 SDK 地址
187
+ j.src = 'https://d.alicdn.com/alilog/mlog/aplus/' + i + '.js';
188
+ f.parentNode.insertBefore(j, f);
189
+ })(window, document, 'script', 'aplus_queue', '203467608');
190
+
191
+ // ========= 基础配置 =========
192
+ // 2. 设置 AppKey (请确保与友盟后台 H5/Web 应用的 AppKey 一致)
193
+ aplus_queue.push({
194
+ action: 'aplus.setMetaInfo',
195
+ arguments: ['appKey', '6a55f95f6f259537c7c966c8'] // 替换你的Umini AppKey
196
+ });
197
+ // 3. 声明终端类型为 PC (关键配置)
198
+ aplus_queue.push({
199
+ action: 'aplus.setMetaInfo',
200
+ arguments: ['aplus-terminal', 'pc']
201
+ });
202
+ // 4. 采集模式配置
203
+ // 传统多页网站(PC)建议改为 'AUTO';如果是 Vue/React 单页应用(SPA)保持 'MAN'
204
+ aplus_queue.push({
205
+ action: 'aplus.setMetaInfo',
206
+ arguments: ['aplus-waiting', 'AUTO']
207
+ });
208
+ // 调试模式,上线务必改为false
209
+ aplus_queue.push({
210
+ action: 'aplus.setMetaInfo',
211
+ arguments: ['DEBUG', false]
212
+ });
213
+
214
+ // ✅【必须补充】显式初始化SDK
215
+ aplus_queue.push({
216
+ action: 'aplus.init'
217
+ });
218
+
219
+ // ⚠️【重点】aplus-idtype 说明
220
+ // 如果你使用业务自己device_id/uuid:值填 uuid
221
+ // 微信openid:openid;支付宝:alipay_id;字节:anonymousid
222
+ // 暂时不确定类型,可以先注释这一行,避免配置错误
223
+ /*
224
+ aplus_queue.push({
225
+ action: 'aplus.setMetaInfo',
226
+ arguments: ['aplus-idtype', 'uuid']
227
+ });
228
+ */
229
+ } catch (e) {
230
+
124
231
  }
125
- // 填充默认行为类型
126
- if (!merged.action_type) {
127
- merged.action_type = this.config.defaultActionType;
232
+
233
+ try {
234
+ // 设置用户唯一ID(登录/游客都可以设置)
235
+ var u_user_id = userObj.uid || userObj.device_id || '';
236
+ // console.log('u_user_id', userObj, u_user_id)
237
+ aplus_queue.push({
238
+ action: 'aplus.setMetaInfo',
239
+ arguments: ['userId', u_user_id]
240
+ });
241
+ } catch (e) {
242
+
128
243
  }
129
- console.log('mergeReportItem-result', merged);
130
- return merged;
244
+
131
245
  },
132
246
 
133
247
  /**
134
- * 时间格式化:yyyy-MM-dd HH:mm:ss
248
+ * 获取当前全局缓存的user公共信息(拷贝返回,防止外部篡改缓存)
249
+ * @returns {Object} 完整user公参对象
135
250
  */
136
- handleTime: function () {
137
- var date_t = new Date();
138
- var y_t = date_t.getFullYear();
139
- var m_t = date_t.getMonth() + 1;
140
- m_t = m_t < 10 ? "0" + m_t : m_t;
141
- var d_t = date_t.getDate();
142
- d_t = d_t < 10 ? "0" + d_t : d_t;
143
- var h_t = date_t.getHours();
144
- h_t = h_t < 10 ? "0" + h_t : h_t;
145
- var min_t = date_t.getMinutes();
146
- min_t = min_t < 10 ? "0" + min_t : min_t;
147
- var sec_t = date_t.getSeconds();
148
- sec_t = sec_t < 10 ? "0" + sec_t : sec_t;
149
- return y_t + "-" + m_t + "-" + d_t + " " + h_t + ":" + min_t + ":" + sec_t;
251
+ getUserInfo: function () {
252
+ return Object.assign({}, globalUserInfo);
150
253
  },
151
254
 
152
- // ===================== 设备ID相关新增方法 =====================
153
255
  /**
154
- * 获取或创建设备唯一ID(持久化,优先内存缓存 > localStorage > cookie)
155
- * @returns {string} uuid v4 device_id
256
+ * 获取或生成持久化设备唯一ID
257
+ * 读取优先级:内存缓存 > localStorage > cookie > 新建UUID
258
+ * @returns {string} 小写标准UUID v4 device_id
156
259
  */
157
260
  createDeviceId: function () {
158
- if (memoryCache) {
159
- return memoryCache;
160
- }
161
-
162
- // 1. 尝试从 LocalStorage 读取
261
+ // 内存存在直接返回,减少存储读取
262
+ if (memoryCache) return memoryCache;
263
+ // 第一步读取localStorage
163
264
  try {
164
- var existing = localStorage.getItem(STORAGE_KEY);
165
- if (existing) {
166
- existing = existing.trim();
167
- memoryCache = existing.toLowerCase();
265
+ var storageId = localStorage.getItem(STORAGE_KEY);
266
+ if (storageId?.trim()) {
267
+ memoryCache = storageId.toLowerCase();
168
268
  return memoryCache;
169
269
  }
170
- } catch (e) {
171
- }
172
-
173
- // 2. 尝试从 Cookie 读取
174
- var existingCookie = getCookie(STORAGE_KEY);
175
- if (existingCookie) {
176
- existingCookie = existingCookie.trim();
177
- memoryCache = existingCookie.toLowerCase();
178
- // 尝试同步回 LocalStorage
179
- try {
180
- localStorage.setItem(STORAGE_KEY, memoryCache);
181
- } catch (e) {
182
- }
270
+ } catch (e) { }
271
+ // 第二步读取cookie
272
+ var cookieId = getCookie(STORAGE_KEY);
273
+ if (cookieId?.trim()) {
274
+ memoryCache = cookieId.toLowerCase();
275
+ // 同步写入localStorage,双存储统一
276
+ try { localStorage.setItem(STORAGE_KEY, memoryCache); } catch (e) { }
183
277
  return memoryCache;
184
278
  }
185
-
186
- // 3. 重新生成并存储
279
+ // 无存储记录,新建UUID并持久化
187
280
  var newId = createUuidV4();
188
281
  memoryCache = newId;
189
-
190
- try {
191
- localStorage.setItem(STORAGE_KEY, newId);
192
- } catch (e) {
193
- }
282
+ try { localStorage.setItem(STORAGE_KEY, newId); } catch (e) { }
194
283
  setCookie(STORAGE_KEY, newId);
195
-
196
284
  return newId;
197
285
  },
198
286
 
199
287
  /**
200
- * 重置/清除设备ID(清除内存缓存、localStorage、cookie)
288
+ * 重置清除本地全部设备ID缓存(退出账号/清除游客标识场景使用)
289
+ * 清空内存、localStorage、cookie三处存储
201
290
  */
202
291
  resetDeviceId: function () {
203
292
  memoryCache = null;
204
- try {
205
- localStorage.removeItem(STORAGE_KEY);
206
- } catch (e) {
207
- }
293
+ // 删除本地存储
294
+ try { localStorage.removeItem(STORAGE_KEY); } catch (e) { }
295
+ // 过期销毁cookie
208
296
  try {
209
297
  var isHttps = window.location.protocol === 'https:';
210
- var cookieParts = [
211
- STORAGE_KEY + '=',
212
- 'max-age=0',
213
- 'path=/',
214
- 'SameSite=Lax'
215
- ];
216
- if (isHttps) {
217
- cookieParts.push('Secure');
218
- }
298
+ var cookieParts = [STORAGE_KEY + '=', 'max-age=0', 'path=/', 'SameSite=Lax'];
299
+ if (isHttps) cookieParts.push('Secure');
219
300
  document.cookie = cookieParts.join(';');
220
- } catch (e) {
221
- }
222
- },
223
-
224
- // ===================== 核心上报方法 =====================
225
- /**
226
- * 单次日志上报
227
- * @param {Object} obj 下划线格式的上报数据
228
- */
229
- superBatchReport: function (obj) {
230
- var mergedItem = this.mergeReportItem(obj);
231
- // 已为下划线字段,直接上报,移除驼峰转译逻辑
232
- var dataObj = {
233
- data: [mergedItem]
234
- };
235
- this.supersetAjax(dataObj);
301
+ } catch (e) { }
236
302
  },
237
303
 
238
304
  /**
239
- * 批量曝光上报
240
- * @param {Array} dataList 上报数据列表(下划线格式)
241
- * @param {Object} commonInfo 可选-公共字段(设备信息,统一填充到所有条目)
242
- */
243
- batchReporting: function (dataList, commonInfo) {
244
- console.log('BatchReporting', dataList);
245
- var exposureFlag = false;
246
- for (var i = 0; i < dataList.length; i++) {
247
- var item = dataList[i];
248
- if (item.exposureFlag) {
249
- exposureFlag = true;
250
- break;
251
- }
252
- }
253
-
254
- if (!exposureFlag) {
255
- var dataObj = {
256
- "data": []
257
- };
258
- commonInfo = commonInfo || {};
259
-
260
- for (var i = 0; i < dataList.length; i++) {
261
- var item = dataList[i];
262
- dataList[i].exposureFlag = true;
263
-
264
- // 合并公共字段 + 当前条目字段
265
- var reportItem = {};
266
- for (var cKey in commonInfo) {
267
- if (commonInfo.hasOwnProperty(cKey)) {
268
- reportItem[cKey] = commonInfo[cKey];
269
- }
270
- }
271
- for (var iKey in item) {
272
- if (item.hasOwnProperty(iKey)) {
273
- reportItem[iKey] = item[iKey];
274
- }
275
- }
276
-
277
- // 合并默认值,直接下划线结构推入数组
278
- var mergedItem = this.mergeReportItem(reportItem);
279
- dataObj.data.push(mergedItem);
280
- }
281
-
282
- console.log(dataObj);
283
- this.supersetAjax(dataObj);
284
- }
285
- },
286
-
287
- /**
288
- * 上报请求封装
289
- * @param {Object} dataObj 完整的请求体
305
+ * 上报请求中转封装,调用底层ES5 ajax
306
+ * @param {Object} dataObj 完整上报报文 {user:{}, data:[]}
290
307
  */
291
308
  supersetAjax: function (dataObj) {
292
309
  this.ajax({
@@ -296,17 +313,17 @@ var supersetLewo = (function () {
296
313
  dataType: 'json',
297
314
  headers: [],
298
315
  success: function (xhr, rsp) {
299
-
316
+ // 上报成功回调,业务可自行扩展
300
317
  },
301
318
  error: function (xhr, rsp) {
302
-
319
+ console.error('[supersetLewo 埋点上报失败]', rsp);
303
320
  }
304
321
  });
305
322
  },
306
323
 
307
324
  /**
308
- * 基础ajax封装
309
- * @param {Object} config 请求配置
325
+ * ES5 底层原生XMLHttpRequest AJAX封装(还原最初版本逻辑,无Promise/Beacon)
326
+ * @param {Object} config 请求配置对象
310
327
  */
311
328
  ajax: function (config) {
312
329
  var url = config.url;
@@ -320,6 +337,7 @@ var supersetLewo = (function () {
320
337
  var fnError = config.error || function () { };
321
338
  var xmlhttp;
322
339
 
340
+ // 兼容IE低版本XMLHttpRequest
323
341
  if (window.XMLHttpRequest) {
324
342
  xmlhttp = new XMLHttpRequest();
325
343
  } else {
@@ -330,16 +348,14 @@ var supersetLewo = (function () {
330
348
  if (xmlhttp.readyState == 4) {
331
349
  var rsp = xmlhttp.responseText || xmlhttp.responseXML;
332
350
  var parseError = false;
333
- // 增加JSON解析异常捕获,避免响应格式错误导致页面报错
351
+ // JSON解析异常捕获,防止页面报错
334
352
  if (dataType == 'json' && typeof rsp == 'string') {
335
353
  try {
336
354
  rsp = JSON.parse(rsp);
337
355
  } catch (e) {
338
356
  parseError = true;
339
- return;
340
357
  }
341
358
  }
342
-
343
359
  if (parseError) {
344
360
  fnError(xmlhttp, rsp);
345
361
  } else if (xmlhttp.status == 200) {
@@ -351,61 +367,185 @@ var supersetLewo = (function () {
351
367
  };
352
368
 
353
369
  xmlhttp.open(type, url, dataAsync);
370
+ // 追加自定义请求头
354
371
  for (var i = 0; i < headers.length; ++i) {
355
372
  xmlhttp.setRequestHeader(headers[i].name, headers[i].value);
356
373
  }
357
374
  xmlhttp.setRequestHeader('Content-Type', contentType);
375
+ // 对象转JSON字符串发送
358
376
  data = JSON.stringify(data);
359
377
  xmlhttp.send(data);
360
- }
378
+ },
379
+
380
+ /**
381
+ * 单条埋点事件上报
382
+ * @param {Object} eventParam 单条事件埋点参数,字段对齐data数组规范
383
+ */
384
+ superBatchReport: function (eventParam) {
385
+ // 填充缺失默认字段与事件时间
386
+ var eventItem = fillEventDefault(eventParam);
387
+ // 组装标准上报报文
388
+ var reportBody = {
389
+ user: this.getUserInfo(), // 全局公共用户设备信息
390
+ data: [eventItem] // 单事件数组
391
+ };
392
+ this.supersetAjax(reportBody);
393
+
394
+ // return
395
+
396
+ try {
397
+ var action_type = eventParam.action_type == '点击' ? 'CLICK' : 'EXPOSURE'
398
+ var eventName = eventParam.element_position + '_' + action_type
399
+
400
+ // console.log('um-action_type', action_type)
401
+ // console.log('um-eventName', eventName)
402
+ // console.log('um-aplus_queue', aplus_queue)
403
+ aplus_queue.push({
404
+ action: 'aplus.record',
405
+ // 第二个参数事件类型:CLICK / EXPOSURE / PAGE等,常规点击用CLICK
406
+ arguments: [eventName, action_type, eventParam]
407
+ });
408
+ } catch (e) {
409
+ console.log("superBatchReport-umeng error", e)
410
+ }
411
+ },
412
+
413
+ /**
414
+ * 批量多条埋点事件上报(一次性上报多个行为,减少请求次数)
415
+ * @param {Array} eventList 多条埋点事件对象数组
416
+ */
417
+ batchReporting: function (eventList) {
418
+ // 非数组/空数组直接终止上报
419
+ if (!Array.isArray(eventList) || eventList.length === 0) return;
420
+ // 批量填充每条事件默认参数
421
+ var filledData = [];
422
+ for (var i = 0; i < eventList.length; i++) {
423
+ filledData.push(fillEventDefault(eventList[i]));
424
+ }
425
+ // 组装批量上报报文
426
+ var reportBody = {
427
+ user: this.getUserInfo(),
428
+ data: filledData
429
+ };
430
+ this.supersetAjax(reportBody);
431
+ },
432
+
361
433
  };
362
434
  })();
363
435
 
364
- // 单次上报调用示例(入参直接使用下划线key,方法名保持驼峰不变)
365
- /*supersetLewo.superBatchReport({
366
- user_id: '', // 用户唯一标识(需要用户登录,产生UID)
367
- device_id: supersetLewo.createDeviceId(), // 设备唯一标识(设备号,如果无,则需要生成)
368
- app_item: '29', // 渠道,如29,x5
369
- content_type: '游戏', // 游戏(乐窝的游戏,这个字段都固定上报游戏)
370
- content_id: '', // 游戏ID(具体乐窝的游戏ID)
371
- content_title: '', // 游戏名(具体的乐窝游戏名)
372
- element_position: '', // 元素位置(埋点范围表对应的 元素位置列)
373
- action_type: '曝光', // 曝光/点击
374
- event_name: '', // 具体事件名(新增,友盟需要按照上面表格传值,通过元素位置和行为生成事件名字)
375
- ad_request_id: '', // 广告请求ID,仅广告相关事件需要
376
- event_time: '', // 客户端事件时间(格式建议:yyyy-MM-dd HH:mm:ss)
436
+ // ===================== 使用示例(带详细注释) =====================
437
+ /**
438
+ * 示例1:页面初始化/登录后 设置全局用户设备公共信息,初始化友盟埋点SDK
439
+ * 仅需初始化一次,后续所有superBatchReport/batchReporting自动携带user参数
440
+ */
441
+ // console.log("initUserInfo");
442
+ /*supersetLewo.initUserInfo({
443
+ os_type: "windows", // 系统类型安卓
444
+ os_version: "1", // 安卓版本15
445
+ app_version: "1.0.0", // APP版本号
446
+ app_item: "", // 渠道编码
447
+ app_name: "", // APP展示名称
448
+ app_project: "", // 产品内部编码
449
+ province: "100", // 省份编码
450
+ city: "", // 城市编码
451
+ uid: "", // 登录用户UID,未登录填空""
452
+ user_id: "", // 会员/游客ID,无则填空""
453
+ device_id: "", // 设备唯一ID,没有或不传则自动生成
454
+ device_brand: "", // 手机品牌小写
455
+ device_model: "", // 手机完整型号
456
+ network_type: "", // 蜂窝流量网络
457
+ carrier: "" // 运营商
458
+ // device_id 不传,内部自动生成UUID填充,无需手动传入
377
459
  });*/
378
-
379
-
380
- // 批量上报调用示例(方法名驼峰,参数下划线)
381
- /*supersetLewo.batchReporting([
460
+ console.log("initUserInfo", supersetLewo.getUserInfo());
461
+
462
+ /**
463
+ * 示例2:单条埋点上报 - 首页曝光事件
464
+ * 调用superBatchReport,仅传入当前事件独有字段,公共user自动拼接
465
+ */
466
+
467
+ /*setTimeout(function () {
468
+ supersetLewo.superBatchReport({
469
+ element_position: "Main_Start", // 元素位置编码
470
+ position_name: "游戏主页", // 中文位置名称
471
+ page_code: "", // OMS后台页面编码,无则空
472
+ page_name: "", // OMS后台页面名称,无则空
473
+ element_type: "page", // 元素类型页面
474
+ element_name: "gamestart", // 元素标识名
475
+ content_id: "10001", // 当前页面绑定内容ID
476
+ content_title: "我和美女有个约会", // 内容标题
477
+ action_type: "曝光", // 行为:曝光
478
+ action_value: "", // 行为值,时长填秒数,订购填金额(单位:分);无时间跨度行为填空
479
+ event_time: '' // event_time不传,内部自动生成当前时间
480
+ });
481
+ }, 2000);*/
482
+
483
+
484
+ /**
485
+ * 示例3:单条埋点上报 - 游戏卡片点击事件
486
+ */
487
+ /*
488
+ supersetLewo.superBatchReport({
489
+ element_position: "Game_Card_List",
490
+ position_name: "游戏列表卡片",
491
+ element_type: "vlist",
492
+ element_name: "game_item",
493
+ content_id: "10002",
494
+ content_title: "开心消消乐",
495
+ action_type: "点击",
496
+ action_value: ""
497
+ });
498
+ */
499
+
500
+ /**
501
+ * 示例4:批量埋点上报,一次性上报曝光+点击两条事件,减少http请求
502
+ * 调用batchReporting,传入事件数组
503
+ */
504
+ /*
505
+ supersetLewo.batchReporting([
506
+ // 第一条:首页曝光
382
507
  {
383
- user_id: 'test123',
384
- device_id: supersetLewo.createDeviceId(),
385
- app_item: '29',
386
- content_type: '游戏',
387
- content_id: '1',
388
- content_title: '2',
389
- element_position: '3',
390
- action_type: '曝光',
391
- event_name: '4',
392
- ad_request_id: '',
393
- event_time: '',
508
+ element_position: "Main_Start",
509
+ position_name: "游戏主页",
510
+ element_type: "page",
511
+ content_id: "10001",
512
+ content_title: "我和美女有个约会",
513
+ action_type: "曝光"
394
514
  },
515
+ // 第二条:游戏卡片点击
395
516
  {
396
- user_id: 'test123444',
397
- device_id: supersetLewo.createDeviceId(),
398
- app_item: '29',
399
- content_type: '游戏',
400
- content_id: '1',
401
- content_title: '2',
402
- element_position: '3',
403
- action_type: '曝光',
404
- event_name: '4',
405
- ad_request_id: '',
406
- event_time: '',
517
+ element_position: "Game_Card_List",
518
+ position_name: "游戏列表卡片",
519
+ element_type: "vlist",
520
+ content_id: "10002",
521
+ content_title: "开心消消乐",
522
+ action_type: "点击"
407
523
  }
408
- ])*/
524
+ ]);
525
+ */
526
+
527
+ /**
528
+ * 示例5:播放结束埋点(携带播放时长action_value,单位秒)
529
+ */
530
+ /*
531
+ supersetLewo.superBatchReport({
532
+ element_position: "Game_Play_Page",
533
+ position_name: "游戏播放页",
534
+ element_type: "page",
535
+ content_id: "10003",
536
+ content_title: "夏日沙滩派对",
537
+ action_type: "结束播放",
538
+ action_value: "125" // 播放总时长125秒
539
+ });
540
+ */
541
+
542
+ /**
543
+ * 示例6:清除游客设备ID(切换账号/清除本地标识使用)
544
+ */
545
+ /*
546
+ supersetLewo.resetDeviceId();
547
+ */
548
+
409
549
 
410
550
  // CommonJS 模块导出(不改动上方 IIFE 逻辑)
411
551
  if (typeof module !== 'undefined' && module.exports) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "drgame-cc",
3
- "version": "1.0.18",
3
+ "version": "1.0.19",
4
4
  "description": "道然游戏适配包",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -32,8 +32,9 @@
32
32
  "rimraf": "^5.0.0"
33
33
  },
34
34
  "dependencies": {
35
- "typescript": "5.9.3",
36
- "fs-extra": "^11.2.0"
35
+ "fs-extra": "^11.2.0",
36
+ "pinyin-pro": "^3.28.2",
37
+ "typescript": "^5.0.0"
37
38
  },
38
39
  "peerDependencies": {
39
40
  "typescript": "^5.0.0"