vue-login-intercept
一個 Vue.js 的小demo
介紹
項目地址,有喜歡的歡迎 star 或提出問題。
該項目是根據(jù) 一個項目學(xué)會前端實現(xiàn)登錄攔截 項目的思想,基于 Element UI 完成一個登錄,攔截登錄,登出,利用 GitHub API 獲取數(shù)據(jù)等功能的一個小 demo ,利用了 vue,vue-router,vuex,axios,webpack,sass 等技術(shù)棧, 通過 eslint 進(jìn)行語法檢查,是 vue 初學(xué)者實踐的一個很好的例子。
項目分為網(wǎng)頁版和桌面版,網(wǎng)頁版使用 vue-webpack 作為項目模板構(gòu)建,桌面版使用基于 Electron 和 Vue 的 electron-vue 模板構(gòu)建。
Electron 是一個能讓你通過 JavaScript、HTML 和 CSS 構(gòu)建桌面應(yīng)用的框架。這些應(yīng)用能打包到 Mac、Windows 和 Linux 電腦上運行,當(dāng)然它們也能上架到 Mac 和 Windows 的 app stores。Electron 結(jié)合了 Chromium、Node.js 和用于調(diào)用操作系統(tǒng)本地功能的 API(如打開文件窗口、通知、圖標(biāo)等)?;?Electron 的開發(fā),就好像開發(fā)一個網(wǎng)頁一樣,而且能夠無縫地使用 Node?;蛘哒f:就好像構(gòu)建一個 Node app,并通過 HTML 和 CSS 構(gòu)建界面。另外,你只需為一個瀏覽器(最新的 Chrome)進(jìn)行設(shè)計,無需考慮兼容性。使用 Electron 可以快速開發(fā)桌面端程序,并且兼容性好,可移植性強(qiáng),是使用前端技術(shù)開發(fā)桌面端應(yīng)用程序的很好地選擇。
網(wǎng)頁版截圖



Electron版截圖


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