01. 登录状态显示
前端的显示中,需要对登录前后两种状态进行区分。
components/Header.vue,先实现根据登录与否差异化显示。
子组件代码:
<template>
<div class="header-box">
<div class="header">
<div class="content">
<div class="logo">
<router-link to="/"><img src="../assets/logo.svg" alt=""></router-link>
</div>
<ul class="nav">
<li v-for="nav in nav.header_nav_list">
<a :href="nav.link" v-if="nav.is_http">{{nav.title}}</a>
<router-link :to="nav.link" v-else>{{nav.title}}</router-link>
</li>
</ul>
<div class="search-warp">
<div class="search-area">
<input class="search-input" placeholder="请输入关键字..." type="text" autocomplete="off">
<div class="hotTags">
<router-link to="/search/?words=Vue" target="_blank" class="">Vue</router-link>
<router-link to="/search/?words=Python" target="_blank" class="last">Python</router-link>
</div>
</div>
<div class="showhide-search" data-show="no"><img class="imv2-search2" src="../assets/search.svg" /></div>
</div>
<div class="login-bar logined-bar" v-show="state.is_login">
<div class="shop-cart ">
<img src="../assets/cart.svg" alt="" />
<span><router-link to="/cart">购物车</router-link></span>
</div>
<div class="login-box ">
<router-link to="">我的课堂</router-link>
<el-dropdown>
<span class="el-dropdown-link">
<el-avatar class="avatar" size="50" src="https://fuguangapi.oss-cn-beijing.aliyuncs.com/avatar.jpg"></el-avatar>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item :icon="UserFilled">学习中心</el-dropdown-item>
<el-dropdown-item :icon="List">订单列表</el-dropdown-item>
<el-dropdown-item :icon="Setting">个人设置</el-dropdown-item>
<el-dropdown-item :icon="Position">注销登录</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
<div class="login-bar" v-show="!state.is_login">
<div class="shop-cart full-left">
<img src="../assets/cart.svg" alt="" />
<span><router-link to="/cart">购物车</router-link></span>
</div>
<div class="login-box full-left">
<span @click="state.show_login=true">登录</span>
/
<span>注册</span>
</div>
</div>
</div>
</div>
</div>
<el-dialog :width="600" v-model="state.show_login">
<Login @successHandle="login_success"></Login>
</el-dialog>
</template>
<script setup>
import {UserFilled, List, Setting, Position} from '@element-plus/icons-vue'
import Login from "./Login.vue"
import nav from "../api/nav";
import {reactive} from "vue";
const state = reactive({
is_login: true, // 登录状态
show_login: false,
})
// 请求头部导航列表
nav.get_header_nav().then(response=>{
nav.header_nav_list = response.data
})
// 用户登录成功以后的处理
const login_success = ()=>{
state.show_login = false
}
</script>
新增样式:
<style scoped>
/* 登陆后状态栏 */
.logined-bar{
margin-top: 0;
height: 72px;
line-height: 72px;
}
.header .logined-bar .shop-cart{
height: 32px;
line-height: 32px;
}
.logined-bar .login-box{
height: 72px;
line-height: 72px;
position: relative;
}
.logined-bar .el-avatar{
float: right;
width: 50px;
height: 50px;
position: absolute;
top: -10px;
left: 10px;
transition: transform .5s ease-in .1s;
}
.logined-bar .el-avatar:hover{
transform: scale(1.3);
}
</style>
如果图标没有显示,可以采用安装以下组件:
yarn add @element-plus/icons-vue
02. 使用Vuex保存状态
Vuex官方地址:https://next.vuex.vuejs.org/zh/index.html
cd ~/Desktop/fuguang/fuguang_web
# 在客户端项目根目录下执行安装命令
yarn add vuex@next
Vuex初始化,是在src目录下创建store目录,store目录下创建index.js文件对vuex进行初始化:
import {createStore} from "vuex"
// 实例化一个vuex存储库
export default createStore({
state () { // 数据存储位置,相当于组件中的data
return {
user: {}
}
},
mutations: { // 操作数据的方法,相当于methods
login (state, user) { // state 就是上面的state state.user 就是上面的数据
state.user = user
}
}
})
main.js中注册vuex,代码:
import { createApp } from 'vue'
import App from './App.vue'
import router from "./router";
import store from "./store"
import 'element-plus/theme-chalk/index.css'
createApp(App).use(router).use(store).mount('#app')
在components/Login.vue子组件中,登录成功以后,
从JWT中获取载荷信息,记录用户信息到vuex中。
<script setup>
import user from "../api/user"
import { ElMessage } from 'element-plus'
const emit = defineEmits(["login_success",])
import {useStore} from "vuex"
const store = useStore()
const loginHandler = ()=>{
// 登录处理
if(user.username.length<1 || user.password.length<1){
// 错误提示
ElMessage.error('错了哦,用户名或密码不能为空!');
return false // 在函数/方法中,可以阻止代码继续往下执行
}
// 发送请求
user.user_login({
username: user.username,
password: user.password
}).then(response=>{
// 保存token,并根据用户的选择,是否记住密码
localStorage.removeItem("token")
sessionStorage.removeItem("token")
if(user.remember){ // 判断是否记住登录状态
// 记住登录
localStorage.token = response.data.token
}else{
// 不记住登录,关闭浏览器以后就删除状态
sessionStorage.token = response.data.token
}
// vuex存储用户登录信息,保存token,并根据用户的选择,是否记住密码
let payload = response.data.token.split(".")[1] // 载荷
let payload_data = JSON.parse(atob(payload)) // 用户信息
console.log(payload_data)
store.commit("login", payload_data)
// 成功提示
ElMessage.success("登录成功!")
// 关闭登录弹窗,对外发送一个登录成功的信息
user.account = ""
user.password = ""
user.mobile = ""
user.code = ""
user.remember = false
emit("login_success")
}).catch(error=>{
console.log(error);
ElMessage.error("登录异常!")
})
}
</script>
记录下来了以后,我们就可以直接components/Header.vue中读取Vuex中的用户信息。
<template>
<div class="header-box">
<div class="header">
<div class="content">
<div class="logo">
<router-link to="/"><img src="../assets/logo.png" alt=""></router-link>
</div>
<ul class="nav">
<li v-for="item in nav.header_nav_list">
<a v-if="item.is_http" :href="item.link">{{item.title}}</a>
<router-link v-else :to="item.link">{{item.title}}</router-link>
</li>
</ul>
<div class="search-warp">
<div class="search-area">
<input class="search-input" placeholder="请输入关键字..." type="text" autocomplete="off">
<div class="hotTags">
<router-link to="/search/?words=Vue" target="_blank" class="">Vue</router-link>
<router-link to="/search/?words=Python" target="_blank" class="last">Python</router-link>
</div>
</div>
<div class="showhide-search" data-show="no"><img class="imv2-search2" src="../assets/search.svg" /></div>
</div>
<div class="login-bar logined-bar" v-if="store.state.user.user_id">
<div class="shop-cart ">
<img src="../assets/cart.svg" alt="" />
<span><router-link to="/cart">购物车</router-link></span>
</div>
<div class="login-box ">
<router-link to="">我的课堂</router-link>
<el-dropdown>
<span class="el-dropdown-link">
<el-avatar class="avatar" size="50" src="https://luffycityapi.oss-cn-beijing.aliyuncs.com/avatar.jpg"></el-avatar>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item :icon="UserFilled" >学习中心</el-dropdown-item>
<el-dropdown-item :icon="List">订单列表</el-dropdown-item>
<el-dropdown-item :icon="Setting">个人设置</el-dropdown-item>
<el-dropdown-item :icon="Position">注销登录</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
<div class="login-bar" v-else>
<div class="shop-cart full-left">
<img src="../assets/cart.svg" alt="" />
<span><router-link to="/cart">购物车</router-link></span>
</div>
<div class="login-box full-left">
<span @click="state.show_login=true">登录</span>
/
<span>注册</span>
</div>
</div>
</div>
</div>
</div>
<el-dialog :width="600" v-model="state.show_login">
<Login @successHandle="login_success"></Login>
</el-dialog>
</template>
<script setup>
import Login from "./Login.vue"
import nav from "../api/nav"
import {reactive} from "vue"
import {useStore} from "vuex"
const store = useStore()
const state = reactive({
show_login: false,
})
nav.get_header_nav().then(response=>{
nav.header_nav_list = response.data;
})
// 用户登录成功以后的处理
const login_success = (token)=>{
state.show_login = false
}
</script>
因为vuex默认是保存数据在内存中的,
基于浏览器开发的网页,如果刷新网页会存在数据丢失的情况。
我们可以把store数据永久存储到localStorage中。
这里就需要使用插件vuex-persistedstate来实现。
在前端项目的根目录下执行安装命令
cd ~/Desktop/fuguang/fuguang_web
yarn add vuex-persistedstate
在vuex的store/index.js文件中导入此插件。
import {createStore} from "vuex"
import createPersistedState from "vuex-persistedstate"
// 实例化一个vuex存储库
export default createStore({
// 调用永久存储vuex数据的插件,localstorage里会多一个名叫vuex的Key,里面就是vuex的数据
plugins: [createPersistedState()],
state(){ // 相当于组件中的data,用于保存全局状态数据
return {
user: {}
}
},
getters: {
getUserInfo(state){
// 从jwt的载荷中提取用户信息
let now = parseInt( (new Date() - 0) / 1000 );
if(state.user.exp === undefined) {
// 没登录
state.user = {}
localStorage.token = null;
sessionStorage.token = null;
return null
}
if(parseInt(state.user.exp) < now) {
// 过期处理
state.user = {}
localStorage.token = null;
sessionStorage.token = null;
return null
}
return state.user;
}
},
mutations: { // 相当于组件中的methods,用于操作state全局数据
login(state, payload){
state.user = payload; // state.user 就是上面声明的user
}
}
})
完成了登录功能以后,
我们要防止用户翻墙访问需要认证身份的页面时,
可以基于vue-router的导航守卫来完成。
src/router/index.js,代码:
import {createRouter, createWebHistory} from 'vue-router'
import store from "../store";
// 路由列表
const routes = [
{
meta:{
title: "首页-浮光在线",
keepAlive: true
},
path: '/', // uri访问地址
name: "Home",
component: ()=> import("../views/Home.vue")
},
{
meta:{
title: "用户登录-浮光在线",
keepAlive: true
},
path:'/login', // uri访问地址
name: "Login",
component: ()=> import("../views/Login.vue")
},{
meta:{
title: "luffy2.0-个人中心",
keepAlive: true,
authorization: true,
},
path: '/user',
name: "User",
component: ()=> import("../views/User.vue"),
},
]
// 路由对象实例化
const router = createRouter({
// history, 指定路由的模式
history: createWebHistory(),
// 路由列表
routes,
});
// 导航守卫
router.beforeEach((to, from, next)=>{
document.title=to.meta.title
// 登录状态验证
if (to.meta.authorization && !store.getters.getUserInfo) {
next({"name": "Login"})
}else{
next()
}
})
// 暴露路由对象
export default router
src/views/User.vue,代码:
<template>
<div>用户中心</div>
</template>
<script>
export default {
name: "User"
}
</script>
<style scoped>
</style>
03. 退出登录功能
退出登录时,需要将状态管理中的数据清除,同时清除浏览器的本地存储。
在vuex的store/index.js中编写一个【登录注销】的方法logout,
代码:
import {createStore} from "vuex"
import createPersistedState from "vuex-persistedstate"
// 实例化一个vuex存储库
export default createStore({
// 调用永久存储vuex数据的插件,localstorage里会多一个名叫vuex的Key,里面就是vuex的数据
plugins: [createPersistedState()],
state(){ // 相当于组件中的data,用于保存全局状态数据
return {
user: {}
}
},
getters: {
getUserInfo(state){
let now = parseInt( (new Date() - 0) / 1000 );
if(state.user.exp === undefined) {
// 没登录
state.user = {}
localStorage.token = null;
sessionStorage.token = null;
return null
}
if(parseInt(state.user.exp) < now) {
// 过期处理
state.user = {}
localStorage.token = null;
sessionStorage.token = null;
return null
}
return state.user;
}
},
mutations: { // 相当于组件中的methods,用于操作state全局数据
login(state, payload){
state.user = payload; // state.user 就是上面声明的user
},
logout(state){ // 退出登录
state.user = {}
localStorage.token = null;
sessionStorage.token = null;
}
}
})
在用户点击头部登录栏的注销登录时绑定登录注销操作。components/Header.vue
,
代码:
<el-dropdown-item :icon="Position" @click="logout">注销登录</el-dropdown-item>
<script setup>
import {UserFilled, List, Setting, Position} from '@element-plus/icons-vue'
import Login from "./Login.vue"
import nav from "../api/nav";
import {reactive} from "vue";
import {useStore} from "vuex"
const store = useStore()
const state = reactive({
show_login: false,
})
// 请求头部导航列表
nav.get_header_nav().then(response=>{
nav.header_nav_list = response.data
})
// 用户登录成功以后的处理
const login_success = ()=>{
state.show_login = false
}
// 登录注销的处理
const logout = ()=>{
store.commit("logout");
}
</script>