vue-stack-tabs 1.1.0 → 1.3.1

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.
Files changed (115) hide show
  1. package/README.en.md +526 -0
  2. package/README.md +141 -39
  3. package/README.ru.md +526 -0
  4. package/dist/iframe-bridge.d.ts +8 -0
  5. package/dist/iframe-bridge.html +77 -0
  6. package/dist/iframe-bridge.mjs +20 -0
  7. package/dist/iframe-child.html +19 -6
  8. package/dist/nuxt/favicon.svg +10 -0
  9. package/dist/nuxt/iframe-bridge.html +77 -0
  10. package/dist/nuxt/iframe-child.html +88 -0
  11. package/dist/nuxt/module.d.ts +6 -0
  12. package/dist/nuxt/module.mjs +28 -0
  13. package/dist/nuxt/plugin.mjs +9 -0
  14. package/dist/nuxt/runtime/plugin.d.ts +2 -0
  15. package/dist/nuxt/runtime/plugin.mjs +9 -0
  16. package/dist/src/lib/StackTabs.vue.d.ts +30 -16
  17. package/dist/src/lib/components/ContextMenu/ContextMenuItem.vue.d.ts +7 -1
  18. package/dist/src/lib/components/ContextMenu/index.vue.d.ts +5 -4
  19. package/dist/src/lib/components/PageLoading.vue.d.ts +2 -1
  20. package/dist/src/lib/components/StackKeepAlive/StackCacheRenderer.vue.d.ts +25 -0
  21. package/dist/src/lib/components/StackKeepAlive/StackKeepAlive.vue.d.ts +12 -0
  22. package/dist/src/lib/components/TabHeader/TabHeaderButton.vue.d.ts +11 -12
  23. package/dist/src/lib/components/TabHeader/TabHeaderItem.vue.d.ts +5 -4
  24. package/dist/src/lib/components/TabHeader/TabHeaderScroll.vue.d.ts +8 -22
  25. package/dist/src/lib/components/TabHeader/index.vue.d.ts +11 -164
  26. package/dist/src/lib/hooks/stackTabsContext.d.ts +49 -0
  27. package/dist/src/lib/hooks/tabPanel/evict.d.ts +21 -61
  28. package/dist/src/lib/hooks/tabPanel/navigationTransaction.d.ts +35 -0
  29. package/dist/src/lib/hooks/tabPanel/refresh.d.ts +17 -0
  30. package/dist/src/lib/hooks/tabPanel/scroll.d.ts +8 -8
  31. package/dist/src/lib/hooks/tabPanel/session.d.ts +9 -8
  32. package/dist/src/lib/hooks/tabPanel/state.d.ts +20 -72
  33. package/dist/src/lib/hooks/useIframeManager.d.ts +35 -0
  34. package/dist/src/lib/hooks/useTabActions.d.ts +5 -46
  35. package/dist/src/lib/hooks/useTabEventBus.d.ts +22 -9
  36. package/dist/src/lib/hooks/useTabPanel.d.ts +14 -73
  37. package/dist/src/lib/i18n/index.d.ts +6 -4
  38. package/dist/src/lib/i18n/lang/en.d.ts +5 -0
  39. package/dist/src/lib/i18n/lang/zh-CN.d.ts +5 -0
  40. package/dist/src/lib/i18n/stackTabsLocale.d.ts +2 -0
  41. package/dist/src/lib/iframe-bridge.d.ts +8 -0
  42. package/dist/src/lib/iframe.vue.d.ts +2 -1
  43. package/dist/src/lib/index.d.ts +4 -5
  44. package/dist/src/lib/model/TabModel.d.ts +11 -40
  45. package/dist/src/lib/nuxt/module.d.ts +6 -0
  46. package/dist/src/lib/nuxt/runtime/plugin.client.d.ts +2 -0
  47. package/dist/src/lib/nuxt/runtime/plugin.d.ts +2 -0
  48. package/dist/src/lib/utils/iframeBridge.d.ts +11 -3
  49. package/dist/src/lib/utils/stackTabsA11y.d.ts +8 -0
  50. package/dist/src/lib/utils/stackTabsMessage.d.ts +7 -0
  51. package/dist/src/lib/utils/tabInfoEncoder.d.ts +0 -15
  52. package/dist/src/lib/utils/typeGuards.d.ts +2 -0
  53. package/dist/src/lib/utils/urlParser.d.ts +24 -3
  54. package/dist/src/lib/versionLogger.d.ts +1 -0
  55. package/dist/style.css +2 -1
  56. package/dist/utils/iframeBridge.d.ts +39 -0
  57. package/dist/vue-stack-tabs.css +2 -1
  58. package/dist/vue-stack-tabs.es.js +1 -2220
  59. package/package.json +71 -50
  60. package/src/lib/StackTabs.vue +223 -196
  61. package/src/lib/assets/style/stackTab.scss +110 -13
  62. package/src/lib/components/ContextMenu/ContextMenuItem.vue +23 -11
  63. package/src/lib/components/ContextMenu/index.vue +94 -10
  64. package/src/lib/components/PageLoading.vue +9 -7
  65. package/src/lib/components/StackKeepAlive/StackCacheRenderer.vue +70 -0
  66. package/src/lib/components/StackKeepAlive/StackKeepAlive.vue +57 -0
  67. package/src/lib/components/TabHeader/TabHeaderButton.vue +19 -8
  68. package/src/lib/components/TabHeader/TabHeaderItem.vue +74 -17
  69. package/src/lib/components/TabHeader/TabHeaderScroll.vue +47 -25
  70. package/src/lib/components/TabHeader/index.vue +181 -13
  71. package/src/lib/hooks/stackTabsContext.ts +135 -0
  72. package/src/lib/hooks/tabPanel/evict.ts +122 -87
  73. package/src/lib/hooks/tabPanel/navigationTransaction.ts +61 -0
  74. package/src/lib/hooks/tabPanel/refresh.ts +109 -0
  75. package/src/lib/hooks/tabPanel/scroll.ts +57 -40
  76. package/src/lib/hooks/tabPanel/session.ts +84 -22
  77. package/src/lib/hooks/tabPanel/state.ts +41 -44
  78. package/src/lib/hooks/useIframeManager.ts +253 -0
  79. package/src/lib/hooks/useTabActions.ts +64 -30
  80. package/src/lib/hooks/useTabEventBus.ts +29 -19
  81. package/src/lib/hooks/useTabLoading.ts +3 -2
  82. package/src/lib/hooks/useTabPanel.tsx +290 -313
  83. package/src/lib/hooks/useTabRouter.ts +111 -97
  84. package/src/lib/i18n/index.ts +53 -45
  85. package/src/lib/i18n/lang/en.ts +6 -1
  86. package/src/lib/i18n/lang/zh-CN.ts +6 -1
  87. package/src/lib/i18n/stackTabsLocale.ts +8 -0
  88. package/src/lib/iframe-bridge.ts +12 -0
  89. package/src/lib/index.ts +14 -19
  90. package/src/lib/model/TabModel.ts +27 -82
  91. package/src/lib/nuxt/module.d.ts +4 -0
  92. package/src/lib/nuxt/module.ts +8 -3
  93. package/src/lib/nuxt/runtime/plugin.client.ts +1 -0
  94. package/src/lib/nuxt/runtime/plugin.ts +11 -0
  95. package/src/lib/utils/iframeBridge.ts +29 -5
  96. package/src/lib/utils/scrollUtils.ts +9 -4
  97. package/src/lib/utils/stackTabsA11y.ts +12 -0
  98. package/src/lib/utils/stackTabsMessage.ts +99 -0
  99. package/src/lib/utils/tabInfoEncoder.ts +70 -21
  100. package/src/lib/utils/typeGuards.ts +3 -0
  101. package/src/lib/utils/urlParser.ts +144 -14
  102. package/src/lib/versionLogger.ts +7 -19
  103. package/dist/src/lib/hooks/tabPanel/evict.spec.d.ts +0 -1
  104. package/dist/src/lib/hooks/useTabPanel.spec.d.ts +0 -1
  105. package/dist/src/lib/model/TabModel.spec.d.ts +0 -1
  106. package/dist/src/lib/utils/iframeBridge.spec.d.ts +0 -1
  107. package/dist/src/lib/utils/tabInfoEncoder.spec.d.ts +0 -1
  108. package/dist/src/lib/utils/urlParser.spec.d.ts +0 -1
  109. package/dist/vue-stack-tabs.umd.js +0 -1
  110. package/src/lib/hooks/tabPanel/evict.spec.ts +0 -85
  111. package/src/lib/hooks/useTabPanel.spec.ts +0 -34
  112. package/src/lib/model/TabModel.spec.ts +0 -40
  113. package/src/lib/utils/iframeBridge.spec.ts +0 -37
  114. package/src/lib/utils/tabInfoEncoder.spec.ts +0 -32
  115. package/src/lib/utils/urlParser.spec.ts +0 -45
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # vue-stack-tabs
2
2
 
3
+ **中文** | [English](./README.en.md) | [Русский](./README.ru.md)
4
+
3
5
  > Vue 3 多标签页管理库,基于 Vue Router。用 Vue 作用域实现类似 iframe TabPanel 的效果——**每个 Tab 间组件作用域互不干扰**。
4
6
 
5
7
  ## ✨ 特性
@@ -36,7 +38,7 @@ pnpm add vue-stack-tabs
36
38
  // main.ts
37
39
  import { createApp } from 'vue'
38
40
  import VueStackTabs from 'vue-stack-tabs'
39
- import 'vue-stack-tabs/dist/vue-stack-tabs.css'
41
+ import 'vue-stack-tabs/dist/style.css'
40
42
  import App from './App.vue'
41
43
  import router from './router'
42
44
 
@@ -46,6 +48,8 @@ app.use(VueStackTabs)
46
48
  app.mount('#app')
47
49
  ```
48
50
 
51
+ > `vue-stack-tabs` 每个 Vue app 只支持一个 `<VueStackTabs>` 实例。默认导入 `VueStackTabs` 是 Vue plugin,用于 `app.use(VueStackTabs)`;如果需要本地组件导入,请使用 named export。
52
+
49
53
  ### 2. 配置路由
50
54
 
51
55
  标签页依赖 Vue Router,需要一个承载 `<VueStackTabs>` 的父路由:
@@ -98,6 +102,12 @@ const defaultTabs: ITabData[] = [
98
102
  </script>
99
103
  ```
100
104
 
105
+ 如果需要本地组件导入,推荐使用 `VueStackTabs` named export;`StackTab` named export 会继续保留作为兼容别名:
106
+
107
+ ```ts
108
+ import { VueStackTabs as VueStackTabsComponent } from 'vue-stack-tabs'
109
+ ```
110
+
101
111
  ### 4. 打开标签
102
112
 
103
113
  ```ts
@@ -133,7 +143,7 @@ export default defineNuxtConfig({
133
143
  vueStackTabs: {
134
144
  locale: 'zh-CN'
135
145
  },
136
- css: ['vue-stack-tabs/dist/vue-stack-tabs.css']
146
+ css: ['vue-stack-tabs/dist/style.css']
137
147
  })
138
148
  ```
139
149
 
@@ -173,6 +183,54 @@ const { openTab } = useTabActions()
173
183
 
174
184
  ---
175
185
 
186
+ ## ESM-only 与 iframe bridge 子入口
187
+
188
+ `vue-stack-tabs` 从当前版本开始只承诺 ESM import,不再支持 `require('vue-stack-tabs')`。
189
+
190
+ 主应用中继续使用 root 入口:
191
+
192
+ ```ts
193
+ import VueStackTabs from 'vue-stack-tabs'
194
+ import 'vue-stack-tabs/dist/style.css'
195
+ ```
196
+
197
+ iframe 内部页面建议使用无样式副作用的子入口:
198
+
199
+ ```ts
200
+ import { postOpenTab, onRefreshRequest } from 'vue-stack-tabs/iframe-bridge'
201
+
202
+ postOpenTab(
203
+ { title: '订单详情', path: '/orders/1' },
204
+ { targetOrigin: 'https://parent.example.com' }
205
+ )
206
+
207
+ const off = onRefreshRequest(() => window.location.reload(), {
208
+ allowedOrigins: ['https://parent.example.com']
209
+ })
210
+ ```
211
+
212
+ 默认未传 `targetOrigin` 时使用 iframe 页面自身的 `window.location.origin`,适合同源父页面;跨域父页面必须显式传 `targetOrigin`。生产环境建议同时显式传入 `targetOrigin` 和 `allowedOrigins`。
213
+
214
+ ---
215
+
216
+ ## iframe 安全策略
217
+
218
+ `VueStackTabs` 支持配置 iframe 安全属性:
219
+
220
+ ```vue
221
+ <VueStackTabs
222
+ iframe-path="/__iframe"
223
+ iframe-sandbox="allow-scripts allow-forms allow-popups allow-downloads allow-same-origin"
224
+ iframe-referrer-policy="strict-origin-when-cross-origin"
225
+ iframe-allow="fullscreen"
226
+ :iframe-load-timeout="15000"
227
+ />
228
+ ```
229
+
230
+ 默认 sandbox 保留常见业务页面能力,属于兼容优先,不是强隔离;强隔离请移除 `allow-same-origin`,并按业务能力继续减少 sandbox token。如果你的同源业务页面确实不能在 sandbox 下工作,可以传入空字符串关闭 sandbox,但这会降低隔离强度。
231
+
232
+ ---
233
+
176
234
  ## 📖 API 参考
177
235
 
178
236
  ### useTabActions
@@ -189,6 +247,7 @@ const {
189
247
  refreshTab, // (id: string) => void
190
248
  refreshAllTabs, // () => void
191
249
  activeTab, // (id: string, isRoute?: boolean) => void
250
+ moveTab, // (id: string, targetIndex: number) => boolean
192
251
  reset, // () => void
193
252
  tabs // Ref<ITabItem[]>
194
253
  } = useTabActions()
@@ -199,9 +258,10 @@ const {
199
258
  | `openTab(tab, renew?)` | 打开新标签。`renew=true` 时若已存在则清空页面栈后重新打开 |
200
259
  | `closeTab(id)` | 关闭指定标签,返回新激活的标签 ID |
201
260
  | `closeAllTabs()` | 关闭所有可关闭标签 |
202
- | `refreshTab(id)` | 刷新指定标签(替换 ULID,重建组件实例) |
261
+ | `refreshTab(id)` | 刷新指定标签(替换缓存 ID,重建组件实例) |
203
262
  | `refreshAllTabs()` | 刷新所有标签 |
204
263
  | `activeTab(id)` | 激活指定标签(切换 Tab) |
264
+ | `moveTab(id, targetIndex)` | 将标签移动到指定索引;标签栏也支持鼠标拖放排序 |
205
265
  | `reset()` | 关闭所有标签并重置状态 |
206
266
 
207
267
  ---
@@ -283,22 +343,26 @@ fetchData().finally(() => closeTabLoading())
283
343
 
284
344
  `<VueStackTabs>` 组件属性:
285
345
 
286
- | Prop | 类型 | 默认值 | 说明 |
287
- | ---------------------- | --------------------------- | ----------------------- | ------------------------------------------------- |
288
- | `iframePath` | `string` | **必填** | iframe 占位路由的路径 |
289
- | `defaultTabs` | `ITabData[]` | `[]` | 初始标签列表 |
290
- | `max` | `number` | `20` | 最大标签数量 |
291
- | `contextmenu` | `boolean \| object` | `true` | 是否启用右键菜单 |
292
- | `pageTransition` | `string` | `'stack-tab-swap'` | 前进时的页面转场动画名 |
293
- | `pageTransitionBack` | `string` | `'stack-tab-swap-back'` | 后退时的页面转场动画名 |
294
- | `tabTransition` | `string \| TransitionProps` | `'stack-tab-zoom'` | 标签增删时的过渡效果 |
295
- | `tabScrollMode` | `TabScrollMode` | `'both'` | 标签栏滚动方式:`'wheel'` / `'button'` / `'both'` |
296
- | `width` | `string` | `'100%'` | 容器宽度 |
297
- | `height` | `string` | `'100%'` | 容器高度 |
298
- | `i18n` | `string` | `'zh-CN'` | 国际化语言 |
299
- | `globalScroll` | `boolean` | `false` | 是否使用页面级滚动记忆 |
300
- | `sessionPrefix` | `string` | `''` | sessionStorage 键前缀 |
301
- | `iframeAllowedOrigins` | `string[]` | 同源 | 允许 iframe postMessage 的来源列表 |
346
+ | Prop | 类型 | 默认值 | 说明 |
347
+ | ---------------------- | --------------------------- | -------------------------------------------------------------------------- | ------------------------------------------------------- |
348
+ | `iframePath` | `string` | **必填** | iframe 占位路由的路径 |
349
+ | `iframeSandbox` | `string` | `allow-scripts allow-forms allow-popups allow-downloads allow-same-origin` | iframe sandbox 策略;默认兼容优先不是强隔离,强隔离请移除 `allow-same-origin`;传空字符串可关闭 sandbox(不推荐) |
350
+ | `iframeReferrerPolicy` | `ReferrerPolicy` | `strict-origin-when-cross-origin` | iframe referrerpolicy 属性 |
351
+ | `iframeAllow` | `string` | `''` | iframe allow 属性,例如 `fullscreen` |
352
+ | `iframeLoadTimeout` | `number` | `15000` | iframe 加载超时时间,单位 ms |
353
+ | `defaultTabs` | `ITabData[]` | `[]` | 初始标签列表 |
354
+ | `max` | `number` | `20` | 最大标签数量 |
355
+ | `contextmenu` | `boolean \| object` | `true` | 是否启用右键菜单 |
356
+ | `pageTransition` | `string` | `'stack-tab-swap'` | 前进时的页面转场动画名 |
357
+ | `pageTransitionBack` | `string` | `'stack-tab-swap-back'` | 后退时的页面转场动画名 |
358
+ | `tabTransition` | `string \| TransitionProps` | `'stack-tab-zoom'` | 标签增删时的过渡效果 |
359
+ | `tabScrollMode` | `TabScrollMode` | `'both'` | 标签栏滚动方式:`'wheel'` / `'button'` / `'both'` |
360
+ | `width` | `string` | `'100%'` | 容器宽度 |
361
+ | `height` | `string` | `'100%'` | 容器高度 |
362
+ | `i18n` | `string` | `'zh-CN'` | 国际化语言 |
363
+ | `globalScroll` | `boolean` | `false` | 是否使用页面级滚动记忆 |
364
+ | `sessionPrefix` | `string` | `''` | sessionStorage 键前缀 |
365
+ | `iframeAllowedOrigins` | `string[]` | 同源 | 允许 iframe postMessage 的来源列表 |
302
366
 
303
367
  ---
304
368
 
@@ -323,38 +387,76 @@ openTab({
323
387
  })
324
388
  ```
325
389
 
326
- ### iframe 内页面打开标签
390
+ ### iframe 页面内操作
391
+
392
+ 在 iframe 标签页内,可以通过 `postMessage` 与宿主容器进行通信,实现打开新标签或响应刷新操作。
327
393
 
328
- 在 iframe 内的页面中,可以通过 `postMessage` 打开宿主的标签:
394
+ #### 1. 使用 Bridge 工具(推荐)
395
+
396
+ 如果 iframe 内页能引用库代码,推荐使用无样式副作用的 `vue-stack-tabs/iframe-bridge` 子入口:
329
397
 
330
398
  ```ts
331
- import { postOpenTab } from 'vue-stack-tabs'
399
+ import { postOpenTab, onRefreshRequest } from 'vue-stack-tabs/iframe-bridge'
332
400
 
333
- // 在 iframe 内调用
334
- postOpenTab({
335
- title: '新页面',
336
- path: '/detail',
337
- query: { id: '1' }
338
- })
339
- ```
401
+ const parentOrigin = 'https://parent.example.com'
340
402
 
341
- ### iframe 刷新
403
+ // 打开标签
404
+ postOpenTab(
405
+ {
406
+ title: '新页面',
407
+ path: '/detail',
408
+ query: { id: '1' }
409
+ },
410
+ { targetOrigin: parentOrigin }
411
+ )
412
+
413
+ // 监听刷新请求
414
+ const off = onRefreshRequest(
415
+ () => {
416
+ // 自定义刷新逻辑
417
+ location.reload()
418
+ },
419
+ { allowedOrigins: [parentOrigin] }
420
+ )
421
+ ```
342
422
 
343
- iframe 刷新有两种模式:
423
+ #### 2. 原生对接方式
344
424
 
345
- - `postMessage`(默认):向 iframe 发送消息,由内页自行刷新
346
- - `reload`:重建 iframe DOM(适用于跨域页面)
425
+ 如果无法引入库工具,或希望保持零依赖,可以使用原生 API。生产环境必须使用明确的父页面 origin,并校验刷新消息来源。`postOpenTab` 的默认 `targetOrigin` 是 iframe 页面的 `window.location.origin`,跨域父页面必须显式传入父页面 origin:
347
426
 
348
427
  ```ts
349
- // iframe 内页面监听刷新请求
350
- import { onRefreshRequest } from 'vue-stack-tabs'
428
+ const parentOrigin = 'https://parent.example.com'
351
429
 
352
- onRefreshRequest(() => {
353
- // 执行刷新逻辑
354
- location.reload()
430
+ // 打开标签
431
+ window.parent.postMessage(
432
+ {
433
+ type: 'vue-stack-tabs:openTab',
434
+ payload: {
435
+ title: '原生新页面',
436
+ path: '/detail'
437
+ }
438
+ },
439
+ parentOrigin
440
+ )
441
+
442
+ // 监听刷新请求
443
+ window.addEventListener('message', (ev) => {
444
+ if (ev.origin !== parentOrigin) return
445
+ if (ev.source !== window.parent) return
446
+ if (ev.data?.type === 'vue-stack-tabs:refresh') {
447
+ // 执行刷新
448
+ location.reload()
449
+ }
355
450
  })
356
451
  ```
357
452
 
453
+ ### iframe 刷新模式
454
+
455
+ 在 `openTab` 时可通过 `iframeRefreshMode` 指定刷新行为:
456
+
457
+ - `postMessage`(默认):宿主向 iframe 发送 `vue-stack-tabs:refresh` 消息,由内页自行决定如何刷新(动画更丝滑)。
458
+ - `reload`:宿主直接重置 iframe 的 `src` 或 `key` 触发浏览器强制重载(适用于跨域或无法修改代码的页面)。
459
+
358
460
  ---
359
461
 
360
462
  ## 🌍 国际化
@@ -388,7 +490,7 @@ app.use(VueStackTabs, [
388
490
  ```ts
389
491
  /** 打开标签时传入的数据 */
390
492
  interface ITabData {
391
- id?: string // 标签 ID(不传则自动生成 ULID)
493
+ id?: string // 标签 ID(不传则自动生成 UUID)
392
494
  title: string // 标签标题
393
495
  path: string // 路由路径或 iframe URL
394
496
  query?: Record<string, string> // 路由参数