vue管理系统,多标签显示,含右键刷新、关闭

背景

  • vue2.0 + antd-vue 组合,采用传统的single-page模式,通过点击左侧menu切换路由、加载右侧页面。现在有个需求,需要将之前点击菜单打开的页面以tab的形式保留,在此基础上,需要对打开的页面做数据的缓存。

实现效果

思路

  • 点击menu触发事件,往tablist插入当前菜单信息,遍历tablist然后使用keep-alive去缓存住打开的页面。

实现代码

  1. menu.vue
  • 这个组件实现的功能是,通过点击菜单切换路由,并且往tablist插入数据(后面会用到);记录下当前选中的菜单项,这个主要是因为通过去切换路由需要联动到更新menu的选中项,如果没有手动设置不会自动更新。
  • PS:配置menu的selectedKeys是为了通过点击标签的时候,切换路由,并手动更新菜单的选中项。*
js 复制代码
       <a-menu
          mode="inline"
          :selectedKeys="this.$store.state.tabs.currentMenu"
          @click="handleClick"
        >
          <a-menu-item
            v-for="item in this.$store.state.tabs.tree[0].children"
            :key="item.url"
          >
            <router-link :to="item.url">
              {{ item.name }}
            </router-link>
          </a-menu-item>
        </a-menu>
    
    handleClick(e) {
      this.$store.commit("tabs/changeCurrentMenu", e.keyPath);
      this.$store.commit("tabs/addPage", e.key);
    },
  1. tabList.vue
  • 这个组件做的事情比较多,首先要根据tabList来模拟一个选项卡出来,对于用户来说每个tab就是一个页面,但实际我们需要在这块分为tab和view两个部分,tab用来显示菜单名称,view才是对应的component。*
  • 另外,由于已经出现在tabList的组件是默认要被缓存的,所以直接就用keep-alive来包裹住router-view就可以实现。*
  • keep-alive的include,一定要和组件的name是一致的!!!不然会找不到对应组件!!!*
  • PS:为什么要模拟tabs而不用真的tabs组件呢,其实我第一次尝试实现的方式就是tabs组件,但在测试过程中发现,如果我已经打开了ABC三个组件,那么tabs的每次加载就会遍历一轮我的tabList数据,这样会导致我的页面被重复渲染了三次,这个方案会在后期整理好代码记录下。*
js 复制代码
      <a-layout-header>
          <div v-if="$store.state.tabs.pageNames.length">
            <span
              class="singleTab"
              v-for="pageName in $store.state.tabs.pageNames"
              :key="pageName"
            >
                <span>
                  <router-link :to="pageName">
                    <span @click="selectCurrentTab(pageName)">{{
                      pageName
                    }}</span>
                  </router-link>
                  <span
                    ><a-icon
                      type="close"
                      @click="handleRemovePage(pageName)"
                    ></a-icon></span
                ></span>   
            </span>
          </div>
        </a-layout-header>

        <a-layout-content>
          <!-- 页面内容展示区域 -->
          <keep-alive :include="$store.state.tabs.pageNames">
            <router-view></router-view>
          </keep-alive>
        </a-layout-content>
    
    //手动更新menu的选中项高亮
    selectCurrentTab(tab) {
      this.$store.commit("tabs/changeCurrentMenu", [tab]);
    },
    //这个方法写的挺笨的,先实现吧以后再优化。
    //一般情况只要删除当前tab会OK,但如果同时打开ABC,当前高亮的菜单是C,但用户直接删除B,那在删除B后把菜单的高亮选中项设置为tabList的最后一个,并且url的路由信息也要同时更新对应的route;如果删除唯一打开的tab,那置空设置会清楚view留存的信息,避免出现用户操作关闭页面但显示的页面没有清空的场景。
    handleRemovePage(pageName) {
      this.$store.commit("tabs/removePage", pageName);

      this.$store.commit(
        "tabs/changeCurrentMenu",
        [
          this.$store.state.tabs.pageNames[
            this.$store.state.tabs.pageNames.length - 1
          ],
        ] || []
      );

      this.$router.push(
        this.$store.state.tabs.pageNames[
          this.$store.state.tabs.pageNames.length - 1
        ] || "/home"
      );
    },
  • 接下来,要添加右键菜单了,常见的就是刷新、关闭其他、关闭所有。这些其实也是对tabList的操作,主要记录下刷新这个方法吧。*
  • 刷新:ABC同时打开的状态下,刷新B组件,清除页面被缓存的数据、刷新后再次渲染的组件在选项卡里的位置不要变。*
js 复制代码
        <a-dropdown placements="bottomLeft" :trigger="['contextmenu']">
                <span>
                  <router-link :to="pageName">
                    <span @click="selectCurrentTab(pageName)">{{
                      pageName
                    }}</span>
                  </router-link>
                  <span
                    ><a-icon
                      type="close"
                      @click="handleRemovePage(pageName)"
                    ></a-icon></span
                ></span>

                <a-menu slot="overlay">
                  <a-menu-item key="0" @click="refreshPage(pageName)">
                    刷新
                  </a-menu-item>
                  <a-menu-item key="1" @click="handleRemovePage(pageName)">
                    关闭当前
                  </a-menu-item>
                  <a-menu-item key="1" @click="closeOther(pageName)">
                    关闭其他
                  </a-menu-item>
                  <a-menu-item key="2" @click="closeAll"> 关闭所有 </a-menu-item>
                </a-menu>
              </a-dropdown>
    //   刷新
    refreshPage(pageName) {
      this.handleRemovePage(pageName);

      this.$nextTick(() => {
        this.$store.commit("tabs/refreshPage", pageName);
        this.$store.commit("tabs/changeCurrentMenu", [pageName]);
        this.$router.push(pageName);
      });
    },
    // 关闭其他
    closeOther(pageName) {
      this.$store.commit("tabs/closeOtherPages", pageName);
      this.$store.commit(
        "tabs/changeCurrentMenu",
        [
          this.$store.state.tabs.pageNames[
            this.$store.state.tabs.pageNames.length - 1
          ],
        ] || []
      );

      this.$router.push(
        this.$store.state.tabs.pageNames[
          this.$store.state.tabs.pageNames.length - 1
        ] || "/home"
      );
    },
    // 关闭所有
    closeAll() {
      this.$store.commit("tabs/closeAllPages");
      this.$router.replace("/");
    },
  1. store.js
js 复制代码
    const tree = [{
    path: '',
    name: 'demo-menu',
    id: '1',
    children: [{
            id: 'a',
            path: 'a',
            name: 'homeA',
            component: 'sonOne',
            url: '/home/a',
        },
        {
            path: 'b',
            name: 'homeB',
            component: 'sonTwo',
            id: '202',
            url: '/home/b'
        },
        {
            path: 'c',
            name: 'homeC',
            component: 'sonThree',
            id: '33',
            url: '/home/c'
        }
    ]
}, ]

export default new Vuex.Store({
    modules: {
        tabs: {
            namespaced: true,
            state: {
                tree,
                currentMenu: [], //当前菜单
                pageNames: [], // 选项卡的页面集合
                refreshPageIndex: 0, //手动刷新的选项卡
            },
            mutations: {
                changeCurrentMenu(state, newValue) {
                    state.currentMenu = newValue
                },
                // 点击菜单添加选项卡
                addPage(state, newPageName) {
                    if (!state.pageNames.includes(newPageName)) {
                        state.pageNames.push(newPageName)
                    }

                },
                // 删除选项卡
                removePage(state, pageName) {
                    const index = state.pageNames.indexOf(pageName)
                    if (index >= 0) {
                        state.pageNames.splice(index, 1)
     //记录删除的tab位置是为了在刷新的时候,我是先删除然后再添加,但是为了保证位置不能改变,所以会在原位置上插入
                        state.refreshPageIndex = index
                    }
                },
                // 刷新单个组件
                refreshPage(state, pageName) {
                    state.pageNames.splice(state.refreshPageIndex, 0, pageName)
                },
                // 关闭其他组件
                closeOtherPages(state, pageName) {
                    state.pageNames = state.pageNames.filter(v => v == pageName)
                },
                // 关闭所有组件
                closeAllPages(state) {
                    state.pageNames = state.currentMenu = []
                },
            }
        }
    }
})

总结

  • 亲测亲测,虽然写的蠢,但是确实可以满足需求。先记录下,如果有好的想法再来优化吧。*
相关推荐
QGC二次开发1 小时前
Vue3:mitt实现组件通信
前端·javascript·vue.js·vue
customer083 小时前
【开源免费】基于SpringBoot+Vue.JS服装商城系统(JAVA毕业设计)
java·vue.js·spring boot·后端·开源
等你许久_孟然3 小时前
【webpack4系列】设计可维护的webpack4.x+vue构建配置(终极篇)
前端·javascript·vue.js
知否技术4 小时前
vue实现动态Tab标签页功能,10分钟拿下!
前端·vue.js
沈健_算法小生5 小时前
Vue3实现类ChatGPT聊天式流式输出(vue-sse实现)
前端·vue.js·chatgpt
The_massif6 小时前
Flip动画+拖拽API实现一个拖拽排序公共组件
前端·vue.js·react.js
计算机学姐6 小时前
基于微信小程序的美食外卖管理系统
java·vue.js·spring boot·微信小程序·小程序·mybatis·美食
程序员大金6 小时前
基于SpringBoot+Vue+MySQL的特色旅游网站系统
java·前端·vue.js·spring boot·后端·mysql·tomcat
陈住气^-^6 小时前
面试题:react、vue中的key有什么作用?(key的内部原理)
javascript·vue.js·react.js
getaxiosluo7 小时前
详解Vite创建Vue3项目router-less-scss-pinia-持久化
前端·vue.js·chrome·typescript·less·scss