【vue】vue3学习笔记(三)

接上篇

面包屑

  • 安装path-to-regexp
  • component/breadcrumb/index
<template>
  <el-breadcrumb class="app-breadcrumb breadcrumb-container" separator="/">
    <el-breadcrumb-item v-for="(item, index) in levelList" :key="item.path">
      <span v-if="index == levelList.length - 1" class="no-redirect">
        {
   
   { item.meta.title }}
      </span>
      <a v-else @click.prevent="handleLink(item)">{
   
   { item.meta.title }}</a>
    </el-breadcrumb-item>
  </el-breadcrumb>
</template>

<script lang="ts">
import {
      
       defineComponent, ref, watch, onBeforeMount } from 'vue'
import {
      
      
  useRoute,
  useRouter,
  RouteLocationMatched,
  RouteLocationRaw
} from 'vue-router'
import {
      
       compile } from 'path-to-regexp'

type PartialRouteLocationMatched = Partial<RouteLocationMatched>

export default defineComponent({
      
      
  name: 'Breadcrumb',
  setup() {
      
      
    const route = useRoute() // 相当于this.$route对象
    const router = useRouter() // 相当于this.$router对象
    const levelList = ref<Array<PartialRouteLocationMatched>>([]) // 导航列表 存放matched里筛选的路由记录

    // 判断是不是Dashboard路由
    const isDashboard = (route?: PartialRouteLocationMatched) => {
      
      
      const name = route && route.name
      if (!name) {
      
      
        return false
      }
      return (
        (name as string).trim().toLocaleLowerCase() ===
        'Dashboard'.toLocaleLowerCase()
      )
    }

    // 获取面包屑导航
    const getBreadcrumb = () => {
      
      
      // 对匹配的路由进行过滤 过滤掉没有title属性的路由,没有title就无法作为面包屑导航
      let matched = route.matched.filter(
        item => item.meta && item.meta.title
      ) as PartialRouteLocationMatched[]
      // 获取第一个匹配路由记录
      const first = matched[0]
      // 我们要把dashboard作为首页 始终固定在面包屑导航第一个 Dashboard/System/Menu Management
      // 如果第一个匹配到的路由记录不是dashboard 我们自己就把它放在记录数组的第一项
      if (!isDashboard(first)) {
      
      
        matched = ([
          {
      
      
            path: '/dashboard',
            meta: {
      
      
              title: 'Dashboard'
            }
          }
        ] as PartialRouteLocationMatched[]).concat(matched)
      }

      levelList.value = matched.filter(
        item => item.meta && item.meta.title && item.meta.breadcrumb !== false
      )
    }

    onBeforeMount(() => {
      
      
      getBreadcrumb()
    })

    watch(
      () => route.path,
      () => {
      
      
        getBreadcrumb()
      }
    )

    // 主要是针对 动态路由 /user/:id 进行动态参数填充
    // path-to-regexp 文档说明 https://www.npmjs.com/package/path-to-regexp
    const pathCompile = (path: string) => {
      
      
      // 根据路径变编译成正则函数 并接收具体参数 比如根据正则/user/:id 帮你将:id替换成具体路径
      const toPath = compile(path) // 比如 path /user/:id
      const params = route.params // { id: 10 }
      return toPath(params) // toPath({ id: 10 }) => /user/10 返回填充后的路径
    }

    // 点击面包屑导航可跳转
    const handleLink = (route: RouteLocationMatched) => {
      
      
      const {
      
       path, redirect } = route
      // 如果是重定向路由 就走重定向路径
      if (redirect) {
      
      
        router.push(redirect as RouteLocationRaw)
        return
      }
      router.push(pathCompile(path))
    }
    return {
      
      
      levelList,
      handleLink
    }
  }
})
</script>

<style lang="scss" scoped>
.app-breadcrumb.el-breadcrumb {
      
      
  display: inline-block;
  /* float: left; */
  line-height: 50px;
  font-size: 14px;
  margin-left: 8px;
}

.no-redirect {
      
      
  color: #97a8be;
  cursor: text;
}
</style>

<style lang="scss">
.breadcrumb-enter-active,
.breadcrumb-leave-active {
      
      
  transition: all 0.5s;
}

.breadcrumb-enter,
.breadcrumb-leave-active {
      
      
  opacity: 0;
  transform: translateX(20px);
}

.breadcrumb-leave-active {
      
      
  position: absolute;
}

.breadcrumb-move {
      
      
  transition: all 0.5s;
}
</style>
  • hambuger就是标签左边的那个图标,点击按钮可以开启关闭左侧菜单。
  • component/hambuger/index
<template>
  <div class="hamburger-container" style="padding: 0 15px" @click="toggleClick">
    <svg
      :class="{ 'is-active': isActive }"
      class="hamburger"
      viewBox="0 0 1024 1024"
      xmlns="http://www.w3.org/2000/svg"
      width="64"
      height="64"
    >
      <path
        d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z"
      />
    </svg>
  </div>
</template>

<script lang="ts">
import {
      
       defineComponent } from 'vue'
export default defineComponent({
      
      
  name: 'Hambuger',
  props: {
      
      
    isActive: {
      
      
      type: Boolean,
      default: false
    }
  },
  emits: ['toggleClick'], // vue3 emits声明列表
  setup(props, {
       
        emit }) {
      
      
    const toggleClick = () => {
      
      
      emit('toggleClick')
    }

    return {
      
      
      toggleClick
    }
  }
})
</script>

<style lang="scss" scoped>
.hamburger-container {
      
      
  line-height: 46px;
  height: 100%;
  float: left;
  cursor: pointer;
  transition: background 0.3s;
  -webkit-tap-highlight-color: transparent;
  &:hover {
      
      
    background: rgba(0, 0, 0, 0.025);
  }
}
.hamburger {
      
      
  display: inline-block;
  vertical-align: middle;
  width: 20px;
  height: 20px;
}

.hamburger.is-active {
      
      
  transform: rotate(180deg);
}
</style>

  • navbar则是将hambuger和面包屑包入。开合状态会存放于store中。、
<template>
  <div class="navbar">
    <hambuger @toggleClick="toggleSidebar" :is-active="sidebar.opened" />
    <breadcrumb />
  </div>
</template>

<script lang="ts">
import {
    
     defineComponent, computed } from 'vue'
import Breadcrumb from '@/components/Breadcrumb/index.vue'
import Hambuger from '@/components/Hambuger/index.vue'
import {
    
     useStore } from '@/store/index'

export default defineComponent({
    
    
  name: 'Navbar',
  components: {
    
    
    Breadcrumb,
    Hambuger
  },
  setup() {
    
    
    const store = useStore()
    const toggleSidebar = () => {
    
    
      store.dispatch('app/toggleSidebar')
    }
    // 从getters中获取sidebar
    const sidebar = computed(() => store.getters.sidebar)

    return {
    
    
      toggleSidebar,
      sidebar
    }
  }
})
</script>

  • layout中导入即可:
  <div class="navbar">
          <navbar></navbar>
        </div>
  • 下面制作vuex,vuex可以通过key来加密:
app
  .use(store, key)
  .use(router)
  .use(installElementPlus)
  .use(initSvgIcon)
  .mount('#app')

  • 在store/index中加上key,每个组件引用的是下面那个useStore而不是vuex的useStore,这样会有提示比较好。
  • 另外需要安装这个持久化插件vuex-persistedstate,设置存储位置,键名,以及白名单。
import {
    
     InjectionKey } from 'vue'
import {
    
     createStore, Store, useStore as baseUseStore } from 'vuex'
import createPersistedState from 'vuex-persistedstate'
import app, {
    
     IAppState } from '@/store/modules/app'
import getters from './getters'

export interface IRootState {
    
    
  app: IAppState
}

// 通过下面方式使用 TypeScript 定义 store 能正确地为 store 提供类型声明。
// https://next.vuex.vuejs.org/guide/typescript-support.html#simplifying-usestore-usage
// eslint-disable-next-line symbol-description
export const key: InjectionKey<Store<IRootState>> = Symbol()

// vuex store持久化 默认使用localstorage持久化
const persisteAppState = createPersistedState({
    
    
  storage: window.sessionStorage, // 指定storage 也可自定义
  key: 'vuex_app', // 存储名 默认都是vuex 多个模块需要指定 否则会覆盖
  // paths: ['app'] // 针对app这个模块持久化
  // 只针对app模块下sidebar.opened状态持久化
  paths: ['app.sidebar.opened'] // 通过点连接符指定state路径
})

export default createStore<IRootState>({
    
    
  plugins: [persisteAppState],
  getters,
  modules: {
    
    
    app
  }
})
// eslint-disable-next-line
export function useStore() {
    
    
  return baseUseStore(key)
}
  • getter中获取:
import {
    
     GetterTree } from 'vuex'
import {
    
     IRootState } from './index'

const getters: GetterTree<IRootState, IRootState> = {
    
    
  sidebar: state => state.app.sidebar
}

export default getters
  • store/module/app.ts
import {
    
     ActionTree, Module, MutationTree } from 'vuex'
import {
    
     IRootState } from '../index' // 全局状态 root state 从src/store/index.ts里定义导出

export interface IAppState {
    
    
  sidebar: {
    
    
    opened: boolean // 菜单导航展开时true 收缩时false
  }
}

const mutations: MutationTree<IAppState> = {
    
    
  TOGGLE_SIDEBAR(state) {
    
    
    state.sidebar.opened = !state.sidebar.opened
  }
}

const actions: ActionTree<IAppState, IRootState> = {
    
    
  toggleSidebar({
    
     commit }) {
    
    
    commit('TOGGLE_SIDEBAR')
  }
}

const app: Module<IAppState, IRootState> = {
    
    
  namespaced: true,
  state: {
    
    
    sidebar: {
    
    
      opened: true // 菜单导航展开时true 收缩时false
    }
  },
  mutations,
  actions
}

export default app

  • 这样面包屑导航就完成了,剩下的下次写。

猜你喜欢

转载自blog.csdn.net/yehuozhili/article/details/117457578