vue-stack-tabs

Vue 3 multi-tab manager built on Vue Router with iframe-like tab isolation: per-tab component scope, stack-based forward/backward navigation, keep-alive page caching, scroll position memory, i18n and a Nuxt 3/4 module.
2,389 1
Install
npm install vue-stack-tabs
Latest Version:2.6.2
Vue:3
License:Apache-2.0
Last Updated:Oct 25, 2026
Links: GitHub  ·  npm
Maintainer: jktantan

vue-stack-tabs

npm version npm downloads license minzipped size docs

中文 | English | Русский

📖 Документация · 🎮 Демо

Библиотека управления вкладками для Vue 3 на основе Vue Router. Она обеспечивает изоляцию вкладок, подобную iframe, через независимые области видимости Vue: каждая вкладка сохраняет собственный экземпляр страницы, кэш и стек навигации.

Возможности

  • Вкладки на уровне маршрутов — каждая вкладка владеет независимыми компонентами и кэшем
  • Стековая навигация внутри вкладки — forward / backward внутри вкладки, аналогично стеку истории браузера
  • Обновление вкладок — обновление одной или всех вкладок с полной пересборкой экземпляров компонентов
  • Вкладки iframe — встраивание iframe-страниц с коммуникацией через postMessage
  • Сохранение сессии — восстановление последних активных вкладок после обновления браузера
  • Запоминание позиции прокрутки — автоматическое восстановление позиции прокрутки при переключении вкладок
  • Контекстное меню — встроенные действия закрытия/обновления
  • Слоты — кнопки слева/справа на панели вкладок, свои плейсхолдеры загрузки/ошибки iframe
  • Интернационализация — встроенные китайский и английский языки, расширяемо
  • Модуль Nuxt 3/4 — работает из коробки

Установка

# npm
npm install vue-stack-tabs

# pnpm
pnpm add vue-stack-tabs

Быстрый старт (Vue 3)

1. Регистрация плагина

// main.ts
import { createApp } from 'vue'
import VueStackTabs from 'vue-stack-tabs'
import 'vue-stack-tabs/dist/style.css'
import App from './App.vue'
import router from './router'

const app = createApp(App)
app.use(router)
app.use(VueStackTabs)
app.mount('#app')

vue-stack-tabs поддерживает только один экземпляр <VueStackTabs> на приложение Vue. Экспорт по умолчанию — плагин Vue для app.use(VueStackTabs); если нужен локальный импорт компонента, используйте именованный экспорт ниже.

2. Настройка маршрутов

Вкладки зависят от Vue Router. Необходим родительский маршрут для размещения <VueStackTabs>:

// router.ts
import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
  history: createWebHistory(),
  routes: [
    {
      path: '/',
      component: () => import('./Layout.vue'),
      children: [
        { path: '', component: () => import('./pages/Home.vue') },
        { path: 'about', component: () => import('./pages/About.vue') },
        { path: 'settings', component: () => import('./pages/Settings.vue') },
        // маршрут-заглушка для iframe (обязательно)
        { path: 'iframe', component: () => import('vue-stack-tabs').then((m) => m.IFrame) }
      ]
    }
  ]
})

export default router

3. Использование компонента

<!-- Layout.vue -->
<template>
  <div style="width: 100%; height: 100vh">
    <VueStackTabs iframe-path="/iframe" :default-tabs="defaultTabs" :max="20" :contextmenu="true" />
  </div>
</template>

<script setup lang="ts">
import type { ITabData } from 'vue-stack-tabs'

const defaultTabs: ITabData[] = [
  {
    title: 'Главная',
    path: '/',
    closable: false,
    refreshable: true
  }
]
</script>

Если нужен локальный импорт компонента, используйте именованный экспорт VueStackTabs; именованный экспорт StackTab сохранён как алиас совместимости:

import { VueStackTabs as VueStackTabsComponent } from 'vue-stack-tabs'

4. Открытие вкладок

import { useTabActions } from 'vue-stack-tabs'

const { openTab, closeTab, refreshTab } = useTabActions()

// Открыть новую вкладку
openTab({
  id: 'about', // необязательно, генерируется автоматически
  title: 'О приложении',
  path: '/about',
  query: { id: '1' } // необязательно
})

// Закрыть вкладку
closeTab('about')

// Обновить вкладку
refreshTab('about')

Быстрый старт (Nuxt 3/4)

1. Регистрация модуля

// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['vue-stack-tabs/nuxt'],
  vueStackTabs: {
    locale: 'zh-CN' // встроенные варианты: 'zh-CN' | 'en'
  },
  css: ['vue-stack-tabs/dist/style.css']
})

2. Использование в Layout

<!-- layouts/default.vue -->
<template>
  <div style="width: 100%; height: 100vh">
    <VueStackTabs iframe-path="/iframe" :default-tabs="defaultTabs" />
  </div>
</template>

<script setup lang="ts">
import type { ITabData } from 'vue-stack-tabs'

const defaultTabs: ITabData[] = [
  { title: 'Главная', path: '/', closable: false, refreshable: true }
]
</script>

3. Использование на страницах

<!-- pages/index.vue -->
<template>
  <div>
    <h1>Главная</h1>
    <button @click="openTab({ title: 'О приложении', path: '/about' })">
      Открыть страницу «О приложении»
    </button>
  </div>
</template>

<script setup>
import { useTabActions } from 'vue-stack-tabs'
const { openTab } = useTabActions()
</script>

ESM-only и суб-вход iframe bridge

vue-stack-tabs начиная с текущей версии поддерживает только ESM import; require('vue-stack-tabs') больше не поддерживается.

Основное приложение продолжает использовать корневой вход:

import VueStackTabs from 'vue-stack-tabs'
import 'vue-stack-tabs/dist/style.css'

Для iframe-страниц используйте суб-вход без побочных эффектов стилей:

import { postOpenTab, onRefreshRequest } from 'vue-stack-tabs/iframe-bridge'

postOpenTab(
  { title: 'Детали заказа', path: '/orders/1' },
  { targetOrigin: 'https://parent.example.com' }
)

const off = onRefreshRequest(() => window.location.reload(), {
  allowedOrigins: ['https://parent.example.com']
})

Если targetOrigin не указан, по умолчанию используется window.location.origin страницы iframe, подходит для родительских страниц с тем же источником; для кросс-доменных родительских страниц необходимо явно передать targetOrigin. В продакшене рекомендуется явно передавать и targetOrigin, и allowedOrigins.


Политика безопасности iframe

VueStackTabs поддерживает настройку атрибутов безопасности iframe:

<VueStackTabs
  iframe-path="/__iframe"
  iframe-sandbox="allow-scripts allow-forms allow-popups allow-downloads allow-same-origin"
  iframe-referrer-policy="strict-origin-when-cross-origin"
  iframe-allow="fullscreen"
  :iframe-load-timeout="15000"
/>

Sandbox по умолчанию сохраняет типичные возможности бизнес-страниц и ориентирован на совместимость, не на строгую изоляцию. Для строгой изоляции удалите allow-same-origin и продолжайте уменьшать токены sandbox по потребностям бизнеса. Если ваши страницы того же источника не могут работать под sandbox, можно передать пустую строку для отключения sandbox, но это снижает уровень изоляции.


Справочник API

useTabActions

Композабл для операций на уровне вкладок.

import { useTabActions } from 'vue-stack-tabs'

const {
  openTab, // (tab: ITabData, renew?: boolean) => Promise<string | undefined>
  closeTab, // (id: string) => string
  closeAllTabs, // () => void
  refreshTab, // (id: string) => void
  refreshAllTabs, // () => void
  activeTab, // (id: string, isRoute?: boolean) => void
  moveTab, // (id: string, targetIndex: number) => boolean
  reset, // () => void
  tabs, // Ref<ITabItem[]>
  setIFramePath, // (path: string) => void
  getWrapper, // ({ tabId?, pageId? }) => HTMLElement | undefined
  openInNewWindow // (id: string) => void
} = useTabActions()
Метод Описание
openTab(tab, renew?) Открыть новую вкладку. При renew=true очищает стек страниц и открывает заново
closeTab(id) Закрыть указанную вкладку, возвращает ID новой активной вкладки
closeAllTabs() Закрыть все закрываемые вкладки
refreshTab(id) Обновить указанную вкладку (заменяет ID кэша, пересоздаёт экземпляр компонента)
refreshAllTabs() Обновить все вкладки
activeTab(id) Активировать указанную вкладку (переключить вкладку)
moveTab(id, targetIndex) Переместить вкладку на указанный индекс; панель вкладок также поддерживает перетаскивание
reset() Закрыть все вкладки и сбросить состояние
setIFramePath(path) Установить путь маршрута-заглушки iframe (вызывается внутри компонента)
getWrapper({ tabId?, pageId? }) Получить DOM-контейнер содержимого; без параметров вернёт первый контейнер, при нескольких вкладках передавайте tabId или pageId
openInNewWindow(id) Открыть вкладку iframe в новом окне (fallback, если встраивание невозможно)

Закрытие слева / справа / остальных — операции только контекстного меню, выполняются внутри TabHeader/ContextMenu через useTabPanel.


useTabRouter

Стековая навигация внутри вкладки. Можно использовать только внутри компонентов страниц во вкладке.

import { useTabRouter } from 'vue-stack-tabs'

const { forward, backward, addScrollTarget } = useTabRouter()
Метод Описание
forward(to) Перейти вперёд к новой странице внутри текущей вкладки
backward(to, backQuery?) Вернуться назад внутри текущей вкладки
addScrollTarget(selector) Зарегистрировать CSS-селектор DOM для запоминания прокрутки
forward
// Перейти на страницу /detail
forward({ path: '/detail', query: { id: '123' } })

// Добавить тот же маршрут в стек (один маршрут может быть добавлен многократно, каждый с независимым кэшем)
forward({ path: '/list', query: { page: '2' } })
backward
// Вернуться на 1 шаг назад (по умолчанию)
backward(1)

// Вернуться на N шагов
backward(3)

// Вернуться к определённому пути (автоматический поиск в стеке)
backward('/list')

// Вернуться с параметрами (целевая страница получает через props._back)
backward('/list', { result: 'success', data: { id: 1 } })

Получение параметров на целевой странице:

<script setup>
// Объявить props для получения параметров обратной навигации
defineProps<{ _back?: { result: string; data: any } }>()
</script>

useTabLoading

Управление состоянием загрузки страницы. Можно использовать только внутри компонентов страниц во вкладке.

import { useTabLoading } from 'vue-stack-tabs'

const { openTabLoading, closeTabLoading } = useTabLoading()

// Показать оверлей загрузки
openTabLoading()

// Закрыть после завершения асинхронной операции
fetchData().finally(() => closeTabLoading())

Загрузка автоматически закрывается при размонтировании компонента — ручная очистка не требуется.


Свойства (Props)

Свойства компонента <VueStackTabs>:

Свойство Тип По умолчанию Описание
iframePath string Обязательно Путь маршрута-заглушки для iframe
iframeSandbox string allow-scripts allow-forms allow-popups allow-downloads allow-same-origin Политика sandbox для iframe; по умолчанию совместимость, не строгая изоляция — удалите allow-same-origin для строгой изоляции; пустая строка отключает sandbox (не рекомендуется)
iframeReferrerPolicy ReferrerPolicy strict-origin-when-cross-origin Атрибут referrerpolicy для iframe
iframeAllow string '' Атрибут allow для iframe, например fullscreen
iframeLoadTimeout number 15000 Тайм-аут загрузки iframe в мс
defaultTabs ITabData[] [] Начальный список вкладок
homeTab ITabData — Закреплённая домашняя вкладка: всегда первая, не закрывается и не перемещается; другие вкладки не могут встать перед ней
max number 20 Максимальное количество вкладок
contextmenu boolean | object true Включить контекстное меню
pageTransition string 'stack-tab-swap' Имя анимации перехода при навигации вперёд
pageTransitionBack string 'stack-tab-swap-back' Имя анимации перехода при навигации назад
tabTransition string | TransitionProps 'stack-tab-zoom' Эффект перехода при добавлении/удалении вкладок
tabScrollMode TabScrollMode 'both' Режим прокрутки панели вкладок: 'wheel' / 'button' / 'both'
space number 300 Шаг прокрутки панели вкладок (px)
width string '100%' Ширина контейнера
height string '100%' Высота контейнера
i18n string 'zh-CN' Локаль интернационализации, встроенные 'zh-CN' / 'en'
globalScroll boolean false Включить запоминание прокрутки на уровне страницы
sessionPrefix string '' Префикс ключа sessionStorage; полезно для нескольких экземпляров на одном источнике
iframeAllowedOrigins string[] [] (плюс текущий источник) Разрешённые источники для iframe postMessage

События (Events)

Событие Параметры Описание
onActive (id: string) Срабатывает при активации вкладки
onPageLoaded — Срабатывает при загрузке компонента страницы
<VueStackTabs
  iframe-path="/iframe"
  @on-active="(id) => console.log('active:', id)"
  @on-page-loaded="() => console.log('page loaded')"
/>

Слоты

Слот Параметры слота Описание
leftButton — Левая область панели вкладок, обычно с TabHeaderButton
rightButton — Правая область панели вкладок
iframeLoading { tab } Свой плейсхолдер загрузки для вкладки iframe
iframeError { tab, retry } Свой плейсхолдер ошибки для вкладки iframe; retry() — перезагрузка
<VueStackTabs iframe-path="/iframe">
  <template #leftButton>
    <TabHeaderButton title="Развернуть" @click="collapse = !collapse" />
  </template>
  <template #iframeError="{ tab, retry }">
    <span>{{ tab.title }} не загрузилась</span>
    <button @click="retry">Повторить</button>
  </template>
</VueStackTabs>

Вкладки iframe

Открытие вкладки iframe

openTab({
  title: 'Внешняя страница',
  path: 'https://example.com',
  iframe: true
})

Операции внутри iframe-страниц

Внутри вкладки iframe можно взаимодействовать с хост-контейнером через postMessage для открытия новых вкладок или обработки действий обновления.

1. Использование утилит Bridge (рекомендуется)

Если iframe-страница может ссылаться на код библиотеки (или импортировать инструменты из iframeBridge.ts), рекомендуется этот подход:

import { postOpenTab, onRefreshRequest } from 'vue-stack-tabs/iframe-bridge'

const parentOrigin = 'https://parent.example.com'

// Открыть вкладку
postOpenTab(
  {
    title: 'Новая страница',
    path: '/detail',
    query: { id: '1' }
  },
  { targetOrigin: parentOrigin }
)

// Слушать запросы на обновление
const off = onRefreshRequest(
  () => {
    // Пользовательская логика обновления
    location.reload()
  },
  { allowedOrigins: [parentOrigin] }
)
2. Нативная интеграция

Если невозможно импортировать утилиты библиотеки или требуется нулевая зависимость, используйте нативный API:

const parentOrigin = 'https://parent.example.com'

// Открыть вкладку
window.parent.postMessage(
  {
    type: 'vue-stack-tabs:openTab',
    payload: {
      title: 'Нативная новая страница',
      path: '/detail'
    }
  },
  parentOrigin
)

// Слушать запросы на обновление
window.addEventListener('message', (ev) => {
  if (ev.origin !== parentOrigin) return
  if (ev.source !== window.parent) return
  if (ev.data?.type === 'vue-stack-tabs:refresh') {
    // Выполнить обновление
    location.reload()
  }
})

Режимы обновления iframe

При вызове openTab можно указать поведение обновления через iframeRefreshMode:

  • postMessage (по умолчанию): хост отправляет сообщение vue-stack-tabs:refresh в iframe; внутренняя страница сама решает, как обновляться (более плавная анимация).
  • reload: хост напрямую сбрасывает src или key iframe для принудительной перезагрузки браузером (подходит для кросс-доменных или немодифицируемых страниц).

Интернационализация

Встроенные языки: zh-CN (китайский), en (английский).

// Переключить язык через prop компонента
// <VueStackTabs iframe-path="/iframe" i18n="en" />

// Или при установке плагина
app.use(VueStackTabs, [{ locale: 'en' }])

Пользовательский языковой пакет (messages — вложенный объект, глубоко сливается со встроенными языками):

app.use(VueStackTabs, [
  {
    locale: 'ru-RU',
    messages: {
      VueStackTab: {
        close: 'Закрыть',
        reload: 'Обновить',
        loading: 'Загрузка'
        // ...
      }
    }
  }
])

Определения типов

import type { LocationQueryRaw } from 'vue-router'

/** Данные, передаваемые при открытии вкладки */
interface ITabData {
  id?: string // ID вкладки (автоматически генерируемый UUID, если не указан)
  title: string // Заголовок вкладки
  path: string // Путь маршрута или URL iframe
  query?: LocationQueryRaw // Параметры маршрута
  closable?: boolean // Можно ли закрыть (по умолчанию true)
  refreshable?: boolean // Можно ли обновить (по умолчанию true)
  iframe?: boolean // Является ли вкладкой iframe (по умолчанию false)
  iframeRefreshMode?: 'postMessage' | 'reload' // Способ обновления iframe
}

/** Режим прокрутки панели вкладок */
enum TabScrollMode {
  WHEEL = 'wheel',
  BUTTON = 'button',
  BOTH = 'both'
}

Структура проекта

src/lib/
├── StackTabs.vue           # Главный компонент
├── index.ts                # Точка входа и экспорты
├── hooks/                  # Основная логика
│   ├── useTabActions.ts    # Публичный API
│   ├── useTabRouter.ts     # Навигация внутри вкладки
│   ├── useTabLoading.ts    # Состояние загрузки
│   └── useTabPanel.tsx     # Ядро движка
├── model/TabModel.ts       # Определения типов
├── components/             # UI-компоненты
├── nuxt/                   # Модуль Nuxt
└── assets/style/           # Стили

Подробное описание архитектуры см. в ARCHITECTURE.md.


Лицензия

Apache-2.0

Related Packages

@xsbcme/vue-tab-router

Vue 3 多标签页路由插件,适用于后台管理系统、工作台和多文档编辑场景。

4,799 12
maz-ui

A standalone components library for Vue.Js 3 & Nuxt.Js 3

1,162,680 564
@unocss/nuxt

Nuxt module for UnoCSS

3,181,743 18,982

Version History

Version Released Vue License
2.6.2 ^3.5.43 Apache-2.0
2.6.1 ^3.5.43 Apache-2.0
2.6.0 ^3.5.43 Apache-2.0
2.5.1 ^3.5.43 Apache-2.0
2.5.0 ^3.5.43 Apache-2.0