Vue.js中如何实现刷新后保持多层b-collapse折叠菜单展开状态
Vue+BootstrapVue 多层折叠菜单刷新保持展开状态方案
核心逻辑
- 用浏览器本地存储持久化保存所有处于展开状态的菜单项唯一标识
- 统一管理所有层级折叠组件的展开状态,状态变更时实时同步到存储
- 页面加载、菜单数据渲染完成后,读取存储的展开标识,自动恢复对应菜单的展开状态
- 完全基于BootstrapVue原生能力实现,不需要额外操作DOM
具体实现步骤
1. 新增状态读写方法
在组件的methods中添加本地存储读写方法,同时改造原有closeAll方法,关闭全部菜单时同步清空存储:
methods: { // 读取已存储的展开菜单ID getExpandedMenuIds() { const stored = localStorage.getItem('expandedMenuIds') return stored ? JSON.parse(stored) : [] }, // 保存当前展开菜单ID到存储,自动去重 saveExpandedMenuIds(ids) { localStorage.setItem('expandedMenuIds', JSON.stringify([...new Set(ids)])) }, closeAll() { localStorage.removeItem('expandedMenuIds') this.expandedIds = [] // 原有其他关闭逻辑保留 } // 其他原有方法保留 }
2. 初始化展开状态
在组件data中新增全局展开状态数组,在菜单数据加载完成后读取存储值完成初始化:
data() { return { // 原有data字段保留,比如mainMenu等 expandedIds: [] } }, async mounted() { // 如果菜单数据是接口异步获取的,等接口返回、mainMenu赋值完成后再执行下一行 // await fetchMenuData() this.expandedIds = this.getExpandedMenuIds() }
3. 改造模板,统一绑定折叠状态
你原有代码存在两个问题:一是只有一级折叠绑定了状态,二是折叠组件的ID和触发按钮的target不匹配(b-collapse的ID应该用当前项的唯一标识,你原来绑的是子项的父ID,会导致匹配错误)。
改造后所有层级的b-collapse统一绑定expandedIds数组,每个折叠项用自身唯一标识(推荐用后端返回的菜单ID,没有的话用post_name,保证全局唯一即可)作为value,同时监听展开/收起事件同步存储:
<div v-for="(main, index) in mainMenu" :key="main.post_name || index"> <template v-if="main.title === 'News'"> <b-link @click="closeAll" v-if="main.type === 'post_type' " :to="'/' + main.slug" v-b-toggle="'collapse'" class="m-1 nobutn" v-html="main.title"></b-link> </template> <template v-else> <b-link v-if="main.type === 'post_type' " :to="'/' + main.slug" v-b-toggle="main.post_name" class="m-1 nobutn" v-html="main.title"></b-link> <b-link v-else :to="'/' + main.url" v-b-toggle="main.post_name" class="m-1 nobutn" v-html="main.title"></b-link> </template> <!-- 一级子菜单折叠 --> <b-collapse v-model="expandedIds" :value="sub.post_name" v-for="(sub, index) in main.child_items" :key="sub.post_name || index" :id="sub.post_name" accordion="my-accordion" role="tabpanel" @shown="saveExpandedMenuIds(expandedIds)" @hidden="saveExpandedMenuIds(expandedIds)" > <template v-if="sub.type === 'post_type'"> <b-link class="secondLayer" :to="'/' + main.slug + '/' + sub.slug" :class="sub.classes"> <b-card v-b-toggle="sub.post_name" class="subTitle" v-html="sub.title"></b-card> </b-link> </template> <template v-else> <b-link :href="sub.url" target="_blank" :class="sub.classes"> <b-card v-b-toggle="sub.post_name" class="subTitle" v-html="sub.title"></b-card> </b-link> </template> <!-- 二级子菜单折叠 --> <b-collapse v-model="expandedIds" :value="subchild.post_name" v-for="(subchild, index) in sub.child_items" :key="subchild.post_name || index" :id="subchild.post_name" accordion="my-accordiontwo" role="tabpanel" @shown="saveExpandedMenuIds(expandedIds)" @hidden="saveExpandedMenuIds(expandedIds)" > <template v-if="subchild.title === 'Members Portal' "> <b-link class="thirdLayer" :href="subchild.url" target="_blank" :class="subchild.classes"> <b-card v-b-toggle="subchild.post_name" class="subchildTitle" v-html="subchild.title"></b-card> </b-link> </template> <template v-else> <b-link class="thirdLayer" :to="'/' + main.slug + '/' + sub.slug + '/' + subchild.slug" :class="subchild.classes"> <b-card v-b-toggle="subchild.post_name" class="subchildTitle" v-html="subchild.title"></b-card> </b-link> </template> <!-- 三级子菜单折叠 --> <b-collapse v-model="expandedIds" :value="fourthsubchild.post_name" v-for="(fourthsubchild, index) in subchild.child_items" :key="fourthsubchild.post_name || index" :id="fourthsubchild.post_name" @shown="saveExpandedMenuIds(expandedIds)" @hidden="saveExpandedMenuIds(expandedIds)" > <b-link class="fouthLayer" :to="'/' + main.slug + '/' + sub.slug + '/' + subchild.slug + '/' + fourthsubchild.slug" :class="fourthsubchild.classes"> <b-card class="fourthchildTitle" v-html="fourthsubchild.title"></b-card> </b-link> </b-collapse> </b-collapse> </b-collapse> </div>
优化提示
- 如果不需要永久保存展开状态,关闭浏览器就重置,把代码里的
localStorage换成sessionStorage即可 - 如果需要同层级菜单可以同时展开,直接删掉对应
b-collapse上的accordion属性就行 - 可以加路由监听,路由跳转时自动匹配当前页面对应的菜单项ID,加入
expandedIds后保存,这样不管是刷新还是直接输入路由地址,都能自动展开对应菜单层级
内容的提问来源于stack exchange,提问作者matt1993
相关产品推荐
相关产品推荐

