项目地址 https://gitee.com/zy4018/vue_zy
登录业务流程
登录业务的相关技术点
登录页面的布局
通过 Element-UI 组件实现布局
这里是打开project里面的vue_shop,在终端中运行以下命令
// 创建分支
git checkout -b login
// 查看分支,带星号表示当前分支
git branch
import Login from './components/Login.vue'
const routes = [
{ path: '/', redirect: '/login' },
{ path: '/login', component: Login },
]
<template>
<div id="app">
App根组件
<!-- 路由占位符 -->
<router-view></router-view>
</div>
</template>
// global.css文件
/* 全局样式表 */
html,
body,
#app {
height: 100%;
margin: 0;
padding: 0;
}
// main.js
//导入全局样式表
import './assets/css/global.css'
// login.vue文件内容
<div class="login_container">
<div class="login_box">
</div>
</div>
<style lang="less" scoped>
.login_container {
background-color: #2b4b6b;
height: 100%;
}
.login_box {
width: 450px;
height: 300px;
background-color: #fff;
border-radius:5px ;
position:absolute;
left:50%;
top: 50%;
transform: translate(-50%,-50%);
}
</style>
// login.vue文件内容
<!-- 头像区域 -->
<div class="avatar_box">
<img src="../assets/logo.png" alt="">
</div>
.avatar_box {
height: 130px;
width: 130px;
border: 1px solid #eee;
border-radius: 50%;
padding: 10px;
box-shadow: 0 0 10px #ddd;
position: absolute;
left: 50%;
transform: translate(-50%, -50%);
background-color: #fff;
//less的嵌套语法
img {
width: 100%;
height: 100%;
border-radius: 50%;
background-color: #eee;
}
}
Element-UI官网
https://element.eleme.cn/#/zh-CN
import Vue from 'vue'
import { Button,Form, FormItem,Input } from 'element-ui'
Vue.use(Button)
Vue.use(Form)
Vue.use(FormItem)
Vue.use(Input)
// Login.vue文件内容
<!-- 登录表单区域 -->
<el-form label-width="0px" class="login_form">
<!-- 用户名 -->
<el-form-item >
<el-input></el-input>
</el-form-item>
<!-- 密码 -->
<el-form-item >
<el-input></el-input>
</el-form-item>
<!-- 按钮区域 -->
<el-form-item class="btns">
<el-button type="primary">登录</el-button>
<el-button type="info">重置</el-button>
</el-form-item>
</el-form>
.login_form {
width: 100%;
position: absolute;
bottom: 0;
padding: 0 20px;
//默认form的box-sizing:content-box
box-sizing:border-box ;
}
.btns {
display: flex;
justify-content: flex-end;
}
https://element.eleme.cn/#/zh-CN/component/input
https://element.eleme.cn/#/zh-CN/component/icon
<!-- 用户名 -->
<el-form-item >
<el-input prefix-icon="el-icon-s-custom"></el-input>
</el-form-item>
<!-- 密码 -->
<el-form-item >
<el-input prefix-icon="el-icon-lock"></el-input>
</el-form-item>
阿里字体图标库的使用
希望把用户名和密码对应的值可以自动绑定到数据源上
// 1. 为el-form添加:model属性绑定,指向一个数据对象
// 2. 为每一个表单项通过v-model绑定到数据对象中对应的属性
<el-form :model="loginForm">
<el-input v-model="loginForm.username"></el-input>
<el-input v-model="loginForm.password" type="password"></el-input>
</el-form>
export default {
data () {
return {
// 登录表单的数据绑定对象
loginForm: {
username: 'admin',
password: '123456'
}
}
}
}
https://element.eleme.cn/#/zh-CN/component/form
//1.为el-form属性绑定一个校验规则对象loginFormRules
//2.在校验规则对象之中定义属性username 和 password
//3.在表单item项中通过prop指定校验规则属性
export default {
data() {
return {
// 表单验证规则对象
loginFormRules: {
//验证用户名是否合法
username: [
{ required: true, message: '请输入登录名称', trigger: 'blur' },
{ min: 3, max: 8, message: '长度在 3 到 8 个字符', trigger: 'blur' },
],
// 验证密码是否合法
password: [
{ required: true, message: '请输入登录密码', trigger: 'blur' },
{ min: 6, max: 15, message: '长度在 6 到 15 个字符', trigger: 'blur' },
],
},
}
},
}
methods: {
// 点击重置按钮,重置登录表单
resetLoginForm () {
// console.log(this) // VueComponent
this.$refs.loginFormRef.resetFields()
}
}
import axios from 'axios'
// 配置请求的根路径
axios.defaults.baseURL = 'http://127.0.0.1:8888/api/private/v1/'
Vue.prototype.$http = axios
<el-button type="primary" @click="login">登录</el-button>
https://element.eleme.cn/#/zh-CN/component/message
import { Message } from 'element-ui'
Vue.prototype.$message = Message // 弹框提示组件挂在在Vue原型上
login() {
// valid是一个布尔值,如果表单格式验证合法就为true
//先判断valid是否为true,如果不等于true就直接返回,等于true发起请求
this.$refs.loginFormRef.validate(async (valid) => {
if (!valid) return
// 发起请求,方式是post,并且携带参数
// data属性结构赋值,重命名为res
const { data: res } = await this.$http.post('login', this.loginForm)
if (res.meta.status != 200) return this.$message.error('登陆失败')
this.$message.success('登陆成功')
// 将登录成功之后的token保存到客户端的sessionStorage中(会话期间的存储机制)(所以不放在localStorage)
// 项目中除了登录之外的其他API接口,必须在登录之后才能访问
window.sessionStorage.setItem('token',res.data.token)
// 通过编程式导航跳转到后台主页,路由地址是/home
this.$router.push('/home')
})
},
import Home from './components/Home.vue'
const routes = [
{ path: '/home', component: Home, }
]
如果用户没有登录,但是直接通过URL访问有特定权限的页面,需要重新导航到登录页面
// 挂载路由导航守卫
router.beforeEach((to, from, next) => {
// to 将要访问的路径
// from 代表从哪个路径跳转而来
// next 是一个函数, 表示放行 next()放行 next('/login')强制跳转
if (to.path === '/login') return next() // 用户访问登录页,直接放行
// 获取token
const tokenStr = window.sessionStorage.getItem('token')
if (!tokenStr) return next('/login') // 没有token, 强制跳转到登录页
next() // 否则(有token)直接放行
})
退出功能实现原理
基于token的方式实现退出比较简单,只需要销毁本地的token即可。
这样,后续的请求就不会携带token,必须重新登录生成一个新的token之后才可以访问页面
//清空token
window.sessionStorage.clear()
//跳转到登录页
this.$router.push('/login')
具体操作 Home.vue
<template>
<div>
<el-button type="info" @click="goout">退出</el-button>
</div>
</template>
export default {
methods:{
goout(){
window.sessionStorage.clear()
this.$router.push('/login')
}
}
}
git status //查看文件状态
git add . //代码添加到缓存区
git commit -m "完成了登录功能" //提交到本地库
git branch //查看分支
git checkout master //切换分支到主分支上
git merge login //在主分支上合并login分支的代码
git push //推送到码云中
// 将login分支也推送到码云中
git checkout login //先切换到login分支
git push -u origin login //推送
如果推送失败,没反应,可以先把仓库和项目再关联一下
git remote add origin https://gitee.com/****/vue_zy.git
https://element.eleme.cn/#/zh-CN/component/container
要先在 .plugins/element.js中引入样式
import { Container,Header,Aside,Main } from 'element-ui'
Vue.use(Container)
Vue.use(Header)
Vue.use(Aside)
Vue.use(Main)
Home.vue内容
<template>
<el-container class="home-container">
//头部区域
//每一个element-UI中提供的组件名称就是类名,即el-header就是类名
<el-header>Header <el-button type="info" @click="goout">退出</el-button>
</el-header>
//页面主体区域
<el-container>
// 侧边栏
<el-aside width="200px">Aside</el-aside>
// 右侧内容主体
<el-main>Main</el-main>
</el-container>
</el-container>
</template>
<style lang="less" scoped>
.home-container {
height: 100%;
}
.el-header {
background-color: #373D41;
}
.el-aside {
background-color: #333744;
}
.el-main {
background-color: #eaedf1;
}
</style>
```html
```cpp
```css
"../assets/heima.png" alt="">
电商后台管理系统
"info" @click="goout">退出
.el-header {
background-color: #373D41;
display: flex;
//左右贴边对齐
justify-content: space-between;
//上下居中
align-items: center;
//字体
color:#fff;
font-size: 20px;
//嵌套子选择器
> div {
display: flex;
align-items: center;
span {
margin-left: 15px;
}
}
//头部区域
<el-header>
<div>
<img src="../assets/heima.png" alt="">
<span>电商后台管理系统</span>
</div>
<el-button type="info" @click="goout">退出</el-button>
</el-header>
.el-header {
background-color: #373D41;
display: flex;
//flex布局左右贴边对齐
justify-content: space-between;
//flex布局上下居中
align-items: center;
//字体
color:#fff;
font-size: 20px;
//嵌套子选择器
> div {
display: flex;
align-items: center;
span {
margin-left: 15px;
}
}
}
https://element.eleme.cn/#/zh-CN/component/menu
// 侧边栏菜单区域
<el-menu background-color="#333744" text-color="#fff" active-text-color="#ffd04b">
//一级菜单
<el-submenu index="1">
//一级菜单的模板区域
<template slot="title">
//图标
<i class="el-icon-location"></i>
//文本
<span>一级菜单</span>
</template>
//二级菜单
<el-menu-item index="1-4-1">
<template slot="title">
<i class="el-icon-location"></i>
<span>二级菜单</span>
</template>
</el-menu-item>
</el-submenu>
</el-menu>
通过axios请求拦截器添加token验证,保证拥有获取数据的权限;
需要授权的 API ,必须在请求头中使用 Authorization字段提供 token 令牌;
后台除了登录接口之外,都需要token权限验证,我们可以通过添加axios请求拦截器来添加token,以保证拥有获取数据的权限;
在main.js中添加代码,在将axios挂载到vue原型之前添加下面的代码
上面是console.log(result)的左侧菜单的数据,可以看到打印出来是一个对象;
meta显示获取菜单列表成功;data是所有数据,总共5个一级菜单,第一个一级菜单通过children属性嵌套了自己的二级菜单
data() {
return {
//拿到数据之后,为了在页面中渲染出来,应该把获取到的数据立即挂载到data里面
//左侧菜单数据
menulist: [],
}
},
//页面加载之前就要获取数据,定义生命周期函数
created() {
this.getMenuList()
},
methods: {
//获取所有菜单
async getMenuList() {
const { data: result } = await this.$http.get('menus')
//如果获取数据失败,就弹出错误消息,并且错误消息是保存在result.meta.msg里面的
if (result.meta.status !== 200) return this.$message.error(result.meta.msg)
//如果成功就赋值
this.menulist = result.data
console.log(result)
},
},
//一级菜单
<el-submenu :index="item.id + ''" v-for="item in menulist" :key="item.id">
//一级菜单的模板区域
<template slot="title">
<i class="el-icon-location"></i>
<span>{{item.authName}}</span>
</template>
<!-- 二级菜单 -->
<el-menu-item :index="subItem.id + '' " v-for="subItem in item.children" :key="subItem.id">
<template slot="title">
<i class="el-icon-location"></i>
<span>{{subItem.authName}}</span>
</template>
</el-menu-item>
</el-submenu>
通过更改el-menu的active-text-color属性可以设置侧边栏菜单中点击的激活项的文字颜色
<el-menu background-color="#333744" text-color="#fff" active-text-color="#409EFF">
在data中添加一个iconsObj,然后将图标类名进行双向数据绑定
iconsObj: {
125: 'el-icon-s-custom',
103: 'el-icon-s-check',
101: 'el-icon-s-goods',
102: 'el-icon-s-order',
145: 'el-icon-s-marketing',
},
一级菜单图标绑定iconsObj中的数据:
<i :class="iconsObj[item.id]"></i>
为了保持左侧菜单每次只能打开一个,显示其中的子菜单,我们可以在el-menu中添加一个属性unique-opened
<el-menu unique-opened>
或者也可以数据绑定进行设置(此时true认为是一个bool值,而不是字符串)
:unique-opened=“true”
解决边框不对齐问题
.el-menu {
border-right: none;
}
//三元运算,当isCollapse 变成true,折叠起来的时候,侧边栏宽度变成60px
<el-aside :width="isCollapse ? '60px':'200px'">
<div class="toggle-button" @click ="toggleCollapse"> ||| </div>
// 侧边栏菜单区域
// collapse 是否水平折叠收起菜单,默认是false,不折叠
// collapse-transition 是否开启折叠动画,默认是true开启,要关闭
<el-menu :collapse="isCollapse" :collapse-transition='false'>
//点击按钮,切换菜单的折叠与展开
data() {
return {
isCollapse:false //不折叠
}
},
methods: {
toggleCollapse(){
this.isCollapse=!this.isCollapse //取反
}
}
.toggle-button {
background-color: #4A5064;
font-size: 10px;
line-height: 24px;
color: #fff;
text-align: center;
letter-spacing: 0.2em;
cursor: pointer;
}
import Welcome from '../components/Welcome.vue'
const routes = [
{
path: '/home',
component: Home,
redirect: '/welcome',
children: [{ path: '/welcome', component: Welcome }]
}
]
<!-- 右侧内容主体 -->
<el-main>
<router-view></router-view>
</el-main>
需要将所有的侧边栏二级菜单都改造成子级路由链接,我们只需要将el-menu的router属性设置为true就可以了,此时当我们点击二级菜单的时候,就会根据菜单的index属性进行路由跳转,如: /110
<!-- 侧边栏菜单区域 -->
<el-menu background-color="#333744" text-color="#fff" active-text-color="#409EFF" unique-opened
:collapse="isCollapse" :collapse-transition='false' :router="true">
使用index id来作为跳转路径不合适,我们可以重新绑定index的值为 :index=“’/’+subItem.path”
<!-- 二级菜单 -->
<el-menu-item :index=" '/'+ subItem.path " v-for="subItem in item.children" :key="subItem.id">