前后端分离项目实战-通用管理系统搭建(前端Vue3+ElementPlus,后端Springboot+Mysql+Redis)第三篇:登录功能优化
天行健,君子以自强不息;地势坤,君子以厚德载物。
每个人都有惰性,但不断学习是好好生活的根本,共勉!
文章均为学习整理笔记,分享记录为主,如有错误请指正,共同学习进步。
仰天大笑出门去,我辈岂是蓬蒿人。
——《南陵别儿童入京》
文章目录
- 19. 登录优化(记住用户名和密码)
- 19.1 api/api.ts
- 19.2 store/index.ts
- 19.3 index/HomeIndex.vue
- 19.4 PhoneCodeForm.vue
- 19.5 QcodeForm.vue
- 19.6 UsernameForm.vue
- 19.7 App.vue
- 19.8 页面效果展示
- 19.8.1 使用短信验证码登录的展示
- 19.8.2 使用账号密码登录的展示
- 19.8.3 使用二维码登录的展示
先进行上一篇最后一部分登录问题进行优化
优化内容如下:
1.部分警告,虽然不影响项目正常使用,但控制台输出的警告看着还是比较难受的
2.取消勾选记住用户名和密码操作,用户名密码依旧显示,该问题是因为当记住用户名密码对应的值移除后,其值变为undefined,而代码中的判断在其为undefined时依旧满足条件执行,需要将其值与ture进行比较
19. 登录优化(记住用户名和密码)
部分其他代码也进行了优化,以下代码基于上一篇最后下载的项目进行改动
改动如下
19.1 api/api.ts
src/api/api.ts 注掉了一部分无用代码
// 引入axios
import axios from 'axios'
// 引入store
import store from '@/store';
import { useRouter } from 'vue-router';// 数据请求自定义配置(实例)
// console.log(import.meta.url,"------------");
const api = axios.create({// baseURL: 'https://mo_sss.blog.csdn.net.cn',// baseURL: import.meta.BaseURL,// baseURL: 'https://hanshanlibai.gms.com',baseURL: 'http://127.0.0.1:8888/',timeout: 1000,headers: {// 'X-Custom-Header': 'foobar''Content-Type': 'application/json;charset=UTF-8'}// withCredentials 表示跨域请求时是否需要使用凭证,默认是true// withCredentials: true,// responseType 表示浏览器将要响应的数据类型,包括arraybuffer、document、json、text、stream// 浏览器专属类型: blob// 默认值就是json// responseType: 'json',// responseEncoding 表示用于解码响应的编码(Node.js专属),注意,忽略responseType值为stream或者客户端请求// 默认值为utf-8// responseEncoding: 'utf-8'
});// const router = useRouter();// 添加请求拦截器
api.interceptors.request.use(function (config) {console.log("request------------------->")// 在发送请求之前做些什么// 获取缓存中的tokenconst token = store.getters.getToken;if(token){// 如果token为真值,则将其赋值赋给请求头config.headers.token = token;}return config;}, function (error) {// 对请求错误做些什么return Promise.reject(error);});// 添加响应拦截器
api.interceptors.response.use(function (response) {console.log("response-------------------> code",response.status)// 2xx 范围内的状态码都会触发该函数。// 对响应数据做点什么// store.commit("logout");// router.push('/UserLogin')return response;}, function (error) {// 超出 2xx 范围的状态码都会触发该函数。// 对响应错误做点什么if(error.response){console.log(error.response.status)switch(error.response.status){case 401:// case 200:store.commit("logout");// router.replace({// path: '/UserLogin',// query: {// redirect: router.currentRoute.value.fullPath// }// })break;default:store.commit("logout");// router.replace({// path: "/UserLogin",// query: {// redirect: router.currentRoute.value.fullPath// }// })}}return Promise.reject(error);});export default api;
19.2 store/index.ts
src/store/index.ts 新增了登出时清除缓存数据的代码,此处为缓存校验时使用
// 引入, 用于存储全局的状态数据,可供其他地方调用
import { createStore } from "vuex";
// 引入工具方法
import utils from "@/utils/utils";// 创建一个新的store实例
const store = createStore({state() {return{// count: 0// 当前登录的用户信息userInfo: {},// 当前登录的标识tokentoken: null,}},getters: {getUserInfo(state:any){return state.userInfo;},getToken(state:any){return state.token;}},mutations: {// 登出,清除缓存中的数据logout: function(state:any){console.log("---111---")state.userInfo = null;utils.removeData("userInfo");utils.removeData("token");// utils.removeData("username");// utils.saveData("username","");// utils.removeData("saveUsername");// utils.removeData("password");// utils.removeData("savePassword");},// 存储用户信息setUserInfo: function(state:any, userInfo:any){state.userInfo = userInfo;utils.saveData('userInfo', userInfo);},// 存储tokensetToken: function(state:any, token:any){state.toekn = token;utils.saveData('token', token);}}})export default store;
19.3 index/HomeIndex.vue
src/views/index/HomeIndex.vue 未做改变,但也列出来看一下
<script setup lang="ts">import utils from '@/utils/utils';
import { onMounted } from 'vue';</script><template>后台主页
</template><style scoped>
</style>
19.4 PhoneCodeForm.vue
src/views/login/components/PhoneCodeForm.vue 对登录缓存校验进行了优化
<script setup lang="ts">
import { ref, reactive, onMounted, onUnmounted } from 'vue'// 引入状态存储工具store
import {useStore} from 'vuex'// 引入工具方法
import utils from '../../../utils/utils'
import api from '../../../api/api'
// 路由引入
import { useRoute, useRouter } from 'vue-router';// 登录表单的实例
// let loginFormRef = ref(null);
let loginFormRef = ref()
// 登录表单的数据
const loginForm = reactive({// 用户名username: '',// 手机验证码smscode: '',// 图片验证imgcode: '',// 记住用户名,默认否saveUsername: false
})// 登录验证规则
const rules = {username: [{required: true,message: '请输入用户名',trigger: 'blur'}],smscode: [{required: true,message: '请输入短信验证码',trigger: 'blur'}],imgcode: [{required: true,message: '请输入图片验证码',trigger: 'blur'}]
}const formSize = "";// 图片验证码路径
let imgCodeSrc = new URL('../../../assets/code.png', import.meta.url).href
// const imgCodeSrc = '../../../assets/code.png';// 刷新图片验证码
const getImgCode = () => {// 后续改为从服务器上获取动态图片imgCodeSrc = new URL('../../../assets/code.png', import.meta.url).href
}// 定时器
let timer: any = null
// 获取短信验证码的间隔时间
let curTime = 0
// 获取短信验证码按钮的文本显示内容
let smsCodeBtnText = ref('获取验证码')// 获取短信验证码
const getSmsCode = () => {// 当点击获取短信验证码时,如果其他信息没填则提示输入if (!loginForm.username) {utils.showError('请输入用户名')return}// if(!loginForm.smscode){// utils.showError('请输入短信验证码');// return;// }// TODO 从后台获取短信验证码// 调用接口生成短信验证码// 1 直接使用axios请求后端完整地址请求
// axios({
// method: 'post',
// url: 'http://127.0.0.1:8888/login/redis/setMessageCode',
// // url: 'login/redis/setMessageCode',
// // 这里需要注意,不管请求方式是什么,这里是根据后端传参方式来定的,如果后端使用@RequestParam则这里使用params作为key
// params: {
// username: loginForm.username
// }
// });// 2 使用axios实例传参请求后端接口地址的用法 api({method: 'post',url: '/login/redis/setMessageCode',params: {username: loginForm.username}})curTime = 60timer = setInterval(() => {curTime--;smsCodeBtnText.value = curTime + '秒后重新获取';if (curTime <= 0) {smsCodeBtnText.value = '获取验证码'clearInterval(timer)// 清除时,值为空,防止重复点击触发多次timer = null}}, 1000)
}// 状态存储的store
const store = useStore();
// 路由,转到指定页面,使用push
// const route = useRoute();
const router = useRouter();// 登录提交事件
const onSubmit = () => {// form表单中的值,校验,loginFormRef.value.validate((valid: string, fileds: any) => {// 如果valid值为假,则遍历输出报错if (!valid) {for (let key in fileds) {// 获取报错信息中的字段对应的key的索引为0的信息utils.showError(fileds[key][0].message)}return}// 登录表单的记住用户名如果被勾选if (loginForm.saveUsername==true) {console.log("短信验证登录1:",loginForm.saveUsername);// 保存输入的用户名utils.saveData('username', loginForm.username)// 保存被勾选的操作utils.saveData('saveUsername', loginForm.saveUsername)} else {console.log("短信验证登录2:",loginForm.saveUsername);// 如果记住用户名的勾选取消,则移除这两个存储的内容utils.removeData('username')utils.removeData('saveUsername')}// TODO 调用接口登录// 因为太快了,所以可能看不到效果,可以将下方的hideLoading方法注掉,可以看到效果utils.showLoadding('正在加载中')api({method: 'get',url: '/login/redis/getMessageCode',params: {username: loginForm.username,smscode: loginForm.smscode// imgcode: loginForm.imgcode}}).then((res) => {utils.hideLoadding()console.log(res)// console.log(res.status)// if (!res || res.status != 200 || !res.data || res.data.result != 200 || !res.data.data) {if (res.status != 200 || res.data.result != 200 || !res.data.msgCode) {utils.showError('登录失败-请求数据返回有误');return;}// console.log(res.data.data, loginForm.smscode);if(res.data.msgCode == loginForm.smscode){utils.showSuccess('登陆成功')// 存储用户token信息并转到主页// let userInfo = res.data.datalet userInfo = res.datalet token = res.data.token// 状态数据存储store.commit('setUserInfo', userInfo);store.commit('setToken', token);// 登录成功后将页面转到主页router.push('/HomeIndex')}else if(res.data.msgCode != loginForm.smscode){utils.showError('登录失败-验证码错误');return;}// utils.showError('登录失败')}).catch((error) => {// utils.hideLoadding();console.log(error);utils.showError('登录失败-出现异常')})// api.post("/api/login/code",{// username: loginForm.username,// smscode: loginForm.smscode,// imgcode: loginForm.imgcode// }).then((res)=>{// utils.hideLoadding();// console.log(res);// console.log(res.status);// if(!res || res.status != 200 || !res.data || res.data.code != 8888 || !res.data.data){// if(res.data.message){// utils.showError(res.data.message);// return;// }// utils.showError('登录失败');// return;// }// // 存储用户token信息并转到主页// let userInfo = res.data.data;// let token = res.data.token;// utils.showSuccess('登陆成功');// }).catch((error)=>{// // utils.hideLoadding();// utils.showError('登录失败');// });// 登录成功信息提示// utils.showSuccess("登录成功");})
}// 挂载
onMounted(() => {// 获取记住用户名的值loginForm.saveUsername = utils.getData('saveUsername')// 如果记住用户名被勾选,则获取用户名显示(saveUsername可能会是undefined,当为undefined时也是真,故这里不能直接使用saveUsername,而是要判断是否为true)if (loginForm.saveUsername==true) {loginForm.username = utils.getData('username')}
})// 清空定时器
onUnmounted(() => {timer && clearInterval(timer)
})
</script><template><!-- 手机验证码登录 --><div class="phoneCodeLoginBox"><el-formref="loginFormRef"style="max-width: 600px":model="loginForm":rules="rules"label-width="0"class="loginFrom":size="formSize"status-icon><!-- 用户名 --><el-form-item prop="username"><!-- 图标设置,动态绑定username,提示信息,设置输入框大小 --><el-inputprefix-icon="UserFilled"v-model="loginForm.username"placeholder="请输入用户名"size="large"/></el-form-item><!-- 短信验证 --><el-form-item prop="smscode"><!-- 使用两个div块来左右布局验证码输入和获取验证码按钮的实现 --><div class="flex loginLine"><div class="flexItem"><el-inputprefix-icon="Iphone"v-model="loginForm.smscode"placeholder="请输入验证码"size="large"/></div><div class="codeBtn"><el-button type="primary" size="large" @click="getSmsCode" :disabled="curTime > 0">{{smsCodeBtnText}}</el-button></div></div></el-form-item><!-- 图片验证 --><el-form-item prop="imgcode"><!-- 使用两个div块来左右布局验证码输入和获取验证码按钮的实现 --><div class="flex loginLine"><div class="flexItem"><el-inputprefix-icon="Picture"v-model="loginForm.imgcode"placeholder="请输入图片验证码"size="large"/><!-- <el-input prefix-icon="Iphone" v-model="loginForm.smscode" placeholder="请输入验证码" size='large' /> --></div><div class="codeBtn"><el-image :src="imgCodeSrc" size="large" @click="getImgCode"></el-image><!-- <el-button type="primary" size="large" @click="getSmsCode" class="" >获取验证码</el-button> --></div></div></el-form-item><!-- 记住用户名 --><el-form-item prop="saveUsername"><el-checkbox v-model="loginForm.saveUsername">记住用户名</el-checkbox></el-form-item><!-- 登录按钮 --><el-form-item><el-button class="loginBtn" type="danger" size="large" @click="onSubmit">登录</el-button></el-form-item></el-form></div>
</template><style scoped>
/* 按钮宽度设为最大 */
.loginBtn {width: 100%;/* 登录按钮圆角边框 */border-radius: 20px;
}/* 验证码按钮样式配置 */
.codeBtn {width: 100px;margin-left: 10px;
}
/* 按钮和图片宽度100px */
.codeBtn:deep(.el-button),
.codeBtn:deep(img) {width: 100px;/* height: 40px; */
}
/* 验证码图片高度 */
.codeBtn:deep(img) {height: 40px;/* 鼠标移上去会变成手型 */cursor: pointer;
}/* 这一行宽度占满 */
.loginLine {width: 100%;
}
</style>
19.5 QcodeForm.vue
src/views/login/components/QcodeForm.vue 对二维码生成部分进行了优化
<script setup lang="ts">import { ref,reactive, onMounted, onUnmounted } from 'vue'// 引入工具方法
import utils from '../../../utils/utils'
import api from '../../../api/api'// 引入store
import {useStore} from 'vuex'
// 引入router
import {useRouter} from 'vue-router'const store = useStore();const router = useRouter();// 二维码// let qcodePath:any = null;// 二维码对应的token, 用于判断当前二维码是否已经被扫码登录let qrToken:string = "";// 第一次获取验证码// api({// method: 'post',// url: 'login/qr/generateQrCodeAsFile'// }).then((res)=>{// if(res.data.result != 200){// utils.showError("登录失败");// }// utils.showSuccess("登录成功");// qcodePath = res.data.data// qrToken = res.data.token// });// qcodePath = 'E:\\WORKPROJECTS\\MySelfPro\\hslb-general-management-system\\src\\main\resources\\login_qr_pngs\\QRCode.png';// 二维码let qcodeSrc = new URL("../../../assets/hslb-qcode.png", import.meta.url).href;// let qcodeSrc = new URL(qcodePath, import.meta.url).href;// let qcodeSrc = qcodePath;const qcodeToken = ref('');// 当前定时器事件const curTime = ref(0);let timer:any = null;let username:string = utils.getData("username");const qrString = "100100100222";// 后台更新获取二维码const loadQcode = () => {// 后续改为从服务器上获取动态图片// const qrString = "100100100222";console.log("9999999====== "+qrString);// let username:string = utils.getData("username");api({method: 'post',url: 'login/qr/generateQrCodeAsFile',params: {username: username,qrContent: qrString}}).then((res)=>{// if(res.data.result != 200){// utils.showError("登录失败");// }// utils.showSuccess("登录成功");// qcodePath = res.data.dataqrToken = res.data.token});qcodeSrc = new URL("../../../assets/hslb-qcode.png", import.meta.url).href;// qcodeSrc = new URL(qcodePath, import.meta.url).href;// qcodeSrc = qcodePath;// 初始化token的值qcodeToken.value = qrToken;// 设定定时时间// curTime.value = 60;// 为了让二维码失效的效果及时,这里暂时设置10秒,后续改回60秒即可curTime.value = 10;// 定义定时器,倒计时timer = setInterval(() => {curTime.value--;// 这里获取toekn,校验是否已经被登陆过checkLogin();if(curTime.value<=0){// 事件为0则清空定时器clearInterval(timer);timer = null;}}, 1000);};// 登录提交事件// const onSubmit = () => {// };// 挂载onMounted(() => {// 获取二维码loadQcode();});// 清空计时器onUnmounted(()=>{timer && clearInterval(timer);});// 使用qcodeToken判断当前二维码是否已经被扫码登录const checkLogin = () => {// TODOapi({method: 'post',url: 'login/qr/generateQrCodeAsFile',params: {username: username,qrContent: qrString}}).then((res)=>{if(res.data.token){utils.showSuccess("登录成功");store.commit('setUserInfo',res.data);store.commit('setToken',res.data.token);router.push('/HomeIndex');}// res.data.token;}).catch((error)=>{console.log(error);// utils.showError("登录失败")});}</script><template><!-- 扫码登录 --><div class="qcodeLoginBox"><div class="qcodeBox" ><img class="qcodeImg" :class="{'endImg':curTime<=0}" :src="qcodeSrc" alt="无法获取二维码,请联系客服解决"><div v-if="curTime<=0" class="endBox" @click="loadQcode" >当前二维码失效,点击重新加载{{ curTime }}秒</div></div><div class="tipInfo" >使用微信或移动端扫码登录 此二维码将在{{ curTime }}秒后刷新</div></div></template><style scoped>/* 二维码窗口样式 */.qcodeBox{width: 80%;height: 80%;position: relative;/* 边框自动 */margin: 0 auto; }/* 二维码图片样式 */.qcodeBox .qcodeImg{width: 100%;height: 100%;}.qcodeBox .endBox{width: 100%;height: 100%;/* 悬浮显示 */position: absolute;/* 靠左 *//* left: 0%; *//* 靠上 */top: 0;/* 居中 *//* text-align: center; *//* 字体大小 */font-size: 14px;/* 字体颜色 */color: red;display: flex;/* 上下居中 */align-items: center;/* justify-items: center; *//* 左右居中 */justify-content: center;/* 背景色为灰色 */background-color: #00000055;}/* .endImg{filter: brightness(10%);} *//* 提示信息样式 */.tipInfo{/* 行高 */line-height: 30px;/* 字体大小 */font-size: 14px;/* 居中 */text-align: center;/* 颜色 */color: var(--el-text-color-placeholder);}</style>
19.6 UsernameForm.vue
src/views/login/components/UsernameForm.vue 对登录缓存校验进行了优化
<script setup lang="ts">import { ref,reactive, onMounted } from 'vue'
// 引入状态存储store
import { useStore } from 'vuex'
// 引入路由工具router
import { useRouter } from 'vue-router'// 引入工具方法
import utils from '../../../utils/utils'
import api from '../../../api/api'// 登录表单的实例// let loginFormRef = ref(null);let loginFormRef = ref();// 登录表单的数据const loginForm = reactive({// 用户名username: '',// 密码password: '',// 图片验证imgcode: '',// 记住用户名,默认否saveUsername: false,// 记住用户名,默认否savePassword: false});// 登录验证规则const rules = ({username:[{required: true,message: '请输入用户名',trigger: 'blur'}],password:[{required: true,message: '请输入密码',trigger: 'blur'}],imgcode:[{required: true,message: '请输入图片验证码',trigger: 'blur'}]});const formSize = "";// 图片验证码路径let imgCodeSrc = new URL("../../../assets/code.png", import.meta.url).href;// const imgCodeSrc = '../../../assets/code.png';// 刷新图片验证码const getImgCode = () => {// 后续改为从服务器上获取动态图片imgCodeSrc = new URL("../../../assets/code.png", import.meta.url).href;};// 全局状态存储const store = useStore();// 路由调用const router = useRouter();// 登录提交事件const onSubmit = () => {// form表单中的值,校验,loginFormRef.value.validate((valid:string, fileds:any)=>{// 如果valid值为假,则遍历输出报错if(!valid){for(let key in fileds){// 获取报错信息中的字段对应的key的索引为0的信息utils.showError(fileds[key][0].message);}return;}// 登录表单的记住用户名如果被勾选if(loginForm.saveUsername){// 保存输入的用户名utils.saveData('username', loginForm.username);// 保存被勾选的操作utils.saveData('saveUsername', loginForm.saveUsername);}else{// 如果记住用户名的勾选取消,则移除这两个存储的内容utils.removeData('username');utils.removeData('saveUsername');}// 登录表单的记住用户名如果被勾选if(loginForm.savePassword){// 保存输入的用户名utils.saveData('password', loginForm.password);// 保存被勾选的操作utils.saveData('savePassword', loginForm.savePassword);}else{// 如果记住用户名的勾选取消,则移除这两个存储的内容utils.removeData('password');utils.removeData('savePassword');}// TODO 调用接口登录utils.showLoadding("正在加载中");api({method: 'get',url: '/login/login',params: {username: loginForm.username,password: loginForm.password}}).then((res)=>{utils.hideLoadding();if(res.status != 200 || res.data.result != 200){utils.showError("登录失败-请求数据返回有误");return;}if(res.data.login == 1){utils.showSuccess("登录成功");// 存储用户信息// let userInfoLogin = res.data.login;let userInfoLogin = res.data;let token = res.data.token;console.log("usernamelogin:", token);store.commit('setUserInfo', userInfoLogin);store.commit('setToken', token);console.log("----------------token: ", token);// 登录成功后跳转主页router.push('/HomeIndex');}else if(res.data.login == 0){utils.showError("登录失败-用户不存在");return;}else if(res.data.login == 2){utils.showError("登录失败-密码错误");return;}// utils.showError("登录失败-返回数据错误")}).catch((error)=>{console.log(error);utils.showError("登录失败-发生异常");});// 登录成功提示// utils.showSuccess("登录成功");});};// 挂载onMounted(() => {// 获取记住用户名的值loginForm.saveUsername = utils.getData('saveUsername');// 如果记住用户名被勾选,则获取用户名显示if(loginForm.saveUsername==true){loginForm.username = utils.getData('username');}// 获取记住密码的值loginForm.savePassword = utils.getData('savePassword');// 如果记住密码被勾选,则获取密码if(loginForm.savePassword==true){loginForm.password = utils.getData('password');}});</script><template><!-- 用户密码登录 --><div class="usernameLoginBox"><el-form ref="loginFormRef"style="max-width: 600px":model="loginForm":rules="rules"label-width="0"class="loginFrom":size="formSize" status-icon><!-- 用户名 --><el-form-item prop="username"><!-- 图标设置,动态绑定username,提示信息,设置输入框大小 --><el-input prefix-icon="UserFilled" v-model="loginForm.username" placeholder="请输入用户名" size='large' /></el-form-item><!-- 密码 --><el-form-item prop="password"><!-- 密码 --><div class="flexItem" ><!-- show-password 属性表示是否显示切换显示密码的图标,true为显示 --><!-- <el-input prefix-icon="Lock" show-password="off" type="password" v-model="loginForm.password" placeholder="请输入密码" size='large' /> --><el-input prefix-icon="Lock" show-password type="password" v-model="loginForm.password" placeholder="请输入密码" size='large' /></div></el-form-item><!-- 图片验证 --><el-form-item prop="imgcode"><!-- 使用两个div块来左右布局验证码输入和获取验证码按钮的实现 --><div class="flex loginLine"><div class="flexItem" ><el-input prefix-icon="Picture" v-model="loginForm.imgcode" placeholder="请输入图片验证码" size='large' /><!-- <el-input prefix-icon="Iphone" v-model="loginForm.smscode" placeholder="请输入验证码" size='large' /> --></div><div class="codeBtn" ><el-image :src="imgCodeSrc" size='large' @click="getImgCode" ></el-image><!-- <el-button type="primary" size="large" @click="getSmsCode" class="" >获取验证码</el-button> --></div></div></el-form-item><!-- <el-form-item prop="saveUsername"> --><el-form-item ><!-- 记住账号密码的勾选 --><div class="flex loginLine" ><!-- 记住用户名 --><div class="flexItem" ><el-form-item prop="saveUsername"><el-checkbox v-model="loginForm.saveUsername">记住用户名</el-checkbox></el-form-item></div><!-- 记住密码 --><div class="flexItem" ><el-form-item prop="savePassword"><el-checkbox v-model="loginForm.savePassword">记住密码</el-checkbox></el-form-item></div></div></el-form-item><!-- <el-form-item prop="savePassword"> --><!-- </el-form-item> --><!-- 登录按钮 --><el-form-item><el-button class="loginBtn" type="danger" size='large' @click="onSubmit">登录</el-button></el-form-item></el-form></div></template><style scoped>/* 按钮宽度设为最大 */.loginBtn{width: 100%;/* 登录按钮圆角边框 */border-radius: 20px;}/* 验证码按钮样式配置 */.codeBtn{width: 100px;margin-left: 10px;}/* 按钮和图片宽度100px */.codeBtn:deep(.el-button),.codeBtn:deep(img){width: 100px;/* height: 40px; */}/* 验证码图片高度 */.codeBtn:deep(img){height: 40px;/* 鼠标移上去会变成手型 */cursor: pointer;}/* 这一行宽度占满 */.loginLine{width: 100%}</style>
19.7 App.vue
src/App.vue 对登录缓存校验进行了优化
<script setup lang="ts">
import { onMounted } from 'vue';
import { useStore } from 'vuex';
import { useRouter } from 'vue-router';
import utils from './utils/utils';
import api from './api/api';// // // 引入暗黑主题的动态切换
// import { useDark, useToggle } from '@vueuse/core'// const isDark = useDark()
// // // 切换主题函数
// const toggleDark = useToggle(isDark)// 状态存储
let store = useStore();// 路由使用
const router = useRouter();onMounted(()=>{// let tt = localStorage.getItem("token");// console.log("tt: ",tt);console.log("=== ===");let token = "";// 由于token可能返回undefined报错,需要进行报错处理try {token = utils.getData("token");} catch (error) {error;}console.log("store-token",token);let userInfo = utils.getData('userInfo');console.log("userInfoL: "+userInfo)console.log("userInfoL: "+token&&userInfo)if(token && userInfo){console.log("token userInfo :",token," -- ", userInfo);// 登录成功,验证utils.showLoadding("正在加载")const username = utils.getData('username');if(!username){// 登录失败,跳转到登录页if(username===undefined){utils.saveData("username","");}// token验证失败utils.showError("用户名过期-请重新登录");router.push('/UserLogin');utils.hideLoadding();}else{console.log("username-", username);api.get('/login/tokenCheck',{params:{username}}).then((res)=>{console.log("res.data.token",res.data);utils.hideLoadding();if(res.data.token==token){// 登陆成功// store.commit('setUserInfo', userInfo);// store.commit('setToken', token);router.push('/HomeIndex');utils.showSuccess("登录成功");}else{// if(username===undefined){// utils.saveData("username","");// }// 登录失败utils.showError("Token已过期,请重新登录");// 登录失败,跳转到登录页router.push('/UserLogin');}});utils.hideLoadding();}}else{// 登录失败,跳转到登录页utils.showError("用户登录缓存过期,请重新登录");router.push('/UserLogin');utils.hideLoadding();}});</script><template><!-- 暗黑主题动态切换按钮实现 --><!-- <button @click="toggleDark()"><i inline-block align-middle i="dark:carbon-moon carbon-sun"/><span class="ml-2">{{ isDark ? 'Dark' : 'Light' }}</span></button> --><RouterView></RouterView></template><style scoped></style>
19.8 页面效果展示
19.8.1 使用短信验证码登录的展示
登录前
不勾选记住用户名登录
输入用户名,点击获取验证码按钮,获取验证码后填入(redis中查看),并填写图片验证码(此项随机填写即可)不勾选记住用户名
登录成功界面
60秒内刷新页面如下
等待60秒后刷新界面(token设定的是60秒过期,故过期后需要重新登录)
勾选记住用户名登录
输入用户名,点击获取验证码按钮,获取验证码后填入(redis中查看),并填写图片验证码(此项随机填写即可),勾选记住用户名
登录成功界面
60秒内刷新页面如下
60秒后刷新界面
19.8.2 使用账号密码登录的展示
登录前
不勾选记住用户名密码登录
登录成功后
60秒后刷新页面
勾选记住用户名密码登录
登录成功
60秒后刷新页面
19.8.3 使用二维码登录的展示
这里没有做任何逻辑进行用户的登录,只是生成了一个token,并且当前用户名是另外两种登录方式存储的缓存,此时直接验证通过
登录成功
60秒后刷新页面会跳转到短信登录
感谢阅读,祝君暴富!
相关文章:

前后端分离项目实战-通用管理系统搭建(前端Vue3+ElementPlus,后端Springboot+Mysql+Redis)第三篇:登录功能优化
天行健,君子以自强不息;地势坤,君子以厚德载物。 每个人都有惰性,但不断学习是好好生活的根本,共勉! 文章均为学习整理笔记,分享记录为主,如有错误请指正,共同学习进步。…...
8.20 Redis ACL配置 多个用户连接同一个Redis
**一、首先通过 linux命令 redis-cli 输入用户名和密码连接redis的客户端** **二、查看用户,创建用户,设置密码操作** (1)**ACL LIST** 命令 可以查看到当前的权限用户 (2)**ACL SETUSER userName** 此…...
【C语言】static和extern的作用
本文首发于 ❄️慕雪的寒舍 简单介绍C/C中static关键字和extern关键字的作用。 1.简介 在之前的博客中,提到过static的三个作用,但是没有详细说明这三个作用的场景,现在回过头来记录一下。 修饰函数修饰全局变量修饰函数内变量 static还有…...

全新分支版本!微软推出Windows 11 Canary Build 27686版
已经很久没有看到 Windows 11 全新的分支版本了,今天微软发布 Windows 11 Canary 新版本,此次版本号已经转移到 Build 27xxx,首发版本为 Build 27686 版。 此次更新带来了多项改进,包括 Windows Sandbox 沙盒功能切换到 Microsof…...
【Linux】ARM服务器命令行安装虚拟机
在Arm服务器上安装虚拟机操作笔记 一、基础环境准备1、环境准备2、检查KVM支持3、启动并启用libvirtd服务4、创建虚拟网络(可选)5、使用virt-manager创建虚拟机(支持KVM)6、管理虚拟机9、监控和日志 二、软虚拟化替代方案1、查看虚…...
Android 10.0 锁屏页面忘记锁屏密码情况下点击5次解锁图标弹出锁屏密码功能实现
1. 前言 在10.0的系统ROM定制化开发中,在一些产品中带锁屏密码的功能中,系统默认是滑动解锁,但是客户会设置锁屏密码,在某些时候会 忘掉锁屏密码,导致需要进入恢复出厂设置然后才能进入系统桌面,这样就导致系统的保存的资料都丢失了,所以需要要求在锁屏密码页面在忘记解…...
Java-CompletableFuture工具类
CompletableFuture 是 Java 8 引入的一个强大的异步编程工具,它提供了对异步计算的高级支 持,包括组合多个任务的能力、处理结果、异常处理等。为了方便地使用 CompletableFuture,你 可以创建一个工具类来封装常用的操作。 CompletableFuture 工具类 下面是一个 Complet…...
C语言:递归
递归简单来说就是函数自己调用自己。 特点:一般代码比较简洁,没有出口。 例子1:用一个函数计算阶乘 #include<stdio.h>//不用递归 int fac(int n) {int val 1;for (int i 1; i <n;i){val * i;}return val; }//用递归 int fac1(…...
自动化测试框架pytest+allure+requests
最近复习了一下关于自动化测试的内容,结合[码尚教育] 相关的思路来对测试框架进行开发。 争取实现零代码来实现自动化测试环境的搭建 AutoTestFrame 介绍 AutoTestFrame是一个基于Python的自动化测试框架,旨在帮助测试人员快速、高效地完成测试任务。…...
Python 笔记 numpy.ndarray切片
NumPy 的 ndarray 类型提供了非常灵活的切片功能,可以方便地访问和操作数组中的元素。切片允许您通过指定索引来选择数组的一部分。下面是一些基本的切片操作及其解释。 一维数组的切片 对于一维数组,切片操作类似于 Python 列表的切片。 示例 impor…...
一、HTML5知识点精讲
一、HTML5介绍 html是用来描述网页的一种语言(就是写网页的一种语言)。 它和CSS,JS称为网页三要素。 HTML负责把元素简单呈现在网页上,是网页的身体CSS负责给网页元素添加各种样式,是网页的衣服JS负责实现各种动态、…...

【杂乱算法】前缀和与差分
前缀和 文章目录 前缀和一维应用 二维差分一维 二维扩展1、前缀和与哈希表 一维 一个数组prefix中,第i个元素表示nums[0]至nums[i-1]的总和,那么我们就称这个prefix数组是nums数组的前缀和。 prefix [ i ] ∑ j 0 i nums [ j ] \text{prefix}[i] \s…...

Arduino调试ESP32常见问题 exit status 1
问题1:代码上传(烧录)报Failed uploading: uploading error: exit status 1大概率原因:没有安装对应的驱动,我的ESP32驱动是CH340点击这里下载CH340 下载后打开,若出现乱码不用在意,点击第一个按…...

“决胜面试:高频题目与算法策略一览”
干货分享,感谢您的阅读! (暂存篇---后续会删除,完整版和持续更新见高频面试题基本总结回顾(含笔试高频算法整理)) 备注:引用请标注出处,同时存在的问题请在相关博客留言…...

Node-RED的安装
最近对Node-RED比较感兴趣,因为在上OpenHarmony课程的时候,一直想找一个可以通过MQTT控制设备的低代码客户端解决方案。第一次指导Node-RED是在试用聆思开发板的时候,它的云端就是使用的Node-RED。 在安装Node-RED之前,请确保您的…...
java中的Collections
Java 的集合框架(Collections Framework)提供了一组标准的数据结构接口和类,用于存储和操作数据。Java 集合类位于 java.util 包中,主要包括以下几个核心接口和实现类。 1. 核心接口 1.1. Collection 接口 Collection 是集合框架的根接口,但它本身并不提供任何直接实现…...

linux Qt QkeyEvent及驱动键盘按键捕获
基于正点原子 QT中有专门的类处理键盘事件的类QKeyEvent 1.include “QKeyEvent” 查看它的说明中的描述 也就是说接受按键事件在keyPressEvent和keyReleaseEvent这两个函数,继续查看 重构这个函数 查看输入的QKeyEvent类,发现有一个方法key返回哪一个按…...

【GH】【EXCEL】P6: Shapes
文章目录 componentslinepicture components line picture Picture A Picture object Input parameters: Worksheet (Generic Data) A Worksheet, Workbook, Range Object, Excel Application, or Text Worksheet NameName (Text) An optional object nameLocation (Point) A p…...

google浏览器chrome用户数据(拓展程序,书签等)丢失问题
一、问题背景 我出现这个情况的问题背景是:因为C盘块满了想清理一部分空间(具体看这:windows -- C盘清理_c盘softwaredistribution-CSDN博客),于是找到了更改AppDatta这个方法,但因为,当时做迁移…...

数据结构——链式队列和循环队列
目录 引言 队列的定义 队列的分类 1.单链表实现 2.数组实现 队列的功能 队列的声明 1.链式队列 2.循环队列 队列的功能实现 1.队列初始化 (1)链式队列 (2)循环队列 (3)复杂度分析 2.判断队列是否为空 (1)链式队列 (2)循环队列 (3)复杂度分析 3.判断队列是否…...

网络编程(Modbus进阶)
思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...

idea大量爆红问题解决
问题描述 在学习和工作中,idea是程序员不可缺少的一个工具,但是突然在有些时候就会出现大量爆红的问题,发现无法跳转,无论是关机重启或者是替换root都无法解决 就是如上所展示的问题,但是程序依然可以启动。 问题解决…...
Ubuntu系统下交叉编译openssl
一、参考资料 OpenSSL&&libcurl库的交叉编译 - hesetone - 博客园 二、准备工作 1. 编译环境 宿主机:Ubuntu 20.04.6 LTSHost:ARM32位交叉编译器:arm-linux-gnueabihf-gcc-11.1.0 2. 设置交叉编译工具链 在交叉编译之前&#x…...
论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(二)
HoST框架核心实现方法详解 - 论文深度解读(第二部分) 《Learning Humanoid Standing-up Control across Diverse Postures》 系列文章: 论文深度解读 + 算法与代码分析(二) 作者机构: 上海AI Lab, 上海交通大学, 香港大学, 浙江大学, 香港中文大学 论文主题: 人形机器人…...

安宝特方案丨XRSOP人员作业标准化管理平台:AR智慧点检验收套件
在选煤厂、化工厂、钢铁厂等过程生产型企业,其生产设备的运行效率和非计划停机对工业制造效益有较大影响。 随着企业自动化和智能化建设的推进,需提前预防假检、错检、漏检,推动智慧生产运维系统数据的流动和现场赋能应用。同时,…...

全志A40i android7.1 调试信息打印串口由uart0改为uart3
一,概述 1. 目的 将调试信息打印串口由uart0改为uart3。 2. 版本信息 Uboot版本:2014.07; Kernel版本:Linux-3.10; 二,Uboot 1. sys_config.fex改动 使能uart3(TX:PH00 RX:PH01),并让boo…...
在web-view 加载的本地及远程HTML中调用uniapp的API及网页和vue页面是如何通讯的?
uni-app 中 Web-view 与 Vue 页面的通讯机制详解 一、Web-view 简介 Web-view 是 uni-app 提供的一个重要组件,用于在原生应用中加载 HTML 页面: 支持加载本地 HTML 文件支持加载远程 HTML 页面实现 Web 与原生的双向通讯可用于嵌入第三方网页或 H5 应…...

【MATLAB代码】基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),附源代码|订阅专栏后可直接查看
文章所述的代码实现了基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),针对传感器观测数据中存在的脉冲型异常噪声问题,通过非线性加权机制提升滤波器的抗干扰能力。代码通过对比传统KF与MCC-KF在含异常值场景下的表现,验证了后者在状态估计鲁棒性方面的显著优…...

AI语音助手的Python实现
引言 语音助手(如小爱同学、Siri)通过语音识别、自然语言处理(NLP)和语音合成技术,为用户提供直观、高效的交互体验。随着人工智能的普及,Python开发者可以利用开源库和AI模型,快速构建自定义语音助手。本文由浅入深,详细介绍如何使用Python开发AI语音助手,涵盖基础功…...

uni-app学习笔记三十五--扩展组件的安装和使用
由于内置组件不能满足日常开发需要,uniapp官方也提供了众多的扩展组件供我们使用。由于不是内置组件,需要安装才能使用。 一、安装扩展插件 安装方法: 1.访问uniapp官方文档组件部分:组件使用的入门教程 | uni-app官网 点击左侧…...