本篇通过 Pinia 实现侧边栏(Sidebar)的展开收起功能,并通过 Pinia 实现展开状态的持久化。
1 安装 Pinia Persistedstate
Pinia 是 Vue.js 的状态管理库,而 pinia-plugin-persistedstate 是一个针对 Pinia 的插件,它能让 Pinia 管理的状态实现持久化存储。在前端开发中,持久化状态意味着即使页面刷新或用户关闭浏览器重新打开,某些关键状态依然能保持不变,极大提升用户体验。比如侧边栏的展开或收起状态,用户设置后希望再次访问页面时依然保持之前的设置。使用以下命令进行安装:
pnpm install pinia-plugin-persistedstate
2 在 main.ts 中使用
在 main.ts 中引入持久化插件 pinia-plugin-persistedstate,通过pinia.use(piniaPluginPersistedstate) 这行代码,将该插件应用到 Pinia 实例中。这样,后续定义的 Store 中的状态,只要按照插件规则配置,就能实现持久化存储。代码如下:
//main.ts
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
import "normalize.css/normalize.css";
import { createPinia } from "pinia";
import element from "./plugins/element";
import piniaPluginPersistedstate from "pinia-plugin-persistedstate";
// import ElementPlus from "element-plus";
// import "element-plus/dist/index.css";
import "@/style/index.scss";
import "uno.css";
const app = createApp(App);
const pinia = createPinia();
pinia.use(piniaPluginPersistedstate); // 安装持久化插件
app.use(router);
app.use(pinia);
app.use(element);
// app.use(ElementPlus);
app.mount("#app");
3 配置 Pinia 和持久化插件
在 src 目录下创建 stores 文件夹,并在其中创建 app.ts 文件,代码如下:
//src/stores/app.ts
// 使用defineStore定义名为'app'的store
export const useAppStore = defineStore(
"app",
() => {
//定义响应式状态
const state = reactive({
sidebar: {
opened: true
}
// ...
// theme
});
//计算属性,方便获取sidebar状态
const sidebar = computed(() => state.sidebar);
//切换侧边栏展开状态的函数
const toggleSidebar = () => {
state.sidebar.opened = !state.sidebar.opened;
};
return { state, sidebar, toggleSidebar };
},
{
//持久化配置
persist: {
storage: window.localStorage,//使用window.localStorage存储状态
pick: ["state.sidebar"]//只持久化state.sidebar这个属性
}
}
);
注:state 必须导出,否则无法使用
在 Store 的定义中,通过 persist 选项来配置持久化相关参数。storage 指定了存储方式,这里使用window.localStorage,意味着状态会存储在浏览器的本地存储中。pick 数组指定了要持久化的具体状态属性,这里只选择了state.sidebar,即只对侧边栏的展开状态进行持久化。如果有多个状态需要持久化,可以在pick数组中添加更多属性路径。
4 创建 Hamburger 组件
在 src/components 下创建 Hamburger 文件夹,其下创建 index.vue 文件,代码如下:
//src/components/Hamburger/index.vue
<template>
<div class="hamburger-container">
<!-- svg-icon组件,用于显示菜单图标,根据collapse状态添加旋转类名 -->
<svg-icon
icon-name="ant-design:bars-outlined"
custom-class="hamburger"
:class="{ 'rotate-180': collapse }"
@click="handleClick"
></svg-icon>
</div>
</template>
<style scoped lang="scss">
.hamburger-container {
@apply leading-[50px] float-left cursor-pointer px-10px hover:(bg-black/5);
}
.hamburger {
@apply w-30px h-30px transition-transform duration-300;
}
</style>
<script lang="ts" setup>
// 定义组件props,接收collapse状态
const { collapse } = defineProps({
collapse: {
type: Boolean,
default: false
}
});
// 定义组件事件emit,用于触发toggleCollapse事件
const emit = defineEmits<{ (e: "toggleCollapse"): void }>();
// 点击处理函数,触发toggleCollapse事件
const handleClick = () => {
emit("toggleCollapse");
};
</script>
5 创建 Navbar 组件
在 src/layout/components 文件夹下创建 Navbar.vue,代码如下:
//src/layout/components/Navbar.vue
<template>
<div class="navbar" flex>
<!-- hamburger组件,绑定toggleCollapse事件和collapse状态 -->
<hamburger
@toggleCollapse="toggleSidebar"
:collapse="sidebar.opened"
></hamburger>
</div>
</template>
<style scoped lang="scss">
.navbar {
@apply h-[var(--navbar-height)];
}
</style>
<script lang="ts" setup>
import { useAppStore } from "@/stores/app";
// 获取app store中的toggleSidebar和sidebar状态
// 在解构的时候要考虑值是不是对象,如果非对象解构出来就 丧失响应式了
const { toggleSidebar, sidebar } = useAppStore();
</script>
6 修改 Sidebar 组件
修改 src/layout/components/Sidebar/index.vue,使用 pinia 存储的值,代码如下:
//src/layout/components/Sidebar/index.vue
<template>
<el-menu
router
class="sidebar-container-menu"
:default-active="defaultActive"
:background-color="variables.menuBg"
:text-color="variables.menuText"
:active-text-color="variables.menuActiveText"
:collapse="sidebar.opened"
>
<el-menu-item index="/dashboard">
<el-icon><setting /></el-icon>
<template #title>Navigator</template>
</el-menu-item>
</el-menu>
</template>
<script lang="ts" setup>
import { useAppStore } from "@/stores/app";
import variables from "@/style/variables.module.scss";
const route = useRoute();
const { sidebar } = useAppStore();
const defaultActive = computed(() => {
return route.path;
});
</script>
<style scoped></style>
7 修改 layout
修改 layout/index.vue,代码如下:
//src/layout/index.vue
<template>
<div class="app-wrapper">
<div class="sidebar-container">
<sidebar></sidebar>
</div>
<div class="main-container">
<div class="header">
<!-- 上边包含收缩的导航条 -->
<navbar></navbar>
</div>
<div class="app-main">
<router-view></router-view>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.app-wrapper {
@apply flex w-full h-full;
.sidebar-container {
// 跨组件设置样式
@apply bg-[var(--menu-bg)];
:deep(.sidebar-container-menu:not(.el-menu--collapse)) {
@apply w-[var(--sidebar-width)];
}
}
.main-container {
@apply flex flex-col flex-1;
}
.header {
@apply h-84px;
.navbar {
@apply h-[var(--navbar-height)] bg-yellow;
}
.tags-view {
@apply h-[var(--tagsview-height)] bg-blue;
}
}
.app-main {
@apply bg-cyan;
min-height: calc(100vh - var(--tagsview-height) - var(--navbar-height));
}
}
</style>
至此,就实现了侧边栏的展开收起及展开收起状态的持久化,页面效果如下:
下一篇将继续探讨菜单组件,敬请期待~