通过路由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

  1. <template>
  2. <div class="tags-view-container">
  3. <div class="tags-view-wrapper">
  4. <router-link
  5. class="tags-view-item"
  6. :class="{
  7. active: isActive(tag)
  8. }"
  9. v-for="(tag, index) in visitedTags"
  10. :key="index"
  11. :to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }"
  12. tag="span"
  13. >
  14. {{ tag.title }}
  15. <!-- affix固定的路由tag是无法删除 -->
  16. <span
  17. v-if="!isAffix(tag)"
  18. class="el-icon-close"
  19. @click.prevent.stop="closeSelectedTag(tag)"
  20. ></span>
  21. </router-link>
  22. </div>
  23. </div>
  24. </template>
  25. <script lang="ts">
  26. import { defineComponent, computed, watch, onMounted } from 'vue'
  27. import { useRoute, RouteRecordRaw, useRouter } from 'vue-router'
  28. import { useStore } from '@/store'
  29. import { RouteLocationWithFullPath } from '@/store/modules/tagsView'
  30. import { routes } from '@/router'
  31. import path from 'path'
  32. export default defineComponent({
  33. name: 'TagsView',
  34. setup() {
  35. const store = useStore()
  36. const router = useRouter()
  37. const route = useRoute()
  38. // 可显示的tags view
  39. const visitedTags = computed(() => store.state.tagsView.visitedViews)
  40. // 从路由表中过滤出要affixed tagviews
  41. const fillterAffixTags = (routes: Array<RouteLocationWithFullPath | RouteRecordRaw>, basePath = '/') => {
  42. let tags: RouteLocationWithFullPath[] = []
  43. routes.forEach(route => {
  44. if (route.meta && route.meta.affix) {
  45. // 把路由路径解析成完整路径,路由可能是相对路径
  46. const tagPath = path.resolve(basePath, route.path)
  47. tags.push({
  48. name: route.name,
  49. path: tagPath,
  50. fullPath: tagPath,
  51. meta: { ...route.meta }
  52. } as RouteLocationWithFullPath)
  53. }
  54. // 深度优先遍历 子路由(子路由路径可能相对于route.path父路由路径)
  55. if (route.children) {
  56. const childTags = fillterAffixTags(route.children, route.path)
  57. if (childTags.length) {
  58. tags = [...tags, ...childTags]
  59. }
  60. }
  61. })
  62. return tags
  63. }
  64. // 初始添加affix的tag
  65. const initTags = () => {
  66. const affixTags = fillterAffixTags(routes)
  67. for (const tag of affixTags) {
  68. if (tag.name) {
  69. store.dispatch('tagsView/addVisitedView', tag)
  70. }
  71. }
  72. }
  73. // 添加tag
  74. const addTags = () => {
  75. const { name } = route
  76. if (name) {
  77. store.dispatch('tagsView/addView', route)
  78. }
  79. }
  80. // 路径发生变化追加tags view
  81. watch(() => route.path, () => {
  82. addTags()
  83. })
  84. // 最近当前router到tags view
  85. onMounted(() => {
  86. initTags()
  87. addTags()
  88. })
  89. // 当前是否是激活的tag
  90. const isActive = (tag: RouteRecordRaw) => {
  91. return tag.path === route.path
  92. }
  93. // 让删除后tags view集合中最后一个为选中状态
  94. const toLastView = (visitedViews: RouteLocationWithFullPath[], view: RouteLocationWithFullPath) => {
  95. // 得到集合中最后一个项tag view 可能没有
  96. const lastView = visitedViews[visitedViews.length - 1]
  97. if (lastView) {
  98. router.push(lastView.fullPath as string)
  99. } else { // 集合中都没有tag view时
  100. // 如果刚刚删除的正是Dashboard 就重定向回Dashboard(首页)
  101. if (view.name === 'Dashboard') {
  102. router.replace({ path: '/redirect' + view.fullPath as string })
  103. } else {
  104. // tag都没有了 删除的也不是Dashboard 只能跳转首页
  105. router.push('/')
  106. }
  107. }
  108. }
  109. // 关闭当前右键的tag路由
  110. const closeSelectedTag = (view: RouteLocationWithFullPath) => {
  111. // 关掉并移除view
  112. store.dispatch('tagsView/delView', view).then(() => {
  113. // 如果移除的view是当前选中状态view, 就让删除后的集合中最后一个tag view为选中态
  114. if (isActive(view)) {
  115. toLastView(visitedTags.value, view)
  116. }
  117. })
  118. }
  119. // 是否是始终固定在tagsview上的tag
  120. const isAffix = (tag: RouteLocationWithFullPath) => {
  121. return tag.meta && tag.meta.affix
  122. }
  123. return {
  124. visitedTags,
  125. isActive,
  126. closeSelectedTag,
  127. isAffix
  128. }
  129. }
  130. })
  131. </script>
  132. <style lang="scss" scoped>
  133. .tags-view-container {
  134. width: 100%;
  135. height: 34px;
  136. background: #fff;
  137. border-bottom: 1px solid #d8dce5;
  138. box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .12), 0 0 3px 0 rgba(0, 0, 0, .04);
  139. .tags-view-wrapper {
  140. .tags-view-item {
  141. display: inline-block;
  142. height: 26px;
  143. line-height: 26px;
  144. border: 1px solid #d8dce5;
  145. background: #fff;
  146. color: #495060;
  147. padding: 0 8px;
  148. box-sizing: border-box;
  149. font-size: 12px;
  150. margin-left: 5px;
  151. margin-top: 4px;
  152. &:first-of-type {
  153. margin-left: 15px;
  154. }
  155. &:last-of-type {
  156. margin-right: 15px;
  157. }
  158. &.active {
  159. background-color: #42b983;
  160. color: #fff;
  161. border-color: #42b983;
  162. &::before {
  163. position: relative;
  164. display: inline-block;
  165. content: '';
  166. width: 8px;
  167. height: 8px;
  168. border-radius: 50%;
  169. margin-right: 5px;
  170. background: #fff;
  171. }
  172. }
  173. }
  174. }
  175. }
  176. </style>
  177. <style lang="scss">
  178. .tags-view-container {
  179. .el-icon-close {
  180. width: 16px;
  181. height: 16px;
  182. position: relative;
  183. left: 2px;
  184. border-radius: 50%;
  185. text-align: center;
  186. transition: all .3s cubic-bezier(.645, .045, .355, 1);
  187. transform-origin: 100% 50%;
  188. &:before {
  189. transform: scale(.6);
  190. display: inline-block;
  191. vertical-align: -1px;
  192. }
  193. &:hover {
  194. background-color: #b4bccc;
  195. color: #fff;
  196. }
  197. }
  198. }
  199. </style>

3-2 修改dashboard路由测试

image.png

本节参考源码

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