vue系列【二】

250 阅读7分钟

1、基础知识点

1.1 常见ES6

(1)变量和常量

  • var 函数作用域,vue中较少用到
<script>
    function show(){
        if(1==1){
            var name = "武沛齐";  //函数作用域=Python
        }
        consolo.log(name);
    }
    show();
</script>
  • let 块级作用域
<script>
    if(1==1){
    	let age = 19;  // 块级作用域
    }
    console.log(age);
</script>
  • const 块级作用域+常量(ref),info不可以被重新赋值,但是info.value可以被重新赋值;
<script>
    if(1==1){
    	const age = 19;  // 块级作用域 + 常量(ref)
        
        const info = {id:1,value:18};  // 块级作用域 + 常量(ref)
    
        info.value = 999;
    }
    console.log(age);
</script>

(2)模版字符串

  • js写法
let info = "我是" + "?" + "今年技术";
  • 模版字符串拼接
<script>
    let name = "张开";
    let age = 73;
    
	let info =  `我叫${name},今年${age}岁`;    // 反引号
</script>

(3)动态参数

  • ...类似于python中的*args**kwargs,表示可以传入0或多个参数,示例代码如下:
<script>
    function info(v1,...data){
        console.log(v1,data);
    }
    
    info(11);
    info(11,22);
    info(11,22,333,444,55);
</script>
<script>
    function info(v1,v2,v3,v4){
        console.log(v1,v2,v3,v4);
    }

    info(11,22,333,444);
    nums = [22,33,44,55,66,77,88];
    info(11,...nums)    // 将nums打散,传入到info中,若数据多余函数定义参数,则取前面部分数据;若数据少于函数定义参数,则少的部分为未定义;
</script>

(4)解构赋值

<script>
    let info = {name:"武沛齐",email:"wupeiqi@live.com",addr:"北京"};
    let {name,addr} = info;    //花括号中的键对应info中的键
    
    console.log(name);
    console.log(addr);
</script>
  • Vue3中需要什么就要导入什么,不像vue2中this.$router this.$route
import {name,addr} from 'vue'
  • 解构赋值示例代码【对象解构赋值】
<script>
    
    function getData(n1,{name,addr}){
    	//let {name,addr} = info; 
        
        console.log(name);
        console.log(addr);
    }
    
    let info = {name:"武沛齐",email:"wupeiqi@live.com",addr:"北京"};
    getData(111,info);    //将info中的name、addr提取并传入到函数中
	
</script>
  • 解构赋值示例代码【数组解构赋值】
<script>
    function getData(n1,[n2,n3,n4]){
        console.log(n1,n2,n3,n4)
    }
    let nums = [11,22,33,44];
    getData(100,nums);
</script>

(5)箭头函数

  • 发送网络请求时,回调函数必须要使用箭头函数。
<script>
    function f1(name,age){
        console.log(name,age);
    }
    f1("张开",99);
	
    //箭头函数
    let f2 = (name,age) =>{
        console.log(name,age);
    }
    f2("张开",99);
</script>
  • 示例代码【一】
<script>
    var name = "源代码";
    let info = {
        name: "武沛齐",
        func: function () {
            console.log(this.name); // 函数内部默认都有this关键字; 输出结果为:武沛齐
        }
    }
    info.func();

    function getData() {
        console.log(this.name); // 函数内部默认都有this关键字,this=window/全局
    }

    getData();    //输出结果为:源代码
</script>
  • 示例代码【二】
<script>
    var name = "源代码";
    let info = {
        name: "武沛齐",
        func: function () {
            console.log(this.name); // 函数内部默认都有this关键字,this=info对象
            
            // 函数内的函数
            function getData() {
                console.log(this.name); // 函数内部默认都有this关键字,this=window/全局
            }
            getData();
        }
    }
    info.func();
</script>
  • 示例代码【三】
<script>
    var name = "源代码";
    let info = {
        name: "武沛齐",
        func: function () {
            console.log(this.name); // 函数内部默认都有this关键字,this=info对象

            let getData = () => {
                console.log(this.name); // 函数内部默认都有this关键字
            }
            getData();
        }
    }
    info.func();
</script>
  • 示例代码【四】 info.data获取不到数据
<script src="https://cdn.bootcdn.net/ajax/libs/axios/0.21.1/axios.min.js"></script>
<script>

    let info = {
        data: null,
        send: function () {
            axios({
                method: "post",
                url: "https://api.luffycity.com/api/v1/auth/password/login/?loginWay=password",
                data: {
                    username: "alex",
                    password: "dsb"
                },
                headers: {
                    "content-type": "application/json"
                }
            }).then(function (res) {
                console.log(this,res);
                this.data = res;
            })
        }
    }
    info.send();
</script>
  • 示例代码【五】 info.data可以获取到数据
<script src="https://cdn.bootcdn.net/ajax/libs/axios/0.21.1/axios.min.js"></script>
<script>

    let info = {
        data: null,
        send: function () {
            axios({
                method: "post",
                url: "https://api.luffycity.com/api/v1/auth/password/login/?loginWay=password",
                data: {
                    username: "alex",
                    password: "dsb"
                },
                headers: {
                    "content-type": "application/json"
                }
            }).then((res) => {    // 改动之处
                console.log(this,res);
                this.data = res;
            })
        }
    }
    info.send();
</script>

(6)模块

  • 代码
// index.js
// 常量、变量、函数等
export {}    // 导出
<script type='module'>
// 引入常量、变量、函数等
</script>
  • 截图 image.png
  • 截图 image.png
  • 截图 image.png

1.2 项目部署

  • 第一步:项目编译
npm run build
  • 第二步:将编译后的代码dist文件上传到服务器(阿里云、腾讯云)

image.png

  • 第三步:安装nginx + 配置 + 启动
yum install nginx
user nginx;
  worker_processes auto;
  error_log /var/log/nginx/error.log;
  pid /run/nginx.pid;
  
  # Load dynamic modules. See /usr/share/doc/nginx/README.dynamic.
  include /usr/share/nginx/modules/*.conf;
  
  events {
      worker_connections 1024;
  }
  
  http {
      log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                        '$status $body_bytes_sent "$http_referer" '
                        '"$http_user_agent" "$http_x_forwarded_for"';
  
      access_log  /var/log/nginx/access.log  main;
  
      sendfile            on;
      tcp_nopush          on;
      tcp_nodelay         on;
      keepalive_timeout   65;
      types_hash_max_size 4096;
  
      include             /etc/nginx/mime.types;
      default_type        application/octet-stream;
  
      include /etc/nginx/conf.d/*.conf;
  
      server {
          listen       80;
          listen       [::]:80;
          server_name  _;
          # 项目目录
          root         /data/mysite;
  
          # Load configuration files for the default server block.
          include /etc/nginx/default.d/*.conf;
  
          error_page 404 /404.html;
          location = /404.html {
          }
  
          error_page 500 502 503 504 /50x.html;
          location = /50x.html {
          }
      }
  }
  >>>systemctl start nginx
  >>>systemctl restart nginx
  • 第四步:访问

2、flex【CSS知识点】

  • 传统的页面布局:div+css+float实现。
  • flex布局更简单。

2.1 容器

(1)布局

<div class='menu'>
    <div class='item'>北京</div>
    <div class='item'>上海</div>
</div>

<style>
    .menu{        
        display:flex;
    }
</style>

(2)元素方向

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        .menu {
            border: 1px solid red;
            width: 500px;

            display: flex;
            flex-direction: row; /*主轴=横向*/
        }
    </style>
</head>
<body>
<div class='menu'>
    <div class='item'>北京</div>
    <div class='item'>上海</div>
</div>


</body>
</html>

(3)元素排列方式

justify-content: 主轴
align-items: 副轴
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        .menu {
            border: 1px solid red;
            width: 500px;
            height: 500px;

            display: flex;
            flex-direction: row; /*主轴=横向*/
            
            /*主轴方向设置*/
            /*justify-content: space-evenly;*/
            justify-content: space-between;

            /*副轴方向设置*/
            align-items: center;
            /*align-items: flex-start;*/
            /*align-items: flex-end;*/
        }
        .menu .item{
            width: 45px;
            height: 50px;
            border: 1px solid green;
        }
    </style>
</head>
<body>
<div class='menu'>
    <div class='item'>北京</div>
    <div class='item'>上海</div>
    <div class='item'>深圳</div>
</div>


</body>
</html>

(4)换行、多行控制

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        .menu {
            border: 1px solid red;
            width: 500px;
            height: 500px;

            display: flex;
            flex-direction: row; /*主轴=横向*/
            /*justify-content: space-evenly;*/
            justify-content: flex-start;

            /*align-items: center;*/
            align-items: flex-start;
            /*align-items: flex-end;*/
            
            
            /*换行*/
            flex-wrap: wrap;
            
            /*多行控制*/
            align-content: center;
            align-content: flex-start; /*多行文本,从顶部开始*/
        }
        .menu .item{
            width: 45px;
            height: 50px;
            border: 1px solid green;
        }
    </style>
</head>
<body>
<div class='menu'>
    <div class='item'>北京</div>
    <div class='item'>上海</div>
    <div class='item'>深圳</div>
    <div class='item'>深圳</div>
    <div class='item'>深圳</div>
    <div class='item'>深圳</div>
    <div class='item'>深圳</div>
    <div class='item'>深圳</div>
    <div class='item'>深圳</div>
    <div class='item'>深圳</div>
    <div class='item'>深圳</div>
    <div class='item'>深圳</div>
    <div class='item'>深圳</div>
    <div class='item'>深圳</div>
    <div class='item'>深圳</div>
    <div class='item'>深圳</div>
</div>


</body>
</html>
  • 案例
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        .menu {
            border: 1px solid red;
            width: 500px;
            height: 500px;

            display: flex;
            flex-direction: row; /*主轴=横向*/


            /*justify-content: space-between;*/
            justify-content: space-around; /*横轴*/

            align-items: flex-start; /*纵轴*/

            flex-wrap: wrap;

            align-content: flex-start; /*多行文本,从顶部开始*/
        }

        .menu .item {
            width: 150px;
            height: 50px;
            border: 1px solid green;
        }
    </style>
</head>
<body>
<div class='menu'>
    <div class='item'>北京</div>
    <div class='item'>上海</div>
    <div class='item'>深圳</div>
    <div class='item'>深圳</div>
    <div class='item'>深圳</div>
    <div class='item'>深圳</div>
    <div class='item'>深圳</div>
    <div class='item'>深圳</div>
    <div class='item'>深圳</div>
    <div class='item'>深圳</div>
    <div class='item'>深圳</div>
    <div class='item'>深圳</div>
    <div class='item'>深圳</div>
    <div class='item'>深圳</div>
    <div class='item'>深圳</div>
    <div class='item'>深圳</div>
    <div class='item'>深圳</div>
    <div class='item'>深圳</div>
</div>


</body>
</html>

2.2 元素

(1)顺序

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        .menu {
            border: 1px solid red;
            width: 500px;
            height: 500px;

            display: flex;
            flex-direction: row; /*主轴=横向*/


            /*justify-content: space-between;*/
            justify-content: space-around; /*横轴*/

            align-items: flex-start; /*纵轴*/

            flex-wrap: wrap;

            align-content: flex-start; /*多行文本,从顶部开始*/
        }

        .menu .item {
            width: 50px;
            height: 50px;
            border: 1px solid green;
        }
    </style>
</head>
<body>
<div class='menu'>
    <!--通过order设置顺序-->
    <div class='item' style="order: 1">北京</div>
    <div class='item' style="order: 0">上海</div>
    <div class='item' style="order: 2">深圳</div>
</div>


</body>
</html>

(2)剩余空间

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        .menu {
            border: 1px solid red;
            width: 500px;
            height: 500px;

            display: flex;
            flex-direction: row; /*主轴=横向*/


            /*justify-content: space-between;*/
            justify-content: flex-start; /*横轴*/
        }

        .menu .item {
            width: 50px;
            height: 50px;
            border: 1px solid green;
        }
    </style>
</head>
<body>
<div class='menu'>
    <!--通过flex-grow控制剩余空间【剩余空间占满】-->
    <div class='item' style="">北京</div>
    <div class='item' style="flex-grow: 2">上海</div>
    <div class='item' style="flex-grow: 1">深圳</div>
</div>


</body>
</html>

(3)案例

image.png

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        .container {
            width: 1100px;
            margin: 0 auto;
        }

        .row1 {
            display: flex;
            flex-direction: row;
            justify-content: space-between;
        }

        .row1 .company {
            width: 210px;
            height: 180px;
            background-color: saddlebrown;
        }

        .row1 .pic {
            width: 266px;
            height: 180px;
            background-color: cadetblue;
        }

        .row2 .title {
            display: flex;
            flex-direction: row;
            justify-content: space-between;
        }

        .row2 .pic-list {
            display: flex;
            flex-direction: row;
            justify-content: space-between;
        }

        .row2 .pic-list .big {
            background-color: aquamarine;
            height: 610px;
            width: 210px;
            margin-right: 20px;
        }

        .row2 .pic-list .right-list {
            background-color: antiquewhite;
            flex-grow: 1;
        }

        .row2 .pic-list .right-list .group {
            display: flex;
            flex-direction: row;
            justify-content: space-between;
            flex-wrap: wrap;
        }

        .row2 .pic-list .right-list .phone {
            margin-bottom: 10px;
            border: 1px solid red;
            width: 200px;
            height: 300px;
        }

        .course-list {
            display: flex;
            justify-content: space-between;
            flex-wrap: wrap;
        }

        .course-list .item {
            width: 24%;
            height: 100px;
            background-color: skyblue;
            margin-top: 15px;
        }
        /*如果最后一个元素,是第3个,右边距=一个位置 + 所有空白位置/3(有三个空白位置)*/
        .course-list .item:last-child:nth-child(4n - 1) {
            margin-right: calc(24% + 4% / 3);
        }
        .course-list .item:last-child:nth-child(4n - 2) {
            margin-right: calc(48% + 8% / 3);
        }
    </style>
</head>
<body>

<div class="container">

    <div class="row1">
        <div class="company"></div>
        <div class="pic"></div>
        <div class="pic"></div>
        <div class="pic"></div>
    </div>

    <div class="row2">
        <div class="title">
            <div>手机</div>
            <div>查看更多</div>
        </div>

        <div class="pic-list">
            <div class="big"></div>
            <div class="right-list">
                <div class="group">
                    <div class="phone"></div>
                    <div class="phone"></div>
                    <div class="phone"></div>
                    <div class="phone"></div>
                </div>
                <div class="group">
                    <div class="phone"></div>
                    <div class="phone"></div>
                    <div class="phone"></div>
                    <div class="phone"></div>
                </div>
            </div>
        </div>
    </div>

    <div class="course-list">
        <div class="item"></div>
        <div class="item"></div>
        <div class="item"></div>
        <div class="item"></div>
        <div class="item"></div>
        <div class="item"></div>

    </div>
</div>

</body>
</html>

3、vue-router

3.1 安装

  • 方法一
npm install vue-router --save
手动创建文件+配置
  • 方法二
vue add router

3.2 必备操作

(1)url传值(get)、动态参数

  • App.vue
<template>
    <div>
        <div class="menu">
            <div class="container">
                <router-link to="/">源代码教育</router-link>
                <router-link to="/home">首页</router-link>
                <router-link to="/course">课程1</router-link>
                <router-link :to="{path:'/course'}">课程2</router-link>
                <router-link :to="{name:'Course'}">课程3</router-link>
                
                <!--url传值(get)-->
                <router-link :to="{name:'Course',query:{size:21,page:9}}">课程-21-9</router-link>
                <router-link :to="{path:'/course',query:{size:22,page:19}}">课程-22-19</router-link>
                
                <!--url动态参数-->
                <router-link :to="{name:'Detail', params:{id:99},query:{size:22,page:3}}">详细-99-22-3</router-link>
                <router-link :to="{name:'Detail', params:{id:1},query:{size:122,page:32}}">详细-1-122-32</router-link>

                <router-link to="/news">资讯</router-link>
            </div>
        </div>
        <div class="container">
            <router-view/>
        </div>
    </div>
</template>
  • CourseView.vue
<template>
    <div>
        <h1>课程页面</h1>
        <h3>讲师:{{name}}</h3>
        <div>当前页:{{page}}</div>
        <div>数量:{{size}}</div>
    </div>
</template>

<script setup>
    /* eslint-disable */
    /* 引入ref,实现name、page等数据双向绑定 */
    import {ref} from 'vue'
    import {useRoute, onBeforeRouteUpdate} from 'vue-router'

    const route = useRoute();

    const name = ref("武沛齐");
    const page = ref(route.query.page || 0);
    const size = ref(route.query.size || 0);
    
    /* onBeforeRouteUpdate 实现页面渲染数据与地址栏中数据同步变化 */
    onBeforeRouteUpdate((to, from) => {
        page.value = to.query.page || 0;
        size.value = to.query.size || 0;
    })

</script>

image.png

(2)路由嵌套

  • router/index.js
const routes = [
    {
        path: '/',
        name: "Index",
        component: HomeView
    },
    {
        path: '/home',
        name: 'Home',
        component: HomeView
    },
    
    /* 路由嵌套 */
    {
        path: '/pins',
        name: 'Pins',
        component: HomeView,
        children:[
            {
                path: 'new',
                name: 'New',
                component: HomeView
            },
            {
                path: 'hot',
                name: 'Hot',
                component: HomeView
            }
        ]
    },
    {
        path: '/course',
        name: 'Course',
        component: () => import('../views/CourseView.vue')
    },
    {
        path: '/detail/:id',
        name: 'Detail',
        component: () => import('../views/DetailView.vue')
    },
    {
        path: '/news',
        name: 'News',
        component: () => import('../views/NewsView.vue')
    }
]

image.png

(3)编程式导航

如果想要是跳转+加载组件:

<template>
    <div>
        <h1>新闻页面</h1>
        <input type="button" value="跳转" @click="doClick"/>
    </div>
</template>

<script setup>

    /* 注意与获取参数时引入的{useRoute}不同 */
    import {useRouter} from 'vue-router'

    const router = useRouter();

    // push与replace在操作浏览器回退按钮时结果不同:push跳转时会建立类似工作栈保存跳转记录[Course、Home、Detail...]的功能,回退时会依据跳转记录逐步往回;而repalce不会;
    
    
    function doClick() {
        //跳转到首页 [Course,]
        // router.push({path:"/home"})
        // router.push({name:"Home"})
        // router.push({name: "Course", query: {page: 10, size: 20}})
        router.push({name: "Detail", params: {id: 100}, query: {page: 10, size: 20}})

        // router.replace({path:"/home"})
        // router.replace({name:"Home"})
        // router.replace({name: "Course", query: {page: 10, size: 20}})
        // router.replace({name: "Detail", params: {id: 100}, query: {page: 10, size: 20}})
        // router.replace({name: "Course", query: {page: 10, size: 20}})

    }
</script>

<style scoped>

</style>
  • 登录跳转案例,含顶部和不含顶部主要区别在于路由嵌套设置部分。
// 登录跳转【含顶部】
const routes = [
    {
        path: '/',
        name: "Index",
        component: HomeView
    },
    {
        path: '/home',
        name: 'Home',
        component: HomeView
    },
    {
        path: '/login',
        name: 'Login',
        component: () => import('../views/LoginView.vue'),
    },
    {
        path: '/pins',
        // name: 'Pins',
        component: () => import('../views/PinsView.vue'),
        children: [
            {
                path: '',
                //component: () => import('../views/NewView.vue'),
                redirect: {name: "New"},
            },
            {
                path: 'new',
                name: 'New',
                component: () => import('../views/NewView.vue'),
            },
            {
                path: 'hot',
                name: 'Hot',
                component: () => import('../views/HotView.vue'),
            },
            {
                path: 'following',
                name: 'Following',
                component: () => import('../views/FollowingView.vue'),
            }
        ]
    },
    {
        path: '/course',
        name: 'Course',
        component: () => import('../views/CourseView.vue')
    },
    {
        path: '/detail/:id',
        name: 'Detail',
        component: () => import('../views/DetailView.vue')
    },
    {
        path: '/news',
        name: 'News',
        component: () => import('../views/NewsView.vue')
    }
]

// 登录跳转【不含顶部】
const routes = [
    {
        path: '/login',
        name: 'Login',
        component: () => import('../views/LoginView.vue'),
    },
    {
        path: '/',
        component: HomeView,
        children: [
            {
                path: '',
                // name: 'Index',
                // component: () => import('../views/IndexView.vue'),
                redirect: {name: "Index"}
            },
            {
                path: 'index',
                name: 'Index',
                component: () => import('../views/IndexView.vue'),
            },
            {
                path: '/course',
                name: 'Course',
                component: () => import('../views/CourseView.vue')
            },
            {
                path: '/news',
                name: 'News',
                component: () => import('../views/NewsView.vue')
            },
            {
                path: '/pins',
                component: () => import('../views/PinsView.vue'),
                children: [
                    {
                        path: '',
                        //component: () => import('../views/NewView.vue'),
                        redirect: {name: "New"},
                    },
                    {
                        path: 'new',
                        name: 'New',
                        component: () => import('../views/NewView.vue'),
                    },
                    {
                        path: 'hot',
                        name: 'Hot',
                        component: () => import('../views/HotView.vue'),
                    },
                    {
                        path: 'following',
                        name: 'Following',
                        component: () => import('../views/FollowingView.vue'),
                    }
                ]
            },
        ]
    },
]

(4)导航守卫(全局)

  • router/index.js
router.beforeEach((to, from, next) => {

    // to,即将访问路由对象
    // from,当前正要离开路由
    // next() 继续向后执行,去to的页面
    // next(false) 不跳转,还在当前页面。
    // next("/xxx")  next({name:"xxx"})  next({pat:"/xxx"})
    
    let token = sessionStorage.getItem("isLogin");

    if (token) {
        // 已登录,可以向目标地址访问
        next();
        return
    }

    // 未登录,登录页面
    if (to.name === "Login") {
        next();
        return;
    }

    // 未登录,访问的其他地址
    next({name: "Login"});

})

4、存储数据

  • cookie,超时时间+发送请求自动携带。
  • sessionStorage,当浏览器关闭时,自动清除。
  • localStorage,长时间存储浏览器。
localStorage.setItem(key,value)
localStorage.getItem(key)
localStorage.removeItem(key)
localStorage.clear()

5、vuex

5.1 安装

vue add vuex (推荐)

5.2 vuex+localStorage存储+导航守卫

  • vuex中的js部分
// store/index.js
import {createStore} from 'vuex'

export default createStore({
    state: {
        // 将localStorage中数据赋值到内存中
        username: localStorage.getItem('username'),
        token: localStorage.getItem('token')
    },
    getters: {},
    mutations: {
        login(state, {username, token}) {
            state.username = username;
            state.token = token;

            //保存在localStorage中
            localStorage.setItem('username', username);
            localStorage.setItem('token', token);
        },
        
        // 注销时清除state及localStorage中数据
        logout(state) {
            state.username = "";
            state.token = "";

            //localStorage清除
            localStorage.removeItem('username');
            localStorage.removeItem('token');
        },
    },
    actions: {},
    modules: {}
})
  • login中的js部分
// views/LoginView.vue

<script setup>
    import {useRouter} from 'vue-router'
    import {useStore} from 'vuex'
    import {ref} from 'vue'

    const router = useRouter();
    const store = useStore();

    const username = ref("");
    const password = ref("");

    function doLogin() {
        //...
        if (username.value.length > 0 && password.value.length > 0) {
            console.log("登录成功");

            // 调用login方法
            const response = {username: username.value, token: "ed5cf238-d8ce-49f9-a1cf-cef55d689a2b", id: 100};
            store.commit("login", response)

            router.replace({name: "Index"})
        } else {
            console.log("登录失败");
        }
    }
</script>
  • 登录后页面中的js部分
// views/HomeView.vue

<script setup>
    import {ref, computed} from 'vue'
    import {useStore} from 'vuex'
    import {useRouter} from 'vue-router'

    const router = useRouter();
    const store = useStore();

    const name = ref(store.state.username);


    function doLogout() {
        //localStorage清空 + state值清空 + 跳转登录
        store.commit("logout");
        router.push({name: "Login"});
    }
</script>
  • “登录”/“用户名” 之间切换
// views/HomeView.vue

<div style="float: right">
    <span>购物车有{{carNum}}件</span>
    <a v-if="name" @click="doLogout">{{name}}</a>
    <router-link v-else to="/login">登录</router-link>
</div>

6、vue3-cookies

实现将数据在cookie中进行存取。

6.1 安装

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')
  • 使用
import {useCookies} from 'vue3-cookies'
const {cookies} = useCookies();
cookies.set("ts", "123123", "10s")

github.com/KanHarI/vue…

  • 案例代码
// plugins/cookie.js

import {useCookies} from "vue3-cookies";

const {cookies} = 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");
}
// store/index.js

import {createStore} from 'vuex'

// 引入cookie
import {getToken, getUserName,setUserToken,clearUserToken} from "@/plugins/cookie";

export default createStore({
    state: {
        // username: localStorage.getItem('username'),
        // token: localStorage.getItem('token'),
        
        // 从cookie中获取
        username: getUserName(),
        token: getToken(),
        car: localStorage.getItem('carNum') || 0,
    },
    getters: {},
    mutations: {
        login(state, {username, token}) {
            state.username = username;
            state.token = token;

            //保存在localStorage中
            // localStorage.setItem('username', username);
            // localStorage.setItem('token', token);
            
            // cookie中设置
            setUserToken(username,token);
        },
        logout(state) {
            state.username = "";
            state.token = "";

            //localStorage清除
            // localStorage.removeItem('username');
            // localStorage.removeItem('token');
            
            //cookie中清除
            clearUserToken();
        },
        addCar(state) {
            state.car = parseInt(state.car) + 1;
            localStorage.setItem('carNum', state.car);
        }
    },
    actions: {},
    modules: {}
})

7、axios

7.1 安装

vue add axios

7.2 使用示例

利用axios发起请求时,then(() => {})后加上catch(() => {接收请求异常的回调}),可避免前端出现类似以下红色报错页面;

image.png

(1)快速发送

<script setup>
    import {useRouter} from 'vue-router'
    import {useStore} from 'vuex'
    import {ref} from 'vue'
    import _axios from "@/plugins/axios";

    const router = useRouter();
    const store = useStore();

    const username = ref("");
    const password = ref("");

    function doLogin() {
        //发送网络请求
        _axios.post("/v1/auth/password/login/?loginWay=password", {
            username: "alex",
            password: "dsb"
        }).then((res) => {
            // {"code":-1,"msg":"校验错误","data":{"global_error":["密码错误"]}}
            console.log("成功", res.data);
        }).catch((error) => {
            console.log("失败", error);
        })


        if (username.value.length > 0 && password.value.length > 0) {
            console.log("登录成功");

            //调用login方法
            const response = {username: username.value, token: "ed5cf238-d8ce-49f9-a1cf-cef55d689a2b", id: 100};
            store.commit("login", response)

            router.replace({name: "Index"})
        } else {
            console.log("登录失败");
        }
    }
</script>

(2)拦截器

/* eslint-disable */

import axios from "axios";

import {useRouter} from "vue-router";
import {useStore} from "vuex";
import {getToken} from "@/plugins/cookie";

const router = useRouter();
const store = useStore();

// 设置基本路由
axios.defaults.baseURL = 'https://api.luffycity.com/api/';
// axios.defaults.headers.common['Authorization'] = getToken();
// axios.defaults.headers.post['Content-Type'] = 'application/json';


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
        // console.log("请求前执行");
        const token = getToken();
        if (token) {
            config.headers['token'] = token;
        }
        return config;
    }
);

// 请求返回拦截器,包含两个函数
// 浏览器上有Token,但是Token在后端已经失效
_axios.interceptors.response.use(
    
    // 请求成功拦截
    function (response) {
        // Do something with response data
        // 请求成功 200成功(登录失效了){code:-1,msg:"登录失效"}   {code:0,msg:data}   {code:1000,msg:"认证失败"}
        if (response.data.code === 1000) {
            //认证失败,token过期,登录失败 -> 登录页面
            store.commit("logout");
            router.replace({name: "Login"});
            return Promise.reject();
        }

        return response;
    },
    
    // 请求错误拦截
    function (error) {
        // Do something with response error
        // 请求失败自动执行此处的代码,返回的状态码:500(认证401)
        if (error.response.status === 401) {
            store.commit("logout");
            router.replace({name: "Login"});
        }
        return Promise.reject(error);
    }
);

export default _axios;

8、后端API

8.1 跨域问题

  • 当前浏览器访问地址:http://localhost:8080/xxxx/xxxx/
  • 点击发送网络请求:https://api.luffycity.com/api/xxxx/xxxx/
  • 本质上想要处理跨域,添加一些响应头即可。
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

注意:测试时一定要移除csrf认证。

8.2 现象(2个请求)

(1)跨域时发送的是:

  • 简单请求:1个请求-
  • 复杂请求:2个请求
    • OPTIONS请求,预检
    • 真正的请求
条件:
  ``1``、请求方式:HEAD、GET、POST
  ``2``、请求头信息:
    ``Accept
    ``Accept``-``Language
    ``Content``-``Language
    ``Last``-``Event``-``ID
    ``Content``-``Type` `对应的值是以下三个中的任意一个
                ``application``/``x``-``www``-``form``-``urlencoded
                ``multipart``/``form``-``data
                ``text``/``plain

注意:同时满足以上两个条件时,则是简单请求,否则为复杂请求

(2)如果非要避免这种情况,那就别跨域

  • 主域名+API域名
https://www.luffycity.com/free-course
https://api.luffycity.com/api/v1/course/category/actual/?courseType=actual
  • 主域名+API域名
https://www.luffycity.com/free-course
https://www.luffycity.com/api/...

8.3 最后跨域

  • 写在中间件的process_response中。

(1)project/md/cors.py

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)project/settings.py

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    # 'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    
    # 添加以下代码
    'md.cors.CorsMiddleware'
]