黑馬程序員前端Vue3小兔鮮電商項目【八】登錄頁面
登錄頁面的主要功能就是表單校驗和登錄登出業務。
賬號密碼
account | password |
---|---|
cdshi0080 | 123456 |
cdshi0081 | 123456 |
cdshi0082 | 123456 |
cdshi0083 | 123456 |
cdshi0084 | 123456 |
cdshi0085 | 123456 |
cdshi0086 | 123456 |
cdshi0087 | 123456 |
cdshi0088 | 123456 |
路由配置
模版代碼
在 src\views\Login\index.vue 中添加登錄頁代碼:
<script setup></script><template><div><header class="login-header"><div class="container m-top-20"><h1 class="logo"><RouterLink to="/">小兔鮮</RouterLink></h1><RouterLink class="entry" to="/">進入網站首頁<i class="iconfont icon-angle-right"></i><i class="iconfont icon-angle-right"></i></RouterLink></div></header><section class="login-section"><div class="wrapper"><nav><a href="javascript:;">賬戶登錄</a></nav><div class="account-box"><div class="form"><el-form label-position="right" label-width="60px"status-icon><el-form-item label="賬戶"><el-input/></el-form-item><el-form-item label="密碼"><el-input/></el-form-item><el-form-item label-width="22px"><el-checkbox size="large">我已同意隱私條款和服務條款</el-checkbox></el-form-item><el-button size="large" class="subBtn">點擊登錄</el-button></el-form></div></div></div></section><footer class="login-footer"><div class="container"><p><a href="javascript:;">關于我們</a><a href="javascript:;">幫助中心</a><a href="javascript:;">售后服務</a><a href="javascript:;">配送與驗收</a><a href="javascript:;">商務合作</a><a href="javascript:;">搜索推薦</a><a href="javascript:;">友情鏈接</a></p><p>CopyRight © 小兔鮮兒</p></div></footer></div>
</template><style scoped lang='scss'>
.login-header {background: #fff;border-bottom: 1px solid #e4e4e4;.container {display: flex;align-items: flex-end;justify-content: space-between;}.logo {width: 200px;a {display: block;height: 132px;width: 100%;text-indent: -9999px;background: url("@/assets/images/logo.png") no-repeat center 18px / contain;}}.sub {flex: 1;font-size: 24px;font-weight: normal;margin-bottom: 38px;margin-left: 20px;color: #666;}.entry {width: 120px;margin-bottom: 38px;font-size: 16px;i {font-size: 14px;color: $xtxColor;letter-spacing: -5px;}}
}.login-section {background: url('@/assets/images/login-bg.png') no-repeat center / cover;height: 488px;position: relative;.wrapper {width: 380px;background: #fff;position: absolute;left: 50%;top: 54px;transform: translate3d(100px, 0, 0);box-shadow: 0 0 10px rgba(0, 0, 0, 0.15);nav {font-size: 14px;height: 55px;margin-bottom: 20px;border-bottom: 1px solid #f5f5f5;display: flex;padding: 0 40px;text-align: right;align-items: center;a {flex: 1;line-height: 1;display: inline-block;font-size: 18px;position: relative;text-align: center;}}}
}.login-footer {padding: 30px 0 50px;background: #fff;p {text-align: center;color: #999;padding-top: 20px;a {line-height: 1;padding: 0 10px;color: #999;display: inline-block;~a {border-left: 1px solid #ccc;}}}
}.account-box {.toggle {padding: 15px 40px;text-align: right;a {color: $xtxColor;i {font-size: 14px;}}}.form {padding: 0 20px 20px 20px;&-item {margin-bottom: 28px;.input {position: relative;height: 36px;>i {width: 34px;height: 34px;background: #cfcdcd;color: #fff;position: absolute;left: 1px;top: 1px;text-align: center;line-height: 34px;font-size: 18px;}input {padding-left: 44px;border: 1px solid #cfcdcd;height: 36px;line-height: 36px;width: 100%;&.error {border-color: $priceColor;}&.active,&:focus {border-color: $xtxColor;}}.code {position: absolute;right: 1px;top: 1px;text-align: center;line-height: 34px;font-size: 14px;background: #f5f5f5;color: #666;width: 90px;height: 34px;cursor: pointer;}}>.error {position: absolute;font-size: 12px;line-height: 28px;color: $priceColor;i {font-size: 14px;margin-right: 2px;}}}.agree {a {color: #069;}}.btn {display: block;width: 100%;height: 40px;color: #fff;text-align: center;line-height: 40px;background: $xtxColor;&.disabled {background: #cfcdcd;}}}.action {padding: 20px 40px;display: flex;justify-content: space-between;align-items: center;.url {a {color: #999;margin-left: 10px;}}}
}.subBtn {background: $xtxColor;width: 100%;color: #fff;
}
</style>
配置路由跳轉
修改 src\views\Layout\components\LayoutNav.vue 文件中【請先登錄】的 a 標簽:
<li><a href="javascript:;" @click="router.push('/login')">請先登錄</a></li>
表單校驗實現
作用:前端提前校驗可以省去一些錯誤的請求提交,為后端節省接口壓力。
ElementPlus 表單組件內置了表單校驗功能,只需要按照組件要求配置必要參數即可(直接看文檔)。ElementPlus表單組件內置了初始的校驗配置,應付簡單的校驗只需要通過配置即可,如果想要定制一些特殊的校驗需求,可以使用自定義校驗規則。
校驗要求
用戶名:不能為空,字段名為 account
密碼:不能為空且為6-14個字符,字段名為 password
同意協議:必選,字段名為 agree
代碼實現
-
按照字段準備表單對象:
// 1.準備表單對象 const form = ref({account: '',password: '',agree: true })
-
按照產品要求準備規則對象:
// 2. 校驗規則對象 const rules = {account: [{ required: true, message: '用戶名不能為空', trigger: 'blur' }],password: [{ required: true, message: '密碼不能為空', trigger: 'blur' },{ min: 6, max: 24, message: '密碼長度要求6-14個字符', trigger: 'blur' }],agree: [{validator: (rule, value, callBack) => {console.log(value)//自定義校驗邏輯// 勾選協議通過,不勾選不通過if (value) {callBack()} else {callBack(new Error('請勾選協議'))}}}] }
-
給表單綁定用戶表單對象和校驗規則:
<el-form label-position="right" :model="form" :rules="rules" label-width="60px" status-icon>... </el-form>
-
指定表單域的校驗字段名:
<el-form-item prop="account" label="賬戶"><el-input /> </el-form-item> <el-form-item prop="password" label="密碼"><el-input /> </el-form-item> <el-form-item prop="agree" label-width="22px"><el-checkbox size="large">我已同意隱私條款和服務條款</el-checkbox> </el-form-item>
-
把表單對象進行雙向綁定:
<el-form-item prop="account" label="賬戶"><el-input v-model="form.account"/>
</el-form-item>
<el-form-item prop="password" label="密碼"><el-input v-model="form.password"/>
</el-form-item>
<el-form-item prop="agree" label-width="22px"><el-checkbox size="large" v-model="form.agree">我已同意隱私條款和服務條款</el-checkbox>
</el-form-item>
統一校驗
在點擊登錄時需要對所有需要校驗的表單進行統一校驗。
-
獲取表單實例:
// 3.獲取 form 實例做統一校驗 const formRef = ref(null)
與表單進行綁定:
<el-form ref="formRef" label-position="right" :model="form" :rules="rules" label-width="60px" status-icon>... </el-form>
-
編寫登錄邏輯:
const doLogin = () => {// 調用實例方法formRef.value.validate(async (valid) => {// valid: 所有表單都通過校驗 才為trueconsole.log(valid)// 以valid做為判斷條件 如果通過校驗才執行登錄邏輯if (valid) {// TODO LOGIN}}) }
-
與登錄按鈕進行綁定:
<el-button size="large" class="subBtn" @click="doLogin">點擊登錄</el-button>
登錄基礎業務實現
基礎思想
- 調用登錄接口獲取用戶信息
- 提示用戶當前是否成功
- 跳轉到首頁
-
新建 src\apis\user.js 文件,編寫登錄 api:
//封裝所有和用戶相關的接口函數 import http from '@/utils/http'export const loginApi = ({ account, password }) => {return http({url: '/login',method: 'POST',data: {account, password}}) }
-
src\views\Login\index.vue 中完善登錄邏輯:
// 3.獲取 form 實例做統一校驗 const router = useRouter() const formRef = ref(null) const doLogin = () => {const { account, password } = form.value// 調用實例方法formRef.value.validate(async (valid) => {// valid: 所有表單都通過校驗 才為trueconsole.log(valid)// 以valid做為判斷條件 如果通過校驗才執行登錄邏輯if (valid) {// TODO LOGINawait loginAPI({ account, password })// 1. 提示用戶ElMessage({ type: 'success', message: '登錄成功' })// 2. 跳轉首頁router.replace({ path: '/' })}}) }
統一錯誤信息提示
在 src\utils\http.js 中的響應攔截器中進行處理:
// axios響應式攔截器
http.interceptors.response.use(res => res.data, e => {// 統一錯誤提示ElMessage({type: 'warning',message: e.response.data.message})return Promise.reject(e)
})
Pinia 管理用戶數據
由于用戶數據的特殊性,在很多組件中都有可能進行共享,共享的數據使用 Pinia 管理會更加方便。Pinia 負責用戶數據相關的 state 和 action,組件中只負責觸發 action 函數并傳遞參數。
-
添加 src\stores\user.js 文件,在其中添加保存用戶數據的方法:
// 管理用戶數據相關 import { defineStore } from 'pinia' import { ref } from 'vue' import { loginAPI } from '@/apis/user'export const useUserStore = defineStore('user', () => {// 1. 定義管理用戶數據的stateconst userInfo = ref({})// 2. 定義獲取接口數據的action函數const getUserInfo = async ({ account, password }) => {const res = await loginAPI({ account, password })userInfo.value = res.result}// 3. 以對象的格式把state和action returnreturn {userInfo,getUserInfo} })
-
替換 src\views\Login\index.vue 中登錄邏輯的原代碼:
import {useUserStore} from '@/stores/user' ... const userStore = useUserStore() // TODO LOGIN await userStore.getUserInfo({ account, password })
Pinia 用戶數據持久化
用戶數據中有一個關鍵的數據叫做 Token(用來標識當前用戶是否登錄),而 Token 持續一段時間才會過期。Pinia 的存儲是基于內存的,刷新就丟失,為了保持登錄狀態就要做到刷新不丟失,需要配合持久化進行存儲。
最終效果:操作 state 時會自動把用戶數據在本地的 localStorage 也存一份,刷新的時候會從 localStorage 中先取。
-
安裝 pinia 持久化插件 persistedstate:
npm i pinia-plugin-persistedstate
-
在 main.js 中注冊 pinia 持久化插件:
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'const app = createApp(App) const pinia = createPinia() //注冊持久化插件 pinia.use(piniaPluginPersistedstate) app.use(pinia)
-
對 store 持久化配置:
export const useUserStore = defineStore('user', () => {... }, {persist: true })
用戶登錄狀態
在首頁根據用戶登錄狀態區分顯示的模塊。
在 src\views\Layout\components\LayoutNav.vue 中獲取 pinia 中存儲的用戶數據:
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
修改模板的 v-if 判斷邏輯:
<!--多模版渲染區分登錄狀態和非登錄狀態-->
<!--適配思路:登錄時顯示第一塊非登錄時顯示第二塊是否有token-->
<template v-if="userStore.userInfo.token"><li><a href="javascript:;"><i class=" iconfont icon-user"></i>{{userStore.userInfo.account}}</a></li>...
</template>
<template v-else>...
</template>
請求攔截器攜帶 token
Token作為用戶標識,在很多個接口中都需要攜帶Token才可以正確獲取數據,所以需要在接口調用時攜帶Token。另外,為了統一控制采取請求攔截器攜帶的方案。
Axiosi請求攔截器可以在接口正式發起之前對請求參數做一些事情,通常Token數據會被注入到請求header中,格式按
照后端要求的格式進行拼接處理。格式如圖:
修改 src\utils\http.js 文件中的請求攔截器,從 pinia 獲取token數據,將 token 存儲到請求的請求頭中:
// axios請求攔截器
htpp.interceptors.request.use(config => {// 1. 從pinia獲取token數據const userStore = useUserStore()// 2. 按照后端的要求拼接token數據const token = userStore.userInfo.tokenif (token) {config.headers.Authorization = `Bearer ${token}`}return config
}, e => Promise.reject(e))
退出登錄實現
基礎思想:
- 清除用戶信息
- 跳轉到登錄頁
-
src\stores\user.js 中新增清除用戶信息方法:
export const useUserStore = defineStore('user', () => {...// 退出時清除用戶信息const clearUserInfo = () => {userInfo.value = {}}// 3. 以對象的格式把state和action returnreturn {userInfo,getUserInfo,clearUserInfo} }, {persist: true })
-
執行退出邏輯,清除用戶信息:
<script setup> import { useUserStore } from '@/stores/userStore' import { useRouter } from 'vue-router' const userStore = useUserStore() const router = useRouter() const confirm = () => {console.log('用戶要退出登錄了')// 退出登錄業務邏輯實現// 1.清除用戶信息 觸發actionuserStore.clearUserInfo()// 2.跳轉到登錄頁router.push('/login') } </script>
Token 失效攔截處理
Token的有效性可以保持一定時間,如果用戶一段時間不做任何操作,Token.就會失效,使用失效的Token再去請求一
些接口,接口就會報401狀態碼錯誤,需要我們做額外處理。
在 src\utils\http.js 中進行處理:
// axios響應式攔截器
http.interceptors.response.use(res => res.data, e => {// 從pinia獲取token數據const userStore = useUserStore()// 統一錯誤提示ElMessage({type: 'warning',message: e.response.data.message})// 401 token 失效處理// 1.清楚本地用戶數據// 2.跳轉登錄頁if(e.response.status===401){userStore.clearUserInfo()router.push('/login')}return Promise.reject(e)
})
本文轉自 https://blog.csdn.net/qq_20185737/article/details/131363742?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522267ecdbd4b79cab0f88ca091b1719024%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=267ecdbd4b79cab0f88ca091b1719024&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2blogfirst_rank_ecpm_v1~rank_v31_ecpm-2-131363742-null-null.nonecase&utm_term=%E5%B0%8F%E5%85%94&spm=1018.2226.3001.4450,如有侵權,請聯系刪除。