第一个vue项目

1:搭建vue-cli脚手架

目录

1:搭建vue-cli脚手架

2:对vue文件的一些认识

3写好各组件的的样式,结构

4:路由组件的搭建vue-router

4.1:配置路由

4.2小结

4.3路由跳转的形式

4.4路由传参

4.4全局组件的注册与使用

5:对axios进行二次封装

6:nprogress进度条的使用

7:vuex的使用

7.1:vuex实现模块式开发,其实就是在store里面为每个模块建一个小仓库

8动态渲染三级联动的数据

9节流与防抖:

10:三级联动的路由跳转以及参数的传递。

11:三级联动在不同组件中实现显示与隐藏

11.1显示与隐藏的动画实现

12:利用swiper组件写轮播图

13:Search模块的开发。

14:getter()方法简化获取数据

15:面包屑相关问题

15.1:组件间的通信

 15.2:子向父传递数据

16 排序模块

17:Detail详情模块的开发(步骤:静态组件,路由,请求接口数据,传递参数)

17.1引入静态组件

17.2路由模块

17.3获取api中的数据,之前类似的写过很多次了,这里就简单介绍下步骤

17.4动态展示数据

 17.5点击谁,谁高亮的效果

17.6 放大镜

18:购物车模块

19购物车列表模块

 20登录注册业务

20.1注册模块

20.2登录模块

21交易模块

 22支付模块

 22.1生成二维码

 23个人中心

24图片懒加载vue-lazyload

 25:表单验证

26:路由懒加载

27打包文件(前台项目完结撒花!!!)

28:购买服务器,发布项目


首先要确定我们电脑有安装node,webpack,以及淘宝镜像cnpm

搭建vue-cli脚手架初始化项目

创建一个名为app的vue项目

  

接着选择vue2,等待下载

2:对vue文件的一些认识

关闭eslint校验工具,比如我们申明一个变量但是未使用,这时vue工具会给我们报错,关闭此校验就不会有这个问题。

在根目录下创建vue.config.js文件

module.exports = {
    // 关闭ESLINT校验工具
    lintOnSave: false,
};

 可以给src配置别名为@,因为后期项目经常会用到src,查找的时候也方便

具体方法,在jsconfig.json里面配置,代码如下

{
    "compilerOptions": {
        "baseUrl": "./",
        "paths": {
            "@/*": [
                "src/*"
            ]
        }
    },
    "exclude": [
        "node_modules",
        "dist"
    ]
}

3写好各组件的的样式,结构

(此处省略一万行代码)

4:路由组件的搭建vue-router

先安装vue-router ,在黑窗口中输入cnpm install --save vue-router

4.1:配置路由

路由组件一般放在pages文件夹里面

然后创建router文件夹,对路由组件进行管理

// 配置路由
import Vue from 'vue';
import VueRouter from 'vue-router';

// 使用插件
Vue.use(VueRouter);

//  引入路由组件
import Home from '@/pages/Home'
import Search from '@/pages/Search'
import Login from '@/pages/Login'
import Register from '@/pages/Register'

export default new VueRouter({
    //  配置路由
    routes: [
        {
            path: "/home",
            component: Home
        },
        {
            path: "/search",
            component: Search
        },
        {
            path: "/login",
            component: Login
        },
        {
            path: "/register",
            component: Register
            }
        }
    ]
})

再回到文件的入口文件中引入路由

// 引入路由
import router from '@/router'

new Vue({
  render: h => h(App),
//注册路由
  router
}).$mount('#app')

最后回到app.vue中展示路由(使用router-view)

<template>
  <div>
    <Header></Header>
    <!-- 路由组件出口的地方 -->
    <router-view></router-view>
    <Footer v-show="$route.meta.show"></Footer>
  </div>
</template>

4.2小结


路由组件与非路由组件的区别?
1:路由组件一般放置在pages|views文件夹,非路由组件一般放置components文件夹中
2:路由组件一般需要在router文件夹中进行注册(使用的即为组件的名字),非路由组件在使用的时候,一般都是以标签的形式使用
3:注册完路由,不管路由路由组件、还是非路由组件身上都有$route、$router属性
$route:一般获取路由信息【路径、query、params等等】
$router:一般进行编程式导航进行路由跳转【push|replace】

4.3路由跳转的形式

声明式导航和编程式导航

声明式导航:

//to代表要去的地方
<router-link to="/login">登录</router-link>

编程式导航: 

 methods: {
    // 向Search路由进行跳转
    goSearch() {
      //按钮中的的gosearch方法
      this.$router.push("/search")
    }
}

路由原信息meta

例如

routes: [
        {
            path: "/home",
            component: Home,
            meta: {
                show: true
            }
        },

访问时可以通过$route.meta.show

4.4路由传参

goSearch() {
      // 路由传递参数:
      // 第一种:字符串形式
      // this.$router.push("/search/"+this.keyword+"?k="+this.keyword.toUpperCase());
      // 第二种:模板字符串
      // this.$router.push(`/search/${this.keyword}?k=${this.keyword.toUpperCase()})`);
      // 第三种:对象(最常用)
      // 对象形式传参要写name!!
      this.$router.push({
        name: "search",
        params: { keyword: this.keyword },
        query: { k: this.keyword.toUpperCase() },
      });
    },
  },

params传参要进行占位

// 传params参数要进行占位,问号代表params参数可传可不传
 path: "/search/:keyword?",

4.4全局组件的注册与使用

import TypeNav from '@/components/TypeNav'
// 第一个参数 全局组件的名字  第二个参数   哪一个组件
Vue.component(TypeNav.name, TypeNav)

使用时直接写标签,不需要引入了。

5:对axios进行二次封装

向服务器发请求的方法有:
XMLHttpRequest、fetch、JQ、axios(最常用)
为什么需要进行二次封装axios?
请求拦截器、响应拦截器:请求拦截器,可以在发请求之前可以处理一些业务、响应拦截器,当服务器数据返回以后,可以处理一些事情。

通常在src下新建api文件夹,然后在request.js文件中对axios进行二次封装

// 对axios进行二次封装
import axios from 'axios';

// 1 利用axios对象的方法create,去创建一个axios实例
// 2:request就是axios,只不过稍微配置一下
const requests = axios.create({
    // 配置对象
    // 基础路径,发请求的时候,路径当中会出现api
    baseURL: "/api",
    // 请求超时的时间
    timeout: 5000
});
// 请求拦截器:在发请求之前,请求拦截器可以检测到,可以在请求发出去之前做一些事情
// interceptors拦截
requests.interceptors.request.use((config) => {
    // config:配置对象,独享里面有一个属性很重要,包含header请求头
    return config;
});

// 响应拦截器
requests.interceptors.response.use((res) => {
    //成功的回调函数:服务器响应的数据回来后,响应拦截器可以检测到,可以做一些事情
    return res.data;
}, (error) => {
    // 响应失败的回调函数
    // 比如终结promise
    return Promise.reject(new Error('fail'));
});

export default requests;

当我们需要向后台发送数据时,可以在api文件夹下的index.js文件写发送请求的代码

// 当前这个模块: 对API进行统一的管理
import requests from './request';

// 三级联动接口 api/product/getBaseCategoryList   get方法  无参数
export const reqCategoryList = () => {
    // 发请求  axios发请求返回结果是promise对象
    return requests({ url: '/product/getBaseCategoryList', method: 'get' });
}

在main.js中引入,并测试能否从后台服务器获取到数据

import { reqCategoryList } from '@/api'

reqCategoryList()

测试后我们发现会遇到跨域的问题,跨域就是协议,域名,端口号不同的请求。

一般的解决方法有jsonp,cros,代理

这里我们用代理的方法来解决

我们在项目中的vue.config.js文件中引入如下代码段

// 解决跨域  webpack devserve里面赋值一段代码
    // 这里是代理跨域
    devServer: {
        proxy: {
            '/api': {
                target: 'http://39.98.123.211',
                // pathRewrite: { '^/api': '' },
            },
        },
    }

6:nprogress进度条的使用

安装nprogress插件   cnpm install --save nprogress

我们在二次封装axios的文件中去引入nprogress

当请求拦截器捕获到请求的时候,进度条开始动

当服务器返回数据成功后进度条结束


// 引入进度条
import nprogress from 'nprogress'
// 引入进度条样式
import "nprogress/nprogress.css"
// start 进度条开始  done: 进度条结束

const requests = axios.create({
    baseURL: "/api",
    timeout: 5000
});
// interceptors拦截
requests.interceptors.request.use((config) => {
    // 进度条开始动
    nprogress.start()
    return config;
});
requests.interceptors.response.use((res) => {
    // 进度条结束
    nprogress.done();
    return res.data;
}, (error) => {
    return Promise.reject(new Error('fail'));
});

效果图 

 

进度条颜色可以在nprogress.css文件中修改

 

7:vuex的使用

vuex是什么?vuex是官方提供的一个插件,状态管理库,集中式管理项目中组件共用的数据,但并不是所有的项目都需要vuex,项目小就不需要。

首先安装vuex:   cnpm install --save vuex

这里我们需要注意安装vuex的版本,我之前下的是4.0.2的版本导致崩了,下回3的版本才解决错误。。。。。

vuex的使用:

在store文件夹中,index.js代码如下:

import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);

// state:仓库存储数据的地方
const state = {};

// mutations:修改state的唯一手段
const mutations = {};

// action:处理action,可以书写自己的业务逻辑,也可以处理异步任务
const actions = {};

// getters 理解为计算属性,用于简化仓库数据的,让仓库获取数据更加简单
const getters = {};

// 对外暴露Store类的一个实例
export default new Vuex.Store({
        state,
        mutations,
        actions,
        getters
})

同样,需要在入口文件中引入并注册。

7.1:vuex实现模块式开发,其实就是在store里面为每个模块建一个小仓库

// home模块的小仓库
// state:仓库存储数据的地方
const state = {
    a: 1
};
// mutations:修改state的唯一手段
const mutations = {};
// action:处理action,可以书写自己的业务逻辑,也可以处理异步任务
const actions = {};
// getters 理解为计算属性,用于简化仓库数据的,让仓库获取数据更加简单
const getters = {};

export default {
    state,
    mutations,
    actions,
    getters
}

在store里面暴露一下就好啦

// 对外暴露Store类的一个实例
export default new Vuex.Store({
    modules: {
        home,
        search
    }

})

8动态渲染三级联动的数据

三级联动的组件是TypeNav,首先在TypeNav组件中向vuex发送请求

// 组件挂载完毕,可以向服务器发送请求
  mounted() {
    // 通知vuex发送请求,将数据存储于仓库中
    this.$store.dispatch("categoryList");
  },

由于是在home模块,所以找到home模块的小仓库,对actions,mutations,state函数进行完善

// home模块的小仓库
import { reqCategoryList } from '@/api'
const state = {
    categoryList: []
};
const mutations = {
    CATEGORYLIST(state, categoryList) {
        state.categoryList = categoryList
    }
};
const actions = {
    //通知API里面的接口函数,向服务器发送请求,获取服务器的数据
    async categoryList({ commit }) {               //解构commit
        let result = await reqCategoryList();
        if (result.code == 200) {
            commit("CATEGORYLIST", result.data)    //向mutations发送数据
        }

    }
};

最后回到TypeNav中即可获取到数据

computed: {
    ...mapState({
      // 注入了一个参数,即为大仓库中的数据
      categoryList: (state) => {
        return state.home.categoryList
      },
    }),
  },

然后在上面进行渲染就ok啦

<div class="sort">
        <div class="all-sort-list2">
          <div
            class="item bo"  v-for="(c1, index) in categoryList" :key="c1.categoryId"
          >
            <h3>
              <a href="">{{ c1.categoryName }}</a>
            </h3>

三级联动的动态背景:

首先给元素绑定一个鼠标经过触发事件 @mouseenter="changeindex(index)"

函数为changeindex(index) { this.currentIndex = index;}

然后通过判断给盒子动态添加样式(cur为自定义样式)

<div
     v-for="(c1, index) in categoryList"
    :key="c1.categoryId"
    :class="{ cur: currentIndex == index }" 

最后记得定义鼠标离开事件mouseleave。

9节流与防抖:

节流:在规定的间隔时间范围内不会重复触发回调,只有大于这个时间间隔才会触发回调,把频繁触发变为少量触发
防抖:前面的所有的触发都被取消,最后一次执行在规定的时间之后才会触发,也就是说如果连续快速的触发 只会执行一次节流:在规定的间隔时间范围内不会重复触发回调,只有大于这个时间间隔才会触发回调,把频繁触发变为少量触发。

简单来说就是

防抖:用户操作很频繁,但是只是执行一次
节流:用户操作很频繁,但把频繁的操作变为少量操作(可以给浏览器有充裕的时间解析代码)

三级联动的节流:使用node包里面的lodash

1 引入lodash:  import _ from 'lodash'

但是最好按需引入

import { throttle } from "lodash";

//下面是es5的写法   为了写节流函数
    changeindex: throttle(function (a) {
      this.currentIndex = a;
    }, 50),

throttle函数不要用箭头函数,防止出现this指向问题

10:三级联动的路由跳转以及参数的传递。

①不能用声明式导航,声明式导航会出现卡顿现象,因为router-link是一个组件,当服务器返回数据时会循环出很多组件实例,导致卡顿。所以我们使用编程式导航,结合事件委派来写。

存在一些问题:

①事件委派,是把全部的子节点【h3、dt、dl、em】的事件委派给父亲节点

②点击a标签的时候,才会进行路由跳转【怎么能确定点击的一定是a标签】

③存在另外一个问题:即使你能确定点击的是a标签,如何区分是一级、二级、三级分类的标签。

我们可以给每一个子节点当中a标签加上自定义属性data-categoryName,其余的子节点是没有的

 例如  :data-categoryName="c1.categoryName"  :data-category1Id="c1.categoryId"

goSearch(event) {
      //获取节点
      let element = event.target;

      //节点有一个属性dataset属性,可以获取节点的自定义属性与属性值
      //注意自定义属性会自动变成小写!
      let { categoryname,category1id,category2id,category3id }=element.dataset;
      
      if (categoryname) {
        // 整理路由跳转的参数
        let location = { name: "search" };
        let query = { categoryName: categoryname };

        //一级分类、二级分类、三级分类的a标签
        if (category1id) {
          query.catrgory1Id = category1id;
        } else if (category2id) {
          query.catrgory2Id = category2id;
        } else {
          query.catrgory3Id = category3id;
        }
        // 整理完参数
        // console.log("12", location, query);
        location.query = query;

        // 路由跳转
        this.$router.push(location);
      }
    },
  },

11:三级联动在不同组件中实现显示与隐藏

在三级联动组件中利用mounted挂载来实现

<div class="sort" v-show="show">
if (this.$route.path != "/home") {
      this.show = false;
    }

再结合mouseenter与mouseleave来实现。

11.1显示与隐藏的动画实现

①在需要动画的地方包裹上transition标签(最好加上名字)

②写动画效果

// 开始状态
    .sort-enter {
      height: 0px;
      // transform: rotate(0deg);
    }
      // 结束状态
    .sort-enter-to {
      height: 461px;
      // transform: rotate(360deg);
    }
    // 定义动画的时间,速率
    .sort-enter-active {
      transition: all 0.5s linear;
    }

性能优化:由于会多次用到TypeNav组件,导致会多次发送同样的请求,所以我们将请求放在根组件里面。即App.vue

mounted() {
    // 通知vuex发送请求,将数据存储于仓库中
    this.$store.dispatch("categoryList");
  },
};

这样就只会调用一次。

12:利用swiper组件写轮播图

①安装swiper :  cnpm install --save swiper@5

②引入 css和js文件和js代码段

import Swiper from'swiper'

import "swiper/css/swiper.css"

js代码段放在mounted里面。

但是这样做以后会发现没有效果,为什么呢?因为我们遇到了异步的问题

 

 我们期待的顺序时①②③④,因为这样就能正常的渲染数据

 而调试完才发现顺序是①②④③,

可以看到bannerlist数据还没回来,mounted就挂载完毕了,所以获取不到数据,关键就是dispatch方法,这是一个异步语句,导致v-for遍历的时候结构还不完全,所以我们需要解决这个问题。

 我们需要用watch监听属性和$nextTick来解决这个问题

官方介绍nextTick:在下次DOM更新 循环结束之后 执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
      当执行了handler方法,代表组件实例身上这个属性的属性更新了,因为一修改数据之后,watch就会检测到,然后使用nextTick方法,获取更新后的 DOM.当你执行这个回调的时候,服务器的数据回来了,v-for执行完毕了,所以轮播图的结构就一定有了。

        <div class="swiper-container" ref="mySwiper">
            <div class="swiper-wrapper">
              <div
                class="swiper-slide"
                v-for="(carousel, index) in bannerList"
                :key="carousel.id"
              >
                <img :src="carousel.imgUrl" />
              </div>
            </div>
            <!-- 如果需要分页器 -->
            <div class="swiper-pagination"></div>

            <!-- 如果需要导航按钮 -->
            <div class="swiper-button-prev"></div>
            <div class="swiper-button-next"></div>
          </div>
       </div>



watch: {
    //监听bannerList数据的变化:因为这条数据发生过变化----由空数组变为数组里面有四个元素
    bannerList: {
      handler(newValue, oldValue) {
        this.$nextTick(() => {
           //swiper的模板
          var mySwiper = new Swiper(
            this.$refs.mySwiper,
            {
              loop: true, // 循环模式选项
              // 如果需要分页器
              pagination: {
                el: ".swiper-pagination",
                clickable: true,
              },
              // 如果需要前进后退按钮
              navigation: {
                nextEl: ".swiper-button-next",
                prevEl: ".swiper-button-prev",
              },
            }
          );
        });
      },
    },
  },

props传递数据:

父组件Home传给子组件Floor数据

 子组件接收:  

 floor模块继续使用swiper组件,但是这里却可以直接把js代码放在mounted中, 之前不能放在mounted中是因为在组件内部发送请求,动态渲染,这里的轮播图数据是父组件中传来的,所以可直接放在mounted中,不用使用watch+$nextTick。

mounted() {
    // 之前不能放在mounted中,因为是在组件内部发送请求,动态渲染的,这里是父组件中传来的数据,所以可以直接放在mounted中,不用使用nextTick
    var mySwiper = new Swiper(this.$refs.cur, {
      // direction: "vertical", // 垂直切换选项
      loop: true, // 循环模式选项

      // 如果需要分页器
      pagination: {
        el: ".swiper-pagination",
        clickable: true,
      },

      // 如果需要前进后退按钮
      navigation: {
        nextEl: ".swiper-button-next",
        prevEl: ".swiper-button-prev",
      },
    });
  },

13:Search模块的开发。

在前面的学习中,我们基本可以了解开发一个模块的大概流程。

①静态页面和静态组件

②发送请求(API)

③vuex的三连环

④组件获取仓库数据,动态展示数据。

这里的第一步就先省略了,来到第二步的api

现在API中发送请求,获取数据。

通过查阅API文档我们知道当前这个函数需要接收外部传递参数,要给服务器传送一个默认参数(至少是一个空对象),所以在api/index.js文件下的代码如下:

export const reqGetSearchInfo = (params) => requests({ url: "/list", method: "post", data: params })

还要再main.js中引入

import { reqGetSearchInfo } from '@/api'

③vuex三连环

第一步在search仓库中写以下代码

 第二步在Search组件中派发getSearchList方法

 vuex三连环比较简单,就不过多讲解了。

14:getter()方法简化获取数据

之前我们用mapState获取数据时比较繁琐,需要state.仓库名.数据.属性来获取数据。

import {mapState} from 'vuex'

.....
computed{
    ...mapState({
      goodsList:state=>state.search.searchList.goodsList  
    })
}

所以使用getter可来简化仓库中的数据

首先在仓库中使用getter方法:

const getters = {
    goodsList(state) {
         //这里需要定义一个空数组是因为加入当前网络不给力的话,goodlist返回的是undefined,就会遍历出错
        return state.searchList.goodsList || [];
    }
};

然后再回到组件中调用以下就好了

import { mapGetters } from "vuex";
.......

computed: {
    ...mapGetters(["goodsList"]),
  },

因为getters是全局属性,所以数据部分模块,我们直接...mapGetters["数据名"]就能获取数据。

下一步,根据不同的参数获取数据并渲染

这是我们需要的数据

 我们在组件挂载之前获取一次数据,把接口需要的参数进行整理,这里我们用到了ES6新增的语法,用Object.assign来合并对象

//当组件挂载完毕之前执行一次【先与mounted之前】
  beforeMount() {
    //复杂的写法
    // this.searchParams.category1ld =this.$route.query.categorylld;
    // this.searchParams.category2Id =this.$route.query.category2Id;
    // this.searchParams.category3Id =this.$route.query.category3Id;
    // this.searchParams.categoryName = this.$route.query.categoryName;
    // this.searchParams.keyword = this.$route.params.keyword;//0bject.assign:ES6新增的语法,合并对象
    // Object.assign:ES6新增语法,合并对象
    Object.assign(this.searchParams, this.$route.query, this.$route.params);
  },

之前放在mounted里面的发送数据方法,要放到methods里面,方便调用

 mounted() {
    // 发请求之前把参数带给服务器
    this.getData();
  },
  methods: {
    getData() {
      this.$store.dispatch("getSearchList", this.searchParams);
    },
  },

18.2:search模块优化

上面的方法只能搜索一次,这里我们使用watch监测数据,一旦路由的参数改变我们就再次发送请求。

watch: {
    $route(newValue, oldValue) {
      // 再次发送请求之前整理好带给服务器的参数
      Object.assign(this.searchParams, this.$route.query, this.$route.params);
      // 再次发起ajax请求
      this.getData();
    },
  },

15:面包屑相关问题

 这些带有‘X’的我们称为面包屑,进入分类界面或者输入关键字进行搜索时,都会出现面包屑,且分类会带有params参数,关键字搜索带有query参数,所以只要参数发生变化,watch就能监测到,从而发起ajax请求。

当我们点击‘X’时,要调用函数来删除面包屑,同时重新渲染页面,让网页的参数变回之前的样子,这时候我们就需要调用一个自定义函数,具体代码如下:

<li class="with-x" v-if="searchParams.categoryName">
   {{ searchParams.categoryName}}<i @click="removeCategoryName()">x</i>
</li>
// 删除分类的名字
    removeCategoryName() {
   // 把带给服务器的参数置空后,还需向服务器发送请求
   // 带给服务器的参数可有可无:应该把字段变为undefined,变为空的话还会带给服务器,而undefined不会
       this.searchParams.categoryName = undefined;
       this.searchParams.category1Id = undefined;
       this.searchParams.category2Id = undefined;
       this.searchParams.category3Id = undefined;
       this.getData();
   // 地址栏也需要修改:可以用路由跳转的方法(跳到自己原来的路由)
   // 但是这个方法不够严谨,这样会把params和query参数都置空,而这里params不需要置空,应该这么写:
      if (this.$route.params) {
        this.$router.push({ name: "search", params: this.$route.params });
      }
   },

15.1:组件间的通信

在搜索时,我们的关键字会出现在面包屑上,当我们点击删除面包屑,也要使搜索栏的关键字置空,我们知道搜索栏是属于Header组件的,而面包屑是属于Search组件的,这时候就需要用到组件间的通信,$bus

首先我们在main.js中配置全局事件总线

然后在Search模块中通知Header组件去触发函数

 最后在Hearer模块中的mounted挂载里面实现关键字的清除,组件挂载时就监听clear事件

 15.2:子向父传递数据

父组件绑定一个自定义函数

子组件中定义一个函数并传递数据

子组件中触发父组件的函数传递数据

 父组件中自定义事件的测试

 这样就完成了子向父传递数据,当我们点击子组件时,会向父组件传递数据,并得到数据

点击苹果(一个子组件)

可以在父组件中得到数据

 

 最终我们是要在父组件中获取子组件的一些参数

品牌的面包屑删除也是同理

<!-- 品牌的面包屑  -->
<li class="with-x" v-if="searchParams.trademark">
     {{ searchParams.trademark.split(":")[1]}}
     <i @click="removeTradeMark()">x</i>
</li>
removeTradeMark() {
      // 将品牌信息置空
      this.searchParams.trademark = undefined;
      // 再次发送请求
      this.getData();
},

search模块基本结束

16 排序模块

先在阿里图标找到我们需要的图标,生成代码,复制到public文件夹中,在index文件里面引入,记得加https:

按钮的升序降序

<ul class="sui-nav">
    <li :class="{ active: isOne }" @click="changeOrder(1)">
         <a>综合
           <span class="iconfont" :class="{ 'icon-down': isDesc, 'icon-up': isAsc }"
                 v-show="isOne">
           </span>
          </a>
     </li>
     <li :class="{ active: isTwo }" @click="changeOrder(2)">
           <a>价格
                 <span v-show="isTwo" class="iconfont"
                  :class="{ 'icon-down': isDesc, 'icon-up': isAsc }">
                 </span>
           </a>
     </li>
</ul>
data(){
  return{
     .......
     // 排序
     order: "1:asc",
  }
} 
//排序的操作
    changeOrder(flag) {
      //这里获取到的是最开始的状态
      let originFlag = this.searchParams.order.split(":")[0];
      let orginSort = this.searchParams.order.split(":")[1];
      //准备一个新的order属性值
      let newOrder = "";

      // 若点击的是同一个按钮,改变升序和降序
      if (flag == originFlag) {
        newOrder = `${flag}:${orginSort == "desc" ? "asc" : "desc"}`;
        // newOrder = `${originFlag}:${orginSort == "desc" ? "asc" : "desc"}`;
      }
 
      else {
        //不是同一个按钮,取传入的按钮,然后默认为降序
        newOrder = `${flag}:${"desc"}`;}

      //将新的order赋了searchParams
      this.searchParams.order = newOrder;
      //再次发请求
      this.getData();
    }

17:Detail详情模块的开发(步骤:静态组件,路由,请求接口数据,传递参数)

17.1引入静态组件

17.2路由模块

①引入路由

import Detail from '@/pages/Detail'

②配置路由

 routes: [
        {
             //params参数占位
            path: "/detail/:skuid",
            component: Detail,
            meta: {
                show: true
            }
        },

我们从search页面跳转到detail页面时,要传递参数,我们的效果是点击图片进入详情页面,所以这里我们可以借用声明式路由导航跳转,并传递参数。

 当路由配置信息太多的时候,我们可以新建一个路由配置文件routes.js,再引入router文件夹下的index.js文件。

当我们从一个路由跳到另一个路由时,如果我们想要滚动条置顶,我们可以在路由管理文件中加入scrollBehavior函数

export default new VueRouter({
    //  配置路由
    routes,
    scrollBehavior(to, from, savedPosition) {
        // 始终滚动到顶部
        return { y: 0 }
    }
})

17.3获取api中的数据,之前类似的写过很多次了,这里就简单介绍下步骤

①在api文件中获取详情的数据

export const reqGoodsInfo = (skuId) => requests({ url: "/item/${`skuId`}", method: "get" })

②在仓库中获取数据

import { reqGoodsInfo } from '@/api';
const state = {
    goodInfo: {},
};
const mutations = {
    GETGOODINFO(state, goodInfo) {
        state.goodInfo = goodInfo
    }
};
const actions = {
    async getGoodInfo({ commit }, skuid) {
        let result = await reqGoodsInfo(skuid)
        if (result.code == 200) {
            commit('GETGOODINFO', result.data)
        }
    }
};
const getters = {};
export default {
    state,
    mutations,
    actions,
    getters
}

 在挂载时派发请求 

mounted() {
    this.$store.dispatch("getGoodInfo", this.$route.params.skuid);
  },

17.4动态展示数据

用getter来简化数据

const getters = {
    categoryView(state) {
        return state.goodInfo.categoryView
    }
};

获取数据 

import { mapGetters } from "vuex";

.....

computed: {
    ...mapGetters(["categoryView"]),
  },

渲染数据

<div class="conPoin">
        <span v-show="categoryView.category1Name">{{categoryView.category1Name}}</span>
        <span v-show="categoryView.category2Name">{{categoryView.category2Name}}</span>
        <span v-show="categoryView.category3Name">{{categoryView.category3Name}}</span>
</div>

这样写数据能够显示出来,但是我们发现会有报错,

 原因其实很简单,因为第一次获取goodInfo时,数据是空的,但是我们还继续获取了goodInfo.categoryView,得到的数据是undefined,所以会报错,我们应该加一个空对象{}

 17.5点击谁,谁高亮的效果

 这里我们要用到排他思想,要获取到当前点击的选项的数组,还有当前选项,然后把当前数组的ischeck全部换成非选中状态,然后令当前点击的选项设置为选中状态即可。

 

17.6 放大镜

先完成轮播图,之前做过,这里简单回顾一下,先引入swiper文件,再引入swiper样式,然后将js代码写入watch中(为什么不写入mounted之前有说过)

watch: {
   // 监听数据:可以保证数据一定ok,但不能保证v-for循环是否完成,所以需要$nextTick来等待v-for完成
    skuImageList(newValue, oldValue) {
    // $nextTick() 在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。

      this.$nextTick(() => {
        new Swiper(this.$refs.cur, {
          loop: true, // 循环模式选项
          // 如果需要前进后退按钮
          navigation: {
            nextEl: ".swiper-button-next",
            prevEl: ".swiper-button-prev",
          },
        });
      });
    },

 接下来用js实现点击图片添加背景,之前也写过类似的

首先默认选中第一张

 然后写判断和点击事件

 最后实现点击事件

methods: {
    changeCurrentIndex(index) {
      this.currentIndex = index;
    },
}

接下来实现点击下面轮播图图片,上面大图也跟着变

因为这涉及到两个兄弟组件传递数据,所以要使用$bus,之前也实现过类似的功能 

通知兄弟组件

 兄弟组件接收数组,在mounted中接收即可

 mounted() {
    // 全局事件总线,获取兄弟组件传递过来的索引值
    this.$bus.$on("getIndex", (index) => {
      //修改当前响应式数据
      this.currentIndex=index
    });
  },

放大镜移动效果,这里就不解释了

<div class="spec-preview">
    <img :src="imgObj.imgUrl" />
    <div class="event" @mousemove="handler"></div>
    <div class="big">
      <img :src="imgObj.imgUrl" ref="big" />
    </div>
    <div class="mask" ref="mask"></div>
  </div>
handler(event) {
      let mask = this.$refs.mask;
      let big = this.$refs.big;
      let left = event.offsetX - mask.offsetWidth / 2;
      let top = event.offsetY - mask.offsetHeight / 2;
      //约束范围
      if (left <= 0) left = 0;
      if (left >= mask.offsetWidth) left = mask.offsetWidth;
      if (top <= 0) top = 0;
      if (top >= mask.offsetHeight) top = mask.offsetHeight;
      //修改元素的left|top属性值
      mask.style.left = left + "px";
      mask.style.top = top + "px";
      big.style.left = -2 * left + "px";
      big.style.top = -2 * top + "px";
    },

18:购物车模块

我们需要向购物车的借接口发送请求,但是这里我们并不需要从接口中获取数据

第一步仍然是在api中写好接口

export const reqAddOrUpdateShopCart = (skuId,skuNum) => 
requests({ url: `/cart/addToCart/${ skuId }/${ skuNum }`, method: "post"})

因为是在Detail文件中实现购物车功能,所以在detail的vuex文件中发送请求获取数据

// 将产品添加到购物车中
async addOrUpdateShopCart({ commit }, { skuId, skuNum }) {
  // 加入购物车返回的结果
  let result = await reqAddOrUpdateShopCart(skuId, skuNum)
  console.log(result);
}

当点击“加入购物车”按钮时,我们派发请求,

  // 加入购物车的回调函数
    addShopcar() {
      // 1发请求--将产品加入数据库(通知服务器)
      this.$store.dispatch("addOrUpdateShopCart", 
        {skuId: this.$route.params.skuid,skuNum: this.skuNum,});

      // 2存储成功,进行路由跳转
      // 3存储失败,给用户提示
    },

 这里我们并不需要vuex三连环,因为服务器并没有返回数据,我们只要成功发送请求就好。

接下来我们要进行加入购物车成功与失败的判断及相关操作

先回到仓库中进行成功与失败的判断

 再回到点击加入购物车的函数中,我们要知道,我们派发的函数最后得到的结果一定是一个promise,因为我们要调用的函数带有async,所以在addShopcar函数中,我们要等待vuex中addOrUpdateShopCart函数返回结果,所以加上await等待promise返回结果 

 将result进行打印,如果成功就会打印ok,失败会打印faile

所以这里我们用try catch 来对不同结果进行处理

 利用会话存储进行参数传递

我们在进行路由跳转并把产品信息带给下一级路由组件时,简单的参数可以通过query参数带过去,但是复杂的数据要通过会话存储传递(不持久话,会话结束数据就消失)。

本地存储/会话存储 一般存储的是字符串

获取数据:

 

 之后再根据数据渲染就可以了

19购物车列表模块

还是一样的操作,写好路由,在api文件中获取接口,组件挂载派发action请求,建小仓库vuex三连环。

首先封装请求函数

export const reqCartList = () => requests({ url: '/cart/cartList', method: 'get' })

因为这里我们要访问API获取详细信息,所以需要验证身份,我们用一个uuidToken来验证,

我们建一个utils文件夹专门放uuid_token,

import { v4 as uuidv4 } from 'uuid'
export const getUUID = () => {
    // 先从本地存储获取一个随机字符串,且每次执行不能发生变化,游客身份持久存储
    let uuid_token = localStorage.getItem('UUIDTOKEN');
    // 如果没有
    if (!uuid_token) {
        // 生成游客身份
        uuid_token = uuidv4();
        // 本地存储一次
        localStorage.setItem('UUIDTOKEN', uuid_token);
    }
    return uuid_token;
}

上面这样写是利用本地存储生成一个唯一的id,然后在detail文件下获取uuid_token

import { getUUID } from '@/utils/uuid_token'
const state = {
    goodInfo: {},
    // 游客临时身份
    uuid_token: getUUID()
};

接着我们在请求拦截器中获取uuid_token

在api/requests.js文件中首先引入store仓库

import store from '@/store'

然后再请求拦截器中写以下代码(if语句)

// 请求拦截器:在发请求之前,请求拦截器可以检测到,可以在请求发出去之前做一些事情
// interceptors拦截  config是配置对象
requests.interceptors.request.use((config) => {

    if (store.state.detail.uuid_token) {
        // 请求头添加一个字段userTempId(该变量是项目中定义好的)
        config.headers.userTempId = store.state.detail.uuid_token;
    }
    nprogress.start()
    // config:配置对象,独享里面有一个属性很重要,包含header请求头
    return config;
});

可以在请求头中成功找到数据

 修改购物车中商品数量:

首先给两个按钮和输入框绑定同一个事件handler,分别传指令,数字和参数

<a  class="mins" @click="handler('minus', -1, cart)">-</a>
<input autocomplete="off" type="text" :value="cart.skuNum" minnum="1"class="itxt"
     @change="handler('change', $event.target.value * 1, cart)"/>
<a class="plus" @click="handler('add', 1, cart)">+</a>

 handler函数:

async handler(type, disNum, cart) {
      switch (type) {
        case "add": disNum = 1; break;

        // 如果是减,要判断产品的个数是否小于1,大于1才传给服务器,小于1就传0
        case "minus":
          disNum = cart.skuNum > 1 ? -1 : 0; break;
      }

      try {
        //等待返回结果,用await,响应的handler要加async
        await this.$store.dispatch("addOrUpdateShopCart", {
          skuId: cart.skuId,
          skuNum: disNum,
        });
        //再次发送请求
        this.getData();
      } catch (error) {}
    },

这样的话看似没有问题,但是如果用户疯狂点击减按钮,数量会变成负数,所以我们要使用节流,就是限制单位时间内点击的次数。  引入一下节流的文件然后稍微修改一下函数即可

import { throttle } from "lodash";
  handler:throttle (async function (type, disNum, cart) {
      ......
    },500),

删除购物车产品

①封装接口

export const reqDeleteCartById = (skuId) => requests({ 
    url: `/cart/deleteCart/${skuId}`, method: 'delete' })

②vuex:

const actions = {
    async deleteCartListBySkuId({ commit }, skuId) {
        let result = await reqDeleteCartById(skuId);
        if (result.code == 200) {
            return 'ok';
        } else {
            return Promise.reject(new Error('faile'));
        }
    }
};

vue文件中定义一个点击事件来触发请求xiuga

methods:{
    async deleteCartById(cart) {
      try {
        //删除成功发送请求
        await this.$store.dispatch("deleteCartListBySkuId", cart.skuId);
        this.getData();
      } catch (error) {
        console.log(error.message);
      }
    },
}

 修改产品的选中状态与删除购物车基本一致,就不再介绍了。

一次删除多个商品

由于该项目没有删除多个商品的API请求,所以我们只能多次调用删除单个商品的接口

我们在删除按钮派发一个dispatch,这里用到一个新的参数context,我们不妨看看context内容是什么(默认传递的参数就是context)

 deleteAllCheckedCart(context) {
        console.log(context);
    }

 可以看到有dispatch,getters,commit等,我们可以理解context为一个小仓库

然后我们在vuex文件中调用

deleteAllCheckedCart({ dispatch, getters }) {
   // context可以理解为小仓库

   let PromiseAll = [];
  // 获取购物车中的全部产品(一个数组)
   getters.cartList.cartInfoList.forEach(item => {
      let promise = item.isChecked == 1 ?dispatch('deleteCartListBySkuId', item.skuId):'';
      PromiseAll.push(promise);// 将每一次返回的Promise添加到数组中
   });

   // Promise.all([p1,p2,p3]) 函数:全部成功才返回成功,只要一个失败就返回失败
   return Promise.all(PromiseAll)
}
// 删除全部选中商品
    async deleteAllCheckedCart() {
      try {
        await this.$store.dispatch("deleteAllCheckedCart");
        this.getData();
      } catch (error) {
        error.message;
      }
    },

这里用到一个专门用于promise的api,Promise.all ( [ p1 , p2 , p3] ),只有全部成功才返回成功。

修改全部商品的选中状态

与一次删除所有商品类似,修改全部商品选中状态也要调用修改单个商品的选中状态函数,

// 修改全部产品的状态
updateAllCartIsChecked({ dispatch, getters }, isChecked) {
    let promiseAll = []
    getters.cartList.cartInfoList.forEach((item) => {
         let promise = dispatch('updateCheckedById', { skuId: item.skuId, isChecked })
         promiseAll.push(promise)
    })
    return Promise.all(promiseAll)
}

 在全选框定义点击事件updateAllCartChecked

// 修改全部商品的选中状态
    async updateAllCartChecked(event) {
      try {
        let isChecked = event.target.checked ? "1" : "0";
        await this.$store.dispatch("updateAllCartIsChecked", isChecked);
        this.getData();
      } catch (error) {
        alert("error.message");
      }
    },

 最后有一个小bug,就是点击全选按钮后再逐个删除商品,商品删除完后全选按钮依旧是选中状态,所以当商品个数为0时我们要使全选框为不勾选状态

 20登录注册业务

注意:assets放置全部组件共用的静态资源

20.1注册模块

封装接口,建立仓库,调用接口,派发action

仓库中获取验证码和点击注册的action

const actions = {
    // h获取验证码
    async getCode({ commit }, phone) {
        let result = await reqGetCode(phone)
        if (result.code == 200) {
            commit("GETCODE", result.data)
            return 'ok'
        } else {
            return Promise.reject(new Error('faile'))
        }
    },
    // 用户注册
    async userRegister({ commit }, user) {
        let result = await reqUserRegister(user);
        if (result.code == 200) {
            return 'ok'
        } else {
            return Promise.reject(new Error('faile'))
        }
    }
};

这里用v-model来双向绑定数据

 派发action,

这里解释一下 const { phone, code, password, password1 } = this;(参考某位大佬的)

 const { phone, code, password, password1 } = this;

等于

const phone=this.phone
const phone=this.code
const passwored1=this.password1
const passwored1=this.password1
//用户注册 
async userRegister() {
      try {
        const { phone, code, password, password1 } = this;
        phone &&code &&password == password1 &&(await this.$store.dispatch("userRegister", 
        {phone,code,password,}));
        this.$router.push("/login");
      } catch (error) {
        console.log(error.message);
      }
    },

20.2登录模块

action模块 

//用户登录
    async userLogin({ commit }, data) {
        let result = await reqUserLogin(data)
        // 服务器会下发一个token,是用户的位移标识符(类似之前的uuid)
        // 将来经常通过token来找服务器要用户的信息进行展示
        if (result.code == 200) {
            commit("USERLOGIN", result.data.token)
            return 'ok'
        } else {
            return Promise.reject(new Error("faile"))
        }
    }

登录函数

// 登录按钮
async userLogin() {
   try {
      const { phone, password } = this;
      phone &&password &&(await this.$store.dispatch("userLogin", { phone, password }));
        this.$router.push("/home");
   } catch (error) {
        alert(error.message);
    }
},

在登录时会收到一个token,由于vuex不是持久化存储, 刷新一下就没有了,所以为了持久化保存token,要用到本地存储

action部分代码:

 //用户登录
  async userLogin({ commit }, data) {
      let result = await reqUserLogin(data)
      // 服务器会下发一个token,是用户的位移标识符(类似之前的uuid)
      // 将来经常通过token来找服务器要用户的信息进行展示
      if (result.code == 200) {
         commit("USERLOGIN", result.data.token);
         // 持久换存储token
         localStorage.setItem('TOKEN', result.data.token)
         return 'ok'
     } else {
         return Promise.reject(new Error("faile"))
     }
  },

state部分的代码

const state = {
    code: '',
    //获取本地存储的token
    token: localStorage.getItem('TOKEN'),
    // token: '',
    userInfo: {}
};

成功登陆后获取用户信息

// 获取用户信息
    async getUserInfo({ commit }) {
        let result = await reqUserInfo()
        console.log(result);
        // 提交用户信息
        if (result.code == 200) {
            commit("GETUSERINFO", result.data)
            return 'ok'
        }
    }

登录部分 还有很多未完善的地方,比如跳转到别的模块刷新一下,又不会显示登录人信息了

退出登录

action部分

// 退出登录
async userLogout({ commit }) {
    let result = await reqLogout()
    if (result.code == 200) {
        commit('CLEAR');
        return 'ok';
    } else {
        return Promise.reject(new Error('faile'))
    }
}

函数部分(记得跳转回首页)

async logout() {
    try {
      await this.$store.dispatch("userLogout");
      //回到首页,路由跳转
      this.$router.push("/home");
    } catch (error) {
      console.log(error.message);
    }
},

路由守卫

先简单复习一下各种路由守卫

21交易模块

这里仍然是前面的四个步骤:

①封装API

②建立小仓库写好vuex三件套

③dispatch派发请求

④数据的渲染

所以就不详细说明了,只讲一些讲得少的地方。

 这里我们需要一个点击谁谁高亮的效果,所以用到排他思想

首先绑定点击事件

 再写函数(address是当前数组的,addressInfo是整个数组)

 changeDefault(address, addressInfo) {
      // 排他思想,全部的default变为0,当前点击的变为1
      addressInfo.forEach((item) => (item.isDefault = 0));
      address.isDefault = 1;
 },

下面的收货人信息也要根据我们所选择的信息改变

我们要用到find方法来选择数据

   // 将来提交订单最终选中的地址
    userDefaultAddress() {
      // find查找数组中符合条件的元素返回,就为最终的结果
      return this.addressInfo.find((item) => item.isDefault == 1)||{};
    },

提交订单模块

我们依旧是封装好提交订单的API,但是假设我们不用vuex来做,那么应该怎么做呢?

首先在main.js文件中统一引入API文件

// 统一接收api文件夹里面的全部函数请求,统一引入
import * as API from '@/api'

然后挂载到原型对象身上

 定义一个点击事件

 这里就会用到$API

完整代码 (这里需要传的参数有点多,一定要细心)

// 提交订单
    async submitOrder() {
      // 交易编码
      let { tradeNo } = this.orderInfo;
      // 其余6个参数
      let data = {
        consignee: this.userDefaultAddress.consignee, //最终收件人名字
        consigneeTel: this.userDefaultAddress.phoneNum, //手机号
        deliveryAddress: this.userDefaultAddress.fullAddress, //收件人地址
        paymentWay: "ONLINE", //支付方式
        orderCommit: this.msg, //买家留言
        orderDetailList: this.orderInfo.detailArrayList, //商品清单
      };
      // 需要带参数的tradeNo
      let result = await this.$API.reqSubmitOrder(tradeNo, data);
      
      if (result.code == 200) {
        this.orderId = result.data;
        // 路由跳转+路由传递参数
        this.$router.push("/pay?orderId=" + this.orderId);
      } else {
        alert(result.data);
      }
    },

 22支付模块

封装支付的接口

// 获取支付信息
export const reqPayInfo = (orderId) => requests({ url: `/payment/weixin/createNative/${orderId}`, method: 'get' })

尽量不要在生命周期函数写async await,所以在method中用

// 尽量不要在生命周期函数写async await,所以在method中用
  // async mounted() {
  //   await this.$API.reqPayInfo(this.orderId)
  // },
  mounted() {
    this.getPayInfo();
  },
  methods: {
    async getPayInfo() {
      let result = await this.$API.reqPayInfo(this.orderId);
      console.log(result, "12121");
    },
  },

后面就是获取数据渲染数据,大同小异

async getPayInfo() {
    let result = await this.$API.reqPayInfo(this.orderId);
    if (result.code == 200) {
       this.payInfo = result.data;
    }
}

使用element-ui 先下载

cnpm install --save element-ui

再去element-ui官网看怎么使用

推荐一个插件vue-helper,输入el就会有提示

在main.js中引入

import { Button, MessageBox } from 'element-ui'
// 注册element-ui 全局组件
Vue.component(Button.name, Button)
// 注册element-ui 另一种方法 挂在原型
Vue.prototype.$msgbox = MessageBox;
Vue.prototype.$alert = MessageBox.alert;

找到对应的组件并使用

open() {
      this.$alert("<strong>这是 <i>HTML</i> 片段</strong>", "HTML 片段", {
        dangerouslyUseHTMLString: true,
        center: true,
        showCancelButton: true,
        cancelButtoText: "支付遇见问题",
        confirmButtonText: "已支付成功",
        showClose: false,
      });
    },

 22.1生成二维码

 npm 中找qrcode

下载 cnpm i qrcode

引入qrcode

import QRCode from "qrcode";

传字符串即可生成二维码

async open() {
      let url = await QRCode.toDataURL(this.payInfo.codeUrl);
      this.$alert(`<img src=${url} />`, "请支付", {
        dangerouslyUseHTMLString: true,
        center: true,
        showCancelButton: true,
        cancelButtoText: "支付遇见问题",
        confirmButtonText: "已支付成功",
        showClose: false,
      });
},

在open函数中,我们先定义一个定时器timer,先在data中定义为null,然后根据返回的结果判断是否跳转到支付成功页面。

 if (!this.timer) {
        this.timer = setInterval(async () => {
          // 发请求获取用户支付状态
          let result = await this.$API.reqPayStatus(this.orderId);
          if (result.code == 200) {
            // 第一步,清除定时器
            clearInterval(this.timer);
            this.timer = null;
            // 保存支付成功返回的code
            this.code = result.code;
            // 关闭弹出框
            this.$msgbox.close();
            //跳转到下一路由
            this.$router.push("/paysuccess");
          }
        }, 1000);
     }

弹出窗口按钮的配置

 23个人中心

这里我们来建立二级路由

首先引入二级路由

import Center from '@/pages/Center'

// 引入二级路由
import MyOrder from '@/pages/Center/myOrder'
import GroupOrder from '@/pages/Center/groupOrder'

引入二级路由的组件

export default [
    {
        // 路由路径都是小写
        path: "/center",
        component: Center,
        meta: {
            show: true
        },
        // 引入二级组件路由
        children: [
            {
                // 不用写/
                path: 'myorder',
                component: MyOrder
            },
            {
                path: 'groupOrder',
                component: GroupOrder
            }
        ],

    },

  在vue文件中跳转

<router-link to="/center/myorder">我的订单</router-link>
<router-link to="/center/grouporder">团购订单</router-link>

并且写路由文件的出口

 <router-view></router-view>

 当进入center路由时,我们可以规定一开始就展示myorder组件,可以使用重定向

后面一些功能就很类似了。

接下来讲一讲性能的优化

24图片懒加载vue-lazyload

直接搜索vue-lazyload,并安装cnpm i vue-lazyload

首先引入插件并注册,还有定义好懒加载的图片是什么(图片和json都是暴露的,不需要手动去暴露)

import james from '@/assets/images/james.gif'
import VueLazyload from 'vue-lazyload'

Vue.use(VueLazyload, {
  // 懒加载的图片
  loading: james
})

使用v-lazy标签

 

 25:表单验证

大佬们都推荐用element-ui的from表单验证

elemen-ui表单验证

26:路由懒加载

当打包构建应用时,JavaScript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就会更加高效。

vue官网路由懒加载

//普通形式
// import Home from '@/pages/Home'

// 懒加载形式
const foo = () =>  import('@/pages/Home')


 {
        path: "/home",
        //普通形式
        // component: Home,

        //懒加载
        component: foo,
        meta: {
            show: true
        }
    },

再升级,最懒的

 {
        path: "/home",
        //最懒的
        component: () => import('@/pages/Home'),
        meta: {
            show: true
        }
    },

27打包文件(前台项目完结撒花!!!)

 

 项目打包后,代码都是经过压缩加密的,如果运行时报错,输出的错误信息无法准确得知是哪里的代码报错。有了map就可以像未加密的代码一样,准确的输出是哪一行哪一列有错。

但是我们项目发布后不需要知道哪里出错了,所以不需要map文件。

我们可以在vue.config.js中添加如下代码

productionSourceMap:false;

28:购买服务器,发布项目

由于囊中羞涩,省略该步骤。。。

  • 4
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值