• 售前

  • 售后

热门帖子
入门百科

Vue实现多页签组件

[复制链接]
幸福341 显示全部楼层 发表于 2021-10-25 19:32:34 |阅读模式 打印 上一主题 下一主题
直接看效果,增长了右键菜单,分别有重新加载、关闭左边、关闭右边、关闭其他功能。

也可以到我的github上看看代码(假如觉得这个组件有用的话,别忘了随手给个小星星)
代码:https://github.com/Caijt/VuePageTab
演示:https://caijt.github.io/VuePageTab/
我这个多页签组件内里的删除缓存的方法不是使用keep-alive组件自带的include、exculde团结的效果,而是使用暴力删除缓存的方法,这个在上个博客中也有提到,用这种方法的话,可以实现更完整的多页签功能,比方同个路由可以根据参数的差别同时打开差别的页签,也能不消去写那些路由的name值。
先直接看组件代码(内里用了一些element-ui的组件,假如你们不消element-ui的话。可以去掉,本身实现)
  1. <template>
  2. <div class="__common-layout-pageTabs">
  3.   <el-scrollbar>
  4.    <div class="__tabs">
  5.     <div
  6.      class="__tab-item"
  7.      v-for="item in openedPageRouters"
  8.      :class="{
  9.       '__is-active': item.fullPath == $route.fullPath,
  10.      }"
  11.      :key="item.fullPath"
  12.      @click="onClick(item)"
  13.      @contextmenu.prevent="showContextMenu($event, item)"
  14.     >
  15.      {{ item.meta.title }}
  16.      <span
  17.       class="el-icon-close"
  18.       @click.stop="onClose(item)"
  19.       @contextmenu.prevent.stop=""
  20.       :style="openedPageRouters.length <= 1 ? 'width:0;' : ''"
  21.      ></span>
  22.     </div>
  23.    </div>
  24.   </el-scrollbar>
  25.   <div v-show="contextMenuVisible">
  26.    <ul
  27.     :style="{ left: contextMenuLeft + 'px', top: contextMenuTop + 'px' }"
  28.     class="__contextmenu"
  29.    >
  30.     <li>
  31.      <el-button type="text" @click="reload()" size="mini">
  32.       重新加载
  33.      </el-button>
  34.     </li>
  35.     <li>
  36.      <el-button
  37.       type="text"
  38.       @click="closeOtherLeft"
  39.       :disabled="false"
  40.       size="mini"
  41.       >关闭左边</el-button
  42.      >
  43.     </li>
  44.     <li>
  45.      <el-button
  46.       type="text"
  47.       @click="closeOtherRight"
  48.       :disabled="false"
  49.       size="mini"
  50.       >关闭右边</el-button
  51.      >
  52.     </li>
  53.     <li>
  54.      <el-button type="text" @click="closeOther" size="mini"
  55.       >关闭其他</el-button
  56.      >
  57.     </li>
  58.    </ul>
  59.   </div>
  60. </div>
  61. </template>
  62. <script>
  63. export default {
  64. props: {
  65.   keepAliveComponentInstance: {}, //keep-alive控件实例对象
  66.   blankRouteName: {
  67.    type: String,
  68.    default: "blank",
  69.   }, //空白路由的name值
  70. },
  71. data() {
  72.   return {
  73.    contextMenuVisible: false, //右键菜单是否显示
  74.    contextMenuLeft: 0, //右键菜单显示位置
  75.    contextMenuTop: 0, //右键菜单显示位置
  76.    contextMenuTargetPageRoute: null, //右键所指向的菜单路由
  77.    openedPageRouters: [], //已打开的路由页面
  78.   };
  79. },
  80. watch: {
  81.   //当路由变更时,执行打开页面的方法
  82.   $route: {
  83.    handler(v) {
  84.     this.openPage(v);
  85.    },
  86.    immediate: true,
  87.   },
  88. },
  89. mounted() {
  90.   //添加点击关闭右键菜单
  91.   window.addEventListener("click", this.closeContextMenu);
  92. },
  93. destroyed() {
  94.   window.removeEventListener("click", this.closeContextMenu);
  95. },
  96. methods: {
  97.   //打开页面
  98.   openPage(route) {
  99.    if (route.name == this.blankRouteName) {
  100.     return;
  101.    }
  102.    let isExist = this.openedPageRouters.some(
  103.     (item) => item.fullPath == route.fullPath
  104.    );
  105.    if (!isExist) {
  106.     let openedPageRoute = this.openedPageRouters.find(
  107.      (item) => item.path == route.path
  108.     );
  109.     //判断页面是否支持不同参数多开页面功能,如果不支持且已存在path值一样的页面路由,那就替换它
  110.     if (!route.meta.canMultipleOpen && openedPageRoute != null) {
  111.      this.delRouteCache(openedPageRoute.fullPath);
  112.      this.openedPageRouters.splice(
  113.       this.openedPageRouters.indexOf(openedPageRoute),
  114.       1,
  115.       route
  116.      );
  117.     } else {
  118.      this.openedPageRouters.push(route);
  119.     }
  120.    }
  121.   },
  122.   //点击页面标签卡时
  123.   onClick(route) {
  124.    if (route.fullPath !== this.$route.fullPath) {
  125.     this.$router.push(route.fullPath);
  126.    }
  127.   },
  128.   //关闭页面标签时
  129.   onClose(route) {
  130.    let index = this.openedPageRouters.indexOf(route);
  131.    this.delPageRoute(route);
  132.    if (route.fullPath === this.$route.fullPath) {
  133.     //删除页面后,跳转到上一页面
  134.     this.$router.replace(
  135.      this.openedPageRouters[index == 0 ? 0 : index - 1]
  136.     );
  137.    }
  138.   },
  139.   //右键显示菜单
  140.   showContextMenu(e, route) {
  141.    this.contextMenuTargetPageRoute = route;
  142.    this.contextMenuLeft = e.layerX;
  143.    this.contextMenuTop = e.layerY;
  144.    this.contextMenuVisible = true;
  145.   },
  146.   //隐藏右键菜单
  147.   closeContextMenu() {
  148.    this.contextMenuVisible = false;
  149.    this.contextMenuTargetPageRoute = null;
  150.   },
  151.   //重载页面
  152.   reload() {
  153.    this.delRouteCache(this.contextMenuTargetPageRoute.fullPath);
  154.    if (this.contextMenuTargetPageRoute.fullPath === this.$route.fullPath) {
  155.     this.$router.replace({ name: this.blankRouteName }).then(() => {
  156.      this.$router.replace(this.contextMenuTargetPageRoute);
  157.     });
  158.    }
  159.   },
  160.   //关闭其他页面
  161.   closeOther() {
  162.    for (let i = 0; i < this.openedPageRouters.length; i++) {
  163.     let r = this.openedPageRouters[i];
  164.     if (r !== this.contextMenuTargetPageRoute) {
  165.      this.delPageRoute(r);
  166.      i--;
  167.     }
  168.    }
  169.    if (this.contextMenuTargetPageRoute.fullPath != this.$route.fullPath) {
  170.     this.$router.replace(this.contextMenuTargetPageRoute);
  171.    }
  172.   },
  173.   //根据路径获取索引
  174.   getPageRouteIndex(fullPath) {
  175.    for (let i = 0; i < this.openedPageRouters.length; i++) {
  176.     if (this.openedPageRouters[i].fullPath === fullPath) {
  177.      return i;
  178.     }
  179.    }
  180.   },
  181.   //关闭左边页面
  182.   closeOtherLeft() {
  183.    let index = this.openedPageRouters.indexOf(
  184.     this.contextMenuTargetPageRoute
  185.    );
  186.    let currentIndex = this.getPageRouteIndex(this.$route.fullPath);
  187.    if (index > currentIndex) {
  188.     this.$router.replace(this.contextMenuTargetPageRoute);
  189.    }
  190.    for (let i = 0; i < index; i++) {
  191.     let r = this.openedPageRouters[i];
  192.     this.delPageRoute(r);
  193.     i--;
  194.     index--;
  195.    }
  196.   },
  197.   //关闭右边页面
  198.   closeOtherRight() {
  199.    let index = this.openedPageRouters.indexOf(
  200.     this.contextMenuTargetPageRoute
  201.    );
  202.    let currentIndex = this.getPageRouteIndex(this.$route.fullPath);
  203.    for (let i = index + 1; i < this.openedPageRouters.length; i++) {
  204.     let r = this.openedPageRouters[i];
  205.     this.delPageRoute(r);
  206.     i--;
  207.    }
  208.    if (index < currentIndex) {
  209.     this.$router.replace(this.contextMenuTargetPageRoute);
  210.    }
  211.   },
  212.   //删除页面
  213.   delPageRoute(route) {
  214.    let routeIndex = this.openedPageRouters.indexOf(route);
  215.    if (routeIndex >= 0) {
  216.     this.openedPageRouters.splice(routeIndex, 1);
  217.    }
  218.    this.delRouteCache(route.fullPath);
  219.   },
  220.   //删除页面缓存
  221.   delRouteCache(key) {
  222.    let cache = this.keepAliveComponentInstance.cache;
  223.    let keys = this.keepAliveComponentInstance.keys;
  224.    for (let i = 0; i < keys.length; i++) {
  225.     if (keys[i] == key) {
  226.      keys.splice(i, 1);
  227.      if (cache[key] != null) {
  228.       delete cache[key];
  229.      }
  230.      break;
  231.     }
  232.    }
  233.   },
  234. },
  235. };
  236. </script>
  237. <style lang="scss">
  238. .__common-layout-pageTabs {
  239. .__contextmenu {
  240.   // width: 100px;
  241.   margin: 0;
  242.   border: 1px solid #e4e7ed;
  243.   background: #fff;
  244.   z-index: 3000;
  245.   position: absolute;
  246.   list-style-type: none;
  247.   padding: 5px 0;
  248.   border-radius: 4px;
  249.   font-size: 14px;
  250.   color: #333;
  251.   box-shadow: 1px 1px 3px 0 rgba(0, 0, 0, 0.1);
  252.   li {
  253.    margin: 0;
  254.    padding: 0px 15px;
  255.    &:hover {
  256.     background: #f2f2f2;
  257.     cursor: pointer;
  258.    }
  259.    button {
  260.     color: #2c3e50;
  261.    }
  262.   }
  263. }
  264. $c-tab-border-color: #dcdfe6;
  265. position: relative;
  266. &::before {
  267.   content: "";
  268.   border-bottom: 1px solid $c-tab-border-color;
  269.   position: absolute;
  270.   left: 0;
  271.   right: 0;
  272.   bottom: 0;
  273.   height: 100%;
  274. }
  275. .__tabs {
  276.   display: flex;
  277.   .__tab-item {
  278.    white-space: nowrap;
  279.    padding: 8px 6px 8px 18px;
  280.    font-size: 12px;
  281.    border: 1px solid $c-tab-border-color;
  282.    border-left: none;
  283.    border-bottom: 0px;
  284.    line-height: 14px;
  285.    cursor: pointer;
  286.    transition: color 0.3s cubic-bezier(0.645, 0.045, 0.355, 1),
  287.     padding 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
  288.    &:first-child {
  289.     border-left: 1px solid $c-tab-border-color;
  290.     border-top-left-radius: 2px;
  291.     margin-left: 10px;
  292.    }
  293.    &:last-child {
  294.     border-top-right-radius: 2px;
  295.     margin-right: 10px;
  296.    }
  297.    &:not(.__is-active):hover {
  298.     color: #409eff;
  299.     .el-icon-close {
  300.      width: 12px;
  301.      margin-right: 0px;
  302.     }
  303.    }
  304.    &.__is-active {
  305.     padding-right: 12px;
  306.     border-bottom: 1px solid #fff;
  307.     color: #409eff;
  308.     .el-icon-close {
  309.      width: 12px;
  310.      margin-right: 0px;
  311.      margin-left: 2px;
  312.     }
  313.    }
  314.    .el-icon-close {
  315.     width: 0px;
  316.     height: 12px;
  317.     overflow: hidden;
  318.     border-radius: 50%;
  319.     font-size: 12px;
  320.     margin-right: 12px;
  321.     transform-origin: 100% 50%;
  322.     transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
  323.     vertical-align: text-top;
  324.     &:hover {
  325.      background-color: #c0c4cc;
  326.      color: #fff;
  327.     }
  328.    }
  329.   }
  330. }
  331. }
  332. </style>
复制代码
这个组件它必要两个属性,一个是keepAliveComponentInstance(keep-alive的控件实例对象),blankRouteName(空白路由的名称)

为什么我必要keep-alive的控件实例对象呢,因为这个对象内里有两个属性,一个是cache,一个是keys,存储着keep-alive的缓存的数据,有了这个对象,我就能在页签关闭时手动删除缓存。那这个对象怎么获取呢,如下所示,在keep-alive地点的父页面上的mounted事故上进行获取(假如keep-alive跟多页签组件不在同一个父页面,那大概就得借用vuex来传值了)
  1. <template>
  2. <div id="app">
  3.   <page-tabs :keep-alive-component-instance="keepAliveComponentInstance" />
  4.   <div ref="keepAliveContainer">
  5.    <keep-alive>
  6.     <router-view :key="$route.fullPath" />
  7.    </keep-alive>
  8.   </div>
  9. </div>
  10. </template>
  11. <script>
  12. import pageTabs from "./components/pageTabs.vue";
  13. export default {
  14. name: "App",
  15. components: {
  16.   pageTabs,
  17. },
  18. mounted() {
  19.   if (this.$refs.keepAliveContainer) {
  20.    this.keepAliveComponentInstance = this.$refs.keepAliveContainer.childNodes[0].__vue__;//获取keep-alive的控件实例对象
  21.   }
  22. },
  23. data() {
  24.   return {
  25.    keepAliveComponentInstance: null,
  26.   };
  27. }
  28. };
  29. </script>
复制代码
而空白路由的名称,是干什么,紧张我要实现刷新当前页面的功能,我们知道vue是不答应跳转到当前页面,那么我就想我先跳转到别的页面,再跳转回返来的页面,不就也实现刷新的效果了。(当然我用的是relpace,以是不会产生汗青记录)
注:这个空白路由并不是固定定义在根路由上,需根据多页签组件地点位置,假如你有一个根router-view,另有一个布局组件,这个组件内里也有一个子router-view,多页签组件就在这个布局组件里,那么空白路由就需定义在布局组件对应的路由的children内里了
另有这个组件会根据路由对象的meta对象进行差别的设置,如下所示
  1. let router = new Router({
  2. routes: [
  3.   //这个是空白页面,重新加载当前页面会用到
  4.   {
  5.    name: "blank",
  6.    path: "/blank",
  7.   },
  8.   {
  9.    path: "/a",
  10.    component: A,
  11.    meta: {
  12.     title: "A页面", //页面标题
  13.     canMultipleOpen: true //支持根据参数不同多开不同页签,如果你需要/a跟/a?v=123都分别打开两个页签,请设置为true,否则就只会显示一个页签,后打开的会替换到前打开的页签
  14.    }
  15.   }
  16. }
复制代码
以上就是Vue实现多页签组件的具体内容,更多关于Vue实现多页签组件的资料请关注草根技术分享其它相干文章!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x

帖子地址: 

回复

使用道具 举报

分享
推广
火星云矿 | 预约S19Pro,享500抵1000!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

草根技术分享(草根吧)是全球知名中文IT技术交流平台,创建于2021年,包含原创博客、精品问答、职业培训、技术社区、资源下载等产品服务,提供原创、优质、完整内容的专业IT技术开发社区。
  • 官方手机版

  • 微信公众号

  • 商务合作