温州公司做网站微信社群营销
目录
- 登录认证与退出登录
- 登录认证实现步骤
- 第一步、登录路由配置
- 第二步、实现登录页效果
- 第三步、Pinia 管理认证状态
- 第四步、提交登录表单
- 第六步、axios在请求头上添加认证token
- 退出登录实现步骤
- 第一步、调用接口api方法 logout
- 第二步、pinia 中实现退出功能
- 第三步、触发退出登录事件
登录认证与退出登录
登录认证实现步骤
第一步、登录路由配置
关于 /login
登录组件路由配置:在 src/router/index.ts
新定义一个 fullscreenRoutes
指定全屏显示路由(登录页),不作用到 layout
布局的渲染出口。
import { createRouter, createWebHistory } from 'vue-router'
import type { RouteRecordRaw } from 'vue-router'// 扩展 RouteMeta 接口,因为 Vue-Router 的配置路由对象的 meta 属性有限,所以需要扩展
declare module 'vue-router' {interface RouteMeta {title?: string; // 菜单标题icon?: string; // 图标linkTo?: string; // 外链地址cache?: boolean; //是否缓存:true缓存,false不缓存,会将 name 值用于 <keep-alive>的includes上hidden?: boolean; // 是否在菜单中显示:true显示,false隐藏isBreadcrumb?: boolean; // 是否显示到面包屑,默认或true会显示,false不显示。}
}
/*** 路由表配置数组(单独抽取,后面扩展后端动态加载)*/
export const dynamicRoutes: RouteRecordRaw[] = [... 省略]
/**
* 全屏显示路由,不作用到 layout 布局渲染出口。
* (后端路由控制:后端配置菜单数据中不需要下面的菜单项)
*/
export const fullscreenRoutes: RouteRecordRaw[] = [{path: '/login',name: 'Login',component: () => import('@/views/auth/login.vue'),meta: {title: '登录',hidden: true,},},
];const router = createRouter({history: createWebHistory(import.meta.env.BASE_URL),routes: [...dynamicRoutes, ...fullscreenRoutes]
})export default router
第二步、实现登录页效果
-
声明登录表单的数据类型
// 登录信息 declare interface LoginData {username: string;password: string; }
-
实现静态页面,静态页面代码如下,直接使用即可
<script setup lang="ts"> import { ref, reactive, toRefs } from 'vue'; import { isUsername, isPassword } from '@/utils/validate'; import { useAuthStore } from '@/stores/auth'; import { useRouter, useRoute } from 'vue-router';const authStore = useAuthStore();const router = useRouter(); const route = useRoute();const formRef = ref();const state = reactive({loading: false,isRemember: true, // 是否记住密码loginData: {username: '',password: '',} as LoginData });const { loading, isRemember, loginData } = {...toRefs(state)};/*** 校验用户名是否合法* @param rule * @param value * @param callback */ function checkUsername(rule: any, value: string, callback: Function) {if (!value) return callback(new Error('请输入有效帐号/手机号!'));if (!isUsername(value)) return callback(new Error('输入的格式不正确,请重新输入!'));callback(); // 通过 } /*** 校验密码是否合法*/ function checkPassword(rule: any, value: string, callback: Function) {if(!value) return callback(new Error('请输入有效密码!'));if(!isPassword(value)) return callback(new Error('密码输入错误,请重新输入!'));callback();// 通过 }// 提交表单 function submitForm(){}</script><template><div class="login-container"><div class="login-wrap"><img class="lagin-logo" src="@/assets/logo-01.png"/><div class="login-title"> 帐号登录 </div><el-form class="login-form" ref="formRef" :model="loginData" size="large"><el-form-item prop="username" :rules="{required: true, validator: checkUsername, trigger: 'blur'}"><el-input v-model="loginData.username" placeholder="请输入帐号/手机号" maxlength="30" prefix-icon="ele-User" clearable/></el-form-item><el-form-item prop="password" :rules="{required: true, validator: checkPassword, trigger: 'blur'}"><el-input v-model="loginData.password" type="password" placeholder="请输入密码" maxlength="30" prefix-icon="ele-Unlock" show-password clearable/></el-form-item><el-form-item><div class="login-other"><el-checkbox v-model="isRemember"><span>记住密码</span></el-checkbox></div><el-button class="login-submit" @click="submitForm()" :loading="loading" type="primary"> 登 录 </el-button></el-form-item></el-form></div></div> </template><style lang="scss" scoped> .login-container {height: 100%;width: 100%;background-image: url('@/assets/loginBj.png');background-size: cover;overflow: hidden;display: flex;align-items: center;// justify-content: center;.login-wrap {width: 410px;height: 460px;background-color: #fff;padding: 30px;margin: auto;margin-right: 130px;box-shadow: #74747462 0 2px 15px;border-radius: 10px;.lagin-logo {max-width: 130px;margin: auto;display: flex;}.login-title {font-size: 20px;height: 70px;line-height: 70px;font-weight: 500;text-align: left;color: #0d1234;letter-spacing: 2px;}.login-form {.el-form-item {margin-bottom: 27px;}.login-other {span {font-size: 13px;font-weight: 500;color: #999;}}.login-submit {width: 100%;height: 40px;letter-spacing: 3px;font-weight: 500;}}}} </style>
效果:
第三步、Pinia 管理认证状态
-
创建
utils/storage.ts
/*** window.localStorage 浏览器永久缓存* @method set 设置永久缓存* @method get 获取永久缓存* @method remove 移除永久缓存* @method clear 移除全部永久缓存*/ export const Local = {// 设置永久缓存set(key: string, val: any) {window.localStorage.setItem(key, JSON.stringify(val || ''));},// 获取永久缓存get(key: string) {let json: any = window.localStorage.getItem(key);return JSON.parse(json);},// 移除永久缓存remove(key: string) {window.localStorage.removeItem(key);},// 移除全部永久缓存clear() {window.localStorage.clear();}, };/*** window.sessionStorage 浏览器临时缓存* @method set 设置临时缓存* @method get 获取临时缓存* @method remove 移除临时缓存* @method clear 移除全部临时缓存*/ export const Session = {// 设置临时缓存set(key: string, val: any) {// val为undefined保存时,当get时parse转成json对象会失败,加上 || ''解决这个window.sessionStorage.setItem(key, JSON.stringify(val || ''));},// 获取临时缓存get(key: string) {let json: any = window.sessionStorage.getItem(key);// parse 无法对undefined 和 ''进行解析的return JSON.parse(json);},// 移除临时缓存remove(key: string) {window.sessionStorage.removeItem(key);},// 移除全部临时缓存clear() {window.sessionStorage.clear();}, };
-
在
src/types/pinia.d.ts
定义 认证状态 类型:// 用户认证信息 declare interface AuthState<T = any> {rememberData?: LoginData; // 记住我(登录数据)accessToken?: string; //访问令牌 }
-
创建
src/stores/auth.ts
,Pinia
实现记住密码、登录处理:
记住密码:将登录信息保存下来。 提交登录信息处理,后端接口校验用户名密码正确后,会返回令牌
access_token
,将令牌保存;
import { defineStore } from 'pinia';
import type { RouteRecordRaw } from 'vue-router';
import { Session, Local } from '@/utils/storage';
import { login } from '@/api/auth';
// 保存到 Local或 Session的key名
export const Key = {rememberKey: 'isRemember', // 记住密码的keyaccessTokenKey: 'accessToken', // 访问令牌本地保存的key
}
/**
* 用户所拥有的路由权限
*/
export const useAuthStore = defineStore('auth', {state: (): AuthState<RouteRecordRaw> => {return {rememberData: Local.get(Key.rememberKey), // 记住密码accessToken: Session.get(Key.accessTokenKey), // 访问令牌字符串}},actions: {// 记住密码setRememberPwd(data?: LoginData) {this.rememberData = data;if (data) {Local.set(Key.rememberKey, { username: data.username, password: data.password });} else {Local.remove(Key.rememberKey);}},// 登录操作userLogin(loginData: LoginData) {return new Promise((resolve, reject) => {login(loginData).then((res: any) => {const { data } = res;// 状态赋值const { access_token } = data;this.accessToken = access_token;// 保存到session中Session.set(Key.accessTokenKey, access_token);// 正常响应钩子resolve(res);}).catch((error: Error) => {reject(error); // 异常});});},}
});
第四步、提交登录表单
- 创建
api/auth/index.ts
文件import request from "@/utils/request"; const baseUrl = "/auth"; // 登录系统 export function login(data: LoginData) {return request({url: `${baseUrl}/token`,method: 'POST',data}); }
- 实现表单登录按钮逻辑
import { useAuthStore } from "@/stores/auth"; const authStore = useAuthStore(); const state = reactive({loading: false,isRemember: true, // 是否记住密码loginData: {username: authStore.rememberData?.username,password: authStore.rememberData?.password,} as LoginData, });// 提交登录 function submitForm() {// 如果在登录中,不允许重复登录if (state.loading) return;formRef.value?.validate(async (valid: boolean) => {if (!valid) return false;try {// 登录中state.loading = true;// 记住密码authStore.setRememberPwd(state.isRemember ? state.loginData : undefined);// 发送登录请求await authStore.userLogin(state.loginData);// 跳转来源地址router.replace({ path: <string>route.query?.redirect || "/" });} catch (error) {} finally {}}); }
- 效果
第六步、axios在请求头上添加认证token
- 在
src/utils/request.ts
的axios
请求拦截器中针对发送的验合法性:Authorization: Bearer ${accessToken}
import { useAuthStore } from '@/stores/auth'; // 请求拦截器 request.interceptors.request.use(config => {// 在此处可向请求头加上认证tokenconst authStore = useAuthStore();// 获取tokenconst accessToken = authStore.accessToken;if (accessToken) {// oauth2 请求头 Authorization: Bearer xxxxxconfig.headers.Authorization = `Bearer ${accessToken}`;}return config; }, error => {// 出现异常, catch可捕获到return Promise.reject(error); })
- 观察登录的时候调用接口,接口中的请求头是否有
token
退出登录实现步骤
第一步、调用接口api方法 logout
import request from "@/utils/request";
const baseUrl = "/auth";
// 退出系统
export function logout() {return request({url: `${baseUrl}/logout`,method: 'POST',});
}
第二步、pinia 中实现退出功能
import { defineStore } from 'pinia';
import type { RouteRecordRaw } from 'vue-router';
import { Session, Local } from '@/utils/storage';
import { login, logout } from '@/api/auth';
// 保存到 Local或 Session的key名
export const Key = {rememberKey: 'isRemember', // 记住密码的keyaccessTokenKey: 'accessToken', // 访问令牌本地保存的keyuserInfoKey: 'userInfo', // 用户信息本地保存的key
}
/**
* 用户所拥有的路由权限
*/
export const useAuthStore = defineStore('auth', {state: (): AuthState<RouteRecordRaw> => {return {rememberData: Local.get(Key.rememberKey), // 记住密码accessToken: Session.get(Key.accessTokenKey), // 访问令牌字符串userInfo: Session.get(Key.userInfoKey),}},actions: {// 记住密码setRememberPwd(data?: LoginData) {this.rememberData = data;if (data) {Local.set(Key.rememberKey, { username: data.username, password: data.password });} else {Local.remove(Key.rememberKey);}},userLogout() {return new Promise((resolve, reject) => {logout().then((res: any) => {// 重置状态this.resetUserState();// 重新加载当前页,需认证页面会去登录页window.location.reload();resolve(res);}).catch((error: Error) => {reject(error);})});},// 重置用户状态resetUserState() {this.accessToken = undefined;this.userInfo = undefined;// 移除保存的数据Session.remove(Key.accessTokenKey);Session.remove(Key.userInfoKey);}}
});
第三步、触发退出登录事件
- 在
src/layout/layoutHeader/userDropdown.vue
显示用户信息和点击退出系统authStore.userLogout()
<template><el-dropdown><template #dropdown><el-dropdown-menu><el-dropdown-item @click="authStore.userLogout()" divided>退出登录</el-dropdown-item></el-dropdown-menu></template></el-dropdown></div> </template> <script lang="ts" setup> import { useFullscreen, useDark } from "@vueuse/core"; import { useLayoutConfigStore } from "../../stores/layoutConfig"; import { useRouter } from "vue-router"; import { useAuthStore } from "../../stores/auth"; const router = useRouter(); const authStore = useAuthStore(); const layoutConfig = useLayoutConfigStore(); </script>
效果: