Cloud E office Springboot+vue - フロントエンド プロジェクトの完全版 (ソース コードを含む)

1. プロジェクト紹介

プロジェクトの背景: 疫病の影響を受け、多くの企業がオンライン オフィスからオフライン オフィスに切り替えました。オンラインで働く人の増加に伴い、ワークフローの自動化、企業のオフィスコストの削減、グリーンオフィスの実現、オフィス効率の向上など、オンラインオフィスの利点が徐々に顕著になってきています。

プロジェクトの紹介: このプロジェクトは、日常の事務処理 (日々のプロセスの承認、ニュース、通知、お知らせ、ファイル情報、財務、人事、経費、資産、管理、プロジェクト、モバイル オフィスなど) を管理するためのオンライン オフィス システムを実装します。ソフトウェアを通じてオフィスシステムを都合に合わせて管理することができ、全体の管理・運用レベルを向上させることができます。

実装方法:本プロジェクトはVue+Spring Bootをベースにフロントエンドとバックエンド分離プロジェクトを構築します。フロントエンドは、コミュニティで非常に活発なオープンソース フレームワーク Vue を使用して構築されています。簡単に言うと、フロントエンドとバックエンドの分離の中心的な考え方 は、フロントエンド ページが ajax を介してデータ対話のためにバックエンドの Restuful API を呼び出し、一方、シングル ページ アプリケーション(シングル ページ Web アプリケーション、SPA) が呼び出すということです。ページが 1 つだけあり、ユーザーがアプリケーションと対話するときにページを随時動的に更新する Web アプリケーション。

1.1 技術アーキテクチャ

1.2 フロントエンドの技術アーキテクチャ

このプロジェクトではフロントエンドとバックエンドを分離した開発モードを採用しており、Spring Boot を使用してバックエンドを構築します。

フロントエンドで使用されるテクノロジーは次のとおりです。

Vue、Vue-cli、Vuex、VueRouter、ElementUI、Axios、ES6、Webpack、WebSocket、font-awesome、js-file-download、vue-chat

プロジェクト構築:Vue-cli、状態管理:Vuex、ルーティング管理:VueRouter、UIインターフェース:ElementUI、通信フレームワーク:Axios、

フロントエンド構文: ES6; パッケージ化ツール: Webpack; オンライン チャット: WebSocket; フォント: font-awesome; ファイルのアップロードとダウンロード: js-file-download; オンライン チャット オープンソース プロジェクト: vue-chat

フロントエンドモジュールは、ログイン、ポジション管理、役職管理、部門管理、オペレータカレンダー、従業員管理、給与口座管理、パーソナルセンター、オンラインチャットに分かれています。

1.3 クラウド E オフィス (フロントエンド)

JSON データは、RESTful API を通じてフロントエンドとバックエンドの間で通信されます。JSP などとは異なり、バックエンドはページ自体のコンテンツには関与しません。開発時、フロントエンドはフロントエンド サーバー (Nginx) を使用し、バックエンドはバックエンド サーバー (Tomcat) を使用します。フロントエンド コンテンツを開発するとき、フロントエンド リクエストを次のサーバーに転送できます。フロントエンド サーバー (リバースプロキシと呼ばれる) を介してバックエンドに接続するため、リアルタイムで結果を観察できます。バックエンドがどのように実装されているかを知る必要はなく、バックエンドによって提供される機能だけを知っていれば十分です。インターフェース。

目次

1. プロジェクト紹介

1.1 技術アーキテクチャ

1.2 フロントエンドの技術アーキテクチャ

1.3 クラウド E オフィス (フロントエンド)

2. Vue.js フレームワーク

2.1 Vueの機能

2.2 MVVM設計パターン

2.3 vue関連コンポーネント

3. vue.js プロジェクトをビルドする

3.1 vue.js プロジェクトのビルド

3.2 フロントエンドプロジェクトのビルド

3.3 Vue プロジェクトの構造分析

3.4 Element-UIのインストール

3.5 axios をインストールする

3.6 Vuex のインストール

3.7 VueRouterのインストール

3.8 font-awesome のインストール

4. フロントエンドインターセプター

4.1 ログインインターセプターの構成

4.2 axiosリクエストインターセプタリクエスト

4.3 Axios 応答インターセプター応答

4.4 パッケージリクエスト

4.5 コード実装 src/utils/api.js

4.6 Main.js はカプセル化リクエストをグローバルに導入します

5. ランディングページ

5.1 スタイルデザイン

5.2 ランディングページの機能設計

5.3 Login.vue ログイン ページ

5.4 ページルーティングの構成 - router/index.js

5.5 フロントエンドルーティングおよびナビゲーションガード

メイン.js

5.6 フロントエンドとバックエンドのクロスドメイン問題の解決

フロントエンド リバース プロキシ

5.7 プロジェクトの実行

6. ホームページ  

 6.1 メニュー機能の設計と実装

ストア/index.jsを設定する

Main.jsでストアを導入

6.2 パッケージメニューリクエストツール

バックエンドはメニュー インターフェイスに情報を返すように要求します。

main.jsを更新する

6.3 スタイルデザイン

6.4 Home.vue代码

6.5 ルートルーター/index.jsを更新する

router/index.js の hidden:true を無視します。

6.6 Index.html の余白を削除する

7. 基本情報の設定

7.1 スタイルデザイン

タブタブページ

7.2 コンポーネントベースの開発

7.3 SysBasic.vue

7.4 部門管理 DepMana.vue コンポーネント

7.5 ポジション管理 PosMana.vue コンポーネント

 PosMana.vue

7.6 タイトル管理 JobLevelMana.vue コンポーネント

7.7 権限グループ PositionMana.vue コンポーネント

八、オペレーター管理

SysAdmin.vue

9. 従業員情報

 EmpBasic.vue

10. 給与口座管理

 サルソブビュー

11. スタッフアカウントセットの設定

SalSobCfg.vue

12.チャット機能

統合プロジェクト

コンポーネント

カードビュー

list.vue

メッセージ.ビュー

ユーザーテキスト.vue

13、個人センター

 AdminInfo.vue

14、ソースコード


2. Vue.js フレームワーク

Webフロントエンド開発は、その黎明期から隆盛期、そして現在に至るまで長年にわたって開発されており、多くの開発経験や開発ツールが蓄積されています。過去の開発者の経験の中には、技術開発や環境の変化によって試されるものもあり、彼らが生み出したアイデアや技術、ツールは、後続の開発者にとっても非常に参考・学習し、直接活用する価値があります。なぜなら、開発ツールや開発言語がどれほど発展しても、それらがどれほど異なっていても、それらが解決する問題は類似しており、統一されているため、次のように要約できます。

(1) JavaScript、html、CSS の 3 つのプログラミング言語の言語機能を拡張します。

(2) 開発プロセスにおける繰り返し作業を解決します。

(3) プロジェクトをモジュール化する。

(4) 機能の再利用と変更の問題を解決します。

(5) 開発環境と製品環境の違いの問題を解決します。

(6) リリースプロセスの問題を解決します。

上記の問題を解決するために、エンジニアリングの考え方が生まれました。エンジニアリングはホイールの繰り返し作成を避けるためのベストプラクティスです。Vue.js はユーザー インターフェイスを構築するための進歩的なフレームワークで、2013 年に中国のプログラム開発者 You Yuxi によって開発されました。Vue.js の簡潔な構文設計、軽量かつ高速な機能は技術コミュニティの開発者の間で人気があるため、Vue.js の宣伝と人気も促進されます。関連ツールとサポート ライブラリを使用すると、Vue.js は複雑な単一ページ アプリケーションを完全に駆動し、大規模な Web アプリケーションを開発できます。Vue.js は、権威ある JavaScript トレンド リストでトップ 30 に躍り出ており、今後もリストの最前線にランクされ続ける可能性があり、明らかに世界でトップの JavaScript フレームワークとなっています。Vue.js の生態はトレンド リストに反映されているだけでなく、それをサポートするデータ管理ライブラリ vuex、ルーティング管理ライブラリ Vue-router、パッケージ化ツール、開発者デバッグ プラグイン、プロジェクト スキャフォールディングなどのツールやライブラリも徐々に開発されています。 、非常に活発な技術コミュニティもあります。

Vue.js は、プログレッシブ ユーザー インターフェイスの構築に使用できる軽量の MVVM フロントエンド フレームワークです。開発者が Vue.js でフロントエンド ページを構築するときは、ページ ロジックの実装のみを気にする必要があります。Vue.js の最大の特徴は、最下層からレイヤーごとに適用されることであり、使いやすいだけでなく、多数のサードパーティ ライブラリと互換性があります。

2.1 Vueの機能

仮想 DOM

従来のフレームワークと異なる vue の特徴は、仮想 DOM です。ブラウザの DOM 操作は多大なオーバーヘッドをもたらすため、仮想 DOM は diff アルゴリズムを通じて Vue に構築され、わずかな変更でデータが更新されるたびに仮想 DOM が再構築されます。

レスポンシブ

Vue.js のコアは応答性の高いデータ バインディング システムとして設計されているため、データを DOM と同期しておくのは非常に便利です。jQuery を使用して DOM を手動で操作すると、多くの場合、命令的で反復的でエラーが発生しやすいコードを作成する傾向があります。また、Vue.js はデータ駆動型ビューの概念を採用しています。これは、通常の HTML テンプレートで特別な構文を使用して、DOM を基礎となるデータに「バインド」することを意味します。このバインディングが作成されると、DOM はデータとの同期が維持されます。データが変更されると、それに応じて DOM も更新されます。このようにして、アプリケーション開発では、ほとんどすべてのビジネス ロジックでデータを直接変更するだけでよく、DOM で別の更新操作を実行する必要がないため、データと DOM の更新が混在することはありません。これにより、アプリケーション コードの作成、理解、保守が容易になります。

コンポーネント化

大規模なアプリケーション開発では、コード ブロックの再利用性と保守性を目的として、アプリケーションが複数の比較的独立したモジュールに抽象化されることがよくあります。ただし、あるモジュールを別コンポーネント化するのは再利用性を考慮した場合であり、実際にはWebのビューインターフェースもコンポーネントツリーに分割することができます。コンポーネント化は、Vue.js の最も強力な機能の 1 つです。コンポーネントはビュー ページのラベル要素を拡張およびカプセル化でき、最終的には再利用可能なコードになります。大まかに理解すると、コンポーネントはカスタム要素にすることもでき、Vue.js コンパイラーのコンパイルを通じて、いくつかの特別な関数をこの要素に追加できます。同時に、コンポーネントは、機能を通じて拡張されたネイティブ HTML 要素にすることもできます。Vue.js は、やはりコンポーネント化の考え方を重視したフロントエンド フレームワークである React.js に似ていますが、Vue.js よりも軽量でシンプルで高度です。

コンポーネント化とは通常、Vue.js が JavaScript コード、ハイパーテキスト マークアップ言語 (HTML) コード、およびカスケード スタイル シート (カスケード スタイル シート、CSS) コードを同じファイル内に記述できることを意味します。実際の開発では、ページの機能を複数回使用する必要がある場合がよくありますが、その際、再利用可能なコンポーネントをコンポーネントディレクトリに構築できます。他のページでこのコンポーネントを使用する必要がある場合は、インポート メソッドを通じて導入できます。ページは複数のコンポーネントで構成されているため、コンポーネント間の結合が低く、コードの繰り返しが大幅に削減されます。

部分リフレッシュ

Vue はシングル ページ アプリケーションです。シングル ページ アプリケーションの主な機能は Web ページの部分更新です。Web ページ アプリケーションはルーティングの制御を通じて AJAX を呼び出し、バックグラウンドはそれを実現するためのインターフェイスを提供するだけで済みます。このようなアプリケーションには明らかな利点があり、まず第一に、ユーザー エクスペリエンスの点でユーザー フレンドリーになり、ページ全体を更新する必要がないため、読み込み速度が速く、エクスペリエンスが向上します。

2.2 MVVM設計パターン

B/S アーキテクチャに基づく Java Web アプリケーション システムを開発する場合、フロントエンド ページの描画と美化はシステム開発の重要な作業です。ページの描画と対話は通常、ドキュメント オブジェクト モデル (ドキュメント オブジェクト モデル、DOM) 要素ノードとデータの操作に基づいていますが、DOM ノードの直接操作はエラーが非常に発生しやすくなります。近年、フロントエンド技術の発展に伴い、MVVM(Model-View-ViewModel)設計パターンに基づいたさまざまなフロントエンドフレームワークが登場し、開発や保守に大きな利便性をもたらしています。フロントエンドプロジェクト。MVVM 設計パターンは従来の MVC 設計パターンから派生したもので、正式名は Model-View-ViewModel です。Model レイヤーはユーザー データを保持する役割を担い、View レイヤーは画面上に視覚要素とコントロールを表示する役割を担い、ViewModel レイヤーはモデルをビューに直接表示できる値に変換する役割を担います。

2.3 vue関連コンポーネント

足場 vue-cli

高速開発ツールvue-cliこれは、開発者が vue.js フレームワークに基づいて迅速に開発するのに役立ちます。Vue-cli はさまざまなツールを標準化し、さまざまな構築ツールがインテリジェントなデフォルト構成に基づいてスムーズに接続できるようにするため、開発者はプロジェクト構成の調整に長い時間を費やすことなく、フロントエンド アプリケーションの作成に集中できます。

ルーティング Vue ルーター

Vue.js で構築されたシングルページ Web アプリケーションは、ルーティングとコンポーネントに基づいている必要があります。このうち、ルーティングの主な機能は、アクセス パスの設定、アクセス パスのマッピング、コンポーネントの表示です。シングルページ Web アプリケーションでは、パス間のジャンプと切り替えは、実際には対応するコンポーネント間の切り替えになります。React.js と同様に、Vue.js 自体にはルーティング機能はありません。したがって、Vue.js フレームワークを使用する場合は、ルーティング ツール ライブラリである Vue-router と連携する必要があります。Vue-router は、さまざまなレベルとネストされたルーティング関係を、対応するネストされたコンポーネントにマッピングでき、パス ジャンプを制御するためのきめ細かいソリューションを提供します。

状態管理ビューックス

Vue.js のビューの変化はコンポーネントの状態に基づいて行われるため、大規模な Web アプリケーションを構築する場合、多数のコンポーネントの状態が生成され、それらの状態を管理する必要があります。vue の「一方向データ フロー」では複数のコンポーネントが状態を共有する問題を解決できないため、vuex が存在します。Vuex は、アプリケーションのすべてのコンポーネントの状態を一元的に保存して管理する管理フレームワークであり、Vue.js フレームワークを使用するアプリケーション用に特別に設計されています。これは、React.js の状態管理ツールである Flux と Redux の設計概念を利用しており、Vue.js のデータ応答メカニズムをより適切に利用できるように、いくつかの概念を簡素化しています。

コミュニケーションフレームワークaxios

コミュニケーションの枠組み。vue の境界は非常に明確であり、DOM を処理するためのものであるため、通信機能はありません。現時点では、サーバーと対話するために追加の通信フレームワークを使用する必要があります。もちろん、直接使用することもできます。 jQueryが提供するAJAX通信機能。古典的な Ajax テクノロジーは Web ページの部分的なデータ更新を実現し、Axios は次の特徴を持つ Ajax を再カプセル化します。

(1) ブラウザからXMLHttpRequestを作成する

(2)node.jsからhttpリクエストを送信する

(3) サポート約束API

(4) リクエストとレスポンスを傍受する

(5) 変換要求とそれに対応するデータ

(6) キャンセルのお申し出

(7) JSONデータを自動変換

(8) クライアントは CSRF/XSRF の防止をサポートします

Axios プラグインは Ajax テクノロジーを非常によくカプセル化しており、プロジェクト開発において記述方法が簡潔かつ明確であるため、間違いを犯しにくく、また間違いがあった場合でもトラブルシューティングが簡単です。

パッケージングツールWebpack

フロントエンド開発と他の開発作業の主な違いは、フロントエンドが多言語、マルチレベルのコーディングと編成作業に基づいていること、次に、フロントエンド製品の配信がブラウザに基づいていることです。これらのリソースは、増分読み込みによってブラウザーに実行されます。開発環境でこれらの断片化されたコードとリソースを整理し、ブラウザーでの高速かつエレガントな読み込みと更新を確実に行うには、モジュラー システムが必要です。

webpackは大規模な js アプリケーション用のモジュール型ツールで、名前空間などの一連の js プログラミングの問題を自動的に処理します。webpack がjs アプリケーションを処理するとき、複雑な依存関係グラフが構築されます。このグラフには、アプリケーションが依存するモジュールが含まれ、さらに多くの静的リソースも含まれます。その後、webpack はこれらのモジュールを、アプリケーションで参照される 1 つ以上の大きなモジュールにパッケージ化します

ES6モジュール

Vue は通常、es6 で書かれ、エクスポート デフォルトでエクスポートされます。エクスポート デフォルトには、データ、ライフ サイクル (マウントなど)、メソッド (メソッド) などが含まれます。具体的な構文については、vue.js ドキュメントを参照してください。ES6 標準では、JavaScript 言語レベルでモジュール システム定義が追加されています。ES6 モジュールの設計思想は、モジュールの依存関係や入出力変数をコンパイル時に決定できるように、できる限り静的であることです。CommonJS モジュールと AMD モジュールは両方とも、実行時にのみこれらのことを決定できます。

UIフレームワーク

Ele.me によって開始された ElementUI

3. vue.js プロジェクトをビルドする

3.1 vue.js プロジェクトのビルド

環境整備

Node.js をインストールします (6.x 以上、8.x を推奨) このプロジェクトのバージョンは v14.18.0 です

Vue CLIをインストールする

Vue CLI をインストールするには npm を使用する必要があり、npm は Node.js に統合されているため、Node.js をインストールする最初のステップとして、公式 Web サイト Node.js にアクセスし、ホームページからダウンロードできます

ダウンロードが完了したら、インストール パッケージを実行し、次の手順に進みます。

次に、cmd に「node -v」と入力して、ノードが正常にインストールされているかどうかを確認します。

npm -v と入力して npm のバージョン番号を表示します

npm -g install npm と入力して、npm を最新バージョンに更新します。

その後、npm install -g vue-cliでスキャフォールディングをインストールします 。(このプロジェクトはバージョン 2.9.6 を使用します)

Vue CLI の 2.x バージョンはこの方法でインストールされますが、最新バージョンは npm install -g @vue/cli を通じてインストールする必要があることに注意してください。新しいバージョンでは、グラフィカル インターフェイスを使用してプロジェクトを初期化し、プロジェクトの健全性監視のコンテンツを追加できますが、新しいバージョンで作成されたプロジェクトの依存関係がこのチュートリアルと一致せず、トスするのが面倒です。

タオバオ ミラー アクセラレータ cnpm がインストールされた Node.js

ほとんどの場合は npm を使用し、インストールできない場合は cnpm を使用します

npm インストール cnpm -g

またはnpm install --registry=https://registry.npm.taabao.org

3.2 フロントエンドプロジェクトのビルド

通用方法

コマンドラインを使用してプロジェクトを直接ビルドします。

次に、コマンド vue init webpack yeb を実行します。ここで、webpack は、生成されたプロジェクトを参照するためのテンプレートとして webpack を使用します。また、ここでは説明しませんが、pwa や simple などのパラメーターに置き換えることもできます。

プログラムの実行中にいくつかのプロンプトが表示されます。デフォルト設定に従って Enter キーを押し続けることも、必要に応じて変更することもできます。たとえば、次の図でプロジェクト名が wj-vue かどうかを尋ねられた場合、 、Enter キーを押して確定します。

ここでは、vue-router をインストールするかどうかも尋ねられます。はいを選択する必要があります。つまり、Enter キーを押すか、Y キーを押します。vue-router は、シングルページ アプリケーションを構築するための鍵です。

また、es-lint を使用するかどうかは、N を選択します。

その後、プロジェクトがビルドされるまで待ちます。これで問題ありません。

ワークスペース ディレクトリにプロジェクト フォルダーが生成されていることがわかります。このフォルダーで npm install を実行し、npm run build してから npm run dev を実行する必要があります。

http://localhost:8080にアクセスし、Web ページのデモを表示すれば完了です。

注: vue プロジェクトでは、プロジェクトを開始するために npm runserve を実行する必要がある場合と、npm run dev を使用する必要がある場合があります。違いは何ですか?

違い

デフォルトでは、dev は[email protected] によってデフォルトでサポートされるコマンドです

デフォルトでは、serve は [email protected] 以降でサポートされるコマンドです。

3.3 Vue プロジェクトの構造分析

├── build --------------------------------- 项目构建(webpack)相关配置文件,配置参数什么的,一般不用动

│   ├── build.js --------------------------webpack打包配置文件

│   ├── check-versions.js ------------------------------ 检查npm,nodejs版本

│   ├── dev-client.js ---------------------------------- 设置环境

│   ├── dev-server.js ---------------------------------- 创建express服务器,配置中间件,启动可热重载的服务器,用于开发项目

│   ├── utils.js --------------------------------------- 配置资源路径,配置css加载器

│   ├── vue-loader.conf.js ----------------------------- 配置css加载器等

│   ├── webpack.base.conf.js --------------------------- webpack基本配置

│   ├── webpack.dev.conf.js ---------------------------- 用于开发的webpack设置

│   ├── webpack.prod.conf.js --------------------------- 用于打包的webpack设置

├── config ---------------------------------- 配置目录,包括端口号等。我们初学可以使用默认的。

│   ├── dev.env.js -------------------------- 开发环境变量

│   ├── index.js ---------------------------- 项目配置文件

│   ├── prod.env.js ------------------------- 生产环境变量

│   ├── test.env.js ------------------------- 测试环境变量

├── node_modules ---------------------------- npm 加载的项目依赖模块

├── src ------------------------------------- 我们要开发的目录,基本上要做的事情都在这个目录里。

│   ├── assets ------------------------------ 静态文件,放置一些图片,如logo等

│   ├── components -------------------------- 组件目录,存放组件文件,可以不用。

│   ├── main.js ----------------------------- 主js

│   ├── App.vue ----------------------------- 项目入口组件,我们也可以直接将组件写这里,而不使用 components 目录。

│   ├── router ------------------------------ 路由

├── static ---------------------------- 静态资源目录,如图片、字体等。

├── .babelrc--------------------------------- babel配置文件

├── .editorconfig---------------------------- 编辑器配置

├── .gitignore------------------------------- 配置git可忽略的文件

├── index.html ------------------------------ 首页入口文件,你可以添加一些 meta 信息或统计代码啥的。

├── package.json ---------------------------- node配置文件,记载着一些命令和依赖还有简要的项目描述信息

├── .README.md------------------------------- 项目的说明文档,markdown 格式。想怎么写怎么写,不会写就参照github上star多的项目,看人家怎么写的

主要書類の詳しい解説

src - [プロジェクトコアファイル]

vue-cli プロジェクトでは、基本的に行うべきことはすべてこのディレクトリ内にあるため、src フォルダーをマスターする必要があります。

Index.html - [ホームページ]

Index.html は他の HTML と同じですが、通常は空のルート ノードのみを定義します。main.js で定義されたインスタンスはルート ノードの下にマウントされ、コンテンツは vue コンポーネントによって埋められ、ビルドされたファイルは次のようになります。自動的に挿入されます。これは、作成した他のコンテンツがこの div に表示されることを意味します。プロジェクト全体で HTML ファイルは 1 つだけなので、これはシングルページ アプリケーションです。このアプリケーションを開くと、表面上は多くのページがあるように見えますが、実際にはすべて 1 つの div に含まれています。

<!DOCTYPE html>

<html>

  <head>

    <meta charset="utf-8">

    <title>vuedemo</title>

  </head>

  <body>

      <!-- 定义的vue实例将挂载在#app节点下 -->

    <div id="app"></div>

  </body>

</html>

App.vue - [ルートコンポーネント]

このコンポーネントには他のコンポーネントが含まれているため、このファイルは「ルート コンポーネント」と呼ばれます。.vue ファイルはカスタム ファイル タイプで、構造が html に似ており、.vue ファイルは vue コンポーネントです。

vue ページは通常、template (テンプレート)、js (スクリプト)、style (スタイル) の 3 つの部分で構成されます。

<!-- テンプレート -->

<template><template>

  <div id="app">

    <img src="./assets/logo.png">

    <router-view></router-view>

  </div>

</template>

<!-- script -->

<script>

export default {

  name: 'app'

}

</script>

  <div id="app">

    <img src="./assets/logo.png">

    <router-view></router-view>

  </div>

</template>

<!-- script -->

<script>

export default {

  name: 'app'

}

</script>

<!-- スタイル -->

<style>

#app {

  font-family: 'Avenir', Helvetica, Arial, sans-serif;

  -webkit-font-smoothing: antialiased;

  -moz-osx-font-smoothing: grayscale;

  text-align: center;

  color: #2c3e50;

  margin-top: 60px;

}

</style>

【テンプレート】

テンプレートには親ノードを 1 つだけ含めることができます。つまり、最上位 div は 1 つだけあります (たとえば、上記のコードでは、親ノードは親ノードが #app である div であり、兄弟ノードはありません) )。ここにも <div id="app"> がありますが、index.html のものとは関係ありません。この id=app は、次の CSS に対応します。

<router-view></router-view> はサブルーティング ビューであり、後続のすべてのルーティング ページがここに表示されます。<router-view> はスロットに似ており、あるルートにジャンプすると、そのルートの下のページがこのスロットに挿入されてレンダリング・表示されます。

【脚本】

<script> タグの内容はコンポーネントのスクリプト、つまり js コードです。エクスポートのデフォルトは ES6 の構文です。これは、このコンポーネントを全体としてエクスポートすることを意味します。その後、インポートを使用してコンポーネントをインポートできます。成分。中括弧内の内容は、このコンポーネントの関連プロパティです。

Vue は通常、es6 で書かれ、エクスポート デフォルトでエクスポートされます。エクスポート デフォルトには、データ、ライフ サイクル (マウントなど)、メソッド (メソッド) などが含まれます。具体的な構文については、vue.js ドキュメントを参照してください。

【スタイル】

スタイルは style タグによってラップされており、デフォルトでは全世界に影響します。このコンポーネントの下でのみ機能するようにスコープを定義したい場合は、タグにscoped を追加する必要があります。

外部 CSS ファイルをインポートする場合は、まずプロジェクトの css-loader 依存関係パッケージをインストールし、cmd を開き、プロジェクト ディレクトリに入り、「npm install css-loader」と入力して、Enter キーを押す必要があります。

インストールが完了したら、必要な CSS ファイルを style タグの下にインポートできます。次に例を示します。

<style>

    import './assets/css/public.css'

</style>

main.js - [エントリファイル]

main.js は主に Vue フレームワーク、ルート コンポーネント、ルーティング設定を導入し、Vue インスタンスを定義します。次のコンポーネント: {App} はインポートされたルート コンポーネント App.vue です。プラグインは後から導入することもできますが、もちろん、プラグインを最初にインストールする必要があります。

前に、App.vue の <div id="app"> は、index.html の <div id="app"> とは無関係であると述べましたが、これら 2 つのファイルはどのように接続されるのでしょうか? エントリーファイルmain.jsのコードを見てみましょう。

/*引入vue框架*/

import Vue from 'vue'

/*引入根组件*/

import App from './App'

/*引入路由设置*/

import router from './router'

/*关闭生产模式下给出的提示*/

Vue.config.productionTip = false

/*定义实例*/

new Vue({

  el: '#app',

  router,

  template: '<App/>',

  components: { App }

})

先頭にはいくつかのモジュールがインポートされており、vueモジュールはnode_modulesにあり、AppはApp.vueで定義されたコンポーネント、routerはrouterフォルダーで定義されたルートです。

Vue.config.productionTip = false 、この機能は、vue が起動時にプロダクション ヒントを生成しないようにすることです。

この js ファイルでは、Vue オブジェクト (インスタンス) を作成し、el 属性は、Vue オブジェクトのマウント ターゲットとしてページ上にすでに存在する DOM 要素を提供します。ここでは、index.html の <div id="app を通じて、 "><div> の id="app" とここの "#app" がマウントされます。

router は、オブジェクトに Vue Router が含まれており、プロジェクトで定義されたルートを使用することを意味します。コンポーネントはオブジェクトに含まれる Vue コンポーネントを示し、テンプレートは Vue インスタンスの識別子として文字列テンプレートを使用します。これは HTML タグの定義に似ています。

3.4 Element-UIのインストール

Element の正式アドレスはhttp://element-cn.eleme.io/#/zh-CNです。

1. 要素のインストール

公式ドキュメントの記述に従い、プロジェクトフォルダ内で npm i element-ui -S を実行

ここに画像の説明を挿入

2.要素の導入

インポートはフル インポートとオンデマンド インポートの 2 つのモードに分かれています。オンデマンド インポートの方がプロジェクトのサイズを小さくできます。ここではフル インポートを選択します。

ドキュメントによると、main.js を次のように変更する必要があります。

'element-ui' から ElementUI をインポートします

'element-ui/lib/theme-chalk/index.css' をインポートします

3.5 axios をインストールする

プロジェクトフォルダーに移動して実行します

このモジュールをインストールするには、npm install --save axios を使用します。

3.6 Vuex のインストール

Vuex は、Vue 用に特別に開発された状態管理ソリューションで、各コンポーネントで渡され使用される必要がある変数とメソッドを定義できます。これまで使ったことがないので、さまざまなコンポーネントから値を渡すのは頭の痛いことですし、さまざまなコンポーネントの値を呼び出すために多くの冗長なコードを書かなければならないので、慣れておくことをお勧めします最初からこの管理方法です。

npm install vuex --save を実行します。

その後、src ディレクトリにフォルダー ストアを作成し、そのディレクトリに新しいindex.js ファイルを作成し、そのファイルに vue と vuex を導入します。コードは次のとおりです。

「ビュー」からビューをインポート

「vuex」から Vuex をインポート

Vue.use(Vuex)

vuex のインストール開始エラー「エクスポート 'watch' が 'vue' に見つかりませんでした」

vue のバージョンが 2.X の場合は、vuex を 3.XX にアップグレードすることで解決できます。

npm install --save [email protected]

vue のバージョンが 3.X の場合は、vuex を 4.XX にアップグレードすることで解決できます。

npm install --save [email protected]

npm install --save [email protected]

バージョンの競合を解決する

npm のバージョンの問題である可能性があり、エラーが報告されます

解決策: コマンドの後に追加します

  --legacy-peer-deps

3.7 VueRouterのインストール

npm install vue-router --save-dev

Vue-router は Vue.js の公式ルーティング プラグインで、vue.js と深く統合されており、シングルページ アプリケーションの構築に適しています。Vue のシングルページ アプリケーションはルーティングとコンポーネントに基づいており、ルーティングはアクセス パスの設定、パスとコンポーネントのマップに使用されます。

router フォルダーの下に、ルーティング設定ファイルであるindex.jsがあります。'/index'、'/list' など、複数のルートを設定できます。もちろん、最初にコンポーネントを導入し、次にコンポーネントのルートを設定する必要があります。

3.8 font-awesome のインストール

npm install font-awesome

4. フロントエンドインターセプター

4.1 ログインインターセプターの構成

名前が示すように、インターセプターはリクエストのインターセプト、つまりリクエスト インターフェイスの前後の前処理作業です。それぞれリクエスト インターセプタとレスポンス インターセプタで、実行順序はリクエスト インターセプタ -> API リクエスト -> レスポンス インターセプタです。インターセプターの機能: a. API がリクエストからデータを返すのに必要な時間をカウントする; b. パブリック リクエスト ヘッダーを設定し、ポップアップ ウィンドウをロードするなど; c. 応答ステータス コードをインターセプトして返す400 または 500 をバックエンドのステータス コードと比較すると、対応するエラー メッセージが返されます。

4.2 axiosリクエストインターセプタリクエスト

Vue プロジェクトでは、通常、バックグラウンド データと対話するためにaxiosを使用します。Axios は、ブラウザーおよびノー​​ド環境で実行できる Promise ベースのライブラリです。リクエスト インターセプタ リクエストの機能: リクエストが送信される前に特定の操作を均一に実行し、リクエスト ヘッダー内のトークンの処理などによく使用されます。 

リクエストインターセプターを追加する方法

axios.interceptors.request.use(function (config) {

// 在发送请求之前做些什么

return config;

}, function (error) {

// 对请求错误做些什么

return Promise.reject(error);

})

4.3 Axios 応答インターセプター応答

返されるオブジェクト応答には、response.status: Http 応答コード、response.data: バックエンドによって返される Json オブジェクト (response.data.code ビジネス ロジック応答コードを含む)、response.data.message: バックエンドによって返される応答プロンプト メッセージが含まれます。 ;

レスポンスインターセプターメソッドを追加

axios.interceptors.response.use(function (response) {

// 对响应数据做点什么

return response;

}, function (error) {

// 对响应错误做点什么

return Promise.reject(error);

});

}

4.4 パッケージリクエスト

 このプロジェクトでは、axios を直接使用せず、1 つのレイヤーにカプセル化します。URL と params を受信して​​から axios オブジェクトを受信する postRequest メソッドを定義するなど、エクスポートを通じてカプセル化されたリクエストをエクスポートします。実際のインターフェース呼び出し操作はaxiosで実行します。

export const postRequest = (url, params) => {

    return axios({

        method: 'post',

        url: `${base}${url}`,

        data: params

    })

}

4.5 コード実装 src/utils/api.js

import axios from "axios";

import {Message} from "element-ui";

import router from "@/router";

// 请求拦截器

axios.interceptors.request.use(config => {

    // 如果存在 token,请求携带这个 token( 登录的时候 把 token 存入了 sessionStorage )

    if (window.sessionStorage.getItem("tokenStr")) {

        // token 的key : Authorization ; value: tokenStr

        config.headers['Authorization'] = window.sessionStorage.getItem('tokenStr')

    }

    return config;

},error => {

    console.log(error)

})

// 响应拦截器 - 统一处理消息提示

axios.interceptors.response.use(success => {

    // 业务逻辑错误

    if (success.status && success.status === 200) { // 调到接口

        // 后端:500 业务逻辑错误,401 未登录,403 无权访问;

        if (success.data.code === 500 || success.data.code === 401 || success.data.code === 403) {

            Message.error({message: success.data.message})

            return

        }

        if (success.data.message) { // 输出后端 添加成功 之类的信息

            Message.success({message: success.data.message})

        }

    }

    return success.data

}, error => { // 没访问到后端接口

    if (error.response.code === 504 || error.response.code === 404) {

        Message.error({message: '服务器不存在'})

    } else if (error.response.code === 403) {

        Message.error({message: '权限不足,请联系管理员!'})

    } else if (error.response.code === 401) {

        Message.error({message: '您还未登录,请登录!'})

        router.replace('/') // 路由替换

    } else {

        if (error.response.data.message) {

            Message.error({message: error.response.data.message})

        } else {

            Message.error({message: '未知错误!'})

        }

    }

    return

})

// 预备前置路径

let base = '';

// 传送 json 格式的 post 请求

export const postRequest = (url, params) => {

    return axios({

        method: 'post',

        url: `${base}${url}`,

        data: params

    })

}

// 传送 json 格式的 get 请求

export const getRequest = (url, params) => {

    return axios({

        method: 'get',

        url: `${base}${url}`,

        data: params

    })

}

// 传送 json 格式的 put 请求

export const putRequest = (url, params) => {

    return axios({

        method: 'put',

        url: `${base}${url}`,

        data: params

    })

}

// 传送 json 格式的 delete 请求

export const deleteRequest = (url, params) => {

    return axios({

        method: 'delete',

        url: `${base}${url}`,

        data: params

    })

}


インターセプタは、名前が示すとおり、リクエスト インターセプタとレスポンス インターセプタであるリクエストをインターセプトします。実行順序は、リクエスト インターセプタ -> API リクエスト -> レスポンス インターセプタです。インターセプターの機能: a. API がリクエストからデータを返すのに必要な時間をカウントする; b. パブリック リクエスト ヘッダーを設定し、ポップアップ ウィンドウをロードするなど; c. 応答ステータス コードをインターセプトして返す400 または 500 をバックエンドのステータス コードと比較すると、対応するエラー メッセージが返されます。

4.6 Main.js はカプセル化リクエストをグローバルに導入します

このメソッドは main.js を通じてグローバルに導入され、プラグインを通じて使用されます。呼び出すときは this.putRequest(url, params) の形式を使用します。

import {postRequest} from "@/utils/api";

import {putRequest} from "@/utils/api";

import {getRequest} from "@/utils/api";

import {deleteRequest} from "@/utils/api";
Vue.prototype.postRequest = postRequest

Vue.prototype.putRequest = putRequest

Vue.prototype.getRequest = getRequest

Vue.prototype.deleteRequest = deleteRequest

5. ランディングページ

5.1 スタイルデザイン

インターフェイスを設計するために注目する必要があるのは、<template> タグ内の html と <style> タグ内の css です。通常、フォームを使用してログイン ボックスを作成し、要素コンポーネント ドキュメント ( http://element-cn.eleme.io/#/zh-CN/component/ ) を開くと、豊富なフォーム コンポーネントが提供されることがわかります。 , 「コードを表示」をクリックして、必要な部分をコピーできます。

ただし、アプリケーション シナリオに特に適した形式がないか、これらは比較的複雑であり、必要なのはほんの一部であるようです。ページをプルダウンすると、このコンポーネントのプロパティ、イベント、メソッドなどに関するドキュメントが表示され、これに従って必要なフォームを自分で構築することができます。

5.2 ランディングページの機能設計

5.3 Login.vue ログイン ページ

確認コードはバックエンド経由で画像を返します。フォームはルールを通じてルールをバインドし、props を通じて要素に属性を追加し、ルール内にルールを記述します。検証方法: this.$refs.loginForm.validate。

/captcha は情報を返します

 /login ログイン返信メッセージ

ログインに成功すると、トークンが返されます。このトークンは、フロントエンドとバックエンド間のデータ通信の資格情報として使用されます。システムのセキュリティを確保するために、バックエンドはトークンを定期的に更新するため、ユーザーはトークンの有効期限が切れた後に再度ログインする必要があります。フロントエンドは、取得したトークンを後でインターフェースを呼び出すためのキーとして sessionStorage に保存します。このトークンは、Axios 経由で get または post リクエストを行うときに一緒に持ち込む必要があります。

リクエスト インターセプターにトークンが存在するかどうかを確認し、各リクエストのトークンを確認します。存在する場合、リクエストはトークンを運び、それを Authorization パラメータに置きます。バックエンドはトークンを検証します。

フロントエンドのログインが成功したら、this.$router.replace('/home') を使用してホームページにジャンプします。replace メソッドを置き換えた後、ブラウザの戻るボタンをクリックしてもログイン ページにはジャンプしません。ログインが失敗した場合、バックエンドは失敗の理由を返します。

ユーザーがログインしていない場合、ユーザーがhttp://localhost:8080/#/でログイン ページにアクセスせず、ログイン後にのみアクセスできるルート ( http://localhost:8080など) にアクセスした場合/#/sys /basic状況に応じて議論する必要があります: 1. ユーザーがホームページのアドレスまたは間違ったアドレスを入力し、ログインに成功した後にホームページにジャンプさせることができます; 2. それ以外の場合、ユーザーは正常に入力したアドレスにジャンプします。

this.$router.replace((パス === '/' || パス === 未定義) ? '/home' : パス)

<template>

  <div>

    <el-form

      v-loading="loading"

      element-loading-text="正在登录......"

      element-loading-spinner="el-icon-loading"

      element-loading-background="rgba(0, 0, 0, 0.8)"

      ref="loginForm" :model="loginForm" :rules="rules" class="loginContainer">

      <h3 class="loginTitle">系统登录</h3>

      <el-form-item prop="username">

        <el-input type="text" v-model="loginForm.username" placeholder="请输入用户名"></el-input>

      </el-form-item>

      <el-form-item prop="password">

        <el-input type="password" v-model="loginForm.password" placeholder="请输入密码"></el-input>

      </el-form-item>

      <el-form-item prop="code">

        <el-input type="text" v-model="loginForm.code" placeholder="点击图片更换验证码"

                  style="width: 250px;margin-right: 5px;"></el-input>

        <img :src="captchaUrl" @click="updateCaptcha">

      </el-form-item>

      <el-button type="primary" style="width: 100%" @click="submitLogin">登录</el-button>

    </el-form>

  </div>

</template>

<script>

  export default {

    name: 'Login',

    components: {},

    props: [],

    data() {

      return {

        // 验证码

        captchaUrl:'/captcha?time=' + new Date(),//获取响应码后端接口

        loginForm: {

          username: 'admin',

          password: '123',

          code: '',

        },

        loading: false, // 加载中

        //校验规则,与表单绑定

        rules: {

          username: [{required: true, message: '请输入用户名', trigger: 'blur'}],

          password: [{required: true, message: '请输入密码', trigger: 'blur'}],

          code: [{required: true, message: '请输入验证码', trigger: 'blur'}]

        }

      }

    },

    mounted(){

 

    },

    methods: {

      // 点击刷新验证码

      updateCaptcha() {

      this.captchaUrl="/captcha?time="+new Date();

 

      },

      submitLogin() {

        // 登录

        this.$refs.loginForm.validate((valid) => {

          if (valid) {

            this.loading = true;//准备调登录接口时,出现正在加载

            //第一个参数请求后端的地址,第二个参数,传给后端的数据

            this.postRequest('/login', this.loginForm).then(resp => {

              this.loading = false;//登录成功后关闭

              if (resp) {

                // 存储用户 token 到 sessionStorage

                const tokenStr = resp.obj.tokenHead + resp.obj.token;

                window.sessionStorage.setItem('tokenStr', tokenStr);

                // 跳转到首页

                // this.$router.push('/home') // 路由跳转,可以回退到上一页

                this.$router.replace('/home') // 路径替换,无法回退到上一页

 

                // 页面跳转

                // 拿到用户要跳转的路径

                let path = this.$route.query.redirect;

                // 用户可能输入首页地址或错误地址,让他跳到首页,否则跳转到他输入的地址

                this.$router.replace((path === '/' || path === undefined) ? '/home' : path)

              }

 

            })

          } else {

            this.$message.error('请输入所有字段!');

            return false;

          }

        })

      }

    }

  }

</script>

<style>

  .loginContainer {

    border-radius: 15px;

    background-clip: padding-box;

    /*属性规定背景的绘制区域 背景被裁剪到内边距框。 margin: 180 px auto;*/

    margin: 180px auto;

    width: 350px;

    padding: 15px 35px;

    background: #fff;

    border: 1px solid #eaeaea;

    box-shadow: 0 0 25px #cac6c6;

    /*  X轴偏移量 Y轴偏移量 [阴影模糊半径] [阴影扩展] [阴影颜色] [投影方式]; */

  }

 

  .loginTitle {

    margin: 0 auto 40px auto;

    text-align: center;

  }

 

  .loginRemember {

    text-align: left;

    margin: 0 0 15px 0;

  }

 

  /*验证码*/

  .el-form-item__content {

    display: flex;

    align-items: center;

  }

</style>

 SessionStorage.setItem()では、axiosが次のリクエスト時にトークン認証を取得できるようにするため、ログイン後、トークンを取得してsessionStrorageに置きます。         

    // ユーザートークンを sessionStorage に保存します

                const tokenStr = resp.obj.tokenHead + resp.obj.token;

                window.sessionStorage.setItem('tokenStr', tokenStr);

5.4 ページルーティングの構成 - router/index.js

import Vue from 'vue'

import Router from 'vue-router'

import Login from "@/views/Login";

Vue.use(Router)

export default new Router({

  routes: [

    {

      path: '/',

      name: 'Login',

      component: Login,

      hidden: true // 不会被循环遍历出来

  },

  ]

})

5.5 フロントエンドルーティングおよびナビゲーションガード

ログイン ページの開発は比較的完了しているように見えますが、実際にはまだ完成していません。このログイン ページは実際には役に立たず、他のユーザーがホームページの URL を直接入力することでログイン ページをバイパスできるからです。これを機能させるには、インターセプターを開発する必要もあります。フック関数を使用して、関数および特定のタイミングで呼び出される関数をインターセプトするかどうかを決定します。ここでは、router.beforeEach() を使用します。これは、各ルートにアクセスする前に呼び出すことを意味します。to は移動するルート、from はどこからのルート、next() は解放します。

sessionStorage.getItem('user') を通じてユーザーのトークンを取得します。トークンが存在しない場合は、ログインする必要があります。

if (to.path == '/') ランディング ページかどうかを判断し、そうであればそのままにし、それ以外の場合はユーザーが指定したルートに従ってログインします。

メイン.js

// 使用 router.beforeEach 注册一个全局前置守卫

router.beforeEach((to, from, next) => {

  // to 要去的路由; from 来自哪里的路由 ; next() 放行

  // 用户登录成功时,把 token 存入 sessionStorage,如果携带 token,初始化菜单,放行

  if (window.sessionStorage.getItem('tokenStr')) {

      // 如果用户不存在

         //待首页功能部分完善后补充

  } else {

      if (to.path === '/') {

          next()

      } else {

          next('/?redirect=' + to.path)

      }

  }

})

5.6 フロントエンドとバックエンドのクロスドメイン問題の解決

フロントエンド ポートのデフォルトは 8080 です。バックエンド ポートが 8081 であるとすると、8080 は 8081 のデータにどのようにアクセスしますか? Node.js を通じて自動ポート転送を実装します。ブラウザの同一オリジン ポリシー: 2 つのページは同じプロトコル (プロトコル) ホスト (ホスト) ポート番号 (ポート) を持つ必要があります。同一オリジン ポリシーはブラウザのセキュリティ メカニズムです。つまり、ブラウザは、非同一オリジン ページ上の DOM 操作や XMLHttpRequest オブジェクトが非同一オリジン サーバーへの http リクエストを開始することを防ぎます。インターフェイスを要求すると、Access-Control-Allow-Origin などが表示され、要求がクロスドメインであることを示します。vue でクロスドメインを解決する方法: vue.config.js ファイルを設定します。設定されていない場合は、自分で新しいファイルを作成します。

原理:

1. ドメイン名をローカルサーバー (localhost:8080) に送信します。

2. 次に、ローカルサーバーが実サーバーを要求します。

3. リクエストはサーバーから送信されるため、クロスドメインの問題はありません。

vue では、node.js によって自動的に行われます。

フロントエンド リバース プロキシ

view.config.js

proxyTable リクエスト アドレスを、node.js の後のバックエンド アドレス 8081 にプロキシするように変更します。

 proxyTable: {

      '/': {

        changeOrigin: true, //跨域

        target: 'http://localhost:8081',

        pathRewrite: {

          // '^/api': ''

        }

      },

   

    },

5.7 プロジェクトの実行

6. ホームページ  

私たちのプロジェクトは基本的に単一ページのアプリケーションですが、表面上には複数の機能ページがあります。ユーザーがこれらのページを簡単に切り替えられるようにするには、ナビゲーション バーを追加する必要があります。このナビゲーションバーの要件は単純です。

すべてのページに表示できる

美しい

最初の要件を達成するには、他のページの親ページにナビゲーション バーを配置する必要があります (Vue の場合、親コンポーネントです)。前に述べたように、App.vue はすべてのコンポーネントの親コンポーネントですが、ナビゲーション バーを配置する ログイン ページにはナビゲーション バーを含めるべきではないため、「Going in」は適切ではありません。この問題を解決するために、views ディレクトリに新しいコンポーネントを直接作成し、Home.vue という名前を付けます。App.vue と同様に、

<router-view/>、サブページ (コンポーネント) が表示される場所です。

Home.vueは、ホームページ全体の左側のメニューの取得と表示、右上のパーソナルセンターの設定を実現します。現在のメニュー情報と現在のユーザーのログイン情報をstore.stateから取得します。

 6.1 メニュー機能の設計と実装

必要なファイルディレクトリは以下の通りです。 views/emp 基本情報 EmpBasic.vue EmpAdv.vue を新規作成します。

ビュー数/従業員プロファイルごと 新しい PerEmp.vu PerEc.vue PerTrain.vue PerSalary.vue PerMv.vue

views/sal  工资账套 SalSob.vue SalSobcfg.vue SalTable.vue SalMonth.vue  SalSearch.vue

views/sta総合情報統計追加 StaAll.vue StaScore.vue StaPers.vue StaRecord.vue

views/sys 系统管理  新增 SysBasic.vue SysConfig.vue SysLog.vue  SysAdmin.vue  SysData.vue SysInit.vue

ストア/index.jsを設定する

vuex によるルーティング状態管理

import Vue from 'vue'

import Vuex from 'vuex'

Vue.use(Vuex)

// 导入 Vuex

const store = new Vuex.Store({

    state: {

        routes: []

    },

    mutations: { // 与 state 同步执行;可以改变 state 对应的值的方法

        // 初始化路由 菜单

        initRoutes(state, data) {

            state.routes = data

        },

    },

    // 异步执行

    actions: {

        }

})

export default store;

Main.jsでストアを導入

import store from './store'

new Vue({

    router,

    store,

    render: h => h(App)

}).$mount('#app')

6.2 パッケージメニューリクエストツール

バックエンドはメニュー インターフェイスに情報を返すように要求します。

私たちが設計したメニューは、ユーザー情報に従ってロードされたルーティング情報です。つまり、異なるユーザーは異なるメニュー権限を持つ可能性があります。インターフェースから返されるメニュー情報は次のとおりです。サブメニューは子によって表され、サブメニューのparentIdが親メニューのIDと等しい場合、特定の親子メニュー関係を示します。以下の関係は、「社員プロフィール/基本プロフィール」という階層メニューがあることを示している。

store.state.routes にデータがある場合は、ルーティング メニューを初期化します。getRequest('/system/config/menu') メソッドを通じてバックエンドからルーティング データを取得し、階層関係に従って分割します。

インターフェイスのコンポーネントフィールドに従って、対応するコードパスを見つけるにはどうすればよいですか?

インターフェイス オブジェクト内のコンポーネント フィールドを分類して検索します。たとえば、コンポーネントは Home で始まり、ソース コードは src/views/Home.vue にあります。

                if (component.startsWith('Home')) {

                    require(['@/views/' + component + '.vue'], resolve);

                }

initMenu メソッドはルーティング データをストアに保存します。ストアにデータがある場合は初期化する必要はありませんが、データがない場合は初期化されます。

いつ呼ばれますか? すべてのページで初期化メニュー メソッドを呼び出す必要があります。これをルートインターセプターに入れて、ルートにアクセスするたびに実行します。

import {getRequest} from "@/utils/api";

// 菜单请求工具类

// router 路由; store Vuex

export const initMenu = (router, store) => {

    // 如果有数据,初始化路由菜单

    if (store.state.routes.length > 0) {

        return;

    }

    getRequest('/system/config/menu').then(data => {

        // 如果数据存在 格式化路由

        if (data) {

            // 格式化好路由

            let fmtRoutes = formatRoutes(data)

            // 添加到 router

            router.addRoutes(fmtRoutes)

            // 将数据存入 Vuex

            store.commit('initRoutes',fmtRoutes)

            // 连接 WebSocket

            store.dispatch('connect')

        }

    })

}

export const formatRoutes = (routes) => {

    let fmtRoutes = []

    routes.forEach(router => {

        let {

            path,

            component,

            name,

            iconCls,

            children

        } = router;

        // 如果有 children 并且类型是数组

        if (children && children instanceof Array) {

            // 递归

            children = formatRoutes(children)

        }

        // 单独对某一个路由格式化 component

        let fmRouter = {

            path: path,

            name: name,

            iconCls: iconCls,

            children: children,

            component(resolve) {

                // 判断组件以什么开头,到对应的目录去找

                if (component.startsWith('Home')) {

                    require(['@/views/' + component + '.vue'], resolve);

                }else if (component.startsWith('Emp')) {

                    require(['@/views/emp/' + component + '.vue'], resolve);

                }else if (component.startsWith('Per')) {

                    require(['@/views/per/' + component + '.vue'], resolve);

                }else if (component.startsWith('Sal')) {

                    require(['@/views/sal/' + component + '.vue'], resolve);

                }else if (component.startsWith('Sta')) {

                    require(['@/views/sta/' + component + '.vue'], resolve);

                }else if (component.startsWith('Sys')) {

                    require(['@/views/sys/' + component + '.vue'], resolve);

                }

            }

        }

        fmtRoutes.push(fmRouter)

    })

    return fmtRoutes

}

main.jsを更新する

現在のユーザーのログイン情報を取得する

現在のユーザー情報をsessionStorageのuserに保存し、経路が切り替わるたびにユーザーのログイン情報を取得します。

// 使用 router.beforeEach 注册一个全局前置守卫

router.beforeEach((to, from, next) => {

  // to 要去的路由; from 来自哪里的路由 ; next() 放行

  // 用户登录成功时,把 token 存入 sessionStorage,如果携带 token,初始化菜单,放行

  if (window.sessionStorage.getItem('tokenStr')) {

      initMenu(router, store)

      // 如果用户不存在

      if (!window.sessionStorage.getItem('user')

      ) {

          // 判断用户信息是否存在

          return getRequest('/admin/info').then(resp => {

              if (resp) {

                  // 存入用户信息,转字符串,存入 sessionStorage

                  window.sessionStorage.setItem('user', JSON.stringify(resp))

                  // 同步用户信息 编辑用户

                  store.commit('INIT_ADMIN',resp)

                  next();

              }

          })

      }

      next();

  } else {

      if (to.path === '/') {

          next()

      } else {

          next('/?redirect=' + to.path)

      }

  }

})

6.3 スタイルデザイン

ログイン後のフロントエンド ページは、上部のナビゲーション バー、左側のメニュー バー、中央のメイン機能領域に分解されます。異なるページ間の切り替えには、中央の機能領域のコンテンツのみを変更する必要があるため、コードが改善されます。再利用性。まず、ページの各領域のコンポーネントをカスタマイズし、各コンポーネント ファイルを Views フォルダーに保存します。各 .vue ファイルは個別のコンポーネントです。ルートで指定されたコンポーネントは、インポート ステートメント <router-view>< を通じてインポートされます。 /router-view> ページ上にレンダリングされます。

レイアウトは、element-ui のコンテナ レイアウト コンテナを使用します: el-container アウター コンテナ; el-header トップ バー コンテナ; el-aside サイドバー コンテナ; el-menu ナビゲーション エリア; el-main メイン エリア コンテナ; el-footer ボトム バー コンテナ

スタイルデザインは次のとおりです。

el-menu ナビゲーションに router 属性を追加して、メニュー ルーティングの動的なレンダリングを実現します。ホームページ ナビゲーション メニューは、element-ui の NavMenu ナビゲーション メニュー コントロールを使用します。メニューがクリックされるたびに 1 つのメニューのみが展開されるようにするには、属性 unique-opened: を使用します。router 属性を使用して、ナビゲーションをアクティブにするときにルーティング ジャンプのパスとしてインデックスを使用します。

el-dropdown-item のコマンドを、el-dropdown のメニュー項目 @command をクリックすることでトリガーされるイベント コールバック メソッドにバインドして、ログアウトとログインを実現し、パーソナル センター機能に入ります。

elemet の MessageBox ブレット ボックスは、ログアウトとログインのプロンプト ボックスを実現します。ログアウト後、vuex のメニュー情報をクリアします。

el-breadcrumb ブレッドクラム コントロールを使用すると、現在のページのパスを表示し、前のページにすぐに戻ります。ホーム ページ以外の場合、v-if="this.$router.currentRoute.path!=='/home'" 表示レベル: 最初のページ/現在のページ。

ホームページ v-if="this.$router.currentRoute.path==='/home'" の場合、ウェルカム フォントを表示します。

6.4 Home.vue代码

<template>

  <div>

    <el-container>

      <el-header class="homeHeader">

        <div class="title">云办公</div>

        <!-- 1-1 添加在线聊天入口 -->

        <div>

          <el-button type="text" icon="el-icon-bell" size="normal"

                     style="margin-right: 8px;color: black;" @click="goChar"></el-button>

          <el-dropdown class="userInfo" @command="commandHandler">

          <span class="el-dropdown-link">

            {
   
   { user.name }}<i><img :src="user.userFace"></i>

          </span>

            <el-dropdown-menu slot="dropdown">

              <el-dropdown-item command="userinfo">个人中心</el-dropdown-item>

              <el-dropdown-item command="setting">设置</el-dropdown-item>

              <el-dropdown-item command="logout">注销登录</el-dropdown-item>

            </el-dropdown-menu>

          </el-dropdown>

        </div>

      </el-header>

      <el-container>

        <el-aside width="200px">

          <!-- 1、添加 router  -->

          <el-menu router unique-opened>

            <!-- 2、循环整个路由组件,不展示 hidden: true 的路由组件 -->

            <el-submenu :index="index +''" v-for="(item,index) in routes"

                        :key="index" v-if="!item.hidden">

              <template slot="title"><i :class="item.iconCls" style="color: black;margin-right: 5px"></i>

                <span>{
   
   { item.name }}</span>

              </template>

              <!-- 3、循环遍历子路由 -->

              <el-menu-item :index="children.path"

                            v-for="(children,index) in item.children" :key="index">{
   
   { children.name }}

              </el-menu-item>

            </el-submenu>

          </el-menu>

        </el-aside>

        <el-main>

          <!-- 面包屑导航区域 -->

          <el-breadcrumb separator-class="el-icon-arrow-right"

                         v-if="this.$router.currentRoute.path!=='/home'">

            <el-breadcrumb-item :to="{ path: '/home' }">首页</el-breadcrumb-item>

            <el-breadcrumb-item>{
   
   { this.$router.currentRoute.name }}</el-breadcrumb-item>

          </el-breadcrumb>

          <div class="homeWelcome" v-if="this.$router.currentRoute.path==='/home'">

            欢迎来到云办公系统!

          </div>

          <!-- 路由点位符 -->

          <router-view class="homeRouterView"/>

        </el-main>

      </el-container>

    </el-container>

  </div>

</template>

<script>

  export default {

    name: 'Home',

    data() {

      return {

        // 获取用户信息,将字符串转对象

        // user: JSON.parse(window.sessionStorage.getItem('user'))

      }

    },

    computed: {

      // 从 vuex 获取 routes

      routes() {

        return this.$store.state.routes

      },

      user() {

        return this.$store.state.currentAdmin

      }

    },

    methods: {

      // 1-2 进入在线聊天页面

      goChar() {

        this.$router.push('/chat')

      },

      // 注销登录

      commandHandler(command) {

        if (command === 'logout') {

          // 弹框提示用户是否要删除

          this.$confirm('此操作将注销登录, 是否继续?', '提示', {

            confirmButtonText: '确定',

            cancelButtonText: '取消',

            type: 'warning'

          }).then(() => {

            // 注销登录

            this.postRequest('/logout')

            // 清空用户信息

            window.sessionStorage.removeItem('tokenStr')

            window.sessionStorage.removeItem('user')

            // 路由替换到登录页面

            // this.$router.replace('/')

            // 清空菜单信息;在src/utils/menus.js 中初始化菜单信息

            this.$store.commit('initRoutes', [])

            this.$router.replace('/')

          }).catch(() => {

            this.$message({

              type: 'info',

              message: '已取消注销登录'

            });

          });

        }

        if (command === 'userinfo') {

          this.$router.push('/userinfo')

        }

      }

    }

  }

</script>

<style scoped>

  .homeHeader {

    background: #3e9ef5;

    display: flex;

    align-items: center;

    justify-content: space-between;

    padding: 0 15px;

    box-sizing: border-box;

  }

  .homeHeader .title {

    font-size: 30px;

    /*font-family: 微软雅黑;*/

    font-family: 华文楷体;

    color: white;

  }

  .homeHeader .userInfo {

    cursor: pointer;

  }

  .el-dropdown-link img {

    width: 48px;

    height: 48px;

    border-radius: 50%;

    margin-left: 8px;

  }

  .homeWelcome {

    text-align: center;

    font-size: 30px;

    font-family: 华文楷体;

    color: #409ef4;

    padding-top: 50px;

  }

  .homeRouterView {

    margin-top: 10px;

  }

</style>

隠し属性を無視する

            <!-- 2. ルーティング コンポーネント全体を循環します。hidden: true ではルーティング コンポーネントを表示しません -->

            <el-submenu :index="index +''" v-for="ルート内の(item,index)"

                        :key="index" v-if="!item.hidden">

6.5 ルートルーター/index.jsを更新する

router/index.js の hidden:true を無視します。

/ home ルートはホームページから取得されます

import Vue from 'vue'

import VueRouter from 'vue-router'

import Login from "@/views/Login";

Vue.use(VueRouter)

const routes = [

    {

        path: '/',

        name: 'Login',

        component: Login,

        hidden: true // 不会被循环遍历出来

    }

]

const router = new VueRouter({

    routes

})

export default router

6.6 Index.html の余白を削除する

スタイルを追加する

<!DOCTYPE html>

<html>

  <head>

    <meta charset="utf-8">

    <meta name="viewport" content="width=device-width,initial-scale=1.0">

    <title>yeb-front</title>

  </head>

  <body style="margin:0px;padding:0px">

    <div id="app"></div>

    <!-- built files will be auto injected -->

  </body>

</html>

7. 基本情報の設定

7.1 スタイルデザイン

システム管理・基本情報設定 部門管理、役職管理、役職管理、賞罰規定、権限グループのモジュール設計

タブタブページ

要素の「タブ」タブ・ページを使用して、さまざまなビジネス機能の切り替えを完了します。関連はあるものの、異なるカテゴリに属する​​個別のデータ・コレクションです。

abs コンポーネントはタブ関数を提供し、最初のタブ ページがデフォルトで選択されます。また、value 属性を通じて現在選択されているタブ ページを指定することもできます。

<template>

  <el-tabs v-model="activeName" @tab-click="handleClick">

    <el-tab-pane label="用户管理" name="first">用户管理</el-tab-pane>

    <el-tab-pane label="配置管理" name="second">配置管理</el-tab-pane>

    <el-tab-pane label="角色管理" name="third">角色管理</el-tab-pane>

    <el-tab-pane label="定时任务补偿" name="fourth">定时任务补偿</el-tab-pane>

  </el-tabs>

</template>

<script>

  export default {

    data() {

      return {

        activeName: 'second'

      };

    },

    methods: {

      handleClick(tab, event) {

        console.log(tab, event);

      }

    }

  };

</script>

7.2 コンポーネントベースの開発

開発の過程では、再利用できる多くのコード ブロックに遭遇することになりますが、Vue はそのようなパッケージ化方法、つまり Vue.component を提供しますコンポーネントベース開発では、部門管理、役職管理、役職管理、賞罰ルール、権限グループなどのコンポーネント手法を導入します。コンポーネントも .vue ファイルです。コンポーネントのインポート方法は、「コンポーネント パス」からコンポーネント名をインポートします。例:

import DepMana from "@/components/sys/basic/DepMana"; インポート直後は使用できず、コンポーネントをコンポーネントオブジェクトに登録する必要があります。その後はコンポーネントのアプリケーションです: <DepMana/> または <DepMana></DepMana/>

コンポーネントカタログ

7.3 SysBasic.vue

<template>

  <div>

    <el-tabs v-model="activeName" type="card">

      <el-tab-pane label="部门管理" name="DepMana"><DepMana/></el-tab-pane>

      <el-tab-pane label="职位管理" name="PosMana"><PosMana/></el-tab-pane>

      <el-tab-pane label="职称管理" name="JobLevelMana"><JobLevelMana/></el-tab-pane>

      <el-tab-pane label="奖惩规则" name="EcMana"><EcMana/></el-tab-pane>

      <el-tab-pane label="权限组" name="PositionMana"><PositionMana/></el-tab-pane>

    </el-tabs>

  </div>

</template>

<script>

import DepMana from "@/components/sys/basic/DepMana";  // 部门管理

import EcMana from "@/components/sys/basic/EcMana"; // 奖惩规则

import JobLevelMana from "@/components/sys/basic/JobLevelMana"; // 职称管理

import PositionMana from "@/components/sys/basic/PositionMana"; // 权限组

import PosMana from "@/components/sys/basic/PosMana"; // 职位管理

export default {

  name: "SysBasic",

  components:{

    JobLevelMana,

    DepMana,

    EcMana,

    PositionMana,

    PosMana

  },

  data() {

    return {

      activeName: 'DepMana' // 激活项

    }

  },

  methods: {}

}

</script>

<style scoped>

</style>

7.4 部門管理 DepMana.vue コンポーネント

使いやすくするために、すべての部門を一度にロードします。

スタイルデザイン: フレックスレイアウト、スペース間の: 両端を揃え、幅を親要素に揃えます。

検索ボックスでノードをフィルタリングするメソッドは、filterNode メソッドを介して 2 つのパラメーター、value (検索によって入力された値)、およびデータ ツリー ラベルにバインドされたデータを渡します。値が空の場合はタグの値をすべて表示、それ以外の場合はdata.name(ツリータグに表示される値)に入力値が見つかるかどうかを判定します ここではjsの検索文字列メソッドindexOfを使用します検索要素の添字を返します。返される添字を見つけることができます (>=0)

 Expand-on-click-node は、マウスで展開ボタンをクリックしたときにのみ展開されるため、関数の追加や削除に便利です。

ボタンをクリックすると、現在のノードのデータ、つまり追加する下位部門の「上位部門」ID(parentId)を取得します。

<template>

  <div style="width: 500px">

    <!-- 1 -->

    <el-input

        placeholder="请输入部门名称进行搜索..."

        prefix-icon="el-icon-search"

        v-model="filterText">

    </el-input>

    <!-- 9、:expand-on-click-node="false" 点击小三角箭头才会展开

            :default-expand-all="false" 设置默认不展开所有节点 -->

    <el-tree

        :data="deps"

        :props="defaultProps"

        default-expand-all

        :filter-node-method="filterNode"

        :expand-on-click-node="false"

        ref="tree">

      <!-- 7、label: 'name' -->

      <!-- 8、style="display: flex;justify-content: space-between;width: 100% 父容器宽度" 让添加和删除按键居右 -->

      <span class="custom-tree-node" slot-scope="{ node, data }"

            style="display: flex;justify-content: space-between;width: 100%">

        <span>{
   
   { data.name }}</span>

        <span>

          <el-button

              plain

              type="primary"

              size="mini"

              class="depBtn"

              @click="() => showAddDep(data)">

            添加部门

          </el-button>

          <!-- 10、showAddDep(data)  deleteDep(data)  data 后端传过来的完整的 json 对象 -->

          <el-button

              plain

              type="danger"

              size="mini"

              class="depBtn"

              @click="() => deleteDep(data)">

            删除部门

          </el-button>

        </span>

      </span>

    </el-tree>

    <!-- 13、对话弹框 -->

    <el-dialog

        title="添加部门"

        :visible.sync="dialogVisible"

        width="30%">

      <!-- 16 -->

      <div>

        <table>

          <tr>

            <td>

              <el-tag>上级部门</el-tag>

            </td>

            <td>{
   
   { pname }}</td>

          </tr>

          <tr>

            <td>

              <el-tag>部门名称</el-tag>

            </td>

            <td>

              <el-input v-model="dep.name" placeholder="请输入部门名称..." size="small"></el-input>

            </td>

          </tr>

        </table>

      </div>

      <span slot="footer" class="dialog-footer">

        <el-button @click="dialogVisible = false">取 消</el-button>

        <!-- 18、确定添加按钮绑定事件 @click="doAddDep" -->

        <el-button type="primary" @click="doAddDep">确 定</el-button>

      </span>

    </el-dialog>

  </div>

</template>

<script>

export default {

  name: "DepMana",

  data() {

    return { // 2

      filterText: '',

      deps: [], // 所有部门整个数组

      defaultProps: { // 2 关联子部门

        children: 'children',

        label: 'name'

      },

      dialogVisible: false, // 14

      dep: { // 15、添加部门数据象

        name: '',

        parentId: -1,

        isParent: ''

      },

      pname: '' // 15、上级部门名称

    }

  },

  watch: {

    // 4、观察者事件,监控输入框的值(框架方法)

    filterText(val) {

      this.$refs.tree.filter(val);

    }

  },

  mounted() {

    this.initDeps()  // 6、调用获取所有部门方法

  },

  methods: {

    // 删除部门调用的方法

    removeDepFromDeps(p, deps, id) {

      for (let i = 0; i < deps.length; i++) {

        let d = deps[i]

        if (d.id === id) {

          deps.splice(i, 1)

          if (deps.length === 0) {

            p.isParent = false

          }

          return;

        } else {

          this.removeDepFromDeps(d, d.children, id)

        }

      }

    },

    // 12、删除部门

    deleteDep(data) {

      // console.log(data)

      if (data.isParent) {

        this.$message.error('父部门删除失败!')

      } else {

        this.$confirm('此操作将永久删除该[' + data.name + ']部门, 是否继续?', '提示', {

          confirmButtonText: '确定',

          cancelButtonText: '取消',

          type: 'warning'

        }).then(() => {

          this.deleteRequest('/system/basic/department/' + data.id).then(resp => {

            if (resp) {

              this.removeDepFromDeps(null, this.deps, data.id)

            }

          })

        }).catch(() => {

          this.$message({

            type: 'info',

            message: '已取消删除'

          });

        });

      }

    },

    // 20、添加完部门 初始化 清空数据

    initDep() {

      this.dep = {

        name: '',

        parentId: -1

      }

      this.pname = ''

    },

    // 22、 递归查询所有部门信息,deps 查询到的整个数组,dep 添加的部门

    addDep2Deps(deps, dep) {

      for (let i = 0; i < deps.length; i++) {

        let d = deps[i] // 父部门

        if (d.id === dep.parentId) {

          d.children = d.children.concat(dep) // 把 dep 加为 d 的子部门

          if (d.children.length > 0) {

            d.isParent = true

          }

          return;

        } else {

          this.addDep2Deps(d.children, dep) // 递归调用此方法 以查询结果为条件 继续查询子部门

        }

      }

    },

    // 19、确认添加部门

    doAddDep() {

      this.postRequest('/system/basic/department/', this.dep).then(resp => {

        if (resp) {

          // console.log(resp)

          this.dialogVisible = false // 关闭对话框

          this.addDep2Deps(this.deps, resp.data) // 23、【无效】手动插入部门 显示添加后的数据

          this.initDep() // 21、调用初始化方法 清空数据

        }

      })

    },

    // 11、17、添加部门弹框

    showAddDep(data) {

      // console.log(data)

      this.dep.parentId = data.id

      this.pname = data.name

      this.dialogVisible = true

    },

    // 5、获取所有部门

    initDeps() {

      this.getRequest('/system/basic/department/').then(resp => {

        if (resp) {

          this.deps = resp

        }

      })

    },

    // 3、事件(框架方法)

    filterNode(value, data) { // data 整行数据

      if (!value) return true; // true 节点可以展示,false 节点隐藏

      return data.name.indexOf(value) !== -1; // label: 'name'

    }

  }

}

</script>

<style scoped>

/* 8 */

.depBtn {

  padding: 2px;

}

</style>

7.5 ポジション管理 PosMana.vue コンポーネント

要素のテーブル コントロール。最初の列は複数選択ボックスで、その type="selection" です。フォーム バインディングの複数選択イベント @selection-change="handleSelectionChange"

 el-dialog 編集ボタンを使用して役職を変更します。バインディングポップアップボックスの表示方法:visible.sync="dialogVisible"、キャンセル時はdialogVisible=false、「編集」クリック時はdialogVisible=true。

 一括削除。multipleSelection 配列を通じて複数選択データを取得します。multipleSelection が空の場合、一括削除ボタンは無効になります。複数の選択情報は、multipleSelection を通じてプロンプト ボックスに表示されます。

インターフェースデータリターン情報

 PosMana.vue

キーボード イベント @keydown.enter.native="addPosition" は、入力後に「追加」ボタン バインド メソッドを自動的に呼び出します。

ジョブ情報を編集した後、リストデータを更新し、ポップアップボックスを閉じます。

データコピー Object.assign(this.updatePos, data) を使用して data 内のデータを updatePos に割り当て、浅いコピーによる updatePos によるデータのデータ変更を回避します。

<template>

  <div>

    <div>

      <el-input

          size="small"

          class="addPosInput"

          placeholder="请选择日期"

          suffix-icon="el-icon-plus"

          @keydown.enter.native="addPosition"

          v-model="pos.name">

      </el-input>

      <el-button size="small" icon="el-icon-plus" type="primary" @click="addPosition">添加</el-button>

    </div>

    <div class="posManaMain">

      <el-table

          border

          stripe

          size="small"

          :data="positions"

          style="width: 70%"

          @selection-change="handleSelectionChange">

        <el-table-column

            type="selection"

            width="55">

        </el-table-column>

        <el-table-column

            prop="id"

            label="编号"

            width="55">

        </el-table-column>

        <el-table-column

            prop="name"

            label="职位"

            width="120">

        </el-table-column>

        <el-table-column

            prop="createDate"

            label="创建时间"

            width="200">

        </el-table-column>

        <el-table-column label="操作">

          <template slot-scope="scope">

            <el-button

                size="mini"

                @click="showEditView(scope.$index, scope.row)">编辑

            </el-button>

            <el-button

                size="mini"

                type="danger"

                @click="handleDelete(scope.$index, scope.row)">删除

            </el-button>

          </template>

        </el-table-column>

      </el-table>

    </div>

    <!-- :disabled 不禁用条件为勾选中,没勾选中为禁用。 -->

    <el-button size="small" style="margin-top: 8px" type="danger"

               :disabled="this.multipleSelection.length===0" @click="deleteMany">批量删除

    </el-button>

    <el-dialog

        title="提示"

        :visible.sync="dialogVisible"

        width="30%">

      <div>

        <el-tag>职位名称</el-tag>

        <el-input v-model="updatePos.name" size="small" class="updatePosInput"></el-input>

      </div>

      <span slot="footer" class="dialog-footer">

        <el-button size="small" @click="dialogVisible = false">取 消</el-button>

        <el-button size="small" type="primary" @click="doUpdate">确 定</el-button>

      </span>

    </el-dialog>

  </div>

</template>

<script>

export default {

  name: "PosMana",

  data() {

    return {

      pos: { // 查询添加职位数据

        name: ''

      },

      positions: [],

      dialogVisible: false,

      updatePos: { // 更新职位数据

        name: ''

      },

      multipleSelection: [] // 批量删除勾选的对象

    }

  },

  mounted() {

    // 调用获取后端接口所有职位数据方法

    this.initPositions()

  },

  methods: {

    // 批量删除请求

    deleteMany() {

      this.$confirm('此操作将永久删除[' + this.multipleSelection.length + ']条职位, 是否继续?', '提示', {

        confirmButtonText: '确定',

        cancelButtonText: '取消',

        type: 'warning'

      }).then(() => {

        let ids = '?'

        this.multipleSelection.forEach(item => {

          ids += 'ids=' + item.id + '&'

        })

        this.deleteRequest('/system/basic/pos/' + ids).then(resp => {

          if (resp) {

            this.initPositions()

          }

        })

      }).catch(() => {

        this.$message({

          type: 'info',

          message: '已取消删除'

        });

      });

    },

    // 批量删除(取值)

    handleSelectionChange(val) {

      this.multipleSelection = val

      // console.log(val)

    },

    // 编辑职位

    doUpdate() {

      this.putRequest('/system/basic/pos/', this.updatePos).then(resp => {

        if (resp) {

          this.initPositions() // 刷新数据列表

          this.dialogVisible = false // 关闭对话框

        }

      })

    },

    // 编辑职位对话框

    showEditView(index, data) {

      Object.assign(this.updatePos, data) // 回显数据,数据拷贝

      // this.updatePos = data // 回显数据 有bug

      this.updatePos.createDate = ''

      this.dialogVisible = true // 显示编辑框

    },

    // 添加职位

    addPosition() {

      if (this.pos.name) {

        this.postRequest('/system/basic/pos/', this.pos).then(resp => {

          if (resp) {

            this.initPositions()

            this.pos.name = ''

          }

        })

      } else {

        this.$message.error('职位名称不能为空!')

      }

    },

    // 删除职位

    handleDelete(index, data) {

      this.$confirm('此操作将永久删除[' + data.name + ']职位, 是否继续?', '提示', {

        confirmButtonText: '确定',

        cancelButtonText: '取消',

        type: 'warning'

      }).then(() => {

        this.deleteRequest(' /system/basic/pos/' + data.id).then(resp => {

          if (resp) {

            this.initPositions()

          }

        })

      }).catch(() => {

        this.$message({

          type: 'info',

          message: '已取消删除'

        });

      });

    },

    // 获取后端接口所有职位数据

    initPositions() {

      this.getRequest('/system/basic/pos/').then(resp => {

        if (resp) {

          this.positions = resp

        }

      })

    }

  }

}

</script>

<style scoped>

/*添加职位输入框*/

.addPosInput {

  width: 300px;

  margin-right: 8px;

}

/*所有数据表格*/

.posManaMain {

  margin-top: 10px;

}

/*编号职位输入框*/

.updatePosInput {

  width: 200px;

  margin-left: 8px;

}

</style>

7.6 タイトル管理 JobLevelMana.vue コンポーネント

タイトル管理では、プロフェッショナルタイトルの追加、更新、一括削除、一括削除の機能を実現します。

要素のスイッチコントロールを使用して、単一の更新でボタンを有効にするかどうか

 JobLevelMana.view

<template>

  <div>

    <div>

      <el-input size="small" v-model="jl.name" placeholder="添加职称名称..."

                prefix-icon="el-icon-plus" style="width: 300px"></el-input>

      <el-select size="small" v-model="jl.titleLevel" placeholder="职称等级" style="margin-left: 6px;margin-right: 6px">

        <el-option

            v-for="item in titleLevels"

            :key="item"

            :label="item"

            :value="item">

        </el-option>

      </el-select>

      <el-button type="primary" icon="el-icon-plus" size="small" @click="addJobLevel">添加</el-button>

    </div>

    <div style="margin-top: 10px;">

      <el-table

          :data="jls"

          stripe

          border

          size="small"

          style="width: 70%;"

          @selection-change="handleSelectionChange">

      <el-table-column

            type="selection"

            width="55">

        </el-table-column>

        <el-table-column

            prop="id"

            label="编号"

            width="55">

        </el-table-column>

        <el-table-column

            prop="name"

            label="职称名称"

            width="150">

        </el-table-column>

        <el-table-column

            prop="titleLevel"

            label="职称等级"

            width="150">

        </el-table-column>

        <el-table-column

            prop="createDate"

            label="创建日期"

            width="150">

        </el-table-column>

        <el-table-column

            prop="enabled"

            label="是否启用"

            width="100">

          <template slot-scope="scope">

            <el-tag type="success" v-if="scope.row.enabled">已启用</el-tag>

            <el-tag type="danger" v-else>未启用</el-tag>

          </template>

        </el-table-column>

        <el-table-column label="操作" width="350">

          <template slot-scope="scope">

            <el-button

                size="small"

                @click="showEditView(scope.row)">编辑

            </el-button>

            <el-button

                size="small"

                type="danger"

                @click="deleteHandle(scope.row)">删除

            </el-button>

          </template>

        </el-table-column>

      </el-table>

      <el-button size="small" style="margin-top: 8px" type="danger"

                 :disabled="this.multipleSelection.length===0" @click="deleteMany">批量删除

      </el-button>

    </div>

    <!-- 编辑弹框 -->

    <el-dialog

        title="编辑职称"

        :visible.sync="dialogVisible"

        width="30%">

      <table>

        <tr>

          <td>

            <el-tag>职称名称</el-tag>

          </td>

          <td>

            <el-input v-model="updateJl.name" size="small" style="margin-left: 6px"></el-input>

          </td>

        </tr>

        <tr>

          <td>

            <el-tag>职称等级</el-tag>

          </td>

          <td>

            <el-select size="small" v-model="updateJl.titleLevel" placeholder="职称等级"

                       style="margin-left: 6px;margin-right: 6px">

              <el-option

                  v-for="item in titleLevels"

                  :key="item"

                  :label="item"

                  :value="item">

              </el-option>

            </el-select>

          </td>

        </tr>

        <tr>

          <td>

            <el-tag>是否启用</el-tag>

          </td>

          <td>

            <el-switch

                style="margin-left: 6px"

                v-model="updateJl.enabled"

                active-color="#13ce66"

                inactive-color="#ff4949"

                active-text="启用"

                inactive-text="未启用">

            </el-switch>

          </td>

        </tr>

      </table>

      <span slot="footer" class="dialog-footer">

        <el-button @click="dialogVisible = false">取 消</el-button>

        <el-button type="primary" @click="doUpdate">确 定</el-button>

      </span>

    </el-dialog>

  </div>

</template>

<script>

export default {

  name: "JobLevelMana",

  data() {

    return {

      // 查询 添加 数据对象

      jl: {

        name: '',

        titleLevel: ''

      },

      // 更新 数据对象

      updateJl: {

        name: '',

        titleLevel: '',

        enabled: false

      },

      titleLevels: [

        '正高级',

        '副高级',

        '中级',

        '初级',

        '员级'

      ],

      jls: [], // 删除单条

      dialogVisible: false,

      multipleSelection: [] // 批量删除勾选中的值

    }

  },

  mounted() {

    this.initJls()

  },

  methods: {

    // 执行批量删除

    deleteMany(){

      this.$confirm('此操作将永久删除[' + this.multipleSelection.length + ']条职称, 是否继续?', '提示', {

        confirmButtonText: '确定',

        cancelButtonText: '取消',

        type: 'warning'

      }).then(() => {

        let ids = '?'

        this.multipleSelection.forEach(item => {

          ids += 'ids=' + item.id + '&'

        })

        this.deleteRequest('/system/basic/joblevel/' + ids).then(resp => {

          if (resp) {

            this.initJls()

          }

        })

      }).catch(() => {

        this.$message({

          type: 'info',

          message: '已取消删除'

        });

      });

    },

    // 批量删除勾选中的值

    handleSelectionChange(val){

      this.multipleSelection = val

    },

    // 更新职称信息

    doUpdate() {

      this.putRequest('/system/basic/joblevel/', this.updateJl).then(resp => {

        if (resp) {

          this.initJls()

          this.dialogVisible = false

        }

      })

    },

    // 显示编辑弹框

    showEditView(data) {

      Object.assign(this.updateJl, data) // 复制数据,注意这里是 , 号隔开

      this.updateJl.createDate = '' // 更新日期由后端处理,这里不用传

      this.dialogVisible = true // 显示编辑弹框

    },

    // 删除职称

    deleteHandle(data) {

      this.$confirm('此操作将永久删除[' + data.name + ']职称, 是否继续?', '提示', {

        confirmButtonText: '确定',

        cancelButtonText: '取消',

        type: 'warning'

      }).then(() => {

        this.deleteRequest(' /system/basic/joblevel/' + data.id).then(resp => {

          if (resp) {

            this.initJls()

          }

        })

      }).catch(() => {

        this.$message({

          type: 'info',

          message: '已取消删除'

        });

      });

    },

    // 添加职称

    addJobLevel() {

      if (this.jl.name && this.jl.titleLevel) {

        this.postRequest('/system/basic/joblevel/', this.jl).then(resp => {

          if (resp) {

            this.initJls()

          }

        })

      } else {

        this.$message.error('字段不能为空!')

      }

    },

    // 获取职称列表数据

    initJls() {

      this.getRequest('/system/basic/joblevel/').then(resp => {

        if (resp) {

          this.jls = resp

          this.jl.name = ''

          this.jl.titleLevel = ''

        }

      })

    }

  }

}

</script>

<style scoped>

</style>

7.7 権限グループ PositionMana.vue コンポーネント

スタイルデザイン: el-collapse 折りたたみパネル-「el-card カード」-el-tree ツリー コントロールが外側から内側に順番に使用されます。

<template slot="prepend">ROLE_</template>

エレメント折りたたみパネルを使用してキャラクターを表示し、折りたたみパネルのアコーディオンモードアコーディオンを使用します。一度に展開できるパネルは 1 つだけです。

バックエンドはすべてのユーザー ロール インターフェイスを取得します

インターフェイスによって返された nameZh ロール フィールドにバインドし、折りたたみ可能なパネルに表示します。

折りたたみパネル内の各ロールのメニュー アクセス権。ツリー コントロールを使用してロール権限を設定します。el-tree は、オプションのボックスを表示するための show-checkbox 属性を追加します。

デフォルトで選択されるロール メニュー リストは、ロール ID に従ってバックエンド インターフェイスによって返されるメニュー selectedMenus を取得します。:default-checked-keys によるバインド。

getCheckedKeys を使用して、選択したノード キーの配列を取得します。let selectedKeys =tree.getCheckedKeys(true) // 選択されたノード配列を取得します。true は、3 レベル分類など、選択されたリーフ ノードのキーのみを返します。

<template>

  <div>

    <div class="positionManaTool">

      <el-input v-model="role.name" placeholder="请输入角色英文名" size="small">

        <template slot="prepend">ROLE_</template>

      </el-input>

      <el-input v-model="role.nameZh" placeholder="请输入角色中文名" size="small" @keydown.enter.native="doAddRole"></el-input>

      <el-button type="primary" icon="el-icon-plus" size="mini" @click="doAddRole">添加角色</el-button>

    </div>

    <!-- 手风琴 -->

    <div class="positionManaMain">

      <el-collapse v-model="activeName" accordion @change="change">

        <el-collapse-item :title="r.nameZh" :name="r.id" v-for="(r,index) in roles" :key="index">

          <el-card class="box-card">

            <div slot="header" class="clearfix">

              <span>可访问资源</span>

              <el-button type="text" icon="el-icon-delete" style="float: right;padding: 3px 0;color: #f41f0a" @click="doDeleteRole(r)">

              </el-button>

            </div>

            <div>

              <el-tree ref="treeRef" show-checkbox :data="allMenus" :props="defaultProps"

                       :default-checked-keys="selectedMenus"

                       node-key="id" :key="index"></el-tree>

              <div style="display: flex;justify-content: flex-end">

                <el-button size="mini" @click="cancelUpdate">取消修改</el-button>

                <el-button size="mini" type="primary" @click="doUpdate(r.id,index)">确认修改</el-button>

              </div>

            </div>

          </el-card>

        </el-collapse-item>

      </el-collapse>

    </div>

  </div>

</template>

<script>

export default {

  name: "PositionMana",

  data() {

    return {

      role: {

        name: '',

        nameZh: ''

      },

      roles: [],

      allMenus: [],

      defaultProps: { // 树形控件

        children: 'children',

        label: 'name' // 绑定数据 :name="r.id"

      },

      selectedMenus: [],

      activeName: -1 // 折叠面板 默认关闭

    }

  },

  mounted() {

    this.initRoles()

    this.initAllMenus()

  },

  methods: {

    // 删除角色

    doDeleteRole(role){

      this.$confirm('此操作将永久删除[' + role.nameZh + ']角色, 是否继续?', '提示', {

        confirmButtonText: '确定',

        cancelButtonText: '取消',

        type: 'warning'

      }).then(() => {

        this.deleteRequest('/system/basic/permission/role/' + role.id).then(resp => {

          if (resp) {

            this.initRoles()

          }

        })

      }).catch(() => {

        this.$message({

          type: 'info',

          message: '已取消删除'

        });

      });

    },

    // 添加角色

    doAddRole(){

      if (this.role.name && this.role.nameZh) {

        this.postRequest('/system/basic/permission/role',this.role).then(resp=>{

          if (resp) {

            this.initRoles()

            this.role.name = ''

            this.role.nameZh = ''

          }

        })

      }else {

        this.$message.error('所有字段不能为空!')

      }

    },

    // 取消修改

    cancelUpdate() {

      this.activeName = -1 // 关闭折叠面板

    },

    // 确认修改

    doUpdate(rid, index) {

      let tree = this.$refs.treeRef[index] // 获取引用对象和索引

      let selectedKeys = tree.getCheckedKeys(true) // 获取选中的节点数组,true 仅返回被选中的叶子节点的 keys,如三级分类

      // console.log(selectedKeys)

      let url = '/system/basic/permission/?rid=' + rid

      selectedKeys.forEach(key => {

        // 循环遍历出数组 id ,拼接在一起

        url += '&mids=' + key

      })

      this.putRequest(url).then(resp => {

        if (resp) {

          this.activeName = -1 // 关闭折叠面板

        }

      })

    },

    // 手风琴点击事件

    change(rid) {

      if (rid) {

        this.initAllMenus() // 调用获取所有菜单

        this.initSelectedMenus(rid) // 调用获取所有选中的菜单

        // alert(rid) // :name="r.id"  label: 'name'

      }

    },

    // 获取所有选中的菜单

    initSelectedMenus(rid) { // :name="r.id"  change(rid)

      this.getRequest('/system/basic/permission/mid/' + rid).then(resp => {

        if (resp) {

          this.selectedMenus = resp

        }

      })

    },

    // 获取所有菜单

    initAllMenus() {

      this.getRequest('/system/basic/permission/menus').then(resp => {

        if (resp) {

          this.allMenus = resp

        }

      })

    },

    // 获取所有角色

    initRoles() {

      this.getRequest('/system/basic/permission/').then(resp => {

        if (resp) {

          this.roles = resp

        }

      })

    }

  }

}

</script>

<style scoped>

.positionManaTool {

  display: flex;

  justify-content: flex-start;

}

.positionManaTool .el-input {

  width: 300px;

  margin-right: 6px;

}

.positionManaMain {

  margin-top: 10px;

  width: 700px;

}

</style>

八、オペレーター管理

オペレーター名を検索することでオペレーター情報を個別に表示できます。全オペレーターを表示する場合、現在ログインしているオペレーターは表示されません。

オペレーターには権限が関係します。つまり、オペレーターがどの役割を持っているか、および役割に応じてオペレーターがどのメニュー権限を持っているかです。

オペレータ管理バックエンド情報の取得リターン

 

SysAdmin.vue

<template>

  <div>

    <!-- 1、 -->

    <div style="display: flex;justify-content: center;margin-top: 10px;">

      <!-- 9、v-model="keywords" \ @click="doSearch">搜索 -->

      <el-input v-model="keywords" placeholder="通过用户名搜索用户..." prefix-icon="el-icon-search"

                style="width: 400px;margin-right: 10px;"></el-input>

      <el-button type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>

    </div>

    <!-- 2、6、 -->

    <div class="admin-container">

      <el-card class="admin-card" v-for="(admin,index) in admins" :key="index">

        <div slot="header" class="clearfix">

            <div class="userInfoTab">

                <div style="margin-top:5px;margin-right: 8px;">{
   
   { admin.name}}</div>

                <div><img :src="admin.userFace" :alt="admin.name" :title="admin.name" class="userFace-img"></div>

            </div>

           <!-- 12、 @click="deleteAdmin(admin)" -->

          <el-button style="color:red;" type="text" icon="el-icon-delete"

                     @click="deleteAdmin(admin)"></el-button>

        </div>

        <div class="userinfo-container">

          <div>用户名:{
   
   { admin.name }}</div>

          <div>手机号码:{
   
   { admin.phone }}</div>

          <div>电话号码:{
   
   { admin.telephone }}</div>

          <div>地址:{
   
   { admin.address }}</div>

          <div>用户状态:

            <!-- 14、更新操作员 @change="enabledChange(admin)" -->

            <el-switch

                v-model="admin.enabled"

                active-color="#13ce66"

                inactive-color="#ff4949"

                @change="enabledChange(admin)"

                active-text="启用"

                inactive-text="禁用">

            </el-switch>

          </div>

          <div>

            用户角色:

            <el-tag style="margin-right: 4px;" type="success" v-for="(role,index) in admin.roles" :key="index">

              {
   
   { role.nameZh }}

            </el-tag>

            <!-- 16、更新操作员角色 弹出框、选择器、 -->

            <!-- 20、@show="showPop(admin)" -->

            <!-- 24、@hide="hidePop(admin)" hide 隐藏时触发-->

            <el-popover

                placement="right"

                title="角色列表"

                width="200"

                @show="showPop(admin)"

                @hide="hidePop(admin)"

                trigger="click">

              <!-- 17、更新操作员角色 下拉框 -->

              <!-- 22、v-model="selectedRoles" 存的是1个角色id,multiple 多选,显示已有角色 -->

              <el-select v-model="selectedRoles" multiple placeholder="请选择">

                <el-option

                    v-for="(r,index) in allRoles"

                    :key="index"

                    :label="r.nameZh"

                    :value="r.id">

                </el-option>

              </el-select>

              <!-- 3个点按钮 ... -->

              <el-button slot="reference" type="text" icon="el-icon-more"></el-button>

            </el-popover>

          </div>

          <div>备注:{
   
   { admin.remark }}</div>

        </div>

      </el-card>

    </div>

  </div>

</template>

<script>

export default {

  name: "SysAdmin",

  data() {

    return {

      admins: [], // 3

      keywords: '', // 8、搜索关键字

      allRoles: [], // 18、更新操作员角色

      selectedRoles: [] // 23

    }

  },

  mounted() {

    this.initAdmins() // 5

  },

  methods: {

    // 25、更新操作员角色

    hidePop(admin) {

      let roles = []

      Object.assign(roles, admin.roles) // 拷贝对象

      let flag = false

      // 如果选中的角色 id 的长度和原来的不一样

      if (roles.length != this.selectedRoles.length) { // 用户对应角色id

        flag = true

      } else {

        // 角色 id 长度和原来的一样,但可能角色不一样

        // 先循环 admin.roles

        for (let i = 0; i < roles.length; i++) {

          let role = roles[i] // 用户对应的角色对象

          for (let j = 0; j < this.selectedRoles.length; j++) {

            let sr = this.selectedRoles[j]  // 拿到用户对应的角色对象的id

            if (role.id == sr) { // 角色一样

              roles.splice(i, 1) // 删除

              i--

              break

            }

          }

        }

        if (roles.length != 0) {

          flag = true

        }

      }

      if (flag) {

        // 拼接 url(参数为 adminId、角色 rids )

        let url = '/system/admin/role?adminId=' + admin.id;

        this.selectedRoles.forEach(sr => {

          url += '&rids=' + sr

        });

        this.putRequest(url).then(resp => {

          if (resp) {

            this.initAdmins()

          }

        });

      }

    },

    // 21、下拉框获取所有用户角色

    showPop(admin) {

      this.initAllRoles()

      let roles = admin.roles // 拿到整个数组

      this.selectedRoles = []

      roles.forEach(r => {

        this.selectedRoles.push(r.id) // r.id 相同的角色放进数组

      })

    },

    // 19、获取所有操作员

    initAllRoles() {

      this.getRequest(' /system/admin/roles').then(resp => {

        if (resp) {

          this.allRoles = resp

        }

      })

    },

    // 15、更新操作员

    enabledChange(admin) {

      this.putRequest('/system/admin/', admin).then(resp => {

        if (resp) {

          this.initAdmins()

        }

      })

    },

    // 13、删除操作员

    deleteAdmin(admin) {

      this.$confirm('此操作将永久删除该[' + admin.name + '], 是否继续?', '提示', {

        confirmButtonText: '确定',

        cancelButtonText: '取消',

        type: 'warning'

      }).then(() => {

        this.deleteRequest('/system/admin/' + admin.id).then(resp => {

          if (resp) {

            this.initAdmins()

          }

        })

      }).catch(() => {

        this.$message({

          type: 'info',

          message: '已取消删除'

        });

      });

    },

    // 10 搜索

    doSearch() {

      this.initAdmins()

    },

    // 4、获取所有操作员;11、加参数关键字

    initAdmins() {

      this.getRequest('/system/admin/?keywords=' + this.keywords).then(resp => {

        if (resp) {

          this.admins = resp

        }

      })

    }

  }

}

</script>

<style >

/* 7 */

.admin-container {

  margin-top: 10px;

  display: flex;

  justify-content: space-between;

  flex-wrap: wrap; /* 自动换行 */

}

.admin-card {

  width: 280px;

  margin-bottom: 20px;

}

.userInfoTab{

    /* background-color: black; */

    display: flex;

    justify-content: center;

}

/* 卡片顶部 */

.clearfix {

    display: flex;

    justify-content: space-between;

}

.userFace-img {

  width: 36px;

  height: 36px;

  border-radius: 36px;

}

/* 头像居中 */

/* .img-container {

  /* width: 100%;

  display: flex;

  justify-content: center;

} */

.userinfo-container {

  font-size: 12px;

  color: #3e9ef5;

}

</style>

9. 従業員情報

1. 全従業員を表示

2. ページング表示

3. 従業員検索

4. 従業員を追加する

5. 更新と削除

6. データのインポートとエクスポート

Axios自体はダウンロード機能を提供していないため、js-file-downloadをインストールする必要があります

npm install js-file-download

出力はストリームの形式であり、ストリームの形式はバイナリ配列です。axios のインターフェイス リクエストと同様に、js-file-download は axios によってカプセル化されたインターセプタ機能を共有しないため、js-file-download もリクエスト インターセプタとレスポンス インターセプタをカプセル化する必要があります。リクエスト インターセプタは、リクエスト ヘッダーの認可の設定をリセットする必要があります。axios のレスポンスインターセプタはレスポンスコードを判定するもので、js-file-download は返されたものが json 文字列であるかどうかを判定する必要があります。返されたヘッダーの content-type を判断し、content-type が application/json 形式の場合は通常の json リターンとなり、バイナリ コードを通常の文字列形式に変換する必要があります。非 json 文字列はストリームの形式で返されるため、fileName と contentType を取得する必要があります。ファイル名が中国語であることによる文字化けを防ぐために、ファイル名の形式を変換する必要があります。

        let fileDownload = require('js-file-download') // 插件

        let fileName = headers['content-disposition'].split(';')[1].split('filename=')[1]//文件名

        let contentType = headers['content-type'] // 响应类型

        fileName = decodeURIComponent(fileName) // 格式转换 防止乱码

        fileDownload(resp.data, fileName, contentType) // 通过插件下载文件

 EmpBasic.vue

<template>

  <div>

    <div>

      <div style="display: flex;justify-content: space-between;">

        <!-- 1、 -->

        <!-- 20、搜索 v-model="empName" <el-input @keydown.enter.native="initEmps" 回车键调用初始化会员方法

             21、@click="initEmps">搜索</el-button>

             22、清空 clearable @clear="initEmps" -->

        <!-- 28-8  :disabled="showAdvanceSearchVisible" -->

        <div style="margin-top: 10px;">

          <el-input style="width: 300px;margin-right: 10px;"

                    prefix-icon="el-icon-search"

                    v-model="empName"

                    placeholder="请输入员工名进行搜索..."

                    @keydown.enter.native="initEmps"

                    clearable

                    @clear="initEmps"

                    :disabled="showAdvanceSearchVisible"

          ></el-input>

          <el-button type="primary" icon="el-icon-search" @click="initEmps"

                     :disabled="showAdvanceSearchVisible">搜索

          </el-button>

          <!-- 28-3 @click="showAdvanceSearchVisible = !showAdvanceSearchVisible" -->

          <!-- 28-5 判断图标样式 :class="showAdvanceSearchVisible?'fa fa-angle-double-up':'fa fa-angle-double-down'"-->

          <el-button type="primary" @click="showAdvanceSearchVisible = !showAdvanceSearchVisible">

            <i :class="showAdvanceSearchVisible?'fa fa-angle-double-up':'fa fa-angle-double-down'"

               aria-hidden="true"></i>高级搜索

          </el-button>

        </div>

        <div>

          <!-- 27-1、3 导入数据 上传组件 用自己的按钮 -->

          <!-- 27-5 on-success 文件上传成功时的钩子; on-error 文件上传失败时的钩子; -->

          <!-- 27-8 导入的时候禁用导入按钮 :disabled="importDataDisabled"  -->

          <!-- 27-11 :headers="headers" 设置上传的请求头部 -->

          <el-upload style="display: inline-flex;margin-right: 8px;" :show-file-list="false"

                     :headers="headers"

                     :before-upload="beforeUpload"

                     :on-success="onSuccess"

                     :on-error="onError"

                     :disabled="importDataDisabled"

                     action="/employee/basic/import"

          >

            <el-button type="success" :icon="importDataBtnIcon" :disabled="importDataDisabled">{
   
   {

                importDataBtnText

              }}

            </el-button>

          </el-upload>

          <!-- 26-1、导出数据 @click="exportData" -->

          <el-button type="success" @click="exportData"><i class="el-icon-download" aria-hidden="true"></i>  导出数据

          </el-button>

          <!-- 23-3、 @click="showAddEmpView" -->

          <el-button type="primary" icon="el-icon-plus" @click="showAddEmpView">添加员工</el-button>

        </div>

      </div>

      <!-- 28-1 高级搜索条件框 -->

      <!-- 28-4 高级搜索条件框 v-show="showAdvanceSearchVisible" -->

      <!-- 28-6 添加展开动画效果 <transition name="fade"> 包含整个搜索条件框 </transition> -->

      <!-- 30-2 绑定搜索条件数据 v-model="searchValue.xxxxx" -->

      <transition name="slide-fade">

        <div v-show="showAdvanceSearchVisible"

             style="border: 1px solid #379ff5;border-radius: 5px;box-sizing: border-box;padding: 5px;margin: 10px 0;">

          <el-row>

            <el-col :span="5">

              政治面貌:

              <el-select v-model="searchValue.politicId" placeholder="请选择政治面貌" size="mini" style="width: 130px;">

                <el-option

                    v-for="item in politicsstatus"

                    :key="item.id"

                    :label="item.name"

                    :value="item.id">

                </el-option>

              </el-select>

            </el-col>

            <el-col :span="4">

              民族:

              <el-select v-model="searchValue.nationId" placeholder="民族" size="mini" style="width: 130px;">

                <el-option

                    v-for="item in nations"

                    :key="item.id"

                    :label="item.name"

                    :value="item.id">

                </el-option>

              </el-select>

            </el-col>

            <el-col :span="4">

              职位:

              <el-select v-model="searchValue.posId" placeholder="职位" size="mini" style="width: 130px;">

                <el-option

                    v-for="item in positions"

                    :key="item.id"

                    :label="item.name"

                    :value="item.id">

                </el-option>

              </el-select>

            </el-col>

            <el-col :span="4">

              职称:

              <el-select v-model="searchValue.jobLevelId" placeholder="职称" size="mini" style="width: 130px;">

                <el-option

                    v-for="item in joblevels"

                    :key="item.id"

                    :label="item.name"

                    :value="item.id">

                </el-option>

              </el-select>

            </el-col>

            <el-col :span="6">

              聘用形式:

              <el-radio-group v-model="searchValue.engageForm">

                <el-radio label="劳动合同">劳动合同</el-radio>

                <el-radio label="劳务合同">劳务合同</el-radio>

              </el-radio-group>

            </el-col>

          </el-row>

          <el-row style="margin-top: 10px;">

            <!-- 30-4 处理部门 v-model="visible2" -->

            <el-col :span="5">

              所属部门:

              <el-popover

                  placement="bottom"

                  title="请选择部门"

                  width="220"

                  trigger="manual"

                  v-model="visible2">

                <!-- 23-20 添加树形控件 default-expand-all  是否默认展开所有节点 ,节点点击事件 @node-click="handleNodeClick" -->

                <el-tree :data="allDeps"

                         :props="defaultProps"

                         default-expand-all

                         @node-click="searchHandleNodeClick"></el-tree>

                <!-- 30-6 @node-click="searchHandleNodeClick" -->

                <!-- node-click 节点被点击时的回调 共三个参数,依次为:传递给 data 属性的数组中该节点所对应的对象、节点对应的 Node、节点组件本身。 -->

                <!-- 自定义点击事件 -->

                <!-- 30-7 @click="showDepView2" -->

                <div slot="reference"

                     style="width:130px;display: inline-flex;

                 border-radius: 5px;border: 1px solid #dedede;height: 28px;cursor: pointer;align-items: center;

                 font-size: 12px;padding-left: 8px;box-sizing: border-box;"

                     @click="showDepView2">{
   
   { inputDepName }}

                </div><!-- 23-25 回显数据 {
   
   {inputDepName}} -->

              </el-popover>

            </el-col>

            <!-- 30-3 处理日期:v-model="searchValue.beginDateScope" value-format="yyyy-MM-dd" ;

                 两个面板各自独立切换当前年份 使用unlink-panels -->

            <el-col :span="10">

              入职日期:

              <el-date-picker

                  unlink-panels

                  size="mini"

                  v-model="searchValue.beginDateScope"

                  type="datetimerange"

                  range-separator="至"

                  value-format="yyyy-MM-dd"

                  start-placeholder="开始日期"

                  end-placeholder="结束日期">

              </el-date-picker>

            </el-col>

            <el-col :span="5" :offset="4">

              <el-button size="mini">取消</el-button>

              <!-- 30-10 @click="initEmps('advanced')" -->

              <el-button type="primary" icon="el-icon-search" size="mini" @click="initEmps('advanced')">搜索</el-button>

            </el-col>

          </el-row>

        </div>

      </transition>

    </div>

    <div style="margin-top: 10px;">

      <!-- 2、表格;6、添加 loading -->

      <el-table

          :data="emps"

          v-loading="loading"

          element-loading-text="拼命加载中"

          element-loading-spinner="el-icon-loading"

          element-loading-background="rgba(0, 0, 0, 0.8)"

          style="width: 100%" stripe border>

        <el-table-column

            type="selection"

            width="55">

        </el-table-column>

        <el-table-column

            prop="name"

            label="姓名"

            align="left"

            fixed

            width="90">

        </el-table-column>

        <el-table-column

            prop="gender"

            label="性别"

            align="left"

            width="40">

        </el-table-column>

        <el-table-column

            prop="workId"

            label="工号"

            align="left"

            width="85">

        </el-table-column>

        <el-table-column

            prop="birthday"

            label="出生日期"

            align="left"

            width="95">

        </el-table-column>

        <el-table-column

            prop="idCard"

            label="身份证号"

            width="150">

        </el-table-column>

        <el-table-column

            prop="wedlock"

            label="婚姻状态"

            align="center"

            width="70">

        </el-table-column>

        <el-table-column

            prop="nation.name"

            label="民族"

            align="left"

            width="50">

        </el-table-column>

        <el-table-column

            prop="nativePlace"

            label="籍贯"

            align="center"

            width="80">

        </el-table-column>

        <el-table-column

            prop="politicsStatus.name"

            label="政治面貌"

            width="100">

        </el-table-column>

        <el-table-column

            prop="email"

            label="电子邮件"

            align="left"

            width="150">

        </el-table-column>

        <el-table-column

            prop="phone"

            label="电话号码"

            align="left"

            width="100">

        </el-table-column>

        <el-table-column

            prop="address"

            label="联系地址"

            align="center"

            width="220">

        </el-table-column>

        <el-table-column

            prop="department.name"

            label="所属部门"

            align="left"

            width="100">

        </el-table-column>

        <el-table-column

            prop="position.name"

            label="职位"

            width="100">

        </el-table-column>

        <el-table-column

            prop="joblevel.name"

            label="级别"

            width="100">

        </el-table-column>

        <el-table-column

            prop="engageForm"

            label="聘用形式"

            align="left"

            width="100">

        </el-table-column>

        <el-table-column

            prop="tiptopDegree"

            label="最高学历"

            align="center"

            width="80">

        </el-table-column>

        <el-table-column

            prop="school"

            label="毕业学校"

            align="left"

            width="150">

        </el-table-column>

        <el-table-column

            prop="specialty"

            label="所属专业"

            align="left"

            width="150">

        </el-table-column>

        <el-table-column

            prop="workState"

            label="在职状态"

            align="center"

            width="70">

        </el-table-column>

        <el-table-column

            prop="beginDate"

            label="入职日期"

            align="left"

            width="95">

        </el-table-column>

        <el-table-column

            prop="conversionTime"

            label="转正日期"

            align="left"

            width="95">

        </el-table-column>

        <el-table-column

            prop="beginContract"

            label="合同起始日期"

            align="left"

            width="95">

        </el-table-column>

        <el-table-column

            prop="endContract"

            label="合同截止日期"

            align="left"

            width="95">

        </el-table-column>

        <el-table-column

            label="合同期限"

            align="left"

            width="100">

          <template slot-scope="scope">

            <el-tag>{
   
   { scope.row.contractTerm }}</el-tag>

            年

          </template>

        </el-table-column>

        <el-table-column

            label="操作"

            fixed="right"

            width="200">

          <template slot-scope="scope">

            <!-- 25-4 给编辑按钮绑定点击事件 @click="showEmpView(scope.row)" -->

            <el-button style="padding: 3px;" size="mini" @click="showEmpView(scope.row)">编辑</el-button>

            <!-- <el-button style="padding: 3px;" size="mini" type="primary" plain>查看高级资料</el-button> -->

            <!-- 24-1 删除员工 @click="deleteEmp(scope.row)" -->

            <el-button style="padding: 3px;" size="mini" type="danger" @click="deleteEmp(scope.row)">删除</el-button>

          </template>

        </el-table-column>

      </el-table>

      <!-- 10、分页 -->

      <div style="display: flex;justify-content: flex-end;margin-top: 10px;">

        <!-- 13、@current-change="currentChange" 当前页

             14、@size-change="sizeChange" 每页显示多少条 -->

        <el-pagination

            prev-text="上一页"

            next-text="下一页"

            @current-change="currentChange"

            @size-change="sizeChange"

            :page-sizes="[10,20,30,50,100]"

            layout="total, sizes, prev, pager, next, jumper"

            :total="total" background>

        </el-pagination>

      </div>

    </div>

    <!-- 23-1、开始- 添加员工弹框 -->

    <!-- 25-1 编辑员工 将添加员工弹框标题改为变量 根据条件显示是添加还是编辑 :title="title"  -->

    <el-dialog

        :title="title"

        :visible.sync="dialogVisible"

        width="80%">

      <div>

        <!-- 23-6、<el-row  <el-form -->

        <!-- 23-28 数据校验对象 :rules="empRules" ,每项属性对应 prop="posId" -->

        <el-form ref="empRef" :model="emp" :rules="empRules">

          <el-row>

            <el-col :span="6">

              <el-form-item label="姓名:" prop="name">

                <el-input v-model="emp.name" prefix-icon="el-icon-edit" placeholder="请输入员工姓名" size="mini"

                          style="width: 150px;"></el-input>

              </el-form-item>

            </el-col>

            <el-col :span="5">

              <el-form-item label="性别:" prop="gender">

                <el-radio-group v-model="emp.gender" style="margin-top: 8px;">

                  <el-radio label="男">男</el-radio>

                  <el-radio label="女">女</el-radio>

                </el-radio-group>

              </el-form-item>

            </el-col>

            <el-col :span="6">

              <el-form-item label="出生日期:" prop="birthday">

                <el-date-picker

                    v-model="emp.birthday"

                    type="date"

                    value-format="yyyy-MM-dd"

                    size="mini"

                    style="width: 150px;"

                    placeholder="出生日期">

                </el-date-picker>

              </el-form-item>

            </el-col>

            <el-col :span="7">

              <!-- 23-10、 添加员工 给每项赋值 -->

              <el-form-item label="政治面貌:" prop="politicId">

                <el-select v-model="emp.politicId" placeholder="请选择政治面貌" size="mini" style="width: 200px;">

                  <el-option

                      v-for="item in politicsstatus"

                      :key="item.id"

                      :label="item.name"

                      :value="item.id">

                  </el-option>

                </el-select>

              </el-form-item>

            </el-col>

          </el-row>

          <el-row>

            <el-col :span="6">

              <el-form-item label="民族:" prop="nationId">

                <el-select v-model="emp.nationId" placeholder="民族" size="mini" style="width: 150px;">

                  <el-option

                      v-for="item in nations"

                      :key="item.id"

                      :label="item.name"

                      :value="item.id">

                  </el-option>

                </el-select>

              </el-form-item>

            </el-col>

            <el-col :span="5">

              <el-form-item label="籍贯:" prop="nativePlace">

                <el-input v-model="emp.nativePlace" placeholder="籍贯" prefix-icon="el-icon-edit" size="small"

                          style="width: 120px;"></el-input>

              </el-form-item>

            </el-col>

            <el-col :span="6">

              <el-form-item label="电子邮箱:" prop="email">

                <el-input v-model="emp.email" placeholder="请输入电子邮箱" prefix-icon="el-icon-message" size="mini"

                          style="width: 150px;"></el-input>

              </el-form-item>

            </el-col>

            <el-col :span="7">

              <el-form-item label="联系地址:" prop="address">

                <el-input v-model="emp.address" placeholder="请输入联系地址" prefix-icon="el-icon-edit" size="mini"

                          style="width: 200px;"></el-input>

              </el-form-item>

            </el-col>

          </el-row>

          <el-row>

            <el-col :span="6">

              <el-form-item label="职位:" prop="posId">

                <el-select v-model="emp.posId" placeholder="职位" size="mini" style="width: 150px;">

                  <el-option

                      v-for="item in positions"

                      :key="item.id"

                      :label="item.name"

                      :value="item.id">

                  </el-option>

                </el-select>

              </el-form-item>

            </el-col>

            <el-col :span="5">

              <el-form-item label="职称:" prop="jobLevelId">

                <el-select v-model="emp.jobLevelId" placeholder="职称" size="mini" style="width: 150px;">

                  <el-option

                      v-for="item in joblevels"

                      :key="item.id"

                      :label="item.name"

                      :value="item.id">

                  </el-option>

                </el-select>

              </el-form-item>

            </el-col>

            <el-col :span="6">

              <!-- 23-15  -->

              <el-form-item label="所属部门:" prop="departmentId">

                <!-- 23-17 manual 手动弹出框 -->

                <el-popover

                    placement="bottom"

                    title="请选择部门"

                    width="200"

                    trigger="manual"

                    v-model="visible">

                  <!-- 23-20 添加树形控件 default-expand-all  是否默认展开所有节点 ,节点点击事件 @node-click="handleNodeClick" -->

                  <el-tree :data="allDeps"

                           :props="defaultProps"

                           default-expand-all

                           @node-click="handleNodeClick"></el-tree>

                  <!-- node-click 节点被点击时的回调 共三个参数,依次为:传递给 data 属性的数组中该节点所对应的对象、节点对应的 Node、节点组件本身。 -->

                  <!-- 自定义点击事件 -->

                  <div slot="reference"

                       style="width:150px;display: inline-flex;

                       border-radius: 5px;border: 1px solid #dedede;height: 28px;cursor: pointer;align-items: center;

                       font-size: 12px;padding-left: 8px;box-sizing: border-box;"

                       @click="showDepView">{
   
   { inputDepName }}

                  </div><!-- 23-25 回显数据 {
   
   {inputDepName}} -->

                </el-popover>

              </el-form-item>

            </el-col>

            <el-col :span="7">

              <el-form-item label="电话号码:" prop="phone">

                <el-input v-model="emp.phone" placeholder="请输入电话号码" size="mini" style="width: 200px;"

                          prefix-icon="el-icon-phone"></el-input>

              </el-form-item>

            </el-col>

          </el-row>

          <el-row>

            <el-col :span="6">

              <el-form-item label="工号:" prop="workId">

                <el-input v-model="emp.workId" placeholder="请输入工号" size="mini" style="width: 150px;"

                          prefix-icon="el-icon-edit" disabled></el-input>

              </el-form-item>

            </el-col>

            <el-col :span="5">

              <!-- 23-14 数据在 data 中写死的 -->

              <el-form-item label="学历:" prop="tiptopDegree">

                <el-select v-model="emp.tiptopDegree" placeholder="职称" size="mini" style="width: 150px;">

                  <el-option

                      v-for="item in tiptopDegrees"

                      :key="item"

                      :label="item"

                      :value="item">

                  </el-option>

                </el-select>

              </el-form-item>

            </el-col>

            <el-col :span="6">

              <el-form-item label="毕业院校:" prop="school">

                <el-input v-model="emp.school" placeholder="请输入学校" size="mini" style="width: 150px;"

                          prefix-icon="el-icon-edit"></el-input>

              </el-form-item>

            </el-col>

            <el-col :span="7">

              <el-form-item label="专业名称:" prop="specialty">

                <el-input v-model="emp.specialty" placeholder="请输入专业名称" size="mini" style="width: 200px;"

                          prefix-icon="el-icon-edit"></el-input>

              </el-form-item>

            </el-col>

          </el-row>

          <el-row>

            <el-col :span="6">

              <el-form-item label="入职日期:" prop="beginDate">

                <el-date-picker

                    v-model="emp.beginDate"

                    type="date"

                    value-format="yyyy-MM-dd"

                    size="mini"

                    style="width: 120px;"

                    placeholder="入职日期">

                </el-date-picker>

              </el-form-item>

            </el-col>

            <el-col :span="5">

              <el-form-item label="转正日期:" prop="conversionTime">

                <el-date-picker

                    v-model="emp.conversionTime"

                    type="date"

                    value-format="yyyy-MM-dd"

                    size="mini"

                    style="width: 120px;"

                    placeholder="转正日期">

                </el-date-picker>

              </el-form-item>

            </el-col>

            <el-col :span="6">

              <el-form-item label="合同起始日期:" prop="beginContract">

                <el-date-picker

                    v-model="emp.beginContract"

                    type="date"

                    value-format="yyyy-MM-dd"

                    size="mini"

                    style="width: 135px;"

                    placeholder="合同起始日期">

                </el-date-picker>

              </el-form-item>

            </el-col>

            <el-col :span="7">

              <el-form-item label="合同截止日期:" prop="endContract">

                <el-date-picker

                    v-model="emp.endContract"

                    type="date"

                    value-format="yyyy-MM-dd"

                    size="mini"

                    style="width: 170px;"

                    placeholder="合同截止日期">

                </el-date-picker>

              </el-form-item>

            </el-col>

          </el-row>

          <el-row>

            <el-col :span="8">

              <el-form-item label="身份证号码:" prop="idCard">

                <el-input v-model="emp.idCard" placeholder="请输入身份证号码"

                          size="mini" prefix-icon="el-icon-edit" style="width: 180px;"></el-input>

              </el-form-item>

            </el-col>

            <el-col :span="8">

              <el-form-item label="聘用形式:" prop="engageForm">

                <el-radio-group v-model="emp.engageForm" style="margin-top: 8px;">

                  <el-radio label="劳动合同">劳动合同</el-radio>

                  <el-radio label="劳务合同">劳务合同</el-radio>

                </el-radio-group>

              </el-form-item>

            </el-col>

            <el-col :span="8">

              <el-form-item label="婚姻状况:" prop="wedlock">

                <el-radio-group v-model="emp.wedlock" style="margin-top: 8px;">

                  <el-radio label="未婚">未婚</el-radio>

                  <el-radio label="已婚">已婚</el-radio>

                  <el-radio label="离异">离异</el-radio>

                </el-radio-group>

              </el-form-item>

            </el-col>

          </el-row>

        </el-form>

      </div>

      <span slot="footer" class="dialog-footer">

        <el-button @click="dialogVisible = false">取 消</el-button>

        <!-- 23-26 @click="doAddEmp"-->

        <el-button type="primary" @click="doAddEmp">确 定</el-button>

      </span>

    </el-dialog>

  </div>

</template>

<script>

export default {

  name: "EmpBasic",

  data() {

    return {

      searchValue: { // 30-1 高级搜索 条件对象

        politicId: null, // 政治面貌

        nationId: null, // 民族

        posId: null, // 职位

        jobLevelId: null, // 职称

        engageForm: '', // 聘用形式

        departmentId: null, // 部门 id

        beginDateScope: null // 入职日期范围

      },

      showAdvanceSearchVisible: false, // 28-2 高级搜索框 动态效果

      headers: { // 27-12 定义请求头

        Authorization: window.sessionStorage.getItem('tokenStr')

      },

      importDataDisabled: false, // 27-9 导入按钮 默认不禁用

      importDataBtnText: '导入数据', // 27-2 导入数据

      importDataBtnIcon: 'el-icon-upload2', // 27-2 导入数据

      title: '', // 25-2 添加编辑员工弹框动态标题

      emps: [], // 3、获取所有员工(分页)

      loading: false, // 7、添加 loading

      total: 0, // 11 分页总条数

      currentPage: 1, // 14、默认显示第1页(currentPage 后端字段)

      size: 10, // 15、默认每页显示 10 条

      empName: '', // 18、搜索

      dialogVisible: false, // 23-2、添加员工弹框

      nations: [],   // 23-7 添加员工 民族

      joblevels: [], // 23-7 职称

      politicsstatus: [], // 23-7 政治面貌

      positions: [],  // 23-7 职位

      department: [], // 部门

      // 23-13、学历

      tiptopDegrees: ['博士', '硕士', '本科', '大专', '高中', '初中', '小学', '其它'],

      // 23-5、添加员工

      emp: {

        id: null,

        name: '',

        gender: '',

        birthday: '',

        idCard: '',

        wedlock: '',

        nationId: null,

        nativePlace: '',

        politicId: null,

        email: '',

        phone: '',

        address: '',

        departmentId: null,

        jobLevelId: null,

        posId: null,

        engageForm: '',

        tiptopDegree: '',

        specialty: '',

        school: '',

        beginDate: '',

        workState: '在职',

        workId: '',

        contractTerm: null,

        conversionTime: '',

        notworkDate: null,

        beginContract: '',

        endContract: '',

        workAge: null,

        salaryId: null

      },

      visible: false, // 23-18 弹出框

      visible2: false, // 30-5 高级搜索 部门

      // 23-21 树形控件

      defaultProps: {

        children: 'children',

        label: 'name'

      },

      allDeps: [], // 23-21 树形控件 绑定 所属部门 数据对象

      inputDepName: '',// 23-23 回显部门数据

      // 23-30 表单数据校验

      empRules: {

        name: [{required: true, message: '请输入员工名', trigger: 'blur'}],

        gender: [{required: true, message: '请输入员工性别', trigger: 'blur'}],

        birthday: [{required: true, message: '请输入出生日期', trigger: 'blur'}],

        idCard: [{required: true, message: '请输入身份证号码', trigger: 'blur'},

          {

            pattern: /(^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$)|(^[1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{2}$)/,

            message: '身份证号码不正确', trigger: 'blur'

          }],

        wedlock: [{required: true, message: '请输入婚姻状况', trigger: 'blur'}],

        nationId: [{required: true, message: '请输入民族', trigger: 'blur'}],

        nativePlace: [{required: true, message: '请输入籍贯', trigger: 'blur'}],

        politicId: [{required: true, message: '请输入政治面貌', trigger: 'blur'}],

        email: [{required: true, message: '请输入邮箱地址', trigger: 'blur'},

          {type: 'email', message: '邮箱地址格式不正确', trigger: 'blur'}],

        phone: [{required: true, message: '请输入电话号码', trigger: 'blur'},

          {

            pattern: /^(0|86|17951)?(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$/,

            message: '请输入合法手机号码', trigger: 'blur'

          }],

        address: [{required: true, message: '请输入地址', trigger: 'blur'}],

        departmentId: [{required: true, message: '请输入部门名称', trigger: 'blur'}],

        jobLevelId: [{required: true, message: '请输入职称', trigger: 'blur'}],

        posId: [{required: true, message: '请输入职位', trigger: 'blur'}],

        engageForm: [{required: true, message: '请输入聘用形式', trigger: 'blur'}],

        tiptopDegree: [{required: true, message: '请输入学历', trigger: 'blur'}],

        specialty: [{required: true, message: '请输入专业', trigger: 'blur'}],

        school: [{required: true, message: '请输入毕业院校', trigger: 'blur'}],

        beginDate: [{required: true, message: '请输入入职日期', trigger: 'blur'}],

        workState: [{required: true, message: '请输入工作状态', trigger: 'blur'}],

        workId: [{required: true, message: '请输入工号', trigger: 'blur'}],

        contractTerm: [{required: true, message: '请输入合同期限', trigger: 'blur'}],

        conversionTime: [{required: true, message: '请输入转正日期', trigger: 'blur'}],

        notworkDate: [{required: true, message: '请输入离职日期', trigger: 'blur'}],

        beginContract: [{required: true, message: '请输入合同起始日期', trigger: 'blur'}],

        endContract: [{required: true, message: '请输入合同结束日期', trigger: 'blur'}],

        workAge: [{required: true, message: '请输入工龄', trigger: 'blur'}]

      }

    }

  },

  mounted() {

    this.initEmps() // 5、获取所有员工(分页)

    this.initData() // 23-9 添加员工

    this.initPositions() // 23-12 获取职位

  },

  methods: {

    // 27-6 数据导入成功 恢复原来的图标和状态

    onSuccess() {

      this.importDataBtnIcon = 'el-icon-upload2'

      this.importDataBtnText = '导入数据'

      this.importDataDisabled = false // 29-10 不禁用导入按钮

      this.initEmps()

    },

    // 27-7 数据导入失败 恢复原来的图标和状态

    onError() {

      this.importDataBtnIcon = 'el-icon-upload2'

      this.importDataBtnText = '导入数据'

      this.importDataDisabled = false // 29-10 不禁用导入按钮

    },

    // 27-4、导入数据 改变图标和添加 loading 状态

    beforeUpload() {

      this.importDataBtnIcon = 'el-icon-loading'

      this.importDataBtnText = '正在导入'

      this.importDataDisabled = true // 29-10 禁用导入按钮

    },

    // 26-2 下载请求

    exportData() {

      this.downloadRequest('/employee/basic/export')

    },

    // 25-5 编辑员工按钮 点击事件

    showEmpView(data) {

      this.title = '编辑员工信息'

      this.emp = data // 回显数据

      this.inputDepName = data.department.name // 25-7 回显部门信息

      this.initPositions() // 25-9 初始化职位信息

      this.dialogVisible = true

    },

    // 24-2 删除员工

    deleteEmp(data) {

      this.$confirm('此操作将永久删除该员工' + data.name + ', 是否继续?', '提示', {

        confirmButtonText: '确定',

        cancelButtonText: '取消',

        type: 'warning'

      }).then(() => {

        this.deleteRequest('/employee/basic/' + data.id).then(resp => {

          if (resp) {

            this.initEmps()

          }

        })

      }).catch(() => {

        this.$message({

          type: 'info',

          message: '已取消删除'

        });

      });

    },

    // 23-27 确定添加员工

    // 25-10 添加或编辑员工 有id编辑员工 没有id添加员工

    // 添加和编辑这里就请求方式不一样 putRequest postRequest ,其它的都一样

    doAddEmp() {

      if (this.emp.id) {

        // 有 id 编辑员工

        this.$refs['empRef'].validate(valid => {

          if (valid) {

            this.putRequest('/employee/basic/', this.emp).then(resp => {

              if (resp) {

                this.dialogVisible = false

                this.initEmps()

              }

            })

          }

        })

      } else {

        // 没有id 添加员工

        // empRef 表单中定义的引用对象 ref="empRef"

        this.$refs['empRef'].validate(valid => {

          if (valid) {

            this.postRequest('/employee/basic/', this.emp).then(resp => {

              if (resp) {

                this.dialogVisible = false

                this.initEmps()

              }

            })

          }

        })

      }

    },

    // 30-7 高级搜索 部门点击事件

    searchHandleNodeClick(data) {

      this.inputDepName = data.name

      this.searchValue.departmentId = data.id

      this.visible2 = !this.visible2 // 弹框

    },

    // 23-22、24 树控件节点点击事件

    handleNodeClick(data) {

      this.inputDepName = data.name

      this.emp.departmentId = data.id

      this.visible = !this.visible // 弹框

    },

    // 30-9 高级搜索 部门弹框

    showDepView2() {

      this.visible2 = !this.visible2

    },

    // 23-16 添加员工 所属部门

    showDepView() {

      this.visible = !this.visible // 23-19 弹出框

    },

    // 23-13 添加员工 获取最大号

    getMaxworkId() {

      this.getRequest('/employee/basic/maxWorkID').then(resp => {

        if (resp) {

          this.emp.workId = resp.obj

        }

      })

    },

    // 23-11、 添加员工 获取职位 有可能变动 打开对话框的时候调用此方法

    initPositions() {

      this.getRequest('/employee/basic/Positions').then(resp => {

        if (resp) {

          this.positions = resp

        }

      })

    },

    // 23-8、添加员工 不怎么变动的数据。放 sessionStorage ,就不用怎么去查

    initData() {

      // 获取民族数据:先从 sessionStorage 里取,取不到再调用接口获取数据

      if (!window.sessionStorage.getItem("nations")) {

        this.getRequest('/employee/basic/nations').then(resp => {

          this.nations = resp

          // 存到 sessionStorage 里,把对象转字符串

          window.sessionStorage.setItem('nations', JSON.stringify(resp))

        })

      } else {

        // 从 sessionStorage 获取,字符串转对象

        this.nations = JSON.parse(window.sessionStorage.getItem('nations'))

      }

      // 获取职称

      if (!window.sessionStorage.getItem('joblevels')) {

        this.getRequest('/employee/basic/joblevels').then(resp => {

          if (resp) {

            this.joblevels = resp

            window.sessionStorage.setItem('joblevels', JSON.stringify(resp))

          }

        })

      } else {

        // 从 sessionStorage 获取,字符串转对象

        this.joblevels = JSON.parse(window.sessionStorage.getItem('joblevels'))

      }

      // 获取政治面貌

      if (!window.sessionStorage.getItem('politicsstatus')) {

        this.getRequest('/employee/basic/politicsStatus').then(resp => {

          if (resp) {

            this.politicsstatus = resp

            window.sessionStorage.setItem('politicsstatus', JSON.stringify(resp))

          }

        })

      } else {

        // 从 sessionStorage 获取,字符串转对象

        this.politicsstatus = JSON.parse(window.sessionStorage.getItem('politicsstatus'))

      }

      // 23-22 树形控件 绑定 所属部门 数据对象

      if (!window.sessionStorage.getItem('allDeps')) {

        this.getRequest('/employee/basic/deps').then(resp => {

          if (resp) {

            this.allDeps = resp

            window.sessionStorage.setItem('allDeps', JSON.parse(resp))

          }

        })

      } else {

        this.allDeps = window.sessionStorage.getItem('allDeps')

      }

    },

    // 23-4、添加员点击事件

    showAddEmpView() {

      // 25-6 清空表单

      this.emp = {

        id: null,

        name: '',

        gender: '',

        birthday: '',

        idCard: '',

        wedlock: '',

        nationId: null,

        nativePlace: '',

        politicId: null,

        email: '',

        phone: '',

        address: '',

        departmentId: null,

        jobLevelId: null,

        posId: null,

        engageForm: '',

        tiptopDegree: '',

        specialty: '',

        school: '',

        beginDate: '',

        workState: '在职',

        workId: '',

        contractTerm: null,

        conversionTime: '',

        notworkDate: null,

        beginContract: '',

        endContract: '',

        workAge: null,

        salaryId: null

      }

      this.inputDepName = '' // 25-8 清空部门信息

      this.title = '添加员工' // 25-3 点击添加员工按钮时,弹出框标题为 添加员工

      this.getMaxworkId() // 23-14 获取最大工号

      this.initPositions() // 23-12 获取职位

      this.dialogVisible = true

    },

    // 15、分页 每页显示多少条 默认会把 size 传进来

    sizeChange(size) {

      this.size = size

      this.initEmps()

    },

    // 13、分页-当前页-currentPage 点击的时候自己会带过来

    currentChange(currentPage) {

      this.currentPage = currentPage // 16

      this.initEmps() // 18、调用方法

    },

    // 4、获取所有员工(分页)

    initEmps(type) {

      this.loading = true // 8、添加 loading

      // 30-11 定义高级搜索 url

      let url = '/employee/basic/?currentPage=' + this.currentPage + '&size=' + this.size

      if (type && type === 'advanced') { // 说明是高级搜索

        if (this.searchValue.politicId) {

          url += '&politicId=' + this.searchValue.politicId

        }

        if (this.searchValue.nationId) {

          url += '&nationId=' + this.searchValue.nationId

        }

        if (this.searchValue.posId) {

          url += '&posId=' + this.searchValue.posId

        }

        if (this.searchValue.jobLevelId) {

          url += '&jobLevelId=' + this.searchValue.jobLevelId

        }

        if (this.searchValue.engageForm) {

          url += '&engageForm=' + this.searchValue.engageForm

        }

        if (this.searchValue.departmentId) {

          url += '&departmentId=' + this.searchValue.departmentId

        }

        if (this.searchValue.beginDateScope) {

          url += '&beginDateScope=' + this.searchValue.beginDateScope

        }

      } else {

        url += '&name=' + this.empName

      }

      // 17、添加分页参数 ?currentPage='+this.currentPage+'&size='+this.size

      // 19、添加用户名搜索参数 +'&name='+this.empName,传参 根据条件搜索,不传参查询所有

      this.getRequest(url).then(resp => {

        // this.getRequest('/employee/basic/').then(resp => {

        this.loading = false // 9、关闭 loading

        if (resp) {

          this.emps = resp.data

          this.total = resp.total // 12、分页

        }

      });

    }

  }

}

</script>

<style>

/*28-7 展开收起条件搜索框动画样式 */

/* 可以设置不同的进入和离开动画 */

/* 设置持续时间和动画函数 */

.slide-fade-enter-active {

  transition: all .8s ease;

}

.slide-fade-leave-active {

  transition: all .8s cubic-bezier(1.0, 0.5, 0.8, 1.0);

}

.slide-fade-enter, .slide-fade-leave-to

  /* .slide-fade-leave-active for below version 2.1.8 */

{

  transform: translateX(10px);

  opacity: 0;

}

</style>

10. 給与口座管理

ツリー構造

 サルソブビュー

<template>

  <div>

    <!-- 1-1 绘制表格 -->

    <div style="display: flex;justify-content: space-between;">

      <!-- 2-3 @click="showAddSalaryView" 点击打开 添加工资账套对话框 -->

      <el-button type="primary" icon="el-icon-plus" size="mini" @click="showAddSalaryView">添加工资账套</el-button>

      <!-- 5-3 刷新功能 直接绑定点击事件 调用获取所有数据方法 -->

      <el-button type="success" icon="el-icon-refresh" size="mini" @click="initSalaries"></el-button>

    </div>

    <div style="margin-top: 10px;">

      <el-table

          :data="salaries"

          stripe

          border>

        <!-- 多选框 type="selection" -->

        <el-table-column

            type="selection"

            width="40">

        </el-table-column>

        <el-table-column

            prop="name"

            label="账套名称"

            width="120">

        </el-table-column>

        <el-table-column

            prop="basicSalary"

            label="基本工资"

            width="70">

        </el-table-column>

        <el-table-column

            prop="trafficSalary"

            label="交通补助"

            width="70">

        </el-table-column>

        <el-table-column

            prop="lunchSalary"

            label="午餐补助"

            width="70">

        </el-table-column>

        <el-table-column

            prop="bonus"

            label="奖金"

            width="70">

        </el-table-column>

        <el-table-column

            prop="createDate"

            label="启用时间"

            width="100">

        </el-table-column>

        <!-- 多级表头:el-table-column 里面嵌套 el-table-column,就可以实现多级表头  -->

        <el-table-column

            label="养老金"

            align="center">

          <el-table-column

              prop="pensionPer"

              label="比率"

              width="70">

          </el-table-column>

          <el-table-column

              prop="pensionBase"

              label="基数"

              width="70">

          </el-table-column>

        </el-table-column>

        <!-- 多级表头 -->

        <el-table-column

            label="医疗保险"

            align="center">

          <el-table-column

              prop="medicalPer"

              label="比率"

              width="70">

          </el-table-column>

          <el-table-column

              prop="medicalBase"

              label="基数"

              width="70">

          </el-table-column>

        </el-table-column>

        <!-- 多级表头 -->

        <el-table-column

            label="公积金"

            align="center">

          <el-table-column

              prop="accumulationFundPer"

              label="比率"

              width="70">

          </el-table-column>

          <el-table-column

              prop="accumulationFundBase"

              label="基数"

              width="70">

          </el-table-column>

        </el-table-column>

        <el-table-column

            label="操作">

          <!-- 5-1 删除工资账套 拿到当前行数据 绑定点击事件 传行数据-->

          <template slot-scope="scope">

            <!-- 6-4 @click="showEditSalaryView(scope.row)">编辑  -->

            <el-button type="primary" size="mini" @click="showEditSalaryView(scope.row)">编辑</el-button>

            <el-button type="danger" size="mini" @click="deleteSalary(scope.row)">删除</el-button>

          </template>

        </el-table-column>

      </el-table>

    </div>

    <!-- 2-1 添加工资账套对话框 -->

    <!-- 6-2 把标题变成属性 -->

    <el-dialog

        :title="dialogTitle"

        :visible.sync="dialogVisible"

        width="50%">

      <!-- 3-8 调整样式 -->

      <div style="display: flex;justify-content: space-around;align-items: center;">

        <!-- 3-1 添加步骤条 -->

        <!-- 3-5  :active="activeItemIndex" -->

        <el-steps direction="vertical" :active="activeItemIndex">

          <!-- 3-3 循环遍历数据 -->

          <el-step :title="itemName" v-for="(itemName,index) in salaryItemName" :key="index"></el-step>

        </el-steps>

        <!-- 3-4 循环遍历数据 -->

        <!-- 3-7 v-show="activeItemIndex = index" 与下标相等才展示 -->

        <!-- 4-2 修改各项的值 绑定和遍历-->

        <el-input v-model="salary[title]" :placeholder="'请输入'+salaryItemName[index]+'...'"

                  v-for="(value,title,index) in salary"

                  :key="index" v-show="activeItemIndex === index" style="width: 200px;"></el-input>

      </div>

      <span slot="footer" class="dialog-footer">

        <!-- 3-10 按钮判断根据索引显示 文字提示 -->

        <el-button @click="preStep">{
   
   { activeItemIndex === 10 ? '取消' : '上一步' }}</el-button>

        <el-button type="primary" @click="nextStep">{
   
   { activeItemIndex === 10 ? '完成' : '下一步' }}</el-button>

      </span>

    </el-dialog>

  </div>

</template>

<script>

export default {

  name: "SalSob",

  data() {

    return {

      dialogTitle: '添加工资账套', // 6-1 标题

      dialogVisible: false, // 2-2 添加工资账套对话框

      salaries: [], // 1-2 定义数组

      activeItemIndex: 0, // 3-6 步骤条激活索引

      salaryItemName: [ // 3-2 步骤条数据对象

        '账套名称',

        '基本工资',

        '交通补助',

        '午餐补助',

        '奖金',

        '养老金比率',

        '养老金基数',

        '医疗保险比率',

        '医疗保险基数',

        '公积金比率',

        '公积金基数'

      ],

      // 4-1 定义工资账套数据

      salary: {

        name: '',

        basicSalary: 0,

        trafficSalary: 0,

        lunchSalary: 0,

        bonus: 0,

        pensionPer: 0.0,

        pensionBase: 0,

        medicalPer: 0.0,

        medicalBase: 0,

        accumulationFundPer: 0.0,

        accumulationFundBase: 0

      }

    }

  },

  mounted() {

    this.initSalaries()

  },

  methods: {

    // 6-5 点击编辑显示对话框

    showEditSalaryView(data) {

      this.dialogTitle = '编辑工资账套' // 设置标题

      this.activeItemIndex = 0 // 默认激活的索引

      this.salary.id = data.id

      this.salary.name = data.name

      this.salary.basicSalary = data.basicSalary

      this.salary.trafficSalary = data.trafficSalary

      this.salary.lunchSalary = data.lunchSalary

      this.salary.bonus = data.bonus

      this.salary.pensionPer = data.pensionPer

      this.salary.pensionBase = data.pensionBase

      this.salary.medicalPer = data.medicalPer

      this.salary.medicalBase = data.medicalBase

      this.salary.accumulationFundPer = data.accumulationFundPer

      this.salary.accumulationFundBase = data.accumulationFundBase

      this.dialogVisible = true // 打开对话框

    },

    // 5-2 删除工资账套

    deleteSalary(data) {

      this.$confirm('此操作将永久删除该[' + data.name + ']工资账套, 是否继续?', '提示', {

        confirmButtonText: '确定',

        cancelButtonText: '取消',

        type: 'warning'

      }).then(() => {

        this.deleteRequest('/salary/sob/' + data.id).then(resp => {

          if (resp) {

            this.initSalaries()

          }

        })

      }).catch(() => {

        this.$message({

          type: 'info',

          message: '已取消删除'

        });

      });

    },

    preStep() { // 3-13 上一步 取消

      if (this.activeItemIndex === 0) {

        return

      } else if (this.activeItemIndex === 10) {

        this.dialogVisible = false;

        return;

      }

      this.activeItemIndex--

    },

    nextStep() { // 3-12 下一步 完成

      if (this.activeItemIndex === 10) {

        // alert("ok")

        // console.log(this.salary)

        // 4-4 添加工资账套

        if (this.salary.id) { // 6-6 有 id 调用编辑接口,没有 id 执行添加

          this.putRequest('/salary/sob/', this.salary).then(resp => {

            if (resp) {

              this.initSalaries()

              this.dialogVisible = false // 关闭弹框

            }

          })

        } else {

          this.postRequest('/salary/sob/', this.salary).then(resp => {

            if (resp) {

              this.initSalaries()

              this.dialogVisible = false

            }

          })

        }

        return

      }

      this.activeItemIndex++

    },

    // 2-4 点击打开添加工资账套对话框

    showAddSalaryView() {

      this.dialogTitle = '添加工资账套' // 6-3 添加的时候显示此标题

      this.salary = { // 4-3 清空表单

        name: '',

        basicSalary: 0,

        trafficSalary: 0,

        lunchSalary: 0,

        bonus: 0,

        pensionPer: 0.0,

        pensionBase: 0,

        medicalPer: 0.0,

        medicalBase: 0,

        accumulationFundPer: 0.0,

        accumulationFundBase: 0

      }

      this.activeItemIndex = 0 // 3-14 步骤条索引从0开始

      this.dialogVisible = true;

    },

    // 1-3 初始化数据

    initSalaries() {

      this.getRequest('/salary/sob/').then(resp => {

        if (resp) {

          this.salaries = resp

        }

      })

    }

  }

}

</script>

<style scoped>

</style>

11. スタッフアカウントセットの設定

SalSobCfg.vue

<template>

  <div>

    <el-table

        size="mini"

        :data="emps"

        stripe

        border>

      <el-table-column

          align="left"

          type="selection"

          width="55">

      </el-table-column>

      <el-table-column

          prop="name"

          label="姓名"

          align="left"

          fixed

          width="120">

      </el-table-column>

      <el-table-column

          prop="workId"

          label="工号"

          align="left"

          width="120">

      </el-table-column>

      <el-table-column

          prop="email"

          label="邮箱地址"

          align="left"

          width="200">

      </el-table-column>

      <el-table-column

          prop="phone"

          label="电话号码"

          align="left"

          width="120">

      </el-table-column>

      <el-table-column

          prop="department.name"

          label="所属部门"

          align="left"

          width="120">

      </el-table-column>

      <el-table-column

          label="工资账套"

          align="center">

        <template slot-scope="scope">

          <el-tooltip placement="right" v-if="scope.row.salary">

            <div slot="content">

              <table>

                <tr>

                  <td>基本工资</td>

                  <td>{
   
   { scope.row.salary.basicSalary }}</td>

                </tr>

                <tr>

                  <td>交通补助</td>

                  <td>{
   
   { scope.row.salary.trafficSalary }}</td>

                </tr>

                <tr>

                  <td>午餐补助</td>

                  <td>{
   
   { scope.row.salary.lunchSalary }}</td>

                </tr>

                <tr>

                  <td>奖金</td>

                  <td>{
   
   { scope.row.salary.bonus }}</td>

                </tr>

                <tr>

                  <td>养老金比率</td>

                  <td>{
   
   { scope.row.salary.pensionPer }}</td>

                </tr>

                <tr>

                  <td>养老金基数</td>

                  <td>{
   
   { scope.row.salary.pensionBase }}</td>

                </tr>

                <tr>

                  <td>医疗保险比率</td>

                  <td>{
   
   { scope.row.salary.medicalPer }}</td>

                </tr>

                <tr>

                  <td>医疗保险基数</td>

                  <td>{
   
   { scope.row.salary.medicalBase }}</td>

                </tr>

                <tr>

                  <td>公积金比率</td>

                  <td>{
   
   { scope.row.salary.accumulationFundPer }}</td>

                </tr>

                <tr>

                  <td>公积金基数</td>

                  <td>{
   
   { scope.row.salary.accumulationFundBase }}</td>

                </tr>

              </table>

            </div>

            <el-tag>{
   
   { scope.row.salary.name }}</el-tag>

          </el-tooltip>

          <el-tag v-else>暂未设置</el-tag>

        </template>

      </el-table-column>

      <!-- 2-1 编辑工资账套 -->

      <el-table-column

          label="操作"

          align="center">

        <template slot-scope="scope">

          <!-- 2-5 当前员工的工资账套 @show="showPop(scope.row.salary)" show 显示时触发 -->

          <!-- 2-9 @hide="hidePop(scope.row)" hide  隐藏时触发 -->

          <el-popover

              size="mini"

              @show="showPop(scope.row.salary)"

              @hide="hidePop(scope.row)"

              placement="right"

              title="编辑工资账套"

              width="200"

              trigger="click">

            <div>

              <!-- 2-6  v-model="currentSalary" -->

              <el-select v-model="currentSalary" placeholder="请选择">

                <el-option

                    size="mini"

                    v-for="item in salaries"

                    :key="item.id"

                    :label="item.name"

                    :value="item.id">

                </el-option>

              </el-select>

            </div>

            <el-button slot="reference" type="danger">修改工资账套</el-button>

          </el-popover>

        </template>

      </el-table-column>

    </el-table>

    <!-- 1-1 分页组件 -->

    <div style="display: flex;justify-content: flex-end;margin-top: 5px;">

      <el-pagination

          @size-change="sizeChange"

          @current-change="currentChange"

          layout="total, sizes, prev, pager, next, jumper"

          :total="total" background>

      </el-pagination>

    </div>

  </div>

</template>

<script>

export default {

  name: "SalSobCfg",

  data() {

    return {

      emps: [],

      salaries: [], // 2-2 工资账套数组

      currentPage: 1, // 1-2 当前页

      size: 10, // 1-2 每页显示条数

      total: 0, // 1-2 分页

      currentSalary: null // 2-7 当前员工工资账套

    }

  },

  mounted() {

    this.initEmps()

    this.initSalaries() // 2-4 初始化 获取所有工资账套

  },

  methods: {

    // 2-10

    hidePop(data) { // 隐藏时触发

      // 当前员工工资账套存在 并且不等于当前的 才更新

      if (this.currentSalary && this.currentSalary!==data.salary.id) {

        this.putRequest('/salary/sobcfg/?eid=' + data.id + '&sid=' + this.currentSalary).then(resp => {

          if (resp) {

            this.initEmps()

          }

        });

      }

    },

    // 2-8 员工工资账套

    showPop(data) { // 显示时触发

      if (data) {

        this.currentSalary = data.id;

      } else {

        this.currentSalary = null

      }

    },

    // 2-3 获取所有工资账套

    initSalaries() {

      this.getRequest('/salary/sobcfg/salaries').then(resp => {

        if (resp) {

          this.salaries = resp

        }

      })

    },

    // 1-3 分页-当前页

    currentChange(page) {

      this.currentPage = page

      this.initEmps()

    },

    // 1-4 分页-每页显示数量

    sizeChange(size) {

      this.size = size

      this.initEmps()

    },

    // 获取所有数据

    initEmps() {

      this.getRequest('/salary/sobcfg/?currentPage=' + this.currentPage + '&size=' + this.size).then(resp => {

        if (resp) {

          this.emps = resp.data

          this.total = resp.total

        }

      })

    }

  }

}

</script>

<style scoped>

</style>

12.チャット機能

npm install --save stompjs をインストールします。

GitHub上のオープンソースプロジェクトと独自プロジェクトを統合してインスタントチャット機能を実現

導入

Vue + Webpack をベースにした簡単なチャットの例。チャット履歴は localStorge に保存されます。Vue のコンポーネント、フィルター、ディレクティブ、コンポーネント間の計算通信およびイベント通信の簡単なデモンストレーション。現在、元のプロジェクトにはバグがあります。プロジェクトを開いてブラウザを閉じ、再度開くとエラーが報告されます。ここでは、このプロジェクトに基づいて再構築されたプロジェクトを使用して、私たちのプロジェクトと統合します。

ダウンロード(Chromeでダウンロードを開きます)

住所:

https://github.com/is-liyiwei/vue-Chat-demo

統合プロジェクト

ダウンロードしたプロジェクトが存在するファイルをこのプロジェクトに追加します。

ここに画像の説明を挿入

資産: Web ページに必要な画像ですが、バックエンドが画像を提供し、変更されたコードがバックエンドから直接取得されるため、これは必要ありません。

コンポーネント: このディレクトリに独自のディレクトリにいくつかのファイルを作成し、それらをコピーします。

vuex: これは stroy です。そのコードをこのプロジェクトの story/index.js に追加します。

main.js: これは通常のエントリであり、概要のためにプロジェクトに追加する必要はありません。

コンポーネント

カードビュー

<template>

  <div id="card">

    <header>

        <img class="avatar" v-bind:src="user.userFace" v-bind:alt="user.name">

        <p class="name">{
   
   {user.name}}</p>

    </header>

    <footer>

        <input class="search" type="text" v-model="$store.state.filterKey" placeholder="search user...">

    </footer>

  </div>

</template>

<script>

export default {

  name: 'card',

  data () {

    return {

      user:JSON.parse(window.sessionStorage.getItem("user"))

    }

  }

}

</script>

<style lang="scss" scoped>

#card {

    padding: 12px;

  .avatar{

    width: 40px;

    height: 40px;

    vertical-align: middle;/*这个是图片和文字居中对齐*/

  }

  .name {

    display: inline-block;

    padding: 10px;

    margin-bottom: 15px;

    font-size: 16px;

  }

  .search {

    background: #26292E;

    height: 30px;

    line-height: 30px;

    padding: 0 10px;

    border: 1px solid #3a3a3a;

    border-radius: 4px;

    outline: none;/*鼠标点击后不会出现蓝色边框*/

    color: #FFF;

  }

}

</style>

list.vue

<template>

  <div id="list">

    <ul style="padding-left: 0;">

      <li v-for="item in admins" :class="{ active: currentSession?item.username === currentSession.username:false }"

          v-on:click="changecurrentSession(item)"><!--   :class="[item.id === currentSession ? 'active':'']" -->

        <!-- 未读消息提示 小红点  <el-badge is-dot> </el-badge> -->

        <el-badge is-dot :is-dot="idDot[user.username+'#'+item.username]"><img class="avatar" :src="item.userFace" :alt="item.name"></el-badge>

        <p class="name">{
   
   { item.name }}</p>

      </li>

    </ul>

  </div>

</template>

<script>

import {mapState} from 'vuex'

export default {

  name: 'list',

  data() {

    return {

      user:JSON.parse(window.sessionStorage.getItem('user'))

    }

  },

  computed: mapState([

    'idDot',

    'admins',

    'currentSession'

  ]),

  methods: {

    changecurrentSession: function (currentSession) {

      this.$store.commit('changecurrentSession', currentSession)

    }

  }

}

</script>

<style lang="scss" scoped>

#list {

  li {

    padding: 0 15px;

    border-bottom: 1px solid #292C33;

    cursor: pointer;

    &:hover {

      background-color: rgba(255, 255, 255, 0.03);

    }

  }

  li.active { /*注意这个是.不是冒号:*/

    background-color: rgba(255, 255, 255, 0.1);

  }

  .avatar {

    border-radius: 2px;

    width: 30px;

    height: 30px;

    vertical-align: middle;

  }

  .name {

    display: inline-block;

    margin-left: 15px;

  }

}

</style>

メッセージ.ビュー

<template>

  <div id="message" v-scroll-bottom="sessions">

    <ul v-if="currentSession">

      <li v-for="entry in sessions[user.username+'#'+currentSession.username]">

        <p class="time">

          <span>{
   
   { entry.date | time }}</span>

        </p>

        <div class="main" :class="{self:entry.self}">

          <img class="avatar" :src="entry.self ? user.userFace:currentSession.userFace" alt="">

          <p class="text">{
   
   { entry.content }}</p>

        </div>

      </li>

    </ul>

  </div>

</template>

<script>

import {mapState} from 'vuex'

export default {

  name: 'message',

  data() {

    return {

      user: JSON.parse(window.sessionStorage.getItem('user')), // 当前用户

    }

  },

  computed: mapState([

    'sessions',

    'currentSession'

  ]),

  filters: {

    time(date) {

      if (date) {

        date = new Date(date);

      }

      return `${date.getHours()}:${date.getMinutes()}`;

    }

  },

  directives: {/*这个是vue的自定义指令,官方文档有详细说明*/

    // 发送消息后滚动到底部,这里无法使用原作者的方法,也未找到合理的方法解决,暂用setTimeout的方法模拟

    'scroll-bottom'(el) {

      //console.log(el.scrollTop);

      setTimeout(function () {

        el.scrollTop += 9999;

      }, 1)

    }

  }

}

</script>

<style lang="scss" scoped>

#message {

  padding: 15px;

  max-height: 68%;

  overflow-y: scroll;

  ul {

    list-style-type: none;

    padding-left: 0;

    li {

      margin-bottom: 15px;

    }

  }

  .time {

    text-align: center;

    margin: 7px 0;

    > span {

      display: inline-block;

      padding: 0 18px;

      font-size: 12px;

      color: #FFF;

      background-color: #dcdcdc;

      border-radius: 2px;

    }

  }

  .main {

    .avatar {

      float: left;

      margin: 0 10px 0 0;

      border-radius: 3px;

      width: 30px;

      height: 30px;

    }

    .text {

      display: inline-block;

      padding: 0 10px;

      max-width: 80%;

      background-color: #fafafa;

      border-radius: 4px;

      line-height: 30px;

    }

  }

  .self {

    text-align: right;

    .avatar {

      float: right;

      margin: 0 0 0 10px;

      border-radius: 3px;

      width: 30px;

      height: 30px;

    }

    .text {

      display: inline-block;

      padding: 0 10px;

      max-width: 80%;

      background-color: #b2e281;

      border-radius: 4px;

      line-height: 30px;

    }

  }

}

</style>

ユーザーテキスト.vue

<template>

  <div id="uesrtext">

    <textarea placeholder="按 Ctrl + Enter 发送" v-model="content" v-on:keyup="addMessage"></textarea>

  </div>

</template>

<script>

import {mapState} from 'vuex'

export default {

  name: 'uesrtext',

  data() {

    return {

      content: ''

    }

  },

  computed: mapState([

    'currentSession'

  ]),

  methods: {

    addMessage(e) {

      if (e.ctrlKey && e.keyCode === 13 && this.content.length) {

        // 自定义发送消息

        let msgObj = {}

        // let msgObj = new Object()

        msgObj.to = this.currentSession.username

        msgObj.content = this.content

        this.$store.state.stomp.send('/ws/chat', {}, JSON.stringify(msgObj))

        this.$store.commit('addMessage', msgObj);

        this.content = '';

      }

    }

  }

}

</script>

<style lang="scss" scoped>

#uesrtext {

  position: absolute;

  bottom: 0;

  right: 0;

  width: 100%;

  height: 30%;

  border-top: solid 1px #DDD;

  > textarea {

    padding: 10px;

    width: 100%;

    height: 100%;

    border: none;

    outline: none;

  }

}

</style>

chat/FriendChat.vue

<template>

  <div id="app">

    <div class="sidebar">

      <card></card>

      <list></list>

    </div>

    <div class="main">

      <message></message>

      <userText></userText>

    </div>

  </div>

</template>

<script>

import card from '@/components/chat/card.vue'

import list from '@/components/chat/list.vue'

import message from '@/components/chat/message.vue'

import userText from '@/components/chat/usertext.vue'

export default {

  name: 'FriendChat',

  data () {

    return {

    }

  },

  mounted:function() {

    this.$store.dispatch('initData');

  },

  components:{

    card,

    list,

    message,

    userText

  }

}

</script>

<style lang="scss" scoped>

#app {

  margin: 20px 100px;

  //margin: 20px auto;

  width: 800px;

  height: 600px;

  overflow: hidden;

  border-radius: 10px;

  border: 1px solid #c8c9c9;

  .sidebar, .main {

    height: 100%;

  }

  .sidebar {

    float: left;

    color: #f4f4f4;

    background-color: #2e3238;

    width: 200px;

  }

  .main {

    position: relative;

    overflow: hidden;

    background-color: #eee;

  }

}

</style>

13、個人センター

 AdminInfo.vue

<template>

  <div>

    <el-card class="box-card" style="width: 400px;">

      <div slot="header" class="clearfix">

        <span>{
   
   { admin.name }}</span>

      </div>

      <div>

        <div>

          <div style="display: flex;justify-content: center;">

            <img title="点击修改用户头像" :src="admin.userFace" style="height: 100px;width: 100px;border-radius: 50px;" alt="">

          </div>

          <div>电话号码:

            <el-tag>{
   
   { admin.telephone }}</el-tag>

          </div>

          <div>手机号码:

            <el-tag>{
   
   { admin.phone }}</el-tag>

          </div>

          <div>居住地址:

            <el-tag>{
   
   { admin.address }}</el-tag>

          </div>

          <div>用户标签:

            <el-tag type="success" v-for="(r,index) in admin.roles" :key="index">{
   
   { r.nameZh }}</el-tag>

          </div>

        </div>

        <div style="display: flex;justify-content: space-around;margin-top: 10px;">

          <!-- 1-3 @click="showUpdateAdminInfoView" -->

          <el-button type="primary" size="mini" @click="showUpdateAdminInfoView">修改信息</el-button>

          <!-- 2-1 用户修改密码 @click="showUpdatePasswordView"  -->

          <el-button type="danger" size="mini" @click="showUpdatePasswordView">修改密码</el-button>

        </div>

      </div>

    </el-card>

    <!-- 1-1 编辑用户信息 -->

    <el-dialog

        title="编辑用户信息"

        :visible.sync="dialogVisible"

        width="30%">

      <div>

        <table>

          <tr>

            <td>用户昵称:</td>

            <td>

              <!-- 1-5 重新给每项赋值 admin2 -->

              <el-input v-model="admin2.name"></el-input>

            </td>

          </tr>

          <tr>

            <td>电话号码:</td>

            <td>

              <el-input v-model="admin2.telephone"></el-input>

            </td>

          </tr>

          <tr>

            <td>手机号码:</td>

            <td>

              <el-input v-model="admin2.phone"></el-input>

            </td>

          </tr>

          <tr>

            <td>用户地址:</td>

            <td>

              <el-input v-model="admin2.address"></el-input>

            </td>

          </tr>

        </table>

      </div>

      <span slot="footer" class="dialog-footer">

        <el-button @click="dialogVisible = false">取 消</el-button>

        <!-- 1-8 @click="updateAdminInfo" -->

        <el-button type="primary" @click="updateAdminInfo">确 定</el-button>

      </span>

    </el-dialog>

    <!-- 2-2 修改密码 -->

    <el-dialog

        title="更新密码"

        :visible.sync="passwordDialogVisible"

        width="30%">

      <div>

        <!-- 2-8 调整修改密码表单 -->

        <el-form :model="ruleForm" status-icon :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm">

          <el-form-item label="请输入旧密码" prop="oldPass">

            <el-input type="password" v-model="ruleForm.oldPass" autocomplete="off"></el-input>

          </el-form-item>

          <el-form-item label="请输入新密码" prop="pass">

            <el-input type="password" v-model="ruleForm.pass" autocomplete="off"></el-input>

          </el-form-item>

          <el-form-item label="确认新密码" prop="checkPass">

            <el-input type="password" v-model="ruleForm.checkPass" autocomplete="off"></el-input>

          </el-form-item>

          <el-form-item>

            <el-button type="primary" @click="submitForm('ruleForm')">提交</el-button>

            <el-button @click="resetForm('ruleForm')">重置</el-button>

          </el-form-item>

        </el-form>

      </div>

    </el-dialog>

  </div>

</template>

<script>

export default {

  name: "AdminInfo",

  data() {

    // 2-5 修改密码校验规则 一定要放最前面

    var validatePass = (rule, value, callback) => {

      if (value === '') {

        callback(new Error('请输入密码'));

      } else {

        if (this.ruleForm.checkPass !== '') {

          this.$refs.ruleForm.validateField('checkPass');

        }

        callback();

      }

    }

    var validatePass2 = (rule, value, callback) => {

      if (value === '') {

        callback(new Error('请再次输入密码'));

      } else if (value !== this.ruleForm.pass) {

        callback(new Error('两次输入密码不一致!'));

      } else {

        callback();

      }

    }

    return {

      admin: null,

      admin2: null, // 1-5 编辑的对象

      dialogVisible: false, // 1-2 编辑用户信息

      passwordDialogVisible: false, // 2-3 修改密码

      ruleForm: { // 2-6 校验对象 规则

        pass: '',

        checkPass: '',

        oldPass: '', // 2-9

      },

      rules: {

        pass: [

          {validator: validatePass, trigger: 'blur'}

        ],

        checkPass: [

          {validator: validatePass2, trigger: 'blur'}

        ],

        oldPass: [

          {validator: validatePass, trigger: 'blur'}

        ]

      }

    }

  },

  mounted() {

    this.initAdmin()

  },

  methods: {

    // 2-7 预校验 提交表单

    submitForm(formName) {

      this.$refs[formName].validate((valid) => {

        if (valid) {

          // alert('submit!');

          this.ruleForm.adminId = this.admin.id

          this.putRequest('/admin/pass', this.ruleForm).then(resp => {

            if (resp) {

              // 更新密码成功后 退出登录

              this.postRequest('/logout') // 退出登录

              window.sessionStorage.removeItem('user')

              window.sessionStorage.removeItem('tokenStr')

              this.$store.commit('initRoutes', []) // 初始化路由 菜单 置空

              this.$router.replace('/') // 跳到登录页面

            }

          })

        } else {

          console.log('error submit!!');

          return false;

        }

      });

    },

    // 2-7 重围修改密码表单

    resetForm(formName) {

      this.$refs[formName].resetFields();

    },

    // 2-4 修改密码

    showUpdatePasswordView() {

      this.passwordDialogVisible = true

    },

    // 1-9 更新用户

    updateAdminInfo() {

      this.putRequest('/admin/info', this.admin2).then(resp => {

        if (resp) {

          this.dialogVisible = false

          this.initAdmin()

        }

      })

    },

    // 1-4 编辑用户信息弹框

    showUpdateAdminInfoView() {

      this.dialogVisible = true

    },

    initAdmin() {

      this.getRequest('/admin/info').then(resp => {

        if (resp) {

          this.admin = resp

          this.admin2 = Object.assign({}, this.admin) // 1-6 对象拷贝给 admin2

          window.sessionStorage.setItem('user', JSON.stringify(resp))

          this.$store.commit('INIT_ADMIN', resp)

        }

      })

    }

  }

}

</script>

<style scoped>

</style>

14、ソースコード

フロントエンドアドレス: https://github.com/OneDayInMarch/yeb-front

バックエンド アドレス: GitHub - OneDayInMarch/yeb-back: クラウド オフィス バックグラウンド システム

おすすめ

転載: blog.csdn.net/qq_36384657/article/details/126275840