1、上傳組件需求分析
我們還需要新建和展示文章,新建文章自然是發送post請求,同時在post中自帶對應的數據,展示文章就是根據id取出已有的數據并且展示出來。
這里有一個難點就是上傳組件,上傳文件是App應用中最基本的需求,在Ajax出現之前,我們一般都使用一個簡單的input框來實現這個上傳的流程,把它的type設置成file,把from提交以后都交給后端處理。
自從有了ajax出現了以后,JS有了異步發送請求的能力,我們可以更方便的在頁面不跳轉的情況下完成文件的上傳,同時還能獲得很好的可視化效果,比如說傳的百分比、圖片的預覽、最終的狀態等等
整個流程,一開始我們應該有能力讓用戶去檢查這個圖片的格式或者大小的,我們會提供一個屬性叫beforeUpload,它是一個function,讓用戶去檢查這個文件的一些具體的需求;然后通過以后,這個時候這個組件就會向外反射一個uploading這樣一個事件;然后上傳成功以后就會觸發filrUploaded這個事件,或者出現錯誤就會觸發uploadedError這個事件。這些事件對于用戶來說非常重要,他們很需要知道這些事情是什么樣的時間發生的,然后可以做取得額外的工作,比如說beforeUploaded我們進行上傳前的檢查如文件類型/大小等等,或者在最終上傳失敗的時候我們可以彈出一些額外信息等等,這些都是事件給我們帶來的魔力,除了這些事件以外,自定義也是我們的一大看點,我們的用戶應該可以使用template來自定義一些顯示的內容,比如說我們一開始上傳區域長成什么樣可以是一大片也可以是一個button等,上傳中顯示什么圖標什么文字,上傳完畢以后要顯示什么樣的數據,這些我們應該是可以完全自定義的
通過流程這么分析,我們大致得出組件大致應該有些什么,如下
它應該有一個action,代表著我們要把這個請求發送到后端哪里去處理;然后應該有一個function即beforeUploaded去完成一些上傳前的校驗;之后就是3個要觸發的事件,uploading(點擊上傳按鈕后)、fileUploaded(上傳成功后)、uploadedError(上傳失敗后);除了這些我們還要支持自定義模板,默認就是一開始它長什么樣,我們這里是一個button,然后我們可以添加這個template,這個template分為uploaded的即上傳前的模板和loading的即上傳中的模板
發送異步請求是上傳組件的一個核心內容,那么接下來我們就談談使用axios來發送異步請求怎樣完成文件的上傳這個流程
2、上傳文件的兩種實現方式
接下來我們來了解上傳文件的原理
我們先從form提交的時候談起,既然要上傳文件,當然就要選擇文件,那么就可以選用如下
<input type="file">的標簽,這時候它就會渲染出一個可以選擇文件的對話框;那么當文件選擇完畢以后,我們有兩種方式上傳,一個是使用傳統的form submit即表單提交的方式;第二種是使用JavaScript發送異步請求的方式,這是我們著重要實現和理解的方法;
(1)如下我們了解一下第一種使用傳統的form submit即表單提交的方式的流程
如下例子,我們選擇文件然后點擊submit的時候它就會運行form的默認行為,帶input當中的數據,然后直接發送到一個特定的HTTP request請求到axios 的url,我們這里axios是/api/upload即會把input中的數據post到后端API為upload的地方中去,然后server端接到這個請求以后會做對應的處理,并且返回結果,這是一種典型的request到server的形式。這里要特別注意,我們這個表單一般于發送普通消息的表單,不同的地方就在于我們發送了文件,文件和普通的字符串不同,文件屬于一種二進制的格式,所以我們需要設置這個enctype="multipart/form-data",注意你要上傳文件或者你表單發送的有二進制的文件,那么就最好設置這種enctype="multipart/form-data"格式,因為表單的默認格式不支持二進制數據,所以需要設置成支持二進制數據的
我們選擇了文件后,點擊submit可以看到后端拿到這個內容以后就會做出對應的響應,它會返回如下圖片地址
我們的接口文檔中也提供了對應的接口
(2)使用JavaScript做file類型的表單上傳功能
了解了這些之后,我們就可以使用JavaScript來發送異步上傳文件的post請求了,提交form其實也就是發送http請求,那么使用JavaScript發送異步當然有更好的體驗了,其實它的過程也是萬變不離其宗的,也是用它來模擬表單的發送http請求
我們來新建文章頁面來嘗試一下,如下
然后我們嘗試一下上傳功能,如下我們點擊上傳某圖片后,上傳成功后它就打印出上傳成功的信息
從上面可以看到,使用JavaScript發送異步請求的方式和form提交的過程是非常相似的,只不過是用javascript模擬原生form的提交方式
3、Uploader組件 第一部分 -- 上傳組件流程
就和上面一樣,完成那個流程即可
(1)首先創建一個按鈕,使得點擊這個按鈕就執行file類型的input的點擊事件即彈出選擇文件的彈窗
如下,創建一個上傳組件叫Uploader.vue
復習一下:還記得怎么在setup中拿到一個dom節點嗎,通過ref咋拿來著,如下首先在標簽里通過加上ref="fileInput",然后const fileInput = ref<null | HTMLInputElement>(null),然后直接通過fileInput.value即可拿到這個fileInputDOM節點了
form那個方式是有一個input框,然后右邊有一個submit按鈕,我們想有一個按鈕,然后點擊這個按鈕就觸發file類型的input框,即想只展現出一個按鈕,這個按鈕的點擊事件中就觸發這個file input即form上傳功能。
怎么做,我們就如下設置一個button,也設置一個file類型的input,但是讓這個input隱藏即可,然后button的點擊事件中去獲取這個input的DOM節點,拿到input節點后接著去觸發input的點擊事件,即form中的那個submit的點擊事件(這個點擊事件就會去讓你挑選文件這樣子)。這樣就實現了頁面中展示一個點擊上傳的按鈕,然后你點擊這個按鈕,就會自動去觸發類型為file的input的點擊事件,從而就會展示出讓你選擇文件的彈窗
接下來我們來添加屬性
我們要接收一個action,這個action是接收發送請求的地址,而且是必選項是必須填的
(2)接下來就是上面說的經典的上傳過程
這里有個問題就是我們要根據不同的上傳階段,展示不同的元素,所以我們需要有一個字段來指示一下狀態如下,然后我們就開始做狀態變化后觸發的函數即handleFileChange函數
在這個函數中,我們做的就是如下獲取input這個DOM節點,這是為了我們在選擇文件的時候去獲取這個input的files屬性,因為我們選擇文件后這個input會帶上files屬性,這個屬性中就是我們選擇的那些文件們。我們獲取到我們選擇的文件后,首先去修改上傳的狀態,如下
這個e.target即事件對象就是這個input
我們選擇文件后,這個input就會加上這個files屬性,這個屬性即currenTarget.files打印出來可以看到就是我們選擇的那些文件,是一個列表list
然后繼續,獲取到我們選擇的文件后,我們把這個files變成一個Array,因為這個files是一個list并不是一個Array,我們用Array.form()來轉換成Array,是為了后面取它第一個。
然后創建一個form表單數據的屬性,怎么創建就通過new FormData() 從而創建一個Form表單數據屬性;然后把files的第一個數據給新建的這個FormData;FormData表單中有數據了,那么就可以進行post請求了,這個post請求中要傳三個參數,一個是url,一個是傳的數據,一個是headers(要寫成如下這個才可以接收二進制文件);最終處理請求的結果,通過.then() 接請求成功的結果然后進行如下處理,通過.catch() 接請求失敗的結果然后進行如下處理,最后那個finally是無論成功還是失敗都進行的函數,因為我們選擇了文件了嘛,所以input中的value被這個文件占了,你得先清空,下次再選擇時才不會出錯
(3)整個上傳流程總結:
我們的template中有button和file類型的input(給隱藏input),然后我們是通過點擊這個button去觸發該input的click點擊事件,這個點擊事件就會彈出讓你選擇文件的彈窗。
然后此時input在變化即change,說明此時在上傳文件,那么我們就給個change事件,這個事件是上傳的重點
在這個change事件中,我們要做的流程就是:獲取上傳的文件-->建表單數據-->發起請求,把選擇的文件傳給對應API
首先,獲取這個input這個dom節點(因為選擇文件后input會增加一個files屬性,這個屬性中就是我們選擇上傳的文件列表),獲取這個input就是為了獲取我們選擇上傳的文件
然后,拿到要上傳的文件后,新建一個表單數據屬性,并且給這個屬性files的值(因為post請求中要提交你選擇的文件嘛)
①把這個files列表變成Array即數組(這是為了獲取列表的第一個文件,我們只能選擇一個文件)
②新建一個form表單數據屬性,通過new FormData()
③把files中第一個文件數據給新建的這個FormData
最后,發起post請求,處理請求可能的請求成功、請求失敗的處理
至此文件已經可以成功上傳
過程中有一個小錯誤:
這里有一個要注意的點,就是發現在setup中說取不到props,要注意,setup(){}的括號中要寫上props才能取到props的值,有時候不小心漏了,setup函數,它接收兩個參數,一個是props,一個是context
4、Uploader組件 第二部分 -- 不同階段自定義操作
上面我們完成了上傳組件的最基本的流程,現在我們要將這里面的功能慢慢豐富進去
第一大功能就是在不同階段暴露出一系列的事件,對于用戶想在不同階段進行自定義操作的話就很有用;第二個是自定義模板,用戶可以根據需求渲染自己想要的頁面
(1)在上傳組件前,我們想自定義一個函數在上傳之前去檢查,比如上傳前檢查圖片是不是JGP格式,是JPG格式才能上傳
在上傳前我們想要有一些檢查的流程即beforeUpload函數,這個檢查是怎么檢查我們想要自定義式,怎么才能自定義呢,就你使用的地方定這個檢查邏輯,在這個上傳組件中只調用這個函數即可。
這個函數中應該接收用戶選擇的文件,然后經過一些列檢查,最終返回布爾值,布爾值代表著檢查的結果
如下在Uploader組件中
如下在Home.vue中,我們創建自定義檢查的這個beforeUpload函數,并且傳給uploader組件
即可如下,點擊上傳比如png的圖片,就會出現如下提示
(2)上傳成功后和上傳失敗后,我們想在這兩個時候做一些自定義的事情,則如下
如下,定義emits,emits中有file-uploaded表示上傳成功時調的函數、file-uploaded-error表示上傳失敗時調的函數,然后在上傳的post請求成功時通過context.emit()去調用對應的函數
從接口文檔中,我們可以看到返回的數據都是code、msg、data這樣的,所以我們到store中定義一個通用的格式,這樣就能享受到TypeScript類型的幫助,如下
然后我們在Home組件中定義這個上傳成功的函數以及上傳失敗的函數,如下
5、Uploader 組件第三部分 -- 自定義模板
(1)使用slot來做自定義模板
我們在Dropdown組件中我們已經見識過自定義模板,它是使用這個slot來完成這個對應功能的,讓我們來復習一下,我們使用name slot 來完成不同自定義模板的需求,我們在組件中添加slot,并且加上對應的name屬性,然后在使用的時候我們可以使用template標簽配合v-slot:name屬性來使用,這里面就是你自定義的HTML內容,v-slot也可以簡寫成一個#
現在要展示3個階段的界面,所以自然要展示三個slot來放置對應的自定義模板,所以如下我們來修改一下Uploader
如下,把正在上傳、上傳成功、點擊上傳都做成一個slot插槽,然后默認沒什么的時候它們就是一個個按鈕
然后如果這里面插入了東西,比如插入一段<h2>,那么就會替換這個插槽,但是這個h2標題還是有這個button這個按鈕功能,如下
插槽名為default時即展示h2的點擊上傳文本
插槽名為loading時,則展示這個旋轉圖標
上傳完畢后,我們需要在父組件Home.vue中拿到子組件Uploader.vue的一些數據,比如我們上傳完畢后想展示出這個圖片啥的,為了讓組件在slot中訪問子組件的某些特性,vue提供我們Scoped Slots就是為了讓我們解決這個問題的
(2)使用scoped slot 來解決子組件傳值給父組件的問題
看文檔,如下,子組件slot標簽中可以通過v-bind將一個屬性綁定到這個slot上面去,然后我們就可以給父組件中對應標簽中加上v-slot="xxx",然后通過xxx.這個屬性即可拿到這個屬性
如下,在Uploader組件中,我們新建一個ref屬性記錄請求成功時返回的數據,然后我們在slot中通過v-bind把這個數據傳出去,如下
然后到Home組件中使用,如下用v-slot接傳過來的值,把傳過來的值取名叫dataProps,然后在img中使用如下
如下,上傳成功后,圖片就展示出來了,這樣就實現了在slot中父組件使用子組件的數據的問題
6、改進路由驗證系統
接下來我們將上傳組件添加到新建文章頁面中,但是現在路由跳轉還有一些問題,我們先處理這個。
我們剛開始的路由驗證流程是如下這樣
但是我們發現了有一些問題,就是我們在下拉菜單組件點擊新建文章按鈕,可以正常跳轉到新建文章頁面,但是如果此時我們在新建文章頁面刷新就會發現會跳轉到登錄頁面,但是我們明明已經登錄了呀
這是因為路由中我們還是使用了store.state.user.isLogin來判斷的,而點擊刷新后store中的數據是都重置回初始值的。所以你登錄后isLogin確實從false置為true了,但是你刷新后isLogin就又是為false了,那么就符合這個if判斷了,所以就重定向到了登錄頁面去給你了
但是其實你此時是已經登錄狀態,而你用store.state.user.isLogin判斷就給你錯判成了未登錄狀態,所以首頁登錄過以后再點擊新建文章通過這個鏈接進行跳轉的話就不會有這個問題,這是我們要解決的問題,這里涉及稍微復雜的流程
所以我們要改進一下路由驗證系統,讓它更完善一些
我們做個流程圖,其實我們多的邏輯就是在發送請求這一步,其他的沒有什么區別,只不過現在的邏輯比以前復雜了一些
如下,一開始我們判斷這個user.isLogin,
如果已登錄是true,則判斷redirectAlreadyLogin這個參數存不存在,存在則跳轉到首頁,不存在則繼續進行;
如果未登錄是flase,那么我們就去判斷是否有token,
如果token不存在,則去判斷它訪問的這個路由是不是需要登錄才能訪問的,如果不是則繼續進行即讓他去這個路由的頁面,如果是需要登錄才能訪問的則跳轉到登錄頁面讓他先登錄;
(前面的和我們之前的一樣,我們新添加的邏輯就在這里)如果token存在,也就是說user的isLogin為false但是有token的情況,那么有可能是像上面這種剛剛刷新了isLogin為false但是其實是已登錄的狀態,也可能是有token但是是之前登錄的現在確實還沒有登錄,有token就說明這個用戶剛才或者之前登錄過,所以就直接重新發送一次異步fetchCurrentUser請求去登錄(這也是常見的,短時間內會自動登錄的嘛);然后就如果登錄失敗就彈出提示回到登錄頁面讓重新登錄,如果登錄成功則判斷是不是要到登錄或注冊頁面,如果是則重定向回首頁,如果不是比如是要到新建文章頁面那么直接next()即去新建文章頁面反正此時都登錄了