大家一定很熟悉你桌面左上角那個小電腦吧,學名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來看看最終效果:
程式碼倉庫: