gio-miniprogram-sdk 4.4.0 → 4.4.2

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.
@@ -5,93 +5,200 @@
5
5
  import { UNIAPP_FUNC_REG } from '@@/constants/regex';
6
6
  import { GrowingIOType } from '@@/types/growingIO';
7
7
 
8
- const ONCE = '~';
9
- const CUSTOM = '^';
10
- let ut;
8
+ const ONCE = '~'; // 用于标记一次性事件
9
+ const CUSTOM = '^'; // 用于标记自定义事件
10
+
11
+ let ut: any;
12
+
11
13
  class GioUniAppAdapter {
12
- public pluginVersion: string;
13
- private uniVue: any;
14
- private exposedNames: any;
14
+ public pluginVersion: string; // 插件版本号
15
+ private uniVue: any; // UniApp框架的Vue实例
16
+ private exposedNames: Record<string, string>; // 存储<script setup>中暴露的方法名映射
17
+
15
18
  constructor(public growingIO: GrowingIOType) {
16
- this.pluginVersion = '__PLUGIN_VERSION__';
17
- ut = this.growingIO.utils;
18
- this.uniVue = this.growingIO?.vdsConfig?.uniVue;
19
- this.exposedNames = {};
19
+ this.pluginVersion = '__PLUGIN_VERSION__'; // 插件版本占位符,会在构建时替换
20
+ ut = this.growingIO.utils; // 初始化工具类
21
+ this.uniVue = this.growingIO?.vdsConfig?.uniVue; // 获取UniApp的Vue实例
22
+ this.exposedNames = {}; // 初始化方法名映射对象
20
23
  }
21
24
 
22
- // 主入口,版本判断
23
- main = () => {
24
- // 插件挂载时可能还没取到
25
+ /**
26
+ * 主入口,根据Vue版本进行不同处理
27
+ * 针对Vue2和Vue3采用不同的代理策略
28
+ */
29
+ main = (): void => {
30
+ // 插件挂载时可能还没取到Vue实例
25
31
  if (!this.uniVue) {
26
32
  this.uniVue = this.growingIO?.vdsConfig?.uniVue;
27
33
  }
28
34
  console.log('Vue version: ', this.uniVue.version);
35
+
36
+ // 提取主版本号
29
37
  const mainVersion = Number(ut.head(ut.split(this.uniVue.version, '.')));
30
38
  switch (mainVersion) {
31
39
  case 2:
32
- this.growingIO.dataStore.eventHooks.nativeGrowing();
40
+ this.uniVue2Proxy();
33
41
  break;
34
42
  case 3:
35
43
  this.uniVue3Proxy(this.uniVue);
36
44
  break;
37
45
  default:
46
+ // 不支持的版本
38
47
  ut.consoleText('不支持的Vue版本,请使用 Vue2 或 Vue3!', 'error');
39
48
  break;
40
49
  }
41
50
  };
42
51
 
43
- // 获取自定义真实的方法名
44
- getHandlerName = (eventName: string, e: any) => {
52
+ /**
53
+ * 获取自定义真实的方法名
54
+ * 用于将框架编译后的方法名映射回用户定义的原始方法名
55
+ * @param eventName 事件名称
56
+ * @param e 事件对象
57
+ * @returns 真实的方法名
58
+ */
59
+ getHandlerName = (eventName: string, e: any): string => {
45
60
  if (eventName === '__e') {
46
- let methodName = '__e';
47
- const { type, currentTarget, target } = e;
48
- const dataSet = currentTarget?.dataset || target?.dataset;
49
- if (dataSet) {
50
- const eventOpts = dataSet.eventOpts || dataSet['event-opts'];
51
- const matchedEventType = (eventType: string, optType: string) =>
52
- eventType === optType ||
53
- (optType === 'regionchange' &&
54
- (eventType === 'begin' || eventType === 'end'));
55
- if (eventOpts) {
56
- eventOpts.forEach((o: any[]) => {
57
- let eType = ut.head(o);
58
- const isCustom = eType.charAt(0) === CUSTOM;
59
- eType = isCustom ? eType.slice(1) : type;
60
- const isOnce = eType.charAt(0) === ONCE;
61
- eType = isOnce ? eType.slice(1) : type;
62
- const eArray = ut.last(o);
63
- if (matchedEventType(type, eType)) {
64
- methodName = ut.head(ut.head(eArray));
65
- }
66
- });
67
- }
68
- }
69
- return methodName;
61
+ // 内置事件,需要从事件对象提取真实方法名
62
+ return this.getEventHandlerNameFromE(e);
70
63
  } else if (!ut.isEmpty(this.exposedNames)) {
64
+ // 从exposedNames中查找映射关系
71
65
  return this.exposedNames[eventName] ?? eventName;
72
66
  } else {
67
+ // 无需映射,直接返回
73
68
  return eventName;
74
69
  }
75
70
  };
76
71
 
77
- // vue3代理
78
- uniVue3Proxy = (vue: any) => {
72
+ /**
73
+ * 从事件对象中获取处理函数的真实名称
74
+ * 处理UniApp特有的事件机制,提取事件处理函数的原始名称
75
+ * @param e 事件对象
76
+ * @returns 处理函数的真实名称
77
+ */
78
+ private getEventHandlerNameFromE = (e: any): string => {
79
+ let methodName = '__e'; // 默认方法名
80
+ const { type, currentTarget, target } = e;
81
+ const dataSet = currentTarget?.dataset || target?.dataset;
82
+
83
+ // 无数据集时直接返回
84
+ if (!dataSet) {
85
+ return methodName;
86
+ }
87
+
88
+ // 获取事件选项
89
+ const eventOpts = dataSet.eventOpts || dataSet['event-opts'];
90
+ if (!eventOpts) {
91
+ return methodName;
92
+ }
93
+
94
+ // 事件类型匹配函数
95
+ // 处理regionchange等特殊事件类型
96
+ const matchedEventType = (eventType: string, optType: string) =>
97
+ eventType === optType ||
98
+ (optType === 'regionchange' &&
99
+ (eventType === 'begin' || eventType === 'end'));
100
+
101
+ // 遍历事件选项查找匹配的方法名
102
+ eventOpts.forEach((o: any[]) => {
103
+ let eType = ut.head(o); // 获取事件类型
104
+ const isCustom = eType.charAt(0) === CUSTOM;
105
+ eType = isCustom ? eType.slice(1) : type; // 处理自定义事件
106
+ const isOnce = eType.charAt(0) === ONCE;
107
+ eType = isOnce ? eType.slice(1) : type; // 处理一次性事件
108
+ const eArray = ut.last(o); // 获取事件处理数组
109
+
110
+ // 找到匹配的事件类型
111
+ if (matchedEventType(type, eType)) {
112
+ methodName = ut.head(ut.head(eArray)); // 提取方法名
113
+ }
114
+ });
115
+
116
+ return methodName;
117
+ };
118
+
119
+ /**
120
+ * Vue2专用代理实现
121
+ * 处理Vue2下的特殊情况,主要是生命周期和事件处理
122
+ * Vue2的生命周期有时会异步执行,需要特殊处理
123
+ */
124
+ uniVue2Proxy = (): void => {
125
+ const {
126
+ dataStore: { eventHooks }
127
+ } = this.growingIO;
128
+ // 重写生命周期回调,vue2中会被处理成异步方法导致时序错位
129
+ this.overrideLifeFcEffectsFn(eventHooks);
130
+ // 初始化原生钩子
131
+ eventHooks.nativeGrowing();
132
+ };
133
+
134
+ /**
135
+ * 重写生命周期效果函数
136
+ * 解决Vue2中页面生命周期异步执行导致的问题
137
+ * 对Page类型的生命周期使用setTimeout延迟执行,保证执行顺序正确
138
+ * @param eventHooks 事件钩子对象
139
+ */
140
+ private overrideLifeFcEffectsFn = (eventHooks: any): void => {
141
+ const originFunc = eventHooks.lifeFcEffectsFn;
142
+ eventHooks.lifeFcEffectsFn = function (...args) {
143
+ // 对Page类型的生命周期特殊处理
144
+ if (args[2] === 'Page') {
145
+ // 使用setTimeout延迟执行,保证在正确的时机执行
146
+ setTimeout(() => {
147
+ return originFunc.apply(this, args);
148
+ }, 0);
149
+ } else {
150
+ // 非Page类型直接执行原函数
151
+ return originFunc.apply(this, args);
152
+ }
153
+ };
154
+ };
155
+
156
+ /**
157
+ * Vue3代理实现
158
+ * 为Vue3框架添加GrowingIO的代理逻辑
159
+ * @param vue Vue3实例
160
+ */
161
+ uniVue3Proxy = (vue: any): void => {
79
162
  const {
80
163
  dataStore: { eventHooks },
81
164
  minipInstance
82
165
  } = this.growingIO;
166
+
83
167
  // 重写对象遍历逻辑,优先hook methods最后才是原生
168
+ this.overrideObjectTraverse(eventHooks);
169
+
170
+ // 初始化原生钩子
171
+ eventHooks.nativeGrowing();
172
+
173
+ // 重写getPageTitle方法,确保在Vue组件中能正确获取页面标题
174
+ this.overrideGetPageTitle(minipInstance);
175
+
176
+ // 添加Vue混入,实现组件方法的代理
177
+ this.addVueMixin(vue, eventHooks);
178
+ };
179
+
180
+ /**
181
+ * 重写对象遍历逻辑
182
+ * 确保按照正确的优先级遍历对象属性
183
+ * @param eventHooks 事件钩子对象
184
+ */
185
+ private overrideObjectTraverse = (eventHooks: any): void => {
84
186
  eventHooks.objectTraverse = (target: any, fn: any) => {
187
+ // 先处理methods,因为这里包含了大部分自定义方法
85
188
  if (ut.has(target, 'methods')) {
86
189
  Object.getOwnPropertyNames(target.methods).forEach((k: string) => {
87
190
  fn(k, target.methods);
88
191
  });
89
192
  }
193
+
194
+ // 处理lifetimes,组件生命周期方法
90
195
  if (ut.has(target, 'lifetimes')) {
91
196
  Object.getOwnPropertyNames(target.lifetimes).forEach((k: string) => {
92
197
  fn(k, target.lifetimes);
93
198
  });
94
199
  }
200
+
201
+ // 处理pageLifetimes,页面生命周期方法
95
202
  if (ut.has(target, 'pageLifetimes')) {
96
203
  Object.getOwnPropertyNames(target.pageLifetimes).forEach(
97
204
  (k: string) => {
@@ -99,6 +206,8 @@ class GioUniAppAdapter {
99
206
  }
100
207
  );
101
208
  }
209
+
210
+ // 最后处理其他属性,确保不重复处理
102
211
  Object.getOwnPropertyNames(target).forEach((k: string) => {
103
212
  if (
104
213
  !ut.niceTry(
@@ -112,106 +221,208 @@ class GioUniAppAdapter {
112
221
  }
113
222
  });
114
223
  };
115
- eventHooks.nativeGrowing();
116
- // 需要单独处理getPageTitle,防止在vue中onShow里拿的是错误的
117
- const originGetter = minipInstance.getPageTitle;
224
+ };
225
+
226
+ /**
227
+ * 重写getPageTitle方法
228
+ * 确保能正确获取Vue组件中定义的页面标题
229
+ * @param minipInstance 小程序实例
230
+ */
231
+ private overrideGetPageTitle = (minipInstance: any): void => {
232
+ const originGetter = minipInstance.getPageTitle; // 保存原始getter
118
233
  minipInstance.getPageTitle = function (...args) {
234
+ // 获取当前页面实例
119
235
  let pageInstance = !ut.isEmpty(args[0])
120
236
  ? args[0]
121
237
  : minipInstance.getCurrentPage();
238
+
239
+ // 检查是否为页面组件且有路由信息
122
240
  if (
123
241
  pageInstance?.$vm?.mpType === 'page' &&
124
242
  pageInstance?.$vm?.__route__
125
243
  ) {
244
+ // 优先使用组件中定义的gioPageTitle
126
245
  const vmTitle = pageInstance?.$vm?.gioPageTitle;
127
246
  return vmTitle ? vmTitle : originGetter.apply(this, args);
128
247
  } else {
248
+ // 回退到原始实现
129
249
  return originGetter.apply(this, args);
130
250
  }
131
251
  };
252
+ };
253
+
254
+ /**
255
+ * 添加Vue混入
256
+ * 注入GrowingIO的代理逻辑到Vue组件生命周期中
257
+ * @param vue Vue实例
258
+ * @param eventHooks 事件钩子对象
259
+ */
260
+ private addVueMixin = (vue: any, eventHooks: any): void => {
132
261
  const self = this;
262
+
133
263
  vue.mixin({
264
+ // 组件挂载前的钩子
134
265
  beforeMount() {
135
- const proxyMethods = (name: string, method: any) => {
136
- if (
137
- ut.isFunction(method) &&
138
- !eventHooks.pageHandlers.includes(name)
139
- ) {
140
- return function (...args) {
141
- return eventHooks.customFcEffects(name, method).apply(this, args);
142
- };
143
- } else {
144
- return method;
145
- }
266
+ // 代理普通方法
267
+ self.proxyMethods(this, eventHooks);
268
+
269
+ // 代理exposed方法(script setup中暴露的方法)
270
+ self.proxyExposedMethods(this, eventHooks);
271
+
272
+ // 使用emit监听组件事件hook
273
+ self.proxyEmit(this, eventHooks);
274
+ },
275
+
276
+ // 组件更新时的钩子
277
+ updated() {
278
+ // 更新时重新代理exposed方法,确保动态添加的方法也被代理
279
+ self.proxyExposedMethods(this, eventHooks);
280
+ }
281
+ });
282
+ };
283
+
284
+ /**
285
+ * 代理普通方法
286
+ * 处理传统Vue选项式API和组合式API中定义的方法
287
+ * @param instance Vue组件实例
288
+ * @param eventHooks 事件钩子对象
289
+ */
290
+ private proxyMethods = (instance: any, eventHooks: any): void => {
291
+ // 代理单个方法的函数
292
+ const proxyMethodFn = (name: string, method: any) => {
293
+ // 只代理非页面生命周期的函数方法
294
+ if (ut.isFunction(method) && !eventHooks.pageHandlers.includes(name)) {
295
+ return function (...args) {
296
+ // 使用customFcEffects包装方法,实现事件追踪
297
+ return eventHooks.customFcEffects(name, method).apply(this, args);
146
298
  };
147
- // hook逻辑
148
- const transferMethods = (origin, target) =>
149
- ut.keys(origin).forEach((k: string) => {
150
- if (ut.isFunction(target[k])) {
151
- Object.defineProperty(target, k, {
152
- value: proxyMethods(k, target[k]),
153
- configurable: true,
154
- writable: true
155
- });
156
- }
157
- });
158
- // 对传统普通事件hook,如果methods存在,说明有传统写法定义自定义的方法
159
- const methods = this.$options.methods;
160
- if (methods) {
161
- transferMethods(methods, this);
162
- }
163
- // 对setup中定义的事件hook
164
- const setupState = this?._?.setupState;
165
- if (setupState) {
166
- transferMethods(setupState, setupState);
167
- }
168
- // 对<script setup>写法的事件hook,编译时会匿名,需要配合expose
169
- const exposed = this?._?.exposed;
170
- self.exposedNames = {};
171
- if (exposed) {
172
- // 保存exposed供<script setup>写法的事件查找真实的事件名
173
- const exposedKeys = ut.keys(exposed);
174
- exposedKeys.forEach((k) => {
175
- if (ut.isFunction(exposed[k])) {
176
- self.exposedNames[exposed[k].name] = k;
177
- }
178
- });
179
- // 只能取到编译后的名称,需要从exposedNames中映射
180
- const methodKeys = ut
181
- .keys(this.$scope)
182
- .filter(
183
- (k) => UNIAPP_FUNC_REG.test(k) && ut.isFunction(this.$scope[k])
184
- );
185
- methodKeys.forEach((k, i) => {
186
- const originFunc = this.$scope[k].value;
187
- const customName = self.splicingMethodName(
188
- originFunc.name,
189
- k,
190
- exposedKeys[i]
191
- );
192
- this.$scope[k].value = eventHooks.customFcEffects(
193
- customName,
194
- originFunc
195
- );
299
+ } else {
300
+ return method;
301
+ }
302
+ };
303
+
304
+ // 对象属性转移函数,将源对象的方法代理后赋值给目标对象
305
+ const transferMethods = (origin, target) =>
306
+ ut.keys(origin).forEach((k: string) => {
307
+ if (ut.isFunction(target[k])) {
308
+ Object.defineProperty(target, k, {
309
+ value: proxyMethodFn(k, target[k]),
310
+ configurable: true,
311
+ writable: true
196
312
  });
197
313
  }
198
- // 使用emit监听组件事件hook
199
- const tEmit = this._.emit;
200
- this._.emit = function (...args) {
201
- (() =>
202
- eventHooks.customFcEffects(args[0], () => {}).apply(this, args))();
203
- return tEmit.apply(this, args);
204
- };
314
+ });
315
+
316
+ // 处理传统选项式API中的methods
317
+ const methods = instance.$options.methods;
318
+ if (methods) {
319
+ transferMethods(methods, instance);
320
+ }
321
+
322
+ // 处理组合式API中的setup返回值
323
+ const setupState = instance?._?.setupState;
324
+ if (setupState) {
325
+ transferMethods(setupState, setupState);
326
+ }
327
+ };
328
+
329
+ /**
330
+ * 代理exposed方法
331
+ * 处理<script setup>中使用defineExpose暴露的方法
332
+ * @param instance Vue组件实例
333
+ * @param eventHooks 事件钩子对象
334
+ */
335
+ private proxyExposedMethods = (instance: any, eventHooks: any): void => {
336
+ // 获取组件暴露的方法
337
+ const exposed = instance?._?.exposed;
338
+ this.exposedNames = {}; // 重置映射
339
+
340
+ // 无exposed时直接返回
341
+ if (!exposed) {
342
+ return;
343
+ }
344
+
345
+ // 保存exposed供<script setup>写法的事件查找真实的事件名
346
+ const exposedKeys = ut.keys(exposed);
347
+ exposedKeys.forEach((k) => {
348
+ if (ut.isFunction(exposed[k])) {
349
+ // 建立函数名到暴露属性名的映射
350
+ this.exposedNames[exposed[k].name] = k;
205
351
  }
206
352
  });
353
+
354
+ // 获取编译后的方法列表
355
+ // 使用正则匹配UniApp编译生成的方法名
356
+ const methodKeys = ut
357
+ .keys(instance.$scope)
358
+ .filter(
359
+ (k) => UNIAPP_FUNC_REG.test(k) && ut.isFunction(instance.$scope[k])
360
+ );
361
+
362
+ // 遍历方法进行代理
363
+ methodKeys.forEach((k, i) => {
364
+ const originFunc = instance.$scope[k].value;
365
+
366
+ // 在updated中避免重复hook,通过_GIO_HOOKED_标记判断
367
+ if (instance._.updated && originFunc._GIO_HOOKED_) {
368
+ return;
369
+ }
370
+
371
+ // 获取自定义方法名
372
+ const customName = this.splicingMethodName(
373
+ originFunc.name,
374
+ k,
375
+ exposedKeys[i]
376
+ );
377
+
378
+ // 使用customFcEffects包装方法
379
+ instance.$scope[k].value = eventHooks.customFcEffects(
380
+ customName,
381
+ originFunc
382
+ );
383
+
384
+ // 标记已hook过的方法,避免重复处理
385
+ instance.$scope[k].value._GIO_HOOKED_ = true;
386
+ });
387
+ };
388
+
389
+ /**
390
+ * 代理emit方法
391
+ * 监听组件通过emit触发的事件
392
+ * @param instance Vue组件实例
393
+ * @param eventHooks 事件钩子对象
394
+ */
395
+ private proxyEmit = (instance: any, eventHooks: any): void => {
396
+ const tEmit = instance._.emit; // 保存原始emit方法
397
+ instance._.emit = function (...args) {
398
+ // 使用自执行函数触发事件追踪,传入空函数作为handler
399
+ (() => eventHooks.customFcEffects(args[0], () => {}).apply(this, args))();
400
+ // 调用原始emit方法
401
+ return tEmit.apply(this, args);
402
+ };
207
403
  };
208
404
 
209
- splicingMethodName = (originName, traversalName, exposedName) => {
405
+ /**
406
+ * 拼接方法名称
407
+ * 根据不同情况组合出最终的方法名
408
+ * @param originName 原始名称
409
+ * @param traversalName 遍历得到的名称
410
+ * @param exposedName 暴露的名称
411
+ * @returns 最终使用的方法名
412
+ */
413
+ splicingMethodName = (
414
+ originName: string,
415
+ traversalName: string,
416
+ exposedName: string
417
+ ): string => {
210
418
  if (originName) {
419
+ // 优先使用原始名称
211
420
  return originName;
212
421
  } else if (exposedName) {
422
+ // 其次使用遍历名称和暴露名称的组合
213
423
  return `${traversalName}#${exposedName}`;
214
424
  } else {
425
+ // 最后使用遍历名称
215
426
  return traversalName;
216
427
  }
217
428
  };
@@ -1,7 +1,7 @@
1
1
  import { AppHookLifeCircle } from '@@/types/eventHooks';
2
2
  import { GrowingIOType } from '@@/types/growingIO';
3
- import { isEmpty, typeOf } from '@@/utils/glodash';
4
- import { getLaunchQuery, qsStringify } from '@@/utils/tools';
3
+ import { isEmpty, typeOf, unset } from '@@/utils/glodash';
4
+ import { getPureParams, qsStringify } from '@@/utils/tools';
5
5
  import EMIT_MSG from '@@/constants/emitMsg';
6
6
 
7
7
  class AppEffects {
@@ -118,18 +118,20 @@ class AppEffects {
118
118
  gioPlatform !== 'quickapp' &&
119
119
  minipInstance.minip.canIUse('getEnterOptionsSync')
120
120
  ) {
121
- const { path, query, scene, referrerInfo } =
122
- minipInstance.minip.getEnterOptionsSync() || {};
121
+ const enterOptions = minipInstance.minip.getEnterOptionsSync() || {};
122
+ const { path, query, scene } = enterOptions;
123
+ const referrerInfo =
124
+ enterOptions.referrerInfo || enterOptions.refererInfo; // tt里取值是refererInfo
123
125
  rPath = path || args.path;
124
126
  rQuery = query || args.query;
125
127
  rAppId = referrerInfo?.appId || args?.referrerInfo?.appId || '';
126
128
  dataStore.scene = scene || args.scene;
129
+ // 移除某些小程序圈选时我们带入的圈选地址参数
130
+ unset(query, 'gdpCircleRoomCollectUrl');
127
131
  // 来源有额外参数时并入页面参数
128
- if (!isEmpty(referrerInfo?.extraData)) {
129
- rQuery = qsStringify(getLaunchQuery(rQuery, referrerInfo.extraData));
130
- } else {
131
- rQuery = qsStringify(rQuery);
132
- }
132
+ rQuery = qsStringify(
133
+ getPureParams(rQuery, referrerInfo?.extraData || {})
134
+ );
133
135
  } else {
134
136
  // 不支持的按老逻辑兜底
135
137
  const { path, query, referrerInfo } = args;