vue-page-store 0.4.0 → 0.5.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.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # vue-page-store
2
2
 
3
- > Vue 2.6 页面级作用域运行时容器 —— source、state、getters、actions、watch、enter/leave,一个页面作用域全收。
3
+ > Vue 2.6 页面级作用域运行时容器 —— source、state、getters、actions、watch、init/enter/leave,一个页面作用域全收。
4
4
 
5
5
  ## 它是什么
6
6
 
@@ -13,16 +13,18 @@
13
13
  - **getters** — 派生计算
14
14
  - **actions** — 业务逻辑
15
15
  - **watch** — 声明式副作用
16
+ - **init** — 一次性初始化(拉字典、注册事件监听等)
16
17
  - **enter / leave** — 页面可见性生命周期
17
18
  - **$setInterval** — 页面级定时器托管
18
19
  - **event bus** — 页面内作用域通信
20
+ - **plugin** — 外部扩展机制(v0.5 新增)
19
21
 
20
22
  页面离开时可以自动清理页面级定时器,页面销毁时 `$destroy` 一键回收,不污染全局。
21
23
 
22
24
  ## 它不是什么
23
25
 
24
26
  - **不是 Vuex / Pinia 替代品** — 全局状态(用户信息、权限、路由)请继续用 Vuex / Pinia
25
- - **不是全局状态管理方案** — 它的作用域是“页面”,不是“应用”
27
+ - **不是全局状态管理方案** — 它的作用域是"页面",不是"应用"
26
28
  - **不是大而全的框架** — 它只解决复杂页面的页面层状态编排
27
29
 
28
30
  | | Vuex / Pinia | vue-page-store |
@@ -109,6 +111,13 @@ export const useOrderStore = definePageStore('orderList', {
109
111
  }
110
112
  },
111
113
 
114
+ // 只执行一次:拉下拉框选项、注册事件监听等
115
+ init() {
116
+ this.loadDictOptions()
117
+ this.$on('child:refresh', () => this.search())
118
+ },
119
+
120
+ // 每次页面可见时执行
112
121
  enter() {
113
122
  this.$source.query = this.$vm.$route.query
114
123
  this.search()
@@ -129,7 +138,7 @@ import { useOrderStore } from './stores/order-list'
129
138
 
130
139
  export default {
131
140
  created() {
132
- // 传入 this → 自动绑定 enter/leave + 自动 provide + 页面销毁时自动回收
141
+ // 传入 this → 自动绑定 init/enter/leave + 自动 provide + 页面销毁时自动回收
133
142
  this.pageStore = useOrderStore(this)
134
143
  }
135
144
  }
@@ -165,8 +174,14 @@ export default {
165
174
  | `getters` | `{ [key]: function }` | 派生计算,`this` 指向 store |
166
175
  | `actions` | `{ [key]: function }` | 业务方法,`this` 指向 store |
167
176
  | `watch` | `{ [path]: handler \| options }` | 声明式 watcher,支持 dot-path |
177
+ | `init` | `function` | store 创建后一次性调用,`$vm` 已可用。适合拉字典、注册事件监听 |
168
178
  | `enter` | `function` | 页面进入可见 / 可交互状态时触发 |
169
179
  | `leave` | `function` | 页面离开可见 / 可交互状态时触发 |
180
+ | *其它字段* | *any* | 注册过的 plugin 可声明自己的字段(见 [Plugin](#plugin)) |
181
+
182
+ ### `registerPlugin(plugin)` *(v0.5 新增)*
183
+
184
+ 注册全局插件,详见 [Plugin](#plugin) 节。
170
185
 
171
186
  ### Store 实例属性与方法
172
187
 
@@ -207,7 +222,7 @@ watch: {
207
222
 
208
223
  ## source 与 state
209
224
 
210
- v0.4 引入了 `source`,用于把“页面输入 / 原始返回”和“业务状态”分开。
225
+ v0.4 引入了 `source`,用于把"页面输入 / 原始返回"和"业务状态"分开。
211
226
 
212
227
  ### 推荐分工
213
228
 
@@ -233,15 +248,58 @@ state: () => ({
233
248
  - getters 可以同时基于 `this.$source` 和 `this.xxx` 计算
234
249
  - `$reset()` 时 source / state 一起恢复,更清晰
235
250
 
236
- ## enter / leave
251
+ ## init / enter / leave
237
252
 
238
253
  v0.4 用 `enter / leave` 替换了 v0.3 的 `lifecycle.mount / unmount / activate / deactivate`。
239
254
 
255
+ v0.4.1 新增 `init`,用于 store 创建后的一次性初始化。
256
+
240
257
  ### 语义
241
258
 
259
+ - **init**:store 创建后一次性调用,`$vm` 已可用,DOM 未就绪
242
260
  - **enter**:页面进入可见 / 可交互状态
243
261
  - **leave**:页面离开可见 / 可交互状态
244
262
 
263
+ ### 执行时序
264
+
265
+ ```
266
+ created() 开始
267
+ └→ useStore(this)
268
+ └→ createStoreInstance() ← state/source/getters/actions 就绪
269
+ └→ plugin.install() ← v0.5:plugin 安装($vm 尚未绑定)
270
+ └→ bindTo(this) ← $vm 赋值
271
+ └→ ★ init() ← $vm 可用,只执行一次
272
+ └→ created() 剩余代码
273
+ mounted()
274
+ └→ ★ enter() ← DOM 就绪,每次可见都执行
275
+ └→ plugin.enter() ← v0.5:plugin enter 钩子
276
+
277
+ --- keep-alive 切走 ---
278
+ deactivated()
279
+ └→ clearAllIntervals()
280
+ └→ ★ leave()
281
+ └→ plugin.leave() ← v0.5:plugin leave 钩子
282
+
283
+ --- keep-alive 切回 ---
284
+ activated()
285
+ └→ ★ enter() ← 重新开轮询、刷数据
286
+ └→ plugin.enter()
287
+
288
+ --- 页面销毁 ---
289
+ beforeDestroy()
290
+ └→ ★ leave()(如果还没 leave)
291
+ └→ plugin.destroy() ← v0.5:plugin destroy 钩子
292
+ └→ $destroy()
293
+ ```
294
+
295
+ ### 分工原则
296
+
297
+ | 钩子 | 执行次数 | $vm | DOM | 典型场景 |
298
+ |---|---|---|---|---|
299
+ | `init` | 一次 | ✅ | ❌ | 拉下拉框选项、注册事件监听、从 localStorage 恢复配置、初始化 WebSocket |
300
+ | `enter` | 每次可见 | ✅ | ✅ | 读路由参数、刷列表数据、开轮询 |
301
+ | `leave` | 每次离开 | ✅ | ✅ | 通常不需要写,interval 已自动清理 |
302
+
245
303
  ### keep-alive 行为
246
304
 
247
305
  - 首次 `mounted` → `enter`
@@ -249,14 +307,26 @@ v0.4 用 `enter / leave` 替换了 v0.3 的 `lifecycle.mount / unmount / activat
249
307
  - `deactivated` → `leave`
250
308
  - `beforeDestroy` → 如果当前还没 leave,先 leave,再 `$destroy`
251
309
 
252
- ### 适合放在 enter / leave 里的逻辑
310
+ ### 适合放在 init 里的逻辑
311
+
312
+ - 拉下拉框 / 字典选项(只需要一次)
313
+ - 注册 `$on` 监听 store 内部事件
314
+ - 从 localStorage 恢复上次的筛选条件
315
+ - 初始化 WebSocket / EventSource 连接
316
+ - 根据用户权限裁剪 columns / 按钮配置
317
+
318
+ ### 适合放在 enter 里的逻辑
253
319
 
254
- - 首屏加载
255
- - 根据 `$route` 初始化 source / state
320
+ - 根据 `$route` 初始化 source / state(keep-alive 切回时路由参数可能变了)
321
+ - 首屏加载 / 刷新列表数据
256
322
  - 启动页面轮询
257
- - 页面离开时做收尾逻辑
258
323
 
259
324
  ```js
325
+ init() {
326
+ this.loadDictOptions()
327
+ this.$on('child:refresh', () => this.search())
328
+ },
329
+
260
330
  enter() {
261
331
  this.$source.query = this.$vm.$route.query
262
332
  this.search()
@@ -360,6 +430,128 @@ state: () => ({
360
430
  })
361
431
  ```
362
432
 
433
+ ## Plugin
434
+
435
+ *v0.5 新增。* Plugin 机制让外部库可以给 `definePageStore` options 增加**新字段**并消费它,同时挂钩 enter / leave / destroy 生命周期——而不需要修改 page-store 本身。
436
+
437
+ > 典型场景:`vue-page-runtime`(请求编排)、`vue-page-persist`(状态持久化)、devtools 扩展。
438
+
439
+ ### 协议
440
+
441
+ Plugin 是一个对象,包含 `name` 和 `install`:
442
+
443
+ ```js
444
+ {
445
+ name: 'tasks', // 同时作为 options 字段匹配键
446
+ install(store, fieldValue, { Vue }) { // fieldValue === options.tasks
447
+ // 初始化 plugin 自己的逻辑
448
+ return {
449
+ enter() { /* page enter 后调用 */ },
450
+ leave() { /* page leave 后调用 */ },
451
+ destroy() { /* store 销毁时调用 */ },
452
+ }
453
+ }
454
+ }
455
+ ```
456
+
457
+ - **匹配规则**:`options[plugin.name] !== undefined` 才会调用 `install`。没有声明字段的 store 完全不受影响。
458
+ - **install 时机**:store 创建末尾,state / getters / actions / $source / $setInterval / $emit 等全部就绪。`$vm` 此时**尚未**绑定。
459
+ - **返回值**:可选 `{ enter?, leave?, destroy? }`。不需要钩子可以不返回。
460
+
461
+ ### 注册
462
+
463
+ 全局注册一次即可:
464
+
465
+ ```js
466
+ // main.js
467
+ import { registerPlugin } from 'vue-page-store'
468
+ import taskPlugin from 'vue-page-runtime/plugin'
469
+
470
+ registerPlugin(taskPlugin)
471
+ ```
472
+
473
+ 之后正常写 store,声明插件字段:
474
+
475
+ ```js
476
+ import { definePageStore } from 'vue-page-store' // 入口不变
477
+
478
+ definePageStore('order', {
479
+ state: () => ({ /* ... */ }),
480
+
481
+ // page-store 不认识这个字段,但会递给注册过的 plugin
482
+ tasks: {
483
+ fetchUser: {
484
+ trigger: 'enter',
485
+ async run() { return api.getUser(this.$vm.$route.params.id) },
486
+ },
487
+ fetchOrders: {
488
+ deps: ['fetchUser'],
489
+ async run() { /* ... */ },
490
+ },
491
+ },
492
+ })
493
+ ```
494
+
495
+ ### 写一个 plugin
496
+
497
+ 最小示例——一个把 `persist` 字段声明持久化到 localStorage 的插件:
498
+
499
+ ```js
500
+ const persistPlugin = {
501
+ name: 'persist',
502
+
503
+ install(store, fieldValue /* options.persist */, { Vue }) {
504
+ const { key, paths } = fieldValue
505
+
506
+ // 恢复
507
+ try {
508
+ const saved = JSON.parse(localStorage.getItem(key) || '{}')
509
+ store.$patch(saved)
510
+ } catch (e) {}
511
+
512
+ // 持久化 —— 监听指定字段
513
+ const stopWatchers = paths.map(p =>
514
+ store._vm.$watch(
515
+ () => store[p],
516
+ (val) => {
517
+ const cur = JSON.parse(localStorage.getItem(key) || '{}')
518
+ cur[p] = val
519
+ localStorage.setItem(key, JSON.stringify(cur))
520
+ }
521
+ )
522
+ )
523
+
524
+ return {
525
+ destroy() {
526
+ stopWatchers.forEach(stop => stop())
527
+ }
528
+ }
529
+ }
530
+ }
531
+
532
+ registerPlugin(persistPlugin)
533
+ ```
534
+
535
+ 使用:
536
+
537
+ ```js
538
+ definePageStore('page', {
539
+ state: () => ({ keyword: '', filters: {} }),
540
+ persist: {
541
+ key: 'page:cache',
542
+ paths: ['keyword', 'filters']
543
+ }
544
+ })
545
+ ```
546
+
547
+ ### 注意事项
548
+
549
+ - **全局注册,影响所有 store**。plugin 只在对应 store 声明了 `options[plugin.name]` 时才激活,但注册本身是全局的。
550
+ - **同名 plugin 只能注册一次**,重复注册会被跳过并打印 warning。
551
+ - **install 返回的钩子会被按注册顺序依次调用**(FIFO)。
552
+ - **plugin 之间不通信**。如果两个 plugin 有依赖关系,应该合并成一个。
553
+ - **$vm 在 install 时为 null**。如果 plugin 需要组件实例,应在 `enter` 钩子里访问(此时 `$vm` 已绑定)。
554
+
363
555
  ## 实例模型:Singleton
364
556
 
365
557
  当前版本采用 **id → singleton** 模型:
@@ -385,7 +577,7 @@ state: () => ({
385
577
  - 仪表盘页面 —— 多模块共享筛选条件、加载状态
386
578
  - 漏斗 / 留存等分析详情页 —— 复杂交互 + 异步数据 + 页面可见性管理
387
579
  - 大型配置页 —— 多 tab / 多步骤表单的状态统一管理
388
- - keep-alive 业务页 —— 需要 enter / leave 感知的页面
580
+ - keep-alive 业务页 —— 需要 init / enter / leave 感知的页面
389
581
  - 微前端子应用 —— 页面作用域隔离,不污染宿主全局状态
390
582
 
391
583
  ## 不适用场景
@@ -412,22 +604,48 @@ actions: {
412
604
 
413
605
  ## 调试
414
606
 
415
- `storeRegistry` 是导出的 Map,可以在控制台直接查看:
607
+ ### `storeRegistry` —— 导出的 Map
608
+
609
+ `storeRegistry` 是导出的 Map,可以在代码里用于调试或自定义 devtools 集成:
416
610
 
417
611
  ```js
418
612
  import { storeRegistry } from 'vue-page-store'
419
613
 
420
- // 查看所有活跃 store
421
614
  storeRegistry.forEach((store, id) => {
422
615
  console.log(id, store.$status, store.$disposed)
423
616
  })
424
617
  ```
425
618
 
619
+ ### `window.__VUE_PAGE_STORE__` —— dev 自动挂载 *(v0.5 新增)*
620
+
621
+ 开发环境下(`process.env.NODE_ENV !== 'production'`)会自动挂到 `window.__VUE_PAGE_STORE__`,方便控制台访问。生产环境和 SSR 环境不会挂。
622
+
623
+ 控制台用法:
624
+
625
+ ```js
626
+ __VUE_PAGE_STORE__ // { registry, stores }
627
+ __VUE_PAGE_STORE__.stores // { orderList: {…}, userProfile: {…} }
628
+ __VUE_PAGE_STORE__.stores.orderList // ← 有属性自动补全
629
+ __VUE_PAGE_STORE__.stores.orderList.$source
630
+ __VUE_PAGE_STORE__.stores.orderList.$loading
631
+
632
+ // 原始 Map 也保留
633
+ __VUE_PAGE_STORE__.registry.forEach(...)
634
+ ```
635
+
636
+ - `registry`:导出的原始 Map,和 `import { storeRegistry }` 拿到的是同一个引用
637
+ - `stores`:getter,每次读取重建对象视图;销毁的 store 自动消失
638
+
639
+ **说明**:
640
+
641
+ - `__VUE_PAGE_STORE__` 是 dev-only 调试接口,shape 和键名可能在后续版本变化,**不要在生产代码里依赖**
642
+ - 微前端场景下,多个子应用都加载 vue-page-store 时,最后挂载的会覆盖前面的。如需共存,请退回手动挂载并用自己的命名
643
+
426
644
  ## 从 v0.3.x 升级
427
645
 
428
646
  ### Breaking Changes
429
647
 
430
- **1. `lifecycle` 被移除,改为 `enter / leave`**
648
+ **1. `lifecycle` 被移除,改为 `init` / `enter` / `leave`**
431
649
 
432
650
  v0.3.x:
433
651
 
@@ -440,16 +658,23 @@ lifecycle: {
440
658
  }
441
659
  ```
442
660
 
443
- v0.4.0
661
+ v0.4.x
444
662
 
445
663
  ```js
446
- enter() {},
447
- leave() {}
664
+ init() {
665
+ // 只执行一次的初始化(拉字典、注册事件等)
666
+ },
667
+ enter() {
668
+ // 每次可见时执行(替代 mount + activate)
669
+ },
670
+ leave() {
671
+ // 每次离开时执行(替代 deactivate + unmount)
672
+ }
448
673
  ```
449
674
 
450
675
  迁移关系:
451
676
 
452
- - `lifecycle.mount` → `enter`
677
+ - `lifecycle.mount` → `enter`(如果包含一次性逻辑,拆到 `init`)
453
678
  - `lifecycle.unmount` → `leave`
454
679
  - `lifecycle.activate` → `enter`
455
680
  - `lifecycle.deactivate` → `leave`
@@ -465,17 +690,30 @@ v0.4.0 中:
465
690
  ### New Features
466
691
 
467
692
  - `source`:页面输入 / 原始返回与业务状态分离
693
+ - `init`:store 创建后一次性钩子,`$vm` 已可用(v0.4.1)
468
694
  - `enter / leave`:更简单的页面可见性生命周期
469
695
  - `$setInterval()`:页面级 interval 托管
470
696
  - `$loading.xxx`:返回 Promise 的 action 自动追踪 loading
471
- - `$vm`:只读逃生口,可在 enter 中访问 `$route / $router`
697
+ - `$vm`:只读逃生口,可在 init / enter 中访问 `$route / $router`
698
+ - `registerPlugin()`:外部扩展机制(v0.5)
699
+
700
+ ## 从 v0.4.x 升级到 v0.5
701
+
702
+ v0.5 **完全向后兼容** v0.4.x:
703
+
704
+ - 所有 v0.4 的 API 行为不变
705
+ - 新增 `registerPlugin()` 导出,不注册 plugin 等同于 v0.4 行为
706
+ - options 现在允许包含插件声明的额外字段(如 `tasks`、`persist`)
707
+ - dev 环境自动挂 `window.__VUE_PAGE_STORE__`,方便控制台调试
708
+
709
+ 升级只需要改版本号,无需改代码。
472
710
 
473
711
  ## Roadmap
474
712
 
475
713
  - **Keyed instance** — `useStore(vm, scopeKey)` 支持同定义多实例
476
- - **Page cache strategy** — TTL、revalidate、stale-while-enter
714
+ - **Official plugins** — 随着 `vue-page-runtime` 等生态库成熟,补充第一方 plugin 文档
477
715
  - **More page runtime helpers** — 在不增加心智负担的前提下继续补页面层能力
478
716
 
479
717
  ## License
480
718
 
481
- MIT © weijianjun
719
+ MIT © weijianjun
package/dist/index.cjs.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * vue-page-store v0.4.0
2
+ * vue-page-store v0.5.0
3
3
  * (c) 2026 weijianjun
4
4
  * @license MIT
5
5
  */
@@ -7,16 +7,18 @@
7
7
 
8
8
  Object.defineProperty(exports, '__esModule', { value: true });
9
9
 
10
+ var Vue = require('vue');
11
+
10
12
  /*!
11
- * vue-page-store v0.4.0
13
+ * vue-page-store v0.5.0
12
14
  * (c) 2026 weijianjun
13
15
  * @license MIT
14
16
  */
15
17
  /**
16
- * vue-page-store 0.4.0 — Vue 2.6 Page Scope Runtime
18
+ * vue-page-store 0.5.0 — Vue 2.6 Page Scope Runtime
17
19
  *
18
20
  * 页面级作用域运行时容器:
19
- * source · state · getters · actions · watch · enter/leave · $setInterval · event bus
21
+ * source · state · getters · actions · watch · init/enter/leave · $setInterval · event bus · plugin
20
22
  *
21
23
  * v0.4 新增:
22
24
  * source → 页面输入 / 原始返回,和业务 state 分开
@@ -24,12 +26,16 @@ Object.defineProperty(exports, '__esModule', { value: true });
24
26
  * $setInterval → 页面级 timer 托管,leave 时自动清理
25
27
  * $loading → async action 自动追踪 loading 状态
26
28
  *
29
+ * v0.4.1 新增:
30
+ * init → store 创建后一次性初始化钩子,$vm 已可用
31
+ *
32
+ * v0.5 新增:
33
+ * plugin → registerPlugin 注册外部扩展,声明字段 + 生命周期钩子
34
+ *
27
35
  * @author weijianjun
28
36
  * @license MIT
29
37
  */
30
38
 
31
- // Store 注册表(导出供调试 / devtools 使用)
32
- var storeRegistry = new Map();
33
39
 
34
40
  // ====== dev-only warning ======
35
41
  var isDev = typeof process !== 'undefined'
@@ -42,6 +48,47 @@ function warn(msg) {
42
48
  }
43
49
  }
44
50
 
51
+ // Store 注册表(导出供调试 / devtools 使用)
52
+ var storeRegistry = new Map();
53
+
54
+ // v0.5 新增:dev 环境自动挂到 window,方便控制台调试
55
+ if (isDev && typeof window !== 'undefined') {
56
+ window.__VUE_PAGE_STORE__ = {
57
+ registry: storeRegistry,
58
+ get stores() {
59
+ var result = {};
60
+ storeRegistry.forEach(function (s, id) { result[id] = s; });
61
+ return result;
62
+ },
63
+ };
64
+ }
65
+
66
+ // ====== v0.5 新增:plugin 机制 ======
67
+ var _plugins = [];
68
+
69
+ /**
70
+ * 注册全局 plugin
71
+ *
72
+ * plugin.name 同时作为 options 字段匹配键:
73
+ * 当 definePageStore options 中存在 options[plugin.name] 时,
74
+ * page-store 会调用 plugin.install(store, fieldValue, { Vue })
75
+ *
76
+ * install 可返回 { enter, leave, destroy } 生命周期钩子
77
+ */
78
+ function registerPlugin(plugin) {
79
+ if (!plugin || typeof plugin.name !== 'string') {
80
+ throw new Error('[vue-page-store] plugin 需要一个 name 属性');
81
+ }
82
+ if (typeof plugin.install !== 'function') {
83
+ throw new Error('[vue-page-store] plugin "' + plugin.name + '" 需要一个 install 方法');
84
+ }
85
+ if (_plugins.some(function (p) { return p.name === plugin.name; })) {
86
+ warn('plugin "' + plugin.name + '" 已注册,跳过重复注册');
87
+ return;
88
+ }
89
+ _plugins.push(plugin);
90
+ }
91
+
45
92
  function createStoreInstance(Vue, id, options) {
46
93
  var initialState = options.state();
47
94
 
@@ -55,6 +102,9 @@ function createStoreInstance(Vue, id, options) {
55
102
  var enterHook = typeof options.enter === 'function' ? options.enter : null;
56
103
  var leaveHook = typeof options.leave === 'function' ? options.leave : null;
57
104
 
105
+ // ====== v0.5 新增:plugin hooks 闭包存储(由 install 填充) ======
106
+ var _pluginHooks = [];
107
+
58
108
  // --- 用一个隐藏的 Vue 实例承载响应式 state + source + loading + computed getters ---
59
109
  var computedDefs = {};
60
110
  var store = { $disposed: false };
@@ -322,6 +372,8 @@ function createStoreInstance(Vue, id, options) {
322
372
  enterHook.call(store);
323
373
  }
324
374
  store.$emit('page:enter');
375
+ // v0.5: plugin enter hooks
376
+ _pluginHooks.forEach(function (h) { if (h.enter) h.enter(); });
325
377
  }
326
378
 
327
379
  function runLeave() {
@@ -334,6 +386,8 @@ function createStoreInstance(Vue, id, options) {
334
386
  leaveHook.call(store);
335
387
  }
336
388
  store.$emit('page:leave');
389
+ // v0.5: plugin leave hooks
390
+ _pluginHooks.forEach(function (h) { if (h.leave) h.leave(); });
337
391
  }
338
392
 
339
393
  // ====== bindTo 去重标记 ======
@@ -400,6 +454,7 @@ function createStoreInstance(Vue, id, options) {
400
454
  * 销毁 store
401
455
  *
402
456
  * v0.4 变更:兜底清理 interval
457
+ * v0.5 变更:调用 plugin destroy 钩子
403
458
  */
404
459
  store.$destroy = function () {
405
460
  if (store.$disposed) return;
@@ -410,6 +465,9 @@ function createStoreInstance(Vue, id, options) {
410
465
  // 兜底清理 interval(正常流程 leave 已经清过,这里防遗漏)
411
466
  clearAllIntervals();
412
467
 
468
+ // v0.5: plugin destroy hooks
469
+ _pluginHooks.forEach(function (h) { if (h.destroy) h.destroy(); });
470
+
413
471
  store.$disposed = true;
414
472
  Object.keys(_listeners).forEach(function (key) { delete _listeners[key]; });
415
473
  vm.$destroy();
@@ -418,6 +476,15 @@ function createStoreInstance(Vue, id, options) {
418
476
 
419
477
  store._vm = vm;
420
478
 
479
+ // ====== v0.5 新增:plugin 安装 ======
480
+ // 放在最后,此时 store 的 state/getters/actions/$源数据/$setInterval 等全部就绪
481
+ _plugins.forEach(function (plugin) {
482
+ var fieldValue = options[plugin.name];
483
+ if (fieldValue === undefined) return;
484
+ var hooks = plugin.install(store, fieldValue, { Vue: Vue });
485
+ if (hooks) _pluginHooks.push(hooks);
486
+ });
487
+
421
488
  return store;
422
489
  }
423
490
 
@@ -428,6 +495,10 @@ function createStoreInstance(Vue, id, options) {
428
495
  * - options 新增 source、enter、leave
429
496
  * - options 移除 lifecycle
430
497
  * - actions 中的 async 函数自动追踪 $loading
498
+ * v0.4.1 变更:
499
+ * - options 新增 init(bindTo 之后一次性调用)
500
+ * v0.5 变更:
501
+ * - options 中注册过的 plugin.name 字段会被交给对应 plugin 处理
431
502
  */
432
503
  function definePageStore(id, options) {
433
504
  // 入参校验
@@ -438,7 +509,7 @@ function definePageStore(id, options) {
438
509
  throw new Error('[vue-page-store] definePageStore("' + id + '") 需要 state 为函数');
439
510
  }
440
511
 
441
- var _Vue = null;
512
+ var _Vue = Vue;
442
513
 
443
514
  return function useStore(componentVm) {
444
515
  if (storeRegistry.has(id)) {
@@ -447,26 +518,26 @@ function definePageStore(id, options) {
447
518
  return existing;
448
519
  }
449
520
 
450
- if (!_Vue) {
451
- try {
452
- _Vue = require('vue');
453
- if (_Vue.default) _Vue = _Vue.default;
454
- } catch (e) {
455
- throw new Error(
456
- '[vue-page-store] 无法自动获取 Vue,请确保 vue 已安装'
457
- );
458
- }
459
- }
460
-
461
521
  var store = createStoreInstance(_Vue, id, options);
462
522
  storeRegistry.set(id, store);
463
523
  if (componentVm) store.bindTo(componentVm);
524
+
525
+ // v0.4.1 新增:init 钩子 —— bindTo 之后调用,$vm 已可用
526
+ if (typeof options.init === 'function') {
527
+ options.init.call(store);
528
+ }
529
+
464
530
  return store;
465
531
  };
466
532
  }
467
533
 
468
- var index = { definePageStore: definePageStore, storeRegistry: storeRegistry };
534
+ var index = {
535
+ definePageStore: definePageStore,
536
+ registerPlugin: registerPlugin,
537
+ storeRegistry: storeRegistry
538
+ };
469
539
 
470
540
  exports.default = index;
471
541
  exports.definePageStore = definePageStore;
542
+ exports.registerPlugin = registerPlugin;
472
543
  exports.storeRegistry = storeRegistry;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * vue-page-store v0.4.0 - TypeScript 类型定义
2
+ * vue-page-store v0.5.0 - TypeScript 类型定义
3
3
  *
4
4
  * Page Scope Runtime for Vue 2.6
5
5
  */
@@ -49,6 +49,13 @@ export interface StoreOptions<
49
49
  };
50
50
  };
51
51
 
52
+ /**
53
+ * store 创建后一次性调用(v0.4.1 新增)
54
+ * bindTo 之后调用,$vm 已可用
55
+ * 适合拉字典、注册事件监听、从 localStorage 恢复配置、初始化 WebSocket
56
+ */
57
+ init?: (this: PageStore<S, Src>) => void;
58
+
52
59
  /**
53
60
  * 页面进入可见/可交互状态(v0.4 新增,替换 v0.3 lifecycle)
54
61
  * keep-alive 切回时也会触发
@@ -61,6 +68,9 @@ export interface StoreOptions<
61
68
  * interval 在 leave 前已自动清理
62
69
  */
63
70
  leave?: (this: PageStore<S, Src>) => void;
71
+
72
+ /** 允许 plugin 声明的额外字段(v0.5 新增) */
73
+ [key: string]: any;
64
74
  }
65
75
 
66
76
  export interface Store<
@@ -129,6 +139,10 @@ export type PageStore<
129
139
  * - options 新增 source、enter、leave
130
140
  * - options 移除 lifecycle(breaking change)
131
141
  * - async action 自动追踪 $loading
142
+ * v0.4.1 变更:
143
+ * - options 新增 init(bindTo 之后一次性调用)
144
+ * v0.5 变更:
145
+ * - options 支持 plugin 声明的自定义字段
132
146
  */
133
147
  export declare function definePageStore<
134
148
  S extends Record<string, any>,
@@ -139,4 +153,44 @@ export declare function definePageStore<
139
153
  ): (componentVm?: Vue2ComponentInstance) => PageStore<S, Src>;
140
154
 
141
155
  /** Store 注册表 */
142
- export declare const storeRegistry: Map<string, PageStore<any, any>>;
156
+ export declare const storeRegistry: Map<string, PageStore<any, any>>;
157
+
158
+ // ====== v0.5 新增:plugin 类型 ======
159
+
160
+ /**
161
+ * Plugin install 返回的生命周期钩子(v0.5 新增)
162
+ * page-store 会在对应时机调用
163
+ */
164
+ export interface PluginHooks {
165
+ /** 每次 page enter 之后调用(用户 enter hook + page:enter event 之后) */
166
+ enter?: () => void;
167
+ /** 每次 page leave 之后调用(用户 leave hook + page:leave event 之后) */
168
+ leave?: () => void;
169
+ /** store $destroy 时调用,用于释放 plugin 自己的资源 */
170
+ destroy?: () => void;
171
+ }
172
+
173
+ /**
174
+ * Plugin 定义(v0.5 新增)
175
+ *
176
+ * plugin.name 同时作为字段名:
177
+ * 当 definePageStore options 中存在 options[name] 时,
178
+ * page-store 会调用 install(store, fieldValue, { Vue })
179
+ *
180
+ * install 在 store 创建末尾、bindTo 之前执行,此时 state/getters/actions/$source/$setInterval 已就绪
181
+ */
182
+ export interface PageStorePlugin {
183
+ name: string;
184
+ install(
185
+ store: PageStore<any, any>,
186
+ fieldValue: any,
187
+ context: { Vue: any }
188
+ ): PluginHooks | void;
189
+ }
190
+
191
+ /**
192
+ * 注册全局 plugin(v0.5 新增)
193
+ *
194
+ * 同名 plugin 重复注册会被跳过并打印 warning
195
+ */
196
+ export declare function registerPlugin(plugin: PageStorePlugin): void;
package/dist/index.esm.js CHANGED
@@ -1,18 +1,20 @@
1
1
  /*!
2
- * vue-page-store v0.4.0
2
+ * vue-page-store v0.5.0
3
3
  * (c) 2026 weijianjun
4
4
  * @license MIT
5
5
  */
6
+ import Vue from 'vue';
7
+
6
8
  /*!
7
- * vue-page-store v0.4.0
9
+ * vue-page-store v0.5.0
8
10
  * (c) 2026 weijianjun
9
11
  * @license MIT
10
12
  */
11
13
  /**
12
- * vue-page-store 0.4.0 — Vue 2.6 Page Scope Runtime
14
+ * vue-page-store 0.5.0 — Vue 2.6 Page Scope Runtime
13
15
  *
14
16
  * 页面级作用域运行时容器:
15
- * source · state · getters · actions · watch · enter/leave · $setInterval · event bus
17
+ * source · state · getters · actions · watch · init/enter/leave · $setInterval · event bus · plugin
16
18
  *
17
19
  * v0.4 新增:
18
20
  * source → 页面输入 / 原始返回,和业务 state 分开
@@ -20,12 +22,16 @@
20
22
  * $setInterval → 页面级 timer 托管,leave 时自动清理
21
23
  * $loading → async action 自动追踪 loading 状态
22
24
  *
25
+ * v0.4.1 新增:
26
+ * init → store 创建后一次性初始化钩子,$vm 已可用
27
+ *
28
+ * v0.5 新增:
29
+ * plugin → registerPlugin 注册外部扩展,声明字段 + 生命周期钩子
30
+ *
23
31
  * @author weijianjun
24
32
  * @license MIT
25
33
  */
26
34
 
27
- // Store 注册表(导出供调试 / devtools 使用)
28
- var storeRegistry = new Map();
29
35
 
30
36
  // ====== dev-only warning ======
31
37
  var isDev = typeof process !== 'undefined'
@@ -38,6 +44,47 @@ function warn(msg) {
38
44
  }
39
45
  }
40
46
 
47
+ // Store 注册表(导出供调试 / devtools 使用)
48
+ var storeRegistry = new Map();
49
+
50
+ // v0.5 新增:dev 环境自动挂到 window,方便控制台调试
51
+ if (isDev && typeof window !== 'undefined') {
52
+ window.__VUE_PAGE_STORE__ = {
53
+ registry: storeRegistry,
54
+ get stores() {
55
+ var result = {};
56
+ storeRegistry.forEach(function (s, id) { result[id] = s; });
57
+ return result;
58
+ },
59
+ };
60
+ }
61
+
62
+ // ====== v0.5 新增:plugin 机制 ======
63
+ var _plugins = [];
64
+
65
+ /**
66
+ * 注册全局 plugin
67
+ *
68
+ * plugin.name 同时作为 options 字段匹配键:
69
+ * 当 definePageStore options 中存在 options[plugin.name] 时,
70
+ * page-store 会调用 plugin.install(store, fieldValue, { Vue })
71
+ *
72
+ * install 可返回 { enter, leave, destroy } 生命周期钩子
73
+ */
74
+ function registerPlugin(plugin) {
75
+ if (!plugin || typeof plugin.name !== 'string') {
76
+ throw new Error('[vue-page-store] plugin 需要一个 name 属性');
77
+ }
78
+ if (typeof plugin.install !== 'function') {
79
+ throw new Error('[vue-page-store] plugin "' + plugin.name + '" 需要一个 install 方法');
80
+ }
81
+ if (_plugins.some(function (p) { return p.name === plugin.name; })) {
82
+ warn('plugin "' + plugin.name + '" 已注册,跳过重复注册');
83
+ return;
84
+ }
85
+ _plugins.push(plugin);
86
+ }
87
+
41
88
  function createStoreInstance(Vue, id, options) {
42
89
  var initialState = options.state();
43
90
 
@@ -51,6 +98,9 @@ function createStoreInstance(Vue, id, options) {
51
98
  var enterHook = typeof options.enter === 'function' ? options.enter : null;
52
99
  var leaveHook = typeof options.leave === 'function' ? options.leave : null;
53
100
 
101
+ // ====== v0.5 新增:plugin hooks 闭包存储(由 install 填充) ======
102
+ var _pluginHooks = [];
103
+
54
104
  // --- 用一个隐藏的 Vue 实例承载响应式 state + source + loading + computed getters ---
55
105
  var computedDefs = {};
56
106
  var store = { $disposed: false };
@@ -318,6 +368,8 @@ function createStoreInstance(Vue, id, options) {
318
368
  enterHook.call(store);
319
369
  }
320
370
  store.$emit('page:enter');
371
+ // v0.5: plugin enter hooks
372
+ _pluginHooks.forEach(function (h) { if (h.enter) h.enter(); });
321
373
  }
322
374
 
323
375
  function runLeave() {
@@ -330,6 +382,8 @@ function createStoreInstance(Vue, id, options) {
330
382
  leaveHook.call(store);
331
383
  }
332
384
  store.$emit('page:leave');
385
+ // v0.5: plugin leave hooks
386
+ _pluginHooks.forEach(function (h) { if (h.leave) h.leave(); });
333
387
  }
334
388
 
335
389
  // ====== bindTo 去重标记 ======
@@ -396,6 +450,7 @@ function createStoreInstance(Vue, id, options) {
396
450
  * 销毁 store
397
451
  *
398
452
  * v0.4 变更:兜底清理 interval
453
+ * v0.5 变更:调用 plugin destroy 钩子
399
454
  */
400
455
  store.$destroy = function () {
401
456
  if (store.$disposed) return;
@@ -406,6 +461,9 @@ function createStoreInstance(Vue, id, options) {
406
461
  // 兜底清理 interval(正常流程 leave 已经清过,这里防遗漏)
407
462
  clearAllIntervals();
408
463
 
464
+ // v0.5: plugin destroy hooks
465
+ _pluginHooks.forEach(function (h) { if (h.destroy) h.destroy(); });
466
+
409
467
  store.$disposed = true;
410
468
  Object.keys(_listeners).forEach(function (key) { delete _listeners[key]; });
411
469
  vm.$destroy();
@@ -414,6 +472,15 @@ function createStoreInstance(Vue, id, options) {
414
472
 
415
473
  store._vm = vm;
416
474
 
475
+ // ====== v0.5 新增:plugin 安装 ======
476
+ // 放在最后,此时 store 的 state/getters/actions/$源数据/$setInterval 等全部就绪
477
+ _plugins.forEach(function (plugin) {
478
+ var fieldValue = options[plugin.name];
479
+ if (fieldValue === undefined) return;
480
+ var hooks = plugin.install(store, fieldValue, { Vue: Vue });
481
+ if (hooks) _pluginHooks.push(hooks);
482
+ });
483
+
417
484
  return store;
418
485
  }
419
486
 
@@ -424,6 +491,10 @@ function createStoreInstance(Vue, id, options) {
424
491
  * - options 新增 source、enter、leave
425
492
  * - options 移除 lifecycle
426
493
  * - actions 中的 async 函数自动追踪 $loading
494
+ * v0.4.1 变更:
495
+ * - options 新增 init(bindTo 之后一次性调用)
496
+ * v0.5 变更:
497
+ * - options 中注册过的 plugin.name 字段会被交给对应 plugin 处理
427
498
  */
428
499
  function definePageStore(id, options) {
429
500
  // 入参校验
@@ -434,7 +505,7 @@ function definePageStore(id, options) {
434
505
  throw new Error('[vue-page-store] definePageStore("' + id + '") 需要 state 为函数');
435
506
  }
436
507
 
437
- var _Vue = null;
508
+ var _Vue = Vue;
438
509
 
439
510
  return function useStore(componentVm) {
440
511
  if (storeRegistry.has(id)) {
@@ -443,24 +514,23 @@ function definePageStore(id, options) {
443
514
  return existing;
444
515
  }
445
516
 
446
- if (!_Vue) {
447
- try {
448
- _Vue = require('vue');
449
- if (_Vue.default) _Vue = _Vue.default;
450
- } catch (e) {
451
- throw new Error(
452
- '[vue-page-store] 无法自动获取 Vue,请确保 vue 已安装'
453
- );
454
- }
455
- }
456
-
457
517
  var store = createStoreInstance(_Vue, id, options);
458
518
  storeRegistry.set(id, store);
459
519
  if (componentVm) store.bindTo(componentVm);
520
+
521
+ // v0.4.1 新增:init 钩子 —— bindTo 之后调用,$vm 已可用
522
+ if (typeof options.init === 'function') {
523
+ options.init.call(store);
524
+ }
525
+
460
526
  return store;
461
527
  };
462
528
  }
463
529
 
464
- var index = { definePageStore: definePageStore, storeRegistry: storeRegistry };
530
+ var index = {
531
+ definePageStore: definePageStore,
532
+ registerPlugin: registerPlugin,
533
+ storeRegistry: storeRegistry
534
+ };
465
535
 
466
- export { index as default, definePageStore, storeRegistry };
536
+ export { index as default, definePageStore, registerPlugin, storeRegistry };
package/dist/index.umd.js CHANGED
@@ -1,24 +1,24 @@
1
1
  /*!
2
- * vue-page-store v0.4.0
2
+ * vue-page-store v0.5.0
3
3
  * (c) 2026 weijianjun
4
4
  * @license MIT
5
5
  */
6
6
  (function (global, factory) {
7
- typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
8
- typeof define === 'function' && define.amd ? define(['exports'], factory) :
9
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.VuePageStore = {}));
10
- })(this, (function (exports) { 'use strict';
7
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('vue')) :
8
+ typeof define === 'function' && define.amd ? define(['exports', 'vue'], factory) :
9
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.VuePageStore = {}, global.Vue));
10
+ })(this, (function (exports, Vue) { 'use strict';
11
11
 
12
12
  /*!
13
- * vue-page-store v0.4.0
13
+ * vue-page-store v0.5.0
14
14
  * (c) 2026 weijianjun
15
15
  * @license MIT
16
16
  */
17
17
  /**
18
- * vue-page-store 0.4.0 — Vue 2.6 Page Scope Runtime
18
+ * vue-page-store 0.5.0 — Vue 2.6 Page Scope Runtime
19
19
  *
20
20
  * 页面级作用域运行时容器:
21
- * source · state · getters · actions · watch · enter/leave · $setInterval · event bus
21
+ * source · state · getters · actions · watch · init/enter/leave · $setInterval · event bus · plugin
22
22
  *
23
23
  * v0.4 新增:
24
24
  * source → 页面输入 / 原始返回,和业务 state 分开
@@ -26,12 +26,16 @@
26
26
  * $setInterval → 页面级 timer 托管,leave 时自动清理
27
27
  * $loading → async action 自动追踪 loading 状态
28
28
  *
29
+ * v0.4.1 新增:
30
+ * init → store 创建后一次性初始化钩子,$vm 已可用
31
+ *
32
+ * v0.5 新增:
33
+ * plugin → registerPlugin 注册外部扩展,声明字段 + 生命周期钩子
34
+ *
29
35
  * @author weijianjun
30
36
  * @license MIT
31
37
  */
32
38
 
33
- // Store 注册表(导出供调试 / devtools 使用)
34
- var storeRegistry = new Map();
35
39
 
36
40
  // ====== dev-only warning ======
37
41
  var isDev = typeof process !== 'undefined'
@@ -44,6 +48,47 @@
44
48
  }
45
49
  }
46
50
 
51
+ // Store 注册表(导出供调试 / devtools 使用)
52
+ var storeRegistry = new Map();
53
+
54
+ // v0.5 新增:dev 环境自动挂到 window,方便控制台调试
55
+ if (isDev && typeof window !== 'undefined') {
56
+ window.__VUE_PAGE_STORE__ = {
57
+ registry: storeRegistry,
58
+ get stores() {
59
+ var result = {};
60
+ storeRegistry.forEach(function (s, id) { result[id] = s; });
61
+ return result;
62
+ },
63
+ };
64
+ }
65
+
66
+ // ====== v0.5 新增:plugin 机制 ======
67
+ var _plugins = [];
68
+
69
+ /**
70
+ * 注册全局 plugin
71
+ *
72
+ * plugin.name 同时作为 options 字段匹配键:
73
+ * 当 definePageStore options 中存在 options[plugin.name] 时,
74
+ * page-store 会调用 plugin.install(store, fieldValue, { Vue })
75
+ *
76
+ * install 可返回 { enter, leave, destroy } 生命周期钩子
77
+ */
78
+ function registerPlugin(plugin) {
79
+ if (!plugin || typeof plugin.name !== 'string') {
80
+ throw new Error('[vue-page-store] plugin 需要一个 name 属性');
81
+ }
82
+ if (typeof plugin.install !== 'function') {
83
+ throw new Error('[vue-page-store] plugin "' + plugin.name + '" 需要一个 install 方法');
84
+ }
85
+ if (_plugins.some(function (p) { return p.name === plugin.name; })) {
86
+ warn('plugin "' + plugin.name + '" 已注册,跳过重复注册');
87
+ return;
88
+ }
89
+ _plugins.push(plugin);
90
+ }
91
+
47
92
  function createStoreInstance(Vue, id, options) {
48
93
  var initialState = options.state();
49
94
 
@@ -57,6 +102,9 @@
57
102
  var enterHook = typeof options.enter === 'function' ? options.enter : null;
58
103
  var leaveHook = typeof options.leave === 'function' ? options.leave : null;
59
104
 
105
+ // ====== v0.5 新增:plugin hooks 闭包存储(由 install 填充) ======
106
+ var _pluginHooks = [];
107
+
60
108
  // --- 用一个隐藏的 Vue 实例承载响应式 state + source + loading + computed getters ---
61
109
  var computedDefs = {};
62
110
  var store = { $disposed: false };
@@ -324,6 +372,8 @@
324
372
  enterHook.call(store);
325
373
  }
326
374
  store.$emit('page:enter');
375
+ // v0.5: plugin enter hooks
376
+ _pluginHooks.forEach(function (h) { if (h.enter) h.enter(); });
327
377
  }
328
378
 
329
379
  function runLeave() {
@@ -336,6 +386,8 @@
336
386
  leaveHook.call(store);
337
387
  }
338
388
  store.$emit('page:leave');
389
+ // v0.5: plugin leave hooks
390
+ _pluginHooks.forEach(function (h) { if (h.leave) h.leave(); });
339
391
  }
340
392
 
341
393
  // ====== bindTo 去重标记 ======
@@ -402,6 +454,7 @@
402
454
  * 销毁 store
403
455
  *
404
456
  * v0.4 变更:兜底清理 interval
457
+ * v0.5 变更:调用 plugin destroy 钩子
405
458
  */
406
459
  store.$destroy = function () {
407
460
  if (store.$disposed) return;
@@ -412,6 +465,9 @@
412
465
  // 兜底清理 interval(正常流程 leave 已经清过,这里防遗漏)
413
466
  clearAllIntervals();
414
467
 
468
+ // v0.5: plugin destroy hooks
469
+ _pluginHooks.forEach(function (h) { if (h.destroy) h.destroy(); });
470
+
415
471
  store.$disposed = true;
416
472
  Object.keys(_listeners).forEach(function (key) { delete _listeners[key]; });
417
473
  vm.$destroy();
@@ -420,6 +476,15 @@
420
476
 
421
477
  store._vm = vm;
422
478
 
479
+ // ====== v0.5 新增:plugin 安装 ======
480
+ // 放在最后,此时 store 的 state/getters/actions/$源数据/$setInterval 等全部就绪
481
+ _plugins.forEach(function (plugin) {
482
+ var fieldValue = options[plugin.name];
483
+ if (fieldValue === undefined) return;
484
+ var hooks = plugin.install(store, fieldValue, { Vue: Vue });
485
+ if (hooks) _pluginHooks.push(hooks);
486
+ });
487
+
423
488
  return store;
424
489
  }
425
490
 
@@ -430,6 +495,10 @@
430
495
  * - options 新增 source、enter、leave
431
496
  * - options 移除 lifecycle
432
497
  * - actions 中的 async 函数自动追踪 $loading
498
+ * v0.4.1 变更:
499
+ * - options 新增 init(bindTo 之后一次性调用)
500
+ * v0.5 变更:
501
+ * - options 中注册过的 plugin.name 字段会被交给对应 plugin 处理
433
502
  */
434
503
  function definePageStore(id, options) {
435
504
  // 入参校验
@@ -440,7 +509,7 @@
440
509
  throw new Error('[vue-page-store] definePageStore("' + id + '") 需要 state 为函数');
441
510
  }
442
511
 
443
- var _Vue = null;
512
+ var _Vue = Vue;
444
513
 
445
514
  return function useStore(componentVm) {
446
515
  if (storeRegistry.has(id)) {
@@ -449,28 +518,28 @@
449
518
  return existing;
450
519
  }
451
520
 
452
- if (!_Vue) {
453
- try {
454
- _Vue = require('vue');
455
- if (_Vue.default) _Vue = _Vue.default;
456
- } catch (e) {
457
- throw new Error(
458
- '[vue-page-store] 无法自动获取 Vue,请确保 vue 已安装'
459
- );
460
- }
461
- }
462
-
463
521
  var store = createStoreInstance(_Vue, id, options);
464
522
  storeRegistry.set(id, store);
465
523
  if (componentVm) store.bindTo(componentVm);
524
+
525
+ // v0.4.1 新增:init 钩子 —— bindTo 之后调用,$vm 已可用
526
+ if (typeof options.init === 'function') {
527
+ options.init.call(store);
528
+ }
529
+
466
530
  return store;
467
531
  };
468
532
  }
469
533
 
470
- var index = { definePageStore: definePageStore, storeRegistry: storeRegistry };
534
+ var index = {
535
+ definePageStore: definePageStore,
536
+ registerPlugin: registerPlugin,
537
+ storeRegistry: storeRegistry
538
+ };
471
539
 
472
540
  exports.default = index;
473
541
  exports.definePageStore = definePageStore;
542
+ exports.registerPlugin = registerPlugin;
474
543
  exports.storeRegistry = storeRegistry;
475
544
 
476
545
  Object.defineProperty(exports, '__esModule', { value: true });
@@ -1,19 +1,19 @@
1
1
  /*!
2
- * vue-page-store v0.4.0
2
+ * vue-page-store v0.5.0
3
3
  * (c) 2026 weijianjun
4
4
  * @license MIT
5
5
  */
6
- !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).VuePageStore={})}(this,function(e){"use strict";
6
+ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("vue")):"function"==typeof define&&define.amd?define(["exports","vue"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).VuePageStore={},e.Vue)}(this,function(e,t){"use strict";
7
7
  /*!
8
- * vue-page-store v0.4.0
8
+ * vue-page-store v0.5.0
9
9
  * (c) 2026 weijianjun
10
10
  * @license MIT
11
11
  */
12
12
  /**
13
- * vue-page-store 0.4.0 — Vue 2.6 Page Scope Runtime
13
+ * vue-page-store 0.5.0 — Vue 2.6 Page Scope Runtime
14
14
  *
15
15
  * 页面级作用域运行时容器:
16
- * source · state · getters · actions · watch · enter/leave · $setInterval · event bus
16
+ * source · state · getters · actions · watch · init/enter/leave · $setInterval · event bus · plugin
17
17
  *
18
18
  * v0.4 新增:
19
19
  * source → 页面输入 / 原始返回,和业务 state 分开
@@ -21,6 +21,12 @@
21
21
  * $setInterval → 页面级 timer 托管,leave 时自动清理
22
22
  * $loading → async action 自动追踪 loading 状态
23
23
  *
24
+ * v0.4.1 新增:
25
+ * init → store 创建后一次性初始化钩子,$vm 已可用
26
+ *
27
+ * v0.5 新增:
28
+ * plugin → registerPlugin 注册外部扩展,声明字段 + 生命周期钩子
29
+ *
24
30
  * @author weijianjun
25
31
  * @license MIT
26
- */var t=new Map,n="undefined"!=typeof process&&process.env&&"production"!==process.env.NODE_ENV;function o(e){n&&console.warn("[vue-page-store] "+e)}function r(e,n,r){var i=r.state(),c="function"==typeof r.source?r.source():{},u=r.getters||{},f=r.actions||{},a="function"==typeof r.enter?r.enter:null,s="function"==typeof r.leave?r.leave:null,d={},l={$disposed:!1};Object.keys(u).forEach(function(e){d[e]=function(){return u[e].call(l)}});var p=new e({data:function(){return{$$state:i,$$source:c,$$loading:{},$$status:{mounted:!1,active:!1}}},computed:d}),v=p.$data.$$state,$=p.$data.$$source,h=p.$data.$$loading,y=p.$data.$$status;Object.keys(i).forEach(function(e){Object.defineProperty(l,e,{enumerable:!0,configurable:!0,get:function(){return v[e]},set:function(t){l.$disposed?o('store "'+n+'" 已销毁,忽略对 "'+e+'" 的写入'):v[e]=t}})}),l.$source=$,l.$loading=h,Object.keys(u).forEach(function(e){Object.defineProperty(l,e,{enumerable:!0,get:function(){return p[e]}})});var b={};function g(t){b[t]--,b[t]<=0&&(b[t]=0,e.set(h,t,!1))}Object.keys(f).forEach(function(t){var n=f[t].bind(l);l[t]=function(){var o=n.apply(null,arguments);o&&"function"==typeof o.then&&(b[t]||(b[t]=0),b[t]++,e.set(h,t,!0),Promise.resolve(o).then(function(){g(t)},function(){g(t)}));return o}});var m=r.watch||{};Object.entries(m).forEach(function(e){var t,r,i=e[0],c=e[1];if("function"==typeof c)t=c,r={};else if(t=c.handler,r={},c.deep&&(r.deep=!0),c.immediate&&(r.immediate=!0),!t)return void o('watch "'+i+'" in store "'+n+'" 缺少 handler,该 watcher 将被跳过');p.$watch(function(){return i.split(".").reduce(function(e,t){return e&&e[t]},l)},t.bind(l),r)}),l.$state=v,l.$status=y,l.$id=n;var O=null;Object.defineProperty(l,"$vm",{enumerable:!0,configurable:!1,get:function(){return O},set:function(){o("$vm 是只读属性,不允许业务侧重写")}}),l.$patch=function(t){if(l.$disposed)o('store "'+n+'" 已销毁,忽略 $patch 操作');else{var r="function"==typeof t?t(v):t;Object.keys(r).forEach(function(t){e.set(v,t,r[t])})}},l.$reset=function(){var t=r.state();Object.keys(t).forEach(function(n){e.set(v,n,t[n])}),Object.keys(v).forEach(function(n){n in t||e.delete(v,n)});var n="function"==typeof r.source?r.source():{};Object.keys(n).forEach(function(t){e.set($,t,n[t])}),Object.keys($).forEach(function(t){t in n||e.delete($,t)})};var E={};l.$emit=function(e,t){var n=E[e];n&&n.slice().forEach(function(e){e(t)})},l.$on=function(e,t){return E[e]||(E[e]=[]),E[e].push(t),function(){if(E[e]){var n=E[e].indexOf(t);n>-1&&E[e].splice(n,1)}}},l.$off=function(e,t){if(E[e])if(t){var n=E[e].indexOf(t);n>-1&&E[e].splice(n,1)}else delete E[e]};var j=[];function k(){j.forEach(function(e){e.stopped||(clearInterval(e.id),e.stopped=!0)}),j.length=0}l.$setInterval=function(e,t){var n={id:setInterval(e,t),stopped:!1};j.push(n);return function(){if(!n.stopped){clearInterval(n.id),n.stopped=!0;var e=j.indexOf(n);e>-1&&j.splice(e,1)}}};var w=!1;function P(){w||(w=!0,y.mounted=!0,y.active=!0,a&&a.call(l),l.$emit("page:enter"))}function S(){w&&(k(),w=!1,y.active=!1,s&&s.call(l),l.$emit("page:leave"))}var x="undefined"!=typeof WeakSet?new WeakSet:null,_=x?null:[];return l.bindTo=function(e){return l.$disposed||function(e){return x?x.has(e):_.indexOf(e)>-1}(e)||(function(e){x?x.add(e):_.push(e)}(e),O=e,(e._provided||(e._provided={})).pageStore=l,e.$on("hook:mounted",function(){P()}),e.$on("hook:activated",function(){P()}),e.$on("hook:deactivated",function(){S()}),e.$on("hook:beforeDestroy",function(){S(),l.$destroy()})),l},l.$destroy=function(){l.$disposed||(y.mounted=!1,y.active=!1,k(),l.$disposed=!0,Object.keys(E).forEach(function(e){delete E[e]}),p.$destroy(),t.delete(n))},l._vm=p,l}function i(e,n){if(!e||"string"!=typeof e)throw new Error("[vue-page-store] definePageStore 需要一个非空字符串作为 id");if(!n||"function"!=typeof n.state)throw new Error('[vue-page-store] definePageStore("'+e+'") 需要 state 为函数');var o=null;return function(i){if(t.has(e)){var c=t.get(e);return i&&c.bindTo(i),c}if(!o)try{(o=require("vue")).default&&(o=o.default)}catch(e){throw new Error("[vue-page-store] 无法自动获取 Vue,请确保 vue 已安装")}var u=r(o,e,n);return t.set(e,u),i&&u.bindTo(i),u}}var c={definePageStore:i,storeRegistry:t};e.default=c,e.definePageStore=i,e.storeRegistry=t,Object.defineProperty(e,"__esModule",{value:!0})});
32
+ */var n="undefined"!=typeof process&&process.env&&"production"!==process.env.NODE_ENV;function o(e){n&&console.warn("[vue-page-store] "+e)}var r=new Map;n&&"undefined"!=typeof window&&(window.__VUE_PAGE_STORE__={registry:r,get stores(){var e={};return r.forEach(function(t,n){e[n]=t}),e}});var i=[];function u(e){if(!e||"string"!=typeof e.name)throw new Error("[vue-page-store] plugin 需要一个 name 属性");if("function"!=typeof e.install)throw new Error('[vue-page-store] plugin "'+e.name+'" 需要一个 install 方法');i.some(function(t){return t.name===e.name})?o('plugin "'+e.name+'" 已注册,跳过重复注册'):i.push(e)}function a(e,t,n){var u=n.state(),a="function"==typeof n.source?n.source():{},f=n.getters||{},c=n.actions||{},s="function"==typeof n.enter?n.enter:null,d="function"==typeof n.leave?n.leave:null,l=[],p={},v={$disposed:!1};Object.keys(f).forEach(function(e){p[e]=function(){return f[e].call(v)}});var $=new e({data:function(){return{$$state:u,$$source:a,$$loading:{},$$status:{mounted:!1,active:!1}}},computed:p}),h=$.$data.$$state,y=$.$data.$$source,g=$.$data.$$loading,b=$.$data.$$status;Object.keys(u).forEach(function(e){Object.defineProperty(v,e,{enumerable:!0,configurable:!0,get:function(){return h[e]},set:function(n){v.$disposed?o('store "'+t+'" 已销毁,忽略对 "'+e+'" 的写入'):h[e]=n}})}),v.$source=y,v.$loading=g,Object.keys(f).forEach(function(e){Object.defineProperty(v,e,{enumerable:!0,get:function(){return $[e]}})});var m={};function E(t){m[t]--,m[t]<=0&&(m[t]=0,e.set(g,t,!1))}Object.keys(c).forEach(function(t){var n=c[t].bind(v);v[t]=function(){var o=n.apply(null,arguments);o&&"function"==typeof o.then&&(m[t]||(m[t]=0),m[t]++,e.set(g,t,!0),Promise.resolve(o).then(function(){E(t)},function(){E(t)}));return o}});var O=n.watch||{};Object.entries(O).forEach(function(e){var n,r,i=e[0],u=e[1];if("function"==typeof u)n=u,r={};else if(n=u.handler,r={},u.deep&&(r.deep=!0),u.immediate&&(r.immediate=!0),!n)return void o('watch "'+i+'" in store "'+t+'" 缺少 handler,该 watcher 将被跳过');$.$watch(function(){return i.split(".").reduce(function(e,t){return e&&e[t]},v)},n.bind(v),r)}),v.$state=h,v.$status=b,v.$id=t;var w=null;Object.defineProperty(v,"$vm",{enumerable:!0,configurable:!1,get:function(){return w},set:function(){o("$vm 是只读属性,不允许业务侧重写")}}),v.$patch=function(n){if(v.$disposed)o('store "'+t+'" 已销毁,忽略 $patch 操作');else{var r="function"==typeof n?n(h):n;Object.keys(r).forEach(function(t){e.set(h,t,r[t])})}},v.$reset=function(){var t=n.state();Object.keys(t).forEach(function(n){e.set(h,n,t[n])}),Object.keys(h).forEach(function(n){n in t||e.delete(h,n)});var o="function"==typeof n.source?n.source():{};Object.keys(o).forEach(function(t){e.set(y,t,o[t])}),Object.keys(y).forEach(function(t){t in o||e.delete(y,t)})};var j={};v.$emit=function(e,t){var n=j[e];n&&n.slice().forEach(function(e){e(t)})},v.$on=function(e,t){return j[e]||(j[e]=[]),j[e].push(t),function(){if(j[e]){var n=j[e].indexOf(t);n>-1&&j[e].splice(n,1)}}},v.$off=function(e,t){if(j[e])if(t){var n=j[e].indexOf(t);n>-1&&j[e].splice(n,1)}else delete j[e]};var k=[];function P(){k.forEach(function(e){e.stopped||(clearInterval(e.id),e.stopped=!0)}),k.length=0}v.$setInterval=function(e,t){var n={id:setInterval(e,t),stopped:!1};k.push(n);return function(){if(!n.stopped){clearInterval(n.id),n.stopped=!0;var e=k.indexOf(n);e>-1&&k.splice(e,1)}}};var _=!1;function S(){_||(_=!0,b.mounted=!0,b.active=!0,s&&s.call(v),v.$emit("page:enter"),l.forEach(function(e){e.enter&&e.enter()}))}function x(){_&&(P(),_=!1,b.active=!1,d&&d.call(v),v.$emit("page:leave"),l.forEach(function(e){e.leave&&e.leave()}))}var T="undefined"!=typeof WeakSet?new WeakSet:null,V=T?null:[];return v.bindTo=function(e){return v.$disposed||function(e){return T?T.has(e):V.indexOf(e)>-1}(e)||(function(e){T?T.add(e):V.push(e)}(e),w=e,(e._provided||(e._provided={})).pageStore=v,e.$on("hook:mounted",function(){S()}),e.$on("hook:activated",function(){S()}),e.$on("hook:deactivated",function(){x()}),e.$on("hook:beforeDestroy",function(){x(),v.$destroy()})),v},v.$destroy=function(){v.$disposed||(b.mounted=!1,b.active=!1,P(),l.forEach(function(e){e.destroy&&e.destroy()}),v.$disposed=!0,Object.keys(j).forEach(function(e){delete j[e]}),$.$destroy(),r.delete(t))},v._vm=$,i.forEach(function(t){var o=n[t.name];if(void 0!==o){var r=t.install(v,o,{Vue:e});r&&l.push(r)}}),v}function f(e,n){if(!e||"string"!=typeof e)throw new Error("[vue-page-store] definePageStore 需要一个非空字符串作为 id");if(!n||"function"!=typeof n.state)throw new Error('[vue-page-store] definePageStore("'+e+'") 需要 state 为函数');var o=t;return function(t){if(r.has(e)){var i=r.get(e);return t&&i.bindTo(t),i}var u=a(o,e,n);return r.set(e,u),t&&u.bindTo(t),"function"==typeof n.init&&n.init.call(u),u}}var c={definePageStore:f,registerPlugin:u,storeRegistry:r};e.default=c,e.definePageStore=f,e.registerPlugin=u,e.storeRegistry=r,Object.defineProperty(e,"__esModule",{value:!0})});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vue-page-store",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Vue 2.6 页面级 Store —— 状态、通信、生命周期,一个作用域全收",
5
5
  "author": "weijianjun",
6
6
  "license": "MIT",