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.
- package/README.en.md +526 -0
- package/README.md +141 -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 +71 -50
- package/src/lib/StackTabs.vue +223 -196
- package/src/lib/assets/style/stackTab.scss +110 -13
- 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 +74 -17
- package/src/lib/components/TabHeader/TabHeaderScroll.vue +47 -25
- package/src/lib/components/TabHeader/index.vue +181 -13
- 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 +64 -30
- package/src/lib/hooks/useTabEventBus.ts +29 -19
- package/src/lib/hooks/useTabLoading.ts +3 -2
- package/src/lib/hooks/useTabPanel.tsx +290 -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
|
|
@@ -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)` |
|
|
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` | **必填**
|
|
289
|
-
| `
|
|
290
|
-
| `
|
|
291
|
-
| `
|
|
292
|
-
| `
|
|
293
|
-
| `
|
|
294
|
-
| `
|
|
295
|
-
| `
|
|
296
|
-
| `
|
|
297
|
-
| `
|
|
298
|
-
| `
|
|
299
|
-
| `
|
|
300
|
-
| `
|
|
301
|
-
| `
|
|
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
|
-
|
|
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
|
-
|
|
334
|
-
postOpenTab({
|
|
335
|
-
title: '新页面',
|
|
336
|
-
path: '/detail',
|
|
337
|
-
query: { id: '1' }
|
|
338
|
-
})
|
|
339
|
-
```
|
|
401
|
+
const parentOrigin = 'https://parent.example.com'
|
|
340
402
|
|
|
341
|
-
|
|
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
|
-
|
|
423
|
+
#### 2. 原生对接方式
|
|
344
424
|
|
|
345
|
-
|
|
346
|
-
- `reload`:重建 iframe DOM(适用于跨域页面)
|
|
425
|
+
如果无法引入库工具,或希望保持零依赖,可以使用原生 API。生产环境必须使用明确的父页面 origin,并校验刷新消息来源。`postOpenTab` 的默认 `targetOrigin` 是 iframe 页面的 `window.location.origin`,跨域父页面必须显式传入父页面 origin:
|
|
347
426
|
|
|
348
427
|
```ts
|
|
349
|
-
|
|
350
|
-
import { onRefreshRequest } from 'vue-stack-tabs'
|
|
428
|
+
const parentOrigin = 'https://parent.example.com'
|
|
351
429
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
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(不传则自动生成
|
|
493
|
+
id?: string // 标签 ID(不传则自动生成 UUID)
|
|
392
494
|
title: string // 标签标题
|
|
393
495
|
path: string // 路由路径或 iframe URL
|
|
394
496
|
query?: Record<string, string> // 路由参数
|