用Vue編寫一個簡單的仿Explorer文件管理器
- 2022 年 3 月 11 日
- 筆記
- HTML, javascript, typescript, VUE
大家一定很熟悉你桌面左上角那個小電腦吧,學名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來看看最終效果:
程式碼倉庫: