vue-stack-tabs 1.0.1 → 1.3.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.
Files changed (116) hide show
  1. package/README.en.md +526 -0
  2. package/README.md +337 -520
  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 +88 -0
  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 -23
  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 -73
  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 +17 -74
  37. package/dist/src/lib/hooks/useTabRouter.d.ts +5 -5
  38. package/dist/src/lib/i18n/index.d.ts +6 -4
  39. package/dist/src/lib/i18n/lang/en.d.ts +5 -0
  40. package/dist/src/lib/i18n/lang/zh-CN.d.ts +5 -0
  41. package/dist/src/lib/i18n/stackTabsLocale.d.ts +2 -0
  42. package/dist/src/lib/iframe-bridge.d.ts +8 -0
  43. package/dist/src/lib/iframe.vue.d.ts +2 -1
  44. package/dist/src/lib/index.d.ts +4 -5
  45. package/dist/src/lib/model/TabModel.d.ts +14 -33
  46. package/dist/src/lib/nuxt/module.d.ts +6 -0
  47. package/dist/src/lib/nuxt/runtime/plugin.client.d.ts +2 -0
  48. package/dist/src/lib/nuxt/runtime/plugin.d.ts +2 -0
  49. package/dist/src/lib/utils/iframeBridge.d.ts +11 -3
  50. package/dist/src/lib/utils/stackTabsA11y.d.ts +8 -0
  51. package/dist/src/lib/utils/stackTabsMessage.d.ts +7 -0
  52. package/dist/src/lib/utils/tabInfoEncoder.d.ts +1 -16
  53. package/dist/src/lib/utils/typeGuards.d.ts +2 -0
  54. package/dist/src/lib/utils/urlParser.d.ts +24 -3
  55. package/dist/src/lib/versionLogger.d.ts +1 -0
  56. package/dist/style.css +2 -1
  57. package/dist/utils/iframeBridge.d.ts +39 -0
  58. package/dist/vue-stack-tabs.css +2 -1
  59. package/dist/vue-stack-tabs.es.js +1 -2411
  60. package/package.json +68 -47
  61. package/src/lib/StackTabs.vue +216 -192
  62. package/src/lib/assets/style/stackTab.scss +84 -12
  63. package/src/lib/components/ContextMenu/ContextMenuItem.vue +23 -11
  64. package/src/lib/components/ContextMenu/index.vue +94 -10
  65. package/src/lib/components/PageLoading.vue +9 -7
  66. package/src/lib/components/StackKeepAlive/StackCacheRenderer.vue +70 -0
  67. package/src/lib/components/StackKeepAlive/StackKeepAlive.vue +57 -0
  68. package/src/lib/components/TabHeader/TabHeaderButton.vue +19 -8
  69. package/src/lib/components/TabHeader/TabHeaderItem.vue +45 -16
  70. package/src/lib/components/TabHeader/TabHeaderScroll.vue +47 -25
  71. package/src/lib/components/TabHeader/index.vue +109 -12
  72. package/src/lib/hooks/stackTabsContext.ts +135 -0
  73. package/src/lib/hooks/tabPanel/evict.ts +123 -41
  74. package/src/lib/hooks/tabPanel/navigationTransaction.ts +61 -0
  75. package/src/lib/hooks/tabPanel/refresh.ts +109 -0
  76. package/src/lib/hooks/tabPanel/scroll.ts +57 -36
  77. package/src/lib/hooks/tabPanel/session.ts +84 -22
  78. package/src/lib/hooks/tabPanel/state.ts +41 -44
  79. package/src/lib/hooks/useIframeManager.ts +253 -0
  80. package/src/lib/hooks/useTabActions.ts +62 -30
  81. package/src/lib/hooks/useTabEventBus.ts +29 -19
  82. package/src/lib/hooks/useTabLoading.ts +3 -2
  83. package/src/lib/hooks/useTabPanel.tsx +415 -213
  84. package/src/lib/hooks/useTabRouter.ts +172 -43
  85. package/src/lib/i18n/index.ts +53 -45
  86. package/src/lib/i18n/lang/en.ts +6 -1
  87. package/src/lib/i18n/lang/zh-CN.ts +6 -1
  88. package/src/lib/i18n/stackTabsLocale.ts +8 -0
  89. package/src/lib/iframe-bridge.ts +12 -0
  90. package/src/lib/index.ts +14 -19
  91. package/src/lib/model/TabModel.ts +34 -86
  92. package/src/lib/nuxt/module.d.ts +4 -0
  93. package/src/lib/nuxt/module.ts +8 -3
  94. package/src/lib/nuxt/runtime/plugin.client.ts +2 -3
  95. package/src/lib/nuxt/runtime/plugin.ts +11 -0
  96. package/src/lib/utils/iframeBridge.ts +29 -5
  97. package/src/lib/utils/scrollUtils.ts +9 -4
  98. package/src/lib/utils/stackTabsA11y.ts +12 -0
  99. package/src/lib/utils/stackTabsMessage.ts +99 -0
  100. package/src/lib/utils/tabInfoEncoder.ts +69 -42
  101. package/src/lib/utils/typeGuards.ts +3 -0
  102. package/src/lib/utils/urlParser.ts +144 -14
  103. package/src/lib/versionLogger.ts +7 -19
  104. package/dist/src/lib/hooks/tabPanel/evict.spec.d.ts +0 -1
  105. package/dist/src/lib/hooks/useTabPanel.spec.d.ts +0 -1
  106. package/dist/src/lib/model/TabModel.spec.d.ts +0 -1
  107. package/dist/src/lib/utils/iframeBridge.spec.d.ts +0 -1
  108. package/dist/src/lib/utils/tabInfoEncoder.spec.d.ts +0 -1
  109. package/dist/src/lib/utils/urlParser.spec.d.ts +0 -1
  110. package/dist/vue-stack-tabs.umd.js +0 -1
  111. package/src/lib/hooks/tabPanel/evict.spec.ts +0 -85
  112. package/src/lib/hooks/useTabPanel.spec.ts +0 -34
  113. package/src/lib/model/TabModel.spec.ts +0 -40
  114. package/src/lib/utils/iframeBridge.spec.ts +0 -37
  115. package/src/lib/utils/tabInfoEncoder.spec.ts +0 -42
  116. package/src/lib/utils/urlParser.spec.ts +0 -45
package/README.ru.md ADDED
@@ -0,0 +1,526 @@
1
+ # vue-stack-tabs
2
+
3
+ [中文](./README.md) | [English](./README.en.md) | **Русский**
4
+
5
+ > Библиотека управления вкладками для Vue 3 на основе Vue Router. Реализует поведение TabPanel, аналогичное iframe, с использованием областей видимости Vue — **каждая вкладка имеет изолированную область компонентов**.
6
+
7
+ ## Возможности
8
+
9
+ - **Вкладки на уровне маршрутов** — каждая вкладка владеет независимыми компонентами и кэшем
10
+ - **Стековая навигация внутри вкладки** — forward / backward внутри вкладки, аналогично стеку истории браузера
11
+ - **Обновление вкладок** — обновление одной или всех вкладок с полной пересборкой экземпляров компонентов
12
+ - **Вкладки iframe** — встраивание iframe-страниц с коммуникацией через postMessage
13
+ - **Сохранение сессии** — восстановление последних активных вкладок после обновления браузера
14
+ - **Запоминание позиции прокрутки** — автоматическое восстановление позиции прокрутки при переключении вкладок
15
+ - **Контекстное меню** — встроенные действия закрытия/обновления
16
+ - **Интернационализация** — встроенные китайский и английский языки, расширяемо
17
+ - **Модуль Nuxt 3/4** — работает из коробки
18
+
19
+ ---
20
+
21
+ ## Установка
22
+
23
+ ```bash
24
+ # npm
25
+ npm install vue-stack-tabs
26
+
27
+ # pnpm
28
+ pnpm add vue-stack-tabs
29
+ ```
30
+
31
+ ---
32
+
33
+ ## Быстрый старт (Vue 3)
34
+
35
+ ### 1. Регистрация плагина
36
+
37
+ ```ts
38
+ // main.ts
39
+ import { createApp } from 'vue'
40
+ import VueStackTabs from 'vue-stack-tabs'
41
+ import 'vue-stack-tabs/dist/vue-stack-tabs.css'
42
+ import App from './App.vue'
43
+ import router from './router'
44
+
45
+ const app = createApp(App)
46
+ app.use(router)
47
+ app.use(VueStackTabs)
48
+ app.mount('#app')
49
+ ```
50
+
51
+ ### 2. Настройка маршрутов
52
+
53
+ Вкладки зависят от Vue Router. Необходим родительский маршрут для размещения `<VueStackTabs>`:
54
+
55
+ ```ts
56
+ // router.ts
57
+ import { createRouter, createWebHistory } from 'vue-router'
58
+
59
+ const router = createRouter({
60
+ history: createWebHistory(),
61
+ routes: [
62
+ {
63
+ path: '/',
64
+ component: () => import('./Layout.vue'),
65
+ children: [
66
+ { path: '', component: () => import('./pages/Home.vue') },
67
+ { path: 'about', component: () => import('./pages/About.vue') },
68
+ { path: 'settings', component: () => import('./pages/Settings.vue') },
69
+ // маршрут-заглушка для iframe (обязательно)
70
+ { path: 'iframe', component: () => import('vue-stack-tabs').then((m) => m.IFrame) }
71
+ ]
72
+ }
73
+ ]
74
+ })
75
+
76
+ export default router
77
+ ```
78
+
79
+ ### 3. Использование компонента
80
+
81
+ ```vue
82
+ <!-- Layout.vue -->
83
+ <template>
84
+ <div style="width: 100%; height: 100vh">
85
+ <VueStackTabs iframe-path="/iframe" :default-tabs="defaultTabs" :max="20" :contextmenu="true" />
86
+ </div>
87
+ </template>
88
+
89
+ <script setup lang="ts">
90
+ import type { ITabData } from 'vue-stack-tabs'
91
+
92
+ const defaultTabs: ITabData[] = [
93
+ {
94
+ title: 'Главная',
95
+ path: '/',
96
+ closable: false,
97
+ refreshable: true
98
+ }
99
+ ]
100
+ </script>
101
+ ```
102
+
103
+ ### 4. Открытие вкладок
104
+
105
+ ```ts
106
+ import { useTabActions } from 'vue-stack-tabs'
107
+
108
+ const { openTab, closeTab, refreshTab } = useTabActions()
109
+
110
+ // Открыть новую вкладку
111
+ openTab({
112
+ id: 'about', // необязательно, генерируется автоматически
113
+ title: 'О приложении',
114
+ path: '/about',
115
+ query: { id: '1' } // необязательно
116
+ })
117
+
118
+ // Закрыть вкладку
119
+ closeTab('about')
120
+
121
+ // Обновить вкладку
122
+ refreshTab('about')
123
+ ```
124
+
125
+ ---
126
+
127
+ ## Быстрый старт (Nuxt 3/4)
128
+
129
+ ### 1. Регистрация модуля
130
+
131
+ ```ts
132
+ // nuxt.config.ts
133
+ export default defineNuxtConfig({
134
+ modules: ['vue-stack-tabs/nuxt'],
135
+ vueStackTabs: {
136
+ locale: 'en-US'
137
+ },
138
+ css: ['vue-stack-tabs/dist/vue-stack-tabs.css']
139
+ })
140
+ ```
141
+
142
+ ### 2. Использование в Layout
143
+
144
+ ```vue
145
+ <!-- layouts/default.vue -->
146
+ <template>
147
+ <div style="width: 100%; height: 100vh">
148
+ <VueStackTabs iframe-path="/iframe" :default-tabs="defaultTabs" />
149
+ </div>
150
+ </template>
151
+
152
+ <script setup lang="ts">
153
+ import type { ITabData } from 'vue-stack-tabs'
154
+
155
+ const defaultTabs: ITabData[] = [{ title: 'Главная', path: '/', closable: false, refreshable: true }]
156
+ </script>
157
+ ```
158
+
159
+ ### 3. Использование на страницах
160
+
161
+ ```vue
162
+ <!-- pages/index.vue -->
163
+ <template>
164
+ <div>
165
+ <h1>Главная</h1>
166
+ <button @click="openTab({ title: 'О приложении', path: '/about' })">Открыть страницу «О приложении»</button>
167
+ </div>
168
+ </template>
169
+
170
+ <script setup>
171
+ import { useTabActions } from 'vue-stack-tabs'
172
+ const { openTab } = useTabActions()
173
+ </script>
174
+ ```
175
+
176
+ ---
177
+
178
+ ## ESM-only и суб-вход iframe bridge
179
+
180
+ `vue-stack-tabs` начиная с текущей версии поддерживает только ESM import; `require('vue-stack-tabs')` больше не поддерживается.
181
+
182
+ Основное приложение продолжает использовать корневой вход:
183
+
184
+ ```ts
185
+ import VueStackTabs from 'vue-stack-tabs'
186
+ import 'vue-stack-tabs/dist/vue-stack-tabs.css'
187
+ ```
188
+
189
+ Для iframe-страниц используйте суб-вход без побочных эффектов стилей:
190
+
191
+ ```ts
192
+ import { postOpenTab, onRefreshRequest } from 'vue-stack-tabs/iframe-bridge'
193
+
194
+ postOpenTab(
195
+ { title: 'Детали заказа', path: '/orders/1' },
196
+ { targetOrigin: 'https://parent.example.com' }
197
+ )
198
+
199
+ const off = onRefreshRequest(() => window.location.reload(), {
200
+ allowedOrigins: ['https://parent.example.com']
201
+ })
202
+ ```
203
+
204
+ Если `targetOrigin` не указан, по умолчанию используется `window.location.origin` страницы iframe, подходит для родительских страниц с тем же источником; для кросс-доменных родительских страниц необходимо явно передать `targetOrigin`. В продакшене рекомендуется явно передавать и `targetOrigin`, и `allowedOrigins`.
205
+
206
+ ---
207
+
208
+ ## Политика безопасности iframe
209
+
210
+ `VueStackTabs` поддерживает настройку атрибутов безопасности iframe:
211
+
212
+ ```vue
213
+ <VueStackTabs
214
+ iframe-path="/__iframe"
215
+ iframe-sandbox="allow-scripts allow-forms allow-popups allow-downloads allow-same-origin"
216
+ iframe-referrer-policy="strict-origin-when-cross-origin"
217
+ iframe-allow="fullscreen"
218
+ :iframe-load-timeout="15000"
219
+ />
220
+ ```
221
+
222
+ Sandbox по умолчанию сохраняет типичные возможности бизнес-страниц и ориентирован на совместимость, не на строгую изоляцию. Для строгой изоляции удалите `allow-same-origin` и продолжайте уменьшать токены sandbox по потребностям бизнеса.
223
+
224
+ ---
225
+
226
+ ## Справочник API
227
+
228
+ ### useTabActions
229
+
230
+ Композабл для операций на уровне вкладок.
231
+
232
+ ```ts
233
+ import { useTabActions } from 'vue-stack-tabs'
234
+
235
+ const {
236
+ openTab, // (tab: ITabData, renew?: boolean) => Promise<string>
237
+ closeTab, // (id: string) => string
238
+ closeAllTabs, // () => void
239
+ refreshTab, // (id: string) => void
240
+ refreshAllTabs, // () => void
241
+ activeTab, // (id: string, isRoute?: boolean) => void
242
+ reset, // () => void
243
+ tabs // Ref<ITabItem[]>
244
+ } = useTabActions()
245
+ ```
246
+
247
+ | Метод | Описание |
248
+ | ---------------------- | --------------------------------------------------------------------------------- |
249
+ | `openTab(tab, renew?)` | Открыть новую вкладку. При `renew=true` очищает стек страниц и открывает заново |
250
+ | `closeTab(id)` | Закрыть указанную вкладку, возвращает ID новой активной вкладки |
251
+ | `closeAllTabs()` | Закрыть все закрываемые вкладки |
252
+ | `refreshTab(id)` | Обновить указанную вкладку (заменяет ID кэша, пересоздаёт экземпляр компонента) |
253
+ | `refreshAllTabs()` | Обновить все вкладки |
254
+ | `activeTab(id)` | Активировать указанную вкладку (переключить вкладку) |
255
+ | `reset()` | Закрыть все вкладки и сбросить состояние |
256
+
257
+ ---
258
+
259
+ ### useTabRouter
260
+
261
+ Стековая навигация внутри вкладки. **Можно использовать только внутри компонентов страниц во вкладке**.
262
+
263
+ ```ts
264
+ import { useTabRouter } from 'vue-stack-tabs'
265
+
266
+ const { forward, backward, addScrollTarget } = useTabRouter()
267
+ ```
268
+
269
+ | Метод | Описание |
270
+ | --------------------------- | ----------------------------------------------------------- |
271
+ | `forward(to)` | Перейти вперёд к новой странице внутри текущей вкладки |
272
+ | `backward(to, backQuery?)` | Вернуться назад внутри текущей вкладки |
273
+ | `addScrollTarget(selector)` | Зарегистрировать CSS-селектор DOM для запоминания прокрутки |
274
+
275
+ #### forward
276
+
277
+ ```ts
278
+ // Перейти на страницу /detail
279
+ forward({ path: '/detail', query: { id: '123' } })
280
+
281
+ // Добавить тот же маршрут в стек (один маршрут может быть добавлен многократно, каждый с независимым кэшем)
282
+ forward({ path: '/list', query: { page: '2' } })
283
+ ```
284
+
285
+ #### backward
286
+
287
+ ```ts
288
+ // Вернуться на 1 шаг назад (по умолчанию)
289
+ backward(1)
290
+
291
+ // Вернуться на N шагов
292
+ backward(3)
293
+
294
+ // Вернуться к определённому пути (автоматический поиск в стеке)
295
+ backward('/list')
296
+
297
+ // Вернуться с параметрами (целевая страница получает через props._back)
298
+ backward('/list', { result: 'success', data: { id: 1 } })
299
+ ```
300
+
301
+ **Получение параметров на целевой странице:**
302
+
303
+ ```vue
304
+ <script setup>
305
+ // Объявить props для получения параметров обратной навигации
306
+ defineProps<{ _back?: { result: string; data: any } }>()
307
+ </script>
308
+ ```
309
+
310
+ ---
311
+
312
+ ### useTabLoading
313
+
314
+ Управление состоянием загрузки страницы. **Можно использовать только внутри компонентов страниц во вкладке**.
315
+
316
+ ```ts
317
+ import { useTabLoading } from 'vue-stack-tabs'
318
+
319
+ const { openTabLoading, closeTabLoading } = useTabLoading()
320
+
321
+ // Показать оверлей загрузки
322
+ openTabLoading()
323
+
324
+ // Закрыть после завершения асинхронной операции
325
+ fetchData().finally(() => closeTabLoading())
326
+ ```
327
+
328
+ > Загрузка автоматически закрывается при размонтировании компонента — ручная очистка не требуется.
329
+
330
+ ---
331
+
332
+ ## Свойства (Props)
333
+
334
+ Свойства компонента `<VueStackTabs>`:
335
+
336
+ | Свойство | Тип | По умолчанию | Описание |
337
+ | ----------------------- | --------------------------- | ----------------------- | ------------------------------------------------------------- |
338
+ | `iframePath` | `string` | **Обязательно** | Путь маршрута-заглушки для iframe |
339
+ | `iframeSandbox` | `string` | `allow-scripts allow-forms allow-popups allow-downloads allow-same-origin` | Политика sandbox для iframe; по умолчанию совместимость, не строгая изоляция — удалите `allow-same-origin` для строгой изоляции; пустая строка отключает sandbox (не рекомендуется) |
340
+ | `iframeReferrerPolicy` | `ReferrerPolicy` | `strict-origin-when-cross-origin` | Атрибут referrerpolicy для iframe |
341
+ | `iframeAllow` | `string` | `''` | Атрибут allow для iframe, например `fullscreen` |
342
+ | `iframeLoadTimeout` | `number` | `15000` | Тайм-аут загрузки iframe в мс |
343
+ | `defaultTabs` | `ITabData[]` | `[]` | Начальный список вкладок |
344
+ | `max` | `number` | `20` | Максимальное количество вкладок |
345
+ | `contextmenu` | `boolean \| object` | `true` | Включить контекстное меню |
346
+ | `pageTransition` | `string` | `'stack-tab-swap'` | Имя анимации перехода при навигации вперёд |
347
+ | `pageTransitionBack` | `string` | `'stack-tab-swap-back'` | Имя анимации перехода при навигации назад |
348
+ | `tabTransition` | `string \| TransitionProps` | `'stack-tab-zoom'` | Эффект перехода при добавлении/удалении вкладок |
349
+ | `tabScrollMode` | `TabScrollMode` | `'both'` | Режим прокрутки панели вкладок: `'wheel'` / `'button'` / `'both'` |
350
+ | `width` | `string` | `'100%'` | Ширина контейнера |
351
+ | `height` | `string` | `'100%'` | Высота контейнера |
352
+ | `i18n` | `string` | `'zh-CN'` | Локаль интернационализации |
353
+ | `globalScroll` | `boolean` | `false` | Включить запоминание прокрутки на уровне страницы |
354
+ | `sessionPrefix` | `string` | `''` | Префикс ключа sessionStorage |
355
+ | `iframeAllowedOrigins` | `string[]` | Тот же источник | Разрешённые источники для iframe postMessage |
356
+
357
+ ---
358
+
359
+ ## События (Events)
360
+
361
+ | Событие | Параметры | Описание |
362
+ | -------------- | -------------- | --------------------------------------------- |
363
+ | `onActive` | `(id: string)` | Срабатывает при активации вкладки |
364
+ | `onPageLoaded` | — | Срабатывает при загрузке компонента страницы |
365
+
366
+ ---
367
+
368
+ ## Вкладки iframe
369
+
370
+ ### Открытие вкладки iframe
371
+
372
+ ```ts
373
+ openTab({
374
+ title: 'Внешняя страница',
375
+ path: 'https://example.com',
376
+ iframe: true
377
+ })
378
+ ```
379
+
380
+ ### Операции внутри iframe-страниц
381
+
382
+ Внутри вкладки iframe можно взаимодействовать с хост-контейнером через `postMessage` для открытия новых вкладок или обработки действий обновления.
383
+
384
+ #### 1. Использование утилит Bridge (рекомендуется)
385
+
386
+ Если iframe-страница может ссылаться на код библиотеки (или импортировать инструменты из `iframeBridge.ts`), рекомендуется этот подход:
387
+
388
+ ```ts
389
+ import { postOpenTab, onRefreshRequest } from 'vue-stack-tabs/iframe-bridge'
390
+
391
+ const parentOrigin = 'https://parent.example.com'
392
+
393
+ // Открыть вкладку
394
+ postOpenTab(
395
+ {
396
+ title: 'Новая страница',
397
+ path: '/detail',
398
+ query: { id: '1' }
399
+ },
400
+ { targetOrigin: parentOrigin }
401
+ )
402
+
403
+ // Слушать запросы на обновление
404
+ const off = onRefreshRequest(
405
+ () => {
406
+ // Пользовательская логика обновления
407
+ location.reload()
408
+ },
409
+ { allowedOrigins: [parentOrigin] }
410
+ )
411
+ ```
412
+
413
+ #### 2. Нативная интеграция
414
+
415
+ Если невозможно импортировать утилиты библиотеки или требуется нулевая зависимость, используйте нативный API:
416
+
417
+ ```ts
418
+ const parentOrigin = 'https://parent.example.com'
419
+
420
+ // Открыть вкладку
421
+ window.parent.postMessage(
422
+ {
423
+ type: 'vue-stack-tabs:openTab',
424
+ payload: {
425
+ title: 'Нативная новая страница',
426
+ path: '/detail'
427
+ }
428
+ },
429
+ parentOrigin
430
+ )
431
+
432
+ // Слушать запросы на обновление
433
+ window.addEventListener('message', (ev) => {
434
+ if (ev.origin !== parentOrigin) return
435
+ if (ev.source !== window.parent) return
436
+ if (ev.data?.type === 'vue-stack-tabs:refresh') {
437
+ // Выполнить обновление
438
+ location.reload()
439
+ }
440
+ })
441
+ ```
442
+
443
+ ### Режимы обновления iframe
444
+
445
+ При вызове `openTab` можно указать поведение обновления через `iframeRefreshMode`:
446
+
447
+ - `postMessage` (по умолчанию): хост отправляет сообщение `vue-stack-tabs:refresh` в iframe; внутренняя страница сама решает, как обновляться (более плавная анимация).
448
+ - `reload`: хост напрямую сбрасывает `src` или `key` iframe для принудительной перезагрузки браузером (подходит для кросс-доменных или немодифицируемых страниц).
449
+
450
+ ---
451
+
452
+ ## Интернационализация
453
+
454
+ Встроенные языки: `zh-CN` (китайский), `en-US` (английский).
455
+
456
+ ```ts
457
+ // Переключить язык
458
+ app.use(VueStackTabs, [{ locale: 'en-US' }])
459
+ ```
460
+
461
+ Пользовательский языковой пакет:
462
+
463
+ ```ts
464
+ app.use(VueStackTabs, [
465
+ {
466
+ locale: 'ru-RU',
467
+ messages: {
468
+ 'VueStackTab.close': 'Закрыть',
469
+ 'VueStackTab.refresh': 'Обновить'
470
+ // ...
471
+ }
472
+ }
473
+ ])
474
+ ```
475
+
476
+ ---
477
+
478
+ ## Определения типов
479
+
480
+ ```ts
481
+ /** Данные, передаваемые при открытии вкладки */
482
+ interface ITabData {
483
+ id?: string // ID вкладки (автоматически генерируемый UUID, если не указан)
484
+ title: string // Заголовок вкладки
485
+ path: string // Путь маршрута или URL iframe
486
+ query?: Record<string, string> // Параметры маршрута
487
+ closable?: boolean // Можно ли закрыть (по умолчанию true)
488
+ refreshable?: boolean // Можно ли обновить (по умолчанию true)
489
+ iframe?: boolean // Является ли вкладкой iframe (по умолчанию false)
490
+ iframeRefreshMode?: 'postMessage' | 'reload' // Способ обновления iframe
491
+ }
492
+
493
+ /** Режим прокрутки панели вкладок */
494
+ enum TabScrollMode {
495
+ WHEEL = 'wheel',
496
+ BUTTON = 'button',
497
+ BOTH = 'both'
498
+ }
499
+ ```
500
+
501
+ ---
502
+
503
+ ## Структура проекта
504
+
505
+ ```
506
+ src/lib/
507
+ ├── StackTabs.vue # Главный компонент
508
+ ├── index.ts # Точка входа и экспорты
509
+ ├── hooks/ # Основная логика
510
+ │ ├── useTabActions.ts # Публичный API
511
+ │ ├── useTabRouter.ts # Навигация внутри вкладки
512
+ │ ├── useTabLoading.ts # Состояние загрузки
513
+ │ └── useTabPanel.tsx # Ядро движка
514
+ ├── model/TabModel.ts # Определения типов
515
+ ├── components/ # UI-компоненты
516
+ ├── nuxt/ # Модуль Nuxt
517
+ └── assets/style/ # Стили
518
+ ```
519
+
520
+ Подробное описание архитектуры см. в [`ARCHITECTURE.md`](./ARCHITECTURE.md).
521
+
522
+ ---
523
+
524
+ ## Лицензия
525
+
526
+ [LGPL-2.1](./LICENSE)
@@ -0,0 +1,8 @@
1
+ /**
2
+ * iframe-bridge 子入口(vue-stack-tabs/iframe-bridge)。
3
+ *
4
+ * 供 iframe 内部页面引用,不包含 Vue 组件和样式等副作用,
5
+ * 仅导出 postMessage 通信工具。
6
+ */
7
+ export { MSG_OPEN_TAB, MSG_REFRESH, onRefreshRequest, postOpenTab } from './utils/iframeBridge';
8
+ export type { IframeBridgeOptions, IframeOpenTabPayload, RefreshRequestOptions } from './utils/iframeBridge';
@@ -0,0 +1,77 @@
1
+ <!doctype html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Iframe Bridge 测试页面</title>
7
+ <style>
8
+ body {
9
+ font-family: system-ui, sans-serif;
10
+ padding: 20px;
11
+ background: #f0f2f5;
12
+ }
13
+ .card {
14
+ background: white;
15
+ padding: 20px;
16
+ border-radius: 8px;
17
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
18
+ }
19
+ button {
20
+ padding: 10px 20px;
21
+ background: #67c23a;
22
+ color: white;
23
+ border: none;
24
+ border-radius: 4px;
25
+ cursor: pointer;
26
+ }
27
+ .status {
28
+ margin-top: 10px;
29
+ color: #606266;
30
+ }
31
+ </style>
32
+ </head>
33
+ <body>
34
+ <div class="card">
35
+ <h2>此页面演示使用封装好的 Bridge 对接方式</h2>
36
+ <p>页面加载时间: <span id="time"></span></p>
37
+ <p>
38
+ 使用 <code>iframeBridge.ts</code> 中提供的 <code>postOpenTab</code> 和
39
+ <code>onRefreshRequest</code>。
40
+ </p>
41
+
42
+ <div style="margin: 20px 0">
43
+ <button id="btnOpen">使用 Bridge 方法请求打开新 Tab</button>
44
+ </div>
45
+ <div class="status" id="status"></div>
46
+ </div>
47
+
48
+ <!-- 注意:在实际项目中,如果是原生 HTML 需要引入编译后的 Bridge 脚本,或者直接使用 type="module" -->
49
+ <script type="module">
50
+ // 这里的导入路径仅为本地调试示意,实际推荐将 Bridge 打包为公共库或 CDN 引用
51
+ import { postOpenTab, onRefreshRequest } from './src/lib/utils/iframeBridge.ts'
52
+
53
+ const timeEl = document.getElementById('time')
54
+ timeEl.innerText = new Date().toLocaleTimeString()
55
+
56
+ // 1. 使用 Bridge 封装的方法打开 Tab
57
+ document.getElementById('btnOpen').onclick = () => {
58
+ postOpenTab({
59
+ id: 'bridge_triggered_tab',
60
+ title: 'Bridge 弹出的新页签',
61
+ path: '/demo/page1',
62
+ closable: true
63
+ })
64
+ }
65
+
66
+ // 2. 使用 Bridge 封装的方法监听刷新请求
67
+ onRefreshRequest(() => {
68
+ console.log('[Bridge Child] Received refresh request from parent')
69
+ const statusEl = document.getElementById('status')
70
+ statusEl.innerText = '正在执行刷新 logic...'
71
+ setTimeout(() => {
72
+ location.reload()
73
+ }, 500)
74
+ })
75
+ </script>
76
+ </body>
77
+ </html>
@@ -0,0 +1,20 @@
1
+ //#region src/lib/utils/iframeBridge.ts
2
+ var e = "vue-stack-tabs:refresh", t = "vue-stack-tabs:openTab";
3
+ function n() {
4
+ return window.location.origin;
5
+ }
6
+ function r(e, r = {}) {
7
+ typeof window > "u" || !window.parent || window.parent.postMessage({
8
+ type: t,
9
+ payload: e
10
+ }, r.targetOrigin ?? n());
11
+ }
12
+ function i(e = () => window.location.reload(), t = {}) {
13
+ if (typeof window > "u") return () => void 0;
14
+ let n = t.allowedOrigins ?? [], r = (t) => {
15
+ t.source === window.parent && (n.length > 0 && !n.includes(t.origin) || t.data?.type === "vue-stack-tabs:refresh" && e());
16
+ };
17
+ return window.addEventListener("message", r), () => window.removeEventListener("message", r);
18
+ }
19
+ //#endregion
20
+ export { t as MSG_OPEN_TAB, e as MSG_REFRESH, i as onRefreshRequest, r as postOpenTab };