Skip to content
On this page

标签导航支持固定 tag

通过路由 meta 里添加 affix 属性为 true,就让当前路由 tag 始终固定在标签导航

Dashboard 路由添加 affix: true image.png 效果 image.png 始终固定在开头不可删除

3-1 修改 tagsView 组件

对于要 affix 的路由 我们在最开始页面加载的时候,就要筛选出来,渲染到标签导航开头,并且不显示关闭 icon

添加筛选 affix 路由 tag 方法 filterAffixTags image.png 页面加载时进行筛选 image.png src/layout/components/TagsView/index.vue

vue
<template>
  <div class="tags-view-container">
    <div class="tags-view-wrapper">
      <router-link
        class="tags-view-item"
        :class="{
          active: isActive(tag)
        }"
        v-for="(tag, index) in visitedTags"
        :key="index"
        :to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }"
        tag="span"
      >
        {{ tag.title }}
        <!-- affix固定的路由tag是无法删除 -->
        <span
          v-if="!isAffix(tag)"
          class="el-icon-close"
          @click.prevent.stop="closeSelectedTag(tag)"
        ></span>
      </router-link>
    </div>
  </div>
</template>

<script lang="ts">
import { defineComponent, computed, watch, onMounted } from 'vue'
import { useRoute, RouteRecordRaw, useRouter } from 'vue-router'
import { useStore } from '@/store'
import { RouteLocationWithFullPath } from '@/store/modules/tagsView'
import { routes } from '@/router'
import path from 'path'

export default defineComponent({
  name: 'TagsView',
  setup() {
    const store = useStore()
    const router = useRouter()
    const route = useRoute()
    // 可显示的tags view
    const visitedTags = computed(() => store.state.tagsView.visitedViews)

    // 从路由表中过滤出要affixed tagviews
    const fillterAffixTags = (routes: Array<RouteLocationWithFullPath | RouteRecordRaw>, basePath = '/') => {
      let tags: RouteLocationWithFullPath[] = []
      routes.forEach(route => {
        if (route.meta && route.meta.affix) {
          // 把路由路径解析成完整路径,路由可能是相对路径
          const tagPath = path.resolve(basePath, route.path)
          tags.push({
            name: route.name,
            path: tagPath,
            fullPath: tagPath,
            meta: { ...route.meta }
          } as RouteLocationWithFullPath)
        }

        // 深度优先遍历 子路由(子路由路径可能相对于route.path父路由路径)
        if (route.children) {
          const childTags = fillterAffixTags(route.children, route.path)
          if (childTags.length) {
            tags = [...tags, ...childTags]
          }
        }
      })
      return tags
    }

    // 初始添加affix的tag
    const initTags = () => {
      const affixTags = fillterAffixTags(routes)
      for (const tag of affixTags) {
        if (tag.name) {
          store.dispatch('tagsView/addVisitedView', tag)
        }
      }
    }

    // 添加tag
    const addTags = () => {
      const { name } = route
      if (name) {
        store.dispatch('tagsView/addView', route)
      }
    }

    // 路径发生变化追加tags view
    watch(() => route.path, () => {
      addTags()
    })

    // 最近当前router到tags view
    onMounted(() => {
      initTags()
      addTags()
    })

    // 当前是否是激活的tag
    const isActive = (tag: RouteRecordRaw) => {
      return tag.path === route.path
    }

    // 让删除后tags view集合中最后一个为选中状态
    const toLastView = (visitedViews: RouteLocationWithFullPath[], view: RouteLocationWithFullPath) => {
      // 得到集合中最后一个项tag view 可能没有
      const lastView = visitedViews[visitedViews.length - 1]
      if (lastView) {
        router.push(lastView.fullPath as string)
      } else { // 集合中都没有tag view时
        // 如果刚刚删除的正是Dashboard 就重定向回Dashboard(首页)
        if (view.name === 'Dashboard') {
          router.replace({ path: '/redirect' + view.fullPath as string })
        } else {
          // tag都没有了 删除的也不是Dashboard 只能跳转首页
          router.push('/')
        }
      }
    }

    // 关闭当前右键的tag路由
    const closeSelectedTag = (view: RouteLocationWithFullPath) => {
      // 关掉并移除view
      store.dispatch('tagsView/delView', view).then(() => {
        // 如果移除的view是当前选中状态view, 就让删除后的集合中最后一个tag view为选中态
        if (isActive(view)) {
          toLastView(visitedTags.value, view)
        }
      })
    }

    // 是否是始终固定在tagsview上的tag
    const isAffix = (tag: RouteLocationWithFullPath) => {
      return tag.meta && tag.meta.affix
    }

    return {
      visitedTags,
      isActive,
      closeSelectedTag,
      isAffix
    }
  }
})
</script>

<style lang="scss" scoped>
.tags-view-container {
  width: 100%;
  height: 34px;
  background: #fff;
  border-bottom: 1px solid #d8dce5;
  box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .12), 0 0 3px 0 rgba(0, 0, 0, .04);
  .tags-view-wrapper {
    .tags-view-item {
      display: inline-block;
      height: 26px;
      line-height: 26px;
      border: 1px solid #d8dce5;
      background: #fff;
      color: #495060;
      padding: 0 8px;
      box-sizing: border-box;
      font-size: 12px;
      margin-left: 5px;
      margin-top: 4px;
      &:first-of-type {
        margin-left: 15px;
      }
      &:last-of-type {
        margin-right: 15px;
      }
      &.active {
        background-color: #42b983;
        color: #fff;
        border-color: #42b983;
        &::before {
          position: relative;
          display: inline-block;
          content: '';
          width: 8px;
          height: 8px;
          border-radius: 50%;
          margin-right: 5px;
          background: #fff;
        }
      }
    }
  }
}
</style>

<style lang="scss">
.tags-view-container {
  .el-icon-close {
    width: 16px;
    height: 16px;
    position: relative;
    left: 2px;
    border-radius: 50%;
    text-align: center;
    transition: all .3s cubic-bezier(.645, .045, .355, 1);
    transform-origin: 100% 50%;
    &:before {
      transform: scale(.6);
      display: inline-block;
      vertical-align: -1px;
    }
    &:hover {
      background-color: #b4bccc;
      color: #fff;
    }
  }
}
</style>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215

3-2 修改 dashboard 路由测试

image.png

本节参考源码

https://gitee.com/brolly/vue3-element-admin/commit/57a145201a064baa9e3e78afd607ae2a3827505d

沪ICP备20006251号-1