vue-stack-tabs 0.1.51 → 1.0.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 (94) hide show
  1. package/README.md +717 -3
  2. package/dist/index.d.ts +3 -0
  3. package/dist/src/lib/StackTabs.vue.d.ts +33 -161
  4. package/dist/src/lib/components/ContextMenu/ContextMenuItem.vue.d.ts +6 -6
  5. package/dist/src/lib/components/ContextMenu/index.vue.d.ts +14 -14
  6. package/dist/src/lib/components/PageLoading.vue.d.ts +5 -5
  7. package/dist/src/lib/components/TabHeader/TabHeaderButton.vue.d.ts +24 -15
  8. package/dist/src/lib/components/TabHeader/TabHeaderItem.vue.d.ts +10 -8
  9. package/dist/src/lib/components/TabHeader/TabHeaderScroll.vue.d.ts +31 -13
  10. package/dist/src/lib/components/TabHeader/index.vue.d.ts +172 -20
  11. package/dist/src/lib/hooks/tabPanel/evict.d.ts +24 -0
  12. package/dist/src/lib/hooks/tabPanel/evict.spec.d.ts +1 -0
  13. package/dist/src/lib/hooks/tabPanel/scroll.d.ts +8 -0
  14. package/dist/src/lib/hooks/tabPanel/session.d.ts +9 -0
  15. package/dist/src/lib/hooks/tabPanel/state.d.ts +77 -0
  16. package/dist/src/lib/hooks/useContextMenu.d.ts +6 -1
  17. package/dist/src/lib/hooks/useTabActions.d.ts +68 -0
  18. package/dist/src/lib/hooks/{useTabMitt.d.ts → useTabEventBus.d.ts} +4 -3
  19. package/dist/src/lib/hooks/useTabLoading.d.ts +6 -0
  20. package/dist/src/lib/hooks/{useTabpanel.d.ts → useTabPanel.d.ts} +20 -14
  21. package/dist/src/lib/hooks/useTabPanel.spec.d.ts +1 -0
  22. package/dist/src/lib/hooks/useTabRouter.d.ts +14 -5
  23. package/dist/src/lib/i18n/index.d.ts +1 -1
  24. package/dist/src/lib/i18n/lang/en.d.ts +1 -0
  25. package/dist/src/lib/i18n/lang/zh-CN.d.ts +1 -0
  26. package/dist/src/lib/index.d.ts +5 -4
  27. package/dist/src/lib/model/TabModel.d.ts +20 -3
  28. package/dist/src/lib/model/TabModel.spec.d.ts +1 -0
  29. package/dist/src/lib/utils/iframeBridge.d.ts +31 -0
  30. package/dist/src/lib/utils/iframeBridge.spec.d.ts +1 -0
  31. package/dist/src/lib/utils/scrollUtils.d.ts +28 -0
  32. package/dist/src/lib/utils/tabInfoEncoder.d.ts +19 -0
  33. package/dist/src/lib/utils/tabInfoEncoder.spec.d.ts +1 -0
  34. package/dist/src/lib/utils/urlParser.d.ts +19 -0
  35. package/dist/src/lib/utils/urlParser.spec.d.ts +1 -0
  36. package/dist/src/lib/versionLogger.d.ts +2 -0
  37. package/dist/style.css +1 -1
  38. package/dist/vue-stack-tabs.css +1 -0
  39. package/dist/vue-stack-tabs.es.js +1795 -1679
  40. package/dist/vue-stack-tabs.umd.js +1 -5
  41. package/package.json +76 -39
  42. package/src/lib/StackTabs.vue +222 -47
  43. package/src/lib/assets/image/open.svg +1 -0
  44. package/src/lib/assets/style/common/var.scss +1 -1
  45. package/src/lib/assets/style/icon.scss +6 -0
  46. package/src/lib/assets/style/stackTab.scss +46 -3
  47. package/src/lib/assets/style/transition.scss +63 -62
  48. package/src/lib/components/ContextMenu/ContextMenuItem.vue +7 -0
  49. package/src/lib/components/ContextMenu/index.vue +50 -38
  50. package/src/lib/components/PageLoading.vue +30 -10
  51. package/src/lib/components/TabHeader/TabHeaderButton.vue +12 -3
  52. package/src/lib/components/TabHeader/TabHeaderItem.vue +31 -27
  53. package/src/lib/components/TabHeader/TabHeaderScroll.vue +83 -77
  54. package/src/lib/components/TabHeader/index.vue +45 -40
  55. package/src/lib/hooks/tabPanel/evict.spec.ts +85 -0
  56. package/src/lib/hooks/tabPanel/evict.ts +57 -0
  57. package/src/lib/hooks/tabPanel/scroll.ts +52 -0
  58. package/src/lib/hooks/tabPanel/session.ts +33 -0
  59. package/src/lib/hooks/tabPanel/state.ts +52 -0
  60. package/src/lib/hooks/useContextMenu.ts +22 -20
  61. package/src/lib/hooks/useTabActions.ts +148 -0
  62. package/src/lib/hooks/useTabEventBus.ts +31 -0
  63. package/src/lib/hooks/useTabLoading.ts +12 -4
  64. package/src/lib/hooks/useTabPanel.spec.ts +34 -0
  65. package/src/lib/hooks/useTabPanel.tsx +546 -0
  66. package/src/lib/hooks/useTabRouter.ts +114 -103
  67. package/src/lib/i18n/index.ts +14 -18
  68. package/src/lib/i18n/lang/en.ts +2 -1
  69. package/src/lib/i18n/lang/zh-CN.ts +2 -1
  70. package/src/lib/iframe.vue +6 -0
  71. package/src/lib/index.ts +27 -8
  72. package/src/lib/model/TabModel.spec.ts +40 -0
  73. package/src/lib/model/TabModel.ts +22 -9
  74. package/src/lib/nuxt/module.d.ts +19 -0
  75. package/src/lib/nuxt/module.ts +34 -0
  76. package/src/lib/nuxt/runtime/plugin.client.ts +11 -0
  77. package/src/lib/utils/iframeBridge.spec.ts +37 -0
  78. package/src/lib/utils/iframeBridge.ts +52 -0
  79. package/src/lib/utils/{TabScrollHelper.ts → scrollUtils.ts} +23 -29
  80. package/src/lib/utils/tabInfoEncoder.spec.ts +42 -0
  81. package/src/lib/utils/tabInfoEncoder.ts +65 -0
  82. package/src/lib/utils/urlParser.spec.ts +45 -0
  83. package/src/lib/utils/urlParser.ts +42 -0
  84. package/src/lib/{banner.ts → versionLogger.ts} +4 -5
  85. package/dist/src/lib/banner.d.ts +0 -2
  86. package/dist/src/lib/hooks/useStackTab.d.ts +0 -11
  87. package/dist/src/lib/utils/TabIdHelper.d.ts +0 -11
  88. package/dist/src/lib/utils/TabScrollHelper.d.ts +0 -33
  89. package/dist/src/lib/utils/UriHelper.d.ts +0 -4
  90. package/src/lib/hooks/useStackTab.ts +0 -119
  91. package/src/lib/hooks/useTabMitt.ts +0 -30
  92. package/src/lib/hooks/useTabpanel.tsx +0 -708
  93. package/src/lib/utils/TabIdHelper.ts +0 -70
  94. package/src/lib/utils/UriHelper.ts +0 -22
package/README.md CHANGED
@@ -1,3 +1,717 @@
1
- # Vue Stack Tabs
2
-
3
- Vue.js tab components, based on Vue Router.
1
+ # Vue Stack Tabs
2
+
3
+ [![npm version](https://img.shields.io/npm/v/vue-stack-tabs.svg)](https://www.npmjs.com/package/vue-stack-tabs)
4
+ [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)
5
+
6
+ A multi-tab management library for Vue 3 and Vue Router. Each tab has its own component stack with isolated scope, suitable for admin dashboards, multi-tab workspaces, and similar scenarios.
7
+
8
+ 基于 Vue 3 和 Vue Router 的多标签页管理库。每个标签拥有独立的组件栈,作用域互不干扰,适合后台管理、多页签工作台等场景。
9
+
10
+ ---
11
+
12
+ ## Table of Contents / 目录
13
+
14
+ - [Features / 特性](#features--特性)
15
+ - [Installation / 安装](#installation--安装)
16
+ - [Quick Start / 快速开始](#quick-start--快速开始)
17
+ - [Props / 属性](#props--属性)
18
+ - [API](#api)
19
+ - [Nuxt](#nuxt)
20
+ - [iframe & PostMessage](#iframe--postmessage)
21
+ - [i18n / 国际化](#i18n--国际化)
22
+ - [Sub-routes / 子路由](#sub-routes-experimental--子路由试验性)
23
+ - [完整中文说明](#完整中文说明)
24
+ - [License / 许可证](#license--许可证)
25
+
26
+ ---
27
+
28
+ ## Features / 特性
29
+
30
+ - **Route-based & iframe tabs** — Tab pages from Vue Router routes or embedded iframes
31
+ - **Tab operations** — Add, close, refresh, batch close (all / left / right / others)
32
+ - **Stack navigation** — Forward / backward within a tab
33
+ - **Scroll position** — Remember and restore scroll per page
34
+ - **Session recovery** — Restore last active tab on refresh
35
+ - **Nuxt 3/4 module** — Official Nuxt integration
36
+
37
+ ---
38
+
39
+ ## Installation / 安装
40
+
41
+ ```bash
42
+ pnpm add vue-stack-tabs
43
+ # or
44
+ npm install vue-stack-tabs
45
+ ```
46
+
47
+ **Peer dependencies:** Vue 3, Vue Router 5.x
48
+
49
+ ---
50
+
51
+ ## Quick Start / 快速开始
52
+
53
+ ### Vue
54
+
55
+ ```ts
56
+ // main.ts
57
+ import { createApp } from 'vue'
58
+ import VueStackTabs from 'vue-stack-tabs'
59
+ import 'vue-stack-tabs/dist/style.css'
60
+ import App from './App.vue'
61
+ import router from './router'
62
+
63
+ const app = createApp(App)
64
+ app.use(router)
65
+ app.use(VueStackTabs)
66
+ app.mount('#app')
67
+ ```
68
+
69
+ ```vue
70
+ <!-- App.vue or layout -->
71
+ <template>
72
+ <vue-stack-tabs iframe-path="/iframe" />
73
+ </template>
74
+ ```
75
+
76
+ Configure the iframe route to match `iframe-path` (see [Route config](#route-config--路由配置)).
77
+
78
+ ### Route Config / 路由配置
79
+
80
+ The parent route must wrap `<vue-stack-tabs>`, and the iframe path route must be registered.
81
+
82
+ ```ts
83
+ // router.ts
84
+ import { createRouter, createWebHistory } from 'vue-router'
85
+ import Frame from '@/components/layout/Frame.vue'
86
+ import { IFrame } from 'vue-stack-tabs'
87
+
88
+ const router = createRouter({
89
+ history: createWebHistory(),
90
+ routes: [
91
+ {
92
+ path: '/',
93
+ component: Frame, // contains <vue-stack-tabs>
94
+ children: [
95
+ { path: '', component: () => import('@/views/home.vue') },
96
+ { path: 'iframe', component: IFrame }, // must match iframe-path
97
+ { path: ':pathMatch(.*)', redirect: '/404' }
98
+ ]
99
+ }
100
+ ]
101
+ })
102
+ ```
103
+
104
+ ---
105
+
106
+ ## Props / 属性
107
+
108
+ | Prop | Type | Default | Description |
109
+ |------|------|---------|-------------|
110
+ | `defaultTabs` | `ITabData[]` | `[]` | Initial tabs |
111
+ | `max` | `number` | `20` | Max open tabs |
112
+ | `iframePath` | `string` | — | iframe route path **(required)** |
113
+ | `iframeAllowedOrigins` | `string[]` | `[]` | Origins allowed for postMessage `openTab` |
114
+ | `pageTransition` | `string` | `stack-tab-swap` | Page transition name |
115
+ | `tabTransition` | `string` | `stack-tab-zoom` | Tab transition name |
116
+ | `tabScrollMode` | `'wheel' \| 'button' \| 'both'` | `both` | Tab bar scroll mode |
117
+ | `width` / `height` | `string` | `100%` | Component size |
118
+ | `i18n` | `string` | `zh-CN` | Locale (`zh-CN` \| `en`) |
119
+ | `space` | `number` | `300` | Tab scroll step (px) |
120
+ | `globalScroll` | `boolean` | `false` | Use page-level scroll |
121
+ | `contextmenu` | `boolean \| object` | `true` | Right-click menu |
122
+ | `sessionPrefix` | `string` | `''` | Prefix for sessionStorage key |
123
+
124
+ ---
125
+
126
+ ## API
127
+
128
+ ### useTabActions
129
+
130
+ ```ts
131
+ import { useTabActions } from 'vue-stack-tabs'
132
+
133
+ const {
134
+ openTab,
135
+ closeTab,
136
+ closeAllTabs,
137
+ refreshTab,
138
+ refreshAllTabs,
139
+ activeTab,
140
+ reset,
141
+ tabs,
142
+ openInNewWindow
143
+ } = useTabActions()
144
+ ```
145
+
146
+ | Method | Description |
147
+ |--------|-------------|
148
+ | `openTab(tab, renew?)` | Open tab. `renew=true` clears stack and reopens |
149
+ | `closeTab(id)` | Close tab |
150
+ | `closeAllTabs()` | Close all tabs |
151
+ | `refreshTab(id)` | Refresh current page of tab |
152
+ | `refreshAllTabs()` | Refresh all tabs |
153
+ | `activeTab(id, isRoute?)` | Activate tab |
154
+ | `reset()` | Reset all tabs |
155
+ | `openInNewWindow(id)` | Open iframe tab in new window (fallback) |
156
+
157
+ **openTab(tab: ITabData)**
158
+
159
+ | Field | Type | Required | Description |
160
+ |-------|------|----------|-------------|
161
+ | `id` | `string` | No | Tab id (auto-generated if omitted) |
162
+ | `title` | `string` | Yes | Tab title |
163
+ | `path` | `string` | Yes | Route path or iframe URL |
164
+ | `query` | `Record<string, string>` | No | Query params |
165
+ | `iframe` | `boolean` | No | Open as iframe |
166
+ | `iframeRefreshMode` | `'postMessage' \| 'reload'` | No | iframe refresh mode |
167
+ | `closable` | `boolean` | No | Default `true` |
168
+ | `refreshable` | `boolean` | No | Default `true` |
169
+
170
+ ### useTabRouter
171
+
172
+ Used **inside tab pages** for in-tab navigation.
173
+
174
+ ```ts
175
+ import { useTabRouter } from 'vue-stack-tabs'
176
+
177
+ const { forward, backward, addScrollTarget } = useTabRouter()
178
+
179
+ // Forward: push new page
180
+ forward({ path: '/detail', query: { id: '1' } })
181
+
182
+ // Backward: pop stack
183
+ backward('/list') // back to first matching path
184
+ backward(1) // back 1 step
185
+ backward(2, { foo: 'bar' }) // back 2 steps, pass query to target
186
+
187
+ // Register scroll containers for position restore
188
+ addScrollTarget('.scroll-area', '#panel')
189
+ ```
190
+
191
+ ### useTabLoading
192
+
193
+ ```ts
194
+ import { useTabLoading } from 'vue-stack-tabs'
195
+
196
+ const { openTabLoading, closeTabLoading } = useTabLoading()
197
+ ```
198
+
199
+ ### Initial Tabs / 初始化页签
200
+
201
+ ```vue
202
+ <vue-stack-tabs
203
+ iframe-path="/iframe"
204
+ :default-tabs="[
205
+ { id: 'home', title: 'Home', path: '/', closable: false },
206
+ { title: 'About', path: '/about' }
207
+ ]"
208
+ />
209
+ ```
210
+
211
+ > The first tab should be non-closable (`closable: false`).
212
+
213
+ ---
214
+
215
+ ## Nuxt
216
+
217
+ ### Module (recommended)
218
+
219
+ ```ts
220
+ // nuxt.config.ts
221
+ export default defineNuxtConfig({
222
+ modules: ['vue-stack-tabs/nuxt'],
223
+ vueStackTabs: {
224
+ locale: 'zh-CN'
225
+ }
226
+ })
227
+ ```
228
+
229
+ The module auto-imports `useTabActions`, `useTabRouter`, `useTabLoading`.
230
+
231
+ ### iframe route in Nuxt
232
+
233
+ ```ts
234
+ // nuxt.config.ts
235
+ export default defineNuxtConfig({
236
+ hooks: {
237
+ 'pages:extend'(pages) {
238
+ pages.push({
239
+ name: 'iframe',
240
+ path: '/iframe',
241
+ file: '~/components/IFrame.vue' // minimal component with useTabRouter()
242
+ })
243
+ }
244
+ }
245
+ })
246
+ ```
247
+
248
+ ---
249
+
250
+ ## iframe & PostMessage
251
+
252
+ ### Open tab from iframe
253
+
254
+ ```ts
255
+ import { postOpenTab } from 'vue-stack-tabs'
256
+
257
+ postOpenTab({
258
+ title: 'New Page',
259
+ path: '/detail',
260
+ query: { id: '123' },
261
+ closable: true,
262
+ refreshable: true
263
+ })
264
+ ```
265
+
266
+ Parent must allow the iframe origin if cross-origin:
267
+
268
+ ```vue
269
+ <vue-stack-tabs
270
+ iframe-path="/iframe"
271
+ :iframe-allowed-origins="['https://iframe-origin.com']"
272
+ />
273
+ ```
274
+
275
+ ### Receive refresh in iframe
276
+
277
+ ```ts
278
+ import { onRefreshRequest } from 'vue-stack-tabs'
279
+ import { onUnmounted } from 'vue'
280
+
281
+ const unlisten = onRefreshRequest() // defaults to location.reload()
282
+ onUnmounted(unlisten)
283
+ ```
284
+
285
+ ### Cross-origin iframe (reload mode)
286
+
287
+ When the iframe is cross-origin and you cannot modify its code:
288
+
289
+ ```ts
290
+ openTab({
291
+ title: 'External',
292
+ path: 'https://example.com',
293
+ iframe: true,
294
+ iframeRefreshMode: 'reload'
295
+ })
296
+ ```
297
+
298
+ ---
299
+
300
+ ## i18n / 国际化
301
+
302
+ Built-in: `zh-CN`, `en`.
303
+
304
+ ```vue
305
+ <vue-stack-tabs iframe-path="/iframe" i18n="zh-CN" />
306
+ ```
307
+
308
+ ### Custom messages
309
+
310
+ ```ts
311
+ app.use(VueStackTabs, [{
312
+ locale: 'zh-CN',
313
+ messages: {
314
+ VueStackTab: {
315
+ close: 'Close',
316
+ closeLefts: 'Close left',
317
+ closeRights: 'Close right',
318
+ closeOthers: 'Close others',
319
+ closeAll: 'Close all',
320
+ reload: 'Reload',
321
+ reloadAll: 'Reload all',
322
+ maximum: 'Maximize',
323
+ restore: 'Restore',
324
+ undefined: 'Undefined',
325
+ loading: 'Loading',
326
+ openInNewWindow: 'Open in new window'
327
+ }
328
+ }
329
+ }])
330
+ ```
331
+
332
+ ---
333
+
334
+ ## Slots
335
+
336
+ | Slot | Description |
337
+ |------|-------------|
338
+ | `leftButton` | Left side of tab bar |
339
+ | `rightButton` | Right side of tab bar |
340
+
341
+ ---
342
+
343
+ ## Events
344
+
345
+ | Event | Payload | Description |
346
+ |-------|---------|-------------|
347
+ | `onActive` | `id: string` | Fired when a tab is activated |
348
+ | `onPageLoaded` | — | Fired when page component is loaded |
349
+
350
+ ---
351
+
352
+ ## Exports
353
+
354
+ ```ts
355
+ import VueStackTabs, {
356
+ useTabActions,
357
+ useTabRouter,
358
+ useTabLoading,
359
+ TabHeaderButton,
360
+ IFrame,
361
+ postOpenTab,
362
+ onRefreshRequest,
363
+ MSG_REFRESH,
364
+ MSG_OPEN_TAB
365
+ } from 'vue-stack-tabs'
366
+ import type { ITabData, ITabBase, IframeOpenTabPayload } from 'vue-stack-tabs'
367
+ ```
368
+
369
+ ---
370
+
371
+ ## Sub-routes (Experimental / 子路由试验性)
372
+
373
+ Nested routes with `children` and `forward`/`backward` are supported. See [SUBROUTE_EXPERIMENTAL.md](./docs/SUBROUTE_EXPERIMENTAL.md) for details. This feature is experimental.
374
+
375
+ ---
376
+
377
+ ## Development / 开发
378
+
379
+ ```bash
380
+ pnpm install
381
+ pnpm run dev # Demo app
382
+ pnpm run test # Unit tests
383
+ pnpm run test:prepack # Pre-pack: unit + Vue + Nuxt (source)
384
+ pnpm run test:packaged # Post-pack: Vue + Nuxt (dist)
385
+ pnpm run docs:dev # Docs preview
386
+ ```
387
+
388
+ See [CONTRIBUTING.md](./CONTRIBUTING.md). [AGENTS.md](./AGENTS.md) for AI assistants.
389
+
390
+ ---
391
+
392
+ ## 完整中文说明
393
+
394
+ ### 安装与引入
395
+
396
+ ```ts
397
+ // main.ts
398
+ import { createApp } from 'vue'
399
+ import VueStackTabs from 'vue-stack-tabs'
400
+ import 'vue-stack-tabs/dist/style.css'
401
+ import App from './App.vue'
402
+ import router from './router'
403
+
404
+ const app = createApp(App)
405
+ app.use(router)
406
+ app.use(VueStackTabs)
407
+ app.mount('#app')
408
+ ```
409
+
410
+ **依赖:** Vue 3、Vue Router 5.x
411
+
412
+ ### 快速开始
413
+
414
+ ```vue
415
+ <!-- App.vue 或布局组件 -->
416
+ <template>
417
+ <vue-stack-tabs iframe-path="/iframe" />
418
+ </template>
419
+ ```
420
+
421
+ 父路由需包裹 `<vue-stack-tabs>`,并注册与 `iframe-path` 一致的 iframe 路由。
422
+
423
+ ### 路由配置
424
+
425
+ ```ts
426
+ // router.ts
427
+ import { createRouter, createWebHistory } from 'vue-router'
428
+ import Frame from '@/components/layout/Frame.vue'
429
+ import { IFrame } from 'vue-stack-tabs'
430
+
431
+ const router = createRouter({
432
+ history: createWebHistory(),
433
+ routes: [
434
+ {
435
+ path: '/',
436
+ component: Frame, // 内部包含 <vue-stack-tabs>
437
+ children: [
438
+ { path: '', component: () => import('@/views/home.vue') },
439
+ { path: 'iframe', component: IFrame }, // 需与 iframe-path 一致
440
+ { path: ':pathMatch(.*)', redirect: '/404' }
441
+ ]
442
+ }
443
+ ]
444
+ })
445
+ ```
446
+
447
+ ### 属性
448
+
449
+ | 属性 | 类型 | 默认值 | 说明 |
450
+ |------|------|--------|------|
451
+ | `defaultTabs` | `ITabData[]` | `[]` | 初始页签 |
452
+ | `max` | `number` | `20` | 最大打开数量 |
453
+ | `iframePath` | `string` | — | iframe 路由路径 **(必填)** |
454
+ | `iframeAllowedOrigins` | `string[]` | `[]` | 允许 postMessage 调用 openTab 的 iframe 来源 |
455
+ | `pageTransition` | `string` | `stack-tab-swap` | 页面转场效果 |
456
+ | `tabTransition` | `string` | `stack-tab-zoom` | 标签转场效果 |
457
+ | `tabScrollMode` | `'wheel' \| 'button' \| 'both'` | `both` | 标签栏滚动方式 |
458
+ | `width` / `height` | `string` | `100%` | 组件尺寸 |
459
+ | `i18n` | `string` | `zh-CN` | 语言(`zh-CN` \| `en`) |
460
+ | `space` | `number` | `300` | 标签滚动步长(px) |
461
+ | `globalScroll` | `boolean` | `false` | 是否使用页面级滚动 |
462
+ | `contextmenu` | `boolean \| object` | `true` | 右键菜单 |
463
+ | `sessionPrefix` | `string` | `''` | sessionStorage key 前缀 |
464
+
465
+ ### API
466
+
467
+ #### useTabActions
468
+
469
+ ```ts
470
+ import { useTabActions } from 'vue-stack-tabs'
471
+
472
+ const {
473
+ openTab,
474
+ closeTab,
475
+ closeAllTabs,
476
+ refreshTab,
477
+ refreshAllTabs,
478
+ activeTab,
479
+ reset,
480
+ tabs,
481
+ openInNewWindow
482
+ } = useTabActions()
483
+ ```
484
+
485
+ | 方法 | 说明 |
486
+ |------|------|
487
+ | `openTab(tab, renew?)` | 打开标签;`renew=true` 时清空栈并重新打开 |
488
+ | `closeTab(id)` | 关闭指定标签 |
489
+ | `closeAllTabs()` | 关闭所有标签 |
490
+ | `refreshTab(id)` | 刷新指定标签当前页 |
491
+ | `refreshAllTabs()` | 刷新所有标签 |
492
+ | `activeTab(id, isRoute?)` | 激活指定标签 |
493
+ | `reset()` | 重置所有标签 |
494
+ | `openInNewWindow(id)` | iframe 标签在新窗口打开(无法嵌入时降级) |
495
+
496
+ **openTab(tab: ITabData)**
497
+
498
+ | 字段 | 类型 | 必填 | 说明 |
499
+ |------|------|------|------|
500
+ | `id` | `string` | 否 | 标签 id,不填则自动生成 |
501
+ | `title` | `string` | 是 | 标签名称 |
502
+ | `path` | `string` | 是 | 路由路径或 iframe URL |
503
+ | `query` | `Record<string, string>` | 否 | 查询参数 |
504
+ | `iframe` | `boolean` | 否 | 是否以 iframe 打开 |
505
+ | `iframeRefreshMode` | `'postMessage' \| 'reload'` | 否 | iframe 刷新方式 |
506
+ | `closable` | `boolean` | 否 | 默认 `true` |
507
+ | `refreshable` | `boolean` | 否 | 默认 `true` |
508
+
509
+ #### useTabRouter
510
+
511
+ 在**标签内的页面**中使用,用于标签内前进/后退。
512
+
513
+ ```ts
514
+ import { useTabRouter } from 'vue-stack-tabs'
515
+
516
+ const { forward, backward, addScrollTarget } = useTabRouter()
517
+
518
+ // 前进:入栈新页面
519
+ forward({ path: '/detail', query: { id: '1' } })
520
+
521
+ // 后退:出栈
522
+ backward('/list') // 回退到首个匹配路径
523
+ backward(1) // 回退 1 步
524
+ backward(2, { foo: 'bar' }) // 回退 2 步,并向目标页传递 query
525
+
526
+ // 注册滚动容器(用于位置记忆与恢复)
527
+ addScrollTarget('.scroll-area', '#panel')
528
+ ```
529
+
530
+ #### useTabLoading
531
+
532
+ ```ts
533
+ import { useTabLoading } from 'vue-stack-tabs'
534
+
535
+ const { openTabLoading, closeTabLoading } = useTabLoading()
536
+ ```
537
+
538
+ #### 初始化页签
539
+
540
+ ```vue
541
+ <vue-stack-tabs
542
+ iframe-path="/iframe"
543
+ :default-tabs="[
544
+ { id: 'home', title: '首页', path: '/', closable: false },
545
+ { title: '关于', path: '/about' }
546
+ ]"
547
+ />
548
+ ```
549
+
550
+ > 第一个页签建议设为不可关闭(`closable: false`)。
551
+
552
+ ### Nuxt
553
+
554
+ #### 模块方式(推荐)
555
+
556
+ ```ts
557
+ // nuxt.config.ts
558
+ export default defineNuxtConfig({
559
+ modules: ['vue-stack-tabs/nuxt'],
560
+ vueStackTabs: {
561
+ locale: 'zh-CN'
562
+ }
563
+ })
564
+ ```
565
+
566
+ 模块会自动导入 `useTabActions`、`useTabRouter`、`useTabLoading`。
567
+
568
+ #### Nuxt 中配置 iframe 路由
569
+
570
+ ```ts
571
+ // nuxt.config.ts
572
+ export default defineNuxtConfig({
573
+ hooks: {
574
+ 'pages:extend'(pages) {
575
+ pages.push({
576
+ name: 'iframe',
577
+ path: '/iframe',
578
+ file: '~/components/IFrame.vue' // 最小组件,含 useTabRouter() 即可
579
+ })
580
+ }
581
+ }
582
+ })
583
+ ```
584
+
585
+ ### iframe 与父窗口通信
586
+
587
+ #### iframe 内打开新标签
588
+
589
+ ```ts
590
+ import { postOpenTab } from 'vue-stack-tabs'
591
+
592
+ postOpenTab({
593
+ title: '新页面',
594
+ path: '/detail',
595
+ query: { id: '123' },
596
+ closable: true,
597
+ refreshable: true
598
+ })
599
+ ```
600
+
601
+ 跨域时父应用需配置 `iframeAllowedOrigins`:
602
+
603
+ ```vue
604
+ <vue-stack-tabs
605
+ iframe-path="/iframe"
606
+ :iframe-allowed-origins="['https://iframe来源域名']"
607
+ />
608
+ ```
609
+
610
+ #### iframe 内接收刷新指令
611
+
612
+ ```ts
613
+ import { onRefreshRequest } from 'vue-stack-tabs'
614
+ import { onUnmounted } from 'vue'
615
+
616
+ const unlisten = onRefreshRequest() // 默认执行 location.reload()
617
+ onUnmounted(unlisten)
618
+ ```
619
+
620
+ #### 跨域 iframe(reload 模式)
621
+
622
+ 若 iframe 为跨域且无法修改其代码,可指定 `iframeRefreshMode: 'reload'`,刷新时直接重载 iframe:
623
+
624
+ ```ts
625
+ openTab({
626
+ title: '外部页面',
627
+ path: 'https://example.com',
628
+ iframe: true,
629
+ iframeRefreshMode: 'reload'
630
+ })
631
+ ```
632
+
633
+ ### 国际化
634
+
635
+ 内置 `zh-CN`、`en`。
636
+
637
+ ```vue
638
+ <vue-stack-tabs iframe-path="/iframe" i18n="zh-CN" />
639
+ ```
640
+
641
+ #### 自定义文案
642
+
643
+ ```ts
644
+ app.use(VueStackTabs, [{
645
+ locale: 'zh-CN',
646
+ messages: {
647
+ VueStackTab: {
648
+ close: '关闭',
649
+ closeLefts: '关闭左侧',
650
+ closeRights: '关闭右侧',
651
+ closeOthers: '关闭其他',
652
+ closeAll: '关闭全部',
653
+ reload: '刷新',
654
+ reloadAll: '刷新全部',
655
+ maximum: '最大化',
656
+ restore: '还原',
657
+ undefined: '未定义',
658
+ loading: '加载中',
659
+ openInNewWindow: '新窗口打开'
660
+ }
661
+ }
662
+ }])
663
+ ```
664
+
665
+ ### 插槽
666
+
667
+ | 插槽 | 说明 |
668
+ |------|------|
669
+ | `leftButton` | 标签栏左侧 |
670
+ | `rightButton` | 标签栏右侧 |
671
+
672
+ ### 事件
673
+
674
+ | 事件 | 参数 | 说明 |
675
+ |------|------|------|
676
+ | `onActive` | `id: string` | 标签被激活时触发 |
677
+ | `onPageLoaded` | — | 页面组件加载完成时触发 |
678
+
679
+ ### 导出
680
+
681
+ ```ts
682
+ import VueStackTabs, {
683
+ useTabActions,
684
+ useTabRouter,
685
+ useTabLoading,
686
+ TabHeaderButton,
687
+ IFrame,
688
+ postOpenTab,
689
+ onRefreshRequest,
690
+ MSG_REFRESH,
691
+ MSG_OPEN_TAB
692
+ } from 'vue-stack-tabs'
693
+ import type { ITabData, ITabBase, IframeOpenTabPayload } from 'vue-stack-tabs'
694
+ ```
695
+
696
+ ### 子路由(试验性)
697
+
698
+ 支持路由 `children` 与 `forward`/`backward` 多级导航。详见 [SUBROUTE_EXPERIMENTAL.md](./docs/SUBROUTE_EXPERIMENTAL.md)。该功能为试验性。
699
+
700
+ ### 开发
701
+
702
+ ```bash
703
+ pnpm install
704
+ pnpm run dev # 演示应用
705
+ pnpm run test # 单元测试
706
+ pnpm run test:prepack # 打包前:单元 + Vue + Nuxt(源码)
707
+ pnpm run test:packaged # 打包后:Vue + Nuxt(dist)
708
+ pnpm run docs:dev # 文档预览
709
+ ```
710
+
711
+ 参见 [CONTRIBUTING.md](./CONTRIBUTING.md)。[AGENTS.md](./AGENTS.md) 供 AI 助手参考。
712
+
713
+ ---
714
+
715
+ ## License / 许可证
716
+
717
+ Apache License 2.0
package/dist/index.d.ts CHANGED
@@ -1,3 +1,6 @@
1
1
  export * from './src/lib/index'
2
+ export {}
2
3
  import VueStackTabs from './src/lib/index'
3
4
  export default VueStackTabs
5
+ export * from './src/lib/index'
6
+ export {}