yrjy_mini_sdk 1.0.0

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.
@@ -0,0 +1,531 @@
1
+ // 引入配置文件
2
+ import config from "../config/index.js";
3
+ import { reportLog } from "../axios/api.js";
4
+ import { safeJsonParse } from "../utils/common.js";
5
+ class Track {
6
+ // 定义配置项(后端接口、开关控制)
7
+ static config = {
8
+ reportUrl: config.logURL, // 后端埋点上报接口 (默认使用配置文件中的logURL)
9
+ enabled: true, // 是否启用埋点(默认开启)
10
+ };
11
+
12
+ //获取当前小程序的缓存信息
13
+ static storageDataFromMiniPro = (() => {
14
+ try {
15
+ const storageData = uni.getStorageSync("storageName");
16
+ return safeJsonParse(storageData, {});
17
+ } catch (error) {
18
+ console.error("❌ 解析缓存数据失败:", error);
19
+ return {};
20
+ }
21
+ })();
22
+
23
+ // 记录小程序启动时间
24
+ static launchTime = null;
25
+ // 缓存网络状态
26
+ static cachedNetworkType = "";
27
+ // 小程序初始化时的基本信息
28
+ static MiniProBaseInfo = {};
29
+ // 网络状态
30
+ static networkType = "";
31
+ // 小程序启动时的参数
32
+ static launchOptions = {};
33
+ // 便于后续埋点使用的参数=======================================================
34
+ // 一级栏目
35
+ static primaryCategory = "";
36
+ // 二级栏目
37
+ static secondaryCategory = "";
38
+
39
+ /**
40
+ * 配置埋点参数,权限校验,初始化埋点(UniApp 启动时调用一次,全局唯一)
41
+ */
42
+ static async init(options) {
43
+ // 初始化时获取一次网络状态并缓存
44
+ await this._updateNetworkType();
45
+ // 记录小程序启动时间
46
+ this.launchTime = Date.now();
47
+ // 合并默认配置和有效用户配置
48
+ this.config = { ...this.config, ...options };
49
+ // 获取小程序的基础信息
50
+ this.MiniProBaseInfo = await this.getBaseInf();
51
+ // 获取小程序启动时的参数
52
+ this.launchOptions = this.getLaunchOptionsSync();
53
+ // 获取并缓存用户信息
54
+ this.cachedUserInfo = this.getUserInfo();
55
+
56
+ // 监听网络状态变化,实时更新缓存
57
+ uni.onNetworkStatusChange((res) => {
58
+ this.networkType = res.networkType || "";
59
+ });
60
+ this.report({ name: "initEvent",action:'埋点初始化' });
61
+ // 添加调试信息,初始化的参数
62
+ console.log("✅ 埋点初始化成功", {
63
+ launchTime: this.launchTime,
64
+ enabled: this.config.enabled,
65
+ reportUrl: this.config.reportUrl,
66
+ MiniProBaseInfo: this.MiniProBaseInfo,
67
+ networkType: this.networkType,
68
+ launchOptions: this.launchOptions,
69
+ cachedUserInfo: this.cachedUserInfo,
70
+ });
71
+ }
72
+ /**
73
+ * 获取一级栏目
74
+ * @returns {string} 当前的一级栏目值
75
+ */
76
+ static getPrimaryCategory() {
77
+ return this.primaryCategory;
78
+ }
79
+ /**
80
+ * 设置一级栏目
81
+ * @param {Array<{ title: string }>} categoryList - 栏目列表,每个元素需包含 title 字段
82
+ * @param {number} selectedIndex - 当前选中的索引
83
+ */
84
+ static setPrimaryCategory(categoryList, selectedIndex) {
85
+ this._setCategory(categoryList, selectedIndex, "primaryCategory", "一级栏目" );
86
+ }
87
+ /**
88
+ * 获取二级栏目
89
+ * @returns {string} 当前的二级栏目值
90
+ */
91
+ static getSecondaryCategory() {
92
+ return this.secondaryCategory;
93
+ }
94
+ /**
95
+ * 设置二级栏目
96
+ * @param {Array<{ title: string }>} itemList - 栏目列表,每个元素需包含 title 字段
97
+ * @param {number} selectedIndex - 当前选中的索引
98
+ */
99
+ static setSecondaryCategory(itemList, selectedIndex) {
100
+ this._setCategory(itemList, selectedIndex, "secondaryCategory", "二级栏目");
101
+ }
102
+ /**
103
+ * 设置栏目
104
+ * @private
105
+ * @param {Array<{ title: string }>} list - 栏目列表,每个元素需包含 title 字段
106
+ * @param {number} selectedIndex - 当前选中的索引
107
+ * @param {string} propertyName - 要设置的属性名
108
+ * @param {string} categoryType - 栏目类型(用于日志)
109
+ */
110
+ static _setCategory(list, selectedIndex, propertyName, categoryType) {
111
+ // 参数校验:确保 list 是非空数组,selectedIndex 是有效数字
112
+ if (!Array.isArray(list) || list.length === 0) {
113
+ this[propertyName] = "";
114
+ console.warn(`❌ ${categoryType}列表必须是非空数组`);
115
+ return;
116
+ }
117
+
118
+ if (
119
+ typeof selectedIndex !== "number" ||
120
+ selectedIndex < 0 ||
121
+ selectedIndex >= list.length
122
+ ) {
123
+ this[propertyName] = "";
124
+ console.warn(`❌ ${categoryType}的selectedIndex无效或超出范围`);
125
+ return;
126
+ }
127
+
128
+ // 获取当前选中项
129
+ const currentItem = list[selectedIndex];
130
+
131
+ // 校验当前项是否包含有效的 title 字段
132
+ if (
133
+ !currentItem ||
134
+ typeof currentItem.title !== "string" ||
135
+ currentItem.title.trim() === ""
136
+ ) {
137
+ this[propertyName] = "";
138
+ console.warn(`❌ ${categoryType}的当前选中项不存在或title为空`);
139
+ return;
140
+ }
141
+
142
+ // 设置栏目并记录日志
143
+ this[propertyName] = currentItem.title;
144
+ console.log(`✅ ${categoryType}已设置为:${currentItem.title}`);
145
+ }
146
+ /**
147
+ * 重置或设置栏目信息
148
+ * @param {string} type - 操作类型,可选值:"primary"(一级栏目)、"secondary"(二级栏目)、"both"(两者都操作)
149
+ * @param {string|Object} [value] - 可选,要设置的值。
150
+ * - 当 type 为 "primary" 或 "secondary" 时,value 应为字符串
151
+ * - 当 type 为 "both" 时,value 可以是字符串(同时设置两个栏目为相同值)或对象(分别设置两个栏目)
152
+ * - 如果不提供,则重置为默认值(空字符串)
153
+ */
154
+ static resetCategory(type = "secondary", value) {
155
+ // 参数校验
156
+ if (!["primary", "secondary", "both"].includes(type)) {
157
+ console.warn("❌ 操作类型无效,可选值为:primary、secondary、both");
158
+ return;
159
+ }
160
+
161
+ // 如果未提供 value,则统一重置为默认值(空字符串)
162
+ if (value === undefined) {
163
+ switch (type) {
164
+ case "primary":
165
+ this.primaryCategory = "";
166
+ console.log("✅ 一级栏目已重置");
167
+ break;
168
+ case "secondary":
169
+ this.secondaryCategory = "";
170
+ console.log("✅ 二级栏目已重置");
171
+ break;
172
+ case "both":
173
+ this.primaryCategory = "";
174
+ this.secondaryCategory = "";
175
+ console.log("✅ 一级栏目和二级栏目均已重置");
176
+ break;
177
+ }
178
+ return;
179
+ }
180
+
181
+ // 校验值的类型
182
+ if (type === "both") {
183
+ // 当 type 为 "both" 时,value 可以是字符串或对象
184
+ if (typeof value !== "string" && (typeof value !== "object" || value === null)) {
185
+ console.warn("❌ 当操作类型为 both 时,值必须是字符串或对象");
186
+ return;
187
+ }
188
+ // 如果是对象,校验对象结构
189
+ if (typeof value === "object") {
190
+ if (value.primary !== undefined && typeof value.primary !== "string") {
191
+ console.warn("❌ 一级栏目值必须是字符串类型");
192
+ return;
193
+ }
194
+ if (value.secondary !== undefined && typeof value.secondary !== "string") {
195
+ console.warn("❌ 二级栏目值必须是字符串类型");
196
+ return;
197
+ }
198
+ }
199
+ } else {
200
+ // 当 type 为 "primary" 或 "secondary" 时,value 必须是字符串
201
+ if (typeof value !== "string") {
202
+ console.warn("❌ 栏目值必须是字符串类型");
203
+ return;
204
+ }
205
+ }
206
+
207
+ // 根据类型和提供的值来操作对应栏目
208
+ switch (type) {
209
+ case "primary":
210
+ this.primaryCategory = value;
211
+ console.log(`✅ 一级栏目已设置为:${value}`);
212
+ break;
213
+ case "secondary":
214
+ this.secondaryCategory = value;
215
+ console.log(`✅ 二级栏目已设置为:${value}`);
216
+ break;
217
+ case "both":
218
+ if (typeof value === "object" && value !== null) {
219
+ // 如果 value 是对象,分别设置两个栏目
220
+ const primaryValue = value.primary !== undefined ? value.primary : "";
221
+ const secondaryValue = value.secondary !== undefined ? value.secondary : "";
222
+ this.primaryCategory = primaryValue;
223
+ this.secondaryCategory = secondaryValue;
224
+ console.log(`✅ 一级栏目已设置为:${primaryValue},二级栏目已设置为:${secondaryValue}`);
225
+ } else {
226
+ // 如果 value 不是对象,使用相同的值
227
+ this.primaryCategory = value;
228
+ this.secondaryCategory = value;
229
+ console.log(`✅ 一级栏目和二级栏目均已设置为:${value}`);
230
+ }
231
+ break;
232
+ }
233
+ }
234
+
235
+ /**
236
+ * 更新网络状态缓存
237
+ * @private
238
+ */
239
+ static _updateNetworkType() {
240
+ return new Promise((resolve) => {
241
+ uni.getNetworkType({
242
+ success: (res) => {
243
+ this.networkType = res.networkType || "";
244
+ resolve();
245
+ },
246
+ fail: (err) => {
247
+ console.error("❌ 获取网络状态失败(API调用失败):", err);
248
+ this.networkType = "";
249
+ resolve();
250
+ },
251
+ });
252
+ });
253
+ }
254
+ /**
255
+ * 获取基本信息(小程序,操作系统,品牌)
256
+ */
257
+ static async getBaseInf() {
258
+ try {
259
+ // 从 小程序缓存 中提取可能存在的系统信息
260
+ let systemInfo = this.storageDataFromMiniPro?.systemInfo?.data;
261
+
262
+ // 如果 storageName 中没有系统信息,使用独立缓存
263
+ if (!systemInfo) {
264
+ const systemInfoCacheKey = "track_system_info";
265
+ if (
266
+ typeof uni === "object" &&
267
+ uni !== null &&
268
+ typeof uni.getStorageSync === "function"
269
+ ) {
270
+ systemInfo = uni.getStorageSync(systemInfoCacheKey);
271
+ }
272
+
273
+ // 如果独立缓存也没有,获取新数据
274
+ if (
275
+ !systemInfo &&
276
+ typeof uni === "object" &&
277
+ uni !== null &&
278
+ typeof uni.getSystemInfo === "function"
279
+ ) {
280
+ const freshSystemInfo = await uni.getSystemInfo();
281
+ systemInfo = {
282
+ system: freshSystemInfo.system || "",
283
+ uniPlatform: freshSystemInfo.uniPlatform || "",
284
+ deviceBrand: freshSystemInfo.deviceBrand || "",
285
+ deviceType: freshSystemInfo.deviceType || "",
286
+ };
287
+
288
+ // 存储到独立缓存中
289
+ if (typeof uni.setStorageSync === "function") {
290
+ uni.setStorageSync(systemInfoCacheKey, systemInfo);
291
+ }
292
+ }
293
+ }
294
+
295
+ // 获取小程序信息
296
+ const miniProgramInfo = this._getMiniProgramInfo(systemInfo?.uniPlatform);
297
+ let obj = { ...systemInfo, ...miniProgramInfo };
298
+ // 返回完整的基础信息
299
+ return obj;
300
+ } catch (error) {
301
+ console.error("❌ 获取基础信息失败:", error);
302
+ return {
303
+ system: "",
304
+ uniPlatform: "",
305
+ deviceBrand: "",
306
+ platform: "",
307
+ appId: "",
308
+ };
309
+ }
310
+ }
311
+
312
+ /**
313
+ * 获取小程序信息(私有方法)
314
+ * @private
315
+ */
316
+ static _getMiniProgramInfo(platform) {
317
+ let appId = "";
318
+ let version = "";
319
+ let envVersion = "";
320
+ try {
321
+ if (platform === "mp-weixin") {
322
+ const info = wx.getAccountInfoSync();
323
+ appId = info.miniProgram?.appId || "";
324
+ version = info.miniProgram?.version || "";
325
+ envVersion = info.miniProgram?.envVersion || "";
326
+ } else if (platform === "mp-douyin") {
327
+ const info = tt.getEnvInfoSync();
328
+ appId = info.microapp?.appId || "";
329
+ version = info.microapp?.mpVersion || "";
330
+ envVersion = info.microapp?.envType || "";
331
+ }
332
+ } catch (error) {
333
+ console.warn("获取平台小程序信息失败:", error);
334
+ }
335
+ let obj = {
336
+ appId,
337
+ version,
338
+ envVersion,
339
+ };
340
+ return obj;
341
+ }
342
+
343
+ /**
344
+ * 获取用户信息(包括用户ID和手机号)
345
+ */
346
+ static getUserInfo() {
347
+ try {
348
+ // 从 小程序缓存 中提取用户信息
349
+ const userInfo = this.storageDataFromMiniPro?.userInfoDeatil?.data;
350
+ if (userInfo) {
351
+ const userId = userInfo.id || ""; // 用户ID
352
+ const userPhone = userInfo.mobile || ""; // 用户手机号
353
+
354
+ return {
355
+ userId: userId,
356
+ userPhone: userPhone,
357
+ };
358
+ } else {
359
+ return {
360
+ userId: "",
361
+ userPhone: "",
362
+ };
363
+ }
364
+ } catch (err) {
365
+ console.error("❌ 获取用户信息失败:", err);
366
+ return {
367
+ userId: "",
368
+ userPhone: "",
369
+ };
370
+ }
371
+ }
372
+ /**
373
+ * 设置用户信息(包括用户ID和手机号)
374
+ * @param {Object} userInfo 用户信息对象,包含 userId 和 userPhone 字段
375
+ */
376
+ static setUserInfo(userInfo) {
377
+ if (!userInfo || !userInfo.userId) {
378
+ console.error("❌ 设置用户信息失败:缺少必要的 userId 字段");
379
+ return;
380
+ }
381
+
382
+ try {
383
+ // 更新缓存的用户信息
384
+ this.cachedUserInfo = {
385
+ userId: userInfo.userId || "",
386
+ userPhone: userInfo.userPhone || "",
387
+ };
388
+ console.log("✅ 用户信息设置成功");
389
+ } catch (err) {
390
+ console.error("❌ 设置用户信息失败:", err);
391
+ }
392
+ }
393
+
394
+ /**
395
+ * 清除用户信息(登出时调用)
396
+ */
397
+ static clearUserInfo() {
398
+ try {
399
+ // 清除缓存的用户信息
400
+ this.cachedUserInfo = {};
401
+ console.log("✅ 用户信息已清除");
402
+ } catch (err) {
403
+ console.error("❌ 清除用户信息失败:", err);
404
+ }
405
+ }
406
+ /**
407
+ * 开启埋点功能
408
+ */
409
+ static enable() {
410
+ this.config.enabled = true;
411
+ console.log("✅ 埋点功能已开启");
412
+ }
413
+
414
+ /**
415
+ * 关闭埋点功能
416
+ */
417
+ static disable() {
418
+ this.config.enabled = false;
419
+ console.log("✅ 埋点功能已关闭");
420
+ }
421
+ /**
422
+ * 获取启动时的参数
423
+ */
424
+ static getLaunchOptionsSync() {
425
+ try {
426
+ if (
427
+ typeof uni === "object" &&
428
+ uni !== null &&
429
+ typeof uni.getLaunchOptionsSync === "function"
430
+ ) {
431
+ const { path, scene, query } = uni.getLaunchOptionsSync() || {};
432
+ return {
433
+ launchPath: path,
434
+ launchScene: scene,
435
+ launchParams: JSON.stringify(query),
436
+ subChannel: query.subChannel || ""
437
+ };
438
+ } else {
439
+ return {};
440
+ }
441
+ } catch (err) {
442
+ console.error("❌ 获取启动参数失败:", err);
443
+ return {};
444
+ }
445
+ }
446
+
447
+ /**
448
+ * 单个埋点上报
449
+ * @param {Object} trackData 埋点数据
450
+ */
451
+ static submitTrackLog(trackData) {
452
+ reportLog(this.config.reportUrl, trackData)
453
+ .then((res) => {
454
+ if (res && res.statusCode === 200) {
455
+ console.log(`✅ 埋点上报成功`);
456
+ } else {
457
+ console.warn("⚠️ 埋点上报返回异常:", res.data);
458
+ }
459
+ })
460
+ .catch((err) => {
461
+ console.error("❌ 埋点上报失败(网络/接口异常):", err);
462
+ });
463
+ }
464
+ /**
465
+ * 统一埋点数据上报(核心方法)
466
+ * @param {Object} trackData 业务埋点数据
467
+ */
468
+ static report(trackData) {
469
+ // 补充全局统一字段
470
+ const completeTrackData = {
471
+ launchTime: this.launchTime,
472
+ currentTime: Date.now(),
473
+ ...this.launchOptions,
474
+ ...this.MiniProBaseInfo,
475
+ ...this.cachedUserInfo,
476
+ networkType: this.networkType, //网络状态
477
+ ...trackData, //自定义埋点数据
478
+ };
479
+
480
+ // 上报到后端接口
481
+ console.warn("埋点数据", completeTrackData);
482
+ this.submitTrackLog(completeTrackData);
483
+ }
484
+ /*
485
+ * 自定义埋点上报
486
+ *action 埋点行为名称
487
+ *data 行为携带参数
488
+ *name 埋点类型
489
+ *needColumn 是否需要栏目信息
490
+ */
491
+ static customReport(
492
+ action,
493
+ data = {},
494
+ name = "clickEvent",
495
+ needColumn = true
496
+ ) {
497
+ // 检查埋点开关是否开启
498
+ if (!this.config.enabled) {
499
+ return;
500
+ }
501
+ // 校验必要参数
502
+ if (!action || !name) {
503
+ console.error("❌ 埋点上报失败:缺少必要的 name 或者 action 字段");
504
+ return;
505
+ }
506
+
507
+ // 构建基础 trackData
508
+ const trackData = {
509
+ action,
510
+ name,
511
+ };
512
+
513
+ // 根据 needColumn 决定是否添加栏目信息
514
+ if (needColumn) {
515
+ const categoryFields = {
516
+ ...(this.primaryCategory && { primaryCategory: this.primaryCategory }),
517
+ ...(this.secondaryCategory && { secondaryCategory: this.secondaryCategory }),
518
+ };
519
+ Object.assign(trackData, categoryFields);
520
+ }
521
+
522
+ // 如果 data 存在且不为空,则添加到 trackData 中
523
+ if (data && Object.keys(data).length > 0) {
524
+ trackData.data = JSON.stringify(data);
525
+ }
526
+ // 上报数据
527
+ this.report(trackData);
528
+ }
529
+ }
530
+ // 导出 Track 类
531
+ export default Track;
@@ -0,0 +1,93 @@
1
+ /**
2
+ * 全局页面埋点混入
3
+ */
4
+ import Track from './trackCore.js';
5
+ import { getCurrentPages, getPagePath } from '../utils/common.js';
6
+
7
+ // 定义页面埋点混入
8
+ export const trackPageMixin = {
9
+ /**
10
+ * UniApp 页面生命周期 - onLoad
11
+ * 初始化页面埋点数据缓存
12
+ */
13
+ onLoad(options) {
14
+ // 使用公共方法获取页面栈
15
+ const pages = getCurrentPages();
16
+ const currentPage = pages[pages.length - 1] || {};
17
+
18
+ // 使用公共方法获取页面路径
19
+ const pagePath = getPagePath(currentPage, this.$scope);
20
+
21
+ // 更新全局当前页面路径
22
+ Track.currentPagePath = pagePath || '';
23
+
24
+ // 页面埋点数据缓存对象
25
+ const pageTrackData = {
26
+ trackType: 'pageview',
27
+ viewPagePath: pagePath || '',
28
+ viewPageLoadTime: Date.now(), // 记录页面首次加载的时间
29
+ viewPageParams: options || {} // 页面参数
30
+ };
31
+
32
+ this.$pageTrackCache = pageTrackData;
33
+ },
34
+
35
+ /**
36
+ * UniApp 页面生命周期 - onUnload
37
+ */
38
+ onUnload() {
39
+ this._reportPageTrack('unload');
40
+ },
41
+
42
+ /**
43
+ * UniApp 页面生命周期 - onHide
44
+ */
45
+ onHide() {
46
+ this._reportPageTrack('hide');
47
+ },
48
+
49
+ /**
50
+ * UniApp 页面生命周期 - onShow
51
+ */
52
+ onShow() {
53
+ // 如果页面从隐藏状态切回,更新开始时间
54
+ if (this.$pageTrackCache) {
55
+ // 记录页面重新显示的时间 记录页面最近一次显示的时间
56
+ this.$pageTrackCache.lastShowTime = Date.now();
57
+ }
58
+
59
+ // 更新全局当前页面路径
60
+ if (this.$pageTrackCache && this.$pageTrackCache.viewPagePath) {
61
+ Track.currentPagePath = this.$pageTrackCache.viewPagePath;
62
+ }
63
+ },
64
+
65
+ // 修复原语法错误:methods是混入的一级属性,与onLoad/onShow同级
66
+ methods: {
67
+ /**
68
+ * 页面埋点统一上报方法
69
+ * @param {string} reportType 上报类型:'unload' 或 'hide'
70
+ */
71
+ _reportPageTrack(reportType) {
72
+ if (!this.$pageTrackCache) return;
73
+ // 计算页面停留时间
74
+ const startTime = this.$pageTrackCache.lastShowTime || this.$pageTrackCache.viewPageLoadTime;
75
+ const stayTime = Date.now() - startTime;
76
+
77
+ // 重新组装数据结构
78
+ const customParams = {
79
+ viewPagePath: this.$pageTrackCache.viewPagePath || "",
80
+ viewPageLoadTime: this.$pageTrackCache.viewPageLoadTime || Date.now(),
81
+ viewPageParams: this.$pageTrackCache.viewPageParams || {},
82
+ stayTime: stayTime
83
+ };
84
+
85
+ Track.customReport('页面曝光', customParams, "pageviewEvent", false);
86
+
87
+ // 清除缓存:hide时清空(切页/退后台),unload时保留至上报完成(页面销毁)
88
+ if (reportType === 'hide') {
89
+ this.$pageTrackCache = null;
90
+ }
91
+ }
92
+ }
93
+ };