vue-page-store 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 weijianjun
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,180 @@
1
+ # vue-page-store
2
+
3
+ > Vue 2.6 页面级 Store —— 状态、通信、生命周期,一个作用域全收。
4
+
5
+ ## 为什么需要它?
6
+
7
+ 在微前端架构(single-spa / qiankun)下,复杂页面(仪表盘、漏斗分析、数据详情)的状态管理面临一个尴尬处境:
8
+
9
+ | 方案 | 问题 |
10
+ |------|------|
11
+ | **Vuex** | 全局 store,页面销毁后状态残留,命名冲突,不适合页面级生命周期 |
12
+ | **全局 EventBus** | 命名冲突、手动 $off 容易遗漏、事件扩散到全局 |
13
+ | **provide / inject** | 只传数据,不管通信和副作用 |
14
+ | **组件 data** | 跨组件共享困难,深层传递 props 地狱 |
15
+
16
+ `vue-page-store` 解决的就是这个中间地带:**页面内部需要共享状态 + 通信 + 副作用管理,但不应该污染全局。**
17
+
18
+ ## 核心特性
19
+
20
+ - **页面级作用域隔离** — 每个 store 独立一份 state 和事件,互不干扰
21
+ - **`$destroy` 一键回收** — 页面销毁时自动清空 state、watchers、事件监听,零泄漏
22
+ - **API 对齐 Pinia** — `state` / `getters` / `actions` / `$patch` / `$subscribe` / `$reset`,零学习成本
23
+ - **内置作用域事件** — `$emit` / `$on` / `$off` 限定在当前 store 内,替代全局 EventBus
24
+ - **声明式 watch** — 页面级副作用自动绑定生命周期
25
+ - **TypeScript 支持** — 开箱即用的类型定义
26
+
27
+ ## 安装
28
+
29
+ ```bash
30
+ npm install vue-page-store
31
+ ```
32
+
33
+ > **前置条件**:项目中已安装 `vue@^2.6.0`
34
+
35
+ ## 快速上手
36
+
37
+ ### 1. 定义 Store
38
+
39
+ ```javascript
40
+ // store.js
41
+ import { definePageStore } from 'vue-page-store';
42
+
43
+ export const useFunnelStore = definePageStore('funnelDetail', {
44
+ state: () => ({
45
+ filters: { dateRange: [], platform: '' },
46
+ loading: false,
47
+ funnelSteps: [],
48
+ }),
49
+
50
+ getters: {
51
+ isReady() {
52
+ return !this.loading && this.funnelSteps.length > 0;
53
+ },
54
+ },
55
+
56
+ actions: {
57
+ async fetchData() {
58
+ this.loading = true;
59
+ try {
60
+ this.funnelSteps = await api.getFunnelSteps(this.filters);
61
+ } finally {
62
+ this.loading = false;
63
+ }
64
+ },
65
+ },
66
+
67
+ watch: {
68
+ 'filters.platform'(newVal) {
69
+ // platform 变了自动重新拉数据
70
+ this.fetchData();
71
+ },
72
+ },
73
+ });
74
+ ```
75
+
76
+ ### 2. 组件中使用
77
+
78
+ ```javascript
79
+ // 任意子组件
80
+ export default {
81
+ computed: {
82
+ store() {
83
+ return useFunnelStore();
84
+ },
85
+ isReady() {
86
+ return this.store.isReady;
87
+ },
88
+ },
89
+ methods: {
90
+ handleSearch() {
91
+ this.store.$patch({ filters: this.localFilters });
92
+ this.store.fetchData();
93
+ },
94
+ },
95
+ };
96
+ ```
97
+
98
+ ### 3. 页面内通信
99
+
100
+ ```javascript
101
+ // 组件 A —— 发射事件
102
+ this.store.$emit('filter:change', newFilters);
103
+
104
+ // 组件 B —— 监听事件
105
+ const off = this.store.$on('filter:change', (filters) => {
106
+ this.applyFilters(filters);
107
+ });
108
+
109
+ // 无需手动清理 —— $destroy 时自动回收
110
+ ```
111
+
112
+ ### 4. 页面销毁时回收
113
+
114
+ ```javascript
115
+ // 页面根组件
116
+ export default {
117
+ beforeDestroy() {
118
+ useFunnelStore().$destroy();
119
+ },
120
+ };
121
+ ```
122
+
123
+ ## API
124
+
125
+ ### `definePageStore(id, options)`
126
+
127
+ 定义一个页面级 Store,返回 `useStore` 函数。
128
+
129
+ | 参数 | 类型 | 说明 |
130
+ |------|------|------|
131
+ | `id` | `string` | 唯一标识 |
132
+ | `options.state` | `() => Object` | 返回初始 state 的工厂函数 |
133
+ | `options.getters` | `Object` | 计算属性,`this` 指向 store |
134
+ | `options.actions` | `Object` | 方法,`this` 指向 store |
135
+ | `options.watch` | `Object` | 声明式侦听,支持点路径 `'a.b.c'` |
136
+
137
+ ### Store 实例方法
138
+
139
+ | 方法 | 说明 |
140
+ |------|------|
141
+ | `$patch(partial)` | 浅合并更新 state,接受对象或 `(state) => Object` 函数 |
142
+ | `$subscribe(cb)` | 订阅 state 变化,返回取消函数 |
143
+ | `$reset()` | 重置 state 到初始值 |
144
+ | `$emit(event, payload?)` | 发射事件(仅当前 store 作用域) |
145
+ | `$on(event, handler)` | 订阅事件,返回取消函数 |
146
+ | `$off(event, handler?)` | 取消事件订阅(不传 handler 则取消该事件全部监听) |
147
+ | `$destroy()` | 销毁 store,回收所有资源 |
148
+
149
+ ### `storeToRefs(store)`
150
+
151
+ 将 store 的 state 属性转为可解构的 refs 对象。
152
+
153
+ ```javascript
154
+ import { storeToRefs } from 'vue-page-store';
155
+
156
+ const store = useFunnelStore();
157
+ const { filters, loading } = storeToRefs(store);
158
+ ```
159
+
160
+ ## 与 Pinia / Vuex 的关系
161
+
162
+ 这不是 Pinia 或 Vuex 的替代品,而是补充:
163
+
164
+ | | Vuex | Pinia | vue-page-store |
165
+ |---|---|---|---|
166
+ | 作用域 | 全局 | 全局 | 页面级 |
167
+ | 生命周期 | 应用级 | 应用级 | 页面级(手动 $destroy) |
168
+ | 事件通信 | 无 | 无 | 内置 $emit/$on |
169
+ | Vue 2.6 支持 | ✅ | ⚠️ 需 @vue/composition-api | ✅ 原生支持 |
170
+ | 适用场景 | 用户信息、权限、全局配置 | 同 Vuex | 复杂页面内部状态 |
171
+
172
+ **推荐组合**:Vuex 管全局,vue-page-store 管页面。
173
+
174
+ ## Vue 3
175
+
176
+ 本库专为 Vue 2.6 设计。Vue 3 项目推荐使用 [Pinia](https://pinia.vuejs.org/)。
177
+
178
+ ## License
179
+
180
+ [MIT](./LICENSE)
@@ -0,0 +1,264 @@
1
+ /*!
2
+ * vue-page-store v0.1.0
3
+ * (c) 2026 weijianjun
4
+ * @license MIT
5
+ */
6
+ 'use strict';
7
+
8
+ Object.defineProperty(exports, '__esModule', { value: true });
9
+
10
+ /**
11
+ * vue-page-store - Vue 2.6 页面级 Store
12
+ *
13
+ * 状态、通信、生命周期,一个作用域全收。
14
+ *
15
+ * 与 Vuex 的区分:
16
+ * Vuex → 全局状态(用户信息、权限、路由等)
17
+ * pageStore → 页面级状态(仪表盘、漏斗详情等复杂页面内部状态)
18
+ * 页面销毁时 $destroy 即可回收,不污染全局
19
+ *
20
+ * @author weijianjun
21
+ * @license MIT
22
+ */
23
+
24
+ // Store 注册表(导出供调试 / devtools 使用)
25
+ const storeRegistry = new Map();
26
+
27
+ function createStoreInstance(Vue, id, options) {
28
+ const initialState = options.state();
29
+ const getters = options.getters || {};
30
+ const actions = options.actions || {};
31
+
32
+ // --- 用一个隐藏的 Vue 实例承载响应式 state + computed getters ---
33
+ const computedDefs = {};
34
+ const store = { _disposed: false };
35
+
36
+ Object.keys(getters).forEach(function (key) {
37
+ computedDefs[key] = function () {
38
+ return getters[key].call(store);
39
+ };
40
+ });
41
+
42
+ const vm = new Vue({
43
+ data: function () {
44
+ return { $$state: initialState };
45
+ },
46
+ computed: computedDefs,
47
+ });
48
+
49
+ const rawState = vm.$data.$$state;
50
+
51
+ // ====== state —— 代理到 vm.$$state ======
52
+ Object.keys(initialState).forEach(function (key) {
53
+ Object.defineProperty(store, key, {
54
+ enumerable: true,
55
+ get: function () { return rawState[key]; },
56
+ set: function (val) { rawState[key] = val; },
57
+ });
58
+ });
59
+
60
+ // ====== getters —— 代理到 vm computed ======
61
+ Object.keys(getters).forEach(function (key) {
62
+ Object.defineProperty(store, key, {
63
+ enumerable: true,
64
+ get: function () { return vm[key]; },
65
+ });
66
+ });
67
+
68
+ // ====== actions ======
69
+ Object.keys(actions).forEach(function (key) {
70
+ store[key] = actions[key].bind(store);
71
+ });
72
+
73
+ // ====== watch —— 声明式副作用,生命周期自动回收 ======
74
+ const watches = options.watch || {};
75
+ Object.entries(watches).forEach(function (_ref) {
76
+ const path = _ref[0];
77
+ const def = _ref[1];
78
+ const handler = typeof def === 'function' ? def : def.handler;
79
+ const watchOpts = { deep: true };
80
+ if (typeof def === 'object' && def.immediate) watchOpts.immediate = true;
81
+ const expr = function () {
82
+ return path.split('.').reduce(function (obj, k) { return obj && obj[k]; }, store);
83
+ };
84
+ vm.$watch(expr, handler.bind(store), watchOpts);
85
+ });
86
+
87
+ // ====== 内置方法 ======
88
+ store.$state = rawState;
89
+ store.$id = id;
90
+
91
+ /**
92
+ * 批量更新 state(浅合并语义)
93
+ * @param {Object|Function} partial - 要合并的对象,或 (state) => Object 函数
94
+ */
95
+ store.$patch = function (partial) {
96
+ const obj = typeof partial === 'function' ? partial(rawState) : partial;
97
+ Object.keys(obj).forEach(function (key) {
98
+ Vue.set(rawState, key, obj[key]);
99
+ });
100
+ };
101
+
102
+ /**
103
+ * 订阅 state 变化
104
+ * @param {Function} callback - (newState) => void
105
+ * @returns {Function} 取消订阅函数
106
+ */
107
+ store.$subscribe = function (callback) {
108
+ return vm.$watch(
109
+ function () { return Object.assign({}, rawState); },
110
+ callback,
111
+ { deep: true }
112
+ );
113
+ };
114
+
115
+ /**
116
+ * 重置 state 到初始值
117
+ */
118
+ store.$reset = function () {
119
+ const fresh = options.state();
120
+ Object.keys(fresh).forEach(function (key) {
121
+ rawState[key] = fresh[key];
122
+ });
123
+ };
124
+
125
+ // ====== 内置事件总线(页面作用域隔离通信) ======
126
+ const _listeners = {};
127
+
128
+ /**
129
+ * 发射事件(仅当前 store 作用域内)
130
+ * @param {string} event - 事件名
131
+ * @param {*} payload - 事件数据
132
+ */
133
+ store.$emit = function (event, payload) {
134
+ const fns = _listeners[event];
135
+ if (fns) fns.slice().forEach(function (fn) { fn(payload); });
136
+ };
137
+
138
+ /**
139
+ * 订阅事件
140
+ * @param {string} event - 事件名
141
+ * @param {Function} handler - 处理函数
142
+ * @returns {Function} 取消订阅函数
143
+ */
144
+ store.$on = function (event, handler) {
145
+ if (!_listeners[event]) _listeners[event] = [];
146
+ _listeners[event].push(handler);
147
+ return function () {
148
+ const idx = _listeners[event].indexOf(handler);
149
+ if (idx > -1) _listeners[event].splice(idx, 1);
150
+ };
151
+ };
152
+
153
+ /**
154
+ * 取消订阅指定事件的所有 handler,或指定 handler
155
+ * @param {string} event - 事件名
156
+ * @param {Function} [handler] - 可选,指定取消某个 handler
157
+ */
158
+ store.$off = function (event, handler) {
159
+ if (!_listeners[event]) return;
160
+ if (handler) {
161
+ const idx = _listeners[event].indexOf(handler);
162
+ if (idx > -1) _listeners[event].splice(idx, 1);
163
+ } else {
164
+ delete _listeners[event];
165
+ }
166
+ };
167
+
168
+ /**
169
+ * 销毁 store —— 清空事件、销毁 vm、移除注册
170
+ */
171
+ store.$destroy = function () {
172
+ store._disposed = true;
173
+ Object.keys(_listeners).forEach(function (key) { delete _listeners[key]; });
174
+ vm.$destroy();
175
+ storeRegistry.delete(id);
176
+ };
177
+
178
+ store._vm = vm;
179
+
180
+ return store;
181
+ }
182
+
183
+ /**
184
+ * 定义页面级 Store
185
+ *
186
+ * @param {string} id - 唯一标识
187
+ * @param {Object} options - { state, getters, actions, watch }
188
+ * @returns {Function} useStore - 调用即获取 / 创建 store 实例
189
+ *
190
+ * @example
191
+ * const useFunnelStore = definePageStore('funnelDetail', {
192
+ * state: () => ({ filters: {}, loading: false }),
193
+ * getters: {
194
+ * isReady() { return !this.loading; }
195
+ * },
196
+ * actions: {
197
+ * async fetchData() { ... }
198
+ * }
199
+ * });
200
+ *
201
+ * // 组件中
202
+ * const store = useFunnelStore();
203
+ * store.fetchData();
204
+ *
205
+ * // 页面销毁时
206
+ * store.$destroy();
207
+ */
208
+ function definePageStore(id, options) {
209
+ if (!id || typeof options.state !== 'function') {
210
+ throw new Error('[vue-page-store] 需要 id 和 state 函数');
211
+ }
212
+
213
+ // 缓存 Vue 引用,首次调用时从 store 的 vm 实例获取
214
+ let _Vue = null;
215
+
216
+ return function useStore() {
217
+ if (storeRegistry.has(id)) {
218
+ return storeRegistry.get(id);
219
+ }
220
+
221
+ // 自动获取 Vue 构造函数
222
+ if (!_Vue) {
223
+ try {
224
+ _Vue = require('vue');
225
+ if (_Vue.default) _Vue = _Vue.default;
226
+ } catch (e) {
227
+ throw new Error(
228
+ '[vue-page-store] 无法自动获取 Vue,请确保 vue 已安装'
229
+ );
230
+ }
231
+ }
232
+
233
+ const store = createStoreInstance(_Vue, id, options);
234
+ storeRegistry.set(id, store);
235
+ return store;
236
+ };
237
+ }
238
+
239
+ /**
240
+ * 将 store 的 state 属性转为可在模板中使用的 refs 对象
241
+ *
242
+ * @param {Object} store - pageStore 实例
243
+ * @returns {Object} refs 对象(可解构赋值到 computed)
244
+ *
245
+ * @example
246
+ * const { filters, loading } = storeToRefs(store);
247
+ */
248
+ function storeToRefs(store) {
249
+ const refs = {};
250
+ Object.keys(store.$state).forEach(function (key) {
251
+ Object.defineProperty(refs, key, {
252
+ enumerable: true,
253
+ get: function () { return store[key]; },
254
+ set: function (val) { store[key] = val; },
255
+ });
256
+ });
257
+ return refs;
258
+ }
259
+ var index = { definePageStore, storeToRefs, storeRegistry };
260
+
261
+ exports.default = index;
262
+ exports.definePageStore = definePageStore;
263
+ exports.storeRegistry = storeRegistry;
264
+ exports.storeToRefs = storeToRefs;
@@ -0,0 +1,75 @@
1
+ /**
2
+ * vue-page-store - TypeScript 类型定义
3
+ */
4
+
5
+ export interface StoreOptions<S extends Record<string, any>> {
6
+ /** 返回初始 state 的工厂函数 */
7
+ state: () => S;
8
+ /** 计算属性(getter 内部 this 指向 store) */
9
+ getters?: Record<string, (this: Store<S>) => any>;
10
+ /** 操作方法(action 内部 this 指向 store) */
11
+ actions?: Record<string, (this: Store<S>, ...args: any[]) => any>;
12
+ /** 声明式侦听器(支持点路径 'a.b.c' 或对象配置) */
13
+ watch?: Record<
14
+ string,
15
+ | ((this: Store<S>, newVal: any, oldVal: any) => void)
16
+ | {
17
+ handler: (this: Store<S>, newVal: any, oldVal: any) => void;
18
+ immediate?: boolean;
19
+ }
20
+ >;
21
+ }
22
+
23
+ export interface Store<S extends Record<string, any>> {
24
+ /** Store 唯一标识 */
25
+ readonly $id: string;
26
+ /** 原始响应式 state 对象 */
27
+ readonly $state: S;
28
+
29
+ /** 批量更新 state(浅合并) */
30
+ $patch(partial: Partial<S>): void;
31
+ $patch(fn: (state: S) => Partial<S>): void;
32
+
33
+ /** 订阅 state 变化 */
34
+ $subscribe(callback: (newState: S) => void): () => void;
35
+
36
+ /** 重置 state 到初始值 */
37
+ $reset(): void;
38
+
39
+ /** 发射事件(仅当前 store 作用域) */
40
+ $emit(event: string, payload?: any): void;
41
+
42
+ /** 订阅事件,返回取消函数 */
43
+ $on(event: string, handler: (payload?: any) => void): () => void;
44
+
45
+ /** 取消订阅事件 */
46
+ $off(event: string, handler?: (payload?: any) => void): void;
47
+
48
+ /** 销毁 store,释放所有资源 */
49
+ $destroy(): void;
50
+ }
51
+
52
+ /** Store 实例类型 = state 属性 + getters + actions + 内置方法 */
53
+ export type PageStore<S extends Record<string, any>> = Store<S> & S;
54
+
55
+ /**
56
+ * 定义页面级 Store
57
+ *
58
+ * @param id - 唯一标识
59
+ * @param options - store 配置
60
+ * @returns useStore 函数,调用即获取 / 创建 store 实例
61
+ */
62
+ export declare function definePageStore<S extends Record<string, any>>(
63
+ id: string,
64
+ options: StoreOptions<S>
65
+ ): () => PageStore<S>;
66
+
67
+ /**
68
+ * 将 store 的 state 属性转为 refs 对象
69
+ */
70
+ export declare function storeToRefs<S extends Record<string, any>>(
71
+ store: PageStore<S>
72
+ ): { [K in keyof S]: S[K] };
73
+
74
+ /** Store 注册表 */
75
+ export declare const storeRegistry: Map<string, PageStore<any>>;
@@ -0,0 +1,257 @@
1
+ /*!
2
+ * vue-page-store v0.1.0
3
+ * (c) 2026 weijianjun
4
+ * @license MIT
5
+ */
6
+ /**
7
+ * vue-page-store - Vue 2.6 页面级 Store
8
+ *
9
+ * 状态、通信、生命周期,一个作用域全收。
10
+ *
11
+ * 与 Vuex 的区分:
12
+ * Vuex → 全局状态(用户信息、权限、路由等)
13
+ * pageStore → 页面级状态(仪表盘、漏斗详情等复杂页面内部状态)
14
+ * 页面销毁时 $destroy 即可回收,不污染全局
15
+ *
16
+ * @author weijianjun
17
+ * @license MIT
18
+ */
19
+
20
+ // Store 注册表(导出供调试 / devtools 使用)
21
+ const storeRegistry = new Map();
22
+
23
+ function createStoreInstance(Vue, id, options) {
24
+ const initialState = options.state();
25
+ const getters = options.getters || {};
26
+ const actions = options.actions || {};
27
+
28
+ // --- 用一个隐藏的 Vue 实例承载响应式 state + computed getters ---
29
+ const computedDefs = {};
30
+ const store = { _disposed: false };
31
+
32
+ Object.keys(getters).forEach(function (key) {
33
+ computedDefs[key] = function () {
34
+ return getters[key].call(store);
35
+ };
36
+ });
37
+
38
+ const vm = new Vue({
39
+ data: function () {
40
+ return { $$state: initialState };
41
+ },
42
+ computed: computedDefs,
43
+ });
44
+
45
+ const rawState = vm.$data.$$state;
46
+
47
+ // ====== state —— 代理到 vm.$$state ======
48
+ Object.keys(initialState).forEach(function (key) {
49
+ Object.defineProperty(store, key, {
50
+ enumerable: true,
51
+ get: function () { return rawState[key]; },
52
+ set: function (val) { rawState[key] = val; },
53
+ });
54
+ });
55
+
56
+ // ====== getters —— 代理到 vm computed ======
57
+ Object.keys(getters).forEach(function (key) {
58
+ Object.defineProperty(store, key, {
59
+ enumerable: true,
60
+ get: function () { return vm[key]; },
61
+ });
62
+ });
63
+
64
+ // ====== actions ======
65
+ Object.keys(actions).forEach(function (key) {
66
+ store[key] = actions[key].bind(store);
67
+ });
68
+
69
+ // ====== watch —— 声明式副作用,生命周期自动回收 ======
70
+ const watches = options.watch || {};
71
+ Object.entries(watches).forEach(function (_ref) {
72
+ const path = _ref[0];
73
+ const def = _ref[1];
74
+ const handler = typeof def === 'function' ? def : def.handler;
75
+ const watchOpts = { deep: true };
76
+ if (typeof def === 'object' && def.immediate) watchOpts.immediate = true;
77
+ const expr = function () {
78
+ return path.split('.').reduce(function (obj, k) { return obj && obj[k]; }, store);
79
+ };
80
+ vm.$watch(expr, handler.bind(store), watchOpts);
81
+ });
82
+
83
+ // ====== 内置方法 ======
84
+ store.$state = rawState;
85
+ store.$id = id;
86
+
87
+ /**
88
+ * 批量更新 state(浅合并语义)
89
+ * @param {Object|Function} partial - 要合并的对象,或 (state) => Object 函数
90
+ */
91
+ store.$patch = function (partial) {
92
+ const obj = typeof partial === 'function' ? partial(rawState) : partial;
93
+ Object.keys(obj).forEach(function (key) {
94
+ Vue.set(rawState, key, obj[key]);
95
+ });
96
+ };
97
+
98
+ /**
99
+ * 订阅 state 变化
100
+ * @param {Function} callback - (newState) => void
101
+ * @returns {Function} 取消订阅函数
102
+ */
103
+ store.$subscribe = function (callback) {
104
+ return vm.$watch(
105
+ function () { return Object.assign({}, rawState); },
106
+ callback,
107
+ { deep: true }
108
+ );
109
+ };
110
+
111
+ /**
112
+ * 重置 state 到初始值
113
+ */
114
+ store.$reset = function () {
115
+ const fresh = options.state();
116
+ Object.keys(fresh).forEach(function (key) {
117
+ rawState[key] = fresh[key];
118
+ });
119
+ };
120
+
121
+ // ====== 内置事件总线(页面作用域隔离通信) ======
122
+ const _listeners = {};
123
+
124
+ /**
125
+ * 发射事件(仅当前 store 作用域内)
126
+ * @param {string} event - 事件名
127
+ * @param {*} payload - 事件数据
128
+ */
129
+ store.$emit = function (event, payload) {
130
+ const fns = _listeners[event];
131
+ if (fns) fns.slice().forEach(function (fn) { fn(payload); });
132
+ };
133
+
134
+ /**
135
+ * 订阅事件
136
+ * @param {string} event - 事件名
137
+ * @param {Function} handler - 处理函数
138
+ * @returns {Function} 取消订阅函数
139
+ */
140
+ store.$on = function (event, handler) {
141
+ if (!_listeners[event]) _listeners[event] = [];
142
+ _listeners[event].push(handler);
143
+ return function () {
144
+ const idx = _listeners[event].indexOf(handler);
145
+ if (idx > -1) _listeners[event].splice(idx, 1);
146
+ };
147
+ };
148
+
149
+ /**
150
+ * 取消订阅指定事件的所有 handler,或指定 handler
151
+ * @param {string} event - 事件名
152
+ * @param {Function} [handler] - 可选,指定取消某个 handler
153
+ */
154
+ store.$off = function (event, handler) {
155
+ if (!_listeners[event]) return;
156
+ if (handler) {
157
+ const idx = _listeners[event].indexOf(handler);
158
+ if (idx > -1) _listeners[event].splice(idx, 1);
159
+ } else {
160
+ delete _listeners[event];
161
+ }
162
+ };
163
+
164
+ /**
165
+ * 销毁 store —— 清空事件、销毁 vm、移除注册
166
+ */
167
+ store.$destroy = function () {
168
+ store._disposed = true;
169
+ Object.keys(_listeners).forEach(function (key) { delete _listeners[key]; });
170
+ vm.$destroy();
171
+ storeRegistry.delete(id);
172
+ };
173
+
174
+ store._vm = vm;
175
+
176
+ return store;
177
+ }
178
+
179
+ /**
180
+ * 定义页面级 Store
181
+ *
182
+ * @param {string} id - 唯一标识
183
+ * @param {Object} options - { state, getters, actions, watch }
184
+ * @returns {Function} useStore - 调用即获取 / 创建 store 实例
185
+ *
186
+ * @example
187
+ * const useFunnelStore = definePageStore('funnelDetail', {
188
+ * state: () => ({ filters: {}, loading: false }),
189
+ * getters: {
190
+ * isReady() { return !this.loading; }
191
+ * },
192
+ * actions: {
193
+ * async fetchData() { ... }
194
+ * }
195
+ * });
196
+ *
197
+ * // 组件中
198
+ * const store = useFunnelStore();
199
+ * store.fetchData();
200
+ *
201
+ * // 页面销毁时
202
+ * store.$destroy();
203
+ */
204
+ function definePageStore(id, options) {
205
+ if (!id || typeof options.state !== 'function') {
206
+ throw new Error('[vue-page-store] 需要 id 和 state 函数');
207
+ }
208
+
209
+ // 缓存 Vue 引用,首次调用时从 store 的 vm 实例获取
210
+ let _Vue = null;
211
+
212
+ return function useStore() {
213
+ if (storeRegistry.has(id)) {
214
+ return storeRegistry.get(id);
215
+ }
216
+
217
+ // 自动获取 Vue 构造函数
218
+ if (!_Vue) {
219
+ try {
220
+ _Vue = require('vue');
221
+ if (_Vue.default) _Vue = _Vue.default;
222
+ } catch (e) {
223
+ throw new Error(
224
+ '[vue-page-store] 无法自动获取 Vue,请确保 vue 已安装'
225
+ );
226
+ }
227
+ }
228
+
229
+ const store = createStoreInstance(_Vue, id, options);
230
+ storeRegistry.set(id, store);
231
+ return store;
232
+ };
233
+ }
234
+
235
+ /**
236
+ * 将 store 的 state 属性转为可在模板中使用的 refs 对象
237
+ *
238
+ * @param {Object} store - pageStore 实例
239
+ * @returns {Object} refs 对象(可解构赋值到 computed)
240
+ *
241
+ * @example
242
+ * const { filters, loading } = storeToRefs(store);
243
+ */
244
+ function storeToRefs(store) {
245
+ const refs = {};
246
+ Object.keys(store.$state).forEach(function (key) {
247
+ Object.defineProperty(refs, key, {
248
+ enumerable: true,
249
+ get: function () { return store[key]; },
250
+ set: function (val) { store[key] = val; },
251
+ });
252
+ });
253
+ return refs;
254
+ }
255
+ var index = { definePageStore, storeToRefs, storeRegistry };
256
+
257
+ export { index as default, definePageStore, storeRegistry, storeToRefs };
@@ -0,0 +1,270 @@
1
+ /*!
2
+ * vue-page-store v0.1.0
3
+ * (c) 2026 weijianjun
4
+ * @license MIT
5
+ */
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';
11
+
12
+ /**
13
+ * vue-page-store - Vue 2.6 页面级 Store
14
+ *
15
+ * 状态、通信、生命周期,一个作用域全收。
16
+ *
17
+ * 与 Vuex 的区分:
18
+ * Vuex → 全局状态(用户信息、权限、路由等)
19
+ * pageStore → 页面级状态(仪表盘、漏斗详情等复杂页面内部状态)
20
+ * 页面销毁时 $destroy 即可回收,不污染全局
21
+ *
22
+ * @author weijianjun
23
+ * @license MIT
24
+ */
25
+
26
+ // Store 注册表(导出供调试 / devtools 使用)
27
+ const storeRegistry = new Map();
28
+
29
+ function createStoreInstance(Vue, id, options) {
30
+ const initialState = options.state();
31
+ const getters = options.getters || {};
32
+ const actions = options.actions || {};
33
+
34
+ // --- 用一个隐藏的 Vue 实例承载响应式 state + computed getters ---
35
+ const computedDefs = {};
36
+ const store = { _disposed: false };
37
+
38
+ Object.keys(getters).forEach(function (key) {
39
+ computedDefs[key] = function () {
40
+ return getters[key].call(store);
41
+ };
42
+ });
43
+
44
+ const vm = new Vue({
45
+ data: function () {
46
+ return { $$state: initialState };
47
+ },
48
+ computed: computedDefs,
49
+ });
50
+
51
+ const rawState = vm.$data.$$state;
52
+
53
+ // ====== state —— 代理到 vm.$$state ======
54
+ Object.keys(initialState).forEach(function (key) {
55
+ Object.defineProperty(store, key, {
56
+ enumerable: true,
57
+ get: function () { return rawState[key]; },
58
+ set: function (val) { rawState[key] = val; },
59
+ });
60
+ });
61
+
62
+ // ====== getters —— 代理到 vm computed ======
63
+ Object.keys(getters).forEach(function (key) {
64
+ Object.defineProperty(store, key, {
65
+ enumerable: true,
66
+ get: function () { return vm[key]; },
67
+ });
68
+ });
69
+
70
+ // ====== actions ======
71
+ Object.keys(actions).forEach(function (key) {
72
+ store[key] = actions[key].bind(store);
73
+ });
74
+
75
+ // ====== watch —— 声明式副作用,生命周期自动回收 ======
76
+ const watches = options.watch || {};
77
+ Object.entries(watches).forEach(function (_ref) {
78
+ const path = _ref[0];
79
+ const def = _ref[1];
80
+ const handler = typeof def === 'function' ? def : def.handler;
81
+ const watchOpts = { deep: true };
82
+ if (typeof def === 'object' && def.immediate) watchOpts.immediate = true;
83
+ const expr = function () {
84
+ return path.split('.').reduce(function (obj, k) { return obj && obj[k]; }, store);
85
+ };
86
+ vm.$watch(expr, handler.bind(store), watchOpts);
87
+ });
88
+
89
+ // ====== 内置方法 ======
90
+ store.$state = rawState;
91
+ store.$id = id;
92
+
93
+ /**
94
+ * 批量更新 state(浅合并语义)
95
+ * @param {Object|Function} partial - 要合并的对象,或 (state) => Object 函数
96
+ */
97
+ store.$patch = function (partial) {
98
+ const obj = typeof partial === 'function' ? partial(rawState) : partial;
99
+ Object.keys(obj).forEach(function (key) {
100
+ Vue.set(rawState, key, obj[key]);
101
+ });
102
+ };
103
+
104
+ /**
105
+ * 订阅 state 变化
106
+ * @param {Function} callback - (newState) => void
107
+ * @returns {Function} 取消订阅函数
108
+ */
109
+ store.$subscribe = function (callback) {
110
+ return vm.$watch(
111
+ function () { return Object.assign({}, rawState); },
112
+ callback,
113
+ { deep: true }
114
+ );
115
+ };
116
+
117
+ /**
118
+ * 重置 state 到初始值
119
+ */
120
+ store.$reset = function () {
121
+ const fresh = options.state();
122
+ Object.keys(fresh).forEach(function (key) {
123
+ rawState[key] = fresh[key];
124
+ });
125
+ };
126
+
127
+ // ====== 内置事件总线(页面作用域隔离通信) ======
128
+ const _listeners = {};
129
+
130
+ /**
131
+ * 发射事件(仅当前 store 作用域内)
132
+ * @param {string} event - 事件名
133
+ * @param {*} payload - 事件数据
134
+ */
135
+ store.$emit = function (event, payload) {
136
+ const fns = _listeners[event];
137
+ if (fns) fns.slice().forEach(function (fn) { fn(payload); });
138
+ };
139
+
140
+ /**
141
+ * 订阅事件
142
+ * @param {string} event - 事件名
143
+ * @param {Function} handler - 处理函数
144
+ * @returns {Function} 取消订阅函数
145
+ */
146
+ store.$on = function (event, handler) {
147
+ if (!_listeners[event]) _listeners[event] = [];
148
+ _listeners[event].push(handler);
149
+ return function () {
150
+ const idx = _listeners[event].indexOf(handler);
151
+ if (idx > -1) _listeners[event].splice(idx, 1);
152
+ };
153
+ };
154
+
155
+ /**
156
+ * 取消订阅指定事件的所有 handler,或指定 handler
157
+ * @param {string} event - 事件名
158
+ * @param {Function} [handler] - 可选,指定取消某个 handler
159
+ */
160
+ store.$off = function (event, handler) {
161
+ if (!_listeners[event]) return;
162
+ if (handler) {
163
+ const idx = _listeners[event].indexOf(handler);
164
+ if (idx > -1) _listeners[event].splice(idx, 1);
165
+ } else {
166
+ delete _listeners[event];
167
+ }
168
+ };
169
+
170
+ /**
171
+ * 销毁 store —— 清空事件、销毁 vm、移除注册
172
+ */
173
+ store.$destroy = function () {
174
+ store._disposed = true;
175
+ Object.keys(_listeners).forEach(function (key) { delete _listeners[key]; });
176
+ vm.$destroy();
177
+ storeRegistry.delete(id);
178
+ };
179
+
180
+ store._vm = vm;
181
+
182
+ return store;
183
+ }
184
+
185
+ /**
186
+ * 定义页面级 Store
187
+ *
188
+ * @param {string} id - 唯一标识
189
+ * @param {Object} options - { state, getters, actions, watch }
190
+ * @returns {Function} useStore - 调用即获取 / 创建 store 实例
191
+ *
192
+ * @example
193
+ * const useFunnelStore = definePageStore('funnelDetail', {
194
+ * state: () => ({ filters: {}, loading: false }),
195
+ * getters: {
196
+ * isReady() { return !this.loading; }
197
+ * },
198
+ * actions: {
199
+ * async fetchData() { ... }
200
+ * }
201
+ * });
202
+ *
203
+ * // 组件中
204
+ * const store = useFunnelStore();
205
+ * store.fetchData();
206
+ *
207
+ * // 页面销毁时
208
+ * store.$destroy();
209
+ */
210
+ function definePageStore(id, options) {
211
+ if (!id || typeof options.state !== 'function') {
212
+ throw new Error('[vue-page-store] 需要 id 和 state 函数');
213
+ }
214
+
215
+ // 缓存 Vue 引用,首次调用时从 store 的 vm 实例获取
216
+ let _Vue = null;
217
+
218
+ return function useStore() {
219
+ if (storeRegistry.has(id)) {
220
+ return storeRegistry.get(id);
221
+ }
222
+
223
+ // 自动获取 Vue 构造函数
224
+ if (!_Vue) {
225
+ try {
226
+ _Vue = require('vue');
227
+ if (_Vue.default) _Vue = _Vue.default;
228
+ } catch (e) {
229
+ throw new Error(
230
+ '[vue-page-store] 无法自动获取 Vue,请确保 vue 已安装'
231
+ );
232
+ }
233
+ }
234
+
235
+ const store = createStoreInstance(_Vue, id, options);
236
+ storeRegistry.set(id, store);
237
+ return store;
238
+ };
239
+ }
240
+
241
+ /**
242
+ * 将 store 的 state 属性转为可在模板中使用的 refs 对象
243
+ *
244
+ * @param {Object} store - pageStore 实例
245
+ * @returns {Object} refs 对象(可解构赋值到 computed)
246
+ *
247
+ * @example
248
+ * const { filters, loading } = storeToRefs(store);
249
+ */
250
+ function storeToRefs(store) {
251
+ const refs = {};
252
+ Object.keys(store.$state).forEach(function (key) {
253
+ Object.defineProperty(refs, key, {
254
+ enumerable: true,
255
+ get: function () { return store[key]; },
256
+ set: function (val) { store[key] = val; },
257
+ });
258
+ });
259
+ return refs;
260
+ }
261
+ var index = { definePageStore, storeToRefs, storeRegistry };
262
+
263
+ exports.default = index;
264
+ exports.definePageStore = definePageStore;
265
+ exports.storeRegistry = storeRegistry;
266
+ exports.storeToRefs = storeToRefs;
267
+
268
+ Object.defineProperty(exports, '__esModule', { value: true });
269
+
270
+ }));
@@ -0,0 +1,19 @@
1
+ /*!
2
+ * vue-page-store v0.1.0
3
+ * (c) 2026 weijianjun
4
+ * @license MIT
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";
7
+ /**
8
+ * vue-page-store - Vue 2.6 页面级 Store
9
+ *
10
+ * 状态、通信、生命周期,一个作用域全收。
11
+ *
12
+ * 与 Vuex 的区分:
13
+ * Vuex → 全局状态(用户信息、权限、路由等)
14
+ * pageStore → 页面级状态(仪表盘、漏斗详情等复杂页面内部状态)
15
+ * 页面销毁时 $destroy 即可回收,不污染全局
16
+ *
17
+ * @author weijianjun
18
+ * @license MIT
19
+ */const t=new Map;function n(e,n){if(!e||"function"!=typeof n.state)throw new Error("[vue-page-store] 需要 id 和 state 函数");let o=null;return function(){if(t.has(e))return t.get(e);if(!o)try{o=require("vue"),o.default&&(o=o.default)}catch(e){throw new Error("[vue-page-store] 无法自动获取 Vue,请确保 vue 已安装")}const c=function(e,n,o){const c=o.state(),r=o.getters||{},f=o.actions||{},i={},u={_disposed:!1};Object.keys(r).forEach(function(e){i[e]=function(){return r[e].call(u)}});const s=new e({data:function(){return{$$state:c}},computed:i}),a=s.$data.$$state;Object.keys(c).forEach(function(e){Object.defineProperty(u,e,{enumerable:!0,get:function(){return a[e]},set:function(t){a[e]=t}})}),Object.keys(r).forEach(function(e){Object.defineProperty(u,e,{enumerable:!0,get:function(){return s[e]}})}),Object.keys(f).forEach(function(e){u[e]=f[e].bind(u)});const d=o.watch||{};Object.entries(d).forEach(function(e){const t=e[0],n=e[1],o="function"==typeof n?n:n.handler,c={deep:!0};"object"==typeof n&&n.immediate&&(c.immediate=!0),s.$watch(function(){return t.split(".").reduce(function(e,t){return e&&e[t]},u)},o.bind(u),c)}),u.$state=a,u.$id=n,u.$patch=function(t){const n="function"==typeof t?t(a):t;Object.keys(n).forEach(function(t){e.set(a,t,n[t])})},u.$subscribe=function(e){return s.$watch(function(){return Object.assign({},a)},e,{deep:!0})},u.$reset=function(){const e=o.state();Object.keys(e).forEach(function(t){a[t]=e[t]})};const l={};return u.$emit=function(e,t){const n=l[e];n&&n.slice().forEach(function(e){e(t)})},u.$on=function(e,t){return l[e]||(l[e]=[]),l[e].push(t),function(){const n=l[e].indexOf(t);n>-1&&l[e].splice(n,1)}},u.$off=function(e,t){if(l[e])if(t){const n=l[e].indexOf(t);n>-1&&l[e].splice(n,1)}else delete l[e]},u.$destroy=function(){u._disposed=!0,Object.keys(l).forEach(function(e){delete l[e]}),s.$destroy(),t.delete(n)},u._vm=s,u}(o,e,n);return t.set(e,c),c}}function o(e){const t={};return Object.keys(e.$state).forEach(function(n){Object.defineProperty(t,n,{enumerable:!0,get:function(){return e[n]},set:function(t){e[n]=t}})}),t}var c={definePageStore:n,storeToRefs:o,storeRegistry:t};e.default=c,e.definePageStore=n,e.storeRegistry=t,e.storeToRefs=o,Object.defineProperty(e,"__esModule",{value:!0})});
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "vue-page-store",
3
+ "version": "0.1.0",
4
+ "description": "Vue 2.6 页面级 Store —— 状态、通信、生命周期,一个作用域全收",
5
+ "author": "weijianjun",
6
+ "license": "MIT",
7
+ "main": "dist/index.cjs.js",
8
+ "module": "dist/index.esm.js",
9
+ "unpkg": "dist/index.umd.js",
10
+ "types": "dist/index.d.ts",
11
+ "files": [
12
+ "dist",
13
+ "README.md",
14
+ "LICENSE"
15
+ ],
16
+ "scripts": {
17
+ "build": "rollup -c rollup.config.mjs && cp src/index.d.ts dist/index.d.ts",
18
+ "prepublishOnly": "npm run build"
19
+ },
20
+ "peerDependencies": {
21
+ "vue": "^2.6.0"
22
+ },
23
+ "devDependencies": {
24
+ "rollup": "^3.29.4",
25
+ "@rollup/plugin-terser": "^0.4.4"
26
+ },
27
+ "keywords": [
28
+ "vue",
29
+ "vue2",
30
+ "store",
31
+ "state-management",
32
+ "page-store",
33
+ "micro-frontend",
34
+ "single-spa"
35
+ ],
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "https://github.com/weijianjunwjj/vue-page-store.git"
39
+ }
40
+ }