大家一定很熟悉你桌面左上角那個小電腦吧,學名Windows資源管理器,幾乎所有的工作都從這里開始,文件雲端化是一種趨勢。怎樣用瀏覽器實現一個Web版本的Windows資源管理器呢?今天來用Vue好好盤一盤它。
一、導航原理
首先操作和仔細觀察導航欄,我們有幾個操作途徑:
- 點擊“向上”按鈕回到上一個目錄,點擊地址欄的文件夾名稱返回任意一個目錄
- 雙擊文件夾進入新目錄
- 點擊“前進”,“后退”按鈕操作導航
其中前進,后退操作,可以點擊小三角查看一個列表,點擊進入文件夾,列表會記錄導航歷史,哪怕反復進入同一個文件夾,列表仍然會記錄下來,如下圖:
那么我們就能分析並抽象出兩個變量:
- 一個用於存儲實際導航的變量(navigationStack)
- 另一個用於存儲導航歷史的變量(navigationHistoryStack)
導航堆棧用於存儲每一個瀏覽文件夾的信息,拼接起這些文件夾就形成了當前路徑, 一組簡單的<li>元素通過綁定導航堆棧,就能形成地址欄(web世界里也叫面包屑導航)了。
navigationStack實際上是一個堆棧,用的是先進后出(FILO)原則
導航歷史則是單純記錄了用戶的操作軌跡,不會收到導航目標的影響,如剛才所述,哪怕反復進入同一個文件夾,列表仍然會記錄下來
navigationHistoryStack實際上是一個隊列,用的是先進先出(FIFO)原則
接下來我們開始碼代碼
我們先新建一個Vue項目(Typescript),打開App.vue文件
script標簽里編寫代碼如下:
二、文件夾跳轉原理
我們先來看如下數據結構
FileDto是定義的文件描述類,這是描述一整個樹形結構的基本單元,通過唯一id和指定它的上級parentId,通過遞歸就可以描述你的某一文件,某一文件夾具體在哪一層級的哪一個分支中。現在假設我們有一堆的文件樹長這樣:
定義查詢函數checkMessage和當前目錄層級的文件集合listMessage:
再定義一個目錄訪問器gotoList函數,通過傳入查詢條件,更新當前目錄層級的文件列表:
編寫UI部分,簡單定義一個table,並綁定文件集合listMessage來顯示所有文件:
當調用gotoList函數的時候,相當與“刷新”功能,獲取了當前查詢條件下的所有文件
三、編寫導航邏輯
導航堆棧處理函數
剛剛我們分析了導航原理,導航堆棧的作用是形成地址,我們定義一個導航堆棧處理邏輯:
- 判斷當前頁面是否在導航堆棧中
- 若是,則彈出至目標在導航堆棧中所在的位置
- 若否,則壓入導航堆棧
其中toFolder函數用於實際導航並刷新頁面的,稍后介紹
“向上”導航函數:
向上的作用屬於一個特定的導航堆棧處理:
- 直接彈出最上的條目,
- 拿到最上層條目並導航
定義跳轉函數toFolder,之后許多函數引用此函數,這個函數單純執行跳轉,傳入文件描述對象,執行導航,刷新頁面,返回bool值代表成功與否:
簡單的寫一下導航操作區域和地址欄的Ui界面:
四、編寫歷史導航處理邏輯
“后退”函數
- 首先確定當前頁面在歷史導航的哪個位置
- 拿到角標后+1(因為是隊列,所以越早的角標越大),拿到歷史導航隊列中后一個頁面條目,並執行導航函數
“前進”函數
- 首先確定當前頁面在歷史導航的哪個位置
- 拿到角標后-1(因為是隊列,所以越晚的角標越小),拿到歷史導航隊列中前一個頁面條目,並執行導航函數
然后我們需要一個函數,用於顯示歷史隊列中(當前)標簽:
簡單的寫一下導航操作區域:
導航按鈕以及歷史列表:
代碼如下:
五、問題修復與優化
問題1:歷史條目判斷錯誤
測試的時候會發現一個問題,用id判斷當前頁面所在的堆棧位置,會始終定位到最近一次,相當於FirstOrDefault,因為歷史隊列可以重復添加,所以需要引入一個isCurrent的bool值屬性,來作為判斷依據。
這相當於是增加了狀態變量,從“無狀態”變換成“有狀態”,意味着我們要維護這個狀態。好處是可以簡單的從isCurrent就能判斷狀態,壞處就是要另寫代碼維護狀態,增加了代碼的復雜性。
將navigationTo函數改寫成如下:
判斷是否為當前的函數則簡化為如下:
從導航歷史隊列跳轉的目錄,也需要處理導航堆棧,因此從navigationTo函數中將這一部分剝離出來單獨形成函數命名為dealWithNavigationStack:
“前進”函數與“后退”函數分別改寫為:
問題2:文件描述對象重疊
先看現象,重復進入“文件夾A”的時候,都標記為(當前),這顯然是錯誤的
請留意navigationTo中的這一段代碼:
這里隱藏了一個bug,邏輯是將所有的歷史隊列條目去除當前標記,然后將最新的目標標記為當前並壓入歷史隊列,這里的 folder這一對象來自於listMessages,
JavaScript在5中基本數據類型(Undefined、Null、Boolean、Number和String)之外的類型,都是按地址訪問的,因此賦值的是對象的引用而不是對象本身,當重復進入文件夾時,folder與上一次進入添加到隊列中的folder,實際上是同一個對象!
因此所有的“文件夾A”都被標記為“(當前)”了
我們需要將 this.navigationHistoryStack.unshift(folder);改寫,提取出一個名稱為pushNavigationHistoryStack的入隊函數:
這里加入了一個控制,歷史隊列最多容納10個條目,大於10個有新的條目入隊列時,將剔除最后一條(也就是最早的一條記錄,記錄越早角標越大)。
接下來運行yarn serve來看看最終效果:
代碼倉庫: