0.项目-注册和登录-页面准备
目标
准备注册和登录页面组件及路由
讲解
新建注册页面组件, 在src/views/register/index.vue, 直接复制标签
<template>
<div>
Reg.vue
</div>
</template>
<script>
export default {
name: 'my-register'
}
</script>
<style lang="less"scoped></style>
新建登录页面组件, 在src/views/login/index.vue, 直接复制标签
<template>
<div>
Login.vue
</div>
</template>
<script>
export default {
name: 'my-login'
}
</script>
<style lang="less"scoped></style>
在src/router/index.js配置路由表
import Vue from'vue'
import VueRouter from'vue-router'
Vue.use(VueRouter)
constroutes= [
{
path: '/reg',
component: () =>import('@/views/register')
},
{
path: '/login',
component: () =>import('@/views/login')
}
]
const router=new VueRouter({
routes
})
export default router
在 App.vue 组件中,定义 <router-view> 如下
可以把之前测试的代码直接覆盖掉
<template>
<router-view></router-view>
</template>
<script>
export default {
name: 'App'
}
</script>
<style lang="less"scoped></style>
启动webpack开发服务器, 然后在页面的地址栏, 手动切换路由地址看是否配置成功
1注册-标签布局和表单校验
目标
完成注册页面, 标签和样式布局
讲解
在src/views/register/index.vue, 初始化注册页面的基础布局,并美化样式,
==先看需求图, 分析后直接复制后阅读==
1 编写基本样式
一 : 如何让一个盒子水平垂直居中:
1行内元素: text-align:center
2 div : margin: 0 auto 只能水平居中,不能垂直居中,不起作用
3 div : 定位 + 偏移
div: 弹性布局
div :子绝父相之后,给子元素div添加上下左右为0,外边距为auto。
参考:
https://blog.csdn.net/LYY_record/article/details/123215386
二: 编写习惯
从外到内,从上到下,从左到右
<template>
<!-- 注册页面的整体盒子 -->
<div class="reg-container">
<!-- 注册的盒子 -->
<div class="reg-box">
<!-- 标题的盒子 -->
<div class="title-box"></div>
<!-- 注册的表单区域 -->
</div>
</div>
</template>
<script>
export default {
name: 'my-register'
}
</script>
<style lang="less" scoped>
.reg-container {
background: url('../../assets/images/login_bg.jpg') center;
background-size: cover;
height: 100vh;
.reg-box {
width: 400px;
height: 335px;
background-color: #fff;
border-radius: 3px;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
padding: 0 30px;
box-sizing: border-box;
.title-box {
height: 60px;
background: url('../../assets/images/login_title.png') center no-repeat;
}
.btn-reg {
width: 100%;
}
}
}
</style>
因为父级没有高度。导致 背景图 不显示
height:100% 往上找,他的父元素么有设置高度
默认body 是没有高度的,在全局样式中设置了
2 编写input
2.2 占位文字,绑定数据
2.3 添加点击事件
2.4 查看页面
label-width 是控制左侧的宽度的。 label都没了,这个应该去掉
修改注册按钮样式:
3表单校验
查找elementUI组件库, 要完成表单组件布局, 并带上基础校验, ==自己分析铺设, 变量可以看, 不可以复制==
规则1: 用户名必须是1-10的大小写字母数字
规则2: 密码必须是6-15的非空字符
规则3: 确认密码必须和密码值一致
<!-- 注册的表单区域 -->
<el-form :model="regForm" :rules="regRules" ref="regRef">
<!-- 用户名 -->
<el-form-item prop="username">
<el-input v-model="regForm.username" placeholder="请输入用户名"></el-input>
</el-form-item>
<!-- 密码 -->
<el-form-item prop="password">
<el-input v-model="regForm.password" type="password" placeholder="请输入密码"></el-input>
</el-form-item>
<!-- 确认密码 -->
<el-form-item prop="repassword">
<el-input v-model="regForm.repassword" type="password" placeholder="请再次确认密码"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" class="btn-reg">注册</el-button>
<el-link type="info">去登录</el-link>
</el-form-item>
</el-form>
<script>
export default {
name: 'my-register',
data () {
// 需要拿到data里边的密码进行校验,所以验证方法写在data里边
const samePwd = (rule, value, callback) => {
if (value !== this.regForm.password) {
// 如果验证失败,则调用 回调函数时,指定一个 Error 对象。
callback(new Error('两次输入的密码不一致!'))
} else {
// 如果验证成功,则直接调用 callback 回调函数即可。
callback()
}
}
return {
// 注册表单的数据对象
regForm: {
username: '',
password: '',
repassword: ''
},
// 注册表单的验证规则对象
regRules: {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{
pattern: /^[a-zA-Z0-9]{1,10}$/,
message: '用户名必须是1-10的大小写字母数字',
trigger: 'blur'
}
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{
pattern: /^\S{6,15}$/,
message: '密码必须是6-15的非空字符',
trigger: 'blur'
}
],
repassword: [
{ required: true, message: '请再次输入密码', trigger: 'blur' },
{ pattern: /^\S{6,15}$/, message: '密码必须是6-15的非空字符', trigger: 'blur' },
{ validator: samePwd, trigger: 'blur' }
]
}
}
}
}
</script>
看数据是否收集到:
注册按钮变大: 加width
.btn-reg {
width: 100%;
}
2 注册-功能实现
目标
完成点击注册按钮校验和注册功能
讲解
核心思想: 注册就是把用户输入的账号和密码做好校验以后, 收集到变量中, 再调用接口发给后台, 后台代码把他们存储到数据库中, 再给前端返回提示
注册按钮, 绑定点击事件
<el-button type="primary" class="btn-reg" @click="regNewUserFn">注册</el-button>
在事件处理函数中, 先执行表单校验
methods: {
// 注册新用户
regNewUserFn () {
// 进行表单预验证
this.$refs.regRef.validate(valid => {
if (!valid) return false
// 尝试拿到用户输入的内容
console.log(this.regForm)
})
}
}
前端准备好了, 准备调用后台接口了, 所以准备接口方法, 在src/api/index.js定义
/**
* 注册接口
* @param {*} param0 { username: 用户名, password: 密码 }
* @returns Promise对象
*/
export const registerAPI = ({ username, password, repassword }) => {
return request({
url: '/api/reg',
method: 'post',
data: {
username,
password,
repassword
}
})
}
在逻辑页面引用接口, 并在注册逻辑中调用, 并使用element绑定在Vue全局属性上的$message弹窗方法
// 注册新用户
regNewUserFn () {
// 进行表单预验证
this.$refs.regRef.validate(async valid => {
if (!valid) return false
// 尝试拿到用户输入的内容
// console.log(this.regForm)
// 1. 调用注册接口
//解构赋值,把axios返回的数据对象里边的data字段对应的值保存到res上
const { data: res } = await registerAPI(this.regForm)
console.log(res)
// 2. 注册失败,提示用户
if (res.code !== 0) return this.$message.error(res.message)
// 3. 注册成功,提示用户
this.$message.success(res.message)
// 4. 跳转到登录页面
this.$router.push('/login')
})
}
3 登录-标签布局和表单校验
目标
在login页面根据需求准备页面标签和样式
讲解
和注册页面差不多, 标签和校验一样, 在src/views/login/index.vue中, 复制如下标签并阅读核对
<template>
<!-- 登录页面的整体盒子 -->
<div class="login-container">
<!-- 登录的盒子 -->
<div class="login-box">
<!-- 标题的盒子 -->
<div class="title-box"></div>
<!-- 登录的表单区域 -->
<el-form :model="loginForm" :rules="loginRules" ref="loginRef">
<!-- 用户名 -->
<el-form-item prop="username">
<el-input v-model="loginForm.username" placeholder="请输入用户名" maxlength="10" minlength="1"></el-input>
</el-form-item>
<!-- 密码 -->
<el-form-item prop="password">
<el-input
v-model="loginForm.password"
type="password"
placeholder="请输入密码"
maxlength="15"
minlength="6"
></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" class="btn-login">登录</el-button>
<el-link type="info">去注册</el-link>
</el-form-item>
</el-form>
</div>
</div>
</template>
<script>
export default {
name: 'my-login',
data () {
return {
// 登录表单的数据对象
loginForm: {
username: '',
password: ''
},
// 登录表单的验证规则对象
loginRules: {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ pattern: /^[a-zA-Z0-9]{1,10}$/, message: '用户名必须是1-10的字母数字', trigger: 'blur' }
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ pattern: /^\S{6,15}$/, message: '密码必须是6-15的非空字符', trigger: 'blur' }
]
}
}
}
}
</script>
<style lang="less" scoped>
.login-container {
background: url('../../assets/images/login_bg.jpg') center;
background-size: cover;
height: 100%;
.login-box {
width: 400px;
height: 270px;
background-color: #fff;
border-radius: 3px;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
padding: 0 30px;
box-sizing: border-box;
.title-box {
height: 60px;
background: url('../../assets/images/login_title.png') center no-repeat;
}
.btn-login {
width: 100%;
}
}
}
</style>
实现注册页面, 点击去登录跳转效果, 在src/views/register/index.vue中, 找到对应标签绑定点击事件跳转路由页面
<el-link type="info" @click="$router.push('/login')">去登录</el-link>
实现登录页面, 点击去注册跳转效果, 在src/views/login/index.vue中, 找到对应标签点击中跳转路由
<el-link type="info" @click="$router.push('/reg')">去注册</el-link>
默认登录页
4登录-功能实现
目标
点击登录按钮, 实现登录逻辑
讲解
核心思想: 通过表单校验, 收集用户输入内容, 调用接口带给后台验证, 返回响应结果, 前端给用户提示结果
为登录按钮绑定点击事件处理函数如下
<el-button type="primary" class="btn-login" @click="loginFn">登录</el-button>
先封装要调用的登录接口, 在src/api/index.js中
/**
* 登录接口
* @param {*} param0 { username: 用户名, password: 密码 }
* @returns Promise对象
*/
export const loginAPI = ({ username, password }) => {
return request({
url: '/api/login',
method: 'post',
data: {
username,
password
}
})
}
在src/view/login/index.vue登录页面, 引入接口方法并, 实现对应事件处理函数逻辑, 校验和调用接口
methods: {
// 登录按钮->点击事件
async loginFn () {
this.$refs.loginRef.validate(async valid => {
if (!valid) return
// 1. 发起登录的请求
const { data: res } = await loginAPI(this.loginForm)
// 2. 登录失败
if (res.code !== 0) return this.$message.error(res.message)
// 3. 登录成功
this.$message.success(res.message)
})
}
}
5 登录-结果存入vuex中
多个页面使用的数据,保存到vuex当中
目标
把登录成功, 后台返回的token字符串存到vuex中
讲解
在src/store/index.js中定义, state里的token变量, 以及更新token的 updateToken
mutation 函数: 想要给state赋值,必须得用mutation
export default new Vuex.Store({
state: {
// 1. 用来存储登录成功之后,得到的 token
token: ''
},
mutations: {
// 2. 更新 token 的 mutation 函数
updateToken(state, newToken) {
state.token = newToken
}
}
})
在src/views/login/index.vue中, 在成功后, 调用vuex里的mutations方法
可以直接调用 / 映射调用
使用mutations 有几种方式?
两种:可以直接调用 / 映射调用
import { mapMutations } from 'vuex'
export default {
// ...其他
methods: {
// 把updateToken映射到当前方法中,映射到组件中
...mapMutations(['updateToken']),
// 登录按钮->点击事件
async loginFn () {
this.$refs.loginRef.validate(async valid => {
if (!valid) return
// 1. 发起登录的请求
const { data: res } = await loginAPI(this.loginForm)
// 2. 登录失败
if (res.code !== 0) return this.$message.error(res.message)
// 3. 登录成功
this.$message.success(res.message)
// 4. 保存到vuex中
this.updateToken(res.token)
})
}
}
}
查看是否保存成功
1登录后获取token, 保存vuex的代码执行思路是?
在登录接口返回响应成功后, 提取后台返回的token字符串, 调用mutations方法, 把值暂存到vuex的state内变量上, 但是仅仅在内存中, 刷新后state里token变量会变成空字符串(又相当于没登录一样)
6持久化存储vuex
vuex 只是内存里边的变量,刷新就没有了
因为一刷新,所有的代码都要重新执行,token又回归了
目标
刷新vuex的值会回归初始化, 如果在保存到vuex时, 它能自动保存到浏览器本地, 默认从浏览器本地取呢?
讲解
自己写localStorage.setItem等 需要一个个写, 很麻烦
这里介绍一个vuex的插件包叫做
vuex-persistedstate@3.2.1
版本(配合vue2使用, 默认最新版是配合vue3使用)
下载此包到当前工程中
yarn add vuex-persistedstate@3.2.1
在src/store/index.js中, 导入并配置
import Vue from 'vue'
import Vuex from 'vuex'
+ import createPersistedState from 'vuex-persistedstate'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
// 1. 用来存储登录成功之后,得到的 token
token: ''
},
mutations: {
// 2. 更新 token 的 mutation 函数
updateToken (state, newToken) {
state.token = newToken
}
},
// 配置为 vuex 的插件
+ plugins: [createPersistedState()]
})
这次再来重新登录, 查看浏览器调试工具vuex和浏览器本地存储位置, 是否自动同步进入
刷新网页看调试工具里vuex的默认值确实从本地取出了默认值, 保证了vuex的持久化
小结
vuex为何要做持久化?
答案:
vuex运行时的值保存在内存里, 如果刷新vuex的变量会变成初始化的,所以让他的默认初始化的值从本地取, 当有人赋值给vuex也同步覆盖式保存到本地一份
7登录-跳转布局页
目标
完成主页的标签和样式以及路由, 然后登录成功跳转
一般都有个框(layout ), 来进行布局。 中间是正常内容显示
讲解
新建页面组件src/views/layout/index.vue, layout布局页面(它右下角包含的是主页), 直接根据需求画面,
==直接复制标签和样式==
<el-container>
<el-header>Header</el-header>
<el-container>
<el-aside width="200px">Aside</el-aside>
<el-main>Main</el-main>
</el-container>
</el-container>
头上: 两个导航栏项
代码:
<template>
<el-container class="main-container">
<!-- 头部区域 -->
<el-header>
<!-- 左侧的 logo -->
<img src="../../assets/images/logo.png" alt="" />
<!-- 右侧的菜单 -->
<el-menu
class="el-menu-top"
mode="horizontal"
background-color="#23262E"
text-color="#fff"
active-text-color="#409EFF"
>
<el-submenu index="1">
<template slot="title">
<!-- 头像 -->
<img src="../../assets/images/logo.png" alt="" class="avatar" />
<span>个人中心</span>
</template>
<el-menu-item index="1-1"><i class="el-icon-s-operation"></i>基本资料</el-menu-item>
<el-menu-item index="1-2"><i class="el-icon-camera"></i>更换头像</el-menu-item>
<el-menu-item index="1-3"><i class="el-icon-key"></i>重置密码</el-menu-item>
</el-submenu>
<el-menu-item index="2"><i class="el-icon-switch-button"></i>退出</el-menu-item>
</el-menu>
</el-header>
<el-container>
<!-- 侧边栏区域 -->
<el-aside width="200px">Aside</el-aside>
<el-container>
<!-- 页面主体区域 -->
<el-main>
Main.vue后台主页
</el-main>
<!-- 底部 footer 区域 -->
<el-footer>© www.itheima.com - 黑马程序员</el-footer>
</el-container>
</el-container>
</el-container>
</template>
<script>
export default {
name: 'my-layout'
}
</script>
<style lang="less" scoped>
.main-container {
height: 100%;
.el-header,
.el-aside {
background-color: #23262e;
}
.el-header {
padding: 0;
display: flex;
justify-content: space-between;
}
.el-main {
overflow-y: scroll;
height: 0;
background-color: #F2F2F2;
}
.el-footer {
background-color: #eee;
font-size: 12px;
display: flex;
justify-content: center;
align-items: center;
}
}
.avatar {
border-radius: 50%;
width: 35px;
height: 35px;
background-color: #fff;
margin-right: 10px;
object-fit: cover;
}
</style>
在src/router/index.js路由中配置规则和组件
{
path: '/',
component: () => import('@/views/layout')
}
在src/views/login/index.vue登录页面, 登录成功后跳转到主页
// 登录成功之后,跳转到后台主页
this.$router.push('/')
8退出登录
目标
完成退出登录的功能, 和业务学习
讲解
核心思想: 退出登录就是清除vuex和本地所有缓存的值, 然后页面强制切换到登录页面
退出登录按钮, 点击事件绑定
<el-menu-item index="2" @click="logoutFn"><i class="el-icon-switch-button"></i>退出</el-menu-item>
实现对应事件处理函数和提示
methods: {
logoutFn () {
// 询问用户是否退出登录
this.$confirm('您确认退出登录吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => {
// TODO:执行退出登录的操作
})
.catch((err) => err)
}
}
执行退出逻辑代码
.then(() => {
// TODO:执行退出登录的操作
// 1. 清空 token
this.$store.commit('updateToken', '')
// 2. 跳转到登录页面
this.$router.push('/login')
})
退出登录的业务逻辑是什么?
答案
就是把vuex和本地的值清空, 然后页面强制跳转到登录页面
为何退出登录不用调用后台接口?
答案
因为我们采用的是前端存储token来表名用户登录的身份, 没有做单点登录(就是多个设备只能保证1个设备登录状态), 所以后端无需记录登录状态所以无接口调用