Vue3.0 + 后端api 建立一个小型GoBook图书商域

924 阅读17分钟

Vue3.0 + 后端api 建立一个小型GoBook图书商域

  • 前言:

    本篇文章讲述了使用Vue3.0+ 后端API和各种前端UI组件,多种框架结合使用,建立的一个小型GoBook图书商域网站。Vue3的使用,让网站可以按照模块化开发。Bootrsap使得该项目同时能够在PC端、Pad端、移动端等多种终端设备运行。JQuery能够最大化地利用封装的函数,同时减少代码的复杂度。Element-plus组件化开发,减少花费在常规的功能组件上的时间。


目录结构

  • 前期准备
  • 项目结构
  • 具体实现
  • 性能优化
  • 网站部署
  • 总结

一、前期准备

1.框架和UI库

2.选择框架的原因

2.1 选择Vue 3

​ 首先选用vue3是为了学习当下最新的知识,因此小编也没有去过多了解vue2。其实 ,使用其它框架也是一种尝试和练习。Bootstrap结合vue3框架,可以让vue3项目更加灵活。

2.2 选择Bootstrap 4

​ 因为vue项目是不可复用的,如果需要重新开发一套新的项目必需 新建立vue项目,因此,使用了Bootstrap就让vue项目可以同时在多种不同的设备上线运行。当然,我们也可以使用css3的新属性@media , 使用这个属性去支持多端设备,这 可不是一个好选择哦!

2.3 选择jquery 3.6.0

​ 一开始建立网站前,对vue认识的不是很深刻,所以才选择使用 JQuery框架对vue的一种补充。初学vue,没有学到真正的内涵,所以开始只能在使用虚拟DOM后 ,再通过 jquery操作真实DOM节点。后面进行过优化,把jquery的部分事件监听去除,只留下了部分功能,并且不再通过jquery触发点击事件。其实,在目前,已经可以完全移除jquery框架。首先,jquery框架已经不再被目前的前端开发所必要的框架,浏览器渐渐支持原生JS,比如说document.querySelector,并且vue, React, Angular的虚拟Dom也已经取代jquery操作真实DOM节点,对页面的性能是一种极大的提升。

2.4 选择使用 Element-plus

​ 最后,使用Element-plus UI组件库,可以加快项目的开发。对于一些常规化的功能,可以使用更美观、实用的组件来构成页面。因为当前项目已经使用多种框架,所以小编也不再使用其它的UI组件库,否则项目会变得臃肿不堪。当然,Bootstrap组件是不被包括在内的,因为Boostrap组件库也依旧是Bootstrap框架的一部分。使用部分高效的组件也是个不错的选择。其实移动端正火热使用的vant组件也是一个非常好的组件库(Vant - 轻量、可靠的移动端组件库 (gitee.io)),如果说在当前引入这个组件库,那么当前的网站已经太复杂化。小型网站而使用大量的组件库,很容易造成性能的浪费。每次加载网页,总是从各个组件库中找到组件再加载。到最后页面的渲染。如果非必要引入某个组件库,则不建议过多使用。

2.4 选择使用图标库

​ 阿里巴巴矢量图标库,却实是现在火热的图标库。不管是Bootstrap-icons图标库(Bootstrap 图标库 · Bootstrap 官方开源图标(icon)库 (bootcss.com)), 还是使用FontAwesome图标库(Font Awesome),对于建立一个网站来说,图标真的是太少太少了。不管是Bootsrap在不断更新图标库,也还是阻挡不了我们对 阿里巴巴矢量图标库的向往。这里是把项目下载到本地,再去访问图标。这种方法可能会比较繁琐,但却是比较建议使用的。如果网速慢的话,可能会存在页面已经加载完毕,可图标还在缓慢显示。

二、项目结构和运行效果

1.1项目结构

/*
├─assets               // 静态资源
│  ├─css                        // css文件
│  │  │  base.css                // 基础css
│  │  │  normalize.css          // 常规的css
│  │  │  other.css
│  │  │
│  │  └─iconfont_css            // iconfont图标css文件
│  │          demo.css
│  │          demo_index.html
│  │          iconfont.css
│  │          iconfont.js
│  │          iconfont.json
│  │          iconfont.ttf
│  │          iconfont.woff
│  │          iconfont.woff2
│  │
│  └─images                   // 图片资源
│      │  404error.png
│      │  collection-fill.png
│      │  collection.png
│      │  gobook-logo.png
│      │  upward.png
│      │
│      ├─img                      // 头部,页脚栏图片
│      │      chengxin.png
│      │      ectrust.png
│      │      logo-lmonkey.png
│      │
│      └─pic                     // 其它数据显示为空的时候,显示的图片
│              bg-address.png
│              bg-cart1.png
│              bg-collect.png
│              bg-login.png
│              bg-order.png
│              icon-qq.png
│              loginCode.png
│ 
├─components                 // 子组件
│  ├─common
│  │  ├─backtop                    // 一键滑动到顶部
│  │  │      BackTop.vue
│  │  │
│  │  ├─footerbar              
│  │  │      FooterBar.vue
│  │  │
│  │  └─headerbar
│  │          HeaderBar.vue
│  │
│  └─content
│      └─goods                               //商品子组件
│              GoodsItem.vue
│              GoodsList.vue
│              GoodsListItem.vue
│
├─network                          // 封装axios网络请求。获取不同页面的数据
│      address.js
│      cart.js
│      category.js
│      collect.js
│      detail.js
│      home.js
│      order.js
│      request.js
│      settings.js
│      user.js
│
├─router                // 路由管理
│      index.js
│
├─store                 // 状态管理
│      actions.js
│      getters.js
│      index.js
│      mutations.js
│
├─utils                     // 包含全国地址的js文件
│      addressMessage.js
│
└─views                    // 视图文件
    │  404error.vue
    │
    ├─address            
    │      Address.vue
    │      AddressEdit.vue
    │
    ├─category
    │      Category.vue
    │
    ├─detail
    │      Detail.vue
    │
    ├─home
    │  │  Home.vue
    │  │
    │  └─childcomps                     // 主页的子组件
    │          HomeSwiper.vue
    │          RecommendView.vue
    │
    ├─order
    │      CreateOrder.vue
    │      Order.vue
    │      OrderDetail.vue
    │
    ├─profile
    │      Collection.vue
    │      Login.vue
    │      Profile.vue
    │      Register.vue
    │      Settings.vue
    │
    ├─search
    │      Search.vue
    │
    └─shopcart
            ShopCart.vue
*/

总共分为8板块,16个页面。

1.2运行结果

因为当前项目是多端适应,所以只展示部分的效果图。

image-20211017085406700.png

image-20211017085428184.png

image-20211017093453694.png

image-20211017093541835.png

image-20211017093643940.png

image-20211017100629605.png

image-20211017094643331.png

image-20211017094746189.png

image-20211017094339581.png

image-20211017095458032.png

image-20211017095542445.png

image-20211017093944374.png

image-20211017101109510.png

image-20211017094042565.png

image-20211017095758013.png

image-20211017103128251.png

image-20211017101513772.png

image-20211017101443725.png

image-20211017095838021.png

image-20211017101223630.png

image-20211017100015766.png

image-20211017101307552.png

三、具体实现

1.前端页面实现

1.1组件分析

​ 因为当前网站涉及的页面比较多,这里展示比较重要的部分

(1.home.vue首页

使用的是bootstrap组件,所以为了适应多端的设备,需要对一些比较特殊的组件进行特殊处理。

其中首页的轮播图使用3个轮播图,一种是移动端等小屏幕显示, 二是PC等大屏幕显示。 三是Ipad中屏显示。小屏,中屏采用简易化轮播,大屏采用的是高级轮播图。两种不同风格的轮播图,以适应PC端的宽屏,和Ipad的中屏,移动端的小屏。 不过,这样下来,也会造成不小的性能开销。

    <!--轮播图内容-->
    <div id="swiper" class="container-fluid">
        <div v-if="banners.length===0">
            <!--轮播图内容栏,空内容显示-->
            <el-carousel :interval="4000" arrow="always" height="200px">
                <el-carousel-item v-for="item in 6" :key="item">
                    <h3 class="medium font-weight-bold" style="font-size:18px; margin-top:-50px;">没数据啦!刷新页面一下哦!</h3>
                </el-carousel-item>
            </el-carousel>
        </div>
        <!--轮播图内容栏,大屏显示-->
        <div class="d-none d-lg-block  " v-if="banners.length>0">
            <el-carousel :interval="4000" type="card">
                <el-carousel-item v-for="(item,index) in banners.slice(0,6)" :keys="index">
                    <h3 class="medium" @click.prevent="goD(item.id)">
                        <el-image :src="item.img_url"></el-image>
                    </h3>
                </el-carousel-item>
            </el-carousel>
        </div>
        <!--轮播图内容栏,中小屏显示-->
        <div class="d-block d-lg-none   " v-if="banners.length>0">
            <el-carousel :interval="4000" arrow="always">
                <el-carousel-item v-for="(item,index) in banners.slice(0,6)" :keys="index">
                    <h3 class="medium" @click.prevent="goD(item.id)">
                        <el-image :src="item.img_url"></el-image>
                    </h3>
                </el-carousel-item>
            </el-carousel>
        </div>
    </div>

​ 使用过babel-scroll移动端的滚动组件,但还是感觉效果不太好。 对PC端的支持性还是不行,流畅性有待提升。也找过其它组件,最后考虑到引入了jquery,所以就尝试使用jquery的窗口事件监听。因为vue和jquery两种不同风格的框架,所以在开发中有比较多的问题。后来经过优化后,才让jquery适应了vue的开发模式。

注意:

第一:如果把监听事件放在login.vue的其它script 中,vue不允许会出错。 一个vue页面中只允许一个script脚本,出现两个script标签就会出错。

第二:如果把监听窗口改变事件放在export default {}同级位置也不行。这虽然不报错,语法也正确。首次运行正常,刷新页面后,不再有效果。

**第三:即使把监听事件放在export default中的setup函数里面也要注意,使用非window和document的事件。需要等待页面挂载完毕后才能监听单个节点的事件。 相对来说,window和document事件是可以不放在onMounted里面的。因为窗口事件,是一直存在。而其它节点,需要等待页面的挂载,否则监听事件是会出错的。 **

这其中要考虑到,其实不应该在组件中通过jquery或者原生js监听事件的。虽然不建议使用,但箭在弦上,不得不发啊!所以现在也只能使用事件的监听。到目前来看,jquery好像是多余的。其实啊!纵观整个项目,用到jquery最多的就是addClass,removeClass, hasClass, 原生js呢使用的是什么? node.classList.add("abc") node.classList.remove("abc") titles[index].classList.toggle("abc") node..className. 现在看来,使用原生js也不会很复杂啊。唉!失算了。

vue渐近式开发不同于传统开发,传统开发是多页面,多文件,多脚本来渲染页面。而vue只是单页面程序,它不会因用户的点击进行页面的跳转,取而代之的是使用路由机制实现html页面内容的更新。

到这里就可以理解了,vue加载完数据后,不再运行子组件(相对于APP.vue根组件,其它的都是子组件)的script内容。就好像window.onload一样只加载一次。只有在组件中export default {}才会继续运行。因此,如果说子组件需要传递信息,一般需要通过$emit向父级派发一个自定义事件,由父组件处理。但在setup中建立的函数与变量,其实包括监听事件,子组件是可以单独处理的。所以啊,把windows监听事件放在setup函数中就没问题了。

            // 监听窗口滚动事件
            $(window).scroll(function () {
                if (Math.ceil($(document).scrollTop()) >= $(document).height() - $(window).height()) {
                    isArriveBottom.value = isShowBackTop.value = true;
                    getMoreMessage();              // 调用函数
                } else {
                    isArriveBottom.value = false;
                    if ($(document).scrollTop() < $(window).height()) {
                        isShowBackTop.value = false;
                    } else {
                        isShowBackTop.value = true;              // 在滑动超过一定高度的时候,必需显示
                    }
                }
            });

(2.login.vue登录页

​ 登录页有帐号登录和扫码登录,一般来说,移动端和ipad端是没有扫码登录的,所以,这里还是需要监听窗口的变化。如果是移动端,会立即切换成帐号登录模式。并且,因为需要在vue监听事件,所以可以把监听窗口变化放在setup(){..} 里面。

<script>
    import $ from 'jquery';                          // 引入$符号
    .... 
    
    export default {
        
        name: "Login",
        setup() {
            ....
            // 监听窗口的改变, 只要是小屏幕,则会更改样式,使扫码功能隐藏,只显示帐号登录。
            // 可能说ipad端没考虑周全,但也允许中屏出现扫码登录。
            $(window).resize(function(){
                if($(window).width()>760 && $(window).width()<780){
                    $('._tab-login >div:eq(0)').addClass('active').siblings('div').removeClass('active');
                    $('._message-login>div:eq(0)').addClass('active').siblings('div').removeClass('active');
                }
            });
            ....
        }
}
</script>

**(3. register.vue注册页 **

​ 在用户输入注册首先会在前端页检测手机号,如果说手机号不存在,也不会使用axios向后端发起异步请求。并且,注册服务为了保证是正常用户注册,所以使用了h5的canvas画布写数字验证码。

​ 其实在这里,没有对验证码进行过多的修改,以前做的canvas验证码现在直接拿来用。所以当时开发的时候也就是把正确的验证码动态地放在html页面上。因为学习过python爬虫,即使的在网页上没有正确的验证码,感觉破解这个验证码也不难啊!并且答案都在网页上,好像反爬的效果不大啊。只是说现在不再去更新优化,否则这也可以进行优化,甚至把jquery从项目中移除也行。

             // 创建自定义的验证码
            const initVerifyCode = ()=>{
                var canvas = document.getElementById("myCanvas");   // 拿到画布
                var _hiddentNode = document.getElementById("_hiddenTxt");      // 隐藏的验证码         //其实这种方法很容易受到攻击

                if (canvas.getContext) {                  //检查支持性
                    var ctx = canvas.getContext("2d");   // 拿到画笔,获得绘制上下文的绘画功能

                    drawCodeValid();
                    canvas.onclick = drawCodeValid;

                    //获取随机颜色
                    function randomColor(min, max) {
                        return "rgba(" + randomRange(min, max) + ',' + randomRange(min, max) + ',' + randomRange(min, max) + ",1)";
                    }

                    // 获取随机数
                    function randomRange(min, max) {     //   5 - 8
                        return Math.floor(Math.random() * (max - min + 1) + min);   // 0- 3 + 5  ===> 5-8
                    }                                 //   0 - 10     ===> 0 - 11
                                                      // -45 - 45     ===> 0 - 91 -45

                    function drawCodeValid() {

                        var number = 4;
                        var txt = "";
                        var cHeight = canvas.height;
                        var cWidth = canvas.width;

                        // 画布绘制
                        ctx.save();
                        ctx.fillStyle = randomColor(180, 220);
                        ctx.beginPath();
                        ctx.fillRect(0, 0, cWidth, cHeight);
                        ctx.restore();


                        // 数字绘制
                        ctx.Baseline = "bottom";                              // 文字底部对齐
                        for (var i = 0; i < number; i++) {
                            var deg = randomRange(-45, 45);
                            var x = (cWidth - 10) / number * i + 10;
                            var y = randomRange(cHeight / 2, cHeight - 10);
                            var color = randomColor(40, 120);
                            var currentNum = randomRange(0, 10).toString()[0];
                            txt += currentNum;
                            ctx.save();
                            ctx.fillStyle = color;                             //颜色必需比背景颜色更深
                            ctx.font = randomRange(25, 40) + "px SimHei";
                            ctx.translate(x, y);
                            ctx.rotate(deg * Math.PI / 180);                        // 移动,旋转画布
                            ctx.beginPath();
                            ctx.fillText(currentNum, 0, 0);
                            ctx.restore();
                        }

                        // 小黑点绘制
                        for (var j = 0; j < number * 30; j++) {
                            var xCircle = randomRange(0, cWidth);
                            var yCircle = randomRange(0, cHeight);
                            ctx.save();
                            ctx.fillStyle = randomColor(40, 120);     // 颜色偏深
                            ctx.beginPath();
                            ctx.arc(xCircle, yCircle, 1, 0, 360 * Math.PI / 180);        // 绘制小圆
                            ctx.closePath();
                            ctx.fill();                              // 自动闭合路径
                            ctx.restore();
                        }

                        // 划线绘制
                        for (var k = 0; k < number * 2; k++) {
                            var xLineStart = randomRange(0, cWidth);
                            var yLineStart = randomRange(0, cHeight);
                            var xLineEnd = randomRange(0, cWidth);
                            var yLineEnd = randomRange(0, cHeight);
                            ctx.save();
                            ctx.strokeStyle = randomColor(80, 140);

                            ctx.beginPath();
                            ctx.moveTo(xLineStart, yLineStart);           // 抬起画笔
                            ctx.lineTo(xLineEnd, yLineEnd);               // 绘制划线
                            ctx.stroke();                                // 绘制路径
                            ctx.restore();
                        }
                        _hiddentNode.innerHTML = txt;            // 隐藏的验证码
                    }

                } else {
                    alert('当前Javascript不支持画布元素');
                }

除此之外,使用的element-plus组件中,步骤条el-step出现了异常,所以对elemnt-plus组件进行修改,只要在子组件在css部分中不能加上scoped属性,加上就不会作用到组件内。 因为每个组件进都存在作用域,如果添加scoped属性,element-plus组件就不能再使用局部的css属性

​ 如果子组件的css不添加scoped属性,子组件的样式可以共享给其它组件使用。一般来说,其实并不建议这样做,因为会出现样式重叠。但因为使用sass预处理器,可以很大程度上避免样式混乱的问题,并且有bootstrap响应式的预定义类名的使用,也减少了这种情况的发生。

(4. search.vue搜索页

​ 因为在许多的页面上都使用了面包屑导航栏,并且都需要显示上一个页面的名称。所以单独地使用router 是无法获取到上一级的页面标题名称,获取路径也没什么作用。所以这里使用localStorage去存储上一页的信息。同时,为了显示正确的信息,还要使用decodeURIComponent() 把乱码的中文字符改成正常的中文。 在进入页面前,通过路由独享的守卫获取上一个页面的信息。

beforeEnter: (to, from) => {             // 路由独享的守卫的from不会来自本身,只能是undefined
            if (from.meta.alias !== undefined && from.fullPath !== '/') {         // 把数据放在浏览器中
                window.localStorage.setItem("lastPage", from.meta.alias);
                window.localStorage.setItem("lastPagePath", from.path);
                if (Object.keys(from.query).length > 0) {
                    window.localStorage.setItem("lastPageQuery", from.fullPath);
                } else {
                    window.localStorage.setItem("lastPageQuery", '');
                }
            } else if (from.fullPath === '/' || from.fullPath === '/home') {
                window.localStorage.setItem("lastPage", '');
                window.localStorage.setItem("lastPagePath", '');
                window.localStorage.setItem("lastPageQuery", '');
            }
        },

​ 使用路由懒加载,在搜索页重新输入信息后,页面将不会更新。同时,搜索页也获取不到路由跳转信息。所以只能通过watch监听路由的变化,再根据新的搜索数据重新加载页面数据。

            // 监听路由的变化,在当前页面搜索时,后出现数据不能及时刷新的情况
            watch(() => route.query.SearchText, (newA) => {    // 通过watch监听数据的改变

                if (newA !== undefined) {                   // 当搜索页面跳转其它页面时,也会触发这个监听器
                    // 但其它页面跳转到当前搜索页面时,却不一定会触发
                    // 清空会有干扰的数据
                    var new_text = newA;
                    if(new_text.startsWith('%') === true){
                        new_text = decodeURIComponent(new_text);
                    }
                    window.localStorage.setItem("lastSearchWords", new_text);

                    searchText.value = new_text;

                    console.log("路由在变化",newA);

                    currentAssociateCategories.value = [];
                    init_ok.init_goods = false;
                    isTotalMesaage.value = false;
                    currentOrder.value = 'sales';
                    tSearchText.value = '';
                    goods.sales.page = 1;
                    goods.sales.list = [];
                    goods.price.page = 1;
                    goods.price.list = [];
                    goods.comments_count.page = 1;
                    goods.comments_count.list = [];
                    isShowBackTop.value = false;
                    isArriveBottom.value = false;
                    isTotal.value = false;

                    dealSearchText();                            // 处理当前的字符串
                }
            });

(5.Order.vue订单页

​ 因为多处使用到了订单信息,所以在订单页的商品,订单详情,创建订单等都是使用同一个子组件,复用子组件以提高代码的复用率。不过,不同的页面,即使内容大体相同,但还是有些不一样。所以,不同的部分可以通过插槽来实现。

​ 注意:插槽不仅仅可以在父组件使用子组件的时候向子组件添加数据,并且子组件还可以再次回传给父组件。并且在插槽内还可以添加事件,插槽虽然是把数据插入到子组件中,但子组件还是要被添加到父组件中。因此,在插槽内可以直接在父组件内定义父组件的事件。

<template v-slot:插槽名称 ="subdata">   

   。。。

 </template>

​ 因此子组件收到父组件传递的props数据,还可以通过插槽回传给父组件。

<!--父组件-->
<goods-item :allList = "allList" :path="$route.fullPath">
                    <template v-slot:numSlot="subdata">
                        <span class="_myNum txt-body px-5  ">x{{subdata.item.num}}</span>
                    </template>
                    <template v-slot:bottom="subdata">
                       
                            .....
                            <div class="px-1 text-black " style="font-weight:bold">
                                实付款
                                <small></small>
                                <span class="txt-subbody">{{(subdata.item.goods.price)  * subdata.item.num}} </span>
                                <small>.00</small>
                            </div>
                           .....
                    </template>
                </goods-item>

(6.用户登录状态管理

​ 用户的信息和地址信息等其它必要信息都是需要放在state中进行管理,当从登录成功的那一个,所以全局需要使用的用户信息,默认地址信息,购物车数据信息都要获取。最后通过commit把信息提交到状态管理容器,如果不想要在当前页面立即获取,也可以通过dispatch分发一个异步请求,后台在异步请求完毕后,再主动提交commit修改状态信息。

​ 状态管理有几个比较重要的部分,getters属性可以获取状态信息。mutations可以执行同步信息,actions可以进行异步处理,所以在一些简单的异步请求中,可以放在actions上进行。

// 主动提交修改的状态信息
store.commit('setAddressUserName',item.name);
// 分发一个异步请求
store.dispatch('updateCart');
// actions.js
const actions = {
    // 异步更新购物车数据
    updateCart({commit}){
        getCart().then(res=>{
            //console.log('updateCart当前的数量是',res.data);
            commit('addCart',{count:res.data.length || 0})
        }).catch(err=>{
            //ElMessage.error('当前获取购物车数据失败');
            console.log('当前获取购物车数据失败');
        })
    },
 }
 // mutaions.js
 const mutations = {
    // 设置登录状态
    setIsLogin(state,payload){
        state.user.islogin = payload;
    },
 }

1.2结构分析

(1. router/index.js路由文件

​ 路由是通过懒加载的方式,并且路由借助了浏览器的History APl来实现的,这样可以使得页面跳转而不刷新,页面的状态就被维持在浏览器中。而vue-router默认使用的是hash模式。

​ 在每个页面访问前,都会全局的路由守卫进行检测是否需要登录,是否有些组件需要卸载,给新加载的页面添加标题。如果需要登录,则会跳转到登录页。

const Home = () => import('../views/home/Home.vue');
const Category = () => import('../views/category/Category.vue');
const Search = () => import('../views/search/Search.vue');
 ....
const routes = [
    {
        path: '/',
        name: 'DefaultHome',  // 默认的路由
        component: Home,
        meta: {
            title: 'GoBook——主页',
            keepSearchAlive: true
        }
    },
    ....
}
const router = createRouter({          // 使用浏览器的history API, 而vue-router默认使用的是hash模式            
    history: createWebHistory(process.env.BASE_URL),
    routes
});

router.beforeEach((to, from, next) => {
    if (to.meta.title === undefined) {          // 自定义 重定向页面
        return next('/404error');
    }
    // getters属性中的方法,可以实时监听数据的改变

    if (to.meta.isAuthRequired === true
        && (store.getters.getLoginState === false || store.getters.getLoginState === undefined)
        && to.meta.isMaster === undefined) {

        ElMessage.error('您还没有登录,请先登录');
        return next('/login');
    } else {
        next();
    }
    document.title = to.meta.title;
});   

2.前端数据获取

​ 当前的vue项目是使用后端API。 前端通过 axios发送异步请求给服务器,服务器因为添加了跨域请求,所以在浏览器中可以通过axios获取后台API数据。因此后台的API就是数据来源。

​ 在vue开发的阶段,对axios封装可以增强对网站的可维护性。不同页面的请求分开放置在不同文件上,并且需要对axios添加拦截器,以便于在请求时添加相应的tokens。

import axios from 'axios';
import {ElMessage} from 'element-plus';
import router from '../router';

export function request(config) {
    const instance = axios.create({
        baseURL: 'https://api.shop.eduwork.cn',
        timeout: 5000, 
    });

    // 请求拦截
    instance.interceptors.request.use(config => {
        // 如果有一个接口需要认证才可以访问,就在这统一设置
        const token = window.localStorage.getItem('token');
        if (token) {
            config.headers.Authorization = 'Bearer' + token;
        }
        // 直接放行
        return config;
    }, err => {
        // 获取错误信息 ,只提示首要错误
        ElMessage.error(err.response.data.errors[Object.keys(err.response.data.errors)[0]][0]);
    });

    // 响应拦截器
    instance.interceptors.response.use(res => {
        return res.data ? res.data : res;
    }, err => {
        // 如果有需要授权才可以访问的接口,统一去login授权
        console.log("request error"+err);
        if (err.response && err.response.status === 401) {
            ElMessage.error('当前没有登录,请登录!');
            window.localStorage.setItem('token', '');   // 清除token
            store.commit('setIsLogin', false);              // 清除登录状态信息
            setTimeout(() => {
                router.push({path: '/login'});
            }, 500);
        }

        // 获取错误信息 ,只提示首要错误
        ElMessage.error(err.response.data.errors[Object.keys(err.response.data.errors)[0]][0]);
    });

    return instance(config)
}

四、性能优化

1.路由懒加载

​ 在页面加载时,加载首屏会把所有相关的文件都加载完毕。如果页面数量太多或者文件太大,网络延迟都会降低用户体验,所以在使用vue的建立网站的时候,官方是建议使用懒加载的方式加载路由所指向的vue文件。懒加载方式也会在打包后生成多个chunk文件。

import Home from '../views/Home.vue' 

const routes = [
    {
        path: '/',               // 路径
        name: 'Home',            // 名称
        component: Home          // 组件    引入方式一
    },
    {
        path: '/about',
        name: 'About', 
        component: () => import('../views/About.vue') // 引入方式二,懒加载方式,打包后会生成多个chunk文件
    }
    ];

2.使用vuex

​ 合理使用vuex保存用户登录状态以及使用localStorage,可以有效地减少对后端数据的访问。因为在相关页面需要维持用户信息和一些默认信息的状态,所以在vuex中没有数据的时候,需要再次向后端请求数据。

以下几点也可以减少api的请求:

  • 1.在向后台发送信息前,进行表单验证
  • 2.编辑地址时,数据没有修改,则不会向服务器发起保存请求
  • 3.使用vue x全局变量,只要vuex中有用户的信息,也不向服务器发起请求
  • 4.购物车结算功能,只有在用户点击结算时,才会对服务器中保存的信息进行修改。

3.使用sass预处理器

​ Sass 是一种css预处理器,CSS 预处理器定义了一种新的语言,其基本思想是,用一种专门的编程语言,为 CSS 增加了一些编程的特性,将 CSS 作为目标生成文件,然后开发者就只要使用这种语言进行编码工作。css预处理器:sass - 简书 (jianshu.com)

4.去除多余的控制台输出

​ 在控制台输出,确实性能不是消耗很大。但是网络的有延迟,对页面的渲染多少有影响。在开发阶段的测试是为了验证数据的正确性。如果需要上线运行,必需要去除无用的输出语句,以减少性能的损耗。但目前该项目不是真正上线运行,还保留部分输出代码。

五、网站部署

​ 在这里,小编不建议购买服务器。如果只是做前端项目的话,可以采取另一种方法------托管网站000webhost。 我们只是需要看到上线的效果,并非真正上线,所以这里可以提供另一种方法。使用fastmock去模拟后端接口,使用FastMock数据模拟平台 。同时,也可以写下接口文档ShowDoc API文档工具。 我们可以先获取后端写出的API接口,再写前端页面。 当然,我们也可以先自己写出模拟数据,写出接口文档,让后端根据模拟的接口文档再去开发真实的api。这里模拟的api也不能随便写,需要自己考虑部分逻辑。

​ 一般开发应用,都是前后端一起开发,并非是谁先谁后。只是说,我们可以通过模拟数据来作为数据源。如果前端页面全部都是使用静态数据,那前后端结合的时候,工作量会比较大。

​ 上线的效果:

image-20211106222038707.png

六、总结

​ vuex框架给我们的开发带来了极大的便利,渐近式开发。所谓的渐近式开发就是从中心视图层向外扩展到构建工具层。以中心视图层App.vue作为根页面加载,其它的页面都作为它的子组件。其中会经过组件机制,将不同的组件挂载到页面上。路由机制接管了默认的浏览器页面的跳转。状态管理为我们提供全局变量的功能,在项目运行的时候,维持部分变量的状态。构造工具中,运行时是 用来创建Vue实例、渲染并处理虚拟DOM等的代码。

​ 组件化开发提高了代码的复用性,为开发节省不少时间。对于性能优化方面,除了以上几个方式外,还可以继续优化项目,以达到最佳性能。标准化开发项目,也会对今后的维护做好了基石。因为各种原因,开发该项目已经结束(并非是一个彻底的商域网站,还有部分功能未开发)。只要功夫深,铁杵磨成针。加油!最后感谢学习猿地 - IT培训 (lmonkey.com) 提供的后端API的支持。

​ 这里写上该项目的链接:gobook: 这是一个vue的图书商城项目。采用vue3 + bootstrap4 + jquery 多种框架集合和前端开源库。 (gitee.com)