mp-weixin-back 0.0.10 → 0.0.12

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.
package/client.d.ts CHANGED
@@ -1,6 +1,10 @@
1
1
  declare module 'mp-weixin-back-helper' {
2
2
  type Config = {
3
- /**
3
+ /**
4
+ * 初始化时是否监听手势返回,默认值为`true`
5
+ */
6
+ initialValue: boolean
7
+ /**
4
8
  * 是否阻止默认的回退事件,默认为 false
5
9
  */
6
10
  preventDefault: boolean
@@ -10,10 +14,19 @@ declare module 'mp-weixin-back-helper' {
10
14
  frequency: number
11
15
  }
12
16
 
13
- function onPageBack(
14
- callback:() => void,
15
- params: Partial<Config>
16
- )
17
+ function onPageBack(callback: () => void, params?: Partial<Config>)
18
+
19
+ /**
20
+ * 开启监听手势滑动事件
21
+ */
22
+ function activeMpBack()
23
+
24
+ /**
25
+ * 禁用监听手势滑动事件
26
+ */
27
+ function inactiveMpBack()
17
28
 
18
29
  export default onPageBack
19
- }
30
+
31
+ export { activeMpBack, inactiveMpBack }
32
+ }
package/dist/index.cjs CHANGED
@@ -33,16 +33,32 @@ function compositionWalk(context, code, sfc, id) {
33
33
  pageBackFnName: "onPageBack",
34
34
  hasImportRef: false,
35
35
  backConfig: { ...context.config },
36
- callbackCode: ""
36
+ callbackCode: "",
37
+ activeFnName: "activeMpBack",
38
+ inActiveFnName: "inactiveMpBack"
37
39
  };
40
+ const activeFnCallsToModify = [];
41
+ const inActiveFnCallsToModify = [];
38
42
  if (setupAst) {
39
43
  astKit.walkAST(setupAst, {
40
44
  enter(node) {
41
45
  if (node.type === "ImportDeclaration") {
42
46
  if (node.source.value.includes(virtualFileId)) {
43
- const importSpecifier = node.specifiers[0];
47
+ const importDefaultSpecifiers = node.specifiers.filter(
48
+ (i) => i.type === "ImportDefaultSpecifier"
49
+ );
50
+ const importDefaultSpecifier = importDefaultSpecifiers[0];
44
51
  pageInfo.hasPageBack = true;
45
- pageInfo.pageBackFnName = importSpecifier.local.name;
52
+ pageInfo.pageBackFnName = importDefaultSpecifier.local.name;
53
+ const importSpecifiers = node.specifiers.filter((i) => i.type === "ImportSpecifier");
54
+ importSpecifiers.map((specifiers) => {
55
+ if (specifiers.imported.type === "Identifier" && specifiers.imported.name === "activeMpBack") {
56
+ pageInfo.activeFnName = specifiers.local.name;
57
+ }
58
+ if (specifiers.imported.type === "Identifier" && specifiers.imported.name === "inactiveMpBack") {
59
+ pageInfo.inActiveFnName = specifiers.local.name;
60
+ }
61
+ });
46
62
  }
47
63
  if (node.source.value === "vue") {
48
64
  node.specifiers.some((specifier) => {
@@ -74,6 +90,28 @@ function compositionWalk(context, code, sfc, id) {
74
90
  }
75
91
  }
76
92
  }
93
+ if (node.type === "ExpressionStatement" && node.expression.type === "CallExpression" && node.expression.callee.loc?.identifierName === pageInfo.activeFnName) {
94
+ activeFnCallsToModify.push({
95
+ start: node.expression.start,
96
+ end: node.expression.end,
97
+ original: sfc.scriptSetup.loc.source.substring(
98
+ node.expression.start,
99
+ node.expression.end
100
+ ),
101
+ name: pageInfo.activeFnName
102
+ });
103
+ }
104
+ if (node.type === "ExpressionStatement" && node.expression.type === "CallExpression" && node.expression.callee.loc?.identifierName === pageInfo.inActiveFnName) {
105
+ inActiveFnCallsToModify.push({
106
+ start: node.expression.start,
107
+ end: node.expression.end,
108
+ original: sfc.scriptSetup.loc.source.substring(
109
+ node.expression.start,
110
+ node.expression.end
111
+ ),
112
+ name: pageInfo.inActiveFnName
113
+ });
114
+ }
77
115
  }
78
116
  });
79
117
  }
@@ -86,9 +124,12 @@ function compositionWalk(context, code, sfc, id) {
86
124
  if (!pageInfo.backConfig.preventDefault) {
87
125
  pageInfo.callbackCode += "uni.navigateBack({ delta: 1 });";
88
126
  }
127
+ const importUseMpWeixinBack = `import { useMpWeixinBack } from '${virtualFileId}'`;
89
128
  const importRefFromVue = !pageInfo.hasImportRef ? `import { ref } from 'vue'` : "";
90
129
  const stateFrequency = "let __MP_BACK_FREQUENCY__ = 1;";
91
- const statePageContainerVar = "const __MP_BACK_SHOW_PAGE_CONTAINER__ = ref(true);";
130
+ const statePageContainerVar = `
131
+ const { __MP_BACK_SHOW_PAGE_CONTAINER__, __MP_WEIXIN_ACTIVEBACK__, __MP_WEIXIN_INACTIVEBACK__ } = useMpWeixinBack(${pageInfo.backConfig.initialValue})
132
+ `;
92
133
  const configBack = (() => {
93
134
  const onPageBack = pageInfo.backConfig.onPageBack;
94
135
  if (!onPageBack)
@@ -104,6 +145,9 @@ function compositionWalk(context, code, sfc, id) {
104
145
  })();
105
146
  const stateBeforeLeave = `
106
147
  const onBeforeLeave = () => {
148
+ if (!__MP_BACK_SHOW_PAGE_CONTAINER__.value) {
149
+ return
150
+ }
107
151
  if (__MP_BACK_FREQUENCY__ < ${pageInfo.backConfig.frequency}) {
108
152
  __MP_BACK_SHOW_PAGE_CONTAINER__.value = false
109
153
  setTimeout(() => __MP_BACK_SHOW_PAGE_CONTAINER__.value = true, 0);
@@ -130,10 +174,31 @@ function compositionWalk(context, code, sfc, id) {
130
174
  const scriptMagicString = new MagicString__default(scriptOffsets.content);
131
175
  scriptMagicString.prepend(
132
176
  ` ${importRefFromVue}
177
+ ${importUseMpWeixinBack}
133
178
  ${stateFrequency}
134
179
  ${statePageContainerVar}
135
180
  ${stateBeforeLeave} `
136
181
  );
182
+ activeFnCallsToModify.forEach((call) => {
183
+ const fnCallRegex = new RegExp(`${call.name}\\(([^)]*)\\)`, "g");
184
+ const newCall = call.original.replace(fnCallRegex, (_match, args) => {
185
+ if (!args.trim()) {
186
+ return `${call.name}(__MP_WEIXIN_ACTIVEBACK__)`;
187
+ }
188
+ return `${call.name}(__MP_WEIXIN_ACTIVEBACK__, ${args})`;
189
+ });
190
+ scriptMagicString.overwrite(call.start, call.end, newCall);
191
+ });
192
+ inActiveFnCallsToModify.forEach((call) => {
193
+ const fnCallRegex = new RegExp(`${call.name}\\(([^)]*)\\)`, "g");
194
+ const newCall = call.original.replace(fnCallRegex, (_match, args) => {
195
+ if (!args.trim()) {
196
+ return `${call.name}(__MP_WEIXIN_INACTIVEBACK__)`;
197
+ }
198
+ return `${call.name}(__MP_WEIXIN_INACTIVEBACK__, ${args})`;
199
+ });
200
+ scriptMagicString.overwrite(call.start, call.end, newCall);
201
+ });
137
202
  codeMs.overwrite(scriptOffsets.start, scriptOffsets.end, scriptMagicString.toString());
138
203
  return codeMs.toString();
139
204
  }
@@ -413,6 +478,7 @@ class pageContext {
413
478
  function MpBackPlugin(userOptions = {}) {
414
479
  let context;
415
480
  const defaultOptions = {
481
+ initialValue: true,
416
482
  preventDefault: false,
417
483
  frequency: 1,
418
484
  debug: false
@@ -437,14 +503,43 @@ function MpBackPlugin(userOptions = {}) {
437
503
  return;
438
504
  }
439
505
  if (id === virtualFileId) {
440
- return `export default function onPageBack() {}`;
506
+ return `
507
+ import { ref } from 'vue'
508
+ export default function onPageBack() {}
509
+ export function activeMpBack(fn = null) {
510
+ fn?.()
511
+ }
512
+ export function inactiveMpBack(fn = null) {
513
+ fn?.()
514
+ }
515
+ export function useMpWeixinBack(initialValue = true) {
516
+ const __MP_BACK_SHOW_PAGE_CONTAINER__ = ref(initialValue)
517
+
518
+ const __MP_WEIXIN_ACTIVEBACK__ = () => {
519
+ __MP_BACK_SHOW_PAGE_CONTAINER__.value = true
520
+ }
521
+
522
+ const __MP_WEIXIN_INACTIVEBACK__ = () => {
523
+ __MP_BACK_SHOW_PAGE_CONTAINER__.value = false
524
+ }
525
+
526
+ return {
527
+ __MP_BACK_SHOW_PAGE_CONTAINER__,
528
+ __MP_WEIXIN_ACTIVEBACK__,
529
+ __MP_WEIXIN_INACTIVEBACK__
530
+ }
531
+ }
532
+ `;
441
533
  }
442
534
  },
443
535
  async transform(code, id) {
444
536
  if (id.includes("node_modules") || !id.includes(".vue")) {
445
537
  return;
446
538
  }
447
- return context.transform(code, id);
539
+ return {
540
+ code: await context.transform(code, id),
541
+ map: null
542
+ };
448
543
  }
449
544
  };
450
545
  }
package/dist/index.d.cts CHANGED
@@ -2,6 +2,10 @@ import { Plugin } from 'vite';
2
2
 
3
3
  type UserOptions = Partial<Config>;
4
4
  type Config = {
5
+ /**
6
+ * 初始化时是否监听手势返回,默认值为`true`
7
+ */
8
+ initialValue: boolean;
5
9
  /**
6
10
  * 是否阻止默认的回退事件,默认为 false
7
11
  */
package/dist/index.d.mts CHANGED
@@ -2,6 +2,10 @@ import { Plugin } from 'vite';
2
2
 
3
3
  type UserOptions = Partial<Config>;
4
4
  type Config = {
5
+ /**
6
+ * 初始化时是否监听手势返回,默认值为`true`
7
+ */
8
+ initialValue: boolean;
5
9
  /**
6
10
  * 是否阻止默认的回退事件,默认为 false
7
11
  */
package/dist/index.d.ts CHANGED
@@ -2,6 +2,10 @@ import { Plugin } from 'vite';
2
2
 
3
3
  type UserOptions = Partial<Config>;
4
4
  type Config = {
5
+ /**
6
+ * 初始化时是否监听手势返回,默认值为`true`
7
+ */
8
+ initialValue: boolean;
5
9
  /**
6
10
  * 是否阻止默认的回退事件,默认为 false
7
11
  */
package/dist/index.mjs CHANGED
@@ -23,16 +23,32 @@ function compositionWalk(context, code, sfc, id) {
23
23
  pageBackFnName: "onPageBack",
24
24
  hasImportRef: false,
25
25
  backConfig: { ...context.config },
26
- callbackCode: ""
26
+ callbackCode: "",
27
+ activeFnName: "activeMpBack",
28
+ inActiveFnName: "inactiveMpBack"
27
29
  };
30
+ const activeFnCallsToModify = [];
31
+ const inActiveFnCallsToModify = [];
28
32
  if (setupAst) {
29
33
  walkAST(setupAst, {
30
34
  enter(node) {
31
35
  if (node.type === "ImportDeclaration") {
32
36
  if (node.source.value.includes(virtualFileId)) {
33
- const importSpecifier = node.specifiers[0];
37
+ const importDefaultSpecifiers = node.specifiers.filter(
38
+ (i) => i.type === "ImportDefaultSpecifier"
39
+ );
40
+ const importDefaultSpecifier = importDefaultSpecifiers[0];
34
41
  pageInfo.hasPageBack = true;
35
- pageInfo.pageBackFnName = importSpecifier.local.name;
42
+ pageInfo.pageBackFnName = importDefaultSpecifier.local.name;
43
+ const importSpecifiers = node.specifiers.filter((i) => i.type === "ImportSpecifier");
44
+ importSpecifiers.map((specifiers) => {
45
+ if (specifiers.imported.type === "Identifier" && specifiers.imported.name === "activeMpBack") {
46
+ pageInfo.activeFnName = specifiers.local.name;
47
+ }
48
+ if (specifiers.imported.type === "Identifier" && specifiers.imported.name === "inactiveMpBack") {
49
+ pageInfo.inActiveFnName = specifiers.local.name;
50
+ }
51
+ });
36
52
  }
37
53
  if (node.source.value === "vue") {
38
54
  node.specifiers.some((specifier) => {
@@ -64,6 +80,28 @@ function compositionWalk(context, code, sfc, id) {
64
80
  }
65
81
  }
66
82
  }
83
+ if (node.type === "ExpressionStatement" && node.expression.type === "CallExpression" && node.expression.callee.loc?.identifierName === pageInfo.activeFnName) {
84
+ activeFnCallsToModify.push({
85
+ start: node.expression.start,
86
+ end: node.expression.end,
87
+ original: sfc.scriptSetup.loc.source.substring(
88
+ node.expression.start,
89
+ node.expression.end
90
+ ),
91
+ name: pageInfo.activeFnName
92
+ });
93
+ }
94
+ if (node.type === "ExpressionStatement" && node.expression.type === "CallExpression" && node.expression.callee.loc?.identifierName === pageInfo.inActiveFnName) {
95
+ inActiveFnCallsToModify.push({
96
+ start: node.expression.start,
97
+ end: node.expression.end,
98
+ original: sfc.scriptSetup.loc.source.substring(
99
+ node.expression.start,
100
+ node.expression.end
101
+ ),
102
+ name: pageInfo.inActiveFnName
103
+ });
104
+ }
67
105
  }
68
106
  });
69
107
  }
@@ -76,9 +114,12 @@ function compositionWalk(context, code, sfc, id) {
76
114
  if (!pageInfo.backConfig.preventDefault) {
77
115
  pageInfo.callbackCode += "uni.navigateBack({ delta: 1 });";
78
116
  }
117
+ const importUseMpWeixinBack = `import { useMpWeixinBack } from '${virtualFileId}'`;
79
118
  const importRefFromVue = !pageInfo.hasImportRef ? `import { ref } from 'vue'` : "";
80
119
  const stateFrequency = "let __MP_BACK_FREQUENCY__ = 1;";
81
- const statePageContainerVar = "const __MP_BACK_SHOW_PAGE_CONTAINER__ = ref(true);";
120
+ const statePageContainerVar = `
121
+ const { __MP_BACK_SHOW_PAGE_CONTAINER__, __MP_WEIXIN_ACTIVEBACK__, __MP_WEIXIN_INACTIVEBACK__ } = useMpWeixinBack(${pageInfo.backConfig.initialValue})
122
+ `;
82
123
  const configBack = (() => {
83
124
  const onPageBack = pageInfo.backConfig.onPageBack;
84
125
  if (!onPageBack)
@@ -94,6 +135,9 @@ function compositionWalk(context, code, sfc, id) {
94
135
  })();
95
136
  const stateBeforeLeave = `
96
137
  const onBeforeLeave = () => {
138
+ if (!__MP_BACK_SHOW_PAGE_CONTAINER__.value) {
139
+ return
140
+ }
97
141
  if (__MP_BACK_FREQUENCY__ < ${pageInfo.backConfig.frequency}) {
98
142
  __MP_BACK_SHOW_PAGE_CONTAINER__.value = false
99
143
  setTimeout(() => __MP_BACK_SHOW_PAGE_CONTAINER__.value = true, 0);
@@ -120,10 +164,31 @@ function compositionWalk(context, code, sfc, id) {
120
164
  const scriptMagicString = new MagicString(scriptOffsets.content);
121
165
  scriptMagicString.prepend(
122
166
  ` ${importRefFromVue}
167
+ ${importUseMpWeixinBack}
123
168
  ${stateFrequency}
124
169
  ${statePageContainerVar}
125
170
  ${stateBeforeLeave} `
126
171
  );
172
+ activeFnCallsToModify.forEach((call) => {
173
+ const fnCallRegex = new RegExp(`${call.name}\\(([^)]*)\\)`, "g");
174
+ const newCall = call.original.replace(fnCallRegex, (_match, args) => {
175
+ if (!args.trim()) {
176
+ return `${call.name}(__MP_WEIXIN_ACTIVEBACK__)`;
177
+ }
178
+ return `${call.name}(__MP_WEIXIN_ACTIVEBACK__, ${args})`;
179
+ });
180
+ scriptMagicString.overwrite(call.start, call.end, newCall);
181
+ });
182
+ inActiveFnCallsToModify.forEach((call) => {
183
+ const fnCallRegex = new RegExp(`${call.name}\\(([^)]*)\\)`, "g");
184
+ const newCall = call.original.replace(fnCallRegex, (_match, args) => {
185
+ if (!args.trim()) {
186
+ return `${call.name}(__MP_WEIXIN_INACTIVEBACK__)`;
187
+ }
188
+ return `${call.name}(__MP_WEIXIN_INACTIVEBACK__, ${args})`;
189
+ });
190
+ scriptMagicString.overwrite(call.start, call.end, newCall);
191
+ });
127
192
  codeMs.overwrite(scriptOffsets.start, scriptOffsets.end, scriptMagicString.toString());
128
193
  return codeMs.toString();
129
194
  }
@@ -403,6 +468,7 @@ class pageContext {
403
468
  function MpBackPlugin(userOptions = {}) {
404
469
  let context;
405
470
  const defaultOptions = {
471
+ initialValue: true,
406
472
  preventDefault: false,
407
473
  frequency: 1,
408
474
  debug: false
@@ -427,14 +493,43 @@ function MpBackPlugin(userOptions = {}) {
427
493
  return;
428
494
  }
429
495
  if (id === virtualFileId) {
430
- return `export default function onPageBack() {}`;
496
+ return `
497
+ import { ref } from 'vue'
498
+ export default function onPageBack() {}
499
+ export function activeMpBack(fn = null) {
500
+ fn?.()
501
+ }
502
+ export function inactiveMpBack(fn = null) {
503
+ fn?.()
504
+ }
505
+ export function useMpWeixinBack(initialValue = true) {
506
+ const __MP_BACK_SHOW_PAGE_CONTAINER__ = ref(initialValue)
507
+
508
+ const __MP_WEIXIN_ACTIVEBACK__ = () => {
509
+ __MP_BACK_SHOW_PAGE_CONTAINER__.value = true
510
+ }
511
+
512
+ const __MP_WEIXIN_INACTIVEBACK__ = () => {
513
+ __MP_BACK_SHOW_PAGE_CONTAINER__.value = false
514
+ }
515
+
516
+ return {
517
+ __MP_BACK_SHOW_PAGE_CONTAINER__,
518
+ __MP_WEIXIN_ACTIVEBACK__,
519
+ __MP_WEIXIN_INACTIVEBACK__
520
+ }
521
+ }
522
+ `;
431
523
  }
432
524
  },
433
525
  async transform(code, id) {
434
526
  if (id.includes("node_modules") || !id.includes(".vue")) {
435
527
  return;
436
528
  }
437
- return context.transform(code, id);
529
+ return {
530
+ code: await context.transform(code, id),
531
+ map: null
532
+ };
438
533
  }
439
534
  };
440
535
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mp-weixin-back",
3
3
  "type": "module",
4
- "version": "0.0.10",
4
+ "version": "0.0.12",
5
5
  "description": "监听微信小程序的手势返回和页面默认导航栏的返回",
6
6
  "main": "dist/index.cjs",
7
7
  "module": "dist/index.mjs",
package/readme.md CHANGED
@@ -1,82 +1,151 @@
1
- ### 功能描述
1
+ # mp-weixin-back
2
2
 
3
- 监听手势返回和页面默认导航栏的返回事件
3
+ ## 功能概述
4
4
 
5
- ### 在项目中使用
5
+ `mp-weixin-back` 是一个专门用于监听微信小程序`手势返回`、`导航栏返回事件`、`navigateBack`的工具库,提供灵活的配置选项和简洁的 API。
6
6
 
7
- #### 下载
7
+ ## 📦 安装
8
8
 
9
- ```ts
9
+ ```bash
10
10
  npm install mp-weixin-back
11
+ # 或
12
+ yarn add mp-weixin-back
11
13
  ```
12
14
 
13
- #### 使用
15
+ ## ⚙️ Vite 配置
14
16
 
15
- `vite.config.ts` 中配置
17
+ `vite.config.ts` 中添加插件:
16
18
 
17
19
  ```ts
20
+ import { defineConfig } from 'vite'
18
21
  import mpBackPlugin from 'mp-weixin-back'
19
22
 
20
23
  export default defineConfig({
21
- plugins: [mpBackPlugin()],
24
+ plugins: [
25
+ mpBackPlugin({
26
+ // 可选配置项
27
+ preventDefault: false, // 是否阻止默认返回行为,设置成 true 则不会返回上一层
28
+ frequency: 1, // 阻止次数,需要一直拦截则设置一个很大的值即可,如:9999
29
+ debug: false, // 调试模式,默认为 false
30
+ onPageBack: () => {
31
+ console.log('返回事件触发')
32
+ }, // 统一钩子,事件触发时执行
33
+ }),
34
+ ],
22
35
  })
23
36
  ```
24
37
 
25
- 具体的配置为:
26
-
27
- ```ts
28
- type Config = {
29
- /**
30
- * 是否阻止默认的回退事件,默认为 false
31
- */
32
- preventDefault: boolean
33
- /**
34
- * 阻止次数,默认是 `1`
35
- */
36
- frequency: number
37
- /**
38
- * 页面回退时触发
39
- */
40
- onPageBack?: (params: BackParams) => void
41
- }
42
- ```
38
+ ## 🚀 快速开始
43
39
 
44
- vue3 中使用
40
+ ### 基本使用
45
41
 
46
42
  ```ts
43
+ <script setup>
47
44
  import onPageBack from 'mp-weixin-back-helper'
48
45
 
46
+ // 简单监听返回事件
49
47
  onPageBack(() => {
50
- console.log('触发了手势返回')
48
+ console.log('检测到返回操作(手势或导航栏返回)')
49
+ // 在这里添加你的处理逻辑
51
50
  })
51
+ </script>
52
52
  ```
53
53
 
54
- onPageBack 的类型定义为:
54
+ ### 高级配置
55
55
 
56
56
  ```ts
57
- type Config = {
58
- /**
59
- * 是否阻止默认的回退事件,默认为 false
60
- */
61
- preventDefault: boolean
62
- /**
63
- * 阻止次数,默认是 `1`
64
- */
65
- frequency: number
66
- }
67
-
68
- function onPageBack(callback: () => void, params: Partial<Config>)
57
+ // 带配置的监听
58
+ onPageBack(
59
+ () => {
60
+ console.log('返回事件被触发')
61
+ // 自定义处理逻辑
62
+ },
63
+ {
64
+ initialValue: false, // 立即生效,默认值为`true`
65
+ preventDefault: true, // 阻止默认返回行为
66
+ frequency: 2, // 阻止次数为2次
67
+ }
68
+ )
69
69
  ```
70
70
 
71
- #### 引入类型
71
+ ## 📚 API 文档
72
+
73
+ ### `onPageBack(callback, config?)`
74
+
75
+ 监听页面返回事件
76
+
77
+ | 参数 | 类型 | 必填 | 说明 |
78
+ | -------- | ------------ | ---- | ------------------------ |
79
+ | callback | `() => void` | 是 | 返回事件触发时的回调函数 |
80
+ | options | Object | 否 | 监听器配置选项 |
81
+
82
+ #### 配置选项
83
+
84
+ | 参数 | 类型 | 默认值 | 说明 |
85
+ | -------------- | ------- | ------ | ----------------------------------------------- |
86
+ | preventDefault | boolean | false | 是否阻止默认返回行为(true 时页面不会实际返回) |
87
+ | frequency | number | 1 | 阻止次数 |
88
+ | initialValue | boolean | true | 是否立即启用监听(设为 false 时需手动激活) |
89
+
90
+ ### 辅助方法
91
+
92
+ #### `activeMpBack()`
93
+
94
+ 启用返回事件监听(需在`<script setup>`中执行)
95
+
96
+ #### `inactiveMpBack()`
97
+
98
+ 禁用返回事件监听(需在`<script setup>`中执行)
99
+
100
+ 举例:
72
101
 
73
- 在项目目录中的`src/env.d.ts` 或`src/shime-uni.d.ts` 文件中引入
102
+ ```html
103
+ <template>
104
+ <div>
105
+ <!-- 页面代码 -->
106
+ <button @click="toggleListener(true)">开启</button>
107
+ <button @click="toggleListener(false)">禁用</button>
108
+ </div>
109
+ </template>
74
110
 
111
+ <script setup>
112
+ import onPageBack, { activeMpBack, inactiveMpBack } from 'mp-weixin-back-helper'
113
+
114
+ const toggleListener = (enable) => {
115
+ enable ? activeMpBack() : inactiveMpBack()
116
+ }
117
+ </script>
75
118
  ```
76
- /// <reference types="mp-weixin-back/client" />
119
+
120
+ ## 🎯 选项式 API 支持(未完善)
121
+
122
+ 组件内直接声明
123
+
124
+ 在 Vue 组件的选项对象中直接定义 onPageBack 方法:
125
+
126
+ ```html
127
+ <template>
128
+ <div class="container">
129
+ <div>当前页面内容</div>
130
+ </div>
131
+ </template>
132
+
133
+ <script>
134
+ export default {
135
+ // 读取 vite 中的配置
136
+ onPageBack() {
137
+ console.log('检测到返回操作')
138
+ // 业务逻辑处理
139
+ },
140
+ }
141
+ </script>
77
142
  ```
78
143
 
79
- 或在 `tsconfig.json` 的 `compilerOptions` 下配置
144
+ ## 🛠 类型支持
145
+
146
+ ### 类型声明配置
147
+
148
+ 在 `tsconfig.json` 中添加:
80
149
 
81
150
  ```json
82
151
  {
@@ -86,9 +155,19 @@ function onPageBack(callback: () => void, params: Partial<Config>)
86
155
  }
87
156
  ```
88
157
 
89
- ### todolist
158
+ 或通过声明文件引用:
159
+
160
+ ```typescript
161
+ // env.d.ts
162
+ /// <reference types="mp-weixin-back/client" />
163
+ ```
164
+
165
+ ## ❓ 常见问题
166
+
167
+ ### Q1: 如何实现多页面独立配置?
168
+
169
+ 每个页面单独调用 `onPageBack` 时传入不同的配置参数即可实现页面级定制。
170
+
171
+ ### Q2: 全局配置与页面配置的优先级?
90
172
 
91
- - [ ] 兼容 uniapp 的 Vue2 项目
92
- - [ ] debug 模式
93
- - [ ] 热更新 pages.json 文件
94
- - [ ] 单元测试
173
+ 页面级配置会覆盖全局配置,建议将通用配置放在全局,特殊需求在页面单独设置。
package/shims-vue.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ // shims-vue.d.ts
2
+ declare module '*.vue' {
3
+ import { DefineComponent } from 'vue'
4
+ const component: DefineComponent<{}, {}, any>
5
+ export default component
6
+ }
package/src/index.ts CHANGED
@@ -7,6 +7,7 @@ function MpBackPlugin(userOptions: UserOptions = {}): Plugin {
7
7
  let context: pageContext
8
8
 
9
9
  const defaultOptions: Config = {
10
+ initialValue: true,
10
11
  preventDefault: false,
11
12
  frequency: 1,
12
13
  debug: false,
@@ -33,14 +34,44 @@ function MpBackPlugin(userOptions: UserOptions = {}): Plugin {
33
34
  }
34
35
  // 导出一个对象
35
36
  if (id === virtualFileId) {
36
- return `export default function onPageBack() {}`
37
+ return `
38
+ import { ref } from 'vue'
39
+ export default function onPageBack() {}
40
+ export function activeMpBack(fn = null) {
41
+ fn?.()
42
+ }
43
+ export function inactiveMpBack(fn = null) {
44
+ fn?.()
45
+ }
46
+ export function useMpWeixinBack(initialValue = true) {
47
+ const __MP_BACK_SHOW_PAGE_CONTAINER__ = ref(initialValue)
48
+
49
+ const __MP_WEIXIN_ACTIVEBACK__ = () => {
50
+ __MP_BACK_SHOW_PAGE_CONTAINER__.value = true
51
+ }
52
+
53
+ const __MP_WEIXIN_INACTIVEBACK__ = () => {
54
+ __MP_BACK_SHOW_PAGE_CONTAINER__.value = false
55
+ }
56
+
57
+ return {
58
+ __MP_BACK_SHOW_PAGE_CONTAINER__,
59
+ __MP_WEIXIN_ACTIVEBACK__,
60
+ __MP_WEIXIN_INACTIVEBACK__
61
+ }
62
+ }
63
+ `
37
64
  }
38
65
  },
39
66
  async transform(code, id) {
40
67
  if (id.includes('node_modules') || !id.includes('.vue')) {
41
68
  return
42
69
  }
43
- return context.transform(code, id)
70
+
71
+ return {
72
+ code: await context.transform(code, id),
73
+ map: null,
74
+ }
44
75
  },
45
76
  } as Plugin
46
77
  }
@@ -0,0 +1,29 @@
1
+ <template>
2
+ <div>我是默认的界面</div>
3
+ <button id="button" @click="disableMpBack"></button>
4
+ <button id="button2" @click="activeMpBack"></button>
5
+ </template>
6
+
7
+ <script setup>
8
+ import onPageBack, { activeMpBack as mpppacitve, inactiveMpBack } from 'mp-weixin-back-helper'
9
+
10
+ onPageBack(
11
+ () => {
12
+ console.log('触发了手势返回')
13
+ },
14
+ {
15
+ initialValue: false,
16
+ }
17
+ )
18
+
19
+ const activeMpBack = () => {
20
+ console.log('执行了activeMpBack')
21
+ mpppacitve()
22
+ }
23
+
24
+ const disableMpBack = () => {
25
+ inactiveMpBack()
26
+ }
27
+ </script>
28
+
29
+ <style></style>
@@ -1,31 +1,64 @@
1
- import { describe, expect, it } from 'vitest'
1
+ import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest'
2
2
  import { mount } from '@vue/test-utils'
3
- // @ts-ignore
4
3
  import IndexSetup from './data/index-setup.vue'
5
- // @ts-ignore
4
+ import IndexUtils from './data/index-utils.vue'
6
5
  import IndexDefault from './data/index-default.vue'
7
6
 
8
7
  describe('generate page-container components', () => {
9
- it('setup compisitionAPI', async () => {
10
- const wrapper = mount(IndexSetup)
11
- await wrapper.vm.$nextTick()
12
-
13
- const pageContainerRef = wrapper.find('page-container')
14
- expect(pageContainerRef.exists()).toBe(true)
15
- expect(wrapper.vm.__MP_BACK_SHOW_PAGE_CONTAINER__).toBe(true)
16
- expect(wrapper.vm.onBeforeLeave()).toBe(true)
8
+ let logSpy: ReturnType<typeof vi.spyOn>
9
+
10
+ beforeEach(() => {
11
+ logSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
12
+ })
13
+
14
+ afterEach(() => {
15
+ logSpy.mockRestore()
16
+ })
17
+
18
+ describe('setup compositionAPI', () => {
19
+ it('default case', async () => {
20
+ const wrapper = mount(IndexSetup)
21
+ await wrapper.vm.$nextTick()
22
+
23
+ const pageContainerRef = wrapper.find('page-container')
24
+ expect(pageContainerRef.exists()).toBe(true)
25
+ expect(wrapper.vm.__MP_BACK_SHOW_PAGE_CONTAINER__).toBe(true)
26
+ expect(typeof wrapper.vm.onBeforeLeave).toBe('function')
27
+ })
28
+
29
+ it('utils case', async () => {
30
+ const wrapper = mount(IndexUtils)
31
+ await wrapper.vm.$nextTick()
32
+
33
+ const pageContainerRef = wrapper.find('page-container')
34
+ expect(pageContainerRef.exists()).toBe(true)
35
+ expect(wrapper.vm.__MP_BACK_SHOW_PAGE_CONTAINER__).toBe(false)
36
+ expect(typeof wrapper.vm.onBeforeLeave).toBe('function')
37
+
38
+ await wrapper.find('#button2').trigger('click')
39
+ await new Promise(resolve => setTimeout(resolve, 0))
40
+
41
+ expect(logSpy).toHaveBeenCalledWith('执行了activeMpBack')
42
+
43
+ expect(wrapper.vm.__MP_BACK_SHOW_PAGE_CONTAINER__).toBe(true)
44
+
45
+ await wrapper.find('#button').trigger('click')
46
+ await new Promise(resolve => setTimeout(resolve, 0))
47
+
48
+ expect(wrapper.vm.__MP_BACK_SHOW_PAGE_CONTAINER__).toBe(false)
49
+ })
17
50
  })
18
51
 
19
- it('default optionsAPI', async () => {
20
- const wrapper = mount(IndexDefault)
21
- await wrapper.vm.$nextTick()
22
- const pageContainerRef = wrapper.find('page-container')
23
- expect(pageContainerRef.exists()).toBe(true)
24
- // @ts-ignore
25
- expect(wrapper.vm.__MP_BACK_SHOW_PAGE_CONTAINER__).toBe(true)
26
- // @ts-ignore
27
- expect(wrapper.vm.__MP_BACK_FREQUENCY__).toBe(1)
28
- // @ts-ignore
29
- expect(wrapper.vm.onBeforeLeave()).toBe(true)
52
+ describe('optionsAPI', () => {
53
+ it('default case', async () => {
54
+ const wrapper = mount(IndexDefault)
55
+ await wrapper.vm.$nextTick()
56
+
57
+ const pageContainerRef = wrapper.find('page-container')
58
+ expect(pageContainerRef.exists()).toBe(true)
59
+ expect(wrapper.vm.__MP_BACK_SHOW_PAGE_CONTAINER__).toBe(true)
60
+ expect(wrapper.vm.__MP_BACK_FREQUENCY__).toBe(1)
61
+ expect(wrapper.vm.onBeforeLeave()).toBe(true)
62
+ })
30
63
  })
31
64
  })
package/types/index.ts CHANGED
@@ -1,6 +1,10 @@
1
1
  export type UserOptions = Partial<Config>
2
2
 
3
3
  export type Config = {
4
+ /**
5
+ * 初始化时是否监听手势返回,默认值为`true`
6
+ */
7
+ initialValue: boolean
4
8
  /**
5
9
  * 是否阻止默认的回退事件,默认为 false
6
10
  */
package/utils/index.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  import { parse } from '@vue/compiler-sfc'
2
2
  import { pageContext } from '../src/context'
3
3
  import { vueWalker } from './walker'
4
- import type { SFCDescriptor } from '@vue/compiler-sfc'
5
4
 
6
5
  export async function transformVueFile(this: pageContext, code: string, id: string) {
7
6
  try {
package/utils/walker.ts CHANGED
@@ -30,16 +30,40 @@ function compositionWalk(context: pageContext, code: string, sfc: any, id: strin
30
30
  hasImportRef: false,
31
31
  backConfig: { ...context.config },
32
32
  callbackCode: '',
33
+ activeFnName: 'activeMpBack',
34
+ inActiveFnName: 'inactiveMpBack',
33
35
  }
34
36
 
37
+ const activeFnCallsToModify: any[] = []
38
+ const inActiveFnCallsToModify: any[] = []
39
+
35
40
  if (setupAst) {
36
41
  walkAST<Node>(setupAst, {
37
42
  enter(node) {
38
43
  if (node.type === 'ImportDeclaration') {
39
44
  if (node.source.value.includes(virtualFileId)) {
40
- const importSpecifier = node.specifiers[0]
45
+ const importDefaultSpecifiers = node.specifiers.filter(
46
+ (i) => i.type === 'ImportDefaultSpecifier'
47
+ )
48
+ const importDefaultSpecifier = importDefaultSpecifiers[0]
41
49
  pageInfo.hasPageBack = true
42
- pageInfo.pageBackFnName = importSpecifier.local.name
50
+ pageInfo.pageBackFnName = importDefaultSpecifier.local.name
51
+
52
+ const importSpecifiers = node.specifiers.filter((i) => i.type === 'ImportSpecifier')
53
+ importSpecifiers.map((specifiers) => {
54
+ if (
55
+ specifiers.imported.type === 'Identifier' &&
56
+ specifiers.imported.name === 'activeMpBack'
57
+ ) {
58
+ pageInfo.activeFnName = specifiers.local.name
59
+ }
60
+ if (
61
+ specifiers.imported.type === 'Identifier' &&
62
+ specifiers.imported.name === 'inactiveMpBack'
63
+ ) {
64
+ pageInfo.inActiveFnName = specifiers.local.name
65
+ }
66
+ })
43
67
  }
44
68
  if (node.source.value === 'vue') {
45
69
  node.specifiers.some((specifier) => {
@@ -83,6 +107,38 @@ function compositionWalk(context: pageContext, code: string, sfc: any, id: strin
83
107
  }
84
108
  }
85
109
  }
110
+
111
+ if (
112
+ node.type === 'ExpressionStatement' &&
113
+ node.expression.type === 'CallExpression' &&
114
+ node.expression.callee.loc?.identifierName === pageInfo.activeFnName
115
+ ) {
116
+ activeFnCallsToModify.push({
117
+ start: node.expression.start,
118
+ end: node.expression.end,
119
+ original: sfc.scriptSetup!.loc.source.substring(
120
+ node.expression.start,
121
+ node.expression.end
122
+ ),
123
+ name: pageInfo.activeFnName,
124
+ })
125
+ }
126
+
127
+ if (
128
+ node.type === 'ExpressionStatement' &&
129
+ node.expression.type === 'CallExpression' &&
130
+ node.expression.callee.loc?.identifierName === pageInfo.inActiveFnName
131
+ ) {
132
+ inActiveFnCallsToModify.push({
133
+ start: node.expression.start,
134
+ end: node.expression.end,
135
+ original: sfc.scriptSetup!.loc.source.substring(
136
+ node.expression.start,
137
+ node.expression.end
138
+ ),
139
+ name: pageInfo.inActiveFnName,
140
+ })
141
+ }
86
142
  },
87
143
  })
88
144
  }
@@ -99,9 +155,14 @@ function compositionWalk(context: pageContext, code: string, sfc: any, id: strin
99
155
  pageInfo.callbackCode += 'uni.navigateBack({ delta: 1 });'
100
156
  }
101
157
 
158
+ const importUseMpWeixinBack = `import { useMpWeixinBack } from '${virtualFileId}'`
102
159
  const importRefFromVue = !pageInfo.hasImportRef ? `import { ref } from 'vue'` : ''
103
160
  const stateFrequency = 'let __MP_BACK_FREQUENCY__ = 1;'
104
- const statePageContainerVar = 'const __MP_BACK_SHOW_PAGE_CONTAINER__ = ref(true);'
161
+
162
+ const statePageContainerVar = `
163
+ const { __MP_BACK_SHOW_PAGE_CONTAINER__, __MP_WEIXIN_ACTIVEBACK__, __MP_WEIXIN_INACTIVEBACK__ } = useMpWeixinBack(${pageInfo.backConfig.initialValue})
164
+ `
165
+
105
166
  // 获取传入插件的统一方法
106
167
  const configBack = (() => {
107
168
  const onPageBack = pageInfo.backConfig.onPageBack
@@ -115,8 +176,12 @@ function compositionWalk(context: pageContext, code: string, sfc: any, id: strin
115
176
  }
116
177
  return `(function ${onPageBack})()`
117
178
  })()
179
+
118
180
  const stateBeforeLeave = `
119
181
  const onBeforeLeave = () => {
182
+ if (!__MP_BACK_SHOW_PAGE_CONTAINER__.value) {
183
+ return
184
+ }
120
185
  if (__MP_BACK_FREQUENCY__ < ${pageInfo.backConfig.frequency}) {
121
186
  __MP_BACK_SHOW_PAGE_CONTAINER__.value = false
122
187
  setTimeout(() => __MP_BACK_SHOW_PAGE_CONTAINER__.value = true, 0);
@@ -147,10 +212,43 @@ function compositionWalk(context: pageContext, code: string, sfc: any, id: strin
147
212
  const scriptMagicString = new MagicString(scriptOffsets.content)
148
213
  scriptMagicString.prepend(
149
214
  ` ${importRefFromVue}
215
+ ${importUseMpWeixinBack}
150
216
  ${stateFrequency}
151
217
  ${statePageContainerVar}
152
218
  ${stateBeforeLeave} `
153
219
  )
220
+
221
+ // 应用 activeMpBack 调用的修改
222
+ activeFnCallsToModify.forEach((call) => {
223
+ // 使用正则匹配函数调用结构,确保我们只修改括号内的内容
224
+ const fnCallRegex = new RegExp(`${call.name}\\(([^)]*)\\)`, 'g')
225
+ const newCall = call.original.replace(fnCallRegex, (_match: any, args: string) => {
226
+ // 如果原调用没有参数
227
+ if (!args.trim()) {
228
+ return `${call.name}(__MP_WEIXIN_ACTIVEBACK__)`
229
+ }
230
+ // 如果有参数,添加新参数
231
+ return `${call.name}(__MP_WEIXIN_ACTIVEBACK__, ${args})`
232
+ })
233
+
234
+ scriptMagicString.overwrite(call.start, call.end, newCall)
235
+ })
236
+
237
+ inActiveFnCallsToModify.forEach((call) => {
238
+ // 使用正则匹配函数调用结构,确保我们只修改括号内的内容
239
+ const fnCallRegex = new RegExp(`${call.name}\\(([^)]*)\\)`, 'g')
240
+ const newCall = call.original.replace(fnCallRegex, (_match: any, args: string) => {
241
+ // 如果原调用没有参数
242
+ if (!args.trim()) {
243
+ return `${call.name}(__MP_WEIXIN_INACTIVEBACK__)`
244
+ }
245
+ // 如果有参数,添加新参数
246
+ return `${call.name}(__MP_WEIXIN_INACTIVEBACK__, ${args})`
247
+ })
248
+
249
+ scriptMagicString.overwrite(call.start, call.end, newCall)
250
+ })
251
+
154
252
  codeMs.overwrite(scriptOffsets.start, scriptOffsets.end, scriptMagicString.toString())
155
253
 
156
254
  return codeMs.toString()
@@ -284,7 +382,7 @@ function optionsWalk(context: pageContext, code: string, sfc: any, id: string) {
284
382
  ],
285
383
  },
286
384
  }
287
- ; (exportDefaultNode as ObjectExpression).properties.push(addData)
385
+ ;(exportDefaultNode as ObjectExpression).properties.push(addData)
288
386
  }
289
387
 
290
388
  // 获取传入插件的统一方法
@@ -340,7 +438,7 @@ function optionsWalk(context: pageContext, code: string, sfc: any, id: string) {
340
438
  },
341
439
  } as ObjectMethod
342
440
  if (methodsNode) {
343
- ; (methodsNode as ObjectExpression).properties.push(newMethodsProperty)
441
+ ;(methodsNode as ObjectExpression).properties.push(newMethodsProperty)
344
442
  } else if (exportDefaultNode) {
345
443
  const addMethods: ObjectProperty = {
346
444
  type: 'ObjectProperty',
@@ -355,7 +453,7 @@ function optionsWalk(context: pageContext, code: string, sfc: any, id: string) {
355
453
  properties: [newMethodsProperty],
356
454
  },
357
455
  }
358
- ; (exportDefaultNode as ObjectExpression).properties.push(addMethods)
456
+ ;(exportDefaultNode as ObjectExpression).properties.push(addMethods)
359
457
  }
360
458
 
361
459
  const { template, script } = sfc
package/vite.config.ts CHANGED
@@ -3,7 +3,16 @@ import vue from '@vitejs/plugin-vue'
3
3
  import mpBack from './dist/index.mjs'
4
4
 
5
5
  export default defineConfig({
6
- plugins: [mpBack(), vue()],
6
+ plugins: [
7
+ mpBack(),
8
+ vue({
9
+ template: {
10
+ compilerOptions: {
11
+ isCustomElement: (tag) => tag.startsWith('page-') || tag.startsWith('mp-'),
12
+ },
13
+ },
14
+ }),
15
+ ],
7
16
  test: {
8
17
  environment: 'happy-dom',
9
18
  },