vue实现用户登录验证(vue实现登录注册和验证码)
1. 技術棧說明
vue2.6 + vue-router + vuex + element-ui
2. 開始:新建項目
前提條件:在個人電腦上安裝好nodejs(我的是14.15.1)之后,利用nodejs自帶的npm包管理器安裝好vue(我的是@vue/cli 4.5.12)
- 在命令行中通過以下指令在指定目錄下安裝腳手架vue-cli
npm install -g @vue/cli
- 使用vue的創建項目命令,vue create xxx (xxx是指項目名稱)
- 選擇項目所需要的插件
? Check the features needed for your project:
? Choose Vue version // 選擇vue版本
? Babel // 支持babel
? TypeScript // 支持使用 TypeScript 書寫源碼
? Progressive Web App (PWA) Support // PWA 支持
? Router // 支持 vue-router
? Vuex // 支持 vuex
? CSS Pre-processors // 支持 CSS 預處理器。
? Linter / Formatter // 支持代碼風格檢查和格式化。
? Unit Testing // 支持單元測試。
? E2E Testing // 支持 E2E 測試。
// 注意:你要集成什么就選就行了(注:空格鍵是選中與取消,A鍵是全選)
- 選擇vue的版本,由于vue3目前只出來了8個月左右,受眾面不廣,所以選擇vue 2.x版本
至此,項目搭建完成,可以cd打開項目,run起來了。
3. 添加element-ui以及nprogress和normalize.css和配置vue.config.js
- 先安裝element-ui,nprogress和normalize.css
npm install element-ui nprogress normalize.css
由于element-ui使用到了sass-loader,所以這里也是需要安裝的
npm install sass-loader
當前項目插件如下:
- 配置vue.config.js
vue.config.js 是一個可選的配置文件,如果項目的 (和 package.json 同級的) 根目錄中存在這個文件,那么它會被 @vue/cli-service 自動加載。你也可以使用 package.json 中的 vue 字段,但是注意這種寫法需要你嚴格遵照 JSON 的格式來寫。
在根目錄中創建 vue.config.js
官方配置vue.config.js的具體詳解
'use strict'
const path = require('path')
function resolve(dir) {
return path.join(__dirname, dir)
}
// All configuration item explanations can be find in https://cli.vuejs.org/config/
module.exports = {
publicPath: '/', // 部署應用包時的基本 URL,用法和 webpack 本身的 output.publicPath 一致
outputDir: 'dist', // 構建輸出目錄(打包位置)
assetsDir: 'static', // 放置生成的靜態資源(js,css,img,fonts)的(相對于outputDir)的目錄
lintOnSave: false, // 是否校驗語法
productionSourceMap: false, // 如果你不需要生產環境的 source map,可以將其設置為 false 以加速生產環境構建
devServer: {
port: 8888,
open: true,
},
configureWebpack: { // 絕對路徑
resolve: {
alias: {
'@': resolve('src')
}
}
}
}
3. 功能實現
先講思路,讓大伙有個大概的印象,不至于看代碼云里霧里。
分以下幾步走:
前端在本地寫好路由表,以及每個路由對應的角色,也就是哪些角色可以看到這個菜單 / 路由。登錄的時候,向后端請求得到登錄用戶的角色(管理者,普通用戶)利用路由守衛者(router.beforeEach),根據取到的用戶角色,跟本地的路由表進行對比,過濾出用戶對應的路由,并利用路由進行菜單渲染
- 我們將儲存在將storage中的token作為用戶是否登錄的標志,如果當前storage中有token,表明當前系統已被登錄
- 將系統所有頁面分為兩類,需要登錄才能查看的頁面,不需要登錄的login.vue, register.vue等
- 前端每次跳轉路由時,做以下判斷:
接下來從技術棧的角度補充幾點:
- 在vue-router的beforeEach方法中實現以上邏輯,判斷前端跳轉去向;
- 出于教程考慮,不引入后端,用模擬數據的用戶信息作為攔截axios發起的服務請求響應;
- 通過window.localStorage.setItem做userInfo的狀態管理;
4. 實現
根據上述的步驟,我們進行每一個步驟的實現
1. 寫好mock數據,用以模擬后端返回的數據源
dynamicUser里面就是模擬的后端數據,一般的后臺數據庫里面,就是分為一個user用戶表,一個role權限路由表,這里不涉及后端,所以只給出最后后端輸出的數據源。
一個完整的后端數據示例如下:
const dynamicUser = [
{
name: "管理員",
avatar: "https://p3.toutiaoimg.com/img/user-avatar/ccb565eca95535ab2caac9f6129b8b7a~300x300.image",
desc: "管理員 - admin",
username: "admin",
password: "654321",
token: "rtVrM4PhiFK8PNopqWuSjsc1n02oKc3f",
routes: [
{ id: 1, name: "/", path: "/", component: "Layout", redirect: "/index", hidden: false, children: [
{ name: "index", path: "/index", meta: { title: "index" }, component: "index/index" },
]},
{ id: 2, name: "/form", path: "/form", component: "Layout", redirect: "/form/index", hidden: false, children: [
{ name: "/form/index", path: "/form/index", meta: { title: "form" }, component: "form/index" }
]},
{ id: 3, name: "/example", path: "/example", component: "Layout", redirect: "/example/tree", meta: { title: "example" }, hidden: false, children: [
{ name: "/tree", path: "/example/tree", meta: { title: "tree" }, component: "tree/index" },
{ name: "/copy", path: "/example/copy", meta: { title: "copy" }, component: "tree/copy" }
] },
{ id: 4, name: "/table", path: "/table", component: "Layout", redirect: "/table/index", hidden: false, children: [
{ name: "/table/index", path: "/table/index", meta: { title: "table" }, component: "table/index" }
] },
{ id: 5, name: "/admin", path: "/admin", component: "Layout", redirect: "/admin/index", hidden: false, children: [
{ name: "/admin/index", path: "/admin/index", meta: { title: "admin" }, component: "admin/index" }
] },
{ id: 6, name: "/people", path: "/people", component: "Layout", redirect: "/people/index", hidden: false, children: [
{ name: "/people/index", path: "/people/index", meta: { title: "people" }, component: "people/index" }
] }
]
},
{
name: "普通用戶",
avatar: "https://p3.toutiaoimg.com/img/user-avatar/6364348965908f03e6a2dd188816e927~300x300.image",
desc: "普通用戶 - people",
username: "people",
password: "123456",
token: "4es8eyDwznXrCX3b3439EmTFnIkrBYWh",
routes: [
{ id: 1, name: "/", path: "/", component: "Layout", redirect: "/index", hidden: false, children: [
{ name: "index", path: "/index", meta: { title: "index" }, component: "index/index" },
]},
{ id: 2, name: "/form", path: "/form", component: "Layout", redirect: "/form/index", hidden: false, children: [
{ name: "/form/index", path: "/form/index", meta: { title: "form" }, component: "form/index" }
]},
{ id: 3, name: "/example", path: "/example", component: "Layout", redirect: "/example/tree", meta: { title: "example" }, hidden: false, children: [
{ name: "/tree", path: "/example/tree", meta: { title: "tree" }, component: "tree/index" },
{ name: "/copy", path: "/example/copy", meta: { title: "copy" }, component: "tree/copy" }
] },
{ id: 4, name: "/table", path: "/table", component: "Layout", redirect: "/table/index", hidden: false, children: [
{ name: "/table/index", path: "/table/index", meta: { title: "table" }, component: "table/index" }
] },
{ id: 6, name: "/people", path: "/people", component: "Layout", redirect: "/people/index", hidden: false, children: [
{ name: "/people/index", path: "/people/index", meta: { title: "people" }, component: "people/index" }
] }
]
}
]
export default dynamicUser
由此可以看出,一般登錄后,返回的數據里,包含了一個用戶的姓名,頭像,簡述以及token(username和password只是用以模擬登錄用到的數據,在正常業務流中,后端不可能帶出來的。),routes就是admin管理員和people普通用戶的差異化動態路由了,admin多了一個admin的頁面,而people是沒有的。
其實這里是有多種思路的
有些開發者喜歡完整的靜態路由都在前端里面,然后根據router的meta屬性,寫上對應user的role,登錄的時候,再根據后端返回的權限,去過濾比對權限,把該用戶角色所對應的路由處理好,渲染處理,這也是主流的一種處理方式。這種就等于是把所有的路由和權限業務處理都放在了前端,一旦上線發布后,想要修改就需要重新打包處理,而且不能經由后臺動態新增刪除
例如:
//代碼位置:router/index.js
{
path: '',
component: layout, //整體頁面的布局(包含左側菜單跟主內容區域)
children: [{
path: 'main',
component: main,
meta: {
title: '首頁', //菜單名稱
roles: ['user', 'admin'], //當前菜單哪些角色可以看到
}
}]
}
還有一種解法,就是所有的路由權限等,都交給后端,后端根據前端的賬號密碼,去獲取角色權限,處理路由,丟出就是已經匹配對應角色的路由了。這種寫法前端運算量不會太大,而且易于修改和后期維護以及動態的增刪改查,本文就是以該種形式實現。
2. 模擬用戶登錄,獲取用戶的權限和路由
- 在main.js里面,引入該頁面,用于做路由守衛者
import Vue from "vue"
import App from "./App.vue"
import router from "./router"
import store from "./store"
import ElementUI from "element-ui"
import 'element-ui/lib/theme-chalk/index.css'
import "./router/router-config" // 路由守衛,做動態路由的地方
Vue.config.productionTip = false
Vue.use(ElementUI)
new Vue({
router,
store,
render: (h) => h(App),
}).$mount("#app")
- 登錄
本來我是寫了mock數據,模擬用戶登錄,請求后端角色的接口,奈何mock掛了,
所以我就直接模擬了:
取到用戶角色,存放進localStorage,然后跳轉主頁
- 在這里,由于用了element-ui的form表單提交,所以直接this.$refs.userForm.validate
element-ui的form表單提交文檔!
- 這里的dynamicUser是mock的數據流,一般后端是直接直接返回對應的結果,可由于fastmock容易掛掉,所以就直接手寫mock了。
定義flag用于做登錄校驗,如果循環都找不到對應的username和password的話,就告訴用戶,該賬號密碼錯誤,登錄失敗..可如果有一次是成功的,那么flag就是為!0的,并且返回對應的用戶信息,用戶路由等。。最后還會進行路由的跳轉初始化頁面(首頁),并進行動態路由加載和路由跳轉。
import dynamicUser from "../../mock"
import { Message } from "element-ui"
login() {
this.$refs.userForm.validate(( valid ) => {
if(valid) {
let flag = !1
window.localStorage.removeItem("userInfo")
dynamicUser.forEach(item => {
if(item["username"] == this.user['username'] && item["password"] == this.user['password']) {
flag = !0
Message({ type: 'success', message: "登錄成功", showClose: true, duration: 3000 })
window.localStorage.setItem("userInfo", JSON.stringify(item))
// 這里用catch捕獲錯誤,而且不打印,解釋在下方
this.$router.replace({ path: "/" }).catch(() => {})
}
})
if(!flag) Message({ type: 'warning', message: "賬號密碼錯誤,請重試!", showClose: true, duration: 3000 })
} else return false
})
}
解釋:如果不捕獲catch錯誤,而且不打印的話,就會出現如圖所示的錯誤。
原因:vue-router路由版本更新產生的問題,導致路由跳轉失敗拋出該錯誤,但并不影響程序功能
- 解決方案1:
在使用編程式導航跳轉時,每次使用,后面都跟上.catch方法,捕獲錯誤信息this.$router.push("/xxx").catch(() => {})
- 解決方法2:
全局解決,替換路由的Push和replace方法,放在src/router/index.js中:
const originalPush = VueRouter.prototype.push
VueRouter.prototype.push = function push(location, onResolve, onReject) {
if (onResolve || onReject) return originalPush.call(this, location, onResolve, onReject)
return originalPush.call(this, location).catch(err => err)
}
- 解決方案3:
對vue-router的版本降低到3.0.7,手動修改,然后刪除node_modules,改完再npm install
修改為:
3. (重點)路由守衛者攔截beforeach, 并動態渲染出路由表
1. 在router文件夾下,創建router.config.js文件,用于做路由守衛者的攔截頁面
2. 引入router,Layout,NProgress三個插件
- router說明
router是引用router/index.js里面導出的router
在router/index.js里面, router是new vue-router,相當于vue-router對象
- layout說明
這是頁面的大體框架,具體頁面詳情如下
- NProgress是進度條插件
import router from "./index"
import Layout from "../layout/index"
import NProgress from 'nprogress' // progress bar
import 'nprogress/nprogress.css' // progress bar style
- 在router的beforeEach里面分別有三個參數to, form和next,分別對應著去哪兒,從哪兒來,下一步
- 接下來,根據去哪兒(to), 就需要判斷路由指向是否需要過濾的路由地址數組里,如果在,則直接進入頁面,無需判斷,例如登錄頁面, 注冊頁面, 找回密碼等(具體看業務需求)
const filterRoutes = ["/login"]
if (filterRoutes.indexOf(to.path) !== -1) {
// 如果是無需權限的靜態路由,可以直接跳走
next()
return false
}
- 然后就是進入動態路由主要部分了,首先判斷當前路由棧的數量,如果路由棧的數量等于你在router/index.js里面的靜態路由的數量,那么表明當前仍未加載動態路由,需要處理路由了,反之,則可以讓它直接進入循環
// 由于我目前的教程里面,只是做了一個login的登錄頁面,所以靜態頁面也是僅有一個而已
if (router.options.routes.length == 1) {
// 此處動態加載路由
} else next() // 表明路由已加載,可直接進入頁面
- 當路由未加載時,就需要獲取登錄時緩存的token和路由棧,由于刷新的時候,vuex的數據無法持久化,所以建議最好routes和token都放在緩存storage里面,當然,cookies里面也是可以的,可是這樣的話,瀏覽器一旦關閉,那么下次打開就需要重新登錄了。
// 獲取token和原始路由數組
// 這里需要做空值合并操作,防止路由存在時,可token已失效,然后JSON.parse轉義失敗的情況導致的報錯
const userInfo = JSON.parse(window.localStorage.getItem("userInfo")) ?? ""
// 當token和原始路由都存在的時候
// 進入路由執行路由過濾和跳轉封裝函數
// 否則,跳回登錄頁面
if(userInfo.token && userInfo.routes) onFilterRoutes(to, next, userInfo.routes)
else next({ path: "/login", replace: true })
Tips小知識
空值合并操作符( ?? )
只有當左側為null和undefined時,才會返回右側的數
空值合并操作符(??)是一個邏輯操作符,當左側的操作數為 null或者 undefined時,返回其右側操作數,否則返回左側操作數。
- 當進入路由過濾和跳轉封裝的時候
- 先執行異步請求,確保路由過濾和路徑補全已完成。先把routes傳入遞歸函數(filterASyncRoutes),用于做路徑的補全和Layout的判斷并賦值,并且當routes存在children(子級路由)的時候,路由需要再次回調遞歸函數(filterASyncRoutes),最后并把處理好的路由棧,返回給路由過濾函數
- 根據異步請求返回的routes,進行路由的排序,畢竟當用戶動態處理了路由后,展示出來的順序跟處理時的順序不一致,那就不太好了。
- 路由都處理完成后,把路由循環,并動態添加進router.options.routes里面,而且路由router里面,要使用addRoute(item),把路由一點點添加進路由表里。
- 最后執行路由跳轉,跳回當前需要跳轉的頁面
// 路由拼接
function loadView(view) {
return () => import(`@/views/${ view }`)
}
// 路由過濾和跳轉
async function onFilterRoutes(to, next, e) {
const routes = await filterASyncRoutes(e) // 路由過濾
routes.sort((a, b) => a['id'] - b['id'])
routes.forEach(item => {
router.options.routes.push(item)
router.addRoute(item)
})
next({ ...to, replace: true })
}
// 路由過濾 遍歷路由 轉換為組件對象和路徑
function filterASyncRoutes(data) {
const routes = data.filter(item => {
if(item["component"] === "Layout") item.component = Layout
else item["component"] = loadView(item["component"])
// 路由遞歸,轉換組件對象和路徑
if(item["children"] && item["children"].length > 0) item["children"] = filterASyncRoutes(item.children)
return true
})
return routes
}
tips:
- 為什么使用router.addroute,而不使用router.addRoutes
新版本router.addRoutes已廢棄:使用 router.addRoute() 代替。
官方的解釋是 router.addRoute 接受的是一個路由規則,也就是一個對象,或者接受一個字符串和一個對象。
- 為什么要使用() => import(@/views/${ view })來做路由拼接
懶加載:又叫延時加載,即在需要的時候進行加載,隨用即載
【相關問題】import() webpack4懶加載使用變量報錯解決:https://www.cnblogs.com/chenxi188/p/13662036.html
import和require的區別
node編程中最重要的思想就是模塊化,import和require都是被模塊化所使用。
遵循規范require 是 AMD規范引入方式import是es6的一個語法標準,如果要兼容瀏覽器的話必須轉化成es5的語法調用時間require是運行時調用,所以require理論上可以運用在代碼的任何地方import是編譯時調用,所以必須放在文件開頭本質require是賦值過程,其實require的結果就是對象、數字、字符串、函數等,再把require的結果賦值給某個變量import是解構過程,但是目前所有的引擎都還沒有實現import,我們在node中使用babel支持ES6,也僅僅是將ES6轉碼為ES5再執行,import語法會被轉碼為require
項目&源碼
源碼地址(gitee):https://gitee.com/lemonote/vue2-dynamic-routing
項目地址:https://dynamic.lemonotes.cn/#/login
整體流程走完了,再容易讓人蒙的地方
1. 根據路由進行菜單展示\
代碼位置:/src/Layout/sideBar/sidebaritem.vue,
先看下elementUI菜單組件,把一些基礎的參數先了解一下,
這里我把菜單渲染寫成了一個組件:
用到了遞歸屬性,保證可以生成多級菜單,
我建議不熟悉的,大家用組件先模擬著寫一個包含跳轉功能、icon展示的菜單,然后再看我寫的組件
2. 用戶退出系統
代碼位置:/src/layout/headerTemp/index.vue
退出的時候,記得清除掉存在localStorage的用戶角色,
然后利用this.$router.replace({ path: "/login" })跳轉到登錄頁,
為什么要用location.reload(),這樣會把之前addRoute的路由清除掉,確保下個用戶登陸后,會重新渲染正確的菜單
// 退出登錄
handleLogout(key) {
if(key == "logout") {
window.localStorage.removeItem("userInfo")
Message({ type: 'success', message: "退出登錄", showClose: true, duration: 3000 })
this.$router.replace({ path: "/login" })
location.reload()
}
}
3. 為什么不用vuex
本來確實是打算用vuex來做路由的處理的,可是后來發現,當瀏覽器手動刷新或者被動刷新的時候,vuex無法做數據持久化,簡而言之,就是vuex里面的state的值會被清空,所以為了穩妥起見,我是選擇了緩存storage來處理路由問題。
如有不正確的地方,還望小伙伴指正哈
最后
公眾號:小何成長,佛系更文,都是自己曾經踩過的坑或者是學到的東西
有興趣的小伙伴歡迎關注我哦,我是:何小玍。大家一起進步鴨
總結
以上是生活随笔為你收集整理的vue实现用户登录验证(vue实现登录注册和验证码)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 网络出现问题应该怎么排查故障(如何排查网
- 下一篇: 微软 Edge 浏览器 114 稳定版发