插件的使用(2)
# vuex
vuex 状态管理器 让多个组件共享数据, 开发中通常用来保持当前的登陆状态
★ 一定要注意一点, 一旦刷新页面, vuex中的数据就没有了!!
vuex存储的数据只是在页面中, 相当于全局变量, 页面刷新时vuex里的数据会重新初始化, 导致数据丢失!!
vuex里的数据是保存在运行内存中的, 当页面刷新时, 页面会重新加载vue实例, vuex里面的数据会被重新赋值!!
先进行安装, 跟router的安装一样, 有两种方式.
方式一: npm install vue-vuex --save
并需手动创建文件和进行配置
方式二: vue add vuex
(会自动创建文件并进行配置) 选它!!
# 综合示例
我们以一个登陆的综合案例来完成以下几个需求:
此示例中的登陆跳转含顶部!
1> 未登录,导航栏右侧显示登录按钮; 登陆成功后,登陆按钮处显示用户名
技术点: v-if v-else vuex 计算属性 (之所以使用计算属性,涉及到单页面应用,组件间切换时,生命周期的开始结束)
2> 解决刷新页面后,登陆状态就消失了
技术点: 浏览器localStorage持久化保存登陆状态
3> 登陆成功后,当前登陆的用户名左边显示 有多少条未读私信. “需保证刷新后,不会归0”
登陆成功后,点击导航栏上的资讯按钮,进入该页面后,点击“发送一条私信按钮” 用户名 左边的 未读私信的条数会加1.
点击 “全读” 按钮,私信条数清0.
技术点: 计算属性、localStorage
4> 未登录,首页和登录页面可以访问,资讯和直播页面需要登录后才能访问
技术点: 导航守卫
5> 添加退出登录按钮,回归未登录的状态
2
3
4
5
6
7
8
9
10
11
12
关键代码逻辑截图如下:
# Q&A
蛮重要的, 请细品.
# 第一个问题
代入上方的综合示例中,App.vue是根组件,关于name常量,若我们写成 const name = store.state.username
你会看到这样的现象,登陆成功后,依旧显示的是登陆按钮,不会显示当前登陆用户.
但如果我们此时刷新下页面.登陆用户就显示出来啦!
如何解释?
第一点:
在router的配置文件中写了两个路由, 其name分别为One、Two, 它们都会渲染到App.vue的<router-view/>处 (单页面应用)!!
One组件先渲染到此处,开始它的生命周期,当<router-view/>处切换到渲染Two组件时,One组件的生命周期结束..
当再次切换One组件时,One组件会开始新一轮的生命周期.
第二点:
♀ 只要不刷新,不管访问啥地址,一直访问的都是App.vue, 就像前面vue-route所说, "同一组件间路由切换, 是复用"!
所以登陆成功,App.vue还是那个App.vue,name的赋值语句不会重新执行..
举个例子.
192.168.106:8080/news --> 192.168.106:8080/live
App.vue还是那个App.vue,只是在`<router-view/>`news组件切换到了live组件 live组件开始新的生命周期,news结束
♀ 当刷新的时候,App.vue的生命周期重新加载!name的赋值语句重新执行.
进一步说
当我们刷新,重新访问/加载 192.168.106:8080/news
App.vue里的代码会重新执行.News组件会重新渲染到App.vue的<router-view/>处!!
借此,我们还可以解释,App.vue中的name和letterNums为啥要用计算属性.
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
做了个小实验对第一点进行验证, 在App live 和 News这三个组件中添加类似于这样的代码:
<script setup>
import {onUnmounted} from 'vue';
console.log("开始Live的生命周期");
onUnmounted(()=>{
console.log("Live的生命周期结束")
})
</script>
2
3
4
5
6
7
验证结果: (ps,页面刷新,浏览器控制台的打印会清空)
切换到哪个组件, 哪个组件就会开始新一轮的生命周期 但上一个组件的销毁在当前切换组件生命周期开始的后面.
# 第二个问题
在vue-router的笔记中, 我们阐述了两种登陆,含顶部和不含顶部的.
如果是不含顶部的登陆代码, 我们发现name是不需要计算属性的!! letterNums需要加计算属性.
这又是为何?
其实第二个问题的本质还是第一个问题.
分析缘由,从路由的结构入手,以下是 不含顶部 的路由结构. 分为了两个一级组件,MenuView和LoginView,MenuView下有嵌套!
但要注意,'/'路由加载的是MenuView,但未给他设置name属性,当加载时,会一并加载嵌套的HomeView组件.
于是,我猜想,从LoginView组件登陆成功后,跳转Home路由. MenuView、HomeView都会重新开始生命周期.
这就导致MenuView里的name赋值语句会重新运行,所以name就不需要计算属性.
而letterNums,是News组件干的,只涉及二级组件在MenuView的<router-view/>处的切换,MenuView组件不会重新渲染
所以,letterNums需要计算属性!!
const routes = [
{
path: '/',
component: MenuView,
children: [
{
path: '',
name: "Index",
component: HomeView,
},
{
path: '/home',
name: 'Home',
redirect: {name: 'Index'},
},
{
path: '/news',
name: 'News',
component: () => import('../views/NewsView.vue')
},
{
path: '/live',
name: 'Live',
component: () => import('../views/LiveView.vue')
},
],
},
{
path: '/login',
name: 'Login',
component: () => import('../views/LoginView.vue')
},
]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
做了个小实验进行佐证!! 验证过程动图如下.
# 第三个问题
是否可以用 localstorage代替Vuex.
对于不变的数据确实可以,但是当两个组件共用一个数据源(对象或数组)时.如果其中的一个组件改变了该数据源,希望另一个组件响应该变化时
Vuex才是首选,loaclstorage是无法做到的.
而且在vuex中,对于数据的修改可以先做一系列的验证! 有解藕的功效!!
vuex中的数据在内存里,loaclstorage的数据在本地文件里,从内存中读取的速度更快.
2
3
4
5
6
# vue3-cookies
官方文档:
https://www.npmjs.com/package/vue3-cookies
需要通过npm安装后, 手动进行文件的配置.
// --save、-S参数意思是把模块的版本信息保存到dependencies(生产环境依赖)中,即package.json文件的dependencies字段中
// 从npm 5.0.0 开始,npm install 默认就使用 --save 选项,不需要再加这个选项
npm install vue3-cookies --save
// 在main.js中配置
import {createApp} from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import VueCookies from 'vue3-cookies'
createApp(App).use(store).use(router).use(VueCookies).mount('#app')
// 使用cookies
import {useCookies} from 'vue3-cookies'
const {cookies} = useCookies();
cookies.set("ts", "123123", "10s") // !!可以设置过期时间
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
举个例子, 我们可以将上面vuex的综合示例中使用localStorage的地方全换成使用cookies!! 关键代码如下:
/* 在src下新建plugins文件夹.在里面创建index.js文件!*/
import {useCookies} from "vue3-cookies";
const {cookies} = useCookies(); // es6里的解构赋值,因为useCookies()是一个对象.
export const getToken = () => {
return cookies.get("token") || "";
}
export const getUserName = () => {
return cookies.get("username") || "";
}
export const setUserToken = (user, token) => {
cookies.set('username', user, "30s");
cookies.set('token', token, "30s");
}
export const clearUserToken = () => {
cookies.remove("username");
cookies.remove("token");
}
/* 将使用localStorage的地方全换成使用cookies */
import {createStore} from 'vuex'
import {getToken, getUserName,setUserToken,clearUserToken} from "@/plugins/cookie";
export default createStore({
state: {
// username: localStorage.getItem('username'),
// token: localStorage.getItem('token'),
username: getUserName(),
token: getToken(),
car: localStorage.getItem('carNum') || 0,
},
getters: {},
mutations: {
login(state, {username, token}) {
state.username = username;
state.token = token;
// localStorage.setItem('username', username);
// localStorage.setItem('token', token);
setUserToken(username,token);
},
logout(state) {
state.username = "";
state.token = "";
// localStorage.removeItem('username');
// localStorage.removeItem('token');
clearUserToken();
},
addCar(state) {
state.car = parseInt(state.car) + 1;
localStorage.setItem('carNum', state.car);
}
},
actions: {},
modules: {}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# axios
重点关注基于vue3的使用!
proxy.$axios.get(`/tran/`, {params: search_params}).then((res) => {}).catch(error => {})
proxy.$axios.post(`/tran/`, {k1:123, k2:456}, {params: search_params}).then((res) => {}).catch(error => {})
2
# 后端代码
下面的示例, 都使用这个后端代码.
pip install django-cors-headers==3.8.0 + 注册'corsheaders' + 添加中间件 + 在setting文件里配置
先复习一点知识
GET or POST: url中的参数 - query_params; 请求头中的 - META、headers都行,注意会加HTTP_.
POST: 请求体中的数据 - data (一般在使用认证类时,前端需要在http请求头中添加token信息)
因为浏览器的同源策略,前后端交互时会出现跨域问题.
解决跨域有很多种办法:
1> 后端使用cors技术.
2> 使用nginx做转发.
3> 前端做代理: 注意, 它只适合测试阶段使用,项目上线后是没有的.
当前浏览器访问的地址 与 向后端API发送网络请求的地址 同源策略!!
请求能发过去,后端api能接收到并处理数据,能返回. 但到浏览器那被拦截了.
2
3
4
5
6
7
8
url.py
from django.urls import path
from api import views
urlpatterns = [
path('user/', views.UserView.as_view()),
]
2
3
4
5
6
views.py
from rest_framework.views import APIView
from rest_framework.response import Response
class UserView(APIView):
# GET请求解决跨域: 'Access-Control-Allow-Origin': '*'
def get(self, request):
return Response(
{'name': '路飞', 'age': '19'},
headers={'Access-Control-Allow-Origin': '*'}
)
# POST请求解决跨域: 借助django-cors-headers (它一并将GET请求的跨域解决了)
def post(self, request):
print(request.META.get("HTTP_TOKEN"))
username = request.data.get('username')
password = request.data.get('password')
if username == "lqz" and password == "123":
return Response({'code': 100, 'msg': "登陆成功!"})
else:
return Response({'code': 101, 'msg': "用户名或密码错误!"})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 基于vue2
该示例是基于vue2实现的,而且没有使用脚手架. 只是axios的简单使用. (无需过多关注,后续开发都使用的vue3)
前端利用axios向后端发送了get和post请求!!post请求在请求体中添加表单数据,在请求头中添加token信息.
安装 npm install -S axios
- 导入 import axios from "axios";
- 使用它发送get/post请求,获取api数据渲染到页面上!
(注意代码中打上五角星的地方!!)
App.vue
<template>
<div id="father">
<span style="color: rgb(128,128,128)">需求: 点击按钮发送get请求获取api数据!</span> <br>
<p>
姓名: {{ name ? name : "暂无" }} | 年龄: {{ age ? age : "暂无" }}
<button @click="handleClick">获取数据!</button>
</p>
<p>后端返回数据: {{ get_data }}</p>
<hr>
<span style="color: rgb(128,128,128);">需求: 前端通过表单向后端发送post请求,请求体和请求头中都携带数据!</span>
<span style="color: rgb(128,128,128);">登陆失败弹出提示框,登陆成功跳转百度页面.</span>
<p>用户名: <input type="text" v-model="username"></p>
<p>密码: <input type="password" v-model="password"></p>
<button @click="handleSubmit">登陆</button>
<p>后端返回数据: {{ post_data }}</p>
</div>
</template>
<style>
#father {
width: 500px;
margin: auto;
padding: 10px;
border: 1px solid bisque;
}
</style>
<script>
import axios from "axios"; // 就像py中安装第三方模块,直接导入模块名就可以使用
export default {
name: 'App',
data() {
return {
name: "",
age: "",
username: "",
password: "",
get_data: "",
post_data: "",
}
},
methods: {
// ★ get请求一般不会通过点击获取后端api数据,而是在生命周期create函数里就发送axios请求了!
handleClick() {
axios.get("http://127.0.0.1:8000/user/").then(res => {
// 注意: 真正的数据在res.data里
this.get_data = res.data
this.name = res.data.name
this.age = res.data.age
})
},
// ★ Post请求,数据放到http的请求体里,token信息放到http的请求头里!
handleSubmit() {
// axios.post(url,请求体,请求头).then()
axios.post("http://127.0.0.1:8000/user/", {
username: this.username,
password: this.password,
}, {
headers: {"token": "abcdefgh"}
},
).then(res => {
this.post_data = res.data
if (res.data.code === 100) {
setTimeout(() => location.href = "https://www.baidu.com/", 2000)
} else {
alert("登陆失败!")
}
})
},
},
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# 基于vue3
安装:
npm install axios --save
orvue add axios
选前者, 因为若选后者, 也需在其自动生成的配置文件上进行修改. 还不如一开始就自己手动配置!
★★★ 有两种方式使用axios!!
方式一: 创建axios对象,页面中导入并实现.
方式二: 创建axios对象,设置vue对象中,其他页面获取vue对象.
简单来说
运行命令 `npm install axios --save` 安装好后, 在src目录下新建 文件夹plugins, 在该目录下 创建文件 axios.js
▲方式一:
src/plugins/axios.js的最后
export default _axios;
LoginView.vue里
import _axios from "@/plugins/axios";
_axios.post().then(res => {console.log(res.data)})
▲方式二:
思想: 通过将属性或方法添加到globalProperties中,可以使它们在应用的任何组件中都可以访问到,而不需要在每个组件中单独引入或定义!
src/plugins/axios.js的最后
export function installAxios(Vue) {
Vue.config.globalProperties.$axios = _axios; // 相当于在vue实例里加入了$axios这个成员!
}
src/main.js里
import {installAxios} from "@/plugins/axios";
const app = createApp(App)
installAxios(app)
app.use(store).use(router).use(ElementPlus).mount('#app')
LoginView.vue里
import {getCurrentInstance, ref} from 'vue'
const {proxy} = getCurrentInstance()
proxy.$axios.post().then(res => {console.log(res.data)})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 拦截器
里面比较重要的知识点是 拦截器! 它是基于promise实现的,不用深究, 知道它如何用就行!!
针对拦截器先来一点科普.
可查看该博客辅助理解: https://juejin.cn/post/7100470316857557006
axios的拦截器是一种机制,它允许你在发送请求或接收响应之前对它们进行拦截和处理.
因此,axios提供了两种类型的拦截器:请求拦截器和响应拦截器.
1> 请求拦截器
在所有的请求发送前进行必要操作处理,例如添加统一cookie、请求体加验证、设置请求头等,相当于是对每个接口里相同操作的一个封装;
2> 响应拦截器
在所有的请求得到响应之后,对响应体的一些处理,通常是数据统一处理等,也常来判断登录失效等.
另外,通过 `axios.interceptors.request.use` 和 `axios.interceptors.response.use` 方法来添加拦截器.
这两个方法都接受两个参数:一个处理成功的回调函数和一个处理失败的回调函数.
具体来说,请求失败和响应失败是两种不同的情况.
- 请求失败是指在发送请求的过程中出现了错误,例如网络连接失败、请求超时等.
这种情况下,请求可能根本没有发送到服务器,因此无法收到任何响应.
在 axios 中,请求失败会触发 `axios.interceptors.request` 中的第二个回调函数.
- 响应失败是指在请求已经发送并收到了服务器的响应,但是服务器返回的<"状态码">表示请求失败.
例如: 服务器返回的状态码为 404 表示请求的资源不存在; 返回的状态码为 500 表示服务器内部错误等.
我们熟知的drf认证失败,返回401.
在 axios 中, 响应失败会触发 `axios.interceptors.response` 中的第二个回调函数.
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
★ 特别注意!! 响应成功不代表真正成功!!
<状态码是200>只能说拿到了后端API返回的数据, 真正的成功要看返回数据中的<"返回码">,通常是code字段值!!
# 方式一
代码是嘛样的, 看下方.
运行命令 npm install axios --save
安装好后, 在src目录下新建 文件夹plugins, 在该目录下 创建文件 axios.js
src/plugins/axios.js
import axios from "axios";
// import {useRouter} from "vue-router";
// const router = useRouter();
/*
1> 基于axios发送请求时,请求url的前缀
axios.defaults.baseURL = 'http://127.0.0.1:8000/api/';
2> 发送请求时,携带请求头 common表示所有请求发送时,都会携带该请求头
import {getToken} from "@/plugins/cookie";
axios.defaults.headers.common['Authorization'] = getToken();
3> 发送请求时,携带请求头 post表示只要发送post请求时,才会携带该请求头
axios.defaults.headers.post['Content-Type'] = 'application/json';
★★注意!! 添加请求头未添加上的话要想到预检!后端没在CORS_ALLOW_HEADERS中配置里添加允许该请求头.
*/
axios.defaults.baseURL = 'http://127.0.0.1:8000/';
/* 通过axios.create()创建一个axios对象时,可以添加一些参数/做一些配置 */
let config = {
// baseURL: process.env.baseURL || process.env.apiUrl || ""
// timeout: 60 * 1000, // Timeout
// withCredentials: true, // Check cross-site Access-Control
};
const _axios = axios.create(config);
/* 请求拦截器 */
_axios.interceptors.request.use(
function (config) {
console.log(config)
// 这里是有token,才在请求头中携带token ;
// 而在上面axios.defaults.headers.common['Authorization'] = "xxx";是有没有token都会有这个请求头
const token = "xxxxxx"
if (token) {
config.headers['token'] = token
}
return config;
}
);
/* 响应拦截器 */
// ★★★ 浏览器上有Token,但是Token在后端已经失效 -- 必须这样解决!! (失不失效后端说的算,而不是看前端)
_axios.interceptors.response.use(
// 前后端可以约定,返回码101表示用户名或密码错误,登陆失败;返回码102表示token过期,登陆失败.
function (response) {
if (response.data.code === 102) {
// 登陆失败,返回登陆界面,通常还需清除本地token信息..
return Promise.reject(); // 执行了这句,就拦腰斩断,不往后走了.
}
// 该返回的数据会被axios.then(res)的参数res接收
return response;
},
function (error) {
// if (error.response.status === 401) {
//
// }
// 该返回的数据会被axios.catch(err)中的error接收
return Promise.reject(error);
}
);
export default _axios;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
LoginView.vue
<template>
<div class="about">
<h1>This is an Login page</h1>
<div>
姓名: <input type="text" v-model="username"> <br>
密码: <input type="text" v-model="password"> <br>
<input type="button" value="登陆" @click="doLogin" style="margin-left: 148px">
</div>
</div>
</template>
<script setup>
import {ref} from "vue";
import _axios from "@/plugins/axios";
const username = ref("")
const password = ref("")
function doLogin() {
_axios.post("user/", {
username: username.value,
password: password.value,
}).then(res => {
console.log(res.data)
}).catch(error => {
console.log(error)
})
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 方式二
代码是嘛样的, 看下方.
运行命令 npm install axios --save
安装好后, 在src目录下新建 文件夹plugins, 在该目录下 创建文件 axios.js
src/plugins/axios.js
import axios from "axios";
axios.defaults.baseURL = 'http://127.0.0.1:8000/';
let config = {
// baseURL: process.env.baseURL || process.env.apiUrl || ""
// timeout: 60 * 1000, // Timeout
// withCredentials: true, // Check cross-site Access-Control
};
const _axios = axios.create(config);
// 下面的请求拦截器和响应拦截器啥也没做.
_axios.interceptors.request.use(
function (config) {
// Do something before request is sent
return config;
},
function (error) {
// Do something with request error
return Promise.reject(error);
}
);
_axios.interceptors.response.use(
function (response) {
// Do something with response data
return response;
},
function (error) {
// Do something with response error
return Promise.reject(error);
}
);
// export default _axios; // 方式一
// 方式二 方式二和方式一不同的地方在这里!!
export function installAxios(Vue) {
Vue.config.globalProperties.$axios = _axios;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
src/main.js
import './plugins/axios'
import {createApp} from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import {installAxios} from "@/plugins/axios";
const app = createApp(App)
installAxios(app)
app.use(store).use(router).use(ElementPlus).mount('#app')
2
3
4
5
6
7
8
9
10
11
12
13
LoginView.vue
<template>
<div class="about">
<h1>This is an Login page</h1>
<div>
姓名: <input type="text" v-model="username"> <br>
密码: <input type="text" v-model="password"> <br>
<input type="button" value="登陆" @click="doLogin" style="margin-left: 148px">
</div>
</div>
</template>
<script setup>
import {getCurrentInstance, ref} from 'vue'
const {proxy} = getCurrentInstance()
const username = ref("")
const password = ref("")
function doLogin() {
proxy.$axios.post("user/", {
username: username.value,
password: password.value,
}).then(res => {
console.log(res.data)
}).catch(error => {
console.log(error)
})
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 跨域问题
CORS 解决的本质就是在返回数据时候加一些响应头即可解决!!
from django.shortcuts import render
from django.http import JsonResponse
def user_list(request):
info = {"code": 0, 'data': "success"}
response = JsonResponse(info)
print(request.method)
response["Access-Control-Allow-Origin"] = "*" # 任意网址
response["Access-Control-Allow-Methods"] = "*" # "PUT,DELETE,GET,POST" 任意的请求方式
response["Access-Control-Allow-Headers"] = "*" # 允许任意的请求头
return response
# Ps:测试时记得移除csrf认证中间件.
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
或者写在自定义中间件里
from django.utils.deprecation import MiddlewareMixin
class CorsMiddleware(MiddlewareMixin):
def process_response(self, request, response):
response["Access-Control-Allow-Origin"] = "*"
response["Access-Control-Allow-Methods"] = "*" # "PUT,DELETE,GET,POST"
response["Access-Control-Allow-Headers"] = "*"
return response
2
3
4
5
6
7
8
9
Ps: 若前后端分离的模式, 非要解决跨域, 有个奇淫技巧. 几乎不会这么做.
[需要跨域]
前 https://www.luffycity.com/free-course
后 https://api.luffycity.com/api/v1/course/
[不需要跨域]
前 https://www.luffycity.com/free-course
后 https://www.luffycity.com/api/v1/course/
2
3
4
5
6
7