当前位置: 首页 > news >正文

【vue3】黑马程序员前端Vue3小兔鲜电商项目【八】

黑马程序员前端Vue3小兔鲜电商项目【八】登录页面

image-20230624165512735

登录页面的主要功能就是表单校验和登录登出业务。

账号密码

accountpassword
cdshi0080123456
cdshi0081123456
cdshi0082123456
cdshi0083123456
cdshi0084123456
cdshi0085123456
cdshi0086123456
cdshi0087123456
cdshi0088123456

路由配置

模版代码

在 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 &copy; 小兔鲜儿</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. 按照字段准备表单对象:

    // 1.准备表单对象
    const form = ref({account: '',password: '',agree: true
    })
  2. 按照产品要求准备规则对象:

    // 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('请勾选协议'))}}}]
    }
  3. 给表单绑定用户表单对象和校验规则:

    <el-form label-position="right" :model="form" :rules="rules" label-width="60px" status-icon>...
    </el-form>
  4. 指定表单域的校验字段名:

    <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>
  5. 把表单对象进行双向绑定:

<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>
统一校验

在点击登录时需要对所有需要校验的表单进行统一校验。

  1. 获取表单实例:

    // 3.获取 form 实例做统一校验
    const formRef = ref(null)

    与表单进行绑定:

    <el-form ref="formRef" label-position="right" :model="form" :rules="rules" label-width="60px" status-icon>...
    </el-form>
  2. 编写登录逻辑:

    const doLogin = () => {// 调用实例方法formRef.value.validate(async (valid) => {// valid: 所有表单都通过校验  才为trueconsole.log(valid)// 以valid做为判断条件 如果通过校验才执行登录逻辑if (valid) {// TODO LOGIN}})
    }
  3. 与登录按钮进行绑定:

     <el-button size="large" class="subBtn" @click="doLogin">点击登录</el-button>

登录基础业务实现

基础思想

  1. 调用登录接口获取用户信息
  2. 提示用户当前是否成功
  3. 跳转到首页
  1. 新建 src\apis\user.js 文件,编写登录 api:

    //封装所有和用户相关的接口函数
    import http from '@/utils/http'export const loginApi = ({ account, password }) => {return http({url: '/login',method: 'POST',data: {account, password}})
    }
  2. 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 函数并传递参数。

  1. 添加 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}
    })
  2. 替换 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 中先取。

  1. 安装 pinia 持久化插件 persistedstate:

    npm i pinia-plugin-persistedstate  
  2. 在 main.js 中注册 pinia 持久化插件:

    import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'const app = createApp(App)
    const pinia = createPinia()
    //注册持久化插件
    pinia.use(piniaPluginPersistedstate)
    app.use(pinia)
  3. 对 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中,格式按
照后端要求的格式进行拼接处理。格式如图:

image-20230624164604499

修改 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))

退出登录实现

基础思想:

  1. 清除用户信息
  2. 跳转到登录页
  1. src\stores\user.js 中新增清除用户信息方法:

    export const useUserStore = defineStore('user', () => {...// 退出时清除用户信息const clearUserInfo = () => {userInfo.value = {}}// 3. 以对象的格式把state和action returnreturn {userInfo,getUserInfo,clearUserInfo}
    }, {persist: true
    })
  2. 执行退出逻辑,清除用户信息:

    <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,如有侵权,请联系删除。

相关文章:

  • Flowable7.x学习笔记(十九)归还我的待办
  • ARM寻址方式
  • 【时时三省】(C语言基础)怎样定义和引用一维数组
  • 【Redis】Redis常用命令
  • 排序功法入门指南【江湖算法笔记】
  • 13.Excel:分列
  • 【论文阅读】LLMOPT:一种提升优化泛化能力的统一学习框架
  • Cona编译问题
  • 工程师 - What is EMF?
  • 工程师 - 小米汽车尾部主动扩散器
  • 文章记单词 | 第64篇(六级)
  • 湖仓一体架构解析:如何平衡数据灵活性与分析性能?
  • 五一作业-day01
  • 从入门到登峰-嵌入式Tracker定位算法全景之旅 Part 3 |混合定位实战:Wi-Fi RTT / LoRa / BLE RSSI AoA 多源融合
  • Python3与Dubbo3.1通讯解决方案(dubbo-python)
  • java技术总监简历模板
  • device_create_sys_dev_entry
  • YOLOv7细节解读
  • 虚函数 vs 纯虚函数 vs 静态函数(C++)
  • 在Qt Creator中使用CUDA
  • 外交部就习近平主席将应邀对俄罗斯进行国事访问并出席纪念苏联伟大卫国战争胜利80周年庆典答问
  • 南京大屠杀幸存者刘贵祥去世,享年95岁
  • 人民日报评论员:把造福人民作为根本价值取向
  • 从“土”到“潮”,唢呐何以“圈粉”年轻人
  • 国际著名学者Charles M. Lieber全职受聘清华深圳国际研究生院
  • 印度宣布即日起对所有巴基斯坦航班关闭领空