# How the TagsView Component Manages Tab State and Right-Click Operations in Vue-Element-Admin

> Explore how the TagsView component in vue-element-admin manages tab state persistence and right-click operations using Vuex and targeted mutations for an intuitive user experience.

- Repository: [花裤衩/vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)
- Tags: internals
- Published: 2026-02-27

---

**The TagsView component in vue-element-admin centralizes tab state in a dedicated Vuex store module while handling right-click operations through targeted mutations that filter visitedViews and cachedViews arrays based on affix metadata and selection state.**

The **TagsView component** provides the multi-tab navigation interface in the vue-element-admin dashboard framework. It synchronizes browser route changes with a persistent tab bar, supporting advanced interactions like "Close Others" and "Close All" through a tightly-coupled Vuex architecture that maintains two distinct state arrays.

## Vuex Store Architecture

The state management relies on two synchronized arrays in [`src/store/modules/tagsView.js`](https://github.com/PanJiaChen/vue-element-admin/blob/main/src/store/modules/tagsView.js):

- **visitedViews**: Tracks all tabs currently displayed in the tab bar
- **cachedViews**: Stores component names for `<keep-alive>` caching to preserve component state

### State Initialization and Affix Tags

When the component mounts, `initTags()` in [`src/layout/components/TagsView/index.vue`](https://github.com/PanJiaChen/vue-element-admin/blob/main/src/layout/components/TagsView/index.vue) extracts routes marked with `meta.affix` (permanent tabs that cannot be closed) and populates the store:

```js
// src/layout/components/TagsView/index.vue
const affixTags = this.affixTags = this.filterAffixTags(this.routes)
for (const tag of affixTags) {
  if (tag.name) {
    this.$store.dispatch('tagsView/addVisitedView', tag)
  }
}

```

Affix tags are protected across all deletion operations, ensuring critical navigation elements like the dashboard homepage remain accessible regardless of bulk close actions.

### Dynamic Route Watching

The component watches `$route` changes to automatically register new tabs. The `addTags()` method dispatches `tagsView/addView`, which triggers both `addVisitedView` and `addCachedView` actions:

```js
// src/store/modules/tagsView.js
addView({ dispatch }, view) {
  dispatch('addVisitedView', view)
  dispatch('addCachedView', view)
}

```

The corresponding mutations `ADD_VISITED_VIEW` and `ADD_CACHED_VIEW` prevent duplicates by checking existing paths before pushing new view objects into the state arrays.

## Right-Click Context Menu Operations

The component renders a hidden `<ul class="contextmenu">` element that becomes visible via `openMenu(tag, $event)` when users right-click a tab. This method sets `selectedTag` and calculates positioning based on container bounds to prevent overflow.

### Close Others Implementation

Selecting "Close Others" triggers `closeOthersTags()`, which first navigates to the selected tag, then dispatches `tagsView/delOthersViews`:

```js
// src/layout/components/TagsView/index.vue
closeOthersTags() {
  this.$router.push(this.selectedTag)
  this.$store.dispatch('tagsView/delOthersViews', this.selectedTag)
    .then(() => { this.moveToCurrentTag() })
}

```

The mutation `DEL_OTHERS_VISITED_VIEWS` filters the array to retain only the selected view and any affix tags:

```js
// src/store/modules/tagsView.js
DEL_OTHERS_VISITED_VIEWS: (state, view) => {
  state.visitedViews = state.visitedViews.filter(v => {
    return v.meta.affix || v.path === view.path
  })
}

```

A corresponding `DEL_OTHERS_CACHED_VIEWS` mutation cleans the component cache. After completion, `moveToCurrentTag()` scrolls the active tab into view using the `ScrollPane` child component.

### Close All Implementation

The "Close All" operation via `closeAllTags()` dispatches `tagsView/delAllViews`, which executes `DEL_ALL_VISITED_VIEWS` and `DEL_ALL_CACHED_VIEWS`. These mutations preserve only affix tags:

```js
// src/store/modules/tagsView.js
DEL_ALL_VISITED_VIEWS: (state) => {
  const affixTags = state.visitedViews.filter(tag => tag.meta.affix)
  state.visitedViews = affixTags
}

```

If the current active view is not an affix tag, `toLastView()` navigates to the last remaining tab or the home route to prevent 404 errors.

### Single Tab Operations

**Close**: `closeSelectedTag()` dispatches `tagsView/delView`, calling both `DEL_VISITED_VIEW` and `DEL_CACHED_VIEW` mutations. If closing the active tab, it automatically navigates to the previous view in the history.

**Refresh**: `refreshSelectedTag()` removes the component from `cachedViews` via `tagsView/delCachedView`, then redirects to a special `/redirect` route containing the original path. This destroys and recreates the component instance without a full page reload.

## State Persistence and Scroll Behavior

Tab state persists for the duration of the SPA session through the Vuex store. Unlike localStorage-based solutions, this approach maintains strict alignment between the tab bar and component cache. The [`ScrollPane.vue`](https://github.com/PanJiaChen/vue-element-admin/blob/main/ScrollPane.vue) helper component handles horizontal scrolling, ensuring the active tag remains visible after navigation or bulk close operations through `moveToCurrentTag()`.

## Summary

- **Vuex Centralization**: All tab state lives in [`src/store/modules/tagsView.js`](https://github.com/PanJiaChen/vue-element-admin/blob/main/src/store/modules/tagsView.js) with separate `visitedViews` and `cachedViews` arrays
- **Affix Protection**: Routes with `meta.affix` survive all bulk deletion operations (Close Others/Close All)
- **Right-Click Flow**: Context menu sets `selectedTag`, then dispatches specific store actions that filter arrays while preserving affix entries
- **Auto-Navigation**: `toLastView()` prevents orphaned routes by redirecting to remaining tabs when the active tab closes
- **Component Cache**: The `cachedViews` array directly feeds Vue's `<keep-alive>` include prop for stateful component preservation

## Frequently Asked Questions

### How does the TagsView component persist tabs across browser navigation?

The component watches `$route` changes in [`src/layout/components/TagsView/index.vue`](https://github.com/PanJiaChen/vue-element-admin/blob/main/src/layout/components/TagsView/index.vue) and dispatches Vuex actions to add views to the store. Since the `tagsView` module maintains `visitedViews` in memory, tabs persist as long as the SPA remains loaded. For persistence across page reloads, integrate a Vuex persistence plugin like `vuex-persistedstate` to sync the store with localStorage.

### Why are some tabs protected when using Close All or Close Others?

Tabs generated from routes with `meta.affix: true` (like the dashboard homepage) are filtered and preserved in the `DEL_ALL_VISITED_VIEWS` and `DEL_OTHERS_VISITED_VIEWS` mutations. The `filterAffixTags()` method identifies these during initialization in [`src/layout/components/TagsView/index.vue`](https://github.com/PanJiaChen/vue-element-admin/blob/main/src/layout/components/TagsView/index.vue), ensuring critical navigation anchors remain available.

### How does the Refresh option reload a component without refreshing the entire page?

`refreshSelectedTag()` dispatches `tagsView/delCachedView` to remove the component name from `cachedViews`, clearing the `<keep-alive>` cache. It then redirects to a special `/redirect` route containing the original path, causing Vue Router to destroy and recreate the component instance while preserving the URL state.

### Where is the horizontal scroll behavior implemented when many tabs are open?

The scroll logic resides in [`src/layout/components/TagsView/ScrollPane.vue`](https://github.com/PanJiaChen/vue-element-admin/blob/main/src/layout/components/TagsView/ScrollPane.vue). The `TagsView` component calls `moveToCurrentTag()` after navigation or bulk close operations, which queries the DOM for the active `<router-link>` element and invokes ScrollPane methods to calculate and apply horizontal scroll offsets.