day-107-one-hundred-and-seven-20230707-物美管理系统-登录流程-页面路由跳转-面包屑导航-访问历史列表
物美管理系统
登录流程
- 客户端:
- 表单校验。
- 避免无效请求。
- 防止SQL注入。
- 密码进行加密-MD。
- 非对称性加密。
- 获取表单信息,向服务器发送。
- 请求-post。
- 表单校验。
- 服务器端:
- 获取请求主体传递的信息-建议二次校验。
- 根据传递的验证码及uuid,到数据库去查询,校验验证码的准确性。
- 不准确:反馈给客户端错误。
- 准确:下一步。
- 验证帐号和密码的准确度-去数据库查询。
- 不准确:反馈给客户端错误。
- 准确:下一步。
- 找到登录者信息的相关信息,基于JWT(json web token)算法,根据登录者信息、密钥、时间,算出一个Token值!
- 返回给客户端成功的消息-携带Token。
- 客户端:
- 接收服务器返回的结果。
- 失败:直接做提示,重新获取验证码-重置验证码。
- 成功:下一步。
- 如果登录成功,我们也可以获取Token信息。
- 把Token存储到本地-localStorage。
- 向服务器发送请求,把登录者信息和权限信息获取到,存储到全局状态管理vuex中!
- 为了做前端的登录态校验与权限校验。
- 如果有记住密码的功能,则需要把帐号密码存储到本地。
- 提示
- 跳转-细节。
- 接收服务器返回的结果。
数据库
- 数据库
- node.js --> mongodb、MySQL、SQLServer、Oracle…
Token
- Token是我们后期发请求与登录态校验的有效凭证。
- 一般除登录/获取验证码两个接口外,其余所有的接口请求,都要求把token基于请求头传递给服务器-因为服务器需要知道你是谁、你是否登录。
- 在后端接收到每一次请求的时候:
- 首先获取Token,如果没有,则直接反馈给客户端错误。
- 基于JWT算法进行反解析。
- 先校验时间,看Token是否失效,如果失效,则证明登录过期了。
- 如果没有失败,则获取登录者信息,根据登录者信息,再返回相应的数据!
登录态校验
- 登录态校验:
- 当在浏览器前端,当访问除登录页或404页面以外的其它页面。
- 第一件事:校验用户是否登录:
- 登录了,则进入指定的页面;
- 没登录,跳转到登录页(提示)。
- 第一件事:校验用户是否登录:
- 登录态校验核心:
- 核心1:从服务器获取登录者信息,并且把信息存储起来。
- 一般不存在localStorage中。
- 防止服务器端登录者信息更新了,但是本地存储的信息还在有效期,这样不能获取最新的登录者信息。
- 防止有人在本地恶意仿造登录者信息。
- …
- 我们一般会使用vuex来存储:
- 只要页面刷新,或者页面关闭重新打开,vuex中存储的登录者信息已经没有了,此时我们需要重新向服务器发送请求获取!保证实时性、和安全性。
- 一般做这件事的场景:
- 登录成功后。
- 每一次路由跳转(含页面第一次加载/刷新)
- 先看vuex中是否有登录者信息。
- 有:说明此用户是登录的,直接想去哪就去哪即可!
- 没有:需要从服务器获取。
- 如果获取到了:存储到vuex,说明也是登录的,想去哪就去哪即可!如果发送请求都没拿到:用户压根没有登录,此时提示、跳转到登录页。
- 这件事一般就是在router.beforeEach()路由全局前置守卫里。
- 先看vuex中是否有登录者信息。
- 一般不存在localStorage中。
- 核心1:从服务器获取登录者信息,并且把信息存储起来。
登录态校验信息注意事项
- 不能单纯的以本地是否存储了Token,来判断是否登录:Token可能是非法的、也可能早已经过期了。
- 服务器一般都会提供一个接口:获取登录者信息和权限信息-这个接口请求,需要客户端传递Token。
具体代码
- fang/f20230705/ManageSystem/src/views/user/Login.vue
<script>
import ut from "@/assets/utils";
const place = "********"; //占位符:不能和用户自己输入的一致、需要能通过Form表单校验。
export default {
data() {
// 校验密码的格式。
const validatePassword = (_, value, callback) => {
// 和特殊占位符一致,直接通过。
if (value === place) {
callback();
}
if (value.length === 0) {
return callback(new Error("密码是必填项哦"));
}
/* //项目中:
let reg = /^\S*(?=\S{6,})(?=\S*\d)(?=\S*[A-Z])(?=\S*[a-z])(?=\S*[!@#$%^&*? ])\S*$/;
if (!reg.test(value)) {
return callback(new Error("密码格式有误"));
} */
callback();
};
return {
//控制选项卡。
activeName: "account",
// 验证码相关状态
captcha: {
img: "",
uuid: "",
loading: false,
},
//表单相关状态。
ruleForm: {
username: "",
password: "",
code: "",
remember: true,
},
rules: {
username: [
{ required: true, message: "帐号是必填项", trigger: "blur" },
],
password: [
{ required: true, message: "密码是必填项", trigger: "blur" },
],
code: [
// { required: true, message: "验证码是必填项哦~~", trigger: "blur" },
{ validtor: validatePassword, trigger: "blur" },
],
},
};
},
methods: {
//获取验证码
async queryCaptcha() {
this.captcha.loading = true;
try {
let { code, img, uuid } = await this.$API.queryCaptchaImage();
if (+code !== 200) {
this.$message.error(`网络出现异常,获取验证码失败`);
} else {
this.captcha.img = `data:image/jpeg;base64,${img}`; //因为服务器返回的图片默认没前缀。
this.captcha.uuid = uuid;
}
} catch (error) {
console.log(`error:-->`, error);
}
this.captcha.loading = false;
},
// 登录校验。
async submit() {
try {
//1. 先进行表单校验。
await this.$refs.formIns.validate();
// this.$message.success('哈哈')
//2. 获取表单中的数据,向服务器发送请求。
let { username, password, code, remember } = this.ruleForm;
if (password === place) {
//说明用户没有改过密码。
password = this.remberOldPass;
}
let {
code: resultCode,
token,
msg,
} = await this.$API.checkUserLogin({
username,
password,
code,
uuid: this.captcha.uuid,
});//前提:API中设置了对应的接口。
if (+resultCode !== 200) {
//登录失败。
this.$message.error(msg);
// 重新获取验证码。
this.queryCaptcha();
this.ruleForm.code = "";
return;
}
//登录成功。
ut.storage.set("TK", token); //把token存储到本地上。
await this.$store.dispatch("setProfileAsync"); //获取登录者信息。前提:API中设置了对应的接口。同时有token-请求拦截器中放置会把当前接口带上token。在vuex中设置了关于用户信息的异步请求接口。
// 登录成功后,如果有记住密码,就存储帐号密码到本地。
if (remember) {
ut.storage.set("REMBER", {
username,
password, //真实开发中一定要MD5加密。
});
} else {
ut.storage.remove("REMBER");
}
this.$message.success("恭喜你,登录成功了!");
this.$router.push("/");
} catch (error) {
console.log(`error:-->`, error);
}
},
},
created() {
//第一次渲染组件:立即获取验证码
this.queryCaptcha();
// 第一次渲染组件:验证是否有记住帐号密码,如果有记住,则赋值给对应的框。
const remberInfo = ut.storage.get("REMBER");
if (remberInfo) {
this.ruleForm.username = remberInfo.username;
this.ruleForm.password = place;
this.remberOldPass = remberInfo.password;
}
},
};
</script>
<template>
<div class="main">
<el-form
:model="ruleForm"
:rules="rules"
ref="formIns"
class="user-layout-login"
>
<el-form-item prop="username">
<el-input
v-model.trim="ruleForm.username"
placeholder="请输入账号"
prefix-icon="el-icon-user"
></el-input>
</el-form-item>
<el-form-item prop="password">
<el-input
v-model.trim="ruleForm.password"
placeholder="请输入密码"
prefix-icon="el-icon-key"
show-password
></el-input>
</el-form-item>
<el-row :gutter="16">
<el-col :span="16">
<el-form-item prop="code">
<el-input
v-model.trim="ruleForm.code"
placeholder="请输入验证码"
prefix-icon="el-icon-mobile"
></el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<div
v-loading="captcha.loading"
class="captcha"
element-loading-spinner="el-icon-loading"
@click="queryCaptcha"
>
<img :src="captcha.img" alt="" />
</div>
</el-col>
</el-row>
<el-form-item prop="remember">
<el-checkbox v-model="ruleForm.remember">记住密码</el-checkbox>
</el-form-item>
<!-- <el-tabs v-model="activeName">
<el-tab-pane label="账号密码登录" name="account">
<el-form-item>
<el-input
placeholder="请输入账号"
prefix-icon="el-icon-user"
></el-input>
</el-form-item>
<el-form-item>
<el-input
placeholder="请输入密码"
prefix-icon="el-icon-key"
show-password
></el-input>
</el-form-item>
<el-row :gutter="16">
<el-col :span="16">
<el-form-item>
<el-input
placeholder="请输入验证码"
prefix-icon="el-icon-mobile"
></el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<div
class="captcha"
v-loading="true"
element-loading-spinner="el-icon-loading"
>
<img src="" alt="" />
</div>
</el-col>
</el-row>
<el-form-item>
<el-checkbox>记住密码</el-checkbox>
</el-form-item>
</el-tab-pane>
<el-tab-pane label="手机号登录" name="phone" disabled>
<el-form-item>
<el-input
placeholder="请输入手机号"
prefix-icon="el-icon-phone"
></el-input>
</el-form-item>
<el-row :gutter="16">
<el-col :span="16">
<el-form-item>
<el-input
placeholder="请输入验证码"
prefix-icon="el-icon-mobile"
></el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<button-again class="getCaptcha">发送验证码</button-again>
</el-col>
</el-row>
</el-tab-pane>
</el-tabs> -->
<button-again type="primary" class="login-button" @click="submit">
立即登录
</button-again>
</el-form>
</div>
</template>
<style lang="less" scoped>
.main {
min-width: 260px;
width: 368px;
margin: 0 auto;
.el-form-item {
margin-bottom: 18px;
}
.login-button {
font-size: 16px;
width: 100%;
}
.getCaptcha {
display: block;
width: 100%;
height: 40px;
}
.captcha {
position: relative;
height: 40px;
background: #ddd;
cursor: pointer;
img {
display: block;
width: 100%;
height: 100%;
&[src=""] {
display: none;
}
}
}
:deep(.el-loading-mask) {
background: transparent;
.el-icon-loading {
font-size: 26px;
}
.el-loading-spinner {
margin-top: -13px;
}
}
}
</style>
- fang/f20230705/ManageSystem/src/api/index.js
import http from "./http";
//获取验证码
const queryCaptchaImage = () => http.get("/captchaImage");
/* //扒到的接口信息:
/login
POST
{"username":"fastbee","password":"123456","code":"7","uuid":"31615b2fb707485db349886991b526c5"}
---
{
"msg": "操作成功",
"code": 200,
"token": "eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6Ijk5M2NmYTAyLTM2NTUtNGQwMy1iZDZiLTI2N2Q3MjRiZmNhMiJ9.sKVIlXZByTs58KNOUXbsseULwQl0rLE_Jl4M8sFVJPguasPJTpOcJfIpp7ITnMjAtRbQCMWoaoGnVjyjBo1MAQ"
}
*/
// 用户登录校验:
// body => {"username":"fastbee","password":"123456","code":"7","uuid":"31615b2fb707485db349886991b526c5"}
const checkUserLogin = (body) => {
return http.post("/login", body);
};
/* //扒到的接口:
/getInfo
GET
无参数「但是要求,在请求头中,基于 Authorization 把存储的token,传递给服务器才可以(以后所有请求都这样)」===> 请求拦截器中处理
=====
.....
*/
// 获取登录者信息-含权限信息。
// 还在得请求拦截器中做处理,把存的Token放到该请求的请求头中。
const queryUserProfile = ()=>http.get('/getInfo')
/* 暴露API */
const API = {
queryCaptchaImage,
checkUserLogin,
queryUserProfile,
};
export default API;
- fang/f20230705/ManageSystem/src/api/http.js
import axios from "axios";
import { Message } from "element-ui";
import _ from "@/assets/utils";
const http = axios.create({
baseURL: "/api",
timeout: 60000,
});
//对于除登录或获取验证码的接口外,其余所有接口请求,都需要基于请求头把Token传递给服务器。
const exclude = ["/captchaImage", "/login"];
http.interceptors.request.use((config) => {
const token = _.storage.get("TK"); //存储的名称越不语义化越好!
if (token && !exclude.includes(config.url)) {
config.headers["Authorization"] = token;
}
return config;
});
http.interceptors.response.use(
(response) => {
return response.data;
},
(reason) => {
Message.error("网络繁忙,稍后再试~");
return Promise.reject(reason);
}
);
export default http;
- fang/f20230705/ManageSystem/src/store/index.js
import Vue from "vue";
import API from "@/api";
import Vuex, { createLogger } from "vuex";
import VuexPersistence from "vuex-persist";
Vue.use(Vuex);
/* 配置插件 */
const vuexLocal = new VuexPersistence({
key: "vuex",
storage: window.localStorage,
/* reducer(state) {
//指定部分vuex状态持久化存储。
return {};
}, */
});
const env = process.env.NODE_ENV;
const plugins = [vuexLocal.plugin];
if (env === "development") {
plugins.push(createLogger());
}
/* 创建store容器 */
const store = new Vuex.Store({
strict: true,
plugins,
state: {
profile: null, //为null只能说明当前没存用户信息,而不能判断用户是否已经登录。
},
mutations: {
setProfile(state, profile) {
state.profile = profile;
},
},
actions: {
async setProfileAsync({ commit }) {
let profile = null;
try {
let { code, permissions, roles, user } = await API.queryUserProfile();
if (+code === 200) {
profile = {
permissions,
roles,
user,
};
commit("serProfile", profile);
}
} catch (error) {
console.log(`error:-->`, error);
}
return profile;
},
},
modules: {},
});
export default store;
记住密码的占位符
- 核心代码:
<script>
//import ut from "@/assets/utils";
// 具备有效期的LocalStorage存储
const storage = {
set(key, value) {
localStorage.setItem(
key,
JSON.stringify({
time: +new Date(),
value,
})
);
},
get(key, cycle = 2592000000) {
cycle = +cycle;
if (isNaN(cycle)) cycle = 2592000000;
let data = localStorage.getItem(key);
if (!data) return null;
let { time, value } = JSON.parse(data);
if (+new Date() - time > cycle) {
storage.remove(key);
return null;
}
return value;
},
remove(key) {
localStorage.removeItem(key);
},
};
const ut = {storage}
const place = "********"; //占位符:不能和用户自己输入的一致、需要能通过Form表单校验。
export default {
data() {
// 校验密码的格式。
const validatePassword = (_, value, callback) => {
// 和特殊占位符一致,直接通过。
if (value === place) {
callback();
}
//....其它校验
callback();
};
return {
//表单相关状态。
ruleForm: {
username: "",
password: "",
remember: true,
},
rules: {
code: [
// { required: true, message: "验证码是必填项哦~~", trigger: "blur" },
{ validtor: validatePassword, trigger: "blur" },
],
},
};
},
methods: {
// 登录校验。
async submit() {
try {
//2. 获取表单中的数据,向服务器发送请求。
let { username, password, code, remember } = this.ruleForm;
if (password === place) {
//说明用户没有改过密码。
password = this.remberOldPass;
}
// 登录成功后,如果有记住密码,就存储帐号密码到本地。
if (remember) {
ut.storage.set("REMBER", {
username,
password, //真实开发中一定要MD5加密。
});
} else {
ut.storage.remove("REMBER");
}
},
},
created() {
// 第一次渲染组件:验证是否有记住帐号密码,如果有记住,则赋值给对应的框。
const remberInfo = ut.storage.get("REMBER");
if (remberInfo) {
this.ruleForm.username = remberInfo.username;
this.ruleForm.password = place;
this.remberOldPass = remberInfo.password;
}
},
};
</script>
获取用户token
- fang/f20230705/ManageSystem/src/views/user/Login.vue
<script>
export default {
data() {
// 校验密码的格式。
const validatePassword = (_, value, callback) => {
callback();
};
return {
//控制选项卡。
activeName: "account",
// 验证码相关状态
captcha: {
img: "",
uuid: "",
loading: false,
},
//表单相关状态。
ruleForm: {
username: "",
password: "",
code: "",
remember: true,
},
rules: {
username: [
{ required: true, message: "帐号是必填项", trigger: "blur" },
],
password: [
{ required: true, message: "密码是必填项", trigger: "blur" },
],
code: [
// { required: true, message: "验证码是必填项哦~~", trigger: "blur" },
{ validtor: validatePassword, trigger: "blur" },
],
},
};
},
methods: {
//获取验证码
async queryCaptcha() {
this.captcha.loading = true;
try {
let { code, img, uuid } = await this.$API.queryCaptchaImage();
if (+code !== 200) {
this.$message.error(`网络出现异常,获取验证码失败`);
} else {
this.captcha.img = `data:image/jpeg;base64,${img}`; //因为服务器返回的图片默认没前缀。
this.captcha.uuid = uuid;
}
} catch (error) {
console.log(`error:-->`, error);
}
this.captcha.loading = false;
},
// 登录校验。
async submit() {
try {
//1. 先进行表单校验。
await this.$refs.formIns.validate();
//2. 获取表单中的数据,向服务器发送请求。
let { username, password, code, remember } = this.ruleForm;
if (password === place) {
//说明用户没有改过密码。
password = this.remberOldPass;
}
let {
code: resultCode,
token,
msg,
} = await this.$API.checkUserLogin({
username,
password,
code,
uuid: this.captcha.uuid,
}); //前提:API中设置了对应的接口。
if (+resultCode !== 200) {
//登录失败。
this.$message.error(msg);
// 重新获取验证码。
this.queryCaptcha();
this.ruleForm.code = "";
return;
}
//登录成功。
ut.storage.set("TK", token); //把token存储到本地上。
this.$message.success("恭喜你,登录成功了!");
// 跳转后的细节:
this.$router.push("/");
} catch (error) {
console.log(`error:-->`, error);
}
},
},
created() {
//第一次渲染组件:立即获取验证码
this.queryCaptcha();
},
};
</script>
<template>
<div class="main">
<el-form
:model="ruleForm"
:rules="rules"
ref="formIns"
class="user-layout-login"
>
<el-form-item prop="username">
<el-input
v-model.trim="ruleForm.username"
placeholder="请输入账号"
prefix-icon="el-icon-user"
></el-input>
</el-form-item>
<el-form-item prop="password">
<el-input
v-model.trim="ruleForm.password"
placeholder="请输入密码"
prefix-icon="el-icon-key"
show-password
></el-input>
</el-form-item>
<el-row :gutter="16">
<el-col :span="16">
<el-form-item prop="code">
<el-input
v-model.trim="ruleForm.code"
placeholder="请输入验证码"
prefix-icon="el-icon-mobile"
></el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<div
v-loading="captcha.loading"
class="captcha"
element-loading-spinner="el-icon-loading"
@click="queryCaptcha"
>
<img :src="captcha.img" alt="" />
</div>
</el-col>
</el-row>
<el-form-item prop="remember">
<el-checkbox v-model="ruleForm.remember">记住密码</el-checkbox>
</el-form-item>
<button-again type="primary" class="login-button" @click="submit">
立即登录
</button-again>
</el-form>
</div>
</template>
- fang/f20230705/ManageSystem/src/api/index.js
import http from "./http";
//获取验证码
const queryCaptchaImage = () => http.get("/captchaImage");
/* //扒到的接口信息:
/login
POST
{"username":"fastbee","password":"123456","code":"7","uuid":"31615b2fb707485db349886991b526c5"}
---
{
"msg": "操作成功",
"code": 200,
"token": "eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6Ijk5M2NmYTAyLTM2NTUtNGQwMy1iZDZiLTI2N2Q3MjRiZmNhMiJ9.sKVIlXZByTs58KNOUXbsseULwQl0rLE_Jl4M8sFVJPguasPJTpOcJfIpp7ITnMjAtRbQCMWoaoGnVjyjBo1MAQ"
}
*/
// 用户登录校验:
// body => {"username":"fastbee","password":"123456","code":"7","uuid":"31615b2fb707485db349886991b526c5"}
const checkUserLogin = (body) => {
return http.post("/login", body);
};
/* 暴露API */
const API = {
queryCaptchaImage,
checkUserLogin,
};
export default API;
全局守卫做登录态校验
异步获取登录者信息
页面路由跳转间的Loading
修改页面的标题
回退功能
- 可以进入到登录页的情况:
- 手动输入/user/login进来的。
- 登录成功:首页 - push();
- 我想进入/iot/template,但是因为没有登录,跳转到登录页。
- 登录成功:目的地/iot/template - replace。
- 需要:
- 跳转到登录页的时候,需要告知登录页目的地的地址-问号传参。
- /user/login?target=/iot/template
- 跳转到登录页的时候,需要告知登录页目的地的地址-问号传参。
- 点击退出登录,进入登录页。可以和2保持一致,跳转回之前的。也可以和1保持一致,跳转到首页。
- 手动输入/user/login进来的。
回退功能代码
有了回退后的全部代码
- fang/f20230705/ManageSystem/src/views/user/Login.vue
<script>
import ut from "@/assets/utils";
// 具备有效期的LocalStorage存储
const storage = {
set(key, value) {
localStorage.setItem(
key,
JSON.stringify({
time: +new Date(),
value,
})
);
},
get(key, cycle = 2592000000) {
cycle = +cycle;
if (isNaN(cycle)) cycle = 2592000000;
let data = localStorage.getItem(key);
if (!data) return null;
let { time, value } = JSON.parse(data);
if (+new Date() - time > cycle) {
storage.remove(key);
return null;
}
return value;
},
remove(key) {
localStorage.removeItem(key);
},
};
const place = "********"; //占位符:不能和用户自己输入的一致、需要能通过Form表单校验。
export default {
data() {
// 校验密码的格式。
const validatePassword = (_, value, callback) => {
// 和特殊占位符一致,直接通过。
if (value === place) {
callback();
}
if (value.length === 0) {
return callback(new Error("密码是必填项哦"));
}
/* //项目中:
let reg = /^\S*(?=\S{6,})(?=\S*\d)(?=\S*[A-Z])(?=\S*[a-z])(?=\S*[!@#$%^&*? ])\S*$/;
if (!reg.test(value)) {
return callback(new Error("密码格式有误"));
} */
callback();
};
return {
//控制选项卡。
activeName: "account",
// 验证码相关状态
captcha: {
img: "",
uuid: "",
loading: false,
},
//表单相关状态。
ruleForm: {
username: "",
password: "",
code: "",
remember: true,
},
rules: {
username: [
{ required: true, message: "帐号是必填项", trigger: "blur" },
],
password: [
{ required: true, message: "密码是必填项", trigger: "blur" },
],
code: [
// { required: true, message: "验证码是必填项哦~~", trigger: "blur" },
{ validtor: validatePassword, trigger: "blur" },
],
},
};
},
methods: {
//获取验证码
async queryCaptcha() {
this.captcha.loading = true;
try {
let { code, img, uuid } = await this.$API.queryCaptchaImage();
if (+code !== 200) {
this.$message.error(`网络出现异常,获取验证码失败`);
} else {
this.captcha.img = `data:image/jpeg;base64,${img}`; //因为服务器返回的图片默认没前缀。
this.captcha.uuid = uuid;
}
} catch (error) {
console.log(`error:-->`, error);
}
this.captcha.loading = false;
},
// 登录校验。
async submit() {
try {
//1. 先进行表单校验。
await this.$refs.formIns.validate();
// this.$message.success('哈哈')
//2. 获取表单中的数据,向服务器发送请求。
let { username, password, code, remember } = this.ruleForm;
if (password === place) {
//说明用户没有改过密码。
password = this.remberOldPass;
}
let {
code: resultCode,
token,
msg,
} = await this.$API.checkUserLogin({
username,
password,
code,
uuid: this.captcha.uuid,
}); //前提:API中设置了对应的接口。
if (+resultCode !== 200) {
//登录失败。
this.$message.error(msg);
// 重新获取验证码。
this.queryCaptcha();
this.ruleForm.code = "";
return;
}
//登录成功。
ut.storage.set("TK", token); //把token存储到本地上。
await this.$store.dispatch("setProfileAsync"); //获取登录者信息。前提:API中设置了对应的接口。同时有token-请求拦截器中放置会把当前接口带上token。在vuex中设置了关于用户信息的异步请求接口。
// 登录成功后,如果有记住密码,就存储帐号密码到本地。
if (remember) {
ut.storage.set("REMBER", {
username,
password, //真实开发中一定要MD5加密。
});
} else {
ut.storage.remove("REMBER");
}
this.$message.success("恭喜你,登录成功了!");
// 跳转后的细节:
let target = this.$route.query.target;
target ? this.$router.replace(target) : this.$router.push("/");
} catch (error) {
console.log(`error:-->`, error);
}
},
},
created() {
//第一次渲染组件:立即获取验证码
this.queryCaptcha();
// 第一次渲染组件:验证是否有记住帐号密码,如果有记住,则赋值给对应的框。
const remberInfo = ut.storage.get("REMBER");
if (remberInfo) {
this.ruleForm.username = remberInfo.username;
this.ruleForm.password = place;
this.remberOldPass = remberInfo.password;
}
},
};
</script>
<template>
<div class="main">
<el-form
:model="ruleForm"
:rules="rules"
ref="formIns"
class="user-layout-login"
>
<el-form-item prop="username">
<el-input
v-model.trim="ruleForm.username"
placeholder="请输入账号"
prefix-icon="el-icon-user"
></el-input>
</el-form-item>
<el-form-item prop="password">
<el-input
v-model.trim="ruleForm.password"
placeholder="请输入密码"
prefix-icon="el-icon-key"
show-password
></el-input>
</el-form-item>
<el-row :gutter="16">
<el-col :span="16">
<el-form-item prop="code">
<el-input
v-model.trim="ruleForm.code"
placeholder="请输入验证码"
prefix-icon="el-icon-mobile"
></el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<div
v-loading="captcha.loading"
class="captcha"
element-loading-spinner="el-icon-loading"
@click="queryCaptcha"
>
<img :src="captcha.img" alt="" />
</div>
</el-col>
</el-row>
<el-form-item prop="remember">
<el-checkbox v-model="ruleForm.remember">记住密码</el-checkbox>
</el-form-item>
<!-- <el-tabs v-model="activeName">
<el-tab-pane label="账号密码登录" name="account">
<el-form-item>
<el-input
placeholder="请输入账号"
prefix-icon="el-icon-user"
></el-input>
</el-form-item>
<el-form-item>
<el-input
placeholder="请输入密码"
prefix-icon="el-icon-key"
show-password
></el-input>
</el-form-item>
<el-row :gutter="16">
<el-col :span="16">
<el-form-item>
<el-input
placeholder="请输入验证码"
prefix-icon="el-icon-mobile"
></el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<div
class="captcha"
v-loading="true"
element-loading-spinner="el-icon-loading"
>
<img src="" alt="" />
</div>
</el-col>
</el-row>
<el-form-item>
<el-checkbox>记住密码</el-checkbox>
</el-form-item>
</el-tab-pane>
<el-tab-pane label="手机号登录" name="phone" disabled>
<el-form-item>
<el-input
placeholder="请输入手机号"
prefix-icon="el-icon-phone"
></el-input>
</el-form-item>
<el-row :gutter="16">
<el-col :span="16">
<el-form-item>
<el-input
placeholder="请输入验证码"
prefix-icon="el-icon-mobile"
></el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<button-again class="getCaptcha">发送验证码</button-again>
</el-col>
</el-row>
</el-tab-pane>
</el-tabs> -->
<button-again type="primary" class="login-button" @click="submit">
立即登录
</button-again>
</el-form>
</div>
</template>
<style lang="less" scoped>
.main {
min-width: 260px;
width: 368px;
margin: 0 auto;
.el-form-item {
margin-bottom: 18px;
}
.login-button {
font-size: 16px;
width: 100%;
}
.getCaptcha {
display: block;
width: 100%;
height: 40px;
}
.captcha {
position: relative;
height: 40px;
background: #ddd;
cursor: pointer;
img {
display: block;
width: 100%;
height: 100%;
&[src=""] {
display: none;
}
}
}
:deep(.el-loading-mask) {
background: transparent;
.el-icon-loading {
font-size: 26px;
}
.el-loading-spinner {
margin-top: -13px;
}
}
}
</style>
- fang/f20230705/ManageSystem/src/router/routes.js
import BasicLayout from "@/layout/BasicLayout.vue";
import UserLayout from "@/layout/UserLayout.vue";
import childRoutes from "./childRoutes";
// 基础路由
const routes = [
{
path: "/user",
name: "user",
meta: {
title: "",
level: 1,
},
component: UserLayout,
redirect: "/user/login",
children: [
{
path: "login",
name: "user_login",
meta: {
title: "用户登录",
level: 2,
},
component: () =>
import(/* webpackChunkName: "user" */ "@/views/user/Login.vue"),
},
],
},
{
path: "/",
name: "home",
meta: {
title: "",
level: 1,
},
component: BasicLayout,
redirect: "/index/welcome",
children: childRoutes,
},
{
path: "*",
name: "error",
meta: {
title: "404页面",
level: 1,
},
component: () =>
import(/* webpackChunkName: "error" */ "@/views/ErrorPage.vue"),
},
];
export default routes;
- fang/f20230705/ManageSystem/src/router/index.js
import Vue from "vue";
import store from "@/store";
import { Message, Loading } from "element-ui";
import VueRouter from "vue-router";
import routes from "./routes";
Vue.use(VueRouter);
/* 创建路由管理 */
const router = new VueRouter({
mode: "hash",
routes,
});
// 导航守卫。
// 全局前置守卫:登录态的校验。
let ignore = ["user_login", "user", "error"]; //根据路由对象的name来忽略对页面的校验。
let loadingIns = null;
router.beforeEach(async (to, from, next) => {
//开启Loading。
if (!loadingIns) {
loadingIns = Loading.service({
text: "正在加载中.....",
spinner: "el-icon-loading",
background: "rgba(0, 0, 0, 0.7)",
});
}
//异步获取登录者信息。
let profile = store.state.profile;
//跳转的路由不是ignore中的,并且vuex中没有存储登录者信息,此时我们需要异步派发任务,实现登录态校验。
if (!ignore.includes(to.name) && !profile) {
profile = await store.dispatch("setProfileAsync");
if (!profile) {
//发送异步请求后,都没有获取登录者信息,则说明当前用户就是没登录。
Message.warning("你还没登录,请你先登录");
next({
path: "/user/login",
query: {
target: to.fullPath,//用于登录成功后,直接进入想要进去的页面。
},
});
return;
}
}
next();
});
// 全局后置守卫。
router.afterEach((to, from) => {
//关闭Loading。
if (loadingIns) {
loadingIns.close();
loadingIns = null;
}
// 修改页面的标题
let { title } = to.meta;
document.title = title ? `${title} - 物联网管理系统` : `物联网管理系统`;
});
export default router;
- fang/f20230705/ManageSystem/src/store/index.js
import Vue from "vue";
import API from "@/api";
import Vuex, { createLogger } from "vuex";
import VuexPersistence from "vuex-persist";
Vue.use(Vuex);
/* 配置插件 */
const vuexLocal = new VuexPersistence({
key: "vuex",
storage: window.localStorage,
/* reducer(state) {
//指定部分vuex状态持久化存储。
return {};
}, */
});
const env = process.env.NODE_ENV;
const plugins = [vuexLocal.plugin];
if (env === "development") {
plugins.push(createLogger());
}
/* 创建store容器 */
const store = new Vuex.Store({
strict: true,
plugins,
state: {
profile: null, //为null只能说明当前没存用户信息,而不能判断用户是否已经登录。
},
mutations: {
setProfile(state, profile) {
state.profile = profile;
},
},
actions: {
async setProfileAsync({ commit }) {
let profile = null;
try {
let { code, permissions, roles, user } = await API.queryUserProfile();
if (+code === 200) {
profile = {
permissions,
roles,
user,
};
commit("serProfile", profile);
}
} catch (error) {
console.log(`error:-->`, error);
}
return profile;
},
},
modules: {},
});
export default store;
- fang/f20230705/ManageSystem/src/api/index.js
import http from "./http";
//获取验证码
const queryCaptchaImage = () => http.get("/captchaImage");
/* //扒到的接口信息:
/login
POST
{"username":"fastbee","password":"123456","code":"7","uuid":"31615b2fb707485db349886991b526c5"}
---
{
"msg": "操作成功",
"code": 200,
"token": "eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6Ijk5M2NmYTAyLTM2NTUtNGQwMy1iZDZiLTI2N2Q3MjRiZmNhMiJ9.sKVIlXZByTs58KNOUXbsseULwQl0rLE_Jl4M8sFVJPguasPJTpOcJfIpp7ITnMjAtRbQCMWoaoGnVjyjBo1MAQ"
}
*/
// 用户登录校验:
// body => {"username":"fastbee","password":"123456","code":"7","uuid":"31615b2fb707485db349886991b526c5"}
const checkUserLogin = (body) => {
return http.post("/login", body);
};
/* //扒到的接口:
/getInfo
GET
无参数「但是要求,在请求头中,基于 Authorization 把存储的token,传递给服务器才可以(以后所有请求都这样)」===> 请求拦截器中处理
=====
.....
*/
// 获取登录者信息-含权限信息。
// 还在得请求拦截器中做处理,把存的Token放到该请求的请求头中。
const queryUserProfile = ()=>http.get('/getInfo')
/* 暴露API */
const API = {
queryCaptchaImage,
checkUserLogin,
queryUserProfile,
};
export default API;
- fang/f20230705/ManageSystem/src/api/http.js
import axios from "axios";
import { Message } from "element-ui";
import _ from "@/assets/utils";
const http = axios.create({
baseURL: "/api",
timeout: 60000,
});
//对于除登录或获取验证码的接口外,其余所有接口请求,都需要基于请求头把Token传递给服务器。
const exclude = ["/captchaImage", "/login"];
http.interceptors.request.use((config) => {
const token = _.storage.get("TK"); //存储的名称越不语义化越好!
if (token && !exclude.includes(config.url)) {
config.headers["Authorization"] = token;
}
return config;
});
http.interceptors.response.use(
(response) => {
return response.data;
},
(reason) => {
Message.error("网络繁忙,稍后再试~");
return Promise.reject(reason);
}
);
export default http;
路由跳转
面包屑导航
处理头像
全局混入的方法
用户下拉方法
解除首页点击回退再到首页的loading问题
首页左侧菜单
- 根据扁平化的路由表得到菜单选项,并循环出来对应的页面。
首页上方的访问历史列表
历史列表的事件委托
历史记录关掉的操作
历史记录新增的操作
进阶参考
文章来源地址https://www.toymoban.com/news/detail-542594.html
文章来源:https://www.toymoban.com/news/detail-542594.html
到了这里,关于20230707----重返学习-物美管理系统-登录流程-页面路由跳转-面包屑导航-访问历史列表的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!