vue-stack-tabs 1.1.0 → 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.
- package/README.en.md +526 -0
- package/README.md +139 -39
- package/README.ru.md +526 -0
- package/dist/iframe-bridge.d.ts +8 -0
- package/dist/iframe-bridge.html +77 -0
- package/dist/iframe-bridge.mjs +20 -0
- package/dist/iframe-child.html +19 -6
- package/dist/nuxt/favicon.svg +10 -0
- package/dist/nuxt/iframe-bridge.html +77 -0
- package/dist/nuxt/iframe-child.html +88 -0
- package/dist/nuxt/module.d.ts +6 -0
- package/dist/nuxt/module.mjs +28 -0
- package/dist/nuxt/plugin.mjs +9 -0
- package/dist/nuxt/runtime/plugin.d.ts +2 -0
- package/dist/nuxt/runtime/plugin.mjs +9 -0
- package/dist/src/lib/StackTabs.vue.d.ts +30 -16
- package/dist/src/lib/components/ContextMenu/ContextMenuItem.vue.d.ts +7 -1
- package/dist/src/lib/components/ContextMenu/index.vue.d.ts +5 -4
- package/dist/src/lib/components/PageLoading.vue.d.ts +2 -1
- package/dist/src/lib/components/StackKeepAlive/StackCacheRenderer.vue.d.ts +25 -0
- package/dist/src/lib/components/StackKeepAlive/StackKeepAlive.vue.d.ts +12 -0
- package/dist/src/lib/components/TabHeader/TabHeaderButton.vue.d.ts +11 -12
- package/dist/src/lib/components/TabHeader/TabHeaderItem.vue.d.ts +5 -4
- package/dist/src/lib/components/TabHeader/TabHeaderScroll.vue.d.ts +8 -22
- package/dist/src/lib/components/TabHeader/index.vue.d.ts +11 -164
- package/dist/src/lib/hooks/stackTabsContext.d.ts +49 -0
- package/dist/src/lib/hooks/tabPanel/evict.d.ts +21 -61
- package/dist/src/lib/hooks/tabPanel/navigationTransaction.d.ts +35 -0
- package/dist/src/lib/hooks/tabPanel/refresh.d.ts +17 -0
- package/dist/src/lib/hooks/tabPanel/scroll.d.ts +8 -8
- package/dist/src/lib/hooks/tabPanel/session.d.ts +9 -8
- package/dist/src/lib/hooks/tabPanel/state.d.ts +20 -72
- package/dist/src/lib/hooks/useIframeManager.d.ts +35 -0
- package/dist/src/lib/hooks/useTabActions.d.ts +5 -46
- package/dist/src/lib/hooks/useTabEventBus.d.ts +22 -9
- package/dist/src/lib/hooks/useTabPanel.d.ts +14 -73
- package/dist/src/lib/i18n/index.d.ts +6 -4
- package/dist/src/lib/i18n/lang/en.d.ts +5 -0
- package/dist/src/lib/i18n/lang/zh-CN.d.ts +5 -0
- package/dist/src/lib/i18n/stackTabsLocale.d.ts +2 -0
- package/dist/src/lib/iframe-bridge.d.ts +8 -0
- package/dist/src/lib/iframe.vue.d.ts +2 -1
- package/dist/src/lib/index.d.ts +4 -5
- package/dist/src/lib/model/TabModel.d.ts +11 -40
- package/dist/src/lib/nuxt/module.d.ts +6 -0
- package/dist/src/lib/nuxt/runtime/plugin.client.d.ts +2 -0
- package/dist/src/lib/nuxt/runtime/plugin.d.ts +2 -0
- package/dist/src/lib/utils/iframeBridge.d.ts +11 -3
- package/dist/src/lib/utils/stackTabsA11y.d.ts +8 -0
- package/dist/src/lib/utils/stackTabsMessage.d.ts +7 -0
- package/dist/src/lib/utils/tabInfoEncoder.d.ts +0 -15
- package/dist/src/lib/utils/typeGuards.d.ts +2 -0
- package/dist/src/lib/utils/urlParser.d.ts +24 -3
- package/dist/src/lib/versionLogger.d.ts +1 -0
- package/dist/style.css +2 -1
- package/dist/utils/iframeBridge.d.ts +39 -0
- package/dist/vue-stack-tabs.css +2 -1
- package/dist/vue-stack-tabs.es.js +1 -2220
- package/package.json +68 -47
- package/src/lib/StackTabs.vue +223 -196
- package/src/lib/assets/style/stackTab.scss +84 -12
- package/src/lib/components/ContextMenu/ContextMenuItem.vue +23 -11
- package/src/lib/components/ContextMenu/index.vue +94 -10
- package/src/lib/components/PageLoading.vue +9 -7
- package/src/lib/components/StackKeepAlive/StackCacheRenderer.vue +70 -0
- package/src/lib/components/StackKeepAlive/StackKeepAlive.vue +57 -0
- package/src/lib/components/TabHeader/TabHeaderButton.vue +19 -8
- package/src/lib/components/TabHeader/TabHeaderItem.vue +45 -16
- package/src/lib/components/TabHeader/TabHeaderScroll.vue +47 -25
- package/src/lib/components/TabHeader/index.vue +109 -12
- package/src/lib/hooks/stackTabsContext.ts +135 -0
- package/src/lib/hooks/tabPanel/evict.ts +122 -87
- package/src/lib/hooks/tabPanel/navigationTransaction.ts +61 -0
- package/src/lib/hooks/tabPanel/refresh.ts +109 -0
- package/src/lib/hooks/tabPanel/scroll.ts +57 -40
- package/src/lib/hooks/tabPanel/session.ts +84 -22
- package/src/lib/hooks/tabPanel/state.ts +41 -44
- package/src/lib/hooks/useIframeManager.ts +253 -0
- package/src/lib/hooks/useTabActions.ts +62 -30
- package/src/lib/hooks/useTabEventBus.ts +29 -19
- package/src/lib/hooks/useTabLoading.ts +3 -2
- package/src/lib/hooks/useTabPanel.tsx +266 -313
- package/src/lib/hooks/useTabRouter.ts +111 -97
- package/src/lib/i18n/index.ts +53 -45
- package/src/lib/i18n/lang/en.ts +6 -1
- package/src/lib/i18n/lang/zh-CN.ts +6 -1
- package/src/lib/i18n/stackTabsLocale.ts +8 -0
- package/src/lib/iframe-bridge.ts +12 -0
- package/src/lib/index.ts +14 -19
- package/src/lib/model/TabModel.ts +27 -82
- package/src/lib/nuxt/module.d.ts +4 -0
- package/src/lib/nuxt/module.ts +8 -3
- package/src/lib/nuxt/runtime/plugin.client.ts +1 -0
- package/src/lib/nuxt/runtime/plugin.ts +11 -0
- package/src/lib/utils/iframeBridge.ts +29 -5
- package/src/lib/utils/scrollUtils.ts +9 -4
- package/src/lib/utils/stackTabsA11y.ts +12 -0
- package/src/lib/utils/stackTabsMessage.ts +99 -0
- package/src/lib/utils/tabInfoEncoder.ts +70 -21
- package/src/lib/utils/typeGuards.ts +3 -0
- package/src/lib/utils/urlParser.ts +144 -14
- package/src/lib/versionLogger.ts +7 -19
- package/dist/src/lib/hooks/tabPanel/evict.spec.d.ts +0 -1
- package/dist/src/lib/hooks/useTabPanel.spec.d.ts +0 -1
- package/dist/src/lib/model/TabModel.spec.d.ts +0 -1
- package/dist/src/lib/utils/iframeBridge.spec.d.ts +0 -1
- package/dist/src/lib/utils/tabInfoEncoder.spec.d.ts +0 -1
- package/dist/src/lib/utils/urlParser.spec.d.ts +0 -1
- package/dist/vue-stack-tabs.umd.js +0 -1
- package/src/lib/hooks/tabPanel/evict.spec.ts +0 -85
- package/src/lib/hooks/useTabPanel.spec.ts +0 -34
- package/src/lib/model/TabModel.spec.ts +0 -40
- package/src/lib/utils/iframeBridge.spec.ts +0 -37
- package/src/lib/utils/tabInfoEncoder.spec.ts +0 -32
- 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/
|
|
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/
|
|
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
|
|
@@ -199,7 +257,7 @@ const {
|
|
|
199
257
|
| `openTab(tab, renew?)` | 打开新标签。`renew=true` 时若已存在则清空页面栈后重新打开 |
|
|
200
258
|
| `closeTab(id)` | 关闭指定标签,返回新激活的标签 ID |
|
|
201
259
|
| `closeAllTabs()` | 关闭所有可关闭标签 |
|
|
202
|
-
| `refreshTab(id)` |
|
|
260
|
+
| `refreshTab(id)` | 刷新指定标签(替换缓存 ID,重建组件实例) |
|
|
203
261
|
| `refreshAllTabs()` | 刷新所有标签 |
|
|
204
262
|
| `activeTab(id)` | 激活指定标签(切换 Tab) |
|
|
205
263
|
| `reset()` | 关闭所有标签并重置状态 |
|
|
@@ -283,22 +341,26 @@ fetchData().finally(() => closeTabLoading())
|
|
|
283
341
|
|
|
284
342
|
`<VueStackTabs>` 组件属性:
|
|
285
343
|
|
|
286
|
-
| Prop | 类型 | 默认值
|
|
287
|
-
| ---------------------- | --------------------------- |
|
|
288
|
-
| `iframePath` | `string` | **必填**
|
|
289
|
-
| `
|
|
290
|
-
| `
|
|
291
|
-
| `
|
|
292
|
-
| `
|
|
293
|
-
| `
|
|
294
|
-
| `
|
|
295
|
-
| `
|
|
296
|
-
| `
|
|
297
|
-
| `
|
|
298
|
-
| `
|
|
299
|
-
| `
|
|
300
|
-
| `
|
|
301
|
-
| `
|
|
344
|
+
| Prop | 类型 | 默认值 | 说明 |
|
|
345
|
+
| ---------------------- | --------------------------- | -------------------------------------------------------------------------- | ------------------------------------------------------- |
|
|
346
|
+
| `iframePath` | `string` | **必填** | iframe 占位路由的路径 |
|
|
347
|
+
| `iframeSandbox` | `string` | `allow-scripts allow-forms allow-popups allow-downloads allow-same-origin` | iframe sandbox 策略;默认兼容优先不是强隔离,强隔离请移除 `allow-same-origin`;传空字符串可关闭 sandbox(不推荐) |
|
|
348
|
+
| `iframeReferrerPolicy` | `ReferrerPolicy` | `strict-origin-when-cross-origin` | iframe referrerpolicy 属性 |
|
|
349
|
+
| `iframeAllow` | `string` | `''` | iframe allow 属性,例如 `fullscreen` |
|
|
350
|
+
| `iframeLoadTimeout` | `number` | `15000` | iframe 加载超时时间,单位 ms |
|
|
351
|
+
| `defaultTabs` | `ITabData[]` | `[]` | 初始标签列表 |
|
|
352
|
+
| `max` | `number` | `20` | 最大标签数量 |
|
|
353
|
+
| `contextmenu` | `boolean \| object` | `true` | 是否启用右键菜单 |
|
|
354
|
+
| `pageTransition` | `string` | `'stack-tab-swap'` | 前进时的页面转场动画名 |
|
|
355
|
+
| `pageTransitionBack` | `string` | `'stack-tab-swap-back'` | 后退时的页面转场动画名 |
|
|
356
|
+
| `tabTransition` | `string \| TransitionProps` | `'stack-tab-zoom'` | 标签增删时的过渡效果 |
|
|
357
|
+
| `tabScrollMode` | `TabScrollMode` | `'both'` | 标签栏滚动方式:`'wheel'` / `'button'` / `'both'` |
|
|
358
|
+
| `width` | `string` | `'100%'` | 容器宽度 |
|
|
359
|
+
| `height` | `string` | `'100%'` | 容器高度 |
|
|
360
|
+
| `i18n` | `string` | `'zh-CN'` | 国际化语言 |
|
|
361
|
+
| `globalScroll` | `boolean` | `false` | 是否使用页面级滚动记忆 |
|
|
362
|
+
| `sessionPrefix` | `string` | `''` | sessionStorage 键前缀 |
|
|
363
|
+
| `iframeAllowedOrigins` | `string[]` | 同源 | 允许 iframe postMessage 的来源列表 |
|
|
302
364
|
|
|
303
365
|
---
|
|
304
366
|
|
|
@@ -323,38 +385,76 @@ openTab({
|
|
|
323
385
|
})
|
|
324
386
|
```
|
|
325
387
|
|
|
326
|
-
### iframe
|
|
388
|
+
### iframe 页面内操作
|
|
389
|
+
|
|
390
|
+
在 iframe 标签页内,可以通过 `postMessage` 与宿主容器进行通信,实现打开新标签或响应刷新操作。
|
|
327
391
|
|
|
328
|
-
|
|
392
|
+
#### 1. 使用 Bridge 工具(推荐)
|
|
393
|
+
|
|
394
|
+
如果 iframe 内页能引用库代码,推荐使用无样式副作用的 `vue-stack-tabs/iframe-bridge` 子入口:
|
|
329
395
|
|
|
330
396
|
```ts
|
|
331
|
-
import { postOpenTab } from 'vue-stack-tabs'
|
|
397
|
+
import { postOpenTab, onRefreshRequest } from 'vue-stack-tabs/iframe-bridge'
|
|
332
398
|
|
|
333
|
-
|
|
334
|
-
postOpenTab({
|
|
335
|
-
title: '新页面',
|
|
336
|
-
path: '/detail',
|
|
337
|
-
query: { id: '1' }
|
|
338
|
-
})
|
|
339
|
-
```
|
|
399
|
+
const parentOrigin = 'https://parent.example.com'
|
|
340
400
|
|
|
341
|
-
|
|
401
|
+
// 打开标签
|
|
402
|
+
postOpenTab(
|
|
403
|
+
{
|
|
404
|
+
title: '新页面',
|
|
405
|
+
path: '/detail',
|
|
406
|
+
query: { id: '1' }
|
|
407
|
+
},
|
|
408
|
+
{ targetOrigin: parentOrigin }
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
// 监听刷新请求
|
|
412
|
+
const off = onRefreshRequest(
|
|
413
|
+
() => {
|
|
414
|
+
// 自定义刷新逻辑
|
|
415
|
+
location.reload()
|
|
416
|
+
},
|
|
417
|
+
{ allowedOrigins: [parentOrigin] }
|
|
418
|
+
)
|
|
419
|
+
```
|
|
342
420
|
|
|
343
|
-
|
|
421
|
+
#### 2. 原生对接方式
|
|
344
422
|
|
|
345
|
-
|
|
346
|
-
- `reload`:重建 iframe DOM(适用于跨域页面)
|
|
423
|
+
如果无法引入库工具,或希望保持零依赖,可以使用原生 API。生产环境必须使用明确的父页面 origin,并校验刷新消息来源。`postOpenTab` 的默认 `targetOrigin` 是 iframe 页面的 `window.location.origin`,跨域父页面必须显式传入父页面 origin:
|
|
347
424
|
|
|
348
425
|
```ts
|
|
349
|
-
|
|
350
|
-
import { onRefreshRequest } from 'vue-stack-tabs'
|
|
426
|
+
const parentOrigin = 'https://parent.example.com'
|
|
351
427
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
428
|
+
// 打开标签
|
|
429
|
+
window.parent.postMessage(
|
|
430
|
+
{
|
|
431
|
+
type: 'vue-stack-tabs:openTab',
|
|
432
|
+
payload: {
|
|
433
|
+
title: '原生新页面',
|
|
434
|
+
path: '/detail'
|
|
435
|
+
}
|
|
436
|
+
},
|
|
437
|
+
parentOrigin
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
// 监听刷新请求
|
|
441
|
+
window.addEventListener('message', (ev) => {
|
|
442
|
+
if (ev.origin !== parentOrigin) return
|
|
443
|
+
if (ev.source !== window.parent) return
|
|
444
|
+
if (ev.data?.type === 'vue-stack-tabs:refresh') {
|
|
445
|
+
// 执行刷新
|
|
446
|
+
location.reload()
|
|
447
|
+
}
|
|
355
448
|
})
|
|
356
449
|
```
|
|
357
450
|
|
|
451
|
+
### iframe 刷新模式
|
|
452
|
+
|
|
453
|
+
在 `openTab` 时可通过 `iframeRefreshMode` 指定刷新行为:
|
|
454
|
+
|
|
455
|
+
- `postMessage`(默认):宿主向 iframe 发送 `vue-stack-tabs:refresh` 消息,由内页自行决定如何刷新(动画更丝滑)。
|
|
456
|
+
- `reload`:宿主直接重置 iframe 的 `src` 或 `key` 触发浏览器强制重载(适用于跨域或无法修改代码的页面)。
|
|
457
|
+
|
|
358
458
|
---
|
|
359
459
|
|
|
360
460
|
## 🌍 国际化
|
|
@@ -388,7 +488,7 @@ app.use(VueStackTabs, [
|
|
|
388
488
|
```ts
|
|
389
489
|
/** 打开标签时传入的数据 */
|
|
390
490
|
interface ITabData {
|
|
391
|
-
id?: string // 标签 ID(不传则自动生成
|
|
491
|
+
id?: string // 标签 ID(不传则自动生成 UUID)
|
|
392
492
|
title: string // 标签标题
|
|
393
493
|
path: string // 路由路径或 iframe URL
|
|
394
494
|
query?: Record<string, string> // 路由参数
|