vue-login-intercept
一個 Vue.js 的小demo
介紹
項目地址,有喜歡的歡迎 star 或提出問題。
該項目是根據 一個項目學會前端實現登錄攔截 項目的思想,基于 Element UI 完成一個登錄,攔截登錄,登出,利用 GitHub API 獲取數據等功能的一個小 demo ,利用了 vue,vue-router,vuex,axios,webpack,sass 等技術棧, 通過 eslint 進行語法檢查,是 vue 初學者實踐的一個很好的例子。
項目分為網頁版和桌面版,網頁版使用 vue-webpack 作為項目模板構建,桌面版使用基于 Electron 和 Vue 的 electron-vue 模板構建。
Electron 是一個能讓你通過 JavaScript、HTML 和 CSS 構建桌面應用的框架。這些應用能打包到 Mac、Windows 和 Linux 電腦上運行,當然它們也能上架到 Mac 和 Windows 的 app stores。Electron 結合了 Chromium、Node.js 和用于調用操作系統本地功能的 API(如打開文件窗口、通知、圖標等)?;?Electron 的開發,就好像開發一個網頁一樣,而且能夠無縫地使用 Node。或者說:就好像構建一個 Node app,并通過 HTML 和 CSS 構建界面。另外,你只需為一個瀏覽器(最新的 Chrome)進行設計,無需考慮兼容性。使用 Electron 可以快速開發桌面端程序,并且兼容性好,可移植性強,是使用前端技術開發桌面端應用程序的很好地選擇。
網頁版截圖
Electron版截圖
技術棧
構建步驟
# 安裝依賴
npm install
#在本地服務器上的8080端口啟動熱加載調試
npm run dev
# 以最小化構建產品
npm run build
# 構建并查看分析器報告
npm run build --report=
項目結構
網頁版
.
├── README.md
├── build // 構建配置文件
│ ├── build.js
│ ├── check-versions.js
│ ├── dev-client.js
│ ├── dev-server.js
│ ├── utils.js
│ ├── vue-loader.conf.js
│ ├── webpack.base.conf.js // 基本的 webpack 配置文件
│ ├── webpack.dev.conf.js
│ └── webpack.prod.conf.js
├── config
│ ├── dev.env.js
│ ├── index.js
│ └── prod.env.js
├── index.html
├── package-lock.json
├── package.json
├── src // 源文件
│ ├── App.vue
│ ├── assets // 資源目錄
│ │ ├── layout.scss
│ │ └── logo.png
│ ├── components // vue 組件
│ │ ├── index.vue // 主頁
│ │ ├── login.vue // 登錄
│ │ └── repository.vue // 倉庫列表
│ ├── main.js // 入口文件
│ ├── router // 路由
│ │ ├── http.js // axios獲取數據
│ │ └── index.js
│ └── store // vuex 狀態管理的文件夾
│ ├── index.js
│ └── mutations.js
└── static // 靜態資源
項目邏輯
1. 組件
分析項目的目的,我們可以得出以下主要過程:
登錄 -> 驗證 -> 獲取 GitHub 上的倉庫信息 -> 注銷
由上面的過程,我們可以抽出 3 個組件:首頁,登錄,倉庫列表。index
組件顯示介紹信息,登錄和驗證功能在 login
組件中完成,獲取 GitHub 上的倉庫信息并進行展示由 repository
組件完成。注銷功能全局可見,直接放到導航欄中即可。
2. 攔截登錄
攔截登錄是這個項目的要點,主要分為兩步,一是通過路由攔截,二是通過 axios
的攔截器。
路由攔截
在定義 reposiroty
組件的路由時需要配置 meta
字段,在 meta
字段中添加一個屬性,用于判斷該路由的訪問是否需要登錄,如果用戶已經登錄,則順利進入路由, 否則就重定向到登錄頁面。
meta: {
requiresAuth: true // 用于判斷進入這個路由是否需要認證
}
// 在每個路由生效之前,先進行一些處理,請參考 vue-router官方文檔-導航鉤子
router.beforeEach((to, from, next) => {
// 對 to.matched 數組中的每個路由調用箭頭函數
if (to.matched.some(record => record.meta.requiresAuth)) {
// 判斷登錄狀態
if (store.state.token) {
// 繼續路由
next()
} else {
// 重定向到登錄界面
next({
path: '/login',
query: { redirect: to.fullPath }
})
}
} else {
// 繼續路由
next()
}
})
router.beforeEach
是 vue-router 提供的導航鉤子,導航鉤子的作用主要用來攔截導航,讓它完成跳轉或取消??梢允褂?router.beforeEach
來注冊一個全局的 before
鉤子,當一個導航觸發時,全局的 before
鉤子會被按照創建順序調用,鉤子是異步解析執行,在所有的鉤子解析完成之前,導航一直處于等待之中。
官方文檔:
每個鉤子方法接收三個參數:
to: Route
: 即將要進入的目標路由對象
from: Route
: 當前導航正要離開的路由
next: Function
: 一定要調用該方法來 resolve 這個鉤子。執行效果依賴 next 方法的調用參數。
next()
: 進行管道中的下一個鉤子。如果全部鉤子執行完了,則導航的狀態就是 confirmed (確認的)。
next(false)
: 中斷當前的導航。如果瀏覽器的 URL 改變了(可能是用戶手動或者瀏覽器后退按鈕),那么 URL 地址會重置到 from 路由對應的地址。
next('/')
或者next({ path: '/' })
: 跳轉到一個不同的地址。當前的導航被中斷,然后進行一個新的導航。
注意: 一定要調用 next
方法,否則鉤子就不會被解析,也就不會起作用。
to
和 from
是路由信息對象,包含了一些基本屬性,其中 matched
屬性是一個數組,記錄了當前路由的所有嵌套路徑片段的 路由記錄。路由記錄就是 routes 配置數組中的對象副本(還有在 children 數組)。
const router = new VueRouter({
routes: [
// 下面的對象就是路由記錄
{ path: '/foo', component: Foo,
children: [
// 這也是個路由記錄
{ path: 'bar', component: Bar }
]
}
]
})
我們通過對 matched
數組調用 some
方法,遍歷數組,檢查所有路由記錄的 meta
字段中是否有需要登錄的標志屬性,如果有,就判斷是否登錄,做進一步處理,否則直接進行路由導航。
axios 攔截器
路由攔截只能進行簡單的攔截,若用戶惡意進行登錄,路由攔截并不能起到很好地作用,還需要根據服務器的返回信息進行攔截,這時就需要時使用 axios 的攔截器。
axios 的攔截器(interceptors)可以在 請求(request)
或者 返回(response)
被 then
或者 catch
處理之前對他們進行攔截,即可以在正式向服務器發送請求之前和使用 then
或者 catch
處理服務器的請求之前對信息進行處理。
部分源代碼
// 請求攔截器
axios.interceptors.request.use(
config => {
if (store.state.token) {
config.headers.Authorization = `token ${store.state.token}`
}
return config
},
err => {
return Promise.reject(err)
})
// 返回攔截器
axios.interceptors.response.use(
response => {
return response
},
error => {
if (error.response) {
switch (error.response.status) {
case 401:
// 401 清除token信息并跳轉到登錄頁面
store.commit(Mutations.LOGOUT)
router.replace({
path: '/login',
query: {redirect: router.currentRoute.fullPath}
})
}
}
return Promise.reject(error.response.data)
})
這里使用 axios
的請求攔截器,在發起請求前判斷用戶的登錄狀態(通過 token 信息),如果登錄的話則在 http header
上加上 token
信息,否則拒絕發送請求。在處理返回的信息時,通過返回的狀態碼,判斷是否為非法登錄(401),如果是非法登錄,則清除登錄信息(本地存儲的 token
信息),重定向到登錄界面。
3.狀態管理(Vuex)
在這個項目里,多個組件(index
和 login
)都需要用到 token
信息,也會有多個組件(login
和 repository
)對 token
或者 title
信息進行更改的情況, 這個時候就涉及到兩個問題:
- 多個視圖依賴于同一狀態。
- 來自不同視圖的行為需要變更同一狀態。
讓我們來看看官方文檔的對這種問題的解釋:
對于問題一,傳參的方法對于多層嵌套的組件將會非常繁瑣,并且對于兄弟組件間的狀態傳遞無能為力。對于問題二,我們經常會采用父子組件直接引用或者通過事件來變更和同步狀態的多份拷貝。以上的這些模式非常脆弱,通常會導致無法維護的代碼。
因此,我們為什么不把組件的共享狀態抽取出來,以一個全局單例模式管理呢?在這種模式下,我們的組件樹構成了一個巨大的“視圖”,不管在樹的哪個位置,任何組件都能獲取狀態或者觸發行為!
另外,通過定義和隔離狀態管理中的各種概念并強制遵守一定的規則,我們的代碼將會變得更結構化且易維護。
這就是 Vuex 背后的基本思想,借鑒了 Flux、Redux、和 The Elm Architecture。與其他模式不同的是,Vuex 是專門為 Vue.js 設計的狀態管理庫,以利用 Vue.js 的細粒度數據響應機制來進行高效的狀態更新。
通過上面的解釋,我們可以看出來,Vuex 能夠很好地解決以上問題。
每一個 Vuex 應用的核心就是 store(倉庫)。"store" 基本上就是一個容器,它包含著你的應用中大部分的狀態(state)。Vuex 和單純的全局對象有以下兩點不同:
Vuex 的狀態存儲是響應式的。當 Vue 組件從 store 中讀取狀態的時候,若 store 中的狀態發生變化,那么相應的組件也會相應地得到高效更新。
你不能直接改變 store 中的狀態。改變 store 中的狀態的唯一途徑就是顯式地提交(commit) mutations。這樣使得我們可以方便地跟蹤每一個狀態的變化,從而讓我們能夠實現一些工具幫助我們更好地了解我們的應用。
創建 store
的過程很簡單,只需要提供一些初始 state
對象和一些 mutations
:
const store = new Vuex.Store({
state: {
title: '攔截登錄',
user: 'wuyiqinng',
token: ''
},
mutations: {
// 使用 ES6 的計算屬性
[Mutations.LOGIN] (state, token) {
localStorage.token = token
state.token = token
},
[Mutations.LOGOUT] (state) {
localStorage.removeItem('token')
state.token = ''
},
[Mutations.TITLE] (state, title) {
state.title = title
}
}
})
現在,你可以通過 store.state 來獲取狀態對象,以及通過 store.commit 方法觸發狀態變更
store.commit(Mutations.LOGOUT)
console.log(store.state.title)
Vuex 主要有幾個核心概念:
- State: 狀態,也就是數據來源,可以看作是組件中的
data
,不過是抽離的公共數據。 - Getters:可以理解為 store 的計算屬性。
- Mutations:更改 store 中的 state 的方法,類似于事件,每個 mutation 都有一個字符串的
事件類型 (類似于事件的名稱)
和一個回調函數 (handler)
,這個回調函數就是我們實際進行狀態更改的地方,并且它會接受 state 作為第一個參數。 mutation 必須是同步函數。 - Actions:Action 類似于 mutation,不過 Action 提交的是 mutation ,而不是直接變更狀態,并且 Action 可以包含任何異步操作。
- Modules:Vuex 允許我們將 store 分割成模塊,避免當狀態較多時, store 對象過于臃腫。
更多關于 Vuex 的內容請看官方文檔
項目文件
網頁版
main.js
main.js
文件是 webpack 打包的入口文件,也是 vue 應用程序的入口文件,在這里要完成一些項目所需模塊的加載,實例化并掛載 vue。
App.vue
App.vue
是主要的應用組件,也是最先被加載的組件,可以認為是傳統網頁中的首頁,但是不同的是,其他組件會被加載到 App.vue
中的路由視圖區 <router-view></router-view>
中,也就是說 App.vue
中的其他內容并不會消失,而是存在整個應用的生命周期中,所以 App.vue
中適合寫一些存在于全部頁面中的結構,如導航欄。
這里,我在 App.vue
中直接寫入了導航欄,因為導航欄結構比較簡單,如果是比較復雜的結構,還是抽離為單頁面組件比較好。
router/index.js
路由的官方介紹:
用 Vue.js + vue-router 創建單頁應用,是非常簡單的。使用 Vue.js ,我們已經可以通過組合組件來組成應用程序,當你要把 vue-router 添加進來,我們需要做的是,將組件(components)映射到路由(routes),然后告訴 vue-router 在哪里渲染它們。
在這里進行基本的路由配置,將組件映射到路由,使得組件能夠被正確的加載并渲染,路由和傳統網頁中的 <a href=""></a>
標簽意義比較接近,不過是針對 vue 的組件進行加載,也可以作為傳統的標簽使用,功能上更加強大。
router/http.js
http.js
文件是 axios 的包裝文件,包裝了 axios 的登錄攔截方法,
components/
components/
目錄下是構成應用的單文件組件,主要有首頁,登錄,倉庫列表三個組件,會被加載到 App.vue
中的路由視圖區中。
stroe/
狀態管理模式 vuex 的配置文件 index.js
和管理 mutation 事件類型的文件 mutations.js
,mutations.js
的作用是:使用常量代替 mutation 事件類型,把這些常量放在單獨的文件中可以讓你的代碼合作者對整個 app 包含的 mutation 一目了然。