You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 09:15:46