【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
- 这样面包屑导航就完成了,剩下的下次写。
上一篇: vue3(1)学习
下一篇: 【vue】vue3学习笔记(二)