欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

【vue】vue3学习笔记(三)

程序员文章站 2022-05-17 20:40:35
...

接上篇

面包屑

  • 安装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

  • 这样面包屑导航就完成了,剩下的下次写。
相关标签: VUE