vue-page-store 0.5.1 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * vue-page-store v0.5.1
2
+ * vue-page-store v0.5.2
3
3
  * (c) 2026 weijianjun
4
4
  * @license MIT
5
5
  */
package/dist/index.esm.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * vue-page-store v0.5.1
2
+ * vue-page-store v0.5.2
3
3
  * (c) 2026 weijianjun
4
4
  * @license MIT
5
5
  */
package/dist/index.umd.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * vue-page-store v0.5.1
2
+ * vue-page-store v0.5.2
3
3
  * (c) 2026 weijianjun
4
4
  * @license MIT
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * vue-page-store v0.5.1
2
+ * vue-page-store v0.5.2
3
3
  * (c) 2026 weijianjun
4
4
  * @license MIT
5
5
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vue-page-store",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
4
4
  "description": "Vue 2.6 页面级 Store —— 状态、通信、生命周期,一个作用域全收",
5
5
  "author": "weijianjun",
6
6
  "license": "MIT",
@@ -10,6 +10,7 @@
10
10
  "types": "dist/index.d.ts",
11
11
  "files": [
12
12
  "dist",
13
+ "src/debug",
13
14
  "README.md",
14
15
  "LICENSE"
15
16
  ],
@@ -0,0 +1,277 @@
1
+ <template>
2
+ <div class="ps-devtools" v-if="visible">
3
+ <div class="ps-devtools__header">
4
+ 🐛 PageStore Debug
5
+ <button @click="visible = false" style="float:right;background:none;border:none;color:#eee;cursor:pointer">✕</button>
6
+ </div>
7
+
8
+ <div class="ps-devtools__body">
9
+ <!-- 左侧:store 列表 -->
10
+ <div class="ps-devtools__sidebar">
11
+ <div
12
+ v-for="item in stores"
13
+ :key="item.id"
14
+ :class="['ps-devtools__store', { active: selectedId === item.id, destroyed: item.destroyed }]"
15
+ @click="selectedId = item.id"
16
+ >
17
+ <div class="name">{{ item.id }}</div>
18
+ <div class="meta">
19
+ {{ item.destroyed ? '🔴 destroyed' : (item.active ? '🟢 active' : '⚪ idle') }}
20
+ </div>
21
+ </div>
22
+ <div v-if="!stores.length" class="meta" style="padding:8px">no stores</div>
23
+ </div>
24
+
25
+ <!-- 右侧:详情 -->
26
+ <div class="ps-devtools__main">
27
+ <!-- tab 切换 -->
28
+ <div class="ps-devtools__tabs">
29
+ <span
30
+ v-for="t in tabs" :key="t"
31
+ :class="{ active: tab === t }"
32
+ @click="tab = t"
33
+ >{{ t }}</span>
34
+ </div>
35
+
36
+ <div class="ps-devtools__pane" v-if="selectedStore">
37
+ <pre v-if="tab === '$state'">{{ stateText }}</pre>
38
+ <pre v-else-if="tab === '$source'">{{ sourceText }}</pre>
39
+ <pre v-else-if="tab === 'getters'">{{ gettersText }}</pre>
40
+ <div v-else-if="tab === 'events'">
41
+ <div v-if="!filteredEvents.length" class="meta">no events</div>
42
+ <div
43
+ v-for="e in filteredEvents"
44
+ :key="e.seq"
45
+ class="event"
46
+ >
47
+ <span class="seq">#{{ e.seq }}</span>
48
+ <span class="time">{{ formatTime(e.ts) }}</span>
49
+ <span class="type">{{ e.type }}</span>
50
+ <span v-if="e.payload && e.payload.action" class="tag">{{ e.payload.action }}</span>
51
+ <span v-if="e.payload && e.payload.duration !== undefined" class="dim">{{ e.payload.duration }}ms</span>
52
+ <span v-if="e.payload && e.payload.error" class="err">{{ e.payload.error }}</span>
53
+ </div>
54
+ </div>
55
+ </div>
56
+ <div v-else class="meta" style="padding:12px">select a store</div>
57
+ </div>
58
+ </div>
59
+ </div>
60
+ <button v-else class="ps-devtools__pill" @click="visible = true">
61
+ 🐛 {{ stores.length }}
62
+ </button>
63
+ </template>
64
+
65
+ <script>
66
+ import { getStores, getEvents } from './registry.js';
67
+
68
+ export default {
69
+ name: 'PageStoreDevPanel',
70
+
71
+ data: function () {
72
+ return {
73
+ visible: false,
74
+ selectedId: null,
75
+ tab: '$state',
76
+ tabs: ['$state', '$source', 'getters', 'events'],
77
+ tick: 0,
78
+ _timer: null,
79
+ };
80
+ },
81
+
82
+ computed: {
83
+ stores: function () {
84
+ this.tick; // 建立响应式依赖,500ms 驱动刷新
85
+ var list = [];
86
+ getStores().forEach(function (m) { list.push(m); });
87
+ return list;
88
+ },
89
+
90
+ selectedStore: function () {
91
+ if (!this.selectedId && this.stores.length) {
92
+ this.selectedId = this.stores[0].id;
93
+ }
94
+ for (var i = 0; i < this.stores.length; i++) {
95
+ if (this.stores[i].id === this.selectedId) return this.stores[i];
96
+ }
97
+ return this.stores[0] || null;
98
+ },
99
+
100
+ ref: function () {
101
+ return this.selectedStore && this.selectedStore.storeRef;
102
+ },
103
+
104
+ stateText: function () {
105
+ this.tick;
106
+ return this.safeJson(this.ref && this.ref.$state);
107
+ },
108
+
109
+ sourceText: function () {
110
+ this.tick;
111
+ return this.safeJson(this.ref && this.ref.$source);
112
+ },
113
+
114
+ gettersText: function () {
115
+ this.tick;
116
+ var s = this.ref;
117
+ if (!s) return '(no store)';
118
+ var stateKeys = s.$state ? Object.keys(s.$state) : [];
119
+ var sourceKeys = s.$source ? Object.keys(s.$source) : [];
120
+ var out = {};
121
+ for (var k in s) {
122
+ if (!k || k[0] === '$' || k[0] === '_') continue;
123
+ if (stateKeys.indexOf(k) > -1 || sourceKeys.indexOf(k) > -1) continue;
124
+ var v;
125
+ try { v = s[k]; } catch (e) { continue; }
126
+ if (typeof v === 'function') continue;
127
+ out[k] = v;
128
+ }
129
+ var str = this.safeJson(out);
130
+ return str === '{}' ? '(no getters)' : str;
131
+ },
132
+
133
+ filteredEvents: function () {
134
+ this.tick;
135
+ if (!this.selectedStore) return [];
136
+ var id = this.selectedStore.id;
137
+ var all = getEvents();
138
+ var filtered = [];
139
+ for (var i = all.length - 1; i >= 0 && filtered.length < 50; i--) {
140
+ if (all[i].storeId === id) filtered.push(all[i]);
141
+ }
142
+ return filtered;
143
+ },
144
+ },
145
+
146
+ mounted: function () {
147
+ var self = this;
148
+ this._timer = setInterval(function () { self.tick++; }, 500);
149
+ },
150
+
151
+ beforeDestroy: function () {
152
+ if (this._timer) clearInterval(this._timer);
153
+ },
154
+
155
+ methods: {
156
+ safeJson: function (val) {
157
+ if (val === undefined || val === null) return String(val);
158
+ try { return JSON.stringify(val, null, 2); }
159
+ catch (e) { return '[Unserializable]'; }
160
+ },
161
+
162
+ formatTime: function (ts) {
163
+ if (!ts) return '';
164
+ var d = new Date(ts);
165
+ var pad = function (n) { return n < 10 ? '0' + n : '' + n; };
166
+ return pad(d.getHours()) + ':' + pad(d.getMinutes()) + ':' + pad(d.getSeconds());
167
+ },
168
+ },
169
+ };
170
+ </script>
171
+
172
+ <style scoped>
173
+ .ps-devtools {
174
+ position: fixed;
175
+ bottom: 10px;
176
+ right: 10px;
177
+ width: 540px;
178
+ height: 400px;
179
+ background: #1e1e1e;
180
+ color: #eee;
181
+ font-size: 12px;
182
+ font-family: monospace;
183
+ z-index: 2147483000;
184
+ border-radius: 6px;
185
+ overflow: hidden;
186
+ display: flex;
187
+ flex-direction: column;
188
+ box-shadow: 0 8px 32px rgba(0,0,0,0.4);
189
+ }
190
+ .ps-devtools__header {
191
+ padding: 6px 10px;
192
+ background: #333;
193
+ font-weight: bold;
194
+ }
195
+ .ps-devtools__body {
196
+ flex: 1;
197
+ display: flex;
198
+ min-height: 0;
199
+ }
200
+ .ps-devtools__sidebar {
201
+ width: 160px;
202
+ flex: 0 0 160px;
203
+ border-right: 1px solid #444;
204
+ overflow-y: auto;
205
+ }
206
+ .ps-devtools__store {
207
+ padding: 6px 8px;
208
+ cursor: pointer;
209
+ border-bottom: 1px solid #333;
210
+ }
211
+ .ps-devtools__store:hover { background: #2a2a2a; }
212
+ .ps-devtools__store.active { background: #444; }
213
+ .ps-devtools__store.destroyed { opacity: 0.45; }
214
+ .ps-devtools__main {
215
+ flex: 1;
216
+ display: flex;
217
+ flex-direction: column;
218
+ min-width: 0;
219
+ }
220
+ .ps-devtools__tabs {
221
+ display: flex;
222
+ border-bottom: 1px solid #444;
223
+ background: #252525;
224
+ }
225
+ .ps-devtools__tabs span {
226
+ padding: 5px 10px;
227
+ cursor: pointer;
228
+ color: #aaa;
229
+ }
230
+ .ps-devtools__tabs span:hover { color: #eee; }
231
+ .ps-devtools__tabs span.active {
232
+ color: #fff;
233
+ background: #444;
234
+ }
235
+ .ps-devtools__pane {
236
+ flex: 1;
237
+ overflow: auto;
238
+ padding: 8px;
239
+ }
240
+ pre {
241
+ margin: 0;
242
+ white-space: pre-wrap;
243
+ word-break: break-all;
244
+ color: #d4d4d4;
245
+ }
246
+ .meta { font-size: 10px; color: #888; }
247
+ .name { color: #eee; }
248
+ .event {
249
+ padding: 2px 0;
250
+ border-bottom: 1px solid #2a2a2a;
251
+ display: flex;
252
+ gap: 6px;
253
+ flex-wrap: wrap;
254
+ align-items: baseline;
255
+ }
256
+ .seq { color: #555; min-width: 28px; }
257
+ .time { color: #888; min-width: 60px; }
258
+ .type { color: #ddd; min-width: 100px; }
259
+ .tag { color: #b088f2; }
260
+ .dim { color: #666; }
261
+ .err { color: #e06464; }
262
+ .ps-devtools__pill {
263
+ position: fixed;
264
+ bottom: 10px;
265
+ right: 10px;
266
+ z-index: 2147483000;
267
+ padding: 6px 14px;
268
+ background: #1e1e1e;
269
+ color: #eee;
270
+ border: 1px solid #444;
271
+ border-radius: 20px;
272
+ cursor: pointer;
273
+ font: 12px monospace;
274
+ box-shadow: 0 4px 16px rgba(0,0,0,0.3);
275
+ }
276
+ .ps-devtools__pill:hover { border-color: #888; }
277
+ </style>
@@ -0,0 +1,116 @@
1
+ /**
2
+ * vue-page-store debug / emit
3
+ *
4
+ * 唯一对外高层入口:
5
+ * emitDebugEvent(store, type, payload)
6
+ *
7
+ * 职责:
8
+ * 1. 把一次领域调用写入 events 时间线(带 seq + ts)
9
+ * 2. 对特定 type 顺带同步 stores meta(create / destroy / lifecycle)
10
+ * 3. payload 做序列化快照,避免后续持有活引用
11
+ * 4. 仅 dev 生效;任何异常一律吞掉,绝不拖累业务
12
+ *
13
+ * 支持的 type:
14
+ * store:create payload = { name? } → addStore(meta)
15
+ * store:destroy payload = undefined → updateStore(id, { destroyed: true, active: false })
16
+ * lifecycle:init payload = undefined → updateStore(id, { route })
17
+ * lifecycle:enter payload = undefined → updateStore(id, { active: true, route })
18
+ * lifecycle:leave payload = undefined → updateStore(id, { active: false })
19
+ * action:start payload = { action, args } → 仅写事件
20
+ * action:end payload = { action, duration } → 仅写事件
21
+ * action:error payload = { action, duration, error } → 仅写事件
22
+ * state:set payload = { key, value } → 仅写事件
23
+ * state:patch payload = { patch } → 仅写事件
24
+ */
25
+
26
+ import {
27
+ isDev,
28
+ nextId,
29
+ addStore,
30
+ updateStore,
31
+ addEvent,
32
+ } from './registry.js';
33
+
34
+ // ---- helpers ----
35
+
36
+ function readRoute(store) {
37
+ try {
38
+ var vm = store && store.$vm;
39
+ if (vm && vm.$route) {
40
+ return {
41
+ path: vm.$route.path,
42
+ fullPath: vm.$route.fullPath,
43
+ name: vm.$route.name,
44
+ };
45
+ }
46
+ } catch (e) { /* ignore */ }
47
+ return null;
48
+ }
49
+
50
+ function snapshot(v) {
51
+ if (v === undefined) return undefined;
52
+ if (v === null) return null;
53
+ var t = typeof v;
54
+ if (t === 'function') return '[Function]';
55
+ if (t !== 'object') return v;
56
+ try {
57
+ return JSON.parse(JSON.stringify(v));
58
+ } catch (e) {
59
+ return '[Unserializable]';
60
+ }
61
+ }
62
+
63
+ // ---- 主入口 ----
64
+
65
+ export function emitDebugEvent(store, type, payload) {
66
+ if (!isDev) return null;
67
+
68
+ try {
69
+ var id = store && store.$id;
70
+
71
+ // 必要的 meta 同步
72
+ switch (type) {
73
+ case 'store:create':
74
+ addStore({
75
+ id: id,
76
+ name: (payload && payload.name) || id,
77
+ route: readRoute(store),
78
+ createdAt: Date.now(),
79
+ active: false,
80
+ destroyed: false,
81
+ keepAlive: false,
82
+ storeRef: store,
83
+ });
84
+ break;
85
+ case 'store:destroy':
86
+ updateStore(id, { destroyed: true, active: false });
87
+ break;
88
+ case 'lifecycle:init':
89
+ updateStore(id, { route: readRoute(store) });
90
+ break;
91
+ case 'lifecycle:enter':
92
+ updateStore(id, { active: true, route: readRoute(store) });
93
+ break;
94
+ case 'lifecycle:leave':
95
+ updateStore(id, { active: false });
96
+ break;
97
+ default:
98
+ break;
99
+ }
100
+
101
+ var event = {
102
+ seq: nextId(),
103
+ ts: Date.now(),
104
+ storeId: id,
105
+ type: type,
106
+ payload: snapshot(payload),
107
+ };
108
+ addEvent(event);
109
+ return event;
110
+ } catch (e) {
111
+ return null;
112
+ }
113
+ }
114
+
115
+ // Re-export:让 index.js 只需要 import 一个 debug 入口
116
+ export { nextId } from './registry.js';
@@ -0,0 +1,26 @@
1
+ import Vue from 'vue';
2
+ import { isDev } from './registry.js';
3
+ import DevPanel from './DevPanel.vue';
4
+
5
+ var _mounted = false;
6
+
7
+ export function installDevPanel() {
8
+ if (!isDev) return;
9
+ if (_mounted) return;
10
+ if (typeof document === 'undefined') return;
11
+ _mounted = true;
12
+
13
+ var mount = function () {
14
+ try {
15
+ var host = document.createElement('div');
16
+ host.id = '__vps_devpanel__';
17
+ document.body.appendChild(host);
18
+ new Vue({ render: function (h) { return h(DevPanel); } }).$mount(host);
19
+ } catch (e) {
20
+ console.warn('[vue-page-store] DevPanel mount failed:', e);
21
+ }
22
+ };
23
+
24
+ if (document.body) mount();
25
+ else document.addEventListener('DOMContentLoaded', mount);
26
+ }
@@ -0,0 +1,118 @@
1
+ /**
2
+ * vue-page-store debug / registry
3
+ *
4
+ * 纯 JS 数据层:不依赖 Vue,不涉及 UI。
5
+ *
6
+ * 对外:
7
+ * window.PAGE_STORE_DEVTOOLS = { stores: Map, events: [], seq: number }
8
+ *
9
+ * API:
10
+ * nextId() — 递增并返回 seq
11
+ * addStore(meta) — stores.set(meta.id, meta)
12
+ * updateStore(id, patch) — 浅合并进已有 meta
13
+ * removeStore(id) — stores.delete(id)
14
+ * addEvent(event) — events.push + 环形裁剪
15
+ * getStores() — 返回 Map(生产返回空 Map)
16
+ * getEvents() — 返回数组(生产返回空数组)
17
+ *
18
+ * 仅开发环境生效。生产环境下 ensure() 返回 null,
19
+ * 所有写 API 静默 no-op,不挂 window,不占内存。
20
+ */
21
+
22
+ var MAX_EVENTS = 500;
23
+
24
+ // ---- isDev:稳健检测 ----
25
+ // webpack 4 / 旧 CRA 有 process;Vite / webpack 5 不 polyfill process 的情况用 try/catch 兜住。
26
+ // 额外允许 window.__VUE_PAGE_STORE_DEV__ = true 强制开启(staging 临开调试面板用)。
27
+ var _isDev = false;
28
+ try {
29
+ if (typeof process !== 'undefined'
30
+ && process.env
31
+ && process.env.NODE_ENV !== 'production') {
32
+ _isDev = true;
33
+ }
34
+ } catch (e) { /* no process polyfill */ }
35
+ try {
36
+ if (typeof window !== 'undefined' && window.__VUE_PAGE_STORE_DEV__ === true) {
37
+ _isDev = true;
38
+ }
39
+ } catch (e) { /* SSR */ }
40
+
41
+ export var isDev = _isDev;
42
+
43
+ // ---- 单例 ----
44
+ var _devtools = null;
45
+ var _empty = { stores: new Map(), events: [] }; // 生产态只读空占位,避免返回值判空
46
+
47
+ function ensure() {
48
+ if (!isDev) return null;
49
+ if (_devtools) return _devtools;
50
+
51
+ _devtools = {
52
+ stores: new Map(),
53
+ events: [],
54
+ seq: 0,
55
+ };
56
+
57
+ if (typeof window !== 'undefined') {
58
+ try { window.PAGE_STORE_DEVTOOLS = _devtools; } catch (e) { /* locked window */ }
59
+ }
60
+
61
+ return _devtools;
62
+ }
63
+
64
+ // ---- API ----
65
+
66
+ export function nextId() {
67
+ var dt = ensure();
68
+ if (!dt) return 0;
69
+ dt.seq++;
70
+ return dt.seq;
71
+ }
72
+
73
+ export function addStore(meta) {
74
+ var dt = ensure();
75
+ if (!dt) return;
76
+ if (!meta || !meta.id) return;
77
+ dt.stores.set(meta.id, meta);
78
+ }
79
+
80
+ export function updateStore(id, patch) {
81
+ var dt = ensure();
82
+ if (!dt) return;
83
+ if (!id || !patch) return;
84
+ var m = dt.stores.get(id);
85
+ if (!m) return;
86
+ for (var k in patch) {
87
+ if (Object.prototype.hasOwnProperty.call(patch, k)) {
88
+ m[k] = patch[k];
89
+ }
90
+ }
91
+ }
92
+
93
+ export function removeStore(id) {
94
+ var dt = ensure();
95
+ if (!dt) return;
96
+ if (!id) return;
97
+ dt.stores.delete(id);
98
+ }
99
+
100
+ export function addEvent(event) {
101
+ var dt = ensure();
102
+ if (!dt) return;
103
+ if (!event) return;
104
+ dt.events.push(event);
105
+ // 环形裁剪,防止长时间开发内存无限增长
106
+ var overflow = dt.events.length - MAX_EVENTS;
107
+ if (overflow > 0) dt.events.splice(0, overflow);
108
+ }
109
+
110
+ export function getStores() {
111
+ var dt = ensure();
112
+ return dt ? dt.stores : _empty.stores;
113
+ }
114
+
115
+ export function getEvents() {
116
+ var dt = ensure();
117
+ return dt ? dt.events : _empty.events;
118
+ }