Vite2 + vue3 + TS + ElementPlus 从零搭建后台管理系统(五)

上一章主要完善了侧边栏菜单组件 Vite2 + vue3 + TS + ElementPlus 从零搭建后台管理系统(四)

这一章开始完善header组件,将包含breadcrumb和tagsView

1. 新增多个路由菜单

  • 在 src/views 下新建:

  • demo/index.vue

  • icon/index.vue

内容简单随意

再更改 router/index.ts:

const router = createRouter({
  history: routerHistory,
  routes: [
    {
      path: '/',
      component: Layout,
      children: [
        {
          path: '/home',
          name:'home',
          component: ()=>import('views/home/index.vue'),
        },
        {
          path: '/demo',
          name:'demo',
          component: ()=>import('views/demo/index.vue'),
        },
        {
          path: '/icon',
          name:'icon',
          component: ()=>import('views/icon/index.vue'),
        }
      ]
    }
  ]
})
复制代码
  • 在 store/interface/index.ts 目录下新增:
//新增
export interface RoutesListState {
	routesList: Array<object>;
}

...
export interface RootStateTypes {
	themeConfig: ThemeConfigState;
  app:App;
	routesList:RoutesListState; //新增
}
复制代码
  • 在 store/modules 目录下新增 routesList.ts

routesList.ts:

import { Module } from 'vuex';

import { RoutesListState, RootStateTypes } from 'store/interface/index';

const routesListModule: Module<RoutesListState, RootStateTypes> = {
	namespaced: true,
	state: {
		routesList: [
      {
        meta: {
          auth: ['admin', 'test'],
          icon: 'iconfont el-icon-menu',
          isAffix: true,
          isHide: false,
          isIframe: false,
          isKeepAlive: true,
          title: '首页',
          index: '1'
        },
        name: 'home',
        path: '/home'
      },
      {
        meta: {
          auth: ['admin', 'test'],
          icon: 'iconfont el-icon-s-grid',
          isAffix: true,
          isHide: false,
          isIframe: false,
          isKeepAlive: true,
          title: 'demo',
          index: '2'
        },
        name: 'demo',
        path: '/demo'
      },
      {
        meta: {
          auth: ['admin', 'test'],
          icon: 'iconfont el-icon-s-grid',
          isAffix: true,
          isHide: false,
          isIframe: false,
          isKeepAlive: true,
          title: 'icon',
          index: '3'
        },
        name: 'icon',
        path: '/icon'
      }
    ],
	},
	mutations: {
		// 设置路由,菜单中使用到
		getRoutesList(state: any, data: Array<object>) {
			state.routesList = data;
		},
	},
	actions: {
		// 设置路由,菜单中使用到
		async setRoutesList({ commit }, data: any) {
			commit('getRoutesList', data);
		},
	},
};

export default routesListModule;

复制代码

新增的routesList.ts 记得再store/index.ts中引用

  • 修改 layout/component/aside.vue 文件中menuList 从 store中获取

aside.vue:

    // 修改++
    const state: any = reactive({
      menuList: [], // 修改++
      clientWidth: ''
    })

    // 新增++
    const setFilterRoutes = () => {
      state.menuList = filterRoutesFun(store.state.routesList.routesList)
    }

    // 新增++
    const filterRoutesFun = (arr: Array<object>) => {
      return arr
        .filter((item: any) => !item.meta.isHide)
        .map((item: any) => {
          item = Object.assign({}, item)
          if (item.children) item.children = filterRoutesFun(item.children)
          return item
        })
    }

    // 修改++
    onBeforeMount(() => {
      initMenuFixed(document.body.clientWidth)
      setFilterRoutes(); // 新增++
    })
复制代码

到此完成了 新增路由菜单,并从store中获取

2. 新增 breadcrumb(面包屑) 组件

先在store的themeConfig模块里新增开两个状态:

  • isBreadcrumb:true // 是否开启 Breadcrumb
  • isBreadcrumbIcon:true // 是否开启 Breadcrumb 图标
  • 在 layout/component/navBars 下新增:

breadcrumb/index.vue // 处理 breadcrumb 相关内容

breadcrumb/breadcrumb.vue // breadcrumb组件

breadcrumb/index.vue:

<template>
	<div class="layout-navbars-breadcrumb-index">
		<Breadcrumb />
    <!-- 可以后面新增用户信息等组件 -->
	</div>
</template>

<script lang="ts">
import Breadcrumb from '../breadcrumb/breadcrumb.vue';

export default {
	name: 'layoutBreadcrumbIndex',
	components: { Breadcrumb },
};
</script>

<style scoped lang="scss">
.layout-navbars-breadcrumb-index {
	height: 50px;
	display: flex;
	align-items: center;
	padding-right: 15px;
	background: var(--bg-topBar);
	overflow: hidden;
	border-bottom: 1px solid #f1f2f3;
}
</style>

复制代码

breadcrumb/breadcrumb.vue

<template>
  <div class="layout-navbars-breadcrumb" v-show="getThemeConfig.isBreadcrumb">
    <i
      class="layout-navbars-breadcrumb-icon"
      :class="getThemeConfig.isCollapse ? 'el-icon-s-unfold' : 'el-icon-s-fold'"
      @click="onThemeConfigChange"
    ></i>
    <el-breadcrumb class="layout-navbars-breadcrumb-hide">
      <transition-group name="breadcrumb" mode="out-in">
        <el-breadcrumb-item
          v-for="(v, k) in breadcrumbList"
          :key="v.meta.title"
        >
          <span
            v-if="k === breadcrumbList.length - 1"
            class="layout-navbars-breadcrumb-span"
          >
            <i
              :class="v.meta.icon"
              class="layout-navbars-breadcrumb-iconfont"
              v-if="getThemeConfig.isBreadcrumbIcon"
            ></i>
            {{ v.meta.title }}
          </span>
          <a v-else @click.prevent="onBreadcrumbClick(v)">
            <i
              :class="v.meta.icon"
              class="layout-navbars-breadcrumb-iconfont"
              v-if="getThemeConfig.isBreadcrumbIcon"
            ></i>
            {{ v.meta.title }}
          </a>
        </el-breadcrumb-item>
      </transition-group>
    </el-breadcrumb>
  </div>
</template>

<script lang="ts">
  import { toRefs, reactive, computed, onMounted } from 'vue'
  import { onBeforeRouteUpdate, useRoute, useRouter } from 'vue-router'
  import { useStore } from 'store/index'
  export default {
    name: 'layoutBreadcrumb',
    setup() {
      const store = useStore()
      const route = useRoute()
      const router = useRouter()
      const state: any = reactive({
        breadcrumbList: [],
        routeSplit: [],
        routeSplitFirst: '',
        routeSplitIndex: 1
      })
      // 获取布局配置信息
      const getThemeConfig = computed(() => store.state.themeConfig)
      // 面包屑点击时
      const onBreadcrumbClick = (v: any) => {
        const { redirect, path } = v
        if (redirect) router.push(redirect)
        else router.push(path)
      }

      // 展开/收起左侧菜单点击
      const onThemeConfigChange = () => {
        store.state.themeConfig.isCollapse = !store.state.themeConfig.isCollapse
      }
      // 处理-分级-面包屑数据
      const getBreadcrumbList = (arr: Array<object>) => {
        arr.map((item: any) => {
          state.routeSplit.map((v: any, k: number, arrs: any) => {
            if (state.routeSplitFirst === item.path) {
              state.routeSplitFirst += `/${arrs[state.routeSplitIndex]}`
              state.breadcrumbList.push(item)
              state.routeSplitIndex++
              if (item.children) getBreadcrumbList(item.children)
            }
          })
        })
      }

      // 当前路由字符串切割成数组,并删除第一项空内容
      const initRouteSplit = (path: string) => {
        if (!store.state.themeConfig.isBreadcrumb) return false
        state.breadcrumbList = [store.state.routesList.routesList[0]]
        state.routeSplit = path.split('/')
        state.routeSplit.shift()
        state.routeSplitFirst = `/${state.routeSplit[0]}`
        state.routeSplitIndex = 1
        getBreadcrumbList(store.state.routesList.routesList)
      }
      // 页面加载时
      onMounted(() => {
        initRouteSplit(route.path)
      })
      // 路由更新时
      onBeforeRouteUpdate((to) => {
        initRouteSplit(to.path)
      })
      return {
        getThemeConfig,
        onBreadcrumbClick,
        onThemeConfigChange,
        ...toRefs(state)
      }
    }
  }
</script>

<style scoped lang="scss">
.layout-navbars-breadcrumb {
	flex: 1;
	height: inherit;
	display: flex;
	align-items: center;
	padding-left: 15px;
	.layout-navbars-breadcrumb-icon {
		cursor: pointer;
		font-size: 18px;
		margin-right: 15px;
		color: var(--bg-topBarColor);
	}
	.layout-navbars-breadcrumb-span {
		opacity: 0.7;
		color: var(--bg-topBarColor);
	}
	.layout-navbars-breadcrumb-iconfont {
		font-size: 14px;
		margin-right: 5px;
	}
	::v-deep(.el-breadcrumb__separator) {
		opacity: 0.7;
		color: var(--bg-topBarColor);
	}
}
</style>

复制代码
  • 再修改 layout/component/navBars/index.vue 引入 BreadcrumbIndex组件:
<template>
  <div class="layout-navbars-container">
    <BreadcrumbIndex></BreadcrumbIndex>
  </div>
</template>

<script lang="ts">
  import { computed } from 'vue'
  import { useStore } from 'store/index'
  import BreadcrumbIndex from './breadcrumb/index.vue'
  export default {
    name: 'layoutNavBars',
    components: { BreadcrumbIndex },
    setup() {
      const store = useStore()
      // 获取布局配置信息
      const getThemeConfig = computed(() => store.state.themeConfig)
      // 展开/收起左侧菜单点击
      const onThemeConfigChange = () => {
        store.state.themeConfig.isCollapse = !store.state.themeConfig.isCollapse
      }
      return {
        getThemeConfig,
        onThemeConfigChange
      }
    }
  }
</script>

<style scoped lang="scss">
  .layout-navbars-container {
    display: flex;
    flex-direction: column;
    width: 100%;
    height: 100%;
  }
</style>

复制代码

至此 breadcrumb 基本完成了:
image.png

3. 新增 tagsView(导航标签) 组件

先在 store/interface/index.ts 内新增:

//新增++
export interface TagsViewRoutesState {
	tagsViewRoutes: Array<object>;
}
...
export interface RootStateTypes {
	themeConfig: ThemeConfigState;
  app:App;
	tagsViewRoutes:TagsViewRoutesState; //新增++
}
复制代码
  • 在 store/modules 目录下新增 tagsViewRoutes.ts

tagsViewRoutes.ts:

import { Module } from 'vuex';
// 此处加上 `.ts` 后缀报错,具体原因不详
import { TagsViewRoutesState, RootStateTypes } from 'store/interface/index';

const tagsViewRoutesModule: Module<TagsViewRoutesState, RootStateTypes> = {
	namespaced: true,
	state: {
		tagsViewRoutes: [
      {
        meta: {
          auth: ['admin', 'test'],
          icon: 'iconfont el-icon-menu',
          isAffix: true,
          isHide: false,
          isIframe: false,
          isKeepAlive: true,
          title: '首页',
          index: '1'
        },
        name: 'home',
        path: '/home'
      },
      {
        meta: {
          auth: ['admin', 'test'],
          icon: 'iconfont el-icon-s-grid',
          isAffix: true,
          isHide: false,
          isIframe: false,
          isKeepAlive: true,
          title: 'demo',
          index: '2'
        },
        name: 'demo',
        path: '/demo'
      },
      {
        meta: {
          auth: ['admin', 'test'],
          icon: 'iconfont el-icon-s-grid',
          isAffix: true,
          isHide: false,
          isIframe: false,
          isKeepAlive: true,
          title: 'icon',
          index: '3'
        },
        name: 'icon',
        path: '/icon'
      }
    ],
	},
	mutations: {
		// 设置 TagsView 路由
		getTagsViewRoutes(state: any, data: Array<string>) {
			state.tagsViewRoutes = data;
		},
	},
	actions: {
		// 设置 TagsView 路由
		async setTagsViewRoutes({ commit }, data: Array<string>) {
			commit('getTagsViewRoutes', data);
		},
	},
};

export default tagsViewRoutesModule;

复制代码

在tagsViewRoutes.ts中先 tagsViewRoutes 的值和routesList的值 默认为一样的后面再做处理

接着在 store 的 themeConfig模块 里新增个状态:

  • isCacheTagsView:false // 是否开启 TagsView 缓存

新增的 tagsViewRoutes.ts 记得再store/index.ts中引用

  • 修改 @/utils/storage.ts :

新增三个处理sessionStorage函数

// 设置临时缓存
export function setSession(key: string, val: any) {
	window.sessionStorage.setItem(key, JSON.stringify(val));
}
// 获取临时缓存
export function getSession(key: string) {
	let json: any = window.sessionStorage.getItem(key);
	return JSON.parse(json);
}
// 移除临时缓存
export function removeSession(key: string) {
	window.sessionStorage.removeItem(key);
}
// 移除全部临时缓存
export function clearSession() {
	window.sessionStorage.clear();
}
复制代码
  • 在 layout/component/navBars 下新增 tagsView/tagsView.vue

tagsView.vue:

<template>
  <div
    class="layout-navbars-tagsview"
    :class="{
      'layout-navbars-tagsview-shadow': getThemeConfig.layout === 'classic'
    }"
  >
    <el-scrollbar ref="scrollbarRef" @wheel.prevent="onHandleScroll">
      <ul class="layout-navbars-tagsview-ul tags-style-one" ref="tagsUlRef">
        <li
          v-for="(v, k) in tagsViewList"
          :key="k"
          class="layout-navbars-tagsview-ul-li"
          :data-name="v.name"
          :class="{ 'is-active': isActive(v.path) }"
          @click="onTagsClick(v, k)"
          :ref="
            (el) => {
              if (el) tagsRefs[k] = el
            }
          "
        >
          <i
            class="iconfont icon-webicon318 layout-navbars-tagsview-ul-li-iconfont font14"
            v-if="isActive(v.path)"
          ></i>
          <i
            class="layout-navbars-tagsview-ul-li-iconfont"
            :class="v.meta.icon"
            v-if="!isActive(v.path) && getThemeConfig.isTagsviewIcon"
          ></i>
          <span>{{ v.meta.title }}</span>
          <template v-if="isActive(v.path)">
            <i
              class="el-icon-close layout-navbars-tagsview-ul-li-icon layout-icon-active"
              v-if="!v.meta.isAffix"
              @click.stop="closeCurrentTagsView(v.path)"
            ></i>
          </template>
          <i
            class="el-icon-close layout-navbars-tagsview-ul-li-icon layout-icon-three"
            v-if="!v.meta.isAffix"
            @click.stop="closeCurrentTagsView(v.path)"
          ></i>
        </li>
      </ul>
    </el-scrollbar>
  </div>
</template>

<script lang="ts">
  import {
    toRefs,
    reactive,
    onMounted,
    computed,
    ref,
    nextTick,
    onBeforeUpdate,
    onBeforeMount,
    onUnmounted,
    getCurrentInstance,
    watch
  } from 'vue'
  import { useRoute, useRouter, onBeforeRouteUpdate } from 'vue-router'
  import { useStore } from 'store/index'
  import { setSession, removeSession } from '@/utils/storage'

  export default {
    name: 'layoutTagsView',
    components: {},
    setup() {
      const { proxy } = getCurrentInstance() as any
      const tagsRefs = ref([])
      const scrollbarRef = ref()
      const tagsUlRef = ref()
      const store = useStore()
      const route = useRoute()
      const router = useRouter()
      const state: any = reactive({
        routePath: route.path,
        tagsRefsIndex: 0,
        tagsViewList: [],
        sortable: '',
        tagsViewRoutesList: []
      })

      // 获取布局配置信息
      const getThemeConfig = computed(() => {
        return store.state.themeConfig
      })
      // 存储 tagsViewList 到浏览器临时缓存中,页面刷新时,保留记录
      const addBrowserSetSession = (tagsViewList: Array<object>) => {
        setSession('tagsViewList', tagsViewList)
      }
      // 获取 vuex 中的 tagsViewRoutes 列表
      const getTagsViewRoutes = () => {
        state.routePath = route.path
        state.tagsViewList = []
        if (!store.state.themeConfig.isCacheTagsView)
          removeSession('tagsViewList')
        state.tagsViewRoutesList = store.state.tagsViewRoutes.tagsViewRoutes
        addTagsView(route.path)
      }

      // 添加 tagsView:未设置隐藏(isHide)也添加到在 tagsView 中
      const addTagsView = (path: string, to?: any) => {
        if (state.tagsViewList.some((v: any) => v.path === path)) return false
        const item = state.tagsViewRoutesList.find((v: any) => v.path === path)
        if (item.meta.isLink && !item.meta.isIframe) return false
        item.query = to?.query ? to?.query : route.query
        state.tagsViewList.push({ ...item })
        addBrowserSetSession(state.tagsViewList)
      }

      // 关闭当前 tagsView:如果是设置了固定的(isAffix),不可以关闭
      const closeCurrentTagsView = (path: string) => {
        state.tagsViewList.map((v: any, k: number, arr: any) => {
          if (!v.meta.isAffix) {
            if (v.path === path) {
              state.tagsViewList.splice(k, 1)
              setTimeout(() => {
                // 最后一个
                if (state.tagsViewList.length === k)
                  router.push({
                    path: arr[arr.length - 1].path,
                    query: arr[arr.length - 1].query
                  })
                // 否则,跳转到下一个
                else router.push({ path: arr[k].path, query: arr[k].query })
              }, 0)
            }
          }
        })
        addBrowserSetSession(state.tagsViewList)
      }

      // 判断页面高亮
      const isActive = (path: string) => {
        return path === state.routePath
      }

      // 当前的 tagsView 项点击时
      const onTagsClick = (v: any, k: number) => {
        state.routePath = v.path
        state.tagsRefsIndex = k
        router.push(v)
      }
      // 更新滚动条显示
      const updateScrollbar = () => {
        proxy.$refs.scrollbarRef.update()
      }
      // 鼠标滚轮滚动
      const onHandleScroll = (e: any) => {
        proxy.$refs.scrollbarRef.$refs.wrap.scrollLeft += e.wheelDelta / 4
      }
      // tagsView 横向滚动
      const tagsViewmoveToCurrentTag = () => {
        nextTick(() => {
          if (tagsRefs.value.length <= 0) return false
          // 当前 li 元素
          let liDom = tagsRefs.value[state.tagsRefsIndex]
          // 当前 li 元素下标
          let liIndex = state.tagsRefsIndex
          // 当前 ul 下 li 元素总长度
          let liLength = tagsRefs.value.length
          // 最前 li
          let liFirst: any = tagsRefs.value[0]
          // 最后 li
          let liLast: any = tagsRefs.value[tagsRefs.value.length - 1]
          // 当前滚动条的值
          let scrollRefs = proxy.$refs.scrollbarRef.$refs.wrap
          // 当前滚动条滚动宽度
          let scrollS = scrollRefs.scrollWidth
          // 当前滚动条偏移宽度
          let offsetW = scrollRefs.offsetWidth
          // 当前滚动条偏移距离
          let scrollL = scrollRefs.scrollLeft
          // 上一个 tags li dom
          let liPrevTag: any = tagsRefs.value[state.tagsRefsIndex - 1]
          // 下一个 tags li dom
          let liNextTag: any = tagsRefs.value[state.tagsRefsIndex + 1]
          // 上一个 tags li dom 的偏移距离
          let beforePrevL: any = ''
          // 下一个 tags li dom 的偏移距离
          let afterNextL: any = ''
          if (liDom === liFirst) {
            // 头部
            scrollRefs.scrollLeft = 0
          } else if (liDom === liLast) {
            // 尾部
            scrollRefs.scrollLeft = scrollS - offsetW
          } else {
            // 非头/尾部
            if (liIndex === 0) beforePrevL = liFirst.offsetLeft - 5
            else beforePrevL = liPrevTag?.offsetLeft - 5
            if (liIndex === liLength)
              afterNextL = liLast.offsetLeft + liLast.offsetWidth + 5
            else afterNextL = liNextTag.offsetLeft + liNextTag.offsetWidth + 5
            if (afterNextL > scrollL + offsetW) {
              scrollRefs.scrollLeft = afterNextL - offsetW
            } else if (beforePrevL < scrollL) {
              scrollRefs.scrollLeft = beforePrevL
            }
          }
          // 更新滚动条,防止不出现
          updateScrollbar()
        })
      }
      // 获取 tagsView 的下标:用于处理 tagsView 点击时的横向滚动
      const getTagsRefsIndex = (path: string) => {
        if (state.tagsViewList.length > 0) {
          state.tagsRefsIndex = state.tagsViewList.findIndex(
            (item: any) => item.path === path
          )
        }
      }

      // 监听路由的变化,动态赋值给 tagsView
      watch(store.state, (val) => {
        if (
          val.tagsViewRoutes.tagsViewRoutes.length ===
          state.tagsViewRoutesList.length
        )
          return false
        getTagsViewRoutes()
      })
      // 页面加载前
      onBeforeMount(() => {})
      // 页面卸载时
      onUnmounted(() => {})
      // 页面更新时
      onBeforeUpdate(() => {
        tagsRefs.value = []
      })
      // 页面加载时
      onMounted(() => {
        // 初始化 vuex 中的 tagsViewRoutes 列表
        getTagsViewRoutes()
      })
      // 路由更新时
      onBeforeRouteUpdate((to) => {
        state.routePath = to.path
        addTagsView(to.path, to)
        getTagsRefsIndex(to.path)
        tagsViewmoveToCurrentTag()
      })
      return {
        isActive,
        getTagsViewRoutes,
        onTagsClick,
        tagsRefs,
        scrollbarRef,
        tagsUlRef,
        onHandleScroll,
        getThemeConfig,
        closeCurrentTagsView,
        ...toRefs(state)
      }
    }
  }
</script>

<style scoped lang="scss">
  .layout-navbars-tagsview {
    flex: 1;
    background-color: #ffffff;
    border-bottom: 1px solid #f1f2f3;
    ::v-deep(.el-scrollbar__wrap) {
      overflow-x: auto !important;
    }
    &-ul {
      list-style: none;
      margin: 0;
      padding: 0;
      height: 34px;
      display: flex;
      align-items: center;
      color: #606266;
      font-size: 12px;
      white-space: nowrap;
      padding: 0 15px;
      &-li {
        height: 26px;
        line-height: 26px;
        display: flex;
        align-items: center;
        border: 1px solid #e6e6e6;
        padding: 0 15px;
        margin-right: 5px;
        border-radius: 2px;
        position: relative;
        z-index: 0;
        cursor: pointer;
        justify-content: space-between;
        &:hover {
          background-color: var(--color-primary-light-9);
          color: var(--color-primary);
          border-color: var(--color-primary-light-6);
        }
        &-iconfont {
          position: relative;
          left: -5px;
          font-size: 12px;
        }
        &-icon {
          border-radius: 100%;
          position: relative;
          height: 14px;
          width: 14px;
          text-align: center;
          line-height: 14px;
          right: -5px;
          &:hover {
            color: #fff;
            background-color: var(--color-primary-light-3);
          }
        }
        .layout-icon-active {
          display: block;
        }
        .layout-icon-three {
          display: none;
        }
      }
      .is-active {
        color: #ffffff;
        background: var(--color-primary);
        border-color: var(--color-primary);
      }
    }
    // 风格2
    .tags-style-two {
      .layout-navbars-tagsview-ul-li {
        height: 34px !important;
        line-height: 34px !important;
        border: none !important;
        .layout-navbars-tagsview-ul-li-iconfont {
          display: none;
        }
        .layout-icon-active {
          display: none;
        }
        .layout-icon-three {
          display: block;
        }
      }
      .is-active {
        background: none !important;
        color: var(--color-primary) !important;
        border-bottom: 2px solid !important;
        border-color: var(--color-primary) !important;
        border-radius: 0 !important;
      }
    }
    // 风格3
    .tags-style-three {
      .layout-navbars-tagsview-ul-li {
        height: 34px !important;
        line-height: 34px !important;
        border-right: 1px solid #f6f6f6 !important;
        border-top: none !important;
        border-bottom: none !important;
        border-left: none !important;
        border-radius: 0 !important;
        margin-right: 0 !important;
        &:first-of-type {
          border-left: 1px solid #f6f6f6 !important;
        }
        .layout-icon-active {
          display: none;
        }
        .layout-icon-three {
          display: block;
        }
      }
      .is-active {
        background: white !important;
        color: var(--color-primary) !important;
        border-top: 1px solid !important;
        border-top-color: var(--color-primary) !important;
      }
    }
    // 风格4
    .tags-style-four {
      .layout-navbars-tagsview-ul-li {
        margin-right: 0 !important;
        border: none !important;
        position: relative;
        border-radius: 3px !important;
        .layout-icon-active {
          display: none;
        }
        .layout-icon-three {
          display: block;
        }
        &:hover {
          background: none !important;
        }
      }
      .is-active {
        background: none !important;
        color: var(--color-primary) !important;
      }
    }
  }
  .layout-navbars-tagsview-shadow {
    box-shadow: rgb(0 21 41 / 4%) 0px 1px 4px;
  }
</style>

复制代码

添加成功 breadcrumb 和 tagsView 的效果如下图:

image.png

最后

到目前为止,后台管理系统路由,侧边栏菜单,顶部面包屑和导航标签 基础基本完成。

这里非常感谢大家的点赞?。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享