Vue的多页签组件

article/2025/11/4 18:14:33

先直接看组件代码(里面用了一些element-ui的组件,如果你们不用element-ui的话。可以去掉,自己实现)

也可以下载Demohttps://download.csdn.net/download/rui1120119095/14155951

<template>
  <div class="__common-layout-pageTabs">
    <el-scrollbar>
      <div class="__tabs">
        <div
          class="__tab-item"
          v-for="item in openedPageRouters"
          :class="{
            '__is-active': item.fullPath == $route.fullPath,
          }"
          :key="item.fullPath"
          @click="onClick(item)"
          @contextmenu.prevent="showContextMenu($event, item)"
        >
          {{ item.meta.title }}
          <span
            class="el-icon-close"
            @click.stop="onClose(item)"
            @contextmenu.prevent.stop=""
            :style="openedPageRouters.length <= 1 ? 'width:0;' : ''"
          ></span>
        </div>
      </div>
    </el-scrollbar>
    <div v-show="contextMenuVisible">
      <ul
        :style="{ left: contextMenuLeft + 'px', top: contextMenuTop + 'px' }"
        class="__contextmenu"
      >
        <li>
          <el-button type="text" @click="reload()" size="mini">
            重新加载
          </el-button>
        </li>
        <li>
          <el-button
            type="text"
            @click="closeOtherLeft"
            :disabled="false"
            size="mini"
            >关闭左边</el-button
          >
        </li>
        <li>
          <el-button
            type="text"
            @click="closeOtherRight"
            :disabled="false"
            size="mini"
            >关闭右边</el-button
          >
        </li>
        <li>
          <el-button type="text" @click="closeOther" size="mini"
            >关闭其他</el-button
          >
        </li>
      </ul>
    </div>
  </div>
</template>
<script>
export default {
  props: {
    keepAliveComponentInstance: {}, //keep-alive控件实例对象
    blankRouteName: {
      type: String,
      default: "blank",
    }, //空白路由的name值
  },
  data() {
    return {
      contextMenuVisible: false, //右键菜单是否显示
      contextMenuLeft: 0, //右键菜单显示位置
      contextMenuTop: 0, //右键菜单显示位置
      contextMenuTargetPageRoute: null, //右键所指向的菜单路由
      openedPageRouters: [], //已打开的路由页面
    };
  },
  watch: {
    //当路由变更时,执行打开页面的方法
    $route: {
      handler(v) {
        this.openPage(v);
      },
      immediate: true,
    },
  },
  mounted() {
    //添加点击关闭右键菜单
    window.addEventListener("click", this.closeContextMenu);
  },
  destroyed() {
    window.removeEventListener("click", this.closeContextMenu);
  },
  methods: {
    //打开页面
    openPage(route) {
      if (route.name == this.blankRouteName) {
        return;
      }
      let isExist = this.openedPageRouters.some(
        (item) => item.fullPath == route.fullPath
      );
      if (!isExist) {
        let openedPageRoute = this.openedPageRouters.find(
          (item) => item.path == route.path
        );
        //判断页面是否支持不同参数多开页面功能,如果不支持且已存在path值一样的页面路由,那就替换它
        if (!route.meta.canMultipleOpen && openedPageRoute != null) {
          this.delRouteCache(openedPageRoute.fullPath);
          this.openedPageRouters.splice(
            this.openedPageRouters.indexOf(openedPageRoute),
            1,
            route
          );
        } else {
          this.openedPageRouters.push(route);
        }
      }
    },
    //点击页面标签卡时
    onClick(route) {
      if (route.fullPath !== this.$route.fullPath) {
        this.$router.push(route.fullPath);
      }
    },
    //关闭页面标签时
    onClose(route) {
      let index = this.openedPageRouters.indexOf(route);
      this.delPageRoute(route);
      if (route.fullPath === this.$route.fullPath) {
        //删除页面后,跳转到上一页面
        this.$router.replace(
          this.openedPageRouters[index == 0 ? 0 : index - 1]
        );
      }
    },
    //右键显示菜单
    showContextMenu(e, route) {
      this.contextMenuTargetPageRoute = route;
      this.contextMenuLeft = e.layerX;
      this.contextMenuTop = e.layerY;
      this.contextMenuVisible = true;
    },
    //隐藏右键菜单
    closeContextMenu() {
      this.contextMenuVisible = false;
      this.contextMenuTargetPageRoute = null;
    },
    //重载页面
    reload() {
      this.delRouteCache(this.contextMenuTargetPageRoute.fullPath);
      if (this.contextMenuTargetPageRoute.fullPath === this.$route.fullPath) {
        this.$router.replace({ name: this.blankRouteName }).then(() => {
          this.$router.replace(this.contextMenuTargetPageRoute);
        });
      }
    },
    //关闭其他页面
    closeOther() {
      for (let i = 0; i < this.openedPageRouters.length; i++) {
        let r = this.openedPageRouters[i];
        if (r !== this.contextMenuTargetPageRoute) {
          this.delPageRoute(r);
          i--;
        }
      }
      if (this.contextMenuTargetPageRoute.fullPath != this.$route.fullPath) {
        this.$router.replace(this.contextMenuTargetPageRoute);
      }
    },
    //根据路径获取索引
    getPageRouteIndex(fullPath) {
      for (let i = 0; i < this.openedPageRouters.length; i++) {
        if (this.openedPageRouters[i].fullPath === fullPath) {
          return i;
        }
      }
    },
    //关闭左边页面
    closeOtherLeft() {
      let index = this.openedPageRouters.indexOf(
        this.contextMenuTargetPageRoute
      );
      let currentIndex = this.getPageRouteIndex(this.$route.fullPath);
      if (index > currentIndex) {
        this.$router.replace(this.contextMenuTargetPageRoute);
      }
      for (let i = 0; i < index; i++) {
        let r = this.openedPageRouters[i];
        this.delPageRoute(r);
        i--;
        index--;
      }
    },
    //关闭右边页面
    closeOtherRight() {
      let index = this.openedPageRouters.indexOf(
        this.contextMenuTargetPageRoute
      );
      let currentIndex = this.getPageRouteIndex(this.$route.fullPath);
      for (let i = index + 1; i < this.openedPageRouters.length; i++) {
        let r = this.openedPageRouters[i];
        this.delPageRoute(r);
        i--;
      }
      if (index < currentIndex) {
        this.$router.replace(this.contextMenuTargetPageRoute);
      }
    },
    //删除页面
    delPageRoute(route) {
      let routeIndex = this.openedPageRouters.indexOf(route);
      if (routeIndex >= 0) {
        this.openedPageRouters.splice(routeIndex, 1);
      }
      this.delRouteCache(route.fullPath);
    },
    //删除页面缓存
    delRouteCache(key) {
      let cache = this.keepAliveComponentInstance.cache;
      let keys = this.keepAliveComponentInstance.keys;
      for (let i = 0; i < keys.length; i++) {
        if (keys[i] == key) {
          keys.splice(i, 1);
          if (cache[key] != null) {
            delete cache[key];
          }
          break;
        }
      }
    },
  },
};
</script>
<style lang="scss">
.__common-layout-pageTabs {
  .__contextmenu {
    // width: 100px;
    margin: 0;
    border: 1px solid #e4e7ed;
    background: #fff;
    z-index: 3000;
    position: absolute;
    list-style-type: none;
    padding: 5px 0;
    border-radius: 4px;
    font-size: 14px;
    color: #333;
    box-shadow: 1px 1px 3px 0 rgba(0, 0, 0, 0.1);
    li {
      margin: 0;
      padding: 0px 15px;
      &:hover {
        background: #f2f2f2;
        cursor: pointer;
      }
      button {
        color: #2c3e50;
      }
    }
  }

  $c-tab-border-color: #dcdfe6;
  position: relative;
  &::before {
    content: "";
    border-bottom: 1px solid $c-tab-border-color;
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 100%;
  }
  .__tabs {
    display: flex;
    .__tab-item {
      white-space: nowrap;
      padding: 8px 6px 8px 18px;
      font-size: 12px;
      border: 1px solid $c-tab-border-color;
      border-left: none;
      border-bottom: 0px;
      line-height: 14px;
      cursor: pointer;
      transition: color 0.3s cubic-bezier(0.645, 0.045, 0.355, 1),
        padding 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
      &:first-child {
        border-left: 1px solid $c-tab-border-color;
        border-top-left-radius: 2px;
        margin-left: 10px;
      }
      &:last-child {
        border-top-right-radius: 2px;
        margin-right: 10px;
      }
      &:not(.__is-active):hover {
        color: #409eff;
        .el-icon-close {
          width: 12px;
          margin-right: 0px;
        }
      }
      &.__is-active {
        padding-right: 12px;
        border-bottom: 1px solid #fff;
        color: #409eff;
        .el-icon-close {
          width: 12px;
          margin-right: 0px;
          margin-left: 2px;
        }
      }
      .el-icon-close {
        width: 0px;
        height: 12px;
        overflow: hidden;
        border-radius: 50%;
        font-size: 12px;
        margin-right: 12px;
        transform-origin: 100% 50%;
        transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
        vertical-align: text-top;
        &:hover {
          background-color: #c0c4cc;
          color: #fff;
        }
      }
    }
  }
}
</style>


http://chatgpt.dhexx.cn/article/CQydGJjE.shtml

相关文章

XSSFWorkbook导出多一个Sheet页签

问题&#xff1a;使用XSSFWorkbook导出Excel多出一个不必要Sheet页。 问题原因&#xff1a;open(FileInputStream s)打开Excel模板时会默认打开一个Sheet页&#xff08;多余的那个Sheet页&#xff09;&#xff08;该Sheet页打开顺序&#xff1a;模板第一格显示的Sheet页->模…

VUEcli3设置页签图标

vuecli3设置网页页签图标非常简单&#xff0c;三步 1.将ico格式的页签图片放置public文件夹目录下 2.在public文件夹目录下的index.html中配置页签图标 3.在vue.config.js中配置下&#xff0c;配置好了重启下项目就行了。 效果

antd-design-pro实现多页签,切换页签保留缓存,keep-alive

感谢该大佬提供的组件&#xff1a;GitHub - CJY0208/react-activation: Hack <KeepAlive /> for React react 里 keep-alive 的实现目前是黑科技&#xff0c;会有些问题 使用过程中遇到问题的话&#xff0c;可以优先看这儿 https://github.com/CJY0208/react-activatio…

【微信小程序】之自定义顶部导航页签

小程序系统提供的导航页签&#xff0c;只能设置字体&#xff0c;却不能自定义字体图片之类的&#xff0c;所以自己写了一个示例。 废话不多说&#xff0c;直接上代码 效果&#xff1a; app.js onLaunch: function() {wx.getSystemInfo({success: e > {this.globalData.S…

ABAP:多页签的选择屏幕

在程序中创建一个100屏幕&#xff0c;然后设定子屏幕区域&#xff0c;可通过屏幕号指定页签&#xff0c;让选择屏幕更多样式&#xff0c;效果如下图&#xff1a; 实现方式如下 定义不同的选择屏幕 * 基本条件屏幕 SELECTION-SCREEN BEGIN OF SCREEN 1100 AS SUBSCREEN. SELE…

【实战】1096- React 中后台系统多页签实现

在中后台管理类系统中&#xff0c;多页签的需求非常普遍&#xff0c;用户常常需要在多个页签内跳转&#xff0c;比如填写表单时去查询某个列表获取一些字段信息再回到表单页面填写。这样的需求在 Vue 中使用 keep-alive 即可实现&#xff0c;但是在 React 中&#xff0c;React …

多种方式带你玩转 javascript 实现关闭浏览器页签

你知道的越多&#xff0c;你不知道的越多 点赞再看&#xff0c;养成习惯 如果您有疑问或者见解&#xff0c;或者没有积分想获取项目&#xff0c;欢迎指教&#xff1a; 企鹅&#xff1a;869192208 文章目录 前言方法一方法二方法三方法四方法五方法六附录 前言 近日&#xff0c…

vue 后台系统中多页面标签

在后台开发中&#xff0c;常用一种页面标签工具&#xff0c;每次点击菜单栏时&#xff0c;会在页面区域上方增加一个【标签页】如下图&#xff0c;可关闭&#xff0c;可切换页面等功能&#xff0c;常见于后台管理系统中。 以前&#xff0c;我以为这个是利用tabs组件开发的&…

js关闭浏览器页签

兼容性 js实现 function closeWebPage(){if (navigator.userAgent.indexOf("MSIE") > 0) {if (navigator.userAgent.indexOf("MSIE 6.0") > 0) {window.opener null;window.close();}else {window.open(, _top);window.top.close();}}else if (nav…

layui————一个页面展示两个页签

html页面 <!DOCTYPE html> <html> <head><meta charset"utf-8"><link rel"stylesheet" href"../../../build/css/base.css" media"all"> </head> <body> <div class"layui-tab la…

SAP BP屏幕增强页签

导语&#xff1a;最近收到了BP的需求&#xff0c;要增加页签&#xff0c;找了一些资料&#xff0c;发现BP的增强页签可是真麻烦啊&#xff0c;下面把我梳理出来的分享一下。 &#x1f449;【增强记录清单…】 需求&#xff1a; 需求是在供应商界面增加一个页签&#xff0c;用…

修改浏览器页签名称

第一种若是整个系统要统一修改为一个名称 在public文件夹下index.html下直接修改或者在相应配置文件package.json或者其他&#xff08;看项目配置&#xff09; 第二种某一个路由或者菜单页签不一样的名称 可以配置到后置路由中或者组件内 语句为:document.title 测试

vue实现tagsview多页签导航功能

文章目录 前言一、效果图二、实现思路1. 新建 tags-view.js2. 在Vuex里面引入 tags-view.js3. 新建 tabsView 组件4. 新建 ScrollPane 组件5. 引入 tabsView 组件6. 使用 keep-alive 组件&#xff0c;进行页签的缓存 总结 前言 基本上后台管理系统都需要有多页签的功能&#x…

基于微前端qiankun的多页签缓存方案实践

作者&#xff1a;vivo 互联网前端团队- Tang Xiao 本文梳理了基于阿里开源微前端框架qiankun&#xff0c;实现多页签及子应用缓存的方案&#xff0c;同时还类比了多个不同方案之间的区别及优劣势&#xff0c;为使用微前端进行多页签开发的同学&#xff0c;提供一些参考。 一、…

Sublime Text的命令行工具subl

在sublime的安装目录下有个subl.exe&#xff0c;是sublime编辑器为用户提供的命令行工具。 修改Windows系统的环境变量&#xff0c;将sublime的安装路径添加到环境变量里&#xff1b; 打开win的命令行提示符程序&#xff0c;输入subl -version,看到结果如下图所示&#xff1a;…

Macbook Pro下安装subl命令,快速使用sublime打开代码

一、使用背景 我在macbook pro电脑上经常使用编辑器直接打开代码&#xff0c;我也经常用iterm2的一些快捷命令操作目录和查看文件。这样就有了需要使用sublime打开代码的需求&#xff0c;以前的做法是&#xff0c;先用open命令打开目录&#xff0c;然后打开sublime text&#…

sublime安装以及配置

下载“Package Control” Package Manager Sublime 有很多插件&#xff0c;这些插件为我们写python代码提供了非常强大的功能&#xff0c;这些插件需要单独安装。 而安装这些插件最方便的方法就是通过Package Control的插件&#xff0c;这其实就是一个插件管理器&#xff0c;帮…

subline的使用

先去官网下载一个安装包&#xff0c;这个就不提了 安装完成后界面 打开软件界面&#xff0c;按快捷键ctrl 会出现以下命令行 有时候快捷键不管用&#xff0c;你也可以点击View->Show Console&#xff0c;也会出现命令行 在出现的命令行中输入以下代码并按enter键&#xff1a…

Sublime 替换和查找的方法

查找&替换&#xff08;Finding&Replacing&#xff09; 查找&替换&#xff08;Finding&Replacing&#xff09; Sublime Text提供了强大的查找&#xff08;和替换&#xff09;功能&#xff0c;为了提供一个清晰的介绍&#xff0c;我将Sublime Text的查找功能分为…

【Mac 教程系列】如何在 Mac 中用终端命令行方式打开 Sublime Text ?

如何在 Mac 中用终端命令行方式打开 Sublime Text ? 用 markdown 格式输出答案。 不少于1000字。细分到2级目录。 如何在 Mac 中用终端命令行方式打开 Sublime Text ? 一、首先确保已经安装 Sublime Text 前往官网https://www.sublimetext.com/下载 Sublime Text,点击 “Do…