Vue に限定されず、他の種類の SPA プロジェクトにも当てはまる問題がいくつかあります。 1. ページ権限制御とログイン認証ページ権限制御ページ権限制御とは何ですか? つまり、Web サイトには管理者や一般ユーザーなどのさまざまな役割があり、さまざまなページにアクセスするには異なる役割が必要です。ロールにページへの不正アクセスがある場合は、制限を課す必要があります。 一つの方法は、ルートやメニューを動的に追加して制御することです。アクセスできないページはルーティングテーブルに追加されません。これが一つの方法です。詳細については、次のセクション「ダイナミック メニュー」を参照してください。 もう 1 つの方法は、すべてのページをルーティング テーブルに配置し、アクセスするときにロールの権限を判断することです。権限がある場合はアクセスが許可されます。権限がない場合はアクセスが拒否され、リクエストは 404 ページにリダイレクトされます。 アイデア 各ルートのメタ属性で、ルートにアクセスできるロールを roles に追加します。各ユーザーがログインすると、そのユーザーのロールが返されます。次に、ページにアクセスすると、ルートのメタ属性がユーザーのロールと比較されます。ユーザーのロールがルートのロール内にある場合は、アクセスが許可されます。そうでない場合は、アクセスが拒否されます。 コードサンプル ルーティング情報 ルート: [ { パス: '/login', 名前: 'ログイン', メタ: { 役割: ['管理者', 'ユーザー'] }, コンポーネント: () => import('../components/Login.vue') }, { パス: 'ホーム', 名前: 'ホーム', メタ: { 役割: ['管理者'] }, コンポーネント: () => import('../views/Home.vue') }, ] ページコントロール // 管理者とユーザーの2つの役割があると仮定します //バックグラウンドから取得したユーザーロールです。const role = 'user' // router.beforeEach イベントはページに入る前にトリガーされます router.beforeEach((to, from, next) => { to.meta.roles.includes(role) の場合 { 次() } それ以外 { 次へ({パス: '/404'}) } }) ログイン認証 通常、Web サイトに一度ログインすると、再度ログインしなくても Web サイトの他のページに直接アクセスできます。これはトークンまたは Cookie を通じて実行できます。次のコードは、トークンを使用してログイン検証を制御する方法を示しています。 router.beforeEach((to, from, next) => { // トークンがある場合、ユーザーがログインしていることを意味します if (localStorage.getItem('token')) { // ログインしている場合、ログインページにアクセスするとホームページにリダイレクトされます if (to.path === '/login') { 次へ({パス: '/'}) } それ以外 { 次({パス: to.path || '/'}) } } それ以外 { // ログインしていない場合、アクセスしたページはすべてログインページにリダイレクトされます if (to.path === '/login') { 次() } それ以外 { 次へ(`/login?redirect=${to.path}`) } } }) 2. ダイナミックメニューバックエンド管理システムを作成するときに、バックグラウンドデータに基づいてルートやメニューを動的に追加するという要件に遭遇したことがある人は多いと思われます。なぜこれをするのですか?ユーザーによって権限が異なるため、アクセスできるページも異なります。 ルートを動的に追加する vue-router の addRoutes メソッドを使用してルートを動的に追加できます。 まずは公式の紹介を見てみましょう。 ルータ.addRoutes router.addRoutes(ルート: Array<RouteConfig>) ルーティング ルールを動的に追加します。引数は、 例えば: const router = 新しいルーター({ ルート: [ { パス: '/login', 名前: 'ログイン', コンポーネント: () => import('../components/Login.vue') }, {パス: '/'、リダイレクト: '/home'}、 ] }) 上記のコードは次のコードと同じ効果があります。 const router = 新しいルーター({ ルート: [ {パス: '/'、リダイレクト: '/home'}、 ] }) ルータ.addRoutes([ { パス: '/login', 名前: 'ログイン', コンポーネント: () => import('../components/Login.vue') } ]) ルートを動的に追加するプロセスで、404 ページがある場合は最後に追加する必要があります。そうしないと、ログイン中にページを追加した後に 404 ページにリダイレクトされます。 これと同様に、このルールは最後に追加する必要があります。 {パス: '*'、リダイレクト: '/404'} 動的に生成されたメニュー バックグラウンドから返されるデータが次のようになると仮定します。 // 左メニューバーデータ menuItems: [ { name: 'home', // ジャンプ先のルートの名前はパスではありません size: 18, // アイコンのサイズ type: 'md-home', // アイコンのタイプ text: 'Homepage' // テキストの内容 }, { テキスト: 'セカンダリメニュー'、 タイプ: 'ios-paper', 子供たち: [ { タイプ: 'ios-grid'、 名前: 't1', テキスト: '表' }, { テキスト: 'レベル 3 メニュー'、 タイプ: 'ios-paper', 子供たち: [ { タイプ: 'ios-notifications-outline', 名前: 'メッセージ', テキスト: 'メッセージを表示' }, ] } ] } ] これをメニュー バーに変換する方法を見てみましょう。車輪の再発明を避けるために、ここでは iview コンポーネントを使用しました。 <!-- メニューバー --> <メニュー ref="asideMenu" theme="dark" width="100%" @on-select="gotoPage" アコーディオン :open-names="openMenus" :active-name="currentPage" @on-open-change="menuChange"> <!-- ダイナミック メニュー --> <div v-for="(item, index) in menuItems" :key="index"> <サブメニュー v-if="item.children" :name="index"> <テンプレートスロット="タイトル"> <アイコン :size="item.size" :type="item.type"/> <span v-show="isShowAsideTitle">{{item.text}}</span> </テンプレート> <div v-for="(subItem, i) in item.children" :key="index + i"> <サブメニュー v-if="subItem.children" :name="index + '-' + i"> <テンプレートスロット="タイトル"> <アイコン :size="サブアイテム.size" :type="サブアイテム.type"/> <span v-show="isShowAsideTitle">{{subItem.text}}</span> </テンプレート> <MenuItem class="menu-level-3" v-for="(threeItem, k) in subItem.children" :name="threeItem.name" :key="index + i + k"> <アイコン :size="threeItem.size" :type="threeItem.type"/> <span v-show="isShowAsideTitle">{{threeItem.text}}</span> </メニュー項目> </サブメニュー> <MenuItem v-else v-show="isShowAsideTitle" :name="サブアイテム名"> <アイコン :size="サブアイテム.size" :type="サブアイテム.type"/> <span v-show="isShowAsideTitle">{{subItem.text}}</span> </メニュー項目> </div> </サブメニュー> <メニュー項目 v-else :name="item.name"> <アイコン :size="item.size" :type="item.type" /> <span v-show="isShowAsideTitle">{{item.text}}</span> </メニュー項目> </div> </メニュー> コードを注意深く見る必要はなく、原理を理解するだけで十分です。実際には、v-for でサブ配列を 3 回ループして、3 レベルのメニューを生成するだけです。 ただし、この動的メニューには、3 レベルのメニューしかサポートされないという欠点があります。より良いアプローチは、メニューを生成するプロセスをコンポーネントにカプセル化し、それを再帰的に呼び出すことです。これにより、無制限の数のメニューをサポートできるようになります。メニューを作成するときは、サブメニューがあるかどうかを判断する必要があります。サブメニューがある場合は、コンポーネントを再帰的に呼び出します。 前述のように、動的ルーティングは addRoutes を使用して実装されます。では、具体的にどのように行うかを見てみましょう。 まず、プロジェクトのすべてのページ ルートを一覧表示し、バックグラウンドによって返されたデータを使用してそれらを動的に一致させます。一致するものがあればルートを追加し、一致しない場合は追加しません。最後に、addRoutes を使用して、新しく生成されたルーティング データをルーティング テーブルに追加します。 定数asyncRoutes = { '家': { パス: 'ホーム', 名前: 'ホーム', コンポーネント: () => import('../views/Home.vue') }, 't1': { パス: 't1', 名前: 't1', コンポーネント: () => import('../views/T1.vue') }, 'パスワード': { パス: 'パスワード', 名前: 'パスワード', コンポーネント: () => import('../views/Password.vue') }, 'メッセージ': { パス: 'msg', 名前: 'メッセージ', コンポーネント: () => import('../views/Msg.vue') }, 'ユーザー情報': { パス: 'userinfo', 名前: 'userinfo', コンポーネント: () => import('../views/UserInfo.vue') } } // ルーティングテーブルを生成するためにバックグラウンドデータを渡します menusToRoutes(menusData) // メニュー情報を対応するルート情報に変換し、動的に追加します function menusToRoutes(data) { 定数結果 = [] 定数の子 = [] 結果.push({ パス: '/'、 コンポーネント: () => import('../components/Index.vue'), 子供たち、 }) データ.forEach(アイテム => { ルートを生成する(子、アイテム) }) children.push({ パス: 'エラー'、 名前: 'エラー'、 コンポーネント: () => import('../components/Error.vue') }) // 最後に 404 ページを追加します。そうしないと、ログイン成功後に 404 ページにジャンプします result.push( {パス: '*'、リダイレクト: '/error'}、 ) 結果を返す } 関数 generateRoutes(children, item) { if (アイテム名) { children.push(asyncRoutes[item.name]) } そうでない場合 (item.children) { アイテム.children.forEach(e => { ルートを生成する(子供、e) }) } } 動的メニューのコード実装は、このプロジェクトの 3. 前方更新は行われますが、後方更新は行われません。要件 1:リストページでは、初めて入力するときにデータの取得を要求します。 リスト項目をクリックして詳細ページに移動し、詳細ページからリストページに戻ると、ページは更新されません。 つまり、他のページから一覧ページに入るときにはデータを取得するために更新する必要があり、詳細ページから一覧ページに戻るときには更新しないということです。 解決 App.vue のセットアップ: <キープアライブ include="リスト"> <ルータービュー/> </キープアライブ> リスト ページが list.vue、詳細ページが detail.vue であり、どちらもサブコンポーネントであると仮定します。 キープアライブにリストページの名前を追加し、リストページをキャッシュします。 次に、リスト ページの作成関数に Ajax リクエストを追加して、リスト ページに初めてアクセスしたときにのみデータがリクエストされるようにします。リスト ページから詳細ページに移動し、詳細ページから戻った場合、リスト ページは更新されません。そうすれば問題は解決します。 要件2: 要件 1 に基づいて、別の要件が追加されました。詳細ページで対応するリスト項目を削除できます。このとき、リスト ページに戻るときに、データを更新して再取得する必要があります。 ルーティング設定ファイルの detail.vue にメタ属性を追加できます。 { パス: '/detail', 名前: '詳細', コンポーネント: () => import('../view/detail.vue'), メタ: {isRefresh: true} }, このメタ属性は このプロパティを設定した後、App.vue ファイルで watch $route プロパティも設定する必要があります。 時計: $route(宛先、送信元) { const fname = from.name 定数 tname = to.name if (from.meta.isRefresh || (fname != 'detail' && tname == 'list')) { from.meta.isRefresh = false // ここでデータを再要求} } }, こうすることで、リストページの作成機能でデータをリクエストするために ajax を使用する必要がなくなり、すべての処理を App.vue 内で行うことができます。 リクエスト データをトリガーする条件は 2 つあります。 リストが他のページ(詳細ページを除く)から入ってくる場合は、データを要求する必要があります。詳細ページからリストページに戻るときに、詳細ページのメタ属性の isRefresh が true の場合は、データの再要求も必要です。 詳細ページで対応するリスト項目を削除する場合は、詳細ページのメタ属性の isRefresh を true に設定できます。このとき、リストページに戻るとページが更新されます。 解決策2 実際には、要件 2 に対するより簡単な解決策があり、それは router-view の <キープアライブ> <ルータービュー:key="$route.fullPath"/> </キープアライブ> まず、keep-alive ではすべてのページをキャッシュすることができます。特定のルートページをキャッシュせずにリロードしたい場合には、ジャンプ時にランダムな文字列を渡すことでリロードすることができます。たとえば、リスト ページから詳細ページに入り、リスト ページでオプションを削除した場合、詳細ページからリスト ページに戻るときに更新する必要があります。次のようにジャンプできます。 this.$router.push({ パス: '/list', クエリ: { 'randomID': 'id' + Math.random() }, }) この解決策は比較的簡単です。 4. 複数のリクエストで読み込みを表示して閉じる一般的に、Vue では、次のように axios のインターセプターを使用して、読み込みの表示と終了を制御します。 App.vue でグローバル ローダーを構成します。 <div class="app"> <キープアライブ:include="keepAliveData"> <ルータービュー/> </キープアライブ> <div class="loading" v-show="isShowLoading"> <スピンサイズ="large"></スピン> </div> </div> axios インターセプターも設定します。 // リクエストインターセプターを追加する this.$axios.interceptors.request.use(config => { this.isShowLoading = true 設定を返す }, エラー => { this.isShowLoading = false Promise.reject(error) を返します。 }) // レスポンスインターセプターを追加します this.$axios.interceptors.response.use(response => { this.isShowLoading = false 応答を返す }, エラー => { this.isShowLoading = false Promise.reject(error) を返します。 }) このインターセプターの機能は、リクエストの前に読み込みをオンにし、リクエストが終了するかエラーが発生したときに読み込みをオフにすることです。 一度にリクエストが 1 つしかない場合は、この方法が有効です。しかし、複数の同時リクエストがある場合は問題が発生します。 例: 2 つのリクエストが同時に開始された場合、リクエストの前に、インターセプター 現在、1 つのリクエストがクローズされています。 その結果、ページリクエストが完了していないのに、読み込みがオフになります。ユーザーはページの読み込みが完了したと認識します。その結果、ページは正常に実行できず、ユーザーエクスペリエンスが低下します。 解決 リクエストの数をカウントするための loadingCount 変数を追加します。 読み込み回数: 0 loadingCount を増減するためのメソッドをさらに 2 つ追加します。 メソッド: { ロードを追加します(){ this.isShowLoading = true this.loadingCount++ }, isCloseLoading() { this.loadingCount-- (this.loadingCount == 0)の場合{ this.isShowLoading = false } } } インターセプターは次のようになります。 // リクエストインターセプターを追加する this.$axios.interceptors.request.use(config => { this.addLoading() 設定を返す }, エラー => { this.isShowLoading = false this.loadingCount = 0 this.$Message.error('ネットワーク例外です。しばらくしてからもう一度お試しください') Promise.reject(error) を返します。 }) // レスポンスインターセプターを追加します this.$axios.interceptors.response.use(response => { this.isCloseLoading() 応答を返す }, エラー => { this.isShowLoading = false this.loadingCount = 0 this.$Message.error('ネットワーク例外です。しばらくしてからもう一度お試しください') Promise.reject(error) を返します。 }) このインターセプターの機能は次のとおりです。 リクエストが開始されるたびに、ロードがオンになり、loadingCount が 1 増加します。 リクエストが終了するたびに、loadingCount が 1 ずつ減算され、loadingCount が 0 かどうかを判断します。0 の場合、ロードは終了しています。 これにより、複数のリクエストのうちの 1 つが早期に終了し、読み込みが終了するという問題を解決できます。 5. フォーム印刷印刷に必要なコンポーネントはprint-jsです 通常のフォーム印刷 一般的なテーブル印刷の場合は、コンポーネントによって提供される例に従うだけです。 プリントJS({ printable: id, // DOM ID タイプ: 'html', スキャンスタイル: false、 }) element-ui テーブルの印刷 (他のコンポーネント ライブラリのテーブルにも同じことが当てはまります) element-ui テーブルは 1 つのテーブルのように見えますが、実際には 2 つのテーブルで構成されています。 ヘッダーはテーブルであり、本文は別のテーブルであるため、印刷時に本文とヘッダーの位置がずれるという問題が発生します。 また、表にスクロールバーが表示されると、位置ずれも発生します。 解決 私のアイデアは、2 つのテーブルを 1 つのテーブルに結合することです。print-js コンポーネントが印刷するとき、実際には id に対応する DOM 内のコンテンツを抽出して印刷します。したがって、ID を渡す前に、ヘッダーが配置されているテーブル コンテンツを抽出し、2 番目のテーブルに挿入して、2 つのテーブルを結合することができます。このとき、印刷時に位置がずれる問題は発生しません。 関数 printHTML(id) { const html = document.querySelector('#' + id).innerHTML // 新しいDOMを作成する 定数div = document.createElement('div') 定数 printDOMID = 'printDOMElement' div.id = 印刷DOMID div.innerHTML = HTML // 最初のテーブルの内容、つまりヘッダーを抽出します。const ths = div.querySelectorAll('.el-table__header-wrapper th') 定数 ThsTextArry = [] (i = 0, len = ths.length; i < len; i++) の場合 { ths[i].innerText !== '' の場合、thsTextArry.push(ths[i].innerText) } // 余分なヘッダーを削除する div.querySelector('.hidden-columns').remove() // 最初のテーブルの内容は抽出後には役に立たなくなります。div.querySelector('.el-table__header-wrapper').remove() を削除します。 // 最初のテーブルの内容を 2 番目のテーブルに挿入します。let newHTML = '<tr>' (i = 0, len = ThsTextArry.length; i < len; i++) の場合 { newHTML += '<td style="text-align: center; font-weight: bold">' + ThsTextArry[i] + '</td>' } 新しいHTML += '</tr>' div.querySelector('.el-table__body-wrapper table').insertAdjacentHTML('afterbegin', newHTML) // 新しい DIV をページに追加し、印刷後に削除します。document.querySelector('body').appendChild(div) プリントJS({ 印刷可能: printDOMID、 タイプ: 'html', スキャンスタイル: false、 style: 'table { border-collapse: collapse }' // 表のスタイル}) div.削除() } 6. バイナリファイルをダウンロードする通常、フロントエンドでファイルをダウンロードする方法は 2 つあります。1 つは、バックグラウンドで URL を提供し、 最初の方法は比較的簡単なので、ここでは説明しません。この記事では主に 2 番目の方法を実装する方法について説明します。 2 番目の方法では Blob オブジェクトを使用する必要があります。これは mdn ドキュメントで次のように説明されています。
具体的な使用法 アクシオス({ メソッド: 'post'、 URL: '/エクスポート', }) .then(res => { // データは返されたバイナリデータであると仮定します const data = res.data const url = window.URL.createObjectURL(新しい Blob([データ]、{タイプ: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"})) 定数リンク = document.createElement('a') link.style.display = 'なし' link.href = URL link.setAttribute('ダウンロード', 'excel.xlsx') document.body.appendChild(リンク) リンク.クリック() document.body.removeChild(リンク) }) ダウンロードしたファイルを開いて、結果が正しいかどうかを確認します。 意味不明な言葉がいっぱい… 何かが間違っているに違いない。 最終的に、問題はサーバー応答のデータ型を示すパラメータ responseType にあることが判明しました。バックエンドから返されるデータはバイナリ データなので、それを arraybuffer に設定し、結果が正しいかどうかを確認する必要があります。 アクシオス({ メソッド: 'post'、 URL: '/エクスポート', レスポンスタイプ: '配列バッファ', }) .then(res => { // データは返されたバイナリデータであると仮定します const data = res.data const url = window.URL.createObjectURL(新しい Blob([データ]、{タイプ: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"})) 定数リンク = document.createElement('a') link.style.display = 'なし' link.href = URL link.setAttribute('ダウンロード', 'excel.xlsx') document.body.appendChild(リンク) リンク.クリック() document.body.removeChild(リンク) }) 今回は問題なく、ファイルは正常に開くことができ、内容も正常で、文字化けもなくなりました。 バックグラウンドインターフェースのコンテンツに基づいてファイルをダウンロードするかどうかを決定します 作者のプロジェクトにはファイルのダウンロードを必要とするページが多数あり、この要件は少し異常です。 具体的な要件は次のとおりです
まずは分析してみましょう。まず、上記によれば、ファイルをダウンロードするためのインターフェース応答データ型は arraybuffer であることがわかっています。返されるデータがバイナリ ファイルであるか JSON 文字列であるかに関係なく、フロント エンドが受信するのは実際には配列バッファーです。そのため、arraybuffer の内容を判断し、データを受信するときに文字列に変換し、コード: 199999 があるかどうかを確認する必要があります。ある場合はエラーメッセージが表示されます。ない場合は正常なファイルなのでダウンロードできます。具体的な実装は以下のとおりです。 xios.interceptors.response.use(レスポンス => { const res = レスポンス.data // 応答データ型がArrayBufferかどうかを判定します。trueはファイルダウンロードインターフェース、falseは通常のインターフェースです。if (res instanceof ArrayBuffer) { const utf8decoder = 新しい TextDecoder() const u8arr = 新しい Uint8Array(res) // バイナリデータを文字列に変換 const temp = utf8decoder.decode(u8arr) temp.includes('{code:199999') の場合 メッセージ({ // 文字列をJSONオブジェクトメッセージに変換: JSON.parse(temp).msg, タイプ: 'エラー'、 期間: 5000、 }) Promise.reject() を返す } } // 通常型インターフェース、コードは省略... 戻り値 }, (エラー) => { // コードは省略... Promise.reject(error) を返します。 }) 7. console.log ステートメントを自動的に無視するエクスポート関数 rewirteLog() { console.log = (関数 (log) { process.env.NODE_ENV == 'development' を返しますか? ログ: function() {} }(コンソールログ)) } この関数を main.js にインポートし、1 回実行すると console.log ステートメントが無視されます。 要約するVue の一般的な問題と解決策に関するこの記事はこれで終わりです。Vue に関するその他の FAQ については、123WORDPRESS.COM で以前の記事を検索するか、以下の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。 以下もご興味があるかもしれません:
|
>>: CentOS7 デプロイメント Flask (Apache、mod_wsgi、Python36、venv)
目次1. WeChat Payを開く1.1 アフィリエイト加盟店番号1.2 加盟店番号を追加する1....
以前、Docker コンテナの起動後にボリュームをマウントできるかどうか尋ねられたことがあります。m...
テキストシャドウテキストシャドウ: 水平オフセット 垂直オフセット ぼかし色互換性: IE10+ &...
この記事では、ランダムロールコールを実装するためのjsの具体的なコードを参考までに共有します。具体的...
背景説明: 既存の負荷分散装置には、付加価値状態にある指標があります (増加するだけで減少しないため...
概要バックグラウンド管理システムには多くのフォーム要件があります。データをjson 形式で書き込み、...
この記事の例では、JavaScriptで等速アニメーションを実装するための具体的なコードを参考までに...
目次序文VMware クローン仮想マシン (準備、3 台の仮想マシンのクローン、1 台のマスター、2...
nginx でファイルサーバーを構築することもありますが、これは一般に公開されていますが、サーバーが...
1. golang:最新のベースイメージ mkdir gotest タッチメイン.go Docker...
#docker 検索#docker プルポーター1. イメージを取得した後、中国語パッケージをダウン...
grubの起動時間を変更するためのオンライン検索は基本的に/etc/default/grubを変更す...
最近VScodeのリモート開発機能をいじっています。Dockerのコンテナに接続できるほか、WSLに...
背景: 1. データベースに通知テーブルがある あなたは見ることができますgmt_create、通知...
1. 自然なレイアウト<br />レイアウトは変更せずに自動的に左揃えになります。 2....