分類
發燒車訊

MongoDB 邏輯備份工具mongodump

mongodump是官方提供的一個對數據庫進行邏輯導出的備份工具,導出文件為BSON二進制格式,無法使用文本編輯工具直接查看。mongodump可以導出mongod或者mongos實例的數據,從集群模式來看,可以備份單實例、副本集、分片集集群。

mongodump作為MongoDB官方工具集中的一部分,從版本4.4開始,文檔說明統一到工具分類中:database-tools。本文是基於MongoDB 4.2 社區版本,具體環境如下:

OS:CentOS Linux release 7.6.1810 (Core)

DB version:v4.2.2

因為安裝的二進製版本,所以mongodump可執行文件通過解壓壓縮包就可以得到。

主要選項

mongodump支持的選項不算太多,可以通過--help選項獲得:

mongodump --help
Usage:
  mongodump <options>

Export the content of a running server into .bson files.

選項分為幾個大類:

  • general options:通用選項
  • connection options:連接選項
  • ssl options:安全連接選項
  • authentication options:驗證選項
  • kerberos options:基於kerboeros驗證選項
  • namespace options:命名空間選項
  • uri options:mongodb uri連接串選項
  • query options:查詢選項
  • output options:輸出選項
  • verbosity options:显示選項

general options(通用選項)

--help      # 打印工具使用方式,選項說明。
--version   # 打印工具版本並退出。

connection options(連接選項)

-h, --host=<hostname>   # 指定連接的實例主機名或者IP地址。                                 
    --port=<port>       # 指定連接的實例端口號。                                 

連接選項也可以分為三種實例模式:單實例、副本集、分片集。

  • Standalone(單實例)

只指定選項--host

mongodump --host="192.168.196.128:27017"

同時指定選項--host--port

mongodump --host="192.168.196.128" --port=27017
  • Replica Set(副本集)

指定選項--host

mongodump --host="dbabdSet/192.168.196.128:27017,192.168.196.128:27018,192.168.196.128:27019"

默認情況下,mongodump讀取Primary節點的數據,如果想讀取Secondary節點的數據,則需要使用選項readPreference

mongodump --host="dbabdSet/192.168.196.128:27017,192.168.196.128:27018,192.168.196.128:27019" --readPreference=secondary
  • Sharded Cluster(分片集群)

Replica Set(副本集)相同的指定方式。

注意:

–host選項默認值為:localhost:27017

–port選項默認值為:27017

執行副本集的導出備份時通常單獨連接某個Secondary節點進行導出備份:

mongodump --host="192.168.196.128:27018"

ssl options(安全連接選項)

--ssl                                                
--sslCAFile=<filename>                               
--sslPEMKeyFile=<filename>                            
--sslPEMKeyPassword=<password>                        
--sslCRLFile=<filename>                               
--sslAllowInvalidCertificates          
--sslAllowInvalidHostnames
--sslFIPSMode

關於ssl安全加密協議連接,本文不過多的贅述,詳情可以參考官方文檔:https://docs.mongodb.com/manual/reference/program/mongodump/index.html#cmdoption-mongodump-ssl

authentication options(驗證選項)

主要用於驗證連接實例的用戶的合法性。

-u, --username=<username>                   # 指定連接用戶
-p, --password=<password>                   # 指定連接用戶密碼
--authenticationDatabase=<database-name>    # 指定連接用戶驗證數據庫
--authenticationMechanism=<mechanism>       # 指定連接驗證機制

注意:

如果需要在導出時显示指示輸入密碼,而不是直接寫在選項中,則在指定--username選項的同時,不指定--password或者為--password選項指定一個空值,如:–password “”

如果沒有驗證數據庫,則mongodump假設指定導出的數據庫中包含用戶的驗證信息,如果沒有驗證數據庫並且也沒有指定導出數據庫,則mongodump假設admin數據庫包含用戶的驗證信息。

kerberos options(kerboeros驗證選項)

該選項主要是基於kerberos驗證機制的連接驗證選項,kerberos只有MongoDB企業版才支持,本文基於社區版本,有關kerberos可以參考官方文檔:https://docs.mongodb.com/manual/core/kerberos/

namespace options(命名空間選項)

主要是指定需要邏輯備份的數據庫和集合。

-d, --db=<database-name>             # 指定數據庫
-c, --collection=<collection-name>   # 指定集合                     

注意:

如果沒有指定導出數據庫,則mongodump導出實例中所有的數據庫,對於集合選項做相同的處理。

uri options(uri連接串選項)

從版本3.4.6開始新增加一種連接MongoDB實例的字符串格式,即URI。

--uri=mongodb-uri  # 指定uri連接字符串
  • Standalone(單實例)
--uri="mongodb://192.168.196.128:27017"
# 開始訪問控制驗證
--uri="mongodb://dbabd:admin@192.168.196.128:27017/?authSource=admin"
  • Replica Set(副本集)
--uri="mongodb://192.168.196.128:27017,192.168.196.128:27018,192.168.196.128:27019/?replicaSet=dbabdSet"
# 開始訪問控制驗證
--uri="mongodb://dbabd:admin@192.168.196.128:27017,192.168.196.128:27018,192.168.196.128:27019/?authSource=admin&replicaSet=dbabdSet"
  • Sharded Cluster(分片集群)

Replica Set(副本集)相同的指定方式。

當指定--uri連接串選項時,會與之前有關連接的選項互斥,這些選項包括:

  • –host
  • –port
  • –db
  • –username
  • –password(如果–uri連接串有指定連接密碼的話)
  • –authenticationDatabase
  • –authenticationMechanism

因為–uri連接串中已經包含了以上選項所涉及的部分。

query options(查詢選項)

指定mongodump導出的條件,可以參考db.collections.find()方法的查詢表達式。

-q, --query=<json>       # 指定符合JSON v2拓展格式的查詢語句 如:'{"x":{"$gt":1}}'
    --queryFile=<json>   # 指定包含JSON v2拓民格式查詢語句的文件
--readPreference=<string>|<json>  # 指定優先讀取順序   如:'nearest' 或 '{mode: "nearest", tagSets: [{a: "b"}],maxStalenessSeconds: 123}')
--forceTableScan         # 指定導出時遍歷集合使用的索引

注意:

如果指定--query選項,則必須同時指定--collection選項。

mongodump對於副本集默認優先讀取primary節點,如果需要從secondary,則指定選項–readPreference=secondary

mongodump導出時遍歷集合時默認使用索引_id,如果要使用其他索引,則指定選項--forceTableScan,該選項沒辦法確保mongodump導出基於某個時間點的快照,如果需要創建某一時間點的快照,則使用選項--oplog,該選項不能與選項--query一起使用。

output options(輸出選項)

指定mongodump導出時保存BSON文件的目錄,默認情況下,導出文件保存在執行mongodump當下目錄中的dump目錄里。

-o, --out=<directory-path>  # 指定導出文件保存目錄
--gzip                      # 使用gzip對導出文件進行壓縮
--oplog                     # 指定保存mongodump導出期間的oplog日誌,文件名為oplog.bson
--archive=<file-path>       # 指定導出文件合併歸檔的目的地
--dumpDbUsersAndRoles       # 指定導出數據庫的用戶和角色定義
--excludeCollection=<collection-name>  # 指定導出時排除某個集合,如有多個,需要指定多次
--excludeCollectionsWithPrefix=<collection-prefix>  # 指定導出時排除多個相同命名前綴的集合
-j, --numParallelCollections=  # 指定導出時可以并行的集合數,默認值為4
--viewsAsCollections # 指定導出時將只讀視圖當成集合保存成BSON文件

注意:

當指定選項--oplog時,mongodump在導出過程中同時會保存這一時間點產生的oplog,並保存為oplog.bson文件,使導出的備份是實例基於某個時間點的快照,如果使用mongorestore還原進行oplog回放時,需要指定選項--oplogReplay。如果沒有指定選項--oplog,則無法保證當前的導出在這一時刻的一致性,在導出過程中有對數據庫進行作何的更新操作都會影響導出的文件變化。

如果mongodump指定選項--oplog導出時客戶端執行以下命令會導致導出失敗:

  • renameCollection
  • db.collection.renameCollection()
  • db.collection.aggregate()並且執行操作符$out

當mongodump連接mongos實例進行整個分片集群的導出備份時,指定選項--oplog是沒有生效的,需要對各個分片節點集群單獨導出並指定--oplog

選項--oplog只有在副本集所有成員都開啟oplog功能才會生效。

選項--oplog並不會民出保存oplog的集合。

當mongodump指定選項--oplog導出時,必須包括導出實例所有的數據庫和集合,即不能指定選項--db只導出某個庫或選項--collection只導出某個集合。

verbosity options(显示選項)

指定導出時log輸出的显示的詳細級別。

-v, --verbose=<level>  # 指定日誌輸出詳細級別,如:-vvvvv 或 指定數值
--quiet                # 指定不輸出作何日誌信息

使用示例

備份導出數據庫

mongodump -h"192.168.196.128:27017" -uroot --authenticationDatabase admin -d dbabd -o /data/mongodump/

備份導出數據庫某個集合

mongodump -h"192.168.196.128:27017" -uroot --authenticationDatabase admin -d dbabd -
c orders -o /data/mongodump/

備份導出排除某個集合

# 如果有多個排除集合,則多次指定選項--excludeCollection
mongodump -h"192.168.196.128:27017" -uroot --authenticationDatabase admin -d dbabd -
-excludeCollection=orders -o /data/mongodump/

備份導出文件歸檔到文件

mongodump -h"192.168.196.128:27017" -uroot --authenticationDatabase admin -d dbabd --archive=/data/mongodump/dbabd.archive

備份導出文件進行壓縮

mongodump -h"192.168.196.128:27017" -uroot --authenticationDatabase admin -d dbabd -
-gzip -o /data/mongodump/

備份導出文件進行壓縮並歸檔

mongodump -h"192.168.196.128:27017" -uroot --authenticationDatabase admin -d dbabd --gzip --archive=/data/mongodump/dbabd.archive

指定條件的備份導出

mongodump -h"192.168.196.128:27017" -uroot --authenticationDatabase admin -d dbabd -c orders -q='{"price":{"$in":[25,50]}}' -o /data/mongodump/

注意

mongodump不會導出有關於local數據庫的內容;

mongodump不會導出文檔的索引,而只會導出文檔的數據,在導出文件還原之後需要重建索引;

對於開啟了訪問控制機制的實例,mongodump要執行導出操作的話必須對每個需要導出的數據庫具有find權限,內建角色backup提供了備份所有數據庫的權限,所以可以對用戶直接授予backup角色。

總結

對於MongoDB實例的邏輯備份工具,mongodump是個不二選擇,官方出品,安全穩定性有保障;

mongodump較適合數據量較少的備份,相對於數據量較大的情況,備份效率不是太高;

mongodump導出時比較消耗服務器性能,而且不支持同時導出多個庫,對於導出庫數據的最終一致性選項--oplog也只能是全庫導出才支持。

參考

https://docs.mongodb.com/database-tools/mongodump

https://docs.mongodb.com/manual/reference/program/mongodump/index.html#cmdoption-mongodump-ssl2

https://docs.mongodb.com/manual/core/kerberos/

〖本人水平有限,文中如有錯誤還請留言批評指正!〗

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

※教你寫出一流的銷售文案?

※超省錢租車方案

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※產品缺大量曝光嗎?你需要的是一流包裝設計!

分類
發燒車訊

求求你,別問了,Java字符串是不可變的

最近,又有好幾個小夥伴問我這個問題:“二哥,為什麼 Java 的 String 要設計成不可變的啊?”說實話,這也是一道非常經典的面試題,面試官超喜歡問。我之前寫過這方面的文章,現在讀起來似乎不太滿意,所以我決定再啰嗦最後一次,交出一份更滿意的答卷,讓小夥伴們在面試官面前更從容一些,更有底氣一些。

關於不可變對象,還有這樣一個小故事。Java 之父詹姆斯高司令曾在一次採訪中被問及這樣一個問題:“高司令,應該什麼時候使用不可變對象啊?”你猜高司令怎麼回答?

如有可能,我願意任何時候都使用不可變對象。

這就是高司令的答案,那有的小夥伴可能不服,老人家會說中文,你瞎扯吧你。也對哈,那就上英文唄:

I would use an immutable whenever I can.

這下徹底被打服了吧?老人家還說,不可變有着非常強大的功能,比如說,緩存、安全性、高性能等等。

01、什麼是不可變對象

不可變對象在創建后,它的內部狀態會保持不變,這就意味着,一旦我們將一個對象分配給一個變量,就無法再通過任何方式更改對象的狀態了。

關於不可變對象的更多信息,可以查看我之前寫的另外一篇文章——這次要說不明白immutable類,我就怎麼地,看完啥都明白了。你看,寫系列文章的好處就是這樣,不需要重複造輪子,用到的時候直接搬出來套上就行了。

02、為什麼 String 是不可變的

重點來了啊,為什麼 String 是不可變的?原因可以從四個方面說起,緩存、安全性、同步和高性能。

1)字符串常量池

字符串恐怕是 Java 中最常用的數據形式了,如果字符串非要謙虛地說自己是老二,就沒有人敢說自己是老大。

因此,把字符串緩存起來,並且重複使用它們會節省大量堆空間(堆內存用來存儲 Java 中的對象,無論是成員變量、局部變量,還是類變量,它們指向的對象都存儲在堆內存中),因為不同的字符串變量引用的是字符串常量池中的同一個對象。這也正是字符串常量池存在的目的。

字符串常量池是 Java 虛擬機用來存儲字符串的一個特殊的區域,由於字符串是不可變的,因此 Java 虛擬機可以在字符串常量池中只為同一個字符串存儲一個字符串副本來節省空間。

字符串常量池的主要使用方法有兩種:

  • 直接使用雙引號聲明出來的字符串對象會直接存儲在常量池中。
  • 否則,可以使用 String 類提供的 intern() 方法強制將當前字符串放入常量池中——常量池中查詢不到當前字符串。

來看下面這段代碼:

String s1 = "沉默王二";
String s2 = "沉默王二";

System.out.println(s1 == s2); // true

由於字符串常量池的存在,所以兩個不同的變量都指向了池中同一個字符串對象,從而節省了稀缺的內存資源。如果是通過 new 關鍵字創建的對象,則需要新的堆空間。

放心,關於字符串常量池,後面有時間的話,我再單獨寫一篇文章詳細地說一說。

2)安全性

字符串在 Java 應用程序中的使用範圍非常廣,幾乎無處不在,比如說存儲用戶名、密碼、數據庫連接地址等等這些非常敏感的信息,因此,必須要保證 String 類的絕對安全性。

來考慮一下下面這段代碼:

void criticalMethod(String userName) {
    // 檢查用戶名是否合法
    if (!isAlphaNumeric(userName)) {
        throw new SecurityException(); 
    }

    // 初始化數據庫連接
    initializeDatabase();

    // 準備修改用戶狀態
    connection.executeUpdate("UPDATE members SET status = 'active' " +
      " WHERE username = '" + userName + "'");
}

通常情況下,用戶名由客戶端傳遞到服務器端,服務器端接收后要先對用戶名進行檢查,再進行其他操作,因為客戶端傳遞過來的信息不一定值得信任。

如果字符串是可變的,那麼我們在執行 executeUpdate 更新數據庫的時候,就有點不放心,因為即便是安全性檢查通過了,字符串仍然有可能被修改。

在調用 isAlphaNumeric() 方法進行安全性檢查期間,userName 的值仍然有可能被 criticalMethod() 方法的調用者進行篡改,就容易造成 SQL 注入。

但如果字符串是不可變的,這方面的擔憂就不存在了。因為在執行更新之前,字符串的值是確定的,就是我們檢查安全性之後的值。

3)線程安全

由於字符串是不可變的,因此可以在多線程之間共享,如果一個線程把字符串的值修改為另外一個,那麼就會在字符串常量池中創建另外一個字符串,原有的字符串仍然會保持不變。

不過,很遺憾,我還不知道怎麼從代碼層面上去證明這一點,只能純理論 yy 一下。小夥伴誰有辦法的,教教我,在線等的那種。

4)哈希碼

字符串廣泛應用於 HashMap、HashTable、HashSet 等需要哈希碼作為鍵的數據結構中,在對這些哈希表進行操作的時候,需要頻繁調用 hashCode() 方法來獲取鍵的哈希碼。

public V put(K key, V value) {
    return putVal(hash(key), key, value, falsetrue);
}
static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

由於字符串是不可變性,這就保證了鍵值的哈希值不會發生改變,因此在第一次調用 String 類的 hashCode() 方法時,就對哈希值進行了緩存,此後,就一直返回相同的值。

/** Cache the hash code for the string */
private int hash; // Default to 0

public int hashCode() {
    int h = hash;
    if (h == 0 && !hashIsZero) {
        h = isLatin1() ? StringLatin1.hashCode(value)
                : StringUTF16.hashCode(value);
        if (h == 0) {
            hashIsZero = true;
        } else {
            hash = h;
        }
    }
    return h;
}

由於哈希值被緩存了,這在另外一種層面上提高了哈希表的訪問性能,因為哈希值不用重新計算了。

假如字符串是可變的,那就意味着哈希碼會有多個,在通過鍵獲取值的時候,就不一定能夠獲取到對的值了。

你看,字符串常量池的存在,哈希碼的存在,在很大程度上提高了程序的性能。

03、總結

好了,我親愛的小夥伴們,以上就是本文的全部內容了。我相信你一定對字符串的不可變性有了充足的了解,由於字符串是不可變的,因此我們可以將它看作是一個特殊的基本數據類型,哪怕是在多線程的環境下,也不用擔心它的值是否會發生改變。

如果覺得文章對你有點幫助,請微信搜索「 沉默王二 」第一時間閱讀。

本文已收錄 GitHub,傳送門~ ,裏面更有大廠面試完整考點,歡迎 Star。

我是沉默王二,一枚有顏值卻靠才華苟且的程序員。關注即可提升學習效率,別忘了三連啊,點贊、收藏、留言,我不挑,嘻嘻

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※別再煩惱如何寫文案,掌握八大原則!

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案

※教你寫出一流的銷售文案?

網頁設計最專業,超強功能平台可客製化

※產品缺大量曝光嗎?你需要的是一流包裝設計!

分類
發燒車訊

Jmeter基礎004—-增加參數化

一、參數化概述

1、參數化概念

      參數化就是動態的獲取並設置數據,當執行批量操作時,如批量插入或批量刪除,之前每執行完一次就需要修改一次,效率太低,參數化可以代替人工獲取並設置數據,安全且高效!

2、Jmeter參數化組件

  • CSV Data Set Config—-CSV數據設置組件
  • 用戶參數
  • 用戶定義的變量
  • 函數

二、參數化實現之CSV Data Set Config

1、概述

     CSV Data Set Config—-CSV數據設置組件,是參數化的實現組件之一,通過這個組件可以動態獲取並設置數據,實現批量操作,如:批量添加操作(執行一次,將多條數據插入到數據庫)。

2、實例1:參數化登錄賬號

      我們錄製的腳本,內容都是固定的,比如手機號、驗證碼都是我們再錄製過程中輸入的,如果我們希望模擬不同用戶登陸,那麼我們並不需要錄製很多個腳本,而只要將腳本中的用戶名、密碼變成變量,而線程執行時,不同線程取得不同的變量值即可。
     下面我們就舉例說明如何參數化登陸賬號。

(1)測試登錄接口:如下圖,新建登陸的HTTP請求並運行,確保登錄接口運行正常。

 

(2)創建一個文本文檔,標準的CSV格式文件,如下圖包括3條數據,每一行數據對應一條登錄信息,不同字段之間使用英文逗號分隔。

(3)線程組右鍵—添加—配置元件—CSV Data Set Config—-CSV,創建一個CSV組件,並聲明數據源、編碼集以及解析格式,如下圖所示:

(4)設置線程組的線程數為3(因為文件中有三條登錄數據),並修改HTTP請求中的參數值,調用CSV數據文件設置中定義的變量,調用格式${變量名},如下圖所示:

 

 (5)運行測試計劃,查看結果樹的運行結果,如下圖所示:

 

 

 3、實例2:批量添加

 實現思想:

 

 

 實現步驟:

(1)創建CSV 數據文件設置,如下圖所示:

 (2)創建HTTP請求,並在請求中調用CSV中定義的變量

 (3)編輯文本文檔,存儲要添加的三條數據

 (4)設置線程組循環次數為3,並運行測試計劃,查看察看結果樹显示。

 

三、參數化之用戶參數

1、用戶參數與CSV參數化的區別

    用戶參數和CSV都是將數據設置進第三方,然後循環讀取數據,區別在於:CSV是將數據設置進外部的文本文檔,而用戶參數是將數據設置進Jmeter內置組件。

2、實現流程  

(1)搭建框架:創建測試計劃、線程組、HTTP請求(請求的JSON數據先不設置)。注意:執行次數是3次(不是設置循環次數,而是設置線程數)

(2)創建Jmeter內置組件存儲要插入的數據:測試計劃右鍵—-添加—-前置處理器—-用戶參數,在用戶參數組件界面添加4個變量、3個用戶,如下圖所示:

 注意:因為此處添加的是用戶,每個用戶對應一個線程 ,添加幾個用戶就應該設置幾個線程,所以這裏設置的是線程組而不是循環次數。

 

(3)將用戶參數組件中的變量名稱設置進HTTP請求的Json數據格式中,調用格式:${變量名},如下圖所示:

 (4)運行測試計劃,查看察看結果樹。

 

四、參數化之用戶定義的變量

1、需求

     當系統執行增刪改查操作時,資源路徑不一定相同,但存在部分相同,如:都是/api/departments/開頭,為了提高編寫路徑的效率,可以將公共路徑定義成變量,然後再在路徑中使用${變量名}調用變量。注:一般定義、存儲全局使用的變量。

2、實現過程

(1)將公共的路徑數據提取出來使用一個組件存儲,如:/api/departments/。測試計劃右鍵—-添加—-配置元件—-用戶定義的變量,創建用戶定義的變量組件,添加自定義變量,如下圖所示:

(2)分別創建HTTP請求,在路徑中公共部分調用定義的路徑變量,非公共部分路徑與原來一致,如下圖所示:

 (3)運行測試計劃,查看結果樹。

 

五、參數化之函數

1、需求

     函數是程序中最基本的封裝單元,封裝了一些常用的功能,比如計數器。在實際應用中當我們需要循環10次查詢信息時,結果數的請求名稱都是一樣的,我們可以使用計數函數添加標號以示區分。

2、實現流程

(1)打開Jmeter內置的函數組件,一共有三種方式:

  •  選項+函數助手對話框
  •  ctrl+shift+F1
  • 工具欄倒數第二個圖標

(2)選擇要使用的函數,給函數傳參,並用Jmeter生成調用格式,如下圖:

 

注:__counter函數的參數:true,每一個用戶單獨一個計數器;false,所有用戶共用一個計數器.

(3)在需要調用函數的位置使用Jmeter生成的調用格式:${_函數名(參數)}

 

 

 (4)運行測試計劃,查看結果樹,如下圖所示:

六、總結

1、參數化—-CSV Data Set Config

     概念:動態獲取並設置數據,操作數據高效安全(程序代替人工)

     實現思想+具體流程:

2、參數化—-用戶參數

實現思想:將數據單獨存儲,然後再將數據讀取到http請求的JSON 數據中

實現流程:

  • 設置執行次數(用戶數)
  • 添加組件用戶參數存儲多條記錄
  • 讀取數據格式: ${變量名 )

3、參數化—-用戶定義的變量

作用:存儲全局性數據 

添加格式:添加用戶定義的變量組件—–鍵和值

調用格式:${變量名}

4、參數化—-函數

概念:程序中的功能單元,封裝了部分實現 

實現: 

  • 打開函數功能模塊
  • 選擇要調用的函數+設置參數+生成調用格式
  • 在需要使用的位置調用即可

5、四種參數化方案比較

  •   CSV和用戶參數使用思想一致,流程上後者更簡單,但是實際應用中,使用CSV居多,因為數據量大時,CSV更方便
  •   用戶定義的變量一般用來存儲全局變量,但是使用場景較少
  •   函數實現更為靈活且內置了好多實現。

總結:最常用的是參數化方法是:CSV+函數

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※教你寫出一流的銷售文案?

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!

分類
發燒車訊

Vue結合路由配置遞歸實現菜單欄

作者:小土豆biubiubiu

博客園:https://www.cnblogs.com/HouJiao/

掘金:https://juejin.im/user/58c61b4361ff4b005d9e894d

微信公眾號:土豆媽的碎碎念(掃碼關注,一起吸貓,一起聽故事,一起學習前端技術)

作者文章的內容均來源於自己的實踐,如果覺得有幫助到你的話,可以點贊給個鼓勵或留下寶貴意見

前言

在日常開發中,項目中的菜單欄都是已經實現好了的。如果需要添加新的菜單,只需要在路由配置中新增一條路由,就可以實現菜單的添加。

相信大家和我一樣,有時候會躍躍欲試自己去實現一個菜單欄。那今天我就將自己實現的菜單欄的整個思路和代碼分享給大家。

本篇文章重在總結和分享菜單欄的一個遞歸實現方式代碼的優化菜單權限等不在本篇文章範圍之內,在文中的相關部分也會做一些提示,有個別不推薦的寫法希望大家不要參考哦。

同時可能會存在一些細節的功能沒有處理或者沒有提及到,忘知曉。

最終的效果

本次實現的這個菜單欄包含有一級菜單二級菜單三級菜單這三種類型,基本上已經可以覆蓋項目中不同的菜單需求。

後面會一步一步從易到難去實現這個菜單。

簡單實現

我們都知道到element提供了 NavMenu 導航菜單組件,因此我們直接按照文檔將這個菜單欄做一個簡單的實現。

基本的布局架構圖如下:

菜單首頁-menuIndex

首先要實現的是菜單首頁這個組件,根據前面的布局架構圖並且參考官方文檔,實現起來非常簡單。

<!-- src/menu/menuIndex.vue -->
<template>
    <div id="menu-index">
        <el-container>
            <el-header>
                <TopMenu :logoPath="logoPath" :name="name"></TopMenu>
            </el-header>
            <el-container id="left-container">
                <el-aside width="200px">
                    <LeftMenu></LeftMenu>                    
                </el-aside>
                <el-main>
                    <router-view/>
                </el-main>
            </el-container>
        </el-container>
    </div>
</template>
<script>
import LeftMenu from './leftMenu';
import TopMenu from './topMenu';
export default {
    name: 'MenuIndex',
    components: {LeftMenu, TopMenu},
    data() {
        return {
            logoPath:  require("../../assets/images/logo1.png"),
            name: '員工管理系統'
        }
    }
}
</script>
<style lang="scss">
    #menu-index{
        .el-header{
            padding: 0px;
        }
    }
</style>

頂部菜單欄-topMenu

頂部菜單欄主要就是一個logo產品名稱

邏輯代碼也很簡單,我直接將代碼貼上。

<!-- src/menu/leftMenu.vue -->
<template>
    <div id="top-menu">
        <img class="logo" :src="logoPath" />
        <p class="name">{{name}}</p>
    </div>
</template>
<script>
export default {
    name: 'topMenu',
    props: ['logoPath', 'name']
}
</script>
<style lang="scss" scoped>
    $topMenuWidth: 80px;
    $logoWidth: 50px;
    $bg-color: #409EFF;
    $name-color: #fff;
    $name-size: 18px;
    #top-menu{
        height: $topMenuWidth;
        text-align: left;
        background-color: $bg-color;
        padding: 20px 20px 0px 20px;
        .logo {
            width: $logoWidth;
            display: inline-block;
        }
        .name{
            display: inline-block;
            vertical-align: bottom;
            color: $name-color;
            font-size: $name-size;
        }
    }
</style>

這段代碼中包含了父組件傳遞給子組件的兩個數據。

props: ['logoPath', 'name']

這個是父組件menuIndex傳遞給子組件topMenu的兩個數據,分別是logo圖標的路徑產品名稱

完成后的界面效果如下。

左側菜單欄-leftMenu

首先按照官方文檔實現一個簡單的菜單欄。

<!-- src/menu/leftMenu.vue -->
<template>
    <div id="left-menu">
        <el-menu 
            :default-active="$route.path" 
            class="el-menu-vertical-demo" 
            :collapse="false">
            <el-menu-item index="1">
                <i class="el-icon-s-home"></i>
                <span slot="title">首頁</span>
            </el-menu-item>
            <el-submenu index="2">
                <template slot="title">
                    <i class="el-icon-user-solid"></i>
                    <span slot="title">員工管理</span>
                </template>
                <el-menu-item index="2-1">員工統計</el-menu-item>
                <el-menu-item index="2-2">員工管理</el-menu-item>
            </el-submenu>
            <el-submenu index="3">
                <template slot="title">
                    <i class="el-icon-s-claim"></i>
                    <span slot="title">考勤管理</span>
                </template>
                <el-menu-item index="3-1">考勤統計</el-menu-item>
                <el-menu-item index="3-2">考勤列表</el-menu-item>
                <el-menu-item index="3-2">異常管理</el-menu-item>
            </el-submenu>
            <el-submenu index="4">
                <template slot="title">
                    <i class="el-icon-location"></i>
                    <span slot="title">工時管理</span>
                </template>
                <el-menu-item index="4-1">工時統計</el-menu-item>
                <el-submenu index="4-2">
                    <template slot="title">工時列表</template>
                    <el-menu-item index="4-2-1">選項一</el-menu-item>
                    <el-menu-item index="4-2-2">選項二</el-menu-item>
                </el-submenu>
            </el-submenu>
        </el-menu>
    </div>
</template>
<script>
export default {
    name: 'LeftMenu'
}
</script>
<style lang="scss">
    // 使左邊的菜單外層的元素高度充滿屏幕
    #left-container{
        position: absolute;
        top: 100px;
        bottom: 0px;
        // 使菜單高度充滿屏幕
        #left-menu, .el-menu-vertical-demo{
            height: 100%;
        }
    }
</style>

注意菜單的樣式代碼,設置了絕對定位,並且設置topbottom使菜單高度撐滿屏幕。

此時在看下界面效果。

基本上算是實現了一個簡單的菜單布局。

不過在實際項目在設計的時候,菜單欄的內容有可能來自後端給我們返回的數據,其中包含菜單名稱菜單圖標以及菜單之間的層級關係

總而言之,我們的菜單是動態生成的,而不是像前面那種固定的寫法。因此下面我將實現一個動態生成的菜單,菜單的數據來源於我們的路由配置

結合路由配置實現動態菜單

路由配置

首先,我將項目的路由配置代碼貼出來。

import Vue from 'vue';
import Router from "vue-router";

// 菜單
import MenuIndex from '@/components/menu/menuIndex.vue';

// 首頁
import Index from '@/components/homePage/index.vue';

// 人員統計
import EmployeeStatistics from '@/components/employeeManage/employeeStatistics.vue';
import EmployeeManage from '@/components/employeeManage/employeeManage.vue'

// 考勤
// 考勤統計
import AttendStatistics from '@/components/attendManage/attendStatistics';
// 考勤列表
import AttendList from '@/components/attendManage/attendList.vue';
// 異常管理
import ExceptManage from '@/components/attendManage/exceptManage.vue';

// 工時
// 工時統計
import TimeStatistics from '@/components/timeManage/timeStatistics.vue';
// 工時列表
import TimeList from '@/components/timeManage/timeList.vue';
Vue.use(Router)


let routes = [
    // 首頁(儀錶盤、快速入口)
    {
        path: '/index',
        name: 'index',
        component: MenuIndex,
        redirect: '/index',  
        meta: {
            title: '首頁',    // 菜單標題
            icon: 'el-icon-s-home',  // 圖標
            hasSubMenu: false, // 是否包含子菜單,false 沒有子菜單;true 有子菜單

        },
        children:[
            {
                path: '/index',
                component: Index
            }
        ]
    },
    // 員工管理
    {
        path: '/employee',
        name: 'employee',
        component: MenuIndex,
        redirect: '/employee/employeeStatistics', 
        meta: {
            title: '員工管理',    // 菜單標題
            icon: 'el-icon-user-solid',  // 圖標
            hasSubMenu: true,   // 是否包含子菜單
        },
        children: [
            // 員工統計
            {
                path: 'employeeStatistics',
                name: 'employeeStatistics',
                meta: {
                    title: '員工統計',    // 菜單標題,
                    hasSubMenu: false    // 是否包含子菜單
                },
                component: EmployeeStatistics,
            },
            // 員工管理(增刪改查)
            {
                path: 'employeeManage',
                name: 'employeeManage',
                meta: {
                    title: '員工管理',    // 菜單標題
                    hasSubMenu: false    // 是否包含子菜單
                },
                component: EmployeeManage
            }
        ]
    },
    // 考勤管理
    {
        path: '/attendManage',
        name: 'attendManage',
        component: MenuIndex,
        redirect: '/attendManage/attendStatistics',
        meta: {
            title: '考勤管理',    // 菜單標題
            icon: 'el-icon-s-claim',  // 圖標
            hasSubMenu: true, // 是否包含子節點,false 沒有子菜單;true 有子菜單
        },
        children:[
            // 考勤統計
            {
                path: 'attendStatistics',
                name: 'attendStatistics',
                meta: {
                    title: '考勤統計',    // 菜單標題   
                    hasSubMenu: false    // 是否包含子菜單               
                },
                component: AttendStatistics,
            },
            // 考勤列表
            {
                path: 'attendList',
                name: 'attendList',
                meta: {
                    title: '考勤列表',    // 菜單標題   
                    hasSubMenu: false    // 是否包含子菜單                 
                },
                component: AttendList,
            },
            // 異常管理
            {
                path: 'exceptManage',
                name: 'exceptManage',
                meta: {
                    title: '異常管理',    // 菜單標題  
                    hasSubMenu: false    // 是否包含子菜單                  
                },
                component: ExceptManage,
            }
        ]
    },
    // 工時管理
    {
        path: '/timeManage',
        name: 'timeManage',
        component: MenuIndex,
        redirect: '/timeManage/timeStatistics',
        meta: {
            title: '工時管理',    // 菜單標題
            icon: 'el-icon-message-solid',  // 圖標
            hasSubMenu: true, // 是否包含子菜單,false 沒有子菜單;true 有子菜單
        },
        children: [
            // 工時統計
            {
                path: 'timeStatistics',
                name: 'timeStatistics',
                meta: {
                    title: '工時統計',    // 菜單標題
                    hasSubMenu: false    // 是否包含子菜單        
                },
                component: TimeStatistics
            },
            // 工時列表
            {
                path: 'timeList',
                name: 'timeList',
                component: TimeList,
                meta: {
                    title: '工時列表',    // 菜單標題
                    hasSubMenu: true    // 是否包含子菜單        
                },
                children: [
                    {
                        path: 'options1',
                        meta: {
                            title: '選項一',    // 菜單標題
                            hasSubMenu: false    // 是否包含子菜單        
                        },
                    },
                    {
                        path: 'options2',
                        meta: {
                            title: '選項二',    // 菜單標題
                            hasSubMenu: false    // 是否包含子菜單        
                        },
                    },
                ]
            }
        ]
    },
];
export default new Router({
    routes
})

在這段代碼的最開始部分,我們引入了需要使用的組件,接着就對路由進行了配置。

此處使用了直接引入組件的方式,項目開發中不推薦這種寫法,應該使用懶加載的方式

路由配置除了最基礎的pathcomponent以及children之外,還配置了一個meta數據項。

meta: {
    title: '工時管理',    // 菜單標題
    icon: 'el-icon-message-solid',  // 圖標
    hasSubMenu: true, // 是否包含子節點,false 沒有子菜單;true 有子菜單
}

meta數據包含的配置有菜單標題(title)、圖標的類名(icon)和是否包含子節點(hasSubMenu)。

根據titleicon這兩個配置項,可以展示當前菜單的標題圖標

hasSubMenu表示當前的菜單項是否有子菜單,如果當前菜單包含有子菜單(hasSubMenutrue),那當前菜單對應的標籤元素就是el-submenu;否則當前菜單對應的菜單標籤元素就是el-menu-item

是否包含子菜單是一個非常關鍵的邏輯,我在實現的時候是直接將其配置到了meta.hasSubMenu這個參數裏面。

根據路由實現多級菜單

路由配置完成后,我們就需要根據路由實現菜單了。

獲取路由配置

既然要根據路由配置實現多級菜單,那第一步就需要獲取我們的路由數據。這裏我使用簡單粗暴的方式去獲取路由配置數據:this.$router.options.routes

這種方式也不太適用日常的項目開發,因為無法在獲取的時候對路由做進一步的處理,比如權限控制

我們在組件加載時打印一下這個數據。

// 代碼位置:src/menu/leftMenu.vue
 mounted(){
    console.log(this.$router.options.routes);
}

打印結果如下。

可以看到這個數據就是我們在router.js中配置的路由數據。

為了方便使用,我將這個數據定義到計算屬性中。

// 代碼位置:src/menu/leftMenu.vue
computed: {
    routesInfo: function(){
        return this.$router.options.routes;
    }
}

一級菜單

首先我們來實現一級菜單

主要的邏輯就是循環路由數據routesInfo,在循環的時候判斷當前路由route是否包含子菜單,如果包含則當前菜單使用el-submenu實現,否則當前菜單使用el-menu-item實現。

<!-- src/menu/leftMenu.vue -->
<el-menu 
    :default-active="$route.path" 
    class="el-menu-vertical-demo" 
    :collapse="false">
    <!--  一級菜單 -->
    <!--  循環路由數據  -->
    <!--  判斷當前路由route是否包含子菜單  -->
    <el-submenu 
        v-for="route in routesInfo" 
        v-if="route.meta.hasSubMenu"
        :index="route.path">
        <template slot="title">
            <i :class="route.meta.icon"></i>
            <span slot="title">{{route.meta.title}}</span>
        </template>
    </el-submenu>
    <el-menu-item :index="route.path" v-else> 
        <i :class="route.meta.icon"></i>
        <span slot="title">{{route.meta.title}}</span>
    </el-menu-item>
</el-menu>

結果:

可以看到,我們第一級菜單已經生成了,員工管理考勤管理工時管理這三個菜單是有子菜單的,所以會有一個下拉按鈕。

不過目前點開是沒有任何內容的,接下來我們就來實現這三個菜單下的二級菜單

二級菜單

二級菜單的實現和一級菜單的邏輯是相同的:循環子路由route.children,在循環的時候判斷子路由childRoute是否包含子菜單,如果包含則當前菜單使用el-submenu實現,否則當前菜單使用el-menu-item實現。

那話不多說,直接上代碼。

<!-- src/menu/leftMenu.vue -->
<el-menu 
    :default-active="$route.path" 
    class="el-menu-vertical-demo" 
    :collapse="false">
    <!--  一級菜單 -->
    <!--  循環路由數據  -->
    <!--  判斷當前路由route是否包含子菜單  -->
    <el-submenu 
        v-for="route in routesInfo" 
        v-if="route.meta.hasSubMenu"
        :index="route.path">
        <template slot="title">
            <i :class="route.meta.icon"></i>
            <span slot="title">{{route.meta.title}}</span>
        </template>
        <!-- 二級菜單 -->
        <!-- 循環子路由`route.children` -->
        <!-- 循環的時候判斷子路由`childRoute`是否包含子菜單 -->
        <el-submenu 
            v-for="childRoute in route.children" 
            v-if="childRoute.meta.hasSubMenu"
            :index="childRoute.path">
            <template slot="title">
                <i :class="childRoute.meta.icon"></i>
                <span slot="title">{{childRoute.meta.title}}</span>
            </template>
        </el-submenu>
        <el-menu-item :index="childRoute.path" v-else> 
            <i :class="childRoute.meta.icon"></i>
            <span slot="title">{{childRoute.meta.title}}</span>
        </el-menu-item>
    </el-submenu>
    <el-menu-item :index="route.path" v-else> 
        <i :class="route.meta.icon"></i>
        <span slot="title">{{route.meta.title}}</span>
    </el-menu-item>
</el-menu>

結果如下:

可以看到二級菜單成功實現。

三級菜單

三級菜單就不用多說了,和一級二級邏輯相同,這裏還是直接上代碼。

<!-- src/menu/leftMenu.vue -->
<el-menu 
    :default-active="$route.path" 
    class="el-menu-vertical-demo" 
    :collapse="false">
    <!--  一級菜單 -->
    <!--  循環路由數據  -->
    <!--  判斷當前路由route是否包含子菜單  -->
    <el-submenu 
        v-for="route in routesInfo" 
        v-if="route.meta.hasSubMenu"
        :index="route.path">
        <template slot="title">
            <i :class="route.meta.icon"></i>
            <span slot="title">{{route.meta.title}}</span>
        </template>
        <!-- 二級菜單 -->
        <!-- 循環子路由`route.children` -->
        <!-- 循環的時候判斷子路由`childRoute`是否包含子菜單 -->
        <el-submenu 
            v-for="childRoute in route.children" 
            v-if="childRoute.meta.hasSubMenu"
            :index="childRoute.path">
            <template slot="title">
                <i :class="childRoute.meta.icon"></i>
                <span slot="title">{{childRoute.meta.title}}</span>
            </template>
            <!-- 三級菜單 -->
            <!-- 循環子路由`childRoute.children` -->
            <!-- 循環的時候判斷子路由`child`是否包含子菜單 -->
            <el-submenu 
                v-for="child in childRoute.children" 
                v-if="child.meta.hasSubMenu"
                :index="child.path">
                <template slot="title">
                    <i :class="child.meta.icon"></i>
                    <span slot="title">{{child.meta.title}}</span>
                </template>
            </el-submenu>
            <el-menu-item :index="child.path" v-else> 
                <i :class="child.meta.icon"></i>
                <span slot="title">{{child.meta.title}}</span>
            </el-menu-item>
        </el-submenu>
        <el-menu-item :index="childRoute.path" v-else> 
            <i :class="childRoute.meta.icon"></i>
            <span slot="title">{{childRoute.meta.title}}</span>
        </el-menu-item>
    </el-submenu>
    <el-menu-item :index="route.path" v-else> 
        <i :class="route.meta.icon"></i>
        <span slot="title">{{route.meta.title}}</span>
    </el-menu-item>
</el-menu>

可以看到工時列表下的三級菜單已經显示了。

總結

此時我們已經結合路由配置實現了這個動態的菜單。

不過這樣的代碼在邏輯上相關於三層嵌套for循環,對應的是我們有三層的菜單。

假如我們有四層五層甚至更多層的菜單時,那我們還得在嵌套更多層for循環。很顯然這樣的方式暴露了前面多層for循環的缺陷,所以我們就需要對這樣的寫法進行一個改進。

遞歸實現動態菜單

前面我們一直在說一級二級三級菜單的實現邏輯都是相同的:循環子路由,在循環的時候判斷子路由是否包含子菜單,如果包含則當前菜單使用el-submenu實現,否則當前菜單使用el-menu-item實現。那這樣的邏輯最適合的就是使用遞歸去實現。

所以我們需要將這部分共同的邏輯抽離出來作為一個獨立的組件,然後遞歸的調用這個組件。

邏輯拆分

<!-- src/menu/menuItem.vue -->
<template>
    <div>
        <el-submenu 
            v-for="child in route" 
            v-if="child.meta.hasSubMenu"
            :index="child.path">
            <template slot="title">
                <i :class="child.meta.icon"></i>
                <span slot="title">{{child.meta.title}}</span>
            </template>
        </el-submenu>
        <el-menu-item :index="child.path" v-else> 
            <i :class="child.meta.icon"></i>
            <span slot="title">{{child.meta.title}}</span>
        </el-menu-item>
    </div>
</template>
<script>
export default {
    name: 'MenuItem',
    props: ['route']
}
</script>

需要注意的是,這次抽離出來的組件循環的時候直接循環的是route數據,那這個route數據是什麼呢。

我們先看一下前面三層循環中循環的數據源分別是什麼。

為了看得更清楚,我將前面代碼中一些不相關的內容進行了刪減。

<!-- src/menu/leftMenu.vue -->

<!--  一級菜單 -->
<el-submenu 
    v-for="route in routesInfo" 
    v-if="route.meta.hasSubMenu">
    <!-- 二級菜單 -->
    
    <el-submenu 
        v-for="childRoute in route.children" 
        v-if="childRoute.meta.hasSubMenu">
        
        <!-- 三級菜單 -->
        <el-submenu 
            v-for="child in childRoute.children" 
            v-if="child.meta.hasSubMenu">
          
        </el-submenu>
    
    </el-submenu>
</el-submenu>

從上面的代碼可以看到:

一級菜單循環的是`routeInfo`,即最初我們獲取的路由數據`this.$router.options.routes`,循環出來的每一項定義為`route`

二級菜單循環的是`route.children`,循環出來的每一項定義為`childRoute`

三級菜單循環的是`childRoute.children`,循環出來的每一項定義為`child`

按照這樣的邏輯,可以發現二級菜單三級菜單循環的數據源都是相同的,即前一個循環結果項的children,而一級菜單的數據來源於this.$router.options.routes

前面我們抽離出來的menuItem組件,循環的是route數據,即不管是一層菜單還是二層三層菜單,都是同一個數據源,因此我們需要統一數據源。那當然也非常好實現,我們在調用組件的時候,為組件傳遞不同的值即可。

代碼實現

前面公共組件已經拆分出來了,後面的代碼就非常好實現了。

首先是抽離出來的meunItem組件,實現的是邏輯判斷以及遞歸調用自身

<!-- src/menu/menuItem.vue -->
<template>
    <div>
        <el-submenu 
            v-for="child in route" 
            v-if="child.meta.hasSubMenu"
            :index="child.path">
            <template slot="title">
                <i :class="child.meta.icon"></i>
                <span slot="title">{{child.meta.title}}</span>
            </template>
            <!--遞歸調用組件自身 -->
            <MenuItem :route="child.children"></MenuItem>
        </el-submenu>
        <el-menu-item :index="child.path" v-else> 
            <i :class="child.meta.icon"></i>
            <span slot="title">{{child.meta.title}}</span>
        </el-menu-item>
    </div>
</template>
<script>
export default {
    name: 'MenuItem',
    props: ['route']
}
</script>

接着是leftMenu組件,調用menuIndex組件,傳遞原始的路由數據routesInfo

<!-- src/menu/leftMenu.vue -->
<template>
    <div id="left-menu">
        <el-menu 
            :default-active="$route.path" 
            class="el-menu-vertical-demo"
            :collapse="false">
            <MenuItem :route="routesInfo"></MenuItem>
        </el-menu>
    </div>
</template>
<script>
import MenuItem from './menuItem'
export default {
    name: 'LeftMenu',
    components: { MenuItem }
}
</script>
<style lang="scss">
    // 使左邊的菜單外層的元素高度充滿屏幕
    #left-container{
        position: absolute;
        top: 100px;
        bottom: 0px;
        // 使菜單高度充滿屏幕
        #left-menu, .el-menu-vertical-demo{
            height: 100%;
        }
    }
</style>

最終的結果這裏就不展示了,和我們需要實現的結果是一致的。

功能完善

到此,我們結合路由配置實現了菜單欄這個功能基本上已經完成了,不過這是一個缺乏靈魂的菜單欄,因為沒有設置菜單的跳轉,我們點擊菜單欄還無法路由跳轉到對應的組件,所以接下來就來實現這個功能。

菜單跳轉的實現方式有兩種,第一種是NavMenu組件提供的跳轉方式。

第二種是在菜單上添加router-link實現跳轉。

那本次我選擇的是第一種方式實現跳轉,這種實現方式需要兩個步驟才能完成,第一步是啟用el-menu上的router;第二步是設置導航的index屬性。

那下面就來實現這兩個步驟。

啟用el-menu上的router

<!-- src/menu/leftMenu.vue -->
<!-- 省略其餘未修改代碼-->
<el-menu 
    :default-active="$route.path" 
    class="el-menu-vertical-demo"
    router
    :collapse="false">
    <MenuItem :route="routesInfo">
    </MenuItem>
</el-menu>

設置導航的index屬性

首先我將每一個菜單標題對應需要設置的index屬性值列出來。

index值對應的是每個菜單在路由中配置的path

首頁        

員工管理    
    員工統計  index="/employee/employeeStatistics"
    員工管理  index="/employee/employeeManage"

考勤管理  
    考勤統計  index="/attendManage/attendStatistics"
    考勤列表  index="/attendManage/attendList"
    異常管理  index="/attendManage/exceptManage"

員工統計  
    員工統計  index="/timeManage/timeStatistics"
    員工統計  index="/timeManage/timeList"
        選項一  index="/timeManage/timeList/options1"
        選項二  index="/timeManage/timeList/options2"

接着在回顧前面遞歸調用的組件,導航菜單的index設置的是child.path,為了看清楚child.path的值,我將其添加菜單標題的右側,讓其显示到界面上。

<!-- src/menu/menuItem.vue -->
<!-- 省略其餘未修改代碼-->
<el-submenu 
    v-for="child in route" 
    v-if="child.meta.hasSubMenu"
    :index="child.path">
    <template slot="title">
        <i :class="child.meta.icon"></i>
        <span slot="title">{{child.meta.title}} | {{child.path}}</span>
    </template>
    <!--遞歸調用組件自身 -->
    <MenuItem :route="child.children"></MenuItem>
</el-submenu>
<el-menu-item :index="child.path" v-else> 
    <i :class="child.meta.icon"></i>
    <span slot="title">{{child.meta.title}} | {{child.path}}</span>
</el-menu-item>

同時將菜單欄的寬度由200px設置為400px

<!-- src/menu/menuIndex.vue -->
<!-- 省略其餘未修改代碼-->
<el-aside width="400px">
    <LeftMenu></LeftMenu>                    
</el-aside>

然後我們看一下效果。

可以發現,child.path的值就是當前菜單在路由中配置path值(router.js中配置的path值)。

那麼問題就來了,前面我們整理了每一個菜單標題對應需要設置的index屬性值,就目前來看,現在設置的index值是不符合要求的。不過仔細觀察現在菜單設置的index值和正常值是有一點接近的,只是缺少了上一級菜單的path值,如果能將上一級菜單path值和當前菜單的path值進行一個拼接,就能得到正確的index值了。

那這個思路實現的方式依然是在遞歸時將當前菜單的path作為參數傳遞給menuItem組件。

<!-- src/menu/menuIndex.vue -->
<!--遞歸調用組件自身 -->
<MenuItem 
    :route="child.children" 
    :basepath="child.path">
</MenuItem>

將當前菜單的path作為參數傳遞給menuItem組件之後,在下一級菜單實現時,就能拿到上一級菜單的path值。然後組件中將basepath的值和當前菜單的path值做一個拼接,作為當前菜單的index值。

<!-- src/menu/menuIndex.vue -->
<el-menu-item :index="getPath(child.path)" v-else> 

</el-menu-item>
<script>
import path from 'path'
export default {
    name: 'MenuItem',
    props: ['route','basepath'],
    data(){
        return {
           
        }
    },
    methods :{
        // routepath 為當前菜單的path值
        // getpath: 拼接 當前菜單的上一級菜單的path 和 當前菜單的path
        getPath: function(routePath){
            return path.resolve(this.basepath, routePath);
        }
    }
}
</script>

再看一下界面。

我們可以看到二級菜單的index值已經沒問題了,但是仔細看,發現工時管理工時列表下的兩個三級菜單index值還是有問題,缺少了工時管理這個一級菜單的path

那這個問題是因為我們在調用組件自身是傳遞的basepath有問題。

<!--遞歸調用組件自身 -->
<MenuItem 
    :route="child.children" 
    :basepath="child.path">
</MenuItem>

basepath傳遞的只是上一級菜單的path,在遞歸二級菜單時,index的值是一級菜單的path值+二級菜單的path值;那當我們遞歸三級菜單時,index的值就是二級菜單的path值+三級菜單的path值,這也就是為什麼工時管理-工時列表下的兩個三級菜單index值存在問題。

所以這裏的basepath值在遞歸的時候應該是累積的,而不只是上一級菜單的path值。因此藉助遞歸算法的優勢,basepath的值也需要通過getPath方法進行處理。

<MenuItem 
    :route="child.children" 
    :basepath="getPath(child.path)">
</MenuItem>

最終完整的代碼如下。

<!-- src/menu/menuIndex.vue -->
<template>
    <div>
        <el-submenu 
            v-for="child in route" 
            v-if="child.meta.hasSubMenu"
            :key="child.path"
            :index="getPath(child.path)">
            <template slot="title">
                <i :class="child.meta.icon"></i>
                    <span slot="title">
                        {{child.meta.title}}
                    </span>
            </template>
            <!--遞歸調用組件自身 -->
            <MenuItem 
                :route="child.children" 
                :basepath="getPath(child.path)">
            </MenuItem>
        </el-submenu>
        <el-menu-item :index="getPath(child.path)" v-else> 
            <i :class="child.meta.icon"></i>
            <span slot="title">
                   {{child.meta.title}}
            </span>
        </el-menu-item>
        
    </div>
</template>
<script>
import path from 'path'
export default {
    name: 'MenuItem',
    props: ['route','basepath'],
    data(){
        return {
           
        }
    },
    methods :{
        // routepath 為當前菜單的path值
        // getpath: 拼接 當前菜單的上一級菜單的path 和 當前菜單的path
        getPath: function(routePath){
            return path.resolve(this.basepath, routePath);
        }
    }
}
</script>

刪除其餘用來調試的代碼

最終效果

文章的最後呢,將本次實現的最終效果在此展示一下。

選項一選項二這兩個三級菜單在路由配置中沒有設置component,這兩個菜單隻是為了實現三級菜單,在最後的結果演示中,我已經刪除了路由中配置的這兩個三級菜單

此處在leftMenu組件中為el-menu開啟了unique-opened

menuIndex組件中,將左側菜單欄的寬度改為200px

關於

作者

小土豆biubiubiu

一個努力學習的前端小菜鳥,知識是無限的。堅信只要不停下學習的腳步,總能到達自己期望的地方

同時還是一個喜歡小貓咪的人,家裡有一隻美短小母貓,名叫土豆

博客園

https://www.cnblogs.com/HouJiao/

掘金

https://juejin.im/user/58c61b4361ff4b005d9e894d

微信公眾號

土豆媽的碎碎念

微信公眾號的初衷是記錄自己和身邊的一些故事,同時會不定期更新一些技術文章

歡迎大家掃碼關注,一起吸貓,一起聽故事,一起學習前端技術

作者寄語

小小總結,歡迎大家指導~

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※超省錢租車方案

※別再煩惱如何寫文案,掌握八大原則!

※回頭車貨運收費標準

※教你寫出一流的銷售文案?

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

分類
發燒車訊

日本香川縣養雞場爆禽流感 將撲殺33萬隻雞

摘錄自2020年11月5日中央社報導

日本香川縣三豊市的一處養雞場從11月1日到4日突然有約3800隻雞死亡,經檢驗後驗出H5型禽流感病毒,該養雞場將撲殺約33萬隻雞。

香川縣政府今天上午召開對策本部會議,決定養雞場飼養的約33萬隻雞將進行撲殺。

日本的養雞場上次發生禽流感疫情,是2018年1月香川縣讚岐市的養雞場驗出H5型禽流感病毒。

日本首相菅義偉今天上午聽取報告後,指示相關單位要提醒家禽業者對此高度警戒,並輔導與支援業者預防、收集現場情報;農林水產省與相關部會緊密合作,迅速進行防疫措施,以及對國民盡速提供正確的防疫資訊。

國際新聞
日本
禽流感

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

分類
發燒車訊

人類感染豬流感H1N2病毒 加拿大通報首例

摘錄自2020年11月5日中央社報導

加拿大衛生當局今(4日)通報,境內發現罕見豬流感H1N2病毒株感染人類首例。

地方衛生官員聲明表示,此一病例是10月中旬在西部亞伯達省(Alberta)發現,看來是獨立病例,「此時,未見對亞伯達省民眾的風險增加」。加拿大衛生官員正在調查病毒的感染源,並要確認病毒尚未擴散。

法新社報導,自2005年迄今,全世界僅通報27例人類感染H1N2病毒病例,這與較尋常所見的H1N1豬流感病毒不同。此前,加拿大未曾出現H1N2感染病例。

官員表示,H1N2病毒不是食物相關的疾病,人類攝食豬肉或其他豬產品,並不會受感染。

國際新聞
加拿大
豬流感

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

※教你寫出一流的銷售文案?

※超省錢租車方案

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※產品缺大量曝光嗎?你需要的是一流包裝設計!

分類
發燒車訊

九成魚種瀕臨滅絕 澳洲科學家建議多吃水母

摘錄自2020年11月5日ETtoday報導

根據《BBC》報導,澳洲海洋生物學家格施溫(Lisa-Ann Gershwin)表示,全球約有90%的魚種瀕臨滅絕,人們應增加水母的食用量,減緩全球漁業枯竭。此外,水母還是很好的節食食品,蛋白質含量高,熱量近乎為零;加上本身無味,配對醬料就變一道佳餚;還能取代魚翅,減少鯊魚捕撈。

格施溫提到,水母被捕後,牠的基因仍會存於海洋;就如同摘蘋果,蘋果樹被摘越多顆蘋果,它們的生命週期增加,這棵樹便會長出更多蘋果;即使蘋果都被摘光,還是會重新長出新蘋果。格施溫認為,水母是可再生物種,被捕撈的水母只是其他水母的複製物種。

海洋
國際新聞
澳洲
水母
漁業資源

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※別再煩惱如何寫文案,掌握八大原則!

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案

※教你寫出一流的銷售文案?

網頁設計最專業,超強功能平台可客製化

※產品缺大量曝光嗎?你需要的是一流包裝設計!

分類
發燒車訊

全球僅五例! 黃金烏龜身世之謎曝光

摘錄自2020年11月4日聯合新聞網報導

美國媒體《紐約郵報》(New York Post)報導,尼泊爾村莊上周二(27日)發現一隻外貌奇特、金黃色的緣板鱉 (Indian flapshell turtle)。《紐約郵報》指出,這隻緣板鱉是因為基因突變的白變(Leucism),導致體內色素減少,皮膚白皙,蒼白或斑駁,而黃色色素細胞通常會變成主導顏色,與白化症不同。

發現這隻緣板鱉的爬行動物專家德沃柯塔(Kamal Devkota)表示他第一次看到這種特殊顏色的烏龜。牠也是尼泊爾史上第一隻、全世界第五隻患白變的烏龜。研究人員已放生這隻緣板鱉回野外,但德沃柯塔擔心牠會面臨生存問題:「在大自然裡這樣的色差非常罕見,可能會在環境中處於不利地位。」

德沃柯塔補充,烏龜在尼泊爾有重要的宗教和文化價值:「人們相信毗濕奴(印度教最著名的神靈之一)變身烏龜以拯救宇宙免受破壞。」

生物多樣性
國際新聞
尼泊爾
烏龜

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※教你寫出一流的銷售文案?

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!

分類
發燒車訊

民主非人類專利 這三種動物也會選舉罷免

環境資訊中心綜合外電;姜唯 編譯;林大利 審校

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※超省錢租車方案

※別再煩惱如何寫文案,掌握八大原則!

※回頭車貨運收費標準

※教你寫出一流的銷售文案?

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

分類
發燒車訊

最低5.48萬起,這些車代表了國產車最高水準?

相比較其它車企來說,寶駿就簡單粗暴多了,價格就是寶駿最大的優勢,因此寶駿也有價格屠夫的稱號,早期寶駿推出寶駿樂馳和寶駿630的時候其實價格優勢並不明顯,而從寶駿730和560開始,寶駿的高性價比稱號便越打越響,而310和510的上市也直接讓其坐穩了家轎市場的頭把交椅。

俗話說,沒有兩把刷子,怎麼能出來混社會;做人如此,造車也是這樣,對於各家車企來說,它們的產品雖然都各有優缺,但是那些賣的好混的不錯的車企卻往往有它們的看家本領。中國汽車品牌的繁榮發展雖然不過短短20年,但是卻也有不少的車企做得越來越大,在中國汽車市場上呼聲較高,除了自主車向來的高性價比之外,這些自主品牌都有什麼看家本領呢?

長安這些年來混得是順風順水,相當不錯;說起長安,可能很多人想到的是長安車型漂亮的外觀內飾,長安最新的睿騁CC和逸動就十分帥氣。

不過長安在自主車型中優勢最大的其實還是底盤,長安的車型底盤的厚實感和懸挂的濾震處理、側傾抑制都是相當出色的。長安的車型在底盤的質感上有一種歐系車的感覺,這是很多自主車型望塵莫及的。

代表車型:長安CS95

作為長安目前產品序列中最高級也是最碩大的車型,CS95代表了長安的產品高度,而實際上這款車的表現也是可圈可點,厚實的底盤不錯的隔音以及較高的性價比都是CS95的競爭力所在。

在自主車型中如果要問誰的發動機最好的話,那麼一定是榮威和MG的車型,目前榮威和MG產品序列中有兩款發動機堪稱強悍,那就是1.5T和2.0T的兩款渦輪增壓發動機,前者擁有169馬力/250牛米的動力,後者輸出更是高達220馬力/350牛米。

這樣的兩台發動機加持使得榮威MG的車型動力十分強悍,我們實測的1.5T名爵6百公里加速時間僅需7.09秒,可以說相當厲害了。除此之外搭載這兩台發動機的車型油耗也普遍比同規格的自主車型更低,實力可見一斑。

代表車型:名爵6

作為名爵榮威最有代表的車型,名爵6的動力十分強悍,1.5T的發動機加持下實測百公里加速僅需7.09秒,而且混合動力版本的名爵6加速還更加恐怖;除了強大的動力之外,名爵6在科技感上也相當出色,加上不錯的外觀,名爵6銷量也比較不錯了。

相比較其它車企來說,寶駿就簡單粗暴多了,價格就是寶駿最大的優勢,因此寶駿也有價格屠夫的稱號,早期寶駿推出寶駿樂馳和寶駿630的時候其實價格優勢並不明顯,而從寶駿730和560開始,寶駿的高性價比稱號便越打越響,而310和510的上市也直接讓其坐穩了家轎市場的頭把交椅。

寶駿的車型在保證品質的基礎上,價格卻總是比同規格的哈弗等車型便宜不少,因此也獲得了市場的熱捧,我們老闆就因為看中了寶駿的低價高質,買了台寶駿730給我們做公務用車呢~

代表車型:寶駿510

自從哈弗H6誕生以來,連續幾年的時間霸佔銷量榜首位,不過510發力之後完成了對哈弗H6的超越,憑藉超高的性價比以及靚麗的外觀,寶駿510為寶駿品牌的形象以及銷量立下了汗馬功勞,可以說510成就了如今的寶駿。

寶馬7系的遙控泊車可以說是一個創舉,但是第一個做遙控泊車的卻並非寶馬,而是咱們的比亞迪,並且比亞迪的遙控泊車不僅可以控制前後行駛,還能轉向,這就是比亞迪在科技配置上創新的體現,而諸如此類的电子配置很多,比如說全景影像、綠凈系統、可以自動切換內外循環的空調以及自帶記錄儀等功能都是比亞迪率先涉足的,在科技配置方面比亞迪說第二,傳統車企里估計沒人敢認第一。

代表車型:宋MAX

雖說宋MAX並非是比亞迪最高端的車型,但確實是比亞迪目前最受市場歡迎的車型了,除了那個漂亮的外觀和內飾之外,宋MAX的配置也是非常誇張,前後雷達、全景影像、電動尾門、前排座椅通風加熱、12.8英寸大屏、全LED大燈等配置出現在這麼一台十萬級的MpV也是十分誇張了。

從前有這麼一個段子,長城的老闆公布年度預算:“研發部門500萬,設計部門10億”,雖然是車友編出來的一個段子,但是也能看得出公眾對於長城車型的印象。而這一個設計花十億的印象也是直接來自於長城現有的車型。

在自主車型中長城的外觀內飾精緻度確實非常高,除了最近哈弗車系大面積推廣的电子手剎之外,在車輛的鈑金噴漆水平、內飾的用料做工甚至按鍵的手感質感上哈弗都在不遺餘力地做好,而這樣帶來的好處就是哈弗的車型與廉價感這一詞已經完全脫離關係了,無論是視覺感受還是觸感都十分出色,讓人愛不釋手。

代表車型:哈弗H7

目前哈弗產品中定位最高的並不是哈弗H7,但是在內飾外觀精緻度上認為哈弗H7是最出色的,尤其是擋桿後方的按鈕分佈,看起來用起來都有幾分奧迪的精緻感,加上車內不錯的做工用料,這個內飾的品質感絕對不輸30萬的合資車。

雖然長安車型的底盤質感不錯,但是長安為了迎合年輕人而做了很多運動化調校,因此多數長安車型的底盤濾振不算徹底,要說自主車型中哪個品牌的車濾振最能給人好感,虎哥覺得一定是東南。

東南車型雖然外觀足夠靚麗,但是並非是普通貨色,在濾振水平上東南可謂相當出色,比如說東南DX3和東南DX7的濾振就足夠徹底,路面上大大小小的振動都能被過濾得十分到位,即使是快速軋過井蓋也不會有單薄感和突兀感,感覺有一種貼地飛行的質感,濾振水平完全不遜色於別克。

代表車型:東南DX3

在試駕DX3就對DX3的濾震水平十分震驚,激烈駕駛時底盤的安穩感很好、經過大大小小的坑窪時車內感覺都相當淡定,懸挂的濾振表現值得表揚,底盤的厚實感也是比較到位了,超過同價位的多數自主車。

2010年前後,中國市場掀起了一股合資自主品牌的風潮,當時許多諸如理念、啟辰、朗世之類的合資自主品牌誕生,但是大潮褪去才知道誰在裸泳,如今完整活下來並且混得還不錯的也就剩下啟辰了。

而說起啟辰最厲害的地方,那就是啟辰的車型基本就是換殼的日產,比如說T70實際上就是海外的7座版老款逍客、D60就是軒逸、D50/R50也就是老款的日產騏達,因此在三大件和造車水準上啟辰也原封不動地繼承了日產的基因,但是在價格上啟辰比哈弗之類的自主車有過之而無不及,因此賣得好也不足為奇了。

代表車型:啟辰T70

啟辰T70實際上就是海外7座版逍客的底盤,加上日產那套成熟的2.0L+CVT動力總成,雖然動力不算強悍,但是勝在可靠性和燃油經濟性好,這也是選擇啟辰的一大重要原因,在性價比上T70也超過了大多數的自主車,比如說11.78萬的T70 2.0L CVT睿享版就有定速巡航、胎壓監測、一鍵啟動無鑰匙進入、主駕駛電動座椅等高端裝備。

有神似奧迪Q3的SR7、跟保時捷macan傻傻分不清的SR9、撞臉大眾概念SUV的大邁X7、有奧迪A6L內涵的眾泰Z700以及神似奧迪Q5的眾泰T600,花五分之一的錢買一輛豪車,誰能不心動?

代表車型:幾乎全系車型~本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案