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 +21 -0
- package/README.md +180 -0
- package/dist/index.cjs.js +264 -0
- package/dist/index.d.ts +75 -0
- package/dist/index.esm.js +257 -0
- package/dist/index.umd.js +270 -0
- package/dist/index.umd.min.js +19 -0
- package/package.json +40 -0
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;
|
package/dist/index.d.ts
ADDED
|
@@ -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
|
+
}
|