Vueはマルチタブコンポーネントを実装します

Vueはマルチタブコンポーネントを実装します

効果を直接確認するために、リロード、左を閉じる、右を閉じる、その他の機能を閉じるなどの右クリック メニューが追加されました。

私の github のコードもチェックしてみてください (このコンポーネントが役に立つと思ったら、忘れずにスターを付けてください)

コード: https://github.com/Caijt/VuePageTab

デモ: https://caijt.github.io/VuePageTab/

私のマルチタブコンポーネントでキャッシュを削除する方法は、キープアライブコンポーネントの包含と除外の組み合わせを使用するのではなく、キャッシュを削除するブルートフォース方式を使用することです。これは以前のブログでも言及しました。この方法を使用すると、より完全なマルチタブ機能を実現できます。たとえば、同じルートで異なるパラメータに応じて同時に異なるタブを開くことができ、それらのルートの名前値を書く必要はありません。

まず、コンポーネントコードを直接見てみましょう(いくつかのelement-uiコンポーネントが使用されています。element-uiを使用しない場合は、それらを削除して自分で実装できます)

<テンプレート>
 <div class="__common-layout-pageTabs">
  <el-スクロールバー>
   <div class="__tabs">
    <div
     クラス="__タブ項目"
     v-for="openedPageRouters 内の項目"
     :class="{
      '__is-active': item.fullPath == $route.fullPath、
     }"
     :key="item.fullPath"
     @click="onClick(アイテム)"
     @contextmenu.prevent="コンテキストメニューを表示する($event, item)"
    >
     {{ item.meta.title }}
     <span
      クラス="el-icon-close"
      @click.stop="onClose(item)"
      @contextmenu.prevent.stop=""
      :style="openedPageRouters.length <= 1 ? 'width:0;' : ''"
     </span>
    </div>
   </div>
  </el-スクロールバー>
  <div v-show="コンテキストメニューを表示">
   <ul
    :style="{ 左: contextMenuLeft + 'px'、上: contextMenuTop + 'px' }"
    クラス="__コンテキストメニュー"
   >
    <li>
     <el-button type="text" @click="reload()" size="mini">
      再読み込み</el-button>
    </li>
    <li>
     <el-ボタン
      タイプ="テキスト"
      @click="その他の左を閉じる"
      :disabled="false"
      サイズ="ミニ"
      >左を閉じる</el-button
     >
    </li>
    <li>
     <el-ボタン
      タイプ="テキスト"
      @click="その他の右を閉じる"
      :disabled="false"
      サイズ="ミニ"
      >右を閉じる</el-button
     >
    </li>
    <li>
     <el-button type="text" @click="closeOther" size="mini"
      >その他を閉じる</el-button
     >
    </li>
   </ul>
  </div>
 </div>
</テンプレート>
<スクリプト>
エクスポートデフォルト{
 小道具: {
  keepAliveComponentInstance: {}, //キープアライブ制御インスタンスオブジェクト blankRouteName: {
   タイプ: 文字列、
   デフォルト: "空白",
  }, // ルート名の値(空白)},
 データ() {
  戻る {
   contextMenuVisible: false, // 右クリック メニューが表示されるかどうかcontextMenuLeft: 0, // 右クリック メニューの表示位置contextMenuTop: 0, // 右クリック メニューの表示位置contextMenuTargetPageRoute: null, // 右ボタンが指すメニュー ルートopenedPageRouters: [], // 開かれたルート ページ};
 },
 時計:
  //ルートが変更されたら、ページを開くメソッドを実行します $route: {
   ハンドラ(v) {
    this.openPage(v);
   },
   即時: true、
  },
 },
 マウント() {
  //右クリック メニューを閉じるためのクリックを追加します。window.addEventListener("click", this.closeContextMenu);
 },
 破壊された() {
  window.removeEventListener("click", this.closeContextMenu);
 },
 メソッド: {
  //ページを開く openPage(route) {
   ルート名が this.blankRouteName の場合
    戻る;
   }
   isExist = this.openedPageRouters.some( とする
    (アイテム) => item.fullPath == route.fullPath
   );
   存在する場合
    開いたページルートを this.openedPageRouters.find( とする
     (アイテム) => item.path == route.path
    );
    // ページが異なるパラメータを持つ複数のページをサポートしているかどうかを確認します。サポートしておらず、同じパス値を持つページルートがすでに存在する場合は、それを置き換えます if (!route.meta.canMultipleOpen && openedPageRoute != null) {
     this.delRouteCache(openedPageRoute.fullPath);
     this.openedPageRouters.splice(
      this.openedPageRouters.indexOf(openedPageRoute)、
      1、
      ルート
     );
    } それ以外 {
     this.openedPageRouters.push(ルート);
    }
   }
  },
  //ページタブをクリック onClick(route) {
   (route.fullPath !== this.$route.fullPath)の場合{
    this.$router.push(route.fullPath);
   }
  },
  //ページタブを閉じる onClose(route) {
   ルートのインデックスを this.openedPageRouters.indexOf(route);
   this.delPageRoute(ルート);
   ルートのfullPathがthis.$route.fullPathの場合{
    //ページを削除した後、前のページにジャンプします this.$router.replace(
     this.openedPageRouters[インデックス == 0 ? 0 : インデックス - 1]
    );
   }
  },
  //右クリックしてメニューを表示しますshowContextMenu(e, route) {
   this.contextMenuTargetPageRoute = ルート;
   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() {
   (i = 0 とします; i < this.openedPageRouters.length; i++) {
    r = this.openedPageRouters[i]とします。
    if (r !== this.contextMenuTargetPageRoute) {
     this.delPageRoute(r);
     私 - ;
    }
   }
   if (this.contextMenuTargetPageRoute.fullPath != this.$route.fullPath) {
    this.$router.replace(this.contextMenuTargetPageRoute);
   }
  },
  //パスに基づいてインデックスを取得する getPageRouteIndex(fullPath) {
   (i = 0 とします; i < this.openedPageRouters.length; i++) {
    if (this.openedPageRouters[i].fullPath === fullPath) {
     i を返します。
    }
   }
  },
  //左ページを閉じる closeOtherLeft() {
   インデックスを this.openedPageRouters.indexOf( とする
    this.contextMenuTargetPageRoute
   );
   currentIndex = this.getPageRouteIndex(this.$route.fullPath); とします。
   if (インデックス > 現在のインデックス) {
    this.$router.replace(this.contextMenuTargetPageRoute);
   }
   (i = 0; i < インデックス; i++) の場合 {
    r = this.openedPageRouters[i]とします。
    this.delPageRoute(r);
    私 - ;
    索引 - ;
   }
  },
  //右ページを閉じる closeOtherRight() {
   インデックスを this.openedPageRouters.indexOf( とする
    this.contextMenuTargetPageRoute
   );
   currentIndex = this.getPageRouteIndex(this.$route.fullPath); とします。
   (i = index + 1 とします; i < this.openedPageRouters.length; i++) {
    r = this.openedPageRouters[i]とします。
    this.delPageRoute(r);
    私 - ;
   }
   if (インデックス < 現在のインデックス) {
    this.$router.replace(this.contextMenuTargetPageRoute);
   }
  },
  //ページを削除する delPageRoute(route) {
   routeIndex = this.openedPageRouters.indexOf(route); とします。
   ルートインデックス >= 0 の場合
    this.openedPageRouters.splice(routeIndex, 1);
   }
   ルートキャッシュを削除します。
  },
  //ページキャッシュを削除する delRouteCache(key) {
   キャッシュを this.keepAliveComponentInstance.cache とします。
   キーを this.keepAliveComponentInstance.keys とします。
   (i = 0 とします; i < keys.length; i++) {
    if (keys[i] == key) {
     キーを連結します(i, 1);
     if (cache[key] != null) {
      キャッシュ[キー]を削除します。
     }
     壊す;
    }
   }
  },
 },
};
</スクリプト>
<スタイル lang="scss">
.__共通レイアウトページタブ {
 .__コンテキストメニュー{
  // 幅: 100px;
  マージン: 0;
  境界線: 1px 実線 #e4e7ed;
  背景: #fff;
  zインデックス: 3000;
  位置: 絶対;
  リストスタイルタイプ: なし;
  パディング: 5px 0;
  境界線の半径: 4px;
  フォントサイズ: 14px;
  色: #333;
  ボックスの影: 1px 1px 3px 0 rgba(0, 0, 0, 0.1);
  li {
   マージン: 0;
   パディング: 0px 15px;
   &:ホバー{
    背景: #f2f2f2;
    カーソル: ポインタ;
   }
   ボタン {
    色: #2c3e50;
   }
  }
 }

 タブの境界線の色: #dcdfe6;
 位置: 相対的;
 &::前に {
  コンテンツ: "";
  境界線下部: 1px 実線 $c-tab-border-color;
  位置: 絶対;
  左: 0;
  右: 0;
  下部: 0;
  高さ: 100%;
 }
 .__タブ {
  ディスプレイ: フレックス;
  .__タブ項目{
   空白: ラップなし;
   パディング: 8px 6px 8px 18px;
   フォントサイズ: 12px;
   境界線: 1px 実線 $c-tab-border-color;
   左境界線: なし;
   下境界線: 0px;
   行の高さ: 14px;
   カーソル: ポインタ;
   遷移: 色 0.3s 立方ベジェ(0.645, 0.045, 0.355, 1)、
    パディング 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
   &:最初の子 {
    左境界線: 1px 実線 $c-tab-border-color;
    左上の境界線の半径: 2px;
    左マージン: 10px;
   }
   &:最後の子 {
    右上の境界線の半径: 2px;
    右マージン: 10px;
   }
   &:not(.__is-active):ホバー {
    色: #409eff;
    .el-icon-close {
     幅: 12px;
     右マージン: 0px;
    }
   }
   &.__ はアクティブです {
    右パディング: 12px;
    下境界線: 1px 実線 #fff;
    色: #409eff;
    .el-icon-close {
     幅: 12px;
     右マージン: 0px;
     左マージン: 2px;
    }
   }
   .el-icon-close {
    幅: 0px;
    高さ: 12px;
    オーバーフロー: 非表示;
    境界線の半径: 50%;
    フォントサイズ: 12px;
    右マージン: 12px;
    変換元: 100% 50%;
    遷移: すべて 0.3 秒 cubic-bezier(0.645, 0.045, 0.355, 1);
    垂直配置: テキスト上;
    &:ホバー{
     背景色: #c0c4cc;
     色: #fff;
    }
   }
  }
 }
}
</スタイル>

このコンポーネントには 2 つのプロパティが必要です。1 つは keepAliveComponentInstance (キープアライブ制御インスタンス オブジェクト)、もう 1 つは blankRouteName (空のルート名) です。

なぜキープアライブ コントロール インスタンス オブジェクトが必要なのでしょうか。このオブジェクトには、キープアライブ キャッシュ データを格納するキャッシュとキーの 2 つのプロパティがあるためです。このオブジェクトを使用すると、タブが閉じられたときにキャッシュを手動で削除できます。このオブジェクトはどうやって取得するのでしょうか? 以下に示すように、keep-alive が配置されている親ページのマウントされたイベントで取得します (keep-alive とマルチページ タブ コンポーネントが同じ親ページにない場合は、vuex を使用して値を渡す必要がある場合があります)

<テンプレート>
 <div id="アプリ">
  <ページタブ:keep-alive-component-instance="keepAliveComponentInstance" />
  <div ref="keepAliveContainer">
   <キープアライブ>
    <ルータービュー:key="$route.fullPath" />
   </キープアライブ>
  </div>
 </div>
</テンプレート>

<スクリプト>
「./components/pageTabs.vue」からpageTabsをインポートします。
エクスポートデフォルト{
 名前:「アプリ」、
 コンポーネント:
  ページタブ、
 },
 マウント() {
  (this.$refs.keepAliveContainer) の場合 {
   this.keepAliveComponentInstance = this.$refs.keepAliveContainer.childNodes[0].__vue__; //キープアライブコントロールインスタンスオブジェクトを取得する}
 },
 データ() {
  戻る {
   keepAliveComponentInstance: null、
  };
 }
};
</スクリプト>

空白ルートの名前は何ですか? 主に、現在のページを更新する機能を実現したいです。 vue では現在のページにジャンプできないことがわかっているので、最初に別のページにジャンプしてから、ページに戻って、更新効果を実現したいと思います。 (もちろん relpace を使用しているので、履歴レコードは生成されません)

注: この空白ルートはルート ルートに固定されていません。これは、マルチタブ コンポーネントの場所によって異なります。ルート ルーター ビューとレイアウト コンポーネントがあり、レイアウト コンポーネントにも子ルーター ビューがあり、マルチタブ コンポーネントがこのレイアウト コンポーネント内にある場合は、レイアウト コンポーネントに対応するルートの子に空白ルートを定義する必要があります。

このコンポーネントは、ルーティングオブジェクトのメタオブジェクトに応じて以下のように異なる構成になります。

ルータ = 新しいルータ({
 ルート: [
  //これは空白ページです。現在のページを再読み込みすると{
   名前: "空白",
   パス: "/空白",
  },
  {
   パス: "/a",
   成分: A、
   メタ: {
    title: "ページ A", //ページ タイトル canMultipleOpen: true //異なるパラメータに基づいて複数のタブを開くことをサポートします。/a と /a?v=123 のそれぞれに 2 つのタブを開く必要がある場合は、true に設定してください。そうでない場合は、1 つのタブのみが表示され、後で開いたタブが前に開いたタブに置き換わります。}
  }
}

以上がVueのマルチタブコンポーネントの実装の詳細です。Vueのマルチタブコンポーネントの実装の詳細については、123WORDPRESS.COMの他の関連記事に注目してください。

以下もご興味があるかもしれません:
  • Vue マルチ選択リスト コンポーネントの詳細な説明
  • Vue で親子コンポーネントの値を双方向バインドするために v-model を使用するときに発生する問題と解決策
  • トランジションコンポーネントのアニメーション効果を使用した Vue サンプルコード
  • Vueコンポーネントの基本のまとめ
  • Vue の動的コンポーネントと非同期コンポーネントの詳細な理解
  • Vue が Ref を使用してレベル間でコンポーネントを取得する手順
  • Vue3でスイッチ関数コンポーネントのプロセス全体を簡単に実装する
  • Vue3 でダイアログとモーダル コンポーネントをカスタマイズする方法
  • Vue でコンポーネントを強制的に再レン​​ダリングする正しい方法
  • Vueでコンポーネントを動的に作成する2つの方法
  • vue3 のコンポーネントの互換性のない変更の詳細な説明

<<:  Vue で HTML 5 ドラッグ アンド ドロップ API を使用する方法

>>:  JavaScriptの詳細な説明 thisキーワード

推薦する

Dockerコンテナデータをコピーしてバックアップする方法の詳細な説明

ここでは、Jenkins コンテナを例に 3 つの方法を紹介します。方法1コンテナをイメージにパッケ...

MySQL マスタースレーブレプリケーションと読み取り書き込み分離の詳細な説明

記事マインドマップマスター/スレーブ レプリケーションと読み取り/書き込み分離を使用する理由は何です...

2つのシンプルなメニューナビゲーションバーの例

メニューバーの例 1: コードをコピーコードは次のとおりです。 <!DOCTYPE html ...

nginx proxy_cache キャッシュ設定の詳細な説明

序文:私は仕事柄、オンラインライブストリーミングの分野に携わっており、ビデオの再生やダウンロードには...

JavaScript マクロタスクとマイクロタスクの実行順序についての簡単な説明

目次1. JavaScriptはシングルスレッドです1. 同期タスク2. 非同期タスク2. タスクキ...

MySQL 5.6.36 Windows x64 バージョンのインストールチュートリアルの詳細

1. 対象環境Windows 7 64ビット2. 材料(1)VC++2010リリースパッケージ(64...

Linux SSHポートを転送する3つの方法

ssh は私が最も頻繁に使用する 2 つのコマンドライン ツールのうちの 1 つです (もう 1 つ...

docker pull imageエラーの問題を解決する

説明する: Windows 10 に VM をインストールし、VM で Docker を実行し、Do...

製品の拡大鏡効果を実現する JavaScript

この記事では、参考までに、製品拡大鏡を実装するためのJavaScriptの具体的なコードを紹介します...

Webpack プロジェクトでローダー プラグインをデバッグする方法

最近、webpackの使い方を学んでいたときに、webpack-replace-loaderの設定正...

MySQLクエリが遅い理由

目次1. 遅いところはどこですか? 2. 不要なデータをクエリしましたか? 1. 不要なレコードをク...

Linux サーバーのステータスとパフォーマンスに関連するコマンドの詳細な説明

サーバーステータス分析Linux サーバーの CPU の詳細を表示する#CPU情報を表示[root@...

WeChatアプレットでラッキーホイールゲームを実装する方法

ここでは主に、WeChat アプレットでラッキーホイール ゲームを開発する方法を紹介します。主に J...

GaussDB for MySQL パフォーマンス最適化の詳細な説明

目次背景インスピレーションは人生から生まれる速達配送の最適化原則GaussDB の最適化 (MySQ...

MySQL 接続数を設定する方法 (接続数が多すぎる)

mysql使用中に接続数が超過していることが判明しました~~~~ [root@linux-node...