Vue實現多頁簽組件

直接看效果,增加瞭右鍵菜單,分別有重新加載、關閉左邊、關閉右邊、關閉其他功能。

也可以到我的github上看看代碼(如果覺得這個組件有用的話,別忘瞭順手給個小星星)

代碼:https://github.com/Caijt/VuePageTab

演示:https://caijt.github.io/VuePageTab/

我這個多頁簽組件裡面的刪除緩存的方法不是使用keep-alive組件自帶的include、exculde結合的效果,而是使用暴力刪除緩存的方法,這個在上個博客中也有提到,用這種方法的話,可以實現更完整的多頁簽功能,例如同個路由可以根據參數的不同同時打開不同的頁簽,也能不用去寫那些路由的name值。

先直接看組件代碼(裡面用瞭一些element-ui的組件,如果你們不用element-ui的話。可以去掉,自己實現)

<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>

這個組件它需要兩個屬性,一個是keepAliveComponentInstance(keep-alive的控件實例對象),blankRouteName(空白路由的名稱)

為什麼我需要keep-alive的控件實例對象呢,因為這個對象裡面有兩個屬性,一個是cache,一個是keys,存儲著keep-alive的緩存的數據,有瞭這個對象,我就能在頁簽關閉時手動刪除緩存。那這個對象怎麼獲取呢,如下所示,在keep-alive所在的父頁面上的mounted事件上進行獲取(如果keep-alive跟多頁簽組件不在同一個父頁面,那可能就得借用vuex來傳值瞭)

<template>
 <div id="app">
  <page-tabs :keep-alive-component-instance="keepAliveComponentInstance" />
  <div ref="keepAliveContainer">
   <keep-alive>
    <router-view :key="$route.fullPath" />
   </keep-alive>
  </div>
 </div>
</template>

<script>
import pageTabs from "./components/pageTabs.vue";
export default {
 name: "App",
 components: {
  pageTabs,
 },
 mounted() {
  if (this.$refs.keepAliveContainer) {
   this.keepAliveComponentInstance = this.$refs.keepAliveContainer.childNodes[0].__vue__;//獲取keep-alive的控件實例對象
  }
 },
 data() {
  return {
   keepAliveComponentInstance: null,
  };
 }
};
</script>

而空白路由的名稱,是幹什麼,主要我要實現刷新當前頁面的功能,我們知道vue是不允許跳轉到當前頁面,那麼我就想我先跳轉到別的頁面,再跳轉回回來的頁面,不就也實現刷新的效果瞭。(當然我用的是relpace,所以不會產生歷史記錄)

註:這個空白路由並不是固定定義在根路由上,需根據多頁簽組件所在位置,假如你有一個根router-view,還有一個佈局組件,這個組件裡面也有一個子router-view,多頁簽組件就在這個佈局組件裡,那麼空白路由就需定義在佈局組件對應的路由的children裡面瞭

還有這個組件會根據路由對象的meta對象進行不同的配置,如下所示

let router = new Router({
 routes: [
  //這個是空白頁面,重新加載當前頁面會用到
  {
   name: "blank",
   path: "/blank",
  },
  {
   path: "/a",
   component: A,
   meta: {
    title: "A頁面", //頁面標題
    canMultipleOpen: true //支持根據參數不同多開不同頁簽,如果你需要/a跟/a?v=123都分別打開兩個頁簽,請設置為true,否則就隻會顯示一個頁簽,後打開的會替換到前打開的頁簽
   }
  }
}

以上就是Vue實現多頁簽組件的詳細內容,更多關於Vue實現多頁簽組件的資料請關註WalkonNet其它相關文章!

推薦閱讀: